From ccb76696c3dde3fd2c9cdca36537019faabff37a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 17 Oct 2023 11:06:14 +0200 Subject: [PATCH 0001/1012] Initial commit --- .gitignore | 8 ++++++++ README.md | 25 +++++++++++++++++++++++++ go.mod | 3 +++ main.go | 7 +++++++ 4 files changed, 43 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 go.mod create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..624a1b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +* +!*/ +!*.q +!*.go +!*.mod +!*.sum +!*.md +!.gitignore diff --git a/README.md b/README.md new file mode 100644 index 0000000..860cdd4 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# q + +A simple programming language. + +## Installation + +```shell +git clone https://git.akyoto.dev/cli/q +cd q +go build +``` + +## Features + +* Fast compilation +* High performance +* Small binaries + +## License + +Please see the [license documentation](https://akyoto.dev/license). + +## Copyright + +© 2023 Eduard Urbach diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2c2b156 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.akyoto.dev/cli/q + +go 1.21 diff --git a/main.go b/main.go new file mode 100644 index 0000000..d2013bb --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "os" + +func main() { + os.Exit(0) +} From 19d23eeec4773d6bb8108a0c3a0b1351d94d630f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 17 Oct 2023 11:06:14 +0200 Subject: [PATCH 0002/1012] Initial commit --- .gitignore | 8 ++++++++ README.md | 25 +++++++++++++++++++++++++ go.mod | 3 +++ main.go | 7 +++++++ 4 files changed, 43 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 go.mod create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..624a1b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +* +!*/ +!*.q +!*.go +!*.mod +!*.sum +!*.md +!.gitignore diff --git a/README.md b/README.md new file mode 100644 index 0000000..860cdd4 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# q + +A simple programming language. + +## Installation + +```shell +git clone https://git.akyoto.dev/cli/q +cd q +go build +``` + +## Features + +* Fast compilation +* High performance +* Small binaries + +## License + +Please see the [license documentation](https://akyoto.dev/license). + +## Copyright + +© 2023 Eduard Urbach diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2c2b156 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.akyoto.dev/cli/q + +go 1.21 diff --git a/main.go b/main.go new file mode 100644 index 0000000..d2013bb --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "os" + +func main() { + os.Exit(0) +} From 4f2bb677dc59caf7345d759a0f90bb814633cdb6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 17 Oct 2023 14:01:01 +0200 Subject: [PATCH 0003/1012] Added CLI commands --- cli/Build.go | 5 +++++ cli/Help.go | 13 +++++++++++++ cli/Main.go | 18 ++++++++++++++++++ cli/System.go | 41 +++++++++++++++++++++++++++++++++++++++++ cli_test.go | 27 +++++++++++++++++++++++++++ go.mod | 2 ++ go.sum | 2 ++ log/log.go | 14 ++++++++++++++ main.go | 8 ++++++-- main_test.go | 15 +++++++++++++++ 10 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 cli/Build.go create mode 100644 cli/Help.go create mode 100644 cli/Main.go create mode 100644 cli/System.go create mode 100644 cli_test.go create mode 100644 go.sum create mode 100644 log/log.go create mode 100644 main_test.go diff --git a/cli/Build.go b/cli/Build.go new file mode 100644 index 0000000..5c7ea6e --- /dev/null +++ b/cli/Build.go @@ -0,0 +1,5 @@ +package cli + +func Build(args []string) int { + return 0 +} diff --git a/cli/Help.go b/cli/Help.go new file mode 100644 index 0000000..464cb35 --- /dev/null +++ b/cli/Help.go @@ -0,0 +1,13 @@ +package cli + +import ( + "git.akyoto.dev/cli/q/log" +) + +func Help(args []string) int { + log.Error.Println("Usage: q [command] [options]") + log.Error.Println("") + log.Error.Println(" build [directory]") + log.Error.Println(" system") + return 1 +} diff --git a/cli/Main.go b/cli/Main.go new file mode 100644 index 0000000..d6d43c6 --- /dev/null +++ b/cli/Main.go @@ -0,0 +1,18 @@ +package cli + +func Main(args []string) int { + if len(args) == 0 { + return Help(nil) + } + + switch args[0] { + case "build": + return Build(args[1:]) + + case "system": + return System(args[1:]) + + default: + return Help(args[1:]) + } +} diff --git a/cli/System.go b/cli/System.go new file mode 100644 index 0000000..524e0fb --- /dev/null +++ b/cli/System.go @@ -0,0 +1,41 @@ +package cli + +import ( + "os" + "runtime" + + "git.akyoto.dev/cli/q/log" +) + +func System(args []string) int { + line := "%-19s%s\n" + + // Platform + log.Info.Printf(line, "Platform:", runtime.GOOS) + + // Architecture + log.Info.Printf(line, "Architecture:", runtime.GOARCH) + + // Go + log.Info.Printf(line, "Go:", runtime.Version()) + + // Directory + directory, err := os.Getwd() + + if err == nil { + log.Info.Printf(line, "Directory:", directory) + } else { + log.Info.Printf(line, "Directory:", err.Error()) + } + + // Compiler + executable, err := os.Executable() + + if err == nil { + log.Info.Printf(line, "Compiler:", executable) + } else { + log.Info.Printf(line, "Compiler:", err.Error()) + } + + return 0 +} diff --git a/cli_test.go b/cli_test.go new file mode 100644 index 0000000..b99942f --- /dev/null +++ b/cli_test.go @@ -0,0 +1,27 @@ +package main_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/cli" + "git.akyoto.dev/go/assert" +) + +func TestCLI(t *testing.T) { + type cliTest struct { + arguments []string + expectedExitCode int + } + + tests := []cliTest{ + {[]string{}, 1}, + {[]string{"invalid"}, 1}, + {[]string{"system"}, 0}, + } + + for _, test := range tests { + exitCode := cli.Main(test.arguments) + t.Log(test.arguments) + assert.Equal(t, exitCode, test.expectedExitCode) + } +} diff --git a/go.mod b/go.mod index 2c2b156..e78e48b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module git.akyoto.dev/cli/q go 1.21 + +require git.akyoto.dev/go/assert v0.1.3 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9fc2547 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= +git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= diff --git a/log/log.go b/log/log.go new file mode 100644 index 0000000..395f6ab --- /dev/null +++ b/log/log.go @@ -0,0 +1,14 @@ +package log + +import ( + "log" + "os" +) + +var ( + // Info is used for general info messages. + Info = log.New(os.Stdout, "", 0) + + // Error is used for error messages. + Error = log.New(os.Stderr, "", 0) +) diff --git a/main.go b/main.go index d2013bb..2a518ef 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,11 @@ package main -import "os" +import ( + "os" + + "git.akyoto.dev/cli/q/cli" +) func main() { - os.Exit(0) + os.Exit(cli.Main(os.Args[1:])) } diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..9bf72ae --- /dev/null +++ b/main_test.go @@ -0,0 +1,15 @@ +package main_test + +import ( + "io" + "os" + "testing" + + "git.akyoto.dev/cli/q/log" +) + +func TestMain(m *testing.M) { + log.Info.SetOutput(io.Discard) + log.Error.SetOutput(io.Discard) + os.Exit(m.Run()) +} From c3925e86b3e7159586434577c3173e1b2a8693fd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 17 Oct 2023 14:01:01 +0200 Subject: [PATCH 0004/1012] Added CLI commands --- cli/Build.go | 5 +++++ cli/Help.go | 13 +++++++++++++ cli/Main.go | 18 ++++++++++++++++++ cli/System.go | 41 +++++++++++++++++++++++++++++++++++++++++ cli_test.go | 27 +++++++++++++++++++++++++++ go.mod | 2 ++ go.sum | 2 ++ log/log.go | 14 ++++++++++++++ main.go | 8 ++++++-- main_test.go | 15 +++++++++++++++ 10 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 cli/Build.go create mode 100644 cli/Help.go create mode 100644 cli/Main.go create mode 100644 cli/System.go create mode 100644 cli_test.go create mode 100644 go.sum create mode 100644 log/log.go create mode 100644 main_test.go diff --git a/cli/Build.go b/cli/Build.go new file mode 100644 index 0000000..5c7ea6e --- /dev/null +++ b/cli/Build.go @@ -0,0 +1,5 @@ +package cli + +func Build(args []string) int { + return 0 +} diff --git a/cli/Help.go b/cli/Help.go new file mode 100644 index 0000000..464cb35 --- /dev/null +++ b/cli/Help.go @@ -0,0 +1,13 @@ +package cli + +import ( + "git.akyoto.dev/cli/q/log" +) + +func Help(args []string) int { + log.Error.Println("Usage: q [command] [options]") + log.Error.Println("") + log.Error.Println(" build [directory]") + log.Error.Println(" system") + return 1 +} diff --git a/cli/Main.go b/cli/Main.go new file mode 100644 index 0000000..d6d43c6 --- /dev/null +++ b/cli/Main.go @@ -0,0 +1,18 @@ +package cli + +func Main(args []string) int { + if len(args) == 0 { + return Help(nil) + } + + switch args[0] { + case "build": + return Build(args[1:]) + + case "system": + return System(args[1:]) + + default: + return Help(args[1:]) + } +} diff --git a/cli/System.go b/cli/System.go new file mode 100644 index 0000000..524e0fb --- /dev/null +++ b/cli/System.go @@ -0,0 +1,41 @@ +package cli + +import ( + "os" + "runtime" + + "git.akyoto.dev/cli/q/log" +) + +func System(args []string) int { + line := "%-19s%s\n" + + // Platform + log.Info.Printf(line, "Platform:", runtime.GOOS) + + // Architecture + log.Info.Printf(line, "Architecture:", runtime.GOARCH) + + // Go + log.Info.Printf(line, "Go:", runtime.Version()) + + // Directory + directory, err := os.Getwd() + + if err == nil { + log.Info.Printf(line, "Directory:", directory) + } else { + log.Info.Printf(line, "Directory:", err.Error()) + } + + // Compiler + executable, err := os.Executable() + + if err == nil { + log.Info.Printf(line, "Compiler:", executable) + } else { + log.Info.Printf(line, "Compiler:", err.Error()) + } + + return 0 +} diff --git a/cli_test.go b/cli_test.go new file mode 100644 index 0000000..b99942f --- /dev/null +++ b/cli_test.go @@ -0,0 +1,27 @@ +package main_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/cli" + "git.akyoto.dev/go/assert" +) + +func TestCLI(t *testing.T) { + type cliTest struct { + arguments []string + expectedExitCode int + } + + tests := []cliTest{ + {[]string{}, 1}, + {[]string{"invalid"}, 1}, + {[]string{"system"}, 0}, + } + + for _, test := range tests { + exitCode := cli.Main(test.arguments) + t.Log(test.arguments) + assert.Equal(t, exitCode, test.expectedExitCode) + } +} diff --git a/go.mod b/go.mod index 2c2b156..e78e48b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module git.akyoto.dev/cli/q go 1.21 + +require git.akyoto.dev/go/assert v0.1.3 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9fc2547 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= +git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= diff --git a/log/log.go b/log/log.go new file mode 100644 index 0000000..395f6ab --- /dev/null +++ b/log/log.go @@ -0,0 +1,14 @@ +package log + +import ( + "log" + "os" +) + +var ( + // Info is used for general info messages. + Info = log.New(os.Stdout, "", 0) + + // Error is used for error messages. + Error = log.New(os.Stderr, "", 0) +) diff --git a/main.go b/main.go index d2013bb..2a518ef 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,11 @@ package main -import "os" +import ( + "os" + + "git.akyoto.dev/cli/q/cli" +) func main() { - os.Exit(0) + os.Exit(cli.Main(os.Args[1:])) } diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..9bf72ae --- /dev/null +++ b/main_test.go @@ -0,0 +1,15 @@ +package main_test + +import ( + "io" + "os" + "testing" + + "git.akyoto.dev/cli/q/log" +) + +func TestMain(m *testing.M) { + log.Info.SetOutput(io.Discard) + log.Error.SetOutput(io.Discard) + os.Exit(m.Run()) +} From 56bb014b517592bb80a6747d5edfe5f069ec25de Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 17 Oct 2023 15:10:35 +0200 Subject: [PATCH 0005/1012] Added build command --- README.md | 14 ++++++++---- build/Build.go | 34 ++++++++++++++++++++++++++++ cli/Build.go | 50 ++++++++++++++++++++++++++++++++++++++++++ cli/Help.go | 2 +- cli/System.go | 5 ----- cli_test.go | 7 ++++-- examples/hello/hello.q | 3 +++ 7 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 build/Build.go create mode 100644 examples/hello/hello.q diff --git a/README.md b/README.md index 860cdd4..99d512d 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@ A simple programming language. +## Features + +* Fast compilation +* High performance +* Small binaries + ## Installation ```shell @@ -10,11 +16,11 @@ cd q go build ``` -## Features +## Usage -* Fast compilation -* High performance -* Small binaries +```shell +q build +``` ## License diff --git a/build/Build.go b/build/Build.go new file mode 100644 index 0000000..9a838b9 --- /dev/null +++ b/build/Build.go @@ -0,0 +1,34 @@ +package build + +import "path/filepath" + +// Build describes a compiler build. +type Build struct { + ExecutableName string + ExecutablePath string + WriteExecutable bool +} + +// New creates a new build. +func New(directory string) (*Build, error) { + directory, err := filepath.Abs(directory) + + if err != nil { + return nil, err + } + + executableName := filepath.Base(directory) + + build := &Build{ + ExecutableName: executableName, + ExecutablePath: filepath.Join(directory, executableName), + WriteExecutable: true, + } + + return build, nil +} + +// Run parses the input files and generates an executable file. +func (build *Build) Run() error { + return nil +} diff --git a/cli/Build.go b/cli/Build.go index 5c7ea6e..19f7544 100644 --- a/cli/Build.go +++ b/cli/Build.go @@ -1,5 +1,55 @@ package cli +import ( + "os" + + "git.akyoto.dev/cli/q/build" + "git.akyoto.dev/cli/q/log" +) + func Build(args []string) int { + directory := "." + + if len(args) > 0 { + directory = args[0] + } + + stat, err := os.Stat(directory) + + if err != nil { + log.Error.Println(err) + return 1 + } + + if !stat.IsDir() { + log.Error.Println("Build path must be a directory") + return 2 + } + + b, err := build.New(directory) + + if err != nil { + log.Error.Println(err) + return 1 + } + + for i := 1; i < len(args); i++ { + switch args[i] { + case "--dry": + b.WriteExecutable = false + + default: + log.Error.Printf("Unknown parameter: %s\n", args[i]) + return 2 + } + } + + err = b.Run() + + if err != nil { + log.Error.Println(err) + return 1 + } + return 0 } diff --git a/cli/Help.go b/cli/Help.go index 464cb35..87da5c3 100644 --- a/cli/Help.go +++ b/cli/Help.go @@ -9,5 +9,5 @@ func Help(args []string) int { log.Error.Println("") log.Error.Println(" build [directory]") log.Error.Println(" system") - return 1 + return 2 } diff --git a/cli/System.go b/cli/System.go index 524e0fb..4bde621 100644 --- a/cli/System.go +++ b/cli/System.go @@ -10,13 +10,8 @@ import ( func System(args []string) int { line := "%-19s%s\n" - // Platform log.Info.Printf(line, "Platform:", runtime.GOOS) - - // Architecture log.Info.Printf(line, "Architecture:", runtime.GOARCH) - - // Go log.Info.Printf(line, "Go:", runtime.Version()) // Directory diff --git a/cli_test.go b/cli_test.go index b99942f..c1aef37 100644 --- a/cli_test.go +++ b/cli_test.go @@ -14,9 +14,12 @@ func TestCLI(t *testing.T) { } tests := []cliTest{ - {[]string{}, 1}, - {[]string{"invalid"}, 1}, + {[]string{}, 2}, + {[]string{"invalid"}, 2}, {[]string{"system"}, 0}, + {[]string{"build", "non-existing-directory"}, 1}, + {[]string{"build", "examples/hello/hello.q"}, 2}, + {[]string{"build", "examples/hello", "--invalid"}, 2}, } for _, test := range tests { diff --git a/examples/hello/hello.q b/examples/hello/hello.q new file mode 100644 index 0000000..d974d48 --- /dev/null +++ b/examples/hello/hello.q @@ -0,0 +1,3 @@ +main() { + +} From cae6696c3e8a8ef76ff76ad71bf4ed43cae8a09a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 17 Oct 2023 15:10:35 +0200 Subject: [PATCH 0006/1012] Added build command --- README.md | 14 ++++++++---- build/Build.go | 34 ++++++++++++++++++++++++++++ cli/Build.go | 50 ++++++++++++++++++++++++++++++++++++++++++ cli/Help.go | 2 +- cli/System.go | 5 ----- cli_test.go | 7 ++++-- examples/hello/hello.q | 3 +++ 7 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 build/Build.go create mode 100644 examples/hello/hello.q diff --git a/README.md b/README.md index 860cdd4..99d512d 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@ A simple programming language. +## Features + +* Fast compilation +* High performance +* Small binaries + ## Installation ```shell @@ -10,11 +16,11 @@ cd q go build ``` -## Features +## Usage -* Fast compilation -* High performance -* Small binaries +```shell +q build +``` ## License diff --git a/build/Build.go b/build/Build.go new file mode 100644 index 0000000..9a838b9 --- /dev/null +++ b/build/Build.go @@ -0,0 +1,34 @@ +package build + +import "path/filepath" + +// Build describes a compiler build. +type Build struct { + ExecutableName string + ExecutablePath string + WriteExecutable bool +} + +// New creates a new build. +func New(directory string) (*Build, error) { + directory, err := filepath.Abs(directory) + + if err != nil { + return nil, err + } + + executableName := filepath.Base(directory) + + build := &Build{ + ExecutableName: executableName, + ExecutablePath: filepath.Join(directory, executableName), + WriteExecutable: true, + } + + return build, nil +} + +// Run parses the input files and generates an executable file. +func (build *Build) Run() error { + return nil +} diff --git a/cli/Build.go b/cli/Build.go index 5c7ea6e..19f7544 100644 --- a/cli/Build.go +++ b/cli/Build.go @@ -1,5 +1,55 @@ package cli +import ( + "os" + + "git.akyoto.dev/cli/q/build" + "git.akyoto.dev/cli/q/log" +) + func Build(args []string) int { + directory := "." + + if len(args) > 0 { + directory = args[0] + } + + stat, err := os.Stat(directory) + + if err != nil { + log.Error.Println(err) + return 1 + } + + if !stat.IsDir() { + log.Error.Println("Build path must be a directory") + return 2 + } + + b, err := build.New(directory) + + if err != nil { + log.Error.Println(err) + return 1 + } + + for i := 1; i < len(args); i++ { + switch args[i] { + case "--dry": + b.WriteExecutable = false + + default: + log.Error.Printf("Unknown parameter: %s\n", args[i]) + return 2 + } + } + + err = b.Run() + + if err != nil { + log.Error.Println(err) + return 1 + } + return 0 } diff --git a/cli/Help.go b/cli/Help.go index 464cb35..87da5c3 100644 --- a/cli/Help.go +++ b/cli/Help.go @@ -9,5 +9,5 @@ func Help(args []string) int { log.Error.Println("") log.Error.Println(" build [directory]") log.Error.Println(" system") - return 1 + return 2 } diff --git a/cli/System.go b/cli/System.go index 524e0fb..4bde621 100644 --- a/cli/System.go +++ b/cli/System.go @@ -10,13 +10,8 @@ import ( func System(args []string) int { line := "%-19s%s\n" - // Platform log.Info.Printf(line, "Platform:", runtime.GOOS) - - // Architecture log.Info.Printf(line, "Architecture:", runtime.GOARCH) - - // Go log.Info.Printf(line, "Go:", runtime.Version()) // Directory diff --git a/cli_test.go b/cli_test.go index b99942f..c1aef37 100644 --- a/cli_test.go +++ b/cli_test.go @@ -14,9 +14,12 @@ func TestCLI(t *testing.T) { } tests := []cliTest{ - {[]string{}, 1}, - {[]string{"invalid"}, 1}, + {[]string{}, 2}, + {[]string{"invalid"}, 2}, {[]string{"system"}, 0}, + {[]string{"build", "non-existing-directory"}, 1}, + {[]string{"build", "examples/hello/hello.q"}, 2}, + {[]string{"build", "examples/hello", "--invalid"}, 2}, } for _, test := range tests { diff --git a/examples/hello/hello.q b/examples/hello/hello.q new file mode 100644 index 0000000..d974d48 --- /dev/null +++ b/examples/hello/hello.q @@ -0,0 +1,3 @@ +main() { + +} From 5fc2911523322544fe01bcbc491d3c43d94f5c0b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 17 Oct 2023 20:29:36 +0200 Subject: [PATCH 0007/1012] Implemented ELF header --- README.md | 2 +- build/Build.go | 40 +++++++++++++++++++++++++++++- build/elf/Header.go | 32 ++++++++++++++++++++++++ build/elf/ProgramHeader.go | 37 +++++++++++++++++++++++++++ build/elf/SectionHeader.go | 45 +++++++++++++++++++++++++++++++++ build/elf/elf.go | 51 ++++++++++++++++++++++++++++++++++++++ build/elf/elf.md | 12 +++++++++ 7 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 build/elf/Header.go create mode 100644 build/elf/ProgramHeader.go create mode 100644 build/elf/SectionHeader.go create mode 100644 build/elf/elf.go create mode 100644 build/elf/elf.md diff --git a/README.md b/README.md index 99d512d..37b03bc 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ go build ## Usage ```shell -q build +./q build examples/hello ``` ## License diff --git a/build/Build.go b/build/Build.go index 9a838b9..338c5be 100644 --- a/build/Build.go +++ b/build/Build.go @@ -1,6 +1,12 @@ package build -import "path/filepath" +import ( + "bufio" + "os" + "path/filepath" + + "git.akyoto.dev/cli/q/build/elf" +) // Build describes a compiler build. type Build struct { @@ -30,5 +36,37 @@ func New(directory string) (*Build, error) { // Run parses the input files and generates an executable file. func (build *Build) Run() error { + if build.WriteExecutable { + sampleCode := []byte{ + 0xb8, 0x3c, 0x00, 0x00, 0x00, // mov rax, 60 + 0xbf, 0x00, 0x00, 0x00, 0x00, // mov rdi, 0 + 0x0f, 0x05, // syscall + } + + return writeToDisk(build.ExecutablePath, sampleCode, nil) + } + return nil } + +// writeToDisk writes the executable file to disk. +func writeToDisk(filePath string, code []byte, data []byte) error { + file, err := os.Create(filePath) + + if err != nil { + return err + } + + buffer := bufio.NewWriter(file) + executable := elf.New() + executable.Write(buffer) + buffer.Flush() + + err = file.Close() + + if err != nil { + return err + } + + return os.Chmod(filePath, 0755) +} diff --git a/build/elf/Header.go b/build/elf/Header.go new file mode 100644 index 0000000..672f065 --- /dev/null +++ b/build/elf/Header.go @@ -0,0 +1,32 @@ +package elf + +const ( + LittleEndian = 1 + TypeExecutable = 2 + ArchitectureAMD64 = 0x3E + HeaderSize = 64 +) + +// Header contains general information. +type Header struct { + Magic [4]byte + Class byte + Endianness byte + Version byte + OSABI byte + ABIVersion byte + _ [7]byte + Type int16 + Architecture int16 + FileVersion int32 + EntryPointInMemory int64 + ProgramHeaderOffset int64 + SectionHeaderOffset int64 + Flags int32 + Size int16 + ProgramHeaderEntrySize int16 + ProgramHeaderEntryCount int16 + SectionHeaderEntrySize int16 + SectionHeaderEntryCount int16 + SectionNameStringTableIndex int16 +} diff --git a/build/elf/ProgramHeader.go b/build/elf/ProgramHeader.go new file mode 100644 index 0000000..cef76c7 --- /dev/null +++ b/build/elf/ProgramHeader.go @@ -0,0 +1,37 @@ +package elf + +// ProgramHeaderSize is equal to the size of a program header in bytes. +const ProgramHeaderSize = 56 + +// ProgramHeader points to the executable part of our program. +type ProgramHeader struct { + Type ProgramType + Flags ProgramFlags + Offset int64 + VirtualAddress int64 + PhysicalAddress int64 + SizeInFile int64 + SizeInMemory int64 + Align int64 +} + +type ProgramType int32 + +const ( + ProgramTypeNULL ProgramType = 0 + ProgramTypeLOAD ProgramType = 1 + ProgramTypeDYNAMIC ProgramType = 2 + ProgramTypeINTERP ProgramType = 3 + ProgramTypeNOTE ProgramType = 4 + ProgramTypeSHLIB ProgramType = 5 + ProgramTypePHDR ProgramType = 6 + ProgramTypeTLS ProgramType = 7 +) + +type ProgramFlags int32 + +const ( + ProgramFlagsExecutable ProgramFlags = 0x1 + ProgramFlagsWritable ProgramFlags = 0x2 + ProgramFlagsReadable ProgramFlags = 0x4 +) diff --git a/build/elf/SectionHeader.go b/build/elf/SectionHeader.go new file mode 100644 index 0000000..4745796 --- /dev/null +++ b/build/elf/SectionHeader.go @@ -0,0 +1,45 @@ +package elf + +// SectionHeaderSize is equal to the size of a section header in bytes. +const SectionHeaderSize = 64 + +// SectionHeader points to the data sections of our program. +type SectionHeader struct { + NameIndex int32 + Type SectionType + Flags SectionFlags + VirtualAddress int64 + Offset int64 + SizeInFile int64 + Link int32 + Info int32 + Align int64 + EntrySize int64 +} + +type SectionType int32 + +const ( + SectionTypeNULL SectionType = 0 + SectionTypePROGBITS SectionType = 1 + SectionTypeSYMTAB SectionType = 2 + SectionTypeSTRTAB SectionType = 3 + SectionTypeRELA SectionType = 4 + SectionTypeHASH SectionType = 5 + SectionTypeDYNAMIC SectionType = 6 + SectionTypeNOTE SectionType = 7 + SectionTypeNOBITS SectionType = 8 + SectionTypeREL SectionType = 9 + SectionTypeSHLIB SectionType = 10 + SectionTypeDYNSYM SectionType = 11 +) + +type SectionFlags int64 + +const ( + SectionFlagsWritable SectionFlags = 1 << 0 + SectionFlagsAllocate SectionFlags = 1 << 1 + SectionFlagsExecutable SectionFlags = 1 << 2 + SectionFlagsStrings SectionFlags = 1 << 5 + SectionFlagsTLS SectionFlags = 1 << 10 +) diff --git a/build/elf/elf.go b/build/elf/elf.go new file mode 100644 index 0000000..68f7bab --- /dev/null +++ b/build/elf/elf.go @@ -0,0 +1,51 @@ +package elf + +import ( + "encoding/binary" + "io" +) + +const ( + baseAddress = 0x400000 + programAlign = 16 + sectionAlign = 16 +) + +// ELF64 represents an ELF 64-bit file. +type ELF64 struct { + Header +} + +// New creates a new 64-bit ELF binary. +func New() *ELF64 { + elf := &ELF64{ + Header: Header{ + Magic: [4]byte{0x7F, 'E', 'L', 'F'}, + Class: 2, + Endianness: LittleEndian, + Version: 1, + OSABI: 0, + ABIVersion: 0, + Type: TypeExecutable, + Architecture: ArchitectureAMD64, + FileVersion: 1, + EntryPointInMemory: 0, + ProgramHeaderOffset: HeaderSize, + SectionHeaderOffset: 0, // TODO + Flags: 0, + Size: HeaderSize, + ProgramHeaderEntrySize: ProgramHeaderSize, + ProgramHeaderEntryCount: 0, + SectionHeaderEntrySize: SectionHeaderSize, + SectionHeaderEntryCount: 0, + SectionNameStringTableIndex: 0, + }, + } + + return elf +} + +// Write writes the ELF64 format to the given writer. +func (elf *ELF64) Write(writer io.Writer) { + binary.Write(writer, binary.LittleEndian, &elf.Header) +} diff --git a/build/elf/elf.md b/build/elf/elf.md new file mode 100644 index 0000000..dd0de12 --- /dev/null +++ b/build/elf/elf.md @@ -0,0 +1,12 @@ +# ELF + +## File contents + +- ELF header +- Program headers +- Sections +- Section headers + +## Entry point + +The entry point is defined in the first 64 bytes (ELF header). From 4554fb82cbd43482dacb70eef34d040892aa3499 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 17 Oct 2023 20:29:36 +0200 Subject: [PATCH 0008/1012] Implemented ELF header --- README.md | 2 +- build/Build.go | 40 +++++++++++++++++++++++++++++- build/elf/Header.go | 32 ++++++++++++++++++++++++ build/elf/ProgramHeader.go | 37 +++++++++++++++++++++++++++ build/elf/SectionHeader.go | 45 +++++++++++++++++++++++++++++++++ build/elf/elf.go | 51 ++++++++++++++++++++++++++++++++++++++ build/elf/elf.md | 12 +++++++++ 7 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 build/elf/Header.go create mode 100644 build/elf/ProgramHeader.go create mode 100644 build/elf/SectionHeader.go create mode 100644 build/elf/elf.go create mode 100644 build/elf/elf.md diff --git a/README.md b/README.md index 99d512d..37b03bc 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ go build ## Usage ```shell -q build +./q build examples/hello ``` ## License diff --git a/build/Build.go b/build/Build.go index 9a838b9..338c5be 100644 --- a/build/Build.go +++ b/build/Build.go @@ -1,6 +1,12 @@ package build -import "path/filepath" +import ( + "bufio" + "os" + "path/filepath" + + "git.akyoto.dev/cli/q/build/elf" +) // Build describes a compiler build. type Build struct { @@ -30,5 +36,37 @@ func New(directory string) (*Build, error) { // Run parses the input files and generates an executable file. func (build *Build) Run() error { + if build.WriteExecutable { + sampleCode := []byte{ + 0xb8, 0x3c, 0x00, 0x00, 0x00, // mov rax, 60 + 0xbf, 0x00, 0x00, 0x00, 0x00, // mov rdi, 0 + 0x0f, 0x05, // syscall + } + + return writeToDisk(build.ExecutablePath, sampleCode, nil) + } + return nil } + +// writeToDisk writes the executable file to disk. +func writeToDisk(filePath string, code []byte, data []byte) error { + file, err := os.Create(filePath) + + if err != nil { + return err + } + + buffer := bufio.NewWriter(file) + executable := elf.New() + executable.Write(buffer) + buffer.Flush() + + err = file.Close() + + if err != nil { + return err + } + + return os.Chmod(filePath, 0755) +} diff --git a/build/elf/Header.go b/build/elf/Header.go new file mode 100644 index 0000000..672f065 --- /dev/null +++ b/build/elf/Header.go @@ -0,0 +1,32 @@ +package elf + +const ( + LittleEndian = 1 + TypeExecutable = 2 + ArchitectureAMD64 = 0x3E + HeaderSize = 64 +) + +// Header contains general information. +type Header struct { + Magic [4]byte + Class byte + Endianness byte + Version byte + OSABI byte + ABIVersion byte + _ [7]byte + Type int16 + Architecture int16 + FileVersion int32 + EntryPointInMemory int64 + ProgramHeaderOffset int64 + SectionHeaderOffset int64 + Flags int32 + Size int16 + ProgramHeaderEntrySize int16 + ProgramHeaderEntryCount int16 + SectionHeaderEntrySize int16 + SectionHeaderEntryCount int16 + SectionNameStringTableIndex int16 +} diff --git a/build/elf/ProgramHeader.go b/build/elf/ProgramHeader.go new file mode 100644 index 0000000..cef76c7 --- /dev/null +++ b/build/elf/ProgramHeader.go @@ -0,0 +1,37 @@ +package elf + +// ProgramHeaderSize is equal to the size of a program header in bytes. +const ProgramHeaderSize = 56 + +// ProgramHeader points to the executable part of our program. +type ProgramHeader struct { + Type ProgramType + Flags ProgramFlags + Offset int64 + VirtualAddress int64 + PhysicalAddress int64 + SizeInFile int64 + SizeInMemory int64 + Align int64 +} + +type ProgramType int32 + +const ( + ProgramTypeNULL ProgramType = 0 + ProgramTypeLOAD ProgramType = 1 + ProgramTypeDYNAMIC ProgramType = 2 + ProgramTypeINTERP ProgramType = 3 + ProgramTypeNOTE ProgramType = 4 + ProgramTypeSHLIB ProgramType = 5 + ProgramTypePHDR ProgramType = 6 + ProgramTypeTLS ProgramType = 7 +) + +type ProgramFlags int32 + +const ( + ProgramFlagsExecutable ProgramFlags = 0x1 + ProgramFlagsWritable ProgramFlags = 0x2 + ProgramFlagsReadable ProgramFlags = 0x4 +) diff --git a/build/elf/SectionHeader.go b/build/elf/SectionHeader.go new file mode 100644 index 0000000..4745796 --- /dev/null +++ b/build/elf/SectionHeader.go @@ -0,0 +1,45 @@ +package elf + +// SectionHeaderSize is equal to the size of a section header in bytes. +const SectionHeaderSize = 64 + +// SectionHeader points to the data sections of our program. +type SectionHeader struct { + NameIndex int32 + Type SectionType + Flags SectionFlags + VirtualAddress int64 + Offset int64 + SizeInFile int64 + Link int32 + Info int32 + Align int64 + EntrySize int64 +} + +type SectionType int32 + +const ( + SectionTypeNULL SectionType = 0 + SectionTypePROGBITS SectionType = 1 + SectionTypeSYMTAB SectionType = 2 + SectionTypeSTRTAB SectionType = 3 + SectionTypeRELA SectionType = 4 + SectionTypeHASH SectionType = 5 + SectionTypeDYNAMIC SectionType = 6 + SectionTypeNOTE SectionType = 7 + SectionTypeNOBITS SectionType = 8 + SectionTypeREL SectionType = 9 + SectionTypeSHLIB SectionType = 10 + SectionTypeDYNSYM SectionType = 11 +) + +type SectionFlags int64 + +const ( + SectionFlagsWritable SectionFlags = 1 << 0 + SectionFlagsAllocate SectionFlags = 1 << 1 + SectionFlagsExecutable SectionFlags = 1 << 2 + SectionFlagsStrings SectionFlags = 1 << 5 + SectionFlagsTLS SectionFlags = 1 << 10 +) diff --git a/build/elf/elf.go b/build/elf/elf.go new file mode 100644 index 0000000..68f7bab --- /dev/null +++ b/build/elf/elf.go @@ -0,0 +1,51 @@ +package elf + +import ( + "encoding/binary" + "io" +) + +const ( + baseAddress = 0x400000 + programAlign = 16 + sectionAlign = 16 +) + +// ELF64 represents an ELF 64-bit file. +type ELF64 struct { + Header +} + +// New creates a new 64-bit ELF binary. +func New() *ELF64 { + elf := &ELF64{ + Header: Header{ + Magic: [4]byte{0x7F, 'E', 'L', 'F'}, + Class: 2, + Endianness: LittleEndian, + Version: 1, + OSABI: 0, + ABIVersion: 0, + Type: TypeExecutable, + Architecture: ArchitectureAMD64, + FileVersion: 1, + EntryPointInMemory: 0, + ProgramHeaderOffset: HeaderSize, + SectionHeaderOffset: 0, // TODO + Flags: 0, + Size: HeaderSize, + ProgramHeaderEntrySize: ProgramHeaderSize, + ProgramHeaderEntryCount: 0, + SectionHeaderEntrySize: SectionHeaderSize, + SectionHeaderEntryCount: 0, + SectionNameStringTableIndex: 0, + }, + } + + return elf +} + +// Write writes the ELF64 format to the given writer. +func (elf *ELF64) Write(writer io.Writer) { + binary.Write(writer, binary.LittleEndian, &elf.Header) +} diff --git a/build/elf/elf.md b/build/elf/elf.md new file mode 100644 index 0000000..dd0de12 --- /dev/null +++ b/build/elf/elf.md @@ -0,0 +1,12 @@ +# ELF + +## File contents + +- ELF header +- Program headers +- Sections +- Section headers + +## Entry point + +The entry point is defined in the first 64 bytes (ELF header). From 65d794458acb2986a3b859c1f21515bc6bf24017 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 17 Oct 2023 22:08:40 +0200 Subject: [PATCH 0009/1012] Added ELF program header --- build/Build.go | 2 +- build/elf/Header.go | 1 + build/elf/elf.go | 30 +++++++++++++++++++++++------- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/build/Build.go b/build/Build.go index 338c5be..0d0b156 100644 --- a/build/Build.go +++ b/build/Build.go @@ -58,7 +58,7 @@ func writeToDisk(filePath string, code []byte, data []byte) error { } buffer := bufio.NewWriter(file) - executable := elf.New() + executable := elf.New(code) executable.Write(buffer) buffer.Flush() diff --git a/build/elf/Header.go b/build/elf/Header.go index 672f065..bdb7e24 100644 --- a/build/elf/Header.go +++ b/build/elf/Header.go @@ -4,6 +4,7 @@ const ( LittleEndian = 1 TypeExecutable = 2 ArchitectureAMD64 = 0x3E + Align = 16 HeaderSize = 64 ) diff --git a/build/elf/elf.go b/build/elf/elf.go index 68f7bab..740ab77 100644 --- a/build/elf/elf.go +++ b/build/elf/elf.go @@ -6,18 +6,19 @@ import ( ) const ( - baseAddress = 0x400000 - programAlign = 16 - sectionAlign = 16 + baseAddress = 0x10000 ) // ELF64 represents an ELF 64-bit file. type ELF64 struct { Header + ProgramHeader + Padding []byte + Code []byte } // New creates a new 64-bit ELF binary. -func New() *ELF64 { +func New(code []byte) *ELF64 { elf := &ELF64{ Header: Header{ Magic: [4]byte{0x7F, 'E', 'L', 'F'}, @@ -29,17 +30,29 @@ func New() *ELF64 { Type: TypeExecutable, Architecture: ArchitectureAMD64, FileVersion: 1, - EntryPointInMemory: 0, + EntryPointInMemory: baseAddress + 0x80, ProgramHeaderOffset: HeaderSize, - SectionHeaderOffset: 0, // TODO + SectionHeaderOffset: 0, Flags: 0, Size: HeaderSize, ProgramHeaderEntrySize: ProgramHeaderSize, - ProgramHeaderEntryCount: 0, + ProgramHeaderEntryCount: 1, SectionHeaderEntrySize: SectionHeaderSize, SectionHeaderEntryCount: 0, SectionNameStringTableIndex: 0, }, + ProgramHeader: ProgramHeader{ + Type: ProgramTypeLOAD, + Flags: ProgramFlagsExecutable, + Offset: 0x80, + VirtualAddress: baseAddress + 0x80, + PhysicalAddress: baseAddress + 0x80, + SizeInFile: int64(len(code)), + SizeInMemory: int64(len(code)), + Align: Align, + }, + Padding: []byte{0, 0, 0, 0, 0, 0, 0, 0}, + Code: code, } return elf @@ -48,4 +61,7 @@ func New() *ELF64 { // Write writes the ELF64 format to the given writer. func (elf *ELF64) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &elf.Header) + binary.Write(writer, binary.LittleEndian, &elf.ProgramHeader) + writer.Write(elf.Padding) + writer.Write(elf.Code) } From 8b66c9588c5cac219f3785f0e7865f8a351672a8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 17 Oct 2023 22:08:40 +0200 Subject: [PATCH 0010/1012] Added ELF program header --- build/Build.go | 2 +- build/elf/Header.go | 1 + build/elf/elf.go | 30 +++++++++++++++++++++++------- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/build/Build.go b/build/Build.go index 338c5be..0d0b156 100644 --- a/build/Build.go +++ b/build/Build.go @@ -58,7 +58,7 @@ func writeToDisk(filePath string, code []byte, data []byte) error { } buffer := bufio.NewWriter(file) - executable := elf.New() + executable := elf.New(code) executable.Write(buffer) buffer.Flush() diff --git a/build/elf/Header.go b/build/elf/Header.go index 672f065..bdb7e24 100644 --- a/build/elf/Header.go +++ b/build/elf/Header.go @@ -4,6 +4,7 @@ const ( LittleEndian = 1 TypeExecutable = 2 ArchitectureAMD64 = 0x3E + Align = 16 HeaderSize = 64 ) diff --git a/build/elf/elf.go b/build/elf/elf.go index 68f7bab..740ab77 100644 --- a/build/elf/elf.go +++ b/build/elf/elf.go @@ -6,18 +6,19 @@ import ( ) const ( - baseAddress = 0x400000 - programAlign = 16 - sectionAlign = 16 + baseAddress = 0x10000 ) // ELF64 represents an ELF 64-bit file. type ELF64 struct { Header + ProgramHeader + Padding []byte + Code []byte } // New creates a new 64-bit ELF binary. -func New() *ELF64 { +func New(code []byte) *ELF64 { elf := &ELF64{ Header: Header{ Magic: [4]byte{0x7F, 'E', 'L', 'F'}, @@ -29,17 +30,29 @@ func New() *ELF64 { Type: TypeExecutable, Architecture: ArchitectureAMD64, FileVersion: 1, - EntryPointInMemory: 0, + EntryPointInMemory: baseAddress + 0x80, ProgramHeaderOffset: HeaderSize, - SectionHeaderOffset: 0, // TODO + SectionHeaderOffset: 0, Flags: 0, Size: HeaderSize, ProgramHeaderEntrySize: ProgramHeaderSize, - ProgramHeaderEntryCount: 0, + ProgramHeaderEntryCount: 1, SectionHeaderEntrySize: SectionHeaderSize, SectionHeaderEntryCount: 0, SectionNameStringTableIndex: 0, }, + ProgramHeader: ProgramHeader{ + Type: ProgramTypeLOAD, + Flags: ProgramFlagsExecutable, + Offset: 0x80, + VirtualAddress: baseAddress + 0x80, + PhysicalAddress: baseAddress + 0x80, + SizeInFile: int64(len(code)), + SizeInMemory: int64(len(code)), + Align: Align, + }, + Padding: []byte{0, 0, 0, 0, 0, 0, 0, 0}, + Code: code, } return elf @@ -48,4 +61,7 @@ func New() *ELF64 { // Write writes the ELF64 format to the given writer. func (elf *ELF64) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &elf.Header) + binary.Write(writer, binary.LittleEndian, &elf.ProgramHeader) + writer.Write(elf.Padding) + writer.Write(elf.Code) } From ef89ce84d70410372ac236c817d65d5ff3799498 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 18 Oct 2023 17:03:31 +0200 Subject: [PATCH 0011/1012] Implemented basic data access --- build/Build.go | 12 ++++++++++-- build/elf/Header.go | 3 ++- build/elf/elf.go | 12 +++++------- build/elf/elf.md | 24 +++++++++++++++++++----- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/build/Build.go b/build/Build.go index 0d0b156..ac3198e 100644 --- a/build/Build.go +++ b/build/Build.go @@ -38,9 +38,17 @@ func New(directory string) (*Build, error) { func (build *Build) Run() error { if build.WriteExecutable { sampleCode := []byte{ - 0xb8, 0x3c, 0x00, 0x00, 0x00, // mov rax, 60 - 0xbf, 0x00, 0x00, 0x00, 0x00, // mov rdi, 0 + 0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1 + 0xbf, 0x01, 0x00, 0x00, 0x00, // mov edi, 1 + 0xbe, 0x80 + 0x22, 0x00, 0x40, 0x00, // mov esi, 0x4000A2 + 0xba, 0x06, 0x00, 0x00, 0x00, // mov edx, 6 0x0f, 0x05, // syscall + + 0xb8, 0x3c, 0x00, 0x00, 0x00, // mov eax, 60 + 0xbf, 0x00, 0x00, 0x00, 0x00, // mov edi, 0 + 0x0f, 0x05, // syscall + + 'H', 'e', 'l', 'l', 'o', '\n', } return writeToDisk(build.ExecutablePath, sampleCode, nil) diff --git a/build/elf/Header.go b/build/elf/Header.go index bdb7e24..49b231c 100644 --- a/build/elf/Header.go +++ b/build/elf/Header.go @@ -4,8 +4,9 @@ const ( LittleEndian = 1 TypeExecutable = 2 ArchitectureAMD64 = 0x3E - Align = 16 HeaderSize = 64 + CacheLineSize = 64 + Align = CacheLineSize ) // Header contains general information. diff --git a/build/elf/elf.go b/build/elf/elf.go index 740ab77..1da251e 100644 --- a/build/elf/elf.go +++ b/build/elf/elf.go @@ -6,15 +6,14 @@ import ( ) const ( - baseAddress = 0x10000 + baseAddress = 0x400000 ) // ELF64 represents an ELF 64-bit file. type ELF64 struct { Header ProgramHeader - Padding []byte - Code []byte + Code []byte } // New creates a new 64-bit ELF binary. @@ -43,7 +42,7 @@ func New(code []byte) *ELF64 { }, ProgramHeader: ProgramHeader{ Type: ProgramTypeLOAD, - Flags: ProgramFlagsExecutable, + Flags: ProgramFlagsExecutable | ProgramFlagsReadable, Offset: 0x80, VirtualAddress: baseAddress + 0x80, PhysicalAddress: baseAddress + 0x80, @@ -51,8 +50,7 @@ func New(code []byte) *ELF64 { SizeInMemory: int64(len(code)), Align: Align, }, - Padding: []byte{0, 0, 0, 0, 0, 0, 0, 0}, - Code: code, + Code: code, } return elf @@ -62,6 +60,6 @@ func New(code []byte) *ELF64 { func (elf *ELF64) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.ProgramHeader) - writer.Write(elf.Padding) + writer.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0}) writer.Write(elf.Code) } diff --git a/build/elf/elf.md b/build/elf/elf.md index dd0de12..83df35c 100644 --- a/build/elf/elf.md +++ b/build/elf/elf.md @@ -1,12 +1,26 @@ # ELF -## File contents +## Basic structure -- ELF header -- Program headers -- Sections -- Section headers +1. ELF header (0x00 - 0x40) +2. Program header (0x40 - 0x78) +3. Padding (0x78 - 0x80) +4. Machine code (0x80) ## Entry point The entry point is defined in the first 64 bytes (ELF header). + +## Base address + +The minimum base address is controlled by the `mmap` settings: + +```shell +sysctl vm.mmap_min_addr +``` + +Usually, this value is 65536 (0x1000). + +## Initialization in Linux + +See `/lib/modules/$(uname -r)/build/arch/x86/include/asm/elf.h`. From 506d1e30bf51b3c8d46759543bafb52e655d0a45 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 18 Oct 2023 17:03:31 +0200 Subject: [PATCH 0012/1012] Implemented basic data access --- build/Build.go | 12 ++++++++++-- build/elf/Header.go | 3 ++- build/elf/elf.go | 12 +++++------- build/elf/elf.md | 24 +++++++++++++++++++----- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/build/Build.go b/build/Build.go index 0d0b156..ac3198e 100644 --- a/build/Build.go +++ b/build/Build.go @@ -38,9 +38,17 @@ func New(directory string) (*Build, error) { func (build *Build) Run() error { if build.WriteExecutable { sampleCode := []byte{ - 0xb8, 0x3c, 0x00, 0x00, 0x00, // mov rax, 60 - 0xbf, 0x00, 0x00, 0x00, 0x00, // mov rdi, 0 + 0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1 + 0xbf, 0x01, 0x00, 0x00, 0x00, // mov edi, 1 + 0xbe, 0x80 + 0x22, 0x00, 0x40, 0x00, // mov esi, 0x4000A2 + 0xba, 0x06, 0x00, 0x00, 0x00, // mov edx, 6 0x0f, 0x05, // syscall + + 0xb8, 0x3c, 0x00, 0x00, 0x00, // mov eax, 60 + 0xbf, 0x00, 0x00, 0x00, 0x00, // mov edi, 0 + 0x0f, 0x05, // syscall + + 'H', 'e', 'l', 'l', 'o', '\n', } return writeToDisk(build.ExecutablePath, sampleCode, nil) diff --git a/build/elf/Header.go b/build/elf/Header.go index bdb7e24..49b231c 100644 --- a/build/elf/Header.go +++ b/build/elf/Header.go @@ -4,8 +4,9 @@ const ( LittleEndian = 1 TypeExecutable = 2 ArchitectureAMD64 = 0x3E - Align = 16 HeaderSize = 64 + CacheLineSize = 64 + Align = CacheLineSize ) // Header contains general information. diff --git a/build/elf/elf.go b/build/elf/elf.go index 740ab77..1da251e 100644 --- a/build/elf/elf.go +++ b/build/elf/elf.go @@ -6,15 +6,14 @@ import ( ) const ( - baseAddress = 0x10000 + baseAddress = 0x400000 ) // ELF64 represents an ELF 64-bit file. type ELF64 struct { Header ProgramHeader - Padding []byte - Code []byte + Code []byte } // New creates a new 64-bit ELF binary. @@ -43,7 +42,7 @@ func New(code []byte) *ELF64 { }, ProgramHeader: ProgramHeader{ Type: ProgramTypeLOAD, - Flags: ProgramFlagsExecutable, + Flags: ProgramFlagsExecutable | ProgramFlagsReadable, Offset: 0x80, VirtualAddress: baseAddress + 0x80, PhysicalAddress: baseAddress + 0x80, @@ -51,8 +50,7 @@ func New(code []byte) *ELF64 { SizeInMemory: int64(len(code)), Align: Align, }, - Padding: []byte{0, 0, 0, 0, 0, 0, 0, 0}, - Code: code, + Code: code, } return elf @@ -62,6 +60,6 @@ func New(code []byte) *ELF64 { func (elf *ELF64) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.ProgramHeader) - writer.Write(elf.Padding) + writer.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0}) writer.Write(elf.Code) } diff --git a/build/elf/elf.md b/build/elf/elf.md index dd0de12..83df35c 100644 --- a/build/elf/elf.md +++ b/build/elf/elf.md @@ -1,12 +1,26 @@ # ELF -## File contents +## Basic structure -- ELF header -- Program headers -- Sections -- Section headers +1. ELF header (0x00 - 0x40) +2. Program header (0x40 - 0x78) +3. Padding (0x78 - 0x80) +4. Machine code (0x80) ## Entry point The entry point is defined in the first 64 bytes (ELF header). + +## Base address + +The minimum base address is controlled by the `mmap` settings: + +```shell +sysctl vm.mmap_min_addr +``` + +Usually, this value is 65536 (0x1000). + +## Initialization in Linux + +See `/lib/modules/$(uname -r)/build/arch/x86/include/asm/elf.h`. From 3880a493c155ce0fee874c5640470dc948dfb213 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 19 Oct 2023 10:14:52 +0200 Subject: [PATCH 0013/1012] Minor changes --- build/Build.go | 55 +++++++++++++++++++++++++++++------------ build/elf/elf.go | 3 ++- cli/Build.go | 16 +----------- cli/Help.go | 2 +- cli/System.go | 2 +- {log => cli/log}/log.go | 0 cli_test.go | 2 +- main_test.go | 2 +- 8 files changed, 46 insertions(+), 36 deletions(-) rename {log => cli/log}/log.go (100%) diff --git a/build/Build.go b/build/Build.go index ac3198e..fbc2a56 100644 --- a/build/Build.go +++ b/build/Build.go @@ -6,6 +6,7 @@ import ( "path/filepath" "git.akyoto.dev/cli/q/build/elf" + "git.akyoto.dev/cli/q/cli/log" ) // Build describes a compiler build. @@ -23,6 +24,23 @@ func New(directory string) (*Build, error) { return nil, err } + file, err := os.Open(directory) + + if err != nil { + return nil, err + } + + defer file.Close() + files, err := file.Readdirnames(0) + + if err != nil { + return nil, err + } + + for _, name := range files { + log.Info.Println(name) + } + executableName := filepath.Base(directory) build := &Build{ @@ -36,29 +54,34 @@ func New(directory string) (*Build, error) { // Run parses the input files and generates an executable file. func (build *Build) Run() error { + code := build.Compile() + if build.WriteExecutable { - sampleCode := []byte{ - 0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1 - 0xbf, 0x01, 0x00, 0x00, 0x00, // mov edi, 1 - 0xbe, 0x80 + 0x22, 0x00, 0x40, 0x00, // mov esi, 0x4000A2 - 0xba, 0x06, 0x00, 0x00, 0x00, // mov edx, 6 - 0x0f, 0x05, // syscall - - 0xb8, 0x3c, 0x00, 0x00, 0x00, // mov eax, 60 - 0xbf, 0x00, 0x00, 0x00, 0x00, // mov edi, 0 - 0x0f, 0x05, // syscall - - 'H', 'e', 'l', 'l', 'o', '\n', - } - - return writeToDisk(build.ExecutablePath, sampleCode, nil) + return writeToDisk(build.ExecutablePath, code) } return nil } +// Compile compiles all the functions. +func (build *Build) Compile() []byte { + return []byte{ + 0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1 + 0xbf, 0x01, 0x00, 0x00, 0x00, // mov edi, 1 + 0xbe, 0xa2, 0x00, 0x40, 0x00, // mov esi, 0x4000a2 + 0xba, 0x06, 0x00, 0x00, 0x00, // mov edx, 6 + 0x0f, 0x05, // syscall + + 0xb8, 0x3c, 0x00, 0x00, 0x00, // mov eax, 60 + 0xbf, 0x00, 0x00, 0x00, 0x00, // mov edi, 0 + 0x0f, 0x05, // syscall + + 'H', 'e', 'l', 'l', 'o', '\n', + } +} + // writeToDisk writes the executable file to disk. -func writeToDisk(filePath string, code []byte, data []byte) error { +func writeToDisk(filePath string, code []byte) error { file, err := os.Create(filePath) if err != nil { diff --git a/build/elf/elf.go b/build/elf/elf.go index 1da251e..22e800e 100644 --- a/build/elf/elf.go +++ b/build/elf/elf.go @@ -6,7 +6,8 @@ import ( ) const ( - baseAddress = 0x400000 + minAddress = 0x10000 + baseAddress = 0x40 * minAddress ) // ELF64 represents an ELF 64-bit file. diff --git a/cli/Build.go b/cli/Build.go index 19f7544..e93d442 100644 --- a/cli/Build.go +++ b/cli/Build.go @@ -1,10 +1,8 @@ package cli import ( - "os" - "git.akyoto.dev/cli/q/build" - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/cli/log" ) func Build(args []string) int { @@ -14,18 +12,6 @@ func Build(args []string) int { directory = args[0] } - stat, err := os.Stat(directory) - - if err != nil { - log.Error.Println(err) - return 1 - } - - if !stat.IsDir() { - log.Error.Println("Build path must be a directory") - return 2 - } - b, err := build.New(directory) if err != nil { diff --git a/cli/Help.go b/cli/Help.go index 87da5c3..73559c9 100644 --- a/cli/Help.go +++ b/cli/Help.go @@ -1,7 +1,7 @@ package cli import ( - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/cli/log" ) func Help(args []string) int { diff --git a/cli/System.go b/cli/System.go index 4bde621..538a178 100644 --- a/cli/System.go +++ b/cli/System.go @@ -4,7 +4,7 @@ import ( "os" "runtime" - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/cli/log" ) func System(args []string) int { diff --git a/log/log.go b/cli/log/log.go similarity index 100% rename from log/log.go rename to cli/log/log.go diff --git a/cli_test.go b/cli_test.go index c1aef37..376ddd1 100644 --- a/cli_test.go +++ b/cli_test.go @@ -18,7 +18,7 @@ func TestCLI(t *testing.T) { {[]string{"invalid"}, 2}, {[]string{"system"}, 0}, {[]string{"build", "non-existing-directory"}, 1}, - {[]string{"build", "examples/hello/hello.q"}, 2}, + {[]string{"build", "examples/hello/hello.q"}, 1}, {[]string{"build", "examples/hello", "--invalid"}, 2}, } diff --git a/main_test.go b/main_test.go index 9bf72ae..d05e9a6 100644 --- a/main_test.go +++ b/main_test.go @@ -5,7 +5,7 @@ import ( "os" "testing" - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/cli/log" ) func TestMain(m *testing.M) { From aab33fe86d982046863086460b668b77bd165c53 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 19 Oct 2023 10:14:52 +0200 Subject: [PATCH 0014/1012] Minor changes --- build/Build.go | 55 +++++++++++++++++++++++++++++------------ build/elf/elf.go | 3 ++- cli/Build.go | 16 +----------- cli/Help.go | 2 +- cli/System.go | 2 +- {log => cli/log}/log.go | 0 cli_test.go | 2 +- main_test.go | 2 +- 8 files changed, 46 insertions(+), 36 deletions(-) rename {log => cli/log}/log.go (100%) diff --git a/build/Build.go b/build/Build.go index ac3198e..fbc2a56 100644 --- a/build/Build.go +++ b/build/Build.go @@ -6,6 +6,7 @@ import ( "path/filepath" "git.akyoto.dev/cli/q/build/elf" + "git.akyoto.dev/cli/q/cli/log" ) // Build describes a compiler build. @@ -23,6 +24,23 @@ func New(directory string) (*Build, error) { return nil, err } + file, err := os.Open(directory) + + if err != nil { + return nil, err + } + + defer file.Close() + files, err := file.Readdirnames(0) + + if err != nil { + return nil, err + } + + for _, name := range files { + log.Info.Println(name) + } + executableName := filepath.Base(directory) build := &Build{ @@ -36,29 +54,34 @@ func New(directory string) (*Build, error) { // Run parses the input files and generates an executable file. func (build *Build) Run() error { + code := build.Compile() + if build.WriteExecutable { - sampleCode := []byte{ - 0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1 - 0xbf, 0x01, 0x00, 0x00, 0x00, // mov edi, 1 - 0xbe, 0x80 + 0x22, 0x00, 0x40, 0x00, // mov esi, 0x4000A2 - 0xba, 0x06, 0x00, 0x00, 0x00, // mov edx, 6 - 0x0f, 0x05, // syscall - - 0xb8, 0x3c, 0x00, 0x00, 0x00, // mov eax, 60 - 0xbf, 0x00, 0x00, 0x00, 0x00, // mov edi, 0 - 0x0f, 0x05, // syscall - - 'H', 'e', 'l', 'l', 'o', '\n', - } - - return writeToDisk(build.ExecutablePath, sampleCode, nil) + return writeToDisk(build.ExecutablePath, code) } return nil } +// Compile compiles all the functions. +func (build *Build) Compile() []byte { + return []byte{ + 0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1 + 0xbf, 0x01, 0x00, 0x00, 0x00, // mov edi, 1 + 0xbe, 0xa2, 0x00, 0x40, 0x00, // mov esi, 0x4000a2 + 0xba, 0x06, 0x00, 0x00, 0x00, // mov edx, 6 + 0x0f, 0x05, // syscall + + 0xb8, 0x3c, 0x00, 0x00, 0x00, // mov eax, 60 + 0xbf, 0x00, 0x00, 0x00, 0x00, // mov edi, 0 + 0x0f, 0x05, // syscall + + 'H', 'e', 'l', 'l', 'o', '\n', + } +} + // writeToDisk writes the executable file to disk. -func writeToDisk(filePath string, code []byte, data []byte) error { +func writeToDisk(filePath string, code []byte) error { file, err := os.Create(filePath) if err != nil { diff --git a/build/elf/elf.go b/build/elf/elf.go index 1da251e..22e800e 100644 --- a/build/elf/elf.go +++ b/build/elf/elf.go @@ -6,7 +6,8 @@ import ( ) const ( - baseAddress = 0x400000 + minAddress = 0x10000 + baseAddress = 0x40 * minAddress ) // ELF64 represents an ELF 64-bit file. diff --git a/cli/Build.go b/cli/Build.go index 19f7544..e93d442 100644 --- a/cli/Build.go +++ b/cli/Build.go @@ -1,10 +1,8 @@ package cli import ( - "os" - "git.akyoto.dev/cli/q/build" - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/cli/log" ) func Build(args []string) int { @@ -14,18 +12,6 @@ func Build(args []string) int { directory = args[0] } - stat, err := os.Stat(directory) - - if err != nil { - log.Error.Println(err) - return 1 - } - - if !stat.IsDir() { - log.Error.Println("Build path must be a directory") - return 2 - } - b, err := build.New(directory) if err != nil { diff --git a/cli/Help.go b/cli/Help.go index 87da5c3..73559c9 100644 --- a/cli/Help.go +++ b/cli/Help.go @@ -1,7 +1,7 @@ package cli import ( - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/cli/log" ) func Help(args []string) int { diff --git a/cli/System.go b/cli/System.go index 4bde621..538a178 100644 --- a/cli/System.go +++ b/cli/System.go @@ -4,7 +4,7 @@ import ( "os" "runtime" - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/cli/log" ) func System(args []string) int { diff --git a/log/log.go b/cli/log/log.go similarity index 100% rename from log/log.go rename to cli/log/log.go diff --git a/cli_test.go b/cli_test.go index c1aef37..376ddd1 100644 --- a/cli_test.go +++ b/cli_test.go @@ -18,7 +18,7 @@ func TestCLI(t *testing.T) { {[]string{"invalid"}, 2}, {[]string{"system"}, 0}, {[]string{"build", "non-existing-directory"}, 1}, - {[]string{"build", "examples/hello/hello.q"}, 2}, + {[]string{"build", "examples/hello/hello.q"}, 1}, {[]string{"build", "examples/hello", "--invalid"}, 2}, } diff --git a/main_test.go b/main_test.go index 9bf72ae..d05e9a6 100644 --- a/main_test.go +++ b/main_test.go @@ -5,7 +5,7 @@ import ( "os" "testing" - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/cli/log" ) func TestMain(m *testing.M) { From 47c2076f2e9c19a41a60739185000a7822ade9d4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 20 Oct 2023 13:22:06 +0200 Subject: [PATCH 0015/1012] Separated code and data --- build/Build.go | 77 ++++++++++++++++++---------------- build/elf/{elf.go => ELF64.go} | 17 ++++---- cli/Build.go | 10 ++--- cli/Help.go | 1 + cli/Main.go | 3 ++ cli/System.go | 1 + cli_test.go => cli/cli_test.go | 19 ++++++++- main_test.go | 15 ------- 8 files changed, 76 insertions(+), 67 deletions(-) rename build/elf/{elf.go => ELF64.go} (84%) rename cli_test.go => cli/cli_test.go (65%) delete mode 100644 main_test.go diff --git a/build/Build.go b/build/Build.go index fbc2a56..c9c6943 100644 --- a/build/Build.go +++ b/build/Build.go @@ -2,6 +2,7 @@ package build import ( "bufio" + "bytes" "os" "path/filepath" @@ -11,61 +12,62 @@ import ( // Build describes a compiler build. type Build struct { - ExecutableName string - ExecutablePath string + Name string + Directory string + Code bytes.Buffer + Data bytes.Buffer WriteExecutable bool } // New creates a new build. -func New(directory string) (*Build, error) { - directory, err := filepath.Abs(directory) +func New(directory string) *Build { + return &Build{ + Name: filepath.Base(directory), + Directory: directory, + WriteExecutable: true, + } +} + +// Run parses the input files and generates an executable file. +func (build *Build) Run() error { + err := build.Compile() if err != nil { - return nil, err + return err } - file, err := os.Open(directory) + if build.WriteExecutable { + return writeToDisk(build.Executable(), build.Code.Bytes(), build.Data.Bytes()) + } + + return nil +} + +// Executable returns the path to the executable. +func (build *Build) Executable() string { + return filepath.Join(build.Directory, build.Name) +} + +// Compile compiles all the functions. +func (build *Build) Compile() error { + file, err := os.Open(build.Directory) if err != nil { - return nil, err + return err } defer file.Close() files, err := file.Readdirnames(0) if err != nil { - return nil, err + return err } for _, name := range files { log.Info.Println(name) } - executableName := filepath.Base(directory) - - build := &Build{ - ExecutableName: executableName, - ExecutablePath: filepath.Join(directory, executableName), - WriteExecutable: true, - } - - return build, nil -} - -// Run parses the input files and generates an executable file. -func (build *Build) Run() error { - code := build.Compile() - - if build.WriteExecutable { - return writeToDisk(build.ExecutablePath, code) - } - - return nil -} - -// Compile compiles all the functions. -func (build *Build) Compile() []byte { - return []byte{ + build.Code.Write([]byte{ 0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1 0xbf, 0x01, 0x00, 0x00, 0x00, // mov edi, 1 0xbe, 0xa2, 0x00, 0x40, 0x00, // mov esi, 0x4000a2 @@ -75,13 +77,14 @@ func (build *Build) Compile() []byte { 0xb8, 0x3c, 0x00, 0x00, 0x00, // mov eax, 60 0xbf, 0x00, 0x00, 0x00, 0x00, // mov edi, 0 0x0f, 0x05, // syscall + }) - 'H', 'e', 'l', 'l', 'o', '\n', - } + build.Data.Write([]byte{'H', 'e', 'l', 'l', 'o', '\n'}) + return nil } // writeToDisk writes the executable file to disk. -func writeToDisk(filePath string, code []byte) error { +func writeToDisk(filePath string, code []byte, data []byte) error { file, err := os.Create(filePath) if err != nil { @@ -89,7 +92,7 @@ func writeToDisk(filePath string, code []byte) error { } buffer := bufio.NewWriter(file) - executable := elf.New(code) + executable := elf.New(code, data) executable.Write(buffer) buffer.Flush() diff --git a/build/elf/elf.go b/build/elf/ELF64.go similarity index 84% rename from build/elf/elf.go rename to build/elf/ELF64.go index 22e800e..30c1ce0 100644 --- a/build/elf/elf.go +++ b/build/elf/ELF64.go @@ -10,16 +10,17 @@ const ( baseAddress = 0x40 * minAddress ) -// ELF64 represents an ELF 64-bit file. -type ELF64 struct { +// ELF represents an ELF file. +type ELF struct { Header ProgramHeader Code []byte + Data []byte } -// New creates a new 64-bit ELF binary. -func New(code []byte) *ELF64 { - elf := &ELF64{ +// New creates a new ELF binary. +func New(code []byte, data []byte) *ELF { + elf := &ELF{ Header: Header{ Magic: [4]byte{0x7F, 'E', 'L', 'F'}, Class: 2, @@ -43,7 +44,7 @@ func New(code []byte) *ELF64 { }, ProgramHeader: ProgramHeader{ Type: ProgramTypeLOAD, - Flags: ProgramFlagsExecutable | ProgramFlagsReadable, + Flags: ProgramFlagsExecutable, Offset: 0x80, VirtualAddress: baseAddress + 0x80, PhysicalAddress: baseAddress + 0x80, @@ -52,15 +53,17 @@ func New(code []byte) *ELF64 { Align: Align, }, Code: code, + Data: data, } return elf } // Write writes the ELF64 format to the given writer. -func (elf *ELF64) Write(writer io.Writer) { +func (elf *ELF) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.ProgramHeader) writer.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0}) writer.Write(elf.Code) + writer.Write(elf.Data) } diff --git a/cli/Build.go b/cli/Build.go index e93d442..b579dbf 100644 --- a/cli/Build.go +++ b/cli/Build.go @@ -5,6 +5,7 @@ import ( "git.akyoto.dev/cli/q/cli/log" ) +// Build builds an executable. func Build(args []string) int { directory := "." @@ -12,12 +13,7 @@ func Build(args []string) int { directory = args[0] } - b, err := build.New(directory) - - if err != nil { - log.Error.Println(err) - return 1 - } + b := build.New(directory) for i := 1; i < len(args); i++ { switch args[i] { @@ -30,7 +26,7 @@ func Build(args []string) int { } } - err = b.Run() + err := b.Run() if err != nil { log.Error.Println(err) diff --git a/cli/Help.go b/cli/Help.go index 73559c9..61ba523 100644 --- a/cli/Help.go +++ b/cli/Help.go @@ -4,6 +4,7 @@ import ( "git.akyoto.dev/cli/q/cli/log" ) +// Help shows the command line argument usage. func Help(args []string) int { log.Error.Println("Usage: q [command] [options]") log.Error.Println("") diff --git a/cli/Main.go b/cli/Main.go index d6d43c6..e681239 100644 --- a/cli/Main.go +++ b/cli/Main.go @@ -1,5 +1,8 @@ package cli +// Main is the entry point for the CLI frontend. +// It returns the exit code of the compiler. +// We never call os.Exit directly here because it's bad for testing. func Main(args []string) int { if len(args) == 0 { return Help(nil) diff --git a/cli/System.go b/cli/System.go index 538a178..1b847aa 100644 --- a/cli/System.go +++ b/cli/System.go @@ -7,6 +7,7 @@ import ( "git.akyoto.dev/cli/q/cli/log" ) +// System shows system information. func System(args []string) int { line := "%-19s%s\n" diff --git a/cli_test.go b/cli/cli_test.go similarity index 65% rename from cli_test.go rename to cli/cli_test.go index 376ddd1..c1e5861 100644 --- a/cli_test.go +++ b/cli/cli_test.go @@ -1,12 +1,21 @@ -package main_test +package cli_test import ( + "io" + "os" "testing" "git.akyoto.dev/cli/q/cli" + "git.akyoto.dev/cli/q/cli/log" "git.akyoto.dev/go/assert" ) +func TestMain(m *testing.M) { + log.Info.SetOutput(io.Discard) + log.Error.SetOutput(io.Discard) + os.Exit(m.Run()) +} + func TestCLI(t *testing.T) { type cliTest struct { arguments []string @@ -28,3 +37,11 @@ func TestCLI(t *testing.T) { assert.Equal(t, exitCode, test.expectedExitCode) } } + +func BenchmarkBuild(b *testing.B) { + args := []string{"build", "examples/hello", "--dry"} + + for i := 0; i < b.N; i++ { + cli.Main(args) + } +} diff --git a/main_test.go b/main_test.go deleted file mode 100644 index d05e9a6..0000000 --- a/main_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package main_test - -import ( - "io" - "os" - "testing" - - "git.akyoto.dev/cli/q/cli/log" -) - -func TestMain(m *testing.M) { - log.Info.SetOutput(io.Discard) - log.Error.SetOutput(io.Discard) - os.Exit(m.Run()) -} From 0fe419da86942fadc1f5e83fd39b214e8fcbe672 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 20 Oct 2023 13:22:06 +0200 Subject: [PATCH 0016/1012] Separated code and data --- build/Build.go | 77 ++++++++++++++++++---------------- build/elf/{elf.go => ELF64.go} | 17 ++++---- cli/Build.go | 10 ++--- cli/Help.go | 1 + cli/Main.go | 3 ++ cli/System.go | 1 + cli_test.go => cli/cli_test.go | 19 ++++++++- main_test.go | 15 ------- 8 files changed, 76 insertions(+), 67 deletions(-) rename build/elf/{elf.go => ELF64.go} (84%) rename cli_test.go => cli/cli_test.go (65%) delete mode 100644 main_test.go diff --git a/build/Build.go b/build/Build.go index fbc2a56..c9c6943 100644 --- a/build/Build.go +++ b/build/Build.go @@ -2,6 +2,7 @@ package build import ( "bufio" + "bytes" "os" "path/filepath" @@ -11,61 +12,62 @@ import ( // Build describes a compiler build. type Build struct { - ExecutableName string - ExecutablePath string + Name string + Directory string + Code bytes.Buffer + Data bytes.Buffer WriteExecutable bool } // New creates a new build. -func New(directory string) (*Build, error) { - directory, err := filepath.Abs(directory) +func New(directory string) *Build { + return &Build{ + Name: filepath.Base(directory), + Directory: directory, + WriteExecutable: true, + } +} + +// Run parses the input files and generates an executable file. +func (build *Build) Run() error { + err := build.Compile() if err != nil { - return nil, err + return err } - file, err := os.Open(directory) + if build.WriteExecutable { + return writeToDisk(build.Executable(), build.Code.Bytes(), build.Data.Bytes()) + } + + return nil +} + +// Executable returns the path to the executable. +func (build *Build) Executable() string { + return filepath.Join(build.Directory, build.Name) +} + +// Compile compiles all the functions. +func (build *Build) Compile() error { + file, err := os.Open(build.Directory) if err != nil { - return nil, err + return err } defer file.Close() files, err := file.Readdirnames(0) if err != nil { - return nil, err + return err } for _, name := range files { log.Info.Println(name) } - executableName := filepath.Base(directory) - - build := &Build{ - ExecutableName: executableName, - ExecutablePath: filepath.Join(directory, executableName), - WriteExecutable: true, - } - - return build, nil -} - -// Run parses the input files and generates an executable file. -func (build *Build) Run() error { - code := build.Compile() - - if build.WriteExecutable { - return writeToDisk(build.ExecutablePath, code) - } - - return nil -} - -// Compile compiles all the functions. -func (build *Build) Compile() []byte { - return []byte{ + build.Code.Write([]byte{ 0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1 0xbf, 0x01, 0x00, 0x00, 0x00, // mov edi, 1 0xbe, 0xa2, 0x00, 0x40, 0x00, // mov esi, 0x4000a2 @@ -75,13 +77,14 @@ func (build *Build) Compile() []byte { 0xb8, 0x3c, 0x00, 0x00, 0x00, // mov eax, 60 0xbf, 0x00, 0x00, 0x00, 0x00, // mov edi, 0 0x0f, 0x05, // syscall + }) - 'H', 'e', 'l', 'l', 'o', '\n', - } + build.Data.Write([]byte{'H', 'e', 'l', 'l', 'o', '\n'}) + return nil } // writeToDisk writes the executable file to disk. -func writeToDisk(filePath string, code []byte) error { +func writeToDisk(filePath string, code []byte, data []byte) error { file, err := os.Create(filePath) if err != nil { @@ -89,7 +92,7 @@ func writeToDisk(filePath string, code []byte) error { } buffer := bufio.NewWriter(file) - executable := elf.New(code) + executable := elf.New(code, data) executable.Write(buffer) buffer.Flush() diff --git a/build/elf/elf.go b/build/elf/ELF64.go similarity index 84% rename from build/elf/elf.go rename to build/elf/ELF64.go index 22e800e..30c1ce0 100644 --- a/build/elf/elf.go +++ b/build/elf/ELF64.go @@ -10,16 +10,17 @@ const ( baseAddress = 0x40 * minAddress ) -// ELF64 represents an ELF 64-bit file. -type ELF64 struct { +// ELF represents an ELF file. +type ELF struct { Header ProgramHeader Code []byte + Data []byte } -// New creates a new 64-bit ELF binary. -func New(code []byte) *ELF64 { - elf := &ELF64{ +// New creates a new ELF binary. +func New(code []byte, data []byte) *ELF { + elf := &ELF{ Header: Header{ Magic: [4]byte{0x7F, 'E', 'L', 'F'}, Class: 2, @@ -43,7 +44,7 @@ func New(code []byte) *ELF64 { }, ProgramHeader: ProgramHeader{ Type: ProgramTypeLOAD, - Flags: ProgramFlagsExecutable | ProgramFlagsReadable, + Flags: ProgramFlagsExecutable, Offset: 0x80, VirtualAddress: baseAddress + 0x80, PhysicalAddress: baseAddress + 0x80, @@ -52,15 +53,17 @@ func New(code []byte) *ELF64 { Align: Align, }, Code: code, + Data: data, } return elf } // Write writes the ELF64 format to the given writer. -func (elf *ELF64) Write(writer io.Writer) { +func (elf *ELF) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.ProgramHeader) writer.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0}) writer.Write(elf.Code) + writer.Write(elf.Data) } diff --git a/cli/Build.go b/cli/Build.go index e93d442..b579dbf 100644 --- a/cli/Build.go +++ b/cli/Build.go @@ -5,6 +5,7 @@ import ( "git.akyoto.dev/cli/q/cli/log" ) +// Build builds an executable. func Build(args []string) int { directory := "." @@ -12,12 +13,7 @@ func Build(args []string) int { directory = args[0] } - b, err := build.New(directory) - - if err != nil { - log.Error.Println(err) - return 1 - } + b := build.New(directory) for i := 1; i < len(args); i++ { switch args[i] { @@ -30,7 +26,7 @@ func Build(args []string) int { } } - err = b.Run() + err := b.Run() if err != nil { log.Error.Println(err) diff --git a/cli/Help.go b/cli/Help.go index 73559c9..61ba523 100644 --- a/cli/Help.go +++ b/cli/Help.go @@ -4,6 +4,7 @@ import ( "git.akyoto.dev/cli/q/cli/log" ) +// Help shows the command line argument usage. func Help(args []string) int { log.Error.Println("Usage: q [command] [options]") log.Error.Println("") diff --git a/cli/Main.go b/cli/Main.go index d6d43c6..e681239 100644 --- a/cli/Main.go +++ b/cli/Main.go @@ -1,5 +1,8 @@ package cli +// Main is the entry point for the CLI frontend. +// It returns the exit code of the compiler. +// We never call os.Exit directly here because it's bad for testing. func Main(args []string) int { if len(args) == 0 { return Help(nil) diff --git a/cli/System.go b/cli/System.go index 538a178..1b847aa 100644 --- a/cli/System.go +++ b/cli/System.go @@ -7,6 +7,7 @@ import ( "git.akyoto.dev/cli/q/cli/log" ) +// System shows system information. func System(args []string) int { line := "%-19s%s\n" diff --git a/cli_test.go b/cli/cli_test.go similarity index 65% rename from cli_test.go rename to cli/cli_test.go index 376ddd1..c1e5861 100644 --- a/cli_test.go +++ b/cli/cli_test.go @@ -1,12 +1,21 @@ -package main_test +package cli_test import ( + "io" + "os" "testing" "git.akyoto.dev/cli/q/cli" + "git.akyoto.dev/cli/q/cli/log" "git.akyoto.dev/go/assert" ) +func TestMain(m *testing.M) { + log.Info.SetOutput(io.Discard) + log.Error.SetOutput(io.Discard) + os.Exit(m.Run()) +} + func TestCLI(t *testing.T) { type cliTest struct { arguments []string @@ -28,3 +37,11 @@ func TestCLI(t *testing.T) { assert.Equal(t, exitCode, test.expectedExitCode) } } + +func BenchmarkBuild(b *testing.B) { + args := []string{"build", "examples/hello", "--dry"} + + for i := 0; i < b.N; i++ { + cli.Main(args) + } +} diff --git a/main_test.go b/main_test.go deleted file mode 100644 index d05e9a6..0000000 --- a/main_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package main_test - -import ( - "io" - "os" - "testing" - - "git.akyoto.dev/cli/q/cli/log" -) - -func TestMain(m *testing.M) { - log.Info.SetOutput(io.Discard) - log.Error.SetOutput(io.Discard) - os.Exit(m.Run()) -} From 3a002a7562822188a4b71ec3b18629b05eb0395e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 20 Oct 2023 15:29:40 +0200 Subject: [PATCH 0017/1012] Implemented directory walk --- build/Build.go | 36 +++++++++-------- cli/Build.go | 2 +- cli/Help.go | 2 +- cli/System.go | 2 +- cli/cli_test.go | 2 +- directory/Walk.go | 61 +++++++++++++++++++++++++++++ {build/elf => elf}/ELF64.go | 0 {build/elf => elf}/Header.go | 0 {build/elf => elf}/ProgramHeader.go | 0 {build/elf => elf}/SectionHeader.go | 0 {build/elf => elf}/elf.md | 0 errors/InvalidPath.go | 15 +++++++ {cli/log => log}/log.go | 0 13 files changed, 100 insertions(+), 20 deletions(-) create mode 100644 directory/Walk.go rename {build/elf => elf}/ELF64.go (100%) rename {build/elf => elf}/Header.go (100%) rename {build/elf => elf}/ProgramHeader.go (100%) rename {build/elf => elf}/SectionHeader.go (100%) rename {build/elf => elf}/elf.md (100%) create mode 100644 errors/InvalidPath.go rename {cli/log => log}/log.go (100%) diff --git a/build/Build.go b/build/Build.go index c9c6943..368b711 100644 --- a/build/Build.go +++ b/build/Build.go @@ -5,9 +5,12 @@ import ( "bytes" "os" "path/filepath" + "strings" - "git.akyoto.dev/cli/q/build/elf" - "git.akyoto.dev/cli/q/cli/log" + "git.akyoto.dev/cli/q/directory" + "git.akyoto.dev/cli/q/elf" + "git.akyoto.dev/cli/q/errors" + "git.akyoto.dev/cli/q/log" ) // Build describes a compiler build. @@ -43,29 +46,25 @@ func (build *Build) Run() error { return nil } -// Executable returns the path to the executable. -func (build *Build) Executable() string { - return filepath.Join(build.Directory, build.Name) -} - // Compile compiles all the functions. func (build *Build) Compile() error { - file, err := os.Open(build.Directory) + stat, err := os.Stat(build.Directory) if err != nil { return err } - defer file.Close() - files, err := file.Readdirnames(0) - - if err != nil { - return err + if !stat.IsDir() { + return &errors.InvalidDirectory{Path: build.Directory} } - for _, name := range files { - log.Info.Println(name) - } + directory.Walk(build.Directory, func(file string) { + if !strings.HasSuffix(file, ".q") { + return + } + + log.Info.Println(file) + }) build.Code.Write([]byte{ 0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1 @@ -83,6 +82,11 @@ func (build *Build) Compile() error { return nil } +// Executable returns the path to the executable. +func (build *Build) Executable() string { + return filepath.Join(build.Directory, build.Name) +} + // writeToDisk writes the executable file to disk. func writeToDisk(filePath string, code []byte, data []byte) error { file, err := os.Create(filePath) diff --git a/cli/Build.go b/cli/Build.go index b579dbf..7c8eac5 100644 --- a/cli/Build.go +++ b/cli/Build.go @@ -2,7 +2,7 @@ package cli import ( "git.akyoto.dev/cli/q/build" - "git.akyoto.dev/cli/q/cli/log" + "git.akyoto.dev/cli/q/log" ) // Build builds an executable. diff --git a/cli/Help.go b/cli/Help.go index 61ba523..e34dc6c 100644 --- a/cli/Help.go +++ b/cli/Help.go @@ -1,7 +1,7 @@ package cli import ( - "git.akyoto.dev/cli/q/cli/log" + "git.akyoto.dev/cli/q/log" ) // Help shows the command line argument usage. diff --git a/cli/System.go b/cli/System.go index 1b847aa..36fde71 100644 --- a/cli/System.go +++ b/cli/System.go @@ -4,7 +4,7 @@ import ( "os" "runtime" - "git.akyoto.dev/cli/q/cli/log" + "git.akyoto.dev/cli/q/log" ) // System shows system information. diff --git a/cli/cli_test.go b/cli/cli_test.go index c1e5861..111df5d 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -6,7 +6,7 @@ import ( "testing" "git.akyoto.dev/cli/q/cli" - "git.akyoto.dev/cli/q/cli/log" + "git.akyoto.dev/cli/q/log" "git.akyoto.dev/go/assert" ) diff --git a/directory/Walk.go b/directory/Walk.go new file mode 100644 index 0000000..5595e3e --- /dev/null +++ b/directory/Walk.go @@ -0,0 +1,61 @@ +package directory + +import ( + "syscall" + "unsafe" +) + +const blockSize = 8 << 10 + +// Walk calls your callback function for every file name inside the directory. +// It doesn't distinguish between files and directories. +func Walk(directory string, callBack func(string)) { + fd, err := syscall.Open(directory, 0, 0) + + if err != nil { + panic(err) + } + + defer syscall.Close(fd) + buffer := make([]byte, blockSize) + + for { + n, err := syscall.ReadDirent(fd, buffer) + + if err != nil { + panic(err) + } + + if n <= 0 { + break + } + + readBuffer := buffer[:n] + + for len(readBuffer) > 0 { + dirent := (*syscall.Dirent)(unsafe.Pointer(&readBuffer[0])) + readBuffer = readBuffer[dirent.Reclen:] + + // Skip deleted files + if dirent.Ino == 0 { + continue + } + + // Skip hidden files + if dirent.Name[0] == '.' { + continue + } + + for i, c := range dirent.Name { + if c != 0 { + continue + } + + bytePointer := (*byte)(unsafe.Pointer(&dirent.Name[0])) + name := unsafe.String(bytePointer, i) + callBack(name) + break + } + } + } +} diff --git a/build/elf/ELF64.go b/elf/ELF64.go similarity index 100% rename from build/elf/ELF64.go rename to elf/ELF64.go diff --git a/build/elf/Header.go b/elf/Header.go similarity index 100% rename from build/elf/Header.go rename to elf/Header.go diff --git a/build/elf/ProgramHeader.go b/elf/ProgramHeader.go similarity index 100% rename from build/elf/ProgramHeader.go rename to elf/ProgramHeader.go diff --git a/build/elf/SectionHeader.go b/elf/SectionHeader.go similarity index 100% rename from build/elf/SectionHeader.go rename to elf/SectionHeader.go diff --git a/build/elf/elf.md b/elf/elf.md similarity index 100% rename from build/elf/elf.md rename to elf/elf.md diff --git a/errors/InvalidPath.go b/errors/InvalidPath.go new file mode 100644 index 0000000..0b71fcf --- /dev/null +++ b/errors/InvalidPath.go @@ -0,0 +1,15 @@ +package errors + +import "fmt" + +type InvalidDirectory struct { + Path string +} + +func (err *InvalidDirectory) Error() string { + if err.Path == "" { + return "Invalid directory" + } + + return fmt.Sprintf("Invalid directory '%s'", err.Path) +} diff --git a/cli/log/log.go b/log/log.go similarity index 100% rename from cli/log/log.go rename to log/log.go From 886ea27d5437a3e66cb43cbf253959e41ac452b0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 20 Oct 2023 15:29:40 +0200 Subject: [PATCH 0018/1012] Implemented directory walk --- build/Build.go | 36 +++++++++-------- cli/Build.go | 2 +- cli/Help.go | 2 +- cli/System.go | 2 +- cli/cli_test.go | 2 +- directory/Walk.go | 61 +++++++++++++++++++++++++++++ {build/elf => elf}/ELF64.go | 0 {build/elf => elf}/Header.go | 0 {build/elf => elf}/ProgramHeader.go | 0 {build/elf => elf}/SectionHeader.go | 0 {build/elf => elf}/elf.md | 0 errors/InvalidPath.go | 15 +++++++ {cli/log => log}/log.go | 0 13 files changed, 100 insertions(+), 20 deletions(-) create mode 100644 directory/Walk.go rename {build/elf => elf}/ELF64.go (100%) rename {build/elf => elf}/Header.go (100%) rename {build/elf => elf}/ProgramHeader.go (100%) rename {build/elf => elf}/SectionHeader.go (100%) rename {build/elf => elf}/elf.md (100%) create mode 100644 errors/InvalidPath.go rename {cli/log => log}/log.go (100%) diff --git a/build/Build.go b/build/Build.go index c9c6943..368b711 100644 --- a/build/Build.go +++ b/build/Build.go @@ -5,9 +5,12 @@ import ( "bytes" "os" "path/filepath" + "strings" - "git.akyoto.dev/cli/q/build/elf" - "git.akyoto.dev/cli/q/cli/log" + "git.akyoto.dev/cli/q/directory" + "git.akyoto.dev/cli/q/elf" + "git.akyoto.dev/cli/q/errors" + "git.akyoto.dev/cli/q/log" ) // Build describes a compiler build. @@ -43,29 +46,25 @@ func (build *Build) Run() error { return nil } -// Executable returns the path to the executable. -func (build *Build) Executable() string { - return filepath.Join(build.Directory, build.Name) -} - // Compile compiles all the functions. func (build *Build) Compile() error { - file, err := os.Open(build.Directory) + stat, err := os.Stat(build.Directory) if err != nil { return err } - defer file.Close() - files, err := file.Readdirnames(0) - - if err != nil { - return err + if !stat.IsDir() { + return &errors.InvalidDirectory{Path: build.Directory} } - for _, name := range files { - log.Info.Println(name) - } + directory.Walk(build.Directory, func(file string) { + if !strings.HasSuffix(file, ".q") { + return + } + + log.Info.Println(file) + }) build.Code.Write([]byte{ 0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1 @@ -83,6 +82,11 @@ func (build *Build) Compile() error { return nil } +// Executable returns the path to the executable. +func (build *Build) Executable() string { + return filepath.Join(build.Directory, build.Name) +} + // writeToDisk writes the executable file to disk. func writeToDisk(filePath string, code []byte, data []byte) error { file, err := os.Create(filePath) diff --git a/cli/Build.go b/cli/Build.go index b579dbf..7c8eac5 100644 --- a/cli/Build.go +++ b/cli/Build.go @@ -2,7 +2,7 @@ package cli import ( "git.akyoto.dev/cli/q/build" - "git.akyoto.dev/cli/q/cli/log" + "git.akyoto.dev/cli/q/log" ) // Build builds an executable. diff --git a/cli/Help.go b/cli/Help.go index 61ba523..e34dc6c 100644 --- a/cli/Help.go +++ b/cli/Help.go @@ -1,7 +1,7 @@ package cli import ( - "git.akyoto.dev/cli/q/cli/log" + "git.akyoto.dev/cli/q/log" ) // Help shows the command line argument usage. diff --git a/cli/System.go b/cli/System.go index 1b847aa..36fde71 100644 --- a/cli/System.go +++ b/cli/System.go @@ -4,7 +4,7 @@ import ( "os" "runtime" - "git.akyoto.dev/cli/q/cli/log" + "git.akyoto.dev/cli/q/log" ) // System shows system information. diff --git a/cli/cli_test.go b/cli/cli_test.go index c1e5861..111df5d 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -6,7 +6,7 @@ import ( "testing" "git.akyoto.dev/cli/q/cli" - "git.akyoto.dev/cli/q/cli/log" + "git.akyoto.dev/cli/q/log" "git.akyoto.dev/go/assert" ) diff --git a/directory/Walk.go b/directory/Walk.go new file mode 100644 index 0000000..5595e3e --- /dev/null +++ b/directory/Walk.go @@ -0,0 +1,61 @@ +package directory + +import ( + "syscall" + "unsafe" +) + +const blockSize = 8 << 10 + +// Walk calls your callback function for every file name inside the directory. +// It doesn't distinguish between files and directories. +func Walk(directory string, callBack func(string)) { + fd, err := syscall.Open(directory, 0, 0) + + if err != nil { + panic(err) + } + + defer syscall.Close(fd) + buffer := make([]byte, blockSize) + + for { + n, err := syscall.ReadDirent(fd, buffer) + + if err != nil { + panic(err) + } + + if n <= 0 { + break + } + + readBuffer := buffer[:n] + + for len(readBuffer) > 0 { + dirent := (*syscall.Dirent)(unsafe.Pointer(&readBuffer[0])) + readBuffer = readBuffer[dirent.Reclen:] + + // Skip deleted files + if dirent.Ino == 0 { + continue + } + + // Skip hidden files + if dirent.Name[0] == '.' { + continue + } + + for i, c := range dirent.Name { + if c != 0 { + continue + } + + bytePointer := (*byte)(unsafe.Pointer(&dirent.Name[0])) + name := unsafe.String(bytePointer, i) + callBack(name) + break + } + } + } +} diff --git a/build/elf/ELF64.go b/elf/ELF64.go similarity index 100% rename from build/elf/ELF64.go rename to elf/ELF64.go diff --git a/build/elf/Header.go b/elf/Header.go similarity index 100% rename from build/elf/Header.go rename to elf/Header.go diff --git a/build/elf/ProgramHeader.go b/elf/ProgramHeader.go similarity index 100% rename from build/elf/ProgramHeader.go rename to elf/ProgramHeader.go diff --git a/build/elf/SectionHeader.go b/elf/SectionHeader.go similarity index 100% rename from build/elf/SectionHeader.go rename to elf/SectionHeader.go diff --git a/build/elf/elf.md b/elf/elf.md similarity index 100% rename from build/elf/elf.md rename to elf/elf.md diff --git a/errors/InvalidPath.go b/errors/InvalidPath.go new file mode 100644 index 0000000..0b71fcf --- /dev/null +++ b/errors/InvalidPath.go @@ -0,0 +1,15 @@ +package errors + +import "fmt" + +type InvalidDirectory struct { + Path string +} + +func (err *InvalidDirectory) Error() string { + if err.Path == "" { + return "Invalid directory" + } + + return fmt.Sprintf("Invalid directory '%s'", err.Path) +} diff --git a/cli/log/log.go b/log/log.go similarity index 100% rename from cli/log/log.go rename to log/log.go From bd73132a30c840d9e43484eace74f77ab95984af Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 20 Oct 2023 17:07:44 +0200 Subject: [PATCH 0019/1012] Improved project structure --- README.md | 5 ++--- examples/fibonacci/fibonacci.q | 19 +++++++++++++++++++ examples/hello/hello.q | 2 +- main.go | 2 +- {build => src/build}/Build.go | 8 ++++---- {cli => src/cli}/Build.go | 4 ++-- {cli => src/cli}/Help.go | 2 +- {cli => src/cli}/Main.go | 0 {cli => src/cli}/System.go | 2 +- {cli => src/cli}/cli_test.go | 4 ++-- {directory => src/directory}/Walk.go | 0 elf/ELF64.go => src/elf/ELF.go | 0 {elf => src/elf}/Header.go | 0 {elf => src/elf}/ProgramHeader.go | 0 {elf => src/elf}/SectionHeader.go | 0 {elf => src/elf}/elf.md | 0 {errors => src/errors}/InvalidPath.go | 0 {log => src/log}/log.go | 0 18 files changed, 33 insertions(+), 15 deletions(-) create mode 100644 examples/fibonacci/fibonacci.q rename {build => src/build}/Build.go (93%) rename {cli => src/cli}/Build.go (87%) rename {cli => src/cli}/Help.go (89%) rename {cli => src/cli}/Main.go (100%) rename {cli => src/cli}/System.go (95%) rename {cli => src/cli}/cli_test.go (93%) rename {directory => src/directory}/Walk.go (100%) rename elf/ELF64.go => src/elf/ELF.go (100%) rename {elf => src/elf}/Header.go (100%) rename {elf => src/elf}/ProgramHeader.go (100%) rename {elf => src/elf}/SectionHeader.go (100%) rename {elf => src/elf}/elf.md (100%) rename {errors => src/errors}/InvalidPath.go (100%) rename {log => src/log}/log.go (100%) diff --git a/README.md b/README.md index 37b03bc..3df933b 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,8 @@ A simple programming language. ## Features -* Fast compilation -* High performance -* Small binaries +* 🔥 Fast compilation +* 📦 Small binaries ## Installation diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q new file mode 100644 index 0000000..c188e38 --- /dev/null +++ b/examples/fibonacci/fibonacci.q @@ -0,0 +1,19 @@ +import sys + +main() { + let f = fibonacci(11) + sys.exit(f) +} + +fibonacci(n Int) -> Int { + mut b = 0 + mut c = 1 + + for 0..n { + let a = b + b = c + c = a + b + } + + return b +} diff --git a/examples/hello/hello.q b/examples/hello/hello.q index d974d48..4f723f0 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,3 +1,3 @@ main() { - + print("Hello") } diff --git a/main.go b/main.go index 2a518ef..60febb9 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,7 @@ package main import ( "os" - "git.akyoto.dev/cli/q/cli" + "git.akyoto.dev/cli/q/src/cli" ) func main() { diff --git a/build/Build.go b/src/build/Build.go similarity index 93% rename from build/Build.go rename to src/build/Build.go index 368b711..0d3df91 100644 --- a/build/Build.go +++ b/src/build/Build.go @@ -7,10 +7,10 @@ import ( "path/filepath" "strings" - "git.akyoto.dev/cli/q/directory" - "git.akyoto.dev/cli/q/elf" - "git.akyoto.dev/cli/q/errors" - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/src/directory" + "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/log" ) // Build describes a compiler build. diff --git a/cli/Build.go b/src/cli/Build.go similarity index 87% rename from cli/Build.go rename to src/cli/Build.go index 7c8eac5..47e76db 100644 --- a/cli/Build.go +++ b/src/cli/Build.go @@ -1,8 +1,8 @@ package cli import ( - "git.akyoto.dev/cli/q/build" - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/cli/q/src/log" ) // Build builds an executable. diff --git a/cli/Help.go b/src/cli/Help.go similarity index 89% rename from cli/Help.go rename to src/cli/Help.go index e34dc6c..35fa8cd 100644 --- a/cli/Help.go +++ b/src/cli/Help.go @@ -1,7 +1,7 @@ package cli import ( - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/src/log" ) // Help shows the command line argument usage. diff --git a/cli/Main.go b/src/cli/Main.go similarity index 100% rename from cli/Main.go rename to src/cli/Main.go diff --git a/cli/System.go b/src/cli/System.go similarity index 95% rename from cli/System.go rename to src/cli/System.go index 36fde71..d76f565 100644 --- a/cli/System.go +++ b/src/cli/System.go @@ -4,7 +4,7 @@ import ( "os" "runtime" - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/src/log" ) // System shows system information. diff --git a/cli/cli_test.go b/src/cli/cli_test.go similarity index 93% rename from cli/cli_test.go rename to src/cli/cli_test.go index 111df5d..eb51e18 100644 --- a/cli/cli_test.go +++ b/src/cli/cli_test.go @@ -5,8 +5,8 @@ import ( "os" "testing" - "git.akyoto.dev/cli/q/cli" - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/src/cli" + "git.akyoto.dev/cli/q/src/log" "git.akyoto.dev/go/assert" ) diff --git a/directory/Walk.go b/src/directory/Walk.go similarity index 100% rename from directory/Walk.go rename to src/directory/Walk.go diff --git a/elf/ELF64.go b/src/elf/ELF.go similarity index 100% rename from elf/ELF64.go rename to src/elf/ELF.go diff --git a/elf/Header.go b/src/elf/Header.go similarity index 100% rename from elf/Header.go rename to src/elf/Header.go diff --git a/elf/ProgramHeader.go b/src/elf/ProgramHeader.go similarity index 100% rename from elf/ProgramHeader.go rename to src/elf/ProgramHeader.go diff --git a/elf/SectionHeader.go b/src/elf/SectionHeader.go similarity index 100% rename from elf/SectionHeader.go rename to src/elf/SectionHeader.go diff --git a/elf/elf.md b/src/elf/elf.md similarity index 100% rename from elf/elf.md rename to src/elf/elf.md diff --git a/errors/InvalidPath.go b/src/errors/InvalidPath.go similarity index 100% rename from errors/InvalidPath.go rename to src/errors/InvalidPath.go diff --git a/log/log.go b/src/log/log.go similarity index 100% rename from log/log.go rename to src/log/log.go From 61af1429303482f64ffb731a442d9f51d1abeb26 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 20 Oct 2023 17:07:44 +0200 Subject: [PATCH 0020/1012] Improved project structure --- README.md | 5 ++--- examples/fibonacci/fibonacci.q | 19 +++++++++++++++++++ examples/hello/hello.q | 2 +- main.go | 2 +- {build => src/build}/Build.go | 8 ++++---- {cli => src/cli}/Build.go | 4 ++-- {cli => src/cli}/Help.go | 2 +- {cli => src/cli}/Main.go | 0 {cli => src/cli}/System.go | 2 +- {cli => src/cli}/cli_test.go | 4 ++-- {directory => src/directory}/Walk.go | 0 elf/ELF64.go => src/elf/ELF.go | 0 {elf => src/elf}/Header.go | 0 {elf => src/elf}/ProgramHeader.go | 0 {elf => src/elf}/SectionHeader.go | 0 {elf => src/elf}/elf.md | 0 {errors => src/errors}/InvalidPath.go | 0 {log => src/log}/log.go | 0 18 files changed, 33 insertions(+), 15 deletions(-) create mode 100644 examples/fibonacci/fibonacci.q rename {build => src/build}/Build.go (93%) rename {cli => src/cli}/Build.go (87%) rename {cli => src/cli}/Help.go (89%) rename {cli => src/cli}/Main.go (100%) rename {cli => src/cli}/System.go (95%) rename {cli => src/cli}/cli_test.go (93%) rename {directory => src/directory}/Walk.go (100%) rename elf/ELF64.go => src/elf/ELF.go (100%) rename {elf => src/elf}/Header.go (100%) rename {elf => src/elf}/ProgramHeader.go (100%) rename {elf => src/elf}/SectionHeader.go (100%) rename {elf => src/elf}/elf.md (100%) rename {errors => src/errors}/InvalidPath.go (100%) rename {log => src/log}/log.go (100%) diff --git a/README.md b/README.md index 37b03bc..3df933b 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,8 @@ A simple programming language. ## Features -* Fast compilation -* High performance -* Small binaries +* 🔥 Fast compilation +* 📦 Small binaries ## Installation diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q new file mode 100644 index 0000000..c188e38 --- /dev/null +++ b/examples/fibonacci/fibonacci.q @@ -0,0 +1,19 @@ +import sys + +main() { + let f = fibonacci(11) + sys.exit(f) +} + +fibonacci(n Int) -> Int { + mut b = 0 + mut c = 1 + + for 0..n { + let a = b + b = c + c = a + b + } + + return b +} diff --git a/examples/hello/hello.q b/examples/hello/hello.q index d974d48..4f723f0 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,3 +1,3 @@ main() { - + print("Hello") } diff --git a/main.go b/main.go index 2a518ef..60febb9 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,7 @@ package main import ( "os" - "git.akyoto.dev/cli/q/cli" + "git.akyoto.dev/cli/q/src/cli" ) func main() { diff --git a/build/Build.go b/src/build/Build.go similarity index 93% rename from build/Build.go rename to src/build/Build.go index 368b711..0d3df91 100644 --- a/build/Build.go +++ b/src/build/Build.go @@ -7,10 +7,10 @@ import ( "path/filepath" "strings" - "git.akyoto.dev/cli/q/directory" - "git.akyoto.dev/cli/q/elf" - "git.akyoto.dev/cli/q/errors" - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/src/directory" + "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/log" ) // Build describes a compiler build. diff --git a/cli/Build.go b/src/cli/Build.go similarity index 87% rename from cli/Build.go rename to src/cli/Build.go index 7c8eac5..47e76db 100644 --- a/cli/Build.go +++ b/src/cli/Build.go @@ -1,8 +1,8 @@ package cli import ( - "git.akyoto.dev/cli/q/build" - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/cli/q/src/log" ) // Build builds an executable. diff --git a/cli/Help.go b/src/cli/Help.go similarity index 89% rename from cli/Help.go rename to src/cli/Help.go index e34dc6c..35fa8cd 100644 --- a/cli/Help.go +++ b/src/cli/Help.go @@ -1,7 +1,7 @@ package cli import ( - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/src/log" ) // Help shows the command line argument usage. diff --git a/cli/Main.go b/src/cli/Main.go similarity index 100% rename from cli/Main.go rename to src/cli/Main.go diff --git a/cli/System.go b/src/cli/System.go similarity index 95% rename from cli/System.go rename to src/cli/System.go index 36fde71..d76f565 100644 --- a/cli/System.go +++ b/src/cli/System.go @@ -4,7 +4,7 @@ import ( "os" "runtime" - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/src/log" ) // System shows system information. diff --git a/cli/cli_test.go b/src/cli/cli_test.go similarity index 93% rename from cli/cli_test.go rename to src/cli/cli_test.go index 111df5d..eb51e18 100644 --- a/cli/cli_test.go +++ b/src/cli/cli_test.go @@ -5,8 +5,8 @@ import ( "os" "testing" - "git.akyoto.dev/cli/q/cli" - "git.akyoto.dev/cli/q/log" + "git.akyoto.dev/cli/q/src/cli" + "git.akyoto.dev/cli/q/src/log" "git.akyoto.dev/go/assert" ) diff --git a/directory/Walk.go b/src/directory/Walk.go similarity index 100% rename from directory/Walk.go rename to src/directory/Walk.go diff --git a/elf/ELF64.go b/src/elf/ELF.go similarity index 100% rename from elf/ELF64.go rename to src/elf/ELF.go diff --git a/elf/Header.go b/src/elf/Header.go similarity index 100% rename from elf/Header.go rename to src/elf/Header.go diff --git a/elf/ProgramHeader.go b/src/elf/ProgramHeader.go similarity index 100% rename from elf/ProgramHeader.go rename to src/elf/ProgramHeader.go diff --git a/elf/SectionHeader.go b/src/elf/SectionHeader.go similarity index 100% rename from elf/SectionHeader.go rename to src/elf/SectionHeader.go diff --git a/elf/elf.md b/src/elf/elf.md similarity index 100% rename from elf/elf.md rename to src/elf/elf.md diff --git a/errors/InvalidPath.go b/src/errors/InvalidPath.go similarity index 100% rename from errors/InvalidPath.go rename to src/errors/InvalidPath.go diff --git a/log/log.go b/src/log/log.go similarity index 100% rename from log/log.go rename to src/log/log.go From 315d2d075e6ebeb35a597109a33337876aa2b21f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 21 Oct 2023 13:41:47 +0200 Subject: [PATCH 0021/1012] Added assembler instructions --- src/asm/Assembler.go | 8 + src/asm/x64/AppendUint32.go | 11 ++ src/asm/x64/MoveRegNum32.go | 10 + src/asm/x64/Syscall.go | 10 + src/build/Build.go | 41 +++-- src/elf/Header.go | 3 +- src/register/General.go | 20 ++ src/register/Named.go | 12 ++ src/register/Syscall_amd64.go | 11 ++ src/syscall/syscall_linux.go | 333 ++++++++++++++++++++++++++++++++++ 10 files changed, 438 insertions(+), 21 deletions(-) create mode 100644 src/asm/Assembler.go create mode 100644 src/asm/x64/AppendUint32.go create mode 100644 src/asm/x64/MoveRegNum32.go create mode 100644 src/asm/x64/Syscall.go create mode 100644 src/register/General.go create mode 100644 src/register/Named.go create mode 100644 src/register/Syscall_amd64.go create mode 100644 src/syscall/syscall_linux.go diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go new file mode 100644 index 0000000..8369cc5 --- /dev/null +++ b/src/asm/Assembler.go @@ -0,0 +1,8 @@ +package asm + +import "bytes" + +type Assembler struct { + Code bytes.Buffer + Data bytes.Buffer +} diff --git a/src/asm/x64/AppendUint32.go b/src/asm/x64/AppendUint32.go new file mode 100644 index 0000000..52cb813 --- /dev/null +++ b/src/asm/x64/AppendUint32.go @@ -0,0 +1,11 @@ +package x64 + +import "io" + +// AppendUint32 appends a 32 bit number in Little Endian to the given writer. +func AppendUint32(w io.ByteWriter, number uint32) { + w.WriteByte(byte(number)) + w.WriteByte(byte(number >> 8)) + w.WriteByte(byte(number >> 16)) + w.WriteByte(byte(number >> 24)) +} diff --git a/src/asm/x64/MoveRegNum32.go b/src/asm/x64/MoveRegNum32.go new file mode 100644 index 0000000..64a22bb --- /dev/null +++ b/src/asm/x64/MoveRegNum32.go @@ -0,0 +1,10 @@ +package x64 + +import ( + "io" +) + +func MoveRegNum32(w io.ByteWriter, register byte, number uint32) { + w.WriteByte(0xb8 + register) + AppendUint32(w, number) +} diff --git a/src/asm/x64/Syscall.go b/src/asm/x64/Syscall.go new file mode 100644 index 0000000..3c22056 --- /dev/null +++ b/src/asm/x64/Syscall.go @@ -0,0 +1,10 @@ +package x64 + +import ( + "io" +) + +func Syscall(w io.ByteWriter) { + w.WriteByte(0x0f) + w.WriteByte(0x05) +} diff --git a/src/build/Build.go b/src/build/Build.go index 0d3df91..58bc7ff 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -2,23 +2,24 @@ package build import ( "bufio" - "bytes" "os" "path/filepath" "strings" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/asm/x64" "git.akyoto.dev/cli/q/src/directory" "git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/log" + "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/cli/q/src/syscall" ) // Build describes a compiler build. type Build struct { Name string Directory string - Code bytes.Buffer - Data bytes.Buffer WriteExecutable bool } @@ -39,11 +40,26 @@ func (build *Build) Run() error { return err } - if build.WriteExecutable { - return writeToDisk(build.Executable(), build.Code.Bytes(), build.Data.Bytes()) + if !build.WriteExecutable { + return nil } - return nil + final := asm.Assembler{} + code := &final.Code + data := &final.Data + + x64.MoveRegNum32(code, register.Syscall0, syscall.Write) + x64.MoveRegNum32(code, register.Syscall1, 1) + x64.MoveRegNum32(code, register.Syscall2, 0x4000a2) + x64.MoveRegNum32(code, register.Syscall3, 6) + x64.Syscall(code) + + x64.MoveRegNum32(code, register.Syscall0, syscall.Exit) + x64.MoveRegNum32(code, register.Syscall1, 0) + x64.Syscall(code) + + data.WriteString("Hello\n") + return writeToDisk(build.Executable(), code.Bytes(), data.Bytes()) } // Compile compiles all the functions. @@ -66,19 +82,6 @@ func (build *Build) Compile() error { log.Info.Println(file) }) - build.Code.Write([]byte{ - 0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1 - 0xbf, 0x01, 0x00, 0x00, 0x00, // mov edi, 1 - 0xbe, 0xa2, 0x00, 0x40, 0x00, // mov esi, 0x4000a2 - 0xba, 0x06, 0x00, 0x00, 0x00, // mov edx, 6 - 0x0f, 0x05, // syscall - - 0xb8, 0x3c, 0x00, 0x00, 0x00, // mov eax, 60 - 0xbf, 0x00, 0x00, 0x00, 0x00, // mov edi, 0 - 0x0f, 0x05, // syscall - }) - - build.Data.Write([]byte{'H', 'e', 'l', 'l', 'o', '\n'}) return nil } diff --git a/src/elf/Header.go b/src/elf/Header.go index 49b231c..089bf00 100644 --- a/src/elf/Header.go +++ b/src/elf/Header.go @@ -5,8 +5,7 @@ const ( TypeExecutable = 2 ArchitectureAMD64 = 0x3E HeaderSize = 64 - CacheLineSize = 64 - Align = CacheLineSize + Align = 16 ) // Header contains general information. diff --git a/src/register/General.go b/src/register/General.go new file mode 100644 index 0000000..4358735 --- /dev/null +++ b/src/register/General.go @@ -0,0 +1,20 @@ +package register + +const ( + R0 = iota + R1 + R2 + R3 + R4 + R5 + R6 + R7 + R8 + R9 + R10 + R11 + R12 + R13 + R14 + R15 +) diff --git a/src/register/Named.go b/src/register/Named.go new file mode 100644 index 0000000..80d2ae7 --- /dev/null +++ b/src/register/Named.go @@ -0,0 +1,12 @@ +package register + +const ( + RAX = R0 + RCX = R1 + RDX = R2 + RBX = R3 + RSP = R4 + RBP = R5 + RSI = R6 + RDI = R7 +) diff --git a/src/register/Syscall_amd64.go b/src/register/Syscall_amd64.go new file mode 100644 index 0000000..62cc6e5 --- /dev/null +++ b/src/register/Syscall_amd64.go @@ -0,0 +1,11 @@ +package register + +const ( + Syscall0 = R0 + Syscall1 = R7 + Syscall2 = R6 + Syscall3 = R2 + Syscall4 = R10 + Syscall5 = R8 + Syscall6 = R9 +) diff --git a/src/syscall/syscall_linux.go b/src/syscall/syscall_linux.go new file mode 100644 index 0000000..c2175b8 --- /dev/null +++ b/src/syscall/syscall_linux.go @@ -0,0 +1,333 @@ +package syscall + +const ( + Read = iota + Write + Open + Close + Stat + Fstat + Lstat + Poll + Lseek + Mmap + Mprotect + Munmap + Brk + Rt_sigaction + Rt_sigprocmask + Rt_sigreturn + Ioctl + Pread64 + Pwrite64 + Readv + Writev + Access + Pipe + Select + Sched_yield + Mremap + Msync + Mincore + Madvise + Shmget + Shmat + Shmctl + Dup + Dup2 + Pause + Nanosleep + Getitimer + Alarm + Setitimer + Getpid + Sendfile + Socket + Connect + Accept + Sendto + Recvfrom + Sendmsg + Recvmsg + Shutdown + Bind + Listen + Getsockname + Getpeername + Socketpair + Setsockopt + Getsockopt + Clone + Fork + Vfork + Execve + Exit + Wait4 + Kill + Uname + Semget + Semop + Semctl + Shmdt + Msgget + Msgsnd + Msgrcv + Msgctl + Fcntl + Flock + Fsync + Fdatasync + Truncate + Ftruncate + Getdents + Getcwd + Chdir + Fchdir + Rename + Mkdir + Rmdir + Creat + Link + Unlink + Symlink + Readlink + Chmod + Fchmod + Chown + Fchown + Lchown + Umask + Gettimeofday + Getrlimit + Getrusage + Sysinfo + Times + Ptrace + Getuid + Syslog + Getgid + Setuid + Setgid + Geteuid + Getegid + Setpgid + Getppid + Getpgrp + Setsid + Setreuid + Setregid + Getgroups + Setgroups + Setresuid + Getresuid + Setresgid + Getresgid + Getpgid + Setfsuid + Setfsgid + Getsid + Capget + Capset + Rt_sigpending + Rt_sigtimedwait + Rt_sigqueueinfo + Rt_sigsuspend + Sigaltstack + Utime + Mknod + Uselib + Personality + Ustat + Statfs + Fstatfs + Sysfs + Getpriority + Setpriority + Sched_setparam + Sched_getparam + Sched_setscheduler + Sched_getscheduler + Sched_get_priority_max + Sched_get_priority_min + Sched_rr_get_interval + Mlock + Munlock + Mlockall + Munlockall + Vhangup + Modify_ldt + Pivot_root + Sysctl + Prctl + Arch_prctl + Adjtimex + Setrlimit + Chroot + Sync + Acct + Settimeofday + Mount + Umount2 + Swapon + Swapoff + Reboot + Sethostname + Setdomainname + Iopl + Ioperm + Create_module + Init_module + Delete_module + Get_kernel_syms + Query_module + Quotactl + Nfsservctl + Getpmsg + Putpmsg + Afs_syscall + Tuxcall + Security + Gettid + Readahead + Setxattr + Lsetxattr + Fsetxattr + Getxattr + Lgetxattr + Fgetxattr + Listxattr + Llistxattr + Flistxattr + Removexattr + Lremovexattr + Fremovexattr + Tkill + Time + Futex + Sched_setaffinity + Sched_getaffinity + Set_thread_area + Io_setup + Io_destroy + Io_getevents + Io_submit + Io_cancel + Get_thread_area + Lookup_dcookie + Epoll_create + Epoll_ctl_old + Epoll_wait_old + Remap_file_pages + Getdents64 + Set_tid_address + Restart_syscall + Semtimedop + Fadvise64 + Timer_create + Timer_settime + Timer_gettime + Timer_getoverrun + Timer_delete + Clock_settime + Clock_gettime + Clock_getres + Clock_nanosleep + Exit_group + Epoll_wait + Epoll_ctl + Tgkill + Utimes + Vserver + Mbind + Set_mempolicy + Get_mempolicy + Mq_open + Mq_unlink + Mq_timedsend + Mq_timedreceive + Mq_notify + Mq_getsetattr + Kexec_load + Waitid + Add_key + Request_key + Keyctl + Ioprio_set + Ioprio_get + Inotify_init + Inotify_add_watch + Inotify_rm_watch + Migrate_pages + Openat + Mkdirat + Mknodat + Fchownat + Futimesat + Newfstatat + Unlinkat + Renameat + Linkat + Symlinkat + Readlinkat + Fchmodat + Faccessat + Pselect6 + Ppoll + Unshare + Set_robust_list + Get_robust_list + Splice + Tee + Sync_file_range + Vmsplice + Move_pages + Utimensat + Epoll_pwait + Signalfd + Timerfd_create + Eventfd + Fallocate + Timerfd_settime + Timerfd_gettime + Accept4 + Signalfd4 + Eventfd2 + Epoll_create1 + Dup3 + Pipe2 + Inotify_init1 + Preadv + Pwritev + Rt_tgsigqueueinfo + Perf_event_open + Recvmmsg + Fanotify_init + Fanotify_mark + Prlimit64 + Name_to_handle_at + Open_by_handle_at + Clock_adjtime + Syncfs + Sendmmsg + Setns + Getcpu + Process_vm_readv + Process_vm_writev + Kcmp + Finit_module + Sched_setattr + Sched_getattr + Renameat2 + Seccomp + Getrandom + Memfd_create + Kexec_file_load + Bpf + Stub_execveat + Userfaultfd + Membarrier + Mlock2 + Copy_file_range + Preadv2 + Pwritev2 +) From 4967719902e28405ecbd5b57bd1ccceba1ab8911 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 21 Oct 2023 13:41:47 +0200 Subject: [PATCH 0022/1012] Added assembler instructions --- src/asm/Assembler.go | 8 + src/asm/x64/AppendUint32.go | 11 ++ src/asm/x64/MoveRegNum32.go | 10 + src/asm/x64/Syscall.go | 10 + src/build/Build.go | 41 +++-- src/elf/Header.go | 3 +- src/register/General.go | 20 ++ src/register/Named.go | 12 ++ src/register/Syscall_amd64.go | 11 ++ src/syscall/syscall_linux.go | 333 ++++++++++++++++++++++++++++++++++ 10 files changed, 438 insertions(+), 21 deletions(-) create mode 100644 src/asm/Assembler.go create mode 100644 src/asm/x64/AppendUint32.go create mode 100644 src/asm/x64/MoveRegNum32.go create mode 100644 src/asm/x64/Syscall.go create mode 100644 src/register/General.go create mode 100644 src/register/Named.go create mode 100644 src/register/Syscall_amd64.go create mode 100644 src/syscall/syscall_linux.go diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go new file mode 100644 index 0000000..8369cc5 --- /dev/null +++ b/src/asm/Assembler.go @@ -0,0 +1,8 @@ +package asm + +import "bytes" + +type Assembler struct { + Code bytes.Buffer + Data bytes.Buffer +} diff --git a/src/asm/x64/AppendUint32.go b/src/asm/x64/AppendUint32.go new file mode 100644 index 0000000..52cb813 --- /dev/null +++ b/src/asm/x64/AppendUint32.go @@ -0,0 +1,11 @@ +package x64 + +import "io" + +// AppendUint32 appends a 32 bit number in Little Endian to the given writer. +func AppendUint32(w io.ByteWriter, number uint32) { + w.WriteByte(byte(number)) + w.WriteByte(byte(number >> 8)) + w.WriteByte(byte(number >> 16)) + w.WriteByte(byte(number >> 24)) +} diff --git a/src/asm/x64/MoveRegNum32.go b/src/asm/x64/MoveRegNum32.go new file mode 100644 index 0000000..64a22bb --- /dev/null +++ b/src/asm/x64/MoveRegNum32.go @@ -0,0 +1,10 @@ +package x64 + +import ( + "io" +) + +func MoveRegNum32(w io.ByteWriter, register byte, number uint32) { + w.WriteByte(0xb8 + register) + AppendUint32(w, number) +} diff --git a/src/asm/x64/Syscall.go b/src/asm/x64/Syscall.go new file mode 100644 index 0000000..3c22056 --- /dev/null +++ b/src/asm/x64/Syscall.go @@ -0,0 +1,10 @@ +package x64 + +import ( + "io" +) + +func Syscall(w io.ByteWriter) { + w.WriteByte(0x0f) + w.WriteByte(0x05) +} diff --git a/src/build/Build.go b/src/build/Build.go index 0d3df91..58bc7ff 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -2,23 +2,24 @@ package build import ( "bufio" - "bytes" "os" "path/filepath" "strings" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/asm/x64" "git.akyoto.dev/cli/q/src/directory" "git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/log" + "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/cli/q/src/syscall" ) // Build describes a compiler build. type Build struct { Name string Directory string - Code bytes.Buffer - Data bytes.Buffer WriteExecutable bool } @@ -39,11 +40,26 @@ func (build *Build) Run() error { return err } - if build.WriteExecutable { - return writeToDisk(build.Executable(), build.Code.Bytes(), build.Data.Bytes()) + if !build.WriteExecutable { + return nil } - return nil + final := asm.Assembler{} + code := &final.Code + data := &final.Data + + x64.MoveRegNum32(code, register.Syscall0, syscall.Write) + x64.MoveRegNum32(code, register.Syscall1, 1) + x64.MoveRegNum32(code, register.Syscall2, 0x4000a2) + x64.MoveRegNum32(code, register.Syscall3, 6) + x64.Syscall(code) + + x64.MoveRegNum32(code, register.Syscall0, syscall.Exit) + x64.MoveRegNum32(code, register.Syscall1, 0) + x64.Syscall(code) + + data.WriteString("Hello\n") + return writeToDisk(build.Executable(), code.Bytes(), data.Bytes()) } // Compile compiles all the functions. @@ -66,19 +82,6 @@ func (build *Build) Compile() error { log.Info.Println(file) }) - build.Code.Write([]byte{ - 0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 1 - 0xbf, 0x01, 0x00, 0x00, 0x00, // mov edi, 1 - 0xbe, 0xa2, 0x00, 0x40, 0x00, // mov esi, 0x4000a2 - 0xba, 0x06, 0x00, 0x00, 0x00, // mov edx, 6 - 0x0f, 0x05, // syscall - - 0xb8, 0x3c, 0x00, 0x00, 0x00, // mov eax, 60 - 0xbf, 0x00, 0x00, 0x00, 0x00, // mov edi, 0 - 0x0f, 0x05, // syscall - }) - - build.Data.Write([]byte{'H', 'e', 'l', 'l', 'o', '\n'}) return nil } diff --git a/src/elf/Header.go b/src/elf/Header.go index 49b231c..089bf00 100644 --- a/src/elf/Header.go +++ b/src/elf/Header.go @@ -5,8 +5,7 @@ const ( TypeExecutable = 2 ArchitectureAMD64 = 0x3E HeaderSize = 64 - CacheLineSize = 64 - Align = CacheLineSize + Align = 16 ) // Header contains general information. diff --git a/src/register/General.go b/src/register/General.go new file mode 100644 index 0000000..4358735 --- /dev/null +++ b/src/register/General.go @@ -0,0 +1,20 @@ +package register + +const ( + R0 = iota + R1 + R2 + R3 + R4 + R5 + R6 + R7 + R8 + R9 + R10 + R11 + R12 + R13 + R14 + R15 +) diff --git a/src/register/Named.go b/src/register/Named.go new file mode 100644 index 0000000..80d2ae7 --- /dev/null +++ b/src/register/Named.go @@ -0,0 +1,12 @@ +package register + +const ( + RAX = R0 + RCX = R1 + RDX = R2 + RBX = R3 + RSP = R4 + RBP = R5 + RSI = R6 + RDI = R7 +) diff --git a/src/register/Syscall_amd64.go b/src/register/Syscall_amd64.go new file mode 100644 index 0000000..62cc6e5 --- /dev/null +++ b/src/register/Syscall_amd64.go @@ -0,0 +1,11 @@ +package register + +const ( + Syscall0 = R0 + Syscall1 = R7 + Syscall2 = R6 + Syscall3 = R2 + Syscall4 = R10 + Syscall5 = R8 + Syscall6 = R9 +) diff --git a/src/syscall/syscall_linux.go b/src/syscall/syscall_linux.go new file mode 100644 index 0000000..c2175b8 --- /dev/null +++ b/src/syscall/syscall_linux.go @@ -0,0 +1,333 @@ +package syscall + +const ( + Read = iota + Write + Open + Close + Stat + Fstat + Lstat + Poll + Lseek + Mmap + Mprotect + Munmap + Brk + Rt_sigaction + Rt_sigprocmask + Rt_sigreturn + Ioctl + Pread64 + Pwrite64 + Readv + Writev + Access + Pipe + Select + Sched_yield + Mremap + Msync + Mincore + Madvise + Shmget + Shmat + Shmctl + Dup + Dup2 + Pause + Nanosleep + Getitimer + Alarm + Setitimer + Getpid + Sendfile + Socket + Connect + Accept + Sendto + Recvfrom + Sendmsg + Recvmsg + Shutdown + Bind + Listen + Getsockname + Getpeername + Socketpair + Setsockopt + Getsockopt + Clone + Fork + Vfork + Execve + Exit + Wait4 + Kill + Uname + Semget + Semop + Semctl + Shmdt + Msgget + Msgsnd + Msgrcv + Msgctl + Fcntl + Flock + Fsync + Fdatasync + Truncate + Ftruncate + Getdents + Getcwd + Chdir + Fchdir + Rename + Mkdir + Rmdir + Creat + Link + Unlink + Symlink + Readlink + Chmod + Fchmod + Chown + Fchown + Lchown + Umask + Gettimeofday + Getrlimit + Getrusage + Sysinfo + Times + Ptrace + Getuid + Syslog + Getgid + Setuid + Setgid + Geteuid + Getegid + Setpgid + Getppid + Getpgrp + Setsid + Setreuid + Setregid + Getgroups + Setgroups + Setresuid + Getresuid + Setresgid + Getresgid + Getpgid + Setfsuid + Setfsgid + Getsid + Capget + Capset + Rt_sigpending + Rt_sigtimedwait + Rt_sigqueueinfo + Rt_sigsuspend + Sigaltstack + Utime + Mknod + Uselib + Personality + Ustat + Statfs + Fstatfs + Sysfs + Getpriority + Setpriority + Sched_setparam + Sched_getparam + Sched_setscheduler + Sched_getscheduler + Sched_get_priority_max + Sched_get_priority_min + Sched_rr_get_interval + Mlock + Munlock + Mlockall + Munlockall + Vhangup + Modify_ldt + Pivot_root + Sysctl + Prctl + Arch_prctl + Adjtimex + Setrlimit + Chroot + Sync + Acct + Settimeofday + Mount + Umount2 + Swapon + Swapoff + Reboot + Sethostname + Setdomainname + Iopl + Ioperm + Create_module + Init_module + Delete_module + Get_kernel_syms + Query_module + Quotactl + Nfsservctl + Getpmsg + Putpmsg + Afs_syscall + Tuxcall + Security + Gettid + Readahead + Setxattr + Lsetxattr + Fsetxattr + Getxattr + Lgetxattr + Fgetxattr + Listxattr + Llistxattr + Flistxattr + Removexattr + Lremovexattr + Fremovexattr + Tkill + Time + Futex + Sched_setaffinity + Sched_getaffinity + Set_thread_area + Io_setup + Io_destroy + Io_getevents + Io_submit + Io_cancel + Get_thread_area + Lookup_dcookie + Epoll_create + Epoll_ctl_old + Epoll_wait_old + Remap_file_pages + Getdents64 + Set_tid_address + Restart_syscall + Semtimedop + Fadvise64 + Timer_create + Timer_settime + Timer_gettime + Timer_getoverrun + Timer_delete + Clock_settime + Clock_gettime + Clock_getres + Clock_nanosleep + Exit_group + Epoll_wait + Epoll_ctl + Tgkill + Utimes + Vserver + Mbind + Set_mempolicy + Get_mempolicy + Mq_open + Mq_unlink + Mq_timedsend + Mq_timedreceive + Mq_notify + Mq_getsetattr + Kexec_load + Waitid + Add_key + Request_key + Keyctl + Ioprio_set + Ioprio_get + Inotify_init + Inotify_add_watch + Inotify_rm_watch + Migrate_pages + Openat + Mkdirat + Mknodat + Fchownat + Futimesat + Newfstatat + Unlinkat + Renameat + Linkat + Symlinkat + Readlinkat + Fchmodat + Faccessat + Pselect6 + Ppoll + Unshare + Set_robust_list + Get_robust_list + Splice + Tee + Sync_file_range + Vmsplice + Move_pages + Utimensat + Epoll_pwait + Signalfd + Timerfd_create + Eventfd + Fallocate + Timerfd_settime + Timerfd_gettime + Accept4 + Signalfd4 + Eventfd2 + Epoll_create1 + Dup3 + Pipe2 + Inotify_init1 + Preadv + Pwritev + Rt_tgsigqueueinfo + Perf_event_open + Recvmmsg + Fanotify_init + Fanotify_mark + Prlimit64 + Name_to_handle_at + Open_by_handle_at + Clock_adjtime + Syncfs + Sendmmsg + Setns + Getcpu + Process_vm_readv + Process_vm_writev + Kcmp + Finit_module + Sched_setattr + Sched_getattr + Renameat2 + Seccomp + Getrandom + Memfd_create + Kexec_file_load + Bpf + Stub_execveat + Userfaultfd + Membarrier + Mlock2 + Copy_file_range + Preadv2 + Pwritev2 +) From d5f752bdd4d11ee38a96712930ff1d6ff2899361 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 21 Oct 2023 17:46:20 +0200 Subject: [PATCH 0023/1012] Implemented instruction lists --- README.md | 12 ++++++-- src/asm/Base.go | 23 ++++++++++++++ src/asm/Instruction.go | 8 +++++ src/asm/InstructionList.go | 47 +++++++++++++++++++++++++++++ src/asm/Mnemonic.go | 21 +++++++++++++ src/asm/RegisterNumber.go | 26 ++++++++++++++++ src/asm/{Assembler.go => Result.go} | 2 +- src/asm/x64/AppendUint32.go | 2 +- src/asm/x64/MoveRegNum32.go | 3 +- src/asm/x64/Syscall.go | 1 + src/build/Build.go | 33 ++++++++++---------- src/register/General.go | 10 +++++- 12 files changed, 165 insertions(+), 23 deletions(-) create mode 100644 src/asm/Base.go create mode 100644 src/asm/Instruction.go create mode 100644 src/asm/InstructionList.go create mode 100644 src/asm/Mnemonic.go create mode 100644 src/asm/RegisterNumber.go rename src/asm/{Assembler.go => Result.go} (74%) diff --git a/README.md b/README.md index 3df933b..4154f99 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ A simple programming language. ## Features -* 🔥 Fast compilation -* 📦 Small binaries +* Fast compilation +* Small binaries ## Installation @@ -17,10 +17,18 @@ go build ## Usage +Build a Linux ELF executable from `examples/hello`: + ```shell ./q build examples/hello ``` +Run the generated executable: + +```shell +./examples/hello/hello +``` + ## License Please see the [license documentation](https://akyoto.dev/license). diff --git a/src/asm/Base.go b/src/asm/Base.go new file mode 100644 index 0000000..a67c670 --- /dev/null +++ b/src/asm/Base.go @@ -0,0 +1,23 @@ +package asm + +import ( + "io" + + "git.akyoto.dev/cli/q/src/asm/x64" +) + +// Base represents the data that is common among all instructions. +type Base struct { + Mnemonic Mnemonic +} + +func (x *Base) Write(w io.ByteWriter) { + switch x.Mnemonic { + case SYSCALL: + x64.Syscall(w) + } +} + +func (x *Base) String() string { + return x.Mnemonic.String() +} diff --git a/src/asm/Instruction.go b/src/asm/Instruction.go new file mode 100644 index 0000000..db13758 --- /dev/null +++ b/src/asm/Instruction.go @@ -0,0 +1,8 @@ +package asm + +import "io" + +type Instruction interface { + Write(io.ByteWriter) + String() string +} diff --git a/src/asm/InstructionList.go b/src/asm/InstructionList.go new file mode 100644 index 0000000..cc7ab7a --- /dev/null +++ b/src/asm/InstructionList.go @@ -0,0 +1,47 @@ +package asm + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/register" +) + +type InstructionList struct { + Instructions []Instruction +} + +// Finalize generates the final assembly code. +func (list *InstructionList) Finalize() *Result { + final := Result{} + + for _, instr := range list.Instructions { + instr.Write(&final.Code) + fmt.Println(instr.String()) + } + + return &final +} + +func (list *InstructionList) MoveRegisterNumber(reg register.Register, number uint64) { + list.addRegisterNumber(MOV, reg, number) +} + +func (list *InstructionList) Syscall() { + list.add(SYSCALL) +} + +// add adds an instruction without any operands. +func (list *InstructionList) add(mnemonic Mnemonic) { + list.Instructions = append(list.Instructions, &Base{Mnemonic: mnemonic}) +} + +// addRegisterNumber adds an instruction using a register and a number. +func (list *InstructionList) addRegisterNumber(mnemonic Mnemonic, reg register.Register, number uint64) { + list.Instructions = append(list.Instructions, &RegisterNumber{ + Base: Base{ + Mnemonic: mnemonic, + }, + Register: reg, + Number: number, + }) +} diff --git a/src/asm/Mnemonic.go b/src/asm/Mnemonic.go new file mode 100644 index 0000000..dea828b --- /dev/null +++ b/src/asm/Mnemonic.go @@ -0,0 +1,21 @@ +package asm + +type Mnemonic uint8 + +const ( + NONE Mnemonic = iota + MOV + SYSCALL +) + +func (m Mnemonic) String() string { + switch m { + case MOV: + return "mov" + + case SYSCALL: + return "syscall" + } + + return "NONE" +} diff --git a/src/asm/RegisterNumber.go b/src/asm/RegisterNumber.go new file mode 100644 index 0000000..5637079 --- /dev/null +++ b/src/asm/RegisterNumber.go @@ -0,0 +1,26 @@ +package asm + +import ( + "fmt" + "io" + + "git.akyoto.dev/cli/q/src/asm/x64" + "git.akyoto.dev/cli/q/src/register" +) + +type RegisterNumber struct { + Base + Register register.Register + Number uint64 +} + +func (x *RegisterNumber) Write(w io.ByteWriter) { + switch x.Mnemonic { + case MOV: + x64.MoveRegNum32(w, uint8(x.Register), uint32(x.Number)) + } +} + +func (x *RegisterNumber) String() string { + return fmt.Sprintf("%s %s, %x", x.Mnemonic, x.Register, x.Number) +} diff --git a/src/asm/Assembler.go b/src/asm/Result.go similarity index 74% rename from src/asm/Assembler.go rename to src/asm/Result.go index 8369cc5..248dc23 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Result.go @@ -2,7 +2,7 @@ package asm import "bytes" -type Assembler struct { +type Result struct { Code bytes.Buffer Data bytes.Buffer } diff --git a/src/asm/x64/AppendUint32.go b/src/asm/x64/AppendUint32.go index 52cb813..78b9040 100644 --- a/src/asm/x64/AppendUint32.go +++ b/src/asm/x64/AppendUint32.go @@ -2,7 +2,7 @@ package x64 import "io" -// AppendUint32 appends a 32 bit number in Little Endian to the given writer. +// AppendUint32 appends a 32 bit integer in Little Endian to the given writer. func AppendUint32(w io.ByteWriter, number uint32) { w.WriteByte(byte(number)) w.WriteByte(byte(number >> 8)) diff --git a/src/asm/x64/MoveRegNum32.go b/src/asm/x64/MoveRegNum32.go index 64a22bb..126dcf5 100644 --- a/src/asm/x64/MoveRegNum32.go +++ b/src/asm/x64/MoveRegNum32.go @@ -4,7 +4,8 @@ import ( "io" ) -func MoveRegNum32(w io.ByteWriter, register byte, number uint32) { +// MoveRegNum32 moves a 32 bit integer into the given register. +func MoveRegNum32(w io.ByteWriter, register uint8, number uint32) { w.WriteByte(0xb8 + register) AppendUint32(w, number) } diff --git a/src/asm/x64/Syscall.go b/src/asm/x64/Syscall.go index 3c22056..145f5a3 100644 --- a/src/asm/x64/Syscall.go +++ b/src/asm/x64/Syscall.go @@ -4,6 +4,7 @@ import ( "io" ) +// Syscall is the primary way to communicate with the OS kernel. func Syscall(w io.ByteWriter) { w.WriteByte(0x0f) w.WriteByte(0x05) diff --git a/src/build/Build.go b/src/build/Build.go index 58bc7ff..5ecf62d 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -7,7 +7,6 @@ import ( "strings" "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/asm/x64" "git.akyoto.dev/cli/q/src/directory" "git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/errors" @@ -40,26 +39,26 @@ func (build *Build) Run() error { return err } + list := asm.InstructionList{} + + list.MoveRegisterNumber(register.Syscall0, syscall.Write) + list.MoveRegisterNumber(register.Syscall1, 1) + list.MoveRegisterNumber(register.Syscall2, 0x4000a2) + list.MoveRegisterNumber(register.Syscall3, 6) + list.Syscall() + + list.MoveRegisterNumber(register.Syscall0, syscall.Exit) + list.MoveRegisterNumber(register.Syscall1, 0) + list.Syscall() + + result := list.Finalize() + result.Data.WriteString("Hello\n") + if !build.WriteExecutable { return nil } - final := asm.Assembler{} - code := &final.Code - data := &final.Data - - x64.MoveRegNum32(code, register.Syscall0, syscall.Write) - x64.MoveRegNum32(code, register.Syscall1, 1) - x64.MoveRegNum32(code, register.Syscall2, 0x4000a2) - x64.MoveRegNum32(code, register.Syscall3, 6) - x64.Syscall(code) - - x64.MoveRegNum32(code, register.Syscall0, syscall.Exit) - x64.MoveRegNum32(code, register.Syscall1, 0) - x64.Syscall(code) - - data.WriteString("Hello\n") - return writeToDisk(build.Executable(), code.Bytes(), data.Bytes()) + return writeToDisk(build.Executable(), result.Code.Bytes(), result.Data.Bytes()) } // Compile compiles all the functions. diff --git a/src/register/General.go b/src/register/General.go index 4358735..d22c83a 100644 --- a/src/register/General.go +++ b/src/register/General.go @@ -1,7 +1,11 @@ package register +import "fmt" + +type Register uint8 + const ( - R0 = iota + R0 Register = iota R1 R2 R3 @@ -18,3 +22,7 @@ const ( R14 R15 ) + +func (r Register) String() string { + return fmt.Sprintf("r%d", r) +} From a54c62f6e0843d83baa70810ceeb3015754d65d6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 21 Oct 2023 17:46:20 +0200 Subject: [PATCH 0024/1012] Implemented instruction lists --- README.md | 12 ++++++-- src/asm/Base.go | 23 ++++++++++++++ src/asm/Instruction.go | 8 +++++ src/asm/InstructionList.go | 47 +++++++++++++++++++++++++++++ src/asm/Mnemonic.go | 21 +++++++++++++ src/asm/RegisterNumber.go | 26 ++++++++++++++++ src/asm/{Assembler.go => Result.go} | 2 +- src/asm/x64/AppendUint32.go | 2 +- src/asm/x64/MoveRegNum32.go | 3 +- src/asm/x64/Syscall.go | 1 + src/build/Build.go | 33 ++++++++++---------- src/register/General.go | 10 +++++- 12 files changed, 165 insertions(+), 23 deletions(-) create mode 100644 src/asm/Base.go create mode 100644 src/asm/Instruction.go create mode 100644 src/asm/InstructionList.go create mode 100644 src/asm/Mnemonic.go create mode 100644 src/asm/RegisterNumber.go rename src/asm/{Assembler.go => Result.go} (74%) diff --git a/README.md b/README.md index 3df933b..4154f99 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ A simple programming language. ## Features -* 🔥 Fast compilation -* 📦 Small binaries +* Fast compilation +* Small binaries ## Installation @@ -17,10 +17,18 @@ go build ## Usage +Build a Linux ELF executable from `examples/hello`: + ```shell ./q build examples/hello ``` +Run the generated executable: + +```shell +./examples/hello/hello +``` + ## License Please see the [license documentation](https://akyoto.dev/license). diff --git a/src/asm/Base.go b/src/asm/Base.go new file mode 100644 index 0000000..a67c670 --- /dev/null +++ b/src/asm/Base.go @@ -0,0 +1,23 @@ +package asm + +import ( + "io" + + "git.akyoto.dev/cli/q/src/asm/x64" +) + +// Base represents the data that is common among all instructions. +type Base struct { + Mnemonic Mnemonic +} + +func (x *Base) Write(w io.ByteWriter) { + switch x.Mnemonic { + case SYSCALL: + x64.Syscall(w) + } +} + +func (x *Base) String() string { + return x.Mnemonic.String() +} diff --git a/src/asm/Instruction.go b/src/asm/Instruction.go new file mode 100644 index 0000000..db13758 --- /dev/null +++ b/src/asm/Instruction.go @@ -0,0 +1,8 @@ +package asm + +import "io" + +type Instruction interface { + Write(io.ByteWriter) + String() string +} diff --git a/src/asm/InstructionList.go b/src/asm/InstructionList.go new file mode 100644 index 0000000..cc7ab7a --- /dev/null +++ b/src/asm/InstructionList.go @@ -0,0 +1,47 @@ +package asm + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/register" +) + +type InstructionList struct { + Instructions []Instruction +} + +// Finalize generates the final assembly code. +func (list *InstructionList) Finalize() *Result { + final := Result{} + + for _, instr := range list.Instructions { + instr.Write(&final.Code) + fmt.Println(instr.String()) + } + + return &final +} + +func (list *InstructionList) MoveRegisterNumber(reg register.Register, number uint64) { + list.addRegisterNumber(MOV, reg, number) +} + +func (list *InstructionList) Syscall() { + list.add(SYSCALL) +} + +// add adds an instruction without any operands. +func (list *InstructionList) add(mnemonic Mnemonic) { + list.Instructions = append(list.Instructions, &Base{Mnemonic: mnemonic}) +} + +// addRegisterNumber adds an instruction using a register and a number. +func (list *InstructionList) addRegisterNumber(mnemonic Mnemonic, reg register.Register, number uint64) { + list.Instructions = append(list.Instructions, &RegisterNumber{ + Base: Base{ + Mnemonic: mnemonic, + }, + Register: reg, + Number: number, + }) +} diff --git a/src/asm/Mnemonic.go b/src/asm/Mnemonic.go new file mode 100644 index 0000000..dea828b --- /dev/null +++ b/src/asm/Mnemonic.go @@ -0,0 +1,21 @@ +package asm + +type Mnemonic uint8 + +const ( + NONE Mnemonic = iota + MOV + SYSCALL +) + +func (m Mnemonic) String() string { + switch m { + case MOV: + return "mov" + + case SYSCALL: + return "syscall" + } + + return "NONE" +} diff --git a/src/asm/RegisterNumber.go b/src/asm/RegisterNumber.go new file mode 100644 index 0000000..5637079 --- /dev/null +++ b/src/asm/RegisterNumber.go @@ -0,0 +1,26 @@ +package asm + +import ( + "fmt" + "io" + + "git.akyoto.dev/cli/q/src/asm/x64" + "git.akyoto.dev/cli/q/src/register" +) + +type RegisterNumber struct { + Base + Register register.Register + Number uint64 +} + +func (x *RegisterNumber) Write(w io.ByteWriter) { + switch x.Mnemonic { + case MOV: + x64.MoveRegNum32(w, uint8(x.Register), uint32(x.Number)) + } +} + +func (x *RegisterNumber) String() string { + return fmt.Sprintf("%s %s, %x", x.Mnemonic, x.Register, x.Number) +} diff --git a/src/asm/Assembler.go b/src/asm/Result.go similarity index 74% rename from src/asm/Assembler.go rename to src/asm/Result.go index 8369cc5..248dc23 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Result.go @@ -2,7 +2,7 @@ package asm import "bytes" -type Assembler struct { +type Result struct { Code bytes.Buffer Data bytes.Buffer } diff --git a/src/asm/x64/AppendUint32.go b/src/asm/x64/AppendUint32.go index 52cb813..78b9040 100644 --- a/src/asm/x64/AppendUint32.go +++ b/src/asm/x64/AppendUint32.go @@ -2,7 +2,7 @@ package x64 import "io" -// AppendUint32 appends a 32 bit number in Little Endian to the given writer. +// AppendUint32 appends a 32 bit integer in Little Endian to the given writer. func AppendUint32(w io.ByteWriter, number uint32) { w.WriteByte(byte(number)) w.WriteByte(byte(number >> 8)) diff --git a/src/asm/x64/MoveRegNum32.go b/src/asm/x64/MoveRegNum32.go index 64a22bb..126dcf5 100644 --- a/src/asm/x64/MoveRegNum32.go +++ b/src/asm/x64/MoveRegNum32.go @@ -4,7 +4,8 @@ import ( "io" ) -func MoveRegNum32(w io.ByteWriter, register byte, number uint32) { +// MoveRegNum32 moves a 32 bit integer into the given register. +func MoveRegNum32(w io.ByteWriter, register uint8, number uint32) { w.WriteByte(0xb8 + register) AppendUint32(w, number) } diff --git a/src/asm/x64/Syscall.go b/src/asm/x64/Syscall.go index 3c22056..145f5a3 100644 --- a/src/asm/x64/Syscall.go +++ b/src/asm/x64/Syscall.go @@ -4,6 +4,7 @@ import ( "io" ) +// Syscall is the primary way to communicate with the OS kernel. func Syscall(w io.ByteWriter) { w.WriteByte(0x0f) w.WriteByte(0x05) diff --git a/src/build/Build.go b/src/build/Build.go index 58bc7ff..5ecf62d 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -7,7 +7,6 @@ import ( "strings" "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/asm/x64" "git.akyoto.dev/cli/q/src/directory" "git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/errors" @@ -40,26 +39,26 @@ func (build *Build) Run() error { return err } + list := asm.InstructionList{} + + list.MoveRegisterNumber(register.Syscall0, syscall.Write) + list.MoveRegisterNumber(register.Syscall1, 1) + list.MoveRegisterNumber(register.Syscall2, 0x4000a2) + list.MoveRegisterNumber(register.Syscall3, 6) + list.Syscall() + + list.MoveRegisterNumber(register.Syscall0, syscall.Exit) + list.MoveRegisterNumber(register.Syscall1, 0) + list.Syscall() + + result := list.Finalize() + result.Data.WriteString("Hello\n") + if !build.WriteExecutable { return nil } - final := asm.Assembler{} - code := &final.Code - data := &final.Data - - x64.MoveRegNum32(code, register.Syscall0, syscall.Write) - x64.MoveRegNum32(code, register.Syscall1, 1) - x64.MoveRegNum32(code, register.Syscall2, 0x4000a2) - x64.MoveRegNum32(code, register.Syscall3, 6) - x64.Syscall(code) - - x64.MoveRegNum32(code, register.Syscall0, syscall.Exit) - x64.MoveRegNum32(code, register.Syscall1, 0) - x64.Syscall(code) - - data.WriteString("Hello\n") - return writeToDisk(build.Executable(), code.Bytes(), data.Bytes()) + return writeToDisk(build.Executable(), result.Code.Bytes(), result.Data.Bytes()) } // Compile compiles all the functions. diff --git a/src/register/General.go b/src/register/General.go index 4358735..d22c83a 100644 --- a/src/register/General.go +++ b/src/register/General.go @@ -1,7 +1,11 @@ package register +import "fmt" + +type Register uint8 + const ( - R0 = iota + R0 Register = iota R1 R2 R3 @@ -18,3 +22,7 @@ const ( R14 R15 ) + +func (r Register) String() string { + return fmt.Sprintf("r%d", r) +} From 8e193c69b67144fa5316f896f184195d1bb597c8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 23 Oct 2023 12:37:20 +0200 Subject: [PATCH 0025/1012] Improved assembler --- README.md | 18 ++- src/cli/cli_test.go => main_test.go | 9 +- src/asm/Assembler.go | 42 +++++++ src/asm/Base.go | 23 ---- src/asm/Instruction.go | 38 ++++++- src/asm/InstructionList.go | 47 -------- src/asm/RegisterNumber.go | 26 ----- src/asm/Result.go | 1 + src/asm/x64/Call.go | 10 ++ src/asm/x64/{MoveRegNum32.go => Move.go} | 2 +- src/asm/x64/Return.go | 9 ++ src/asm/x64/{AppendUint32.go => x64.go} | 4 +- src/build/Build.go | 28 ++--- src/cpu/CPU.go | 106 ++++++++++++++++++ src/cpu/List.go | 29 +++++ src/cpu/Register.go | 39 +++++++ src/directory/Walk.go | 2 +- .../{InvalidPath.go => InvalidDirectory.go} | 1 + src/errors/RegisterInUse.go | 14 +++ src/register/{General.go => ID.go} | 7 +- src/register/Named.go | 12 -- src/syscall/syscall_linux.go | 1 + 22 files changed, 329 insertions(+), 139 deletions(-) rename src/cli/cli_test.go => main_test.go (75%) create mode 100644 src/asm/Assembler.go delete mode 100644 src/asm/Base.go delete mode 100644 src/asm/InstructionList.go delete mode 100644 src/asm/RegisterNumber.go create mode 100644 src/asm/x64/Call.go rename src/asm/x64/{MoveRegNum32.go => Move.go} (88%) create mode 100644 src/asm/x64/Return.go rename src/asm/x64/{AppendUint32.go => x64.go} (59%) create mode 100644 src/cpu/CPU.go create mode 100644 src/cpu/List.go create mode 100644 src/cpu/Register.go rename src/errors/{InvalidPath.go => InvalidDirectory.go} (73%) create mode 100644 src/errors/RegisterInUse.go rename src/register/{General.go => ID.go} (57%) delete mode 100644 src/register/Named.go diff --git a/README.md b/README.md index 4154f99..9cbc7db 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,26 @@ Build a Linux ELF executable from `examples/hello`: ```shell ./q build examples/hello +./examples/hello/hello ``` -Run the generated executable: +## Source + +- [main.go](main.go) +- [src/cli/Main.go](src/cli/Main.go) +- [src/cli/Build.go](src/cli/Build.go) +- [src/build/Build.go](src/build/Build.go) + +## Tests ```shell -./examples/hello/hello +go test -coverpkg=./... +``` + +## Benchmarks + +```shell +go test -bench=. -benchmem ``` ## License diff --git a/src/cli/cli_test.go b/main_test.go similarity index 75% rename from src/cli/cli_test.go rename to main_test.go index eb51e18..b25bf96 100644 --- a/src/cli/cli_test.go +++ b/main_test.go @@ -1,4 +1,4 @@ -package cli_test +package main_test import ( "io" @@ -26,9 +26,10 @@ func TestCLI(t *testing.T) { {[]string{}, 2}, {[]string{"invalid"}, 2}, {[]string{"system"}, 0}, - {[]string{"build", "non-existing-directory"}, 1}, - {[]string{"build", "examples/hello/hello.q"}, 1}, - {[]string{"build", "examples/hello", "--invalid"}, 2}, + // {[]string{"build", "non-existing-directory"}, 1}, + // {[]string{"build", "examples/hello/hello.q"}, 1}, + // {[]string{"build", "examples/hello", "--invalid"}, 2}, + // {[]string{"build", "examples/hello", "--dry"}, 0}, } for _, test := range tests { diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go new file mode 100644 index 0000000..8f0e6d3 --- /dev/null +++ b/src/asm/Assembler.go @@ -0,0 +1,42 @@ +package asm + +import ( + "git.akyoto.dev/cli/q/src/register" +) + +// Assembler contains a list of instructions. +type Assembler struct { + Instructions []Instruction +} + +// New creates a new assembler. +func New() *Assembler { + return &Assembler{ + Instructions: make([]Instruction, 0, 8), + } +} + +// Finalize generates the final machine code. +func (list *Assembler) Finalize() *Result { + final := Result{} + + for _, instr := range list.Instructions { + instr.Write(&final.Code) + } + + return &final +} + +func (list *Assembler) MoveRegisterNumber(reg register.ID, number uint64) { + list.Instructions = append(list.Instructions, Instruction{ + Mnemonic: MOV, + Destination: reg, + Number: number, + }) +} + +func (list *Assembler) Syscall() { + list.Instructions = append(list.Instructions, Instruction{ + Mnemonic: SYSCALL, + }) +} diff --git a/src/asm/Base.go b/src/asm/Base.go deleted file mode 100644 index a67c670..0000000 --- a/src/asm/Base.go +++ /dev/null @@ -1,23 +0,0 @@ -package asm - -import ( - "io" - - "git.akyoto.dev/cli/q/src/asm/x64" -) - -// Base represents the data that is common among all instructions. -type Base struct { - Mnemonic Mnemonic -} - -func (x *Base) Write(w io.ByteWriter) { - switch x.Mnemonic { - case SYSCALL: - x64.Syscall(w) - } -} - -func (x *Base) String() string { - return x.Mnemonic.String() -} diff --git a/src/asm/Instruction.go b/src/asm/Instruction.go index db13758..210f253 100644 --- a/src/asm/Instruction.go +++ b/src/asm/Instruction.go @@ -1,8 +1,38 @@ package asm -import "io" +import ( + "fmt" + "io" -type Instruction interface { - Write(io.ByteWriter) - String() string + "git.akyoto.dev/cli/q/src/asm/x64" + "git.akyoto.dev/cli/q/src/register" +) + +// Instruction represents a single instruction which can be converted to machine code. +type Instruction struct { + Mnemonic Mnemonic + Source register.ID + Destination register.ID + Number uint64 +} + +// Write writes the machine code of the instruction. +func (x *Instruction) Write(w io.ByteWriter) { + switch x.Mnemonic { + case MOV: + x64.MoveRegNum32(w, uint8(x.Destination), uint32(x.Number)) + case SYSCALL: + x64.Syscall(w) + } +} + +func (x *Instruction) String() string { + switch x.Mnemonic { + case MOV: + return fmt.Sprintf("%s %s, %x", x.Mnemonic, x.Destination, x.Number) + case SYSCALL: + return x.Mnemonic.String() + default: + return "" + } } diff --git a/src/asm/InstructionList.go b/src/asm/InstructionList.go deleted file mode 100644 index cc7ab7a..0000000 --- a/src/asm/InstructionList.go +++ /dev/null @@ -1,47 +0,0 @@ -package asm - -import ( - "fmt" - - "git.akyoto.dev/cli/q/src/register" -) - -type InstructionList struct { - Instructions []Instruction -} - -// Finalize generates the final assembly code. -func (list *InstructionList) Finalize() *Result { - final := Result{} - - for _, instr := range list.Instructions { - instr.Write(&final.Code) - fmt.Println(instr.String()) - } - - return &final -} - -func (list *InstructionList) MoveRegisterNumber(reg register.Register, number uint64) { - list.addRegisterNumber(MOV, reg, number) -} - -func (list *InstructionList) Syscall() { - list.add(SYSCALL) -} - -// add adds an instruction without any operands. -func (list *InstructionList) add(mnemonic Mnemonic) { - list.Instructions = append(list.Instructions, &Base{Mnemonic: mnemonic}) -} - -// addRegisterNumber adds an instruction using a register and a number. -func (list *InstructionList) addRegisterNumber(mnemonic Mnemonic, reg register.Register, number uint64) { - list.Instructions = append(list.Instructions, &RegisterNumber{ - Base: Base{ - Mnemonic: mnemonic, - }, - Register: reg, - Number: number, - }) -} diff --git a/src/asm/RegisterNumber.go b/src/asm/RegisterNumber.go deleted file mode 100644 index 5637079..0000000 --- a/src/asm/RegisterNumber.go +++ /dev/null @@ -1,26 +0,0 @@ -package asm - -import ( - "fmt" - "io" - - "git.akyoto.dev/cli/q/src/asm/x64" - "git.akyoto.dev/cli/q/src/register" -) - -type RegisterNumber struct { - Base - Register register.Register - Number uint64 -} - -func (x *RegisterNumber) Write(w io.ByteWriter) { - switch x.Mnemonic { - case MOV: - x64.MoveRegNum32(w, uint8(x.Register), uint32(x.Number)) - } -} - -func (x *RegisterNumber) String() string { - return fmt.Sprintf("%s %s, %x", x.Mnemonic, x.Register, x.Number) -} diff --git a/src/asm/Result.go b/src/asm/Result.go index 248dc23..62eedad 100644 --- a/src/asm/Result.go +++ b/src/asm/Result.go @@ -2,6 +2,7 @@ package asm import "bytes" +// Result is the compilation result and contains the machine code as well as the data. type Result struct { Code bytes.Buffer Data bytes.Buffer diff --git a/src/asm/x64/Call.go b/src/asm/x64/Call.go new file mode 100644 index 0000000..d3958bf --- /dev/null +++ b/src/asm/x64/Call.go @@ -0,0 +1,10 @@ +package x64 + +import "io" + +// Call places the return address on the top of the stack and continues +// program flow at the new address. The address is relative to the next instruction. +func Call(w io.ByteWriter, address uint32) { + w.WriteByte(0xe8) + appendUint32(w, address) +} diff --git a/src/asm/x64/MoveRegNum32.go b/src/asm/x64/Move.go similarity index 88% rename from src/asm/x64/MoveRegNum32.go rename to src/asm/x64/Move.go index 126dcf5..d130195 100644 --- a/src/asm/x64/MoveRegNum32.go +++ b/src/asm/x64/Move.go @@ -7,5 +7,5 @@ import ( // MoveRegNum32 moves a 32 bit integer into the given register. func MoveRegNum32(w io.ByteWriter, register uint8, number uint32) { w.WriteByte(0xb8 + register) - AppendUint32(w, number) + appendUint32(w, number) } diff --git a/src/asm/x64/Return.go b/src/asm/x64/Return.go new file mode 100644 index 0000000..47a5b34 --- /dev/null +++ b/src/asm/x64/Return.go @@ -0,0 +1,9 @@ +package x64 + +import "io" + +// Return transfers program control to a return address located on the top of the stack. +// The address is usually placed on the stack by a Call instruction. +func Return(w io.ByteWriter) { + w.WriteByte(0xc3) +} diff --git a/src/asm/x64/AppendUint32.go b/src/asm/x64/x64.go similarity index 59% rename from src/asm/x64/AppendUint32.go rename to src/asm/x64/x64.go index 78b9040..f6ab914 100644 --- a/src/asm/x64/AppendUint32.go +++ b/src/asm/x64/x64.go @@ -2,8 +2,8 @@ package x64 import "io" -// AppendUint32 appends a 32 bit integer in Little Endian to the given writer. -func AppendUint32(w io.ByteWriter, number uint32) { +// appendUint32 appends a 32 bit integer in Little Endian to the given writer. +func appendUint32(w io.ByteWriter, number uint32) { w.WriteByte(byte(number)) w.WriteByte(byte(number >> 8)) w.WriteByte(byte(number >> 16)) diff --git a/src/build/Build.go b/src/build/Build.go index 5ecf62d..88d08d5 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -33,25 +33,25 @@ func New(directory string) *Build { // Run parses the input files and generates an executable file. func (build *Build) Run() error { - err := build.Compile() + // err := build.Compile() - if err != nil { - return err - } + // if err != nil { + // return err + // } - list := asm.InstructionList{} + a := asm.New() - list.MoveRegisterNumber(register.Syscall0, syscall.Write) - list.MoveRegisterNumber(register.Syscall1, 1) - list.MoveRegisterNumber(register.Syscall2, 0x4000a2) - list.MoveRegisterNumber(register.Syscall3, 6) - list.Syscall() + a.MoveRegisterNumber(register.Syscall0, syscall.Write) + a.MoveRegisterNumber(register.Syscall1, 1) + a.MoveRegisterNumber(register.Syscall2, 0x4000a2) + a.MoveRegisterNumber(register.Syscall3, 6) + a.Syscall() - list.MoveRegisterNumber(register.Syscall0, syscall.Exit) - list.MoveRegisterNumber(register.Syscall1, 0) - list.Syscall() + a.MoveRegisterNumber(register.Syscall0, syscall.Exit) + a.MoveRegisterNumber(register.Syscall1, 0) + a.Syscall() - result := list.Finalize() + result := a.Finalize() result.Data.WriteString("Hello\n") if !build.WriteExecutable { diff --git a/src/cpu/CPU.go b/src/cpu/CPU.go new file mode 100644 index 0000000..ea16af0 --- /dev/null +++ b/src/cpu/CPU.go @@ -0,0 +1,106 @@ +package cpu + +import "git.akyoto.dev/cli/q/src/register" + +// CPU manages the allocation state of registers. +type CPU struct { + All List + General List + Call List + Syscall List +} + +// New creates a new CPU state. +func New() *CPU { + // Rather than doing lots of mini allocations + // we'll allocate memory for all registers at once. + registers := [16]Register{ + {ID: register.R0}, + {ID: register.R1}, + {ID: register.R2}, + {ID: register.R3}, + {ID: register.R4}, + {ID: register.R5}, + {ID: register.R6}, + {ID: register.R7}, + {ID: register.R8}, + {ID: register.R9}, + {ID: register.R10}, + {ID: register.R11}, + {ID: register.R12}, + {ID: register.R13}, + {ID: register.R14}, + {ID: register.R15}, + } + + rax := ®isters[0] + rcx := ®isters[1] + rdx := ®isters[2] + rbx := ®isters[3] + rsp := ®isters[4] + rbp := ®isters[5] + rsi := ®isters[6] + rdi := ®isters[7] + r8 := ®isters[8] + r9 := ®isters[9] + r10 := ®isters[10] + r11 := ®isters[11] + r12 := ®isters[12] + r13 := ®isters[13] + r14 := ®isters[14] + r15 := ®isters[15] + + // Register configuration + return &CPU{ + All: List{ + rax, + rcx, + rdx, + rbx, + rsp, + rbp, + rsi, + rdi, + r8, + r9, + r10, + r11, + r12, + r13, + r14, + r15, + }, + General: List{ + rcx, + rbx, + rbp, + r11, + r12, + r13, + r14, + r15, + }, + Call: List{ + rdi, + rsi, + rdx, + r10, + r8, + r9, + }, + Syscall: List{ + rax, + rdi, + rsi, + rdx, + r10, + r8, + r9, + }, + } +} + +// ByID returns the register with the given ID. +func (cpu *CPU) ByID(id register.ID) *Register { + return cpu.All[id] +} diff --git a/src/cpu/List.go b/src/cpu/List.go new file mode 100644 index 0000000..e9234aa --- /dev/null +++ b/src/cpu/List.go @@ -0,0 +1,29 @@ +package cpu + +// List is a list of registers. +type List []*Register + +// FindFree tries to find a free register +// and returns nil when all are currently occupied. +func (registers List) FindFree() *Register { + for _, register := range registers { + if register.IsFree() { + return register + } + } + + return nil +} + +// InUse returns a list of registers that are currently in use. +func (registers List) InUse() List { + var inUse List + + for _, register := range registers { + if !register.IsFree() { + inUse = append(inUse, register) + } + } + + return inUse +} diff --git a/src/cpu/Register.go b/src/cpu/Register.go new file mode 100644 index 0000000..b11add9 --- /dev/null +++ b/src/cpu/Register.go @@ -0,0 +1,39 @@ +package cpu + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/register" +) + +// Register represents a single CPU register. +type Register struct { + ID register.ID + user fmt.Stringer +} + +// Use marks the register as used by the given object. +func (register *Register) Use(obj fmt.Stringer) error { + if register.user != nil { + return &errors.RegisterInUse{Register: register.ID.String(), User: register.user.String()} + } + + register.user = obj + return nil +} + +// Free frees the register so that it can be used for new calculations. +func (register *Register) Free() { + register.user = nil +} + +// IsFree returns true if the register is not in use. +func (register *Register) IsFree() bool { + return register.user == nil +} + +// String returns a human-readable representation of the register. +func (register *Register) String() string { + return fmt.Sprintf("%s%s%v", register.ID, "=", register.user) +} diff --git a/src/directory/Walk.go b/src/directory/Walk.go index 5595e3e..f871c64 100644 --- a/src/directory/Walk.go +++ b/src/directory/Walk.go @@ -5,7 +5,7 @@ import ( "unsafe" ) -const blockSize = 8 << 10 +const blockSize = 4096 // Walk calls your callback function for every file name inside the directory. // It doesn't distinguish between files and directories. diff --git a/src/errors/InvalidPath.go b/src/errors/InvalidDirectory.go similarity index 73% rename from src/errors/InvalidPath.go rename to src/errors/InvalidDirectory.go index 0b71fcf..161cdf0 100644 --- a/src/errors/InvalidPath.go +++ b/src/errors/InvalidDirectory.go @@ -2,6 +2,7 @@ package errors import "fmt" +// InvalidDirectory errors are returned when the specified path is not a directory. type InvalidDirectory struct { Path string } diff --git a/src/errors/RegisterInUse.go b/src/errors/RegisterInUse.go new file mode 100644 index 0000000..27f40a5 --- /dev/null +++ b/src/errors/RegisterInUse.go @@ -0,0 +1,14 @@ +package errors + +import "fmt" + +// RegisterInUse errors are returned when a register is already in use. +type RegisterInUse struct { + Register string + User string +} + +// Error implements the text representation. +func (err *RegisterInUse) Error() string { + return fmt.Sprintf("Register '%s' already used by '%s'", err.Register, err.User) +} diff --git a/src/register/General.go b/src/register/ID.go similarity index 57% rename from src/register/General.go rename to src/register/ID.go index d22c83a..08ac82b 100644 --- a/src/register/General.go +++ b/src/register/ID.go @@ -2,10 +2,11 @@ package register import "fmt" -type Register uint8 +// ID represents the number of the register. +type ID uint8 const ( - R0 Register = iota + R0 ID = iota R1 R2 R3 @@ -23,6 +24,6 @@ const ( R15 ) -func (r Register) String() string { +func (r ID) String() string { return fmt.Sprintf("r%d", r) } diff --git a/src/register/Named.go b/src/register/Named.go deleted file mode 100644 index 80d2ae7..0000000 --- a/src/register/Named.go +++ /dev/null @@ -1,12 +0,0 @@ -package register - -const ( - RAX = R0 - RCX = R1 - RDX = R2 - RBX = R3 - RSP = R4 - RBP = R5 - RSI = R6 - RDI = R7 -) diff --git a/src/syscall/syscall_linux.go b/src/syscall/syscall_linux.go index c2175b8..7c5d6f8 100644 --- a/src/syscall/syscall_linux.go +++ b/src/syscall/syscall_linux.go @@ -1,5 +1,6 @@ package syscall +// Linux syscalls const ( Read = iota Write From ab48a86ccd04661f9f6dc8996802a3b0fccac5da Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 23 Oct 2023 12:37:20 +0200 Subject: [PATCH 0026/1012] Improved assembler --- README.md | 18 ++- src/cli/cli_test.go => main_test.go | 9 +- src/asm/Assembler.go | 42 +++++++ src/asm/Base.go | 23 ---- src/asm/Instruction.go | 38 ++++++- src/asm/InstructionList.go | 47 -------- src/asm/RegisterNumber.go | 26 ----- src/asm/Result.go | 1 + src/asm/x64/Call.go | 10 ++ src/asm/x64/{MoveRegNum32.go => Move.go} | 2 +- src/asm/x64/Return.go | 9 ++ src/asm/x64/{AppendUint32.go => x64.go} | 4 +- src/build/Build.go | 28 ++--- src/cpu/CPU.go | 106 ++++++++++++++++++ src/cpu/List.go | 29 +++++ src/cpu/Register.go | 39 +++++++ src/directory/Walk.go | 2 +- .../{InvalidPath.go => InvalidDirectory.go} | 1 + src/errors/RegisterInUse.go | 14 +++ src/register/{General.go => ID.go} | 7 +- src/register/Named.go | 12 -- src/syscall/syscall_linux.go | 1 + 22 files changed, 329 insertions(+), 139 deletions(-) rename src/cli/cli_test.go => main_test.go (75%) create mode 100644 src/asm/Assembler.go delete mode 100644 src/asm/Base.go delete mode 100644 src/asm/InstructionList.go delete mode 100644 src/asm/RegisterNumber.go create mode 100644 src/asm/x64/Call.go rename src/asm/x64/{MoveRegNum32.go => Move.go} (88%) create mode 100644 src/asm/x64/Return.go rename src/asm/x64/{AppendUint32.go => x64.go} (59%) create mode 100644 src/cpu/CPU.go create mode 100644 src/cpu/List.go create mode 100644 src/cpu/Register.go rename src/errors/{InvalidPath.go => InvalidDirectory.go} (73%) create mode 100644 src/errors/RegisterInUse.go rename src/register/{General.go => ID.go} (57%) delete mode 100644 src/register/Named.go diff --git a/README.md b/README.md index 4154f99..9cbc7db 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,26 @@ Build a Linux ELF executable from `examples/hello`: ```shell ./q build examples/hello +./examples/hello/hello ``` -Run the generated executable: +## Source + +- [main.go](main.go) +- [src/cli/Main.go](src/cli/Main.go) +- [src/cli/Build.go](src/cli/Build.go) +- [src/build/Build.go](src/build/Build.go) + +## Tests ```shell -./examples/hello/hello +go test -coverpkg=./... +``` + +## Benchmarks + +```shell +go test -bench=. -benchmem ``` ## License diff --git a/src/cli/cli_test.go b/main_test.go similarity index 75% rename from src/cli/cli_test.go rename to main_test.go index eb51e18..b25bf96 100644 --- a/src/cli/cli_test.go +++ b/main_test.go @@ -1,4 +1,4 @@ -package cli_test +package main_test import ( "io" @@ -26,9 +26,10 @@ func TestCLI(t *testing.T) { {[]string{}, 2}, {[]string{"invalid"}, 2}, {[]string{"system"}, 0}, - {[]string{"build", "non-existing-directory"}, 1}, - {[]string{"build", "examples/hello/hello.q"}, 1}, - {[]string{"build", "examples/hello", "--invalid"}, 2}, + // {[]string{"build", "non-existing-directory"}, 1}, + // {[]string{"build", "examples/hello/hello.q"}, 1}, + // {[]string{"build", "examples/hello", "--invalid"}, 2}, + // {[]string{"build", "examples/hello", "--dry"}, 0}, } for _, test := range tests { diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go new file mode 100644 index 0000000..8f0e6d3 --- /dev/null +++ b/src/asm/Assembler.go @@ -0,0 +1,42 @@ +package asm + +import ( + "git.akyoto.dev/cli/q/src/register" +) + +// Assembler contains a list of instructions. +type Assembler struct { + Instructions []Instruction +} + +// New creates a new assembler. +func New() *Assembler { + return &Assembler{ + Instructions: make([]Instruction, 0, 8), + } +} + +// Finalize generates the final machine code. +func (list *Assembler) Finalize() *Result { + final := Result{} + + for _, instr := range list.Instructions { + instr.Write(&final.Code) + } + + return &final +} + +func (list *Assembler) MoveRegisterNumber(reg register.ID, number uint64) { + list.Instructions = append(list.Instructions, Instruction{ + Mnemonic: MOV, + Destination: reg, + Number: number, + }) +} + +func (list *Assembler) Syscall() { + list.Instructions = append(list.Instructions, Instruction{ + Mnemonic: SYSCALL, + }) +} diff --git a/src/asm/Base.go b/src/asm/Base.go deleted file mode 100644 index a67c670..0000000 --- a/src/asm/Base.go +++ /dev/null @@ -1,23 +0,0 @@ -package asm - -import ( - "io" - - "git.akyoto.dev/cli/q/src/asm/x64" -) - -// Base represents the data that is common among all instructions. -type Base struct { - Mnemonic Mnemonic -} - -func (x *Base) Write(w io.ByteWriter) { - switch x.Mnemonic { - case SYSCALL: - x64.Syscall(w) - } -} - -func (x *Base) String() string { - return x.Mnemonic.String() -} diff --git a/src/asm/Instruction.go b/src/asm/Instruction.go index db13758..210f253 100644 --- a/src/asm/Instruction.go +++ b/src/asm/Instruction.go @@ -1,8 +1,38 @@ package asm -import "io" +import ( + "fmt" + "io" -type Instruction interface { - Write(io.ByteWriter) - String() string + "git.akyoto.dev/cli/q/src/asm/x64" + "git.akyoto.dev/cli/q/src/register" +) + +// Instruction represents a single instruction which can be converted to machine code. +type Instruction struct { + Mnemonic Mnemonic + Source register.ID + Destination register.ID + Number uint64 +} + +// Write writes the machine code of the instruction. +func (x *Instruction) Write(w io.ByteWriter) { + switch x.Mnemonic { + case MOV: + x64.MoveRegNum32(w, uint8(x.Destination), uint32(x.Number)) + case SYSCALL: + x64.Syscall(w) + } +} + +func (x *Instruction) String() string { + switch x.Mnemonic { + case MOV: + return fmt.Sprintf("%s %s, %x", x.Mnemonic, x.Destination, x.Number) + case SYSCALL: + return x.Mnemonic.String() + default: + return "" + } } diff --git a/src/asm/InstructionList.go b/src/asm/InstructionList.go deleted file mode 100644 index cc7ab7a..0000000 --- a/src/asm/InstructionList.go +++ /dev/null @@ -1,47 +0,0 @@ -package asm - -import ( - "fmt" - - "git.akyoto.dev/cli/q/src/register" -) - -type InstructionList struct { - Instructions []Instruction -} - -// Finalize generates the final assembly code. -func (list *InstructionList) Finalize() *Result { - final := Result{} - - for _, instr := range list.Instructions { - instr.Write(&final.Code) - fmt.Println(instr.String()) - } - - return &final -} - -func (list *InstructionList) MoveRegisterNumber(reg register.Register, number uint64) { - list.addRegisterNumber(MOV, reg, number) -} - -func (list *InstructionList) Syscall() { - list.add(SYSCALL) -} - -// add adds an instruction without any operands. -func (list *InstructionList) add(mnemonic Mnemonic) { - list.Instructions = append(list.Instructions, &Base{Mnemonic: mnemonic}) -} - -// addRegisterNumber adds an instruction using a register and a number. -func (list *InstructionList) addRegisterNumber(mnemonic Mnemonic, reg register.Register, number uint64) { - list.Instructions = append(list.Instructions, &RegisterNumber{ - Base: Base{ - Mnemonic: mnemonic, - }, - Register: reg, - Number: number, - }) -} diff --git a/src/asm/RegisterNumber.go b/src/asm/RegisterNumber.go deleted file mode 100644 index 5637079..0000000 --- a/src/asm/RegisterNumber.go +++ /dev/null @@ -1,26 +0,0 @@ -package asm - -import ( - "fmt" - "io" - - "git.akyoto.dev/cli/q/src/asm/x64" - "git.akyoto.dev/cli/q/src/register" -) - -type RegisterNumber struct { - Base - Register register.Register - Number uint64 -} - -func (x *RegisterNumber) Write(w io.ByteWriter) { - switch x.Mnemonic { - case MOV: - x64.MoveRegNum32(w, uint8(x.Register), uint32(x.Number)) - } -} - -func (x *RegisterNumber) String() string { - return fmt.Sprintf("%s %s, %x", x.Mnemonic, x.Register, x.Number) -} diff --git a/src/asm/Result.go b/src/asm/Result.go index 248dc23..62eedad 100644 --- a/src/asm/Result.go +++ b/src/asm/Result.go @@ -2,6 +2,7 @@ package asm import "bytes" +// Result is the compilation result and contains the machine code as well as the data. type Result struct { Code bytes.Buffer Data bytes.Buffer diff --git a/src/asm/x64/Call.go b/src/asm/x64/Call.go new file mode 100644 index 0000000..d3958bf --- /dev/null +++ b/src/asm/x64/Call.go @@ -0,0 +1,10 @@ +package x64 + +import "io" + +// Call places the return address on the top of the stack and continues +// program flow at the new address. The address is relative to the next instruction. +func Call(w io.ByteWriter, address uint32) { + w.WriteByte(0xe8) + appendUint32(w, address) +} diff --git a/src/asm/x64/MoveRegNum32.go b/src/asm/x64/Move.go similarity index 88% rename from src/asm/x64/MoveRegNum32.go rename to src/asm/x64/Move.go index 126dcf5..d130195 100644 --- a/src/asm/x64/MoveRegNum32.go +++ b/src/asm/x64/Move.go @@ -7,5 +7,5 @@ import ( // MoveRegNum32 moves a 32 bit integer into the given register. func MoveRegNum32(w io.ByteWriter, register uint8, number uint32) { w.WriteByte(0xb8 + register) - AppendUint32(w, number) + appendUint32(w, number) } diff --git a/src/asm/x64/Return.go b/src/asm/x64/Return.go new file mode 100644 index 0000000..47a5b34 --- /dev/null +++ b/src/asm/x64/Return.go @@ -0,0 +1,9 @@ +package x64 + +import "io" + +// Return transfers program control to a return address located on the top of the stack. +// The address is usually placed on the stack by a Call instruction. +func Return(w io.ByteWriter) { + w.WriteByte(0xc3) +} diff --git a/src/asm/x64/AppendUint32.go b/src/asm/x64/x64.go similarity index 59% rename from src/asm/x64/AppendUint32.go rename to src/asm/x64/x64.go index 78b9040..f6ab914 100644 --- a/src/asm/x64/AppendUint32.go +++ b/src/asm/x64/x64.go @@ -2,8 +2,8 @@ package x64 import "io" -// AppendUint32 appends a 32 bit integer in Little Endian to the given writer. -func AppendUint32(w io.ByteWriter, number uint32) { +// appendUint32 appends a 32 bit integer in Little Endian to the given writer. +func appendUint32(w io.ByteWriter, number uint32) { w.WriteByte(byte(number)) w.WriteByte(byte(number >> 8)) w.WriteByte(byte(number >> 16)) diff --git a/src/build/Build.go b/src/build/Build.go index 5ecf62d..88d08d5 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -33,25 +33,25 @@ func New(directory string) *Build { // Run parses the input files and generates an executable file. func (build *Build) Run() error { - err := build.Compile() + // err := build.Compile() - if err != nil { - return err - } + // if err != nil { + // return err + // } - list := asm.InstructionList{} + a := asm.New() - list.MoveRegisterNumber(register.Syscall0, syscall.Write) - list.MoveRegisterNumber(register.Syscall1, 1) - list.MoveRegisterNumber(register.Syscall2, 0x4000a2) - list.MoveRegisterNumber(register.Syscall3, 6) - list.Syscall() + a.MoveRegisterNumber(register.Syscall0, syscall.Write) + a.MoveRegisterNumber(register.Syscall1, 1) + a.MoveRegisterNumber(register.Syscall2, 0x4000a2) + a.MoveRegisterNumber(register.Syscall3, 6) + a.Syscall() - list.MoveRegisterNumber(register.Syscall0, syscall.Exit) - list.MoveRegisterNumber(register.Syscall1, 0) - list.Syscall() + a.MoveRegisterNumber(register.Syscall0, syscall.Exit) + a.MoveRegisterNumber(register.Syscall1, 0) + a.Syscall() - result := list.Finalize() + result := a.Finalize() result.Data.WriteString("Hello\n") if !build.WriteExecutable { diff --git a/src/cpu/CPU.go b/src/cpu/CPU.go new file mode 100644 index 0000000..ea16af0 --- /dev/null +++ b/src/cpu/CPU.go @@ -0,0 +1,106 @@ +package cpu + +import "git.akyoto.dev/cli/q/src/register" + +// CPU manages the allocation state of registers. +type CPU struct { + All List + General List + Call List + Syscall List +} + +// New creates a new CPU state. +func New() *CPU { + // Rather than doing lots of mini allocations + // we'll allocate memory for all registers at once. + registers := [16]Register{ + {ID: register.R0}, + {ID: register.R1}, + {ID: register.R2}, + {ID: register.R3}, + {ID: register.R4}, + {ID: register.R5}, + {ID: register.R6}, + {ID: register.R7}, + {ID: register.R8}, + {ID: register.R9}, + {ID: register.R10}, + {ID: register.R11}, + {ID: register.R12}, + {ID: register.R13}, + {ID: register.R14}, + {ID: register.R15}, + } + + rax := ®isters[0] + rcx := ®isters[1] + rdx := ®isters[2] + rbx := ®isters[3] + rsp := ®isters[4] + rbp := ®isters[5] + rsi := ®isters[6] + rdi := ®isters[7] + r8 := ®isters[8] + r9 := ®isters[9] + r10 := ®isters[10] + r11 := ®isters[11] + r12 := ®isters[12] + r13 := ®isters[13] + r14 := ®isters[14] + r15 := ®isters[15] + + // Register configuration + return &CPU{ + All: List{ + rax, + rcx, + rdx, + rbx, + rsp, + rbp, + rsi, + rdi, + r8, + r9, + r10, + r11, + r12, + r13, + r14, + r15, + }, + General: List{ + rcx, + rbx, + rbp, + r11, + r12, + r13, + r14, + r15, + }, + Call: List{ + rdi, + rsi, + rdx, + r10, + r8, + r9, + }, + Syscall: List{ + rax, + rdi, + rsi, + rdx, + r10, + r8, + r9, + }, + } +} + +// ByID returns the register with the given ID. +func (cpu *CPU) ByID(id register.ID) *Register { + return cpu.All[id] +} diff --git a/src/cpu/List.go b/src/cpu/List.go new file mode 100644 index 0000000..e9234aa --- /dev/null +++ b/src/cpu/List.go @@ -0,0 +1,29 @@ +package cpu + +// List is a list of registers. +type List []*Register + +// FindFree tries to find a free register +// and returns nil when all are currently occupied. +func (registers List) FindFree() *Register { + for _, register := range registers { + if register.IsFree() { + return register + } + } + + return nil +} + +// InUse returns a list of registers that are currently in use. +func (registers List) InUse() List { + var inUse List + + for _, register := range registers { + if !register.IsFree() { + inUse = append(inUse, register) + } + } + + return inUse +} diff --git a/src/cpu/Register.go b/src/cpu/Register.go new file mode 100644 index 0000000..b11add9 --- /dev/null +++ b/src/cpu/Register.go @@ -0,0 +1,39 @@ +package cpu + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/register" +) + +// Register represents a single CPU register. +type Register struct { + ID register.ID + user fmt.Stringer +} + +// Use marks the register as used by the given object. +func (register *Register) Use(obj fmt.Stringer) error { + if register.user != nil { + return &errors.RegisterInUse{Register: register.ID.String(), User: register.user.String()} + } + + register.user = obj + return nil +} + +// Free frees the register so that it can be used for new calculations. +func (register *Register) Free() { + register.user = nil +} + +// IsFree returns true if the register is not in use. +func (register *Register) IsFree() bool { + return register.user == nil +} + +// String returns a human-readable representation of the register. +func (register *Register) String() string { + return fmt.Sprintf("%s%s%v", register.ID, "=", register.user) +} diff --git a/src/directory/Walk.go b/src/directory/Walk.go index 5595e3e..f871c64 100644 --- a/src/directory/Walk.go +++ b/src/directory/Walk.go @@ -5,7 +5,7 @@ import ( "unsafe" ) -const blockSize = 8 << 10 +const blockSize = 4096 // Walk calls your callback function for every file name inside the directory. // It doesn't distinguish between files and directories. diff --git a/src/errors/InvalidPath.go b/src/errors/InvalidDirectory.go similarity index 73% rename from src/errors/InvalidPath.go rename to src/errors/InvalidDirectory.go index 0b71fcf..161cdf0 100644 --- a/src/errors/InvalidPath.go +++ b/src/errors/InvalidDirectory.go @@ -2,6 +2,7 @@ package errors import "fmt" +// InvalidDirectory errors are returned when the specified path is not a directory. type InvalidDirectory struct { Path string } diff --git a/src/errors/RegisterInUse.go b/src/errors/RegisterInUse.go new file mode 100644 index 0000000..27f40a5 --- /dev/null +++ b/src/errors/RegisterInUse.go @@ -0,0 +1,14 @@ +package errors + +import "fmt" + +// RegisterInUse errors are returned when a register is already in use. +type RegisterInUse struct { + Register string + User string +} + +// Error implements the text representation. +func (err *RegisterInUse) Error() string { + return fmt.Sprintf("Register '%s' already used by '%s'", err.Register, err.User) +} diff --git a/src/register/General.go b/src/register/ID.go similarity index 57% rename from src/register/General.go rename to src/register/ID.go index d22c83a..08ac82b 100644 --- a/src/register/General.go +++ b/src/register/ID.go @@ -2,10 +2,11 @@ package register import "fmt" -type Register uint8 +// ID represents the number of the register. +type ID uint8 const ( - R0 Register = iota + R0 ID = iota R1 R2 R3 @@ -23,6 +24,6 @@ const ( R15 ) -func (r Register) String() string { +func (r ID) String() string { return fmt.Sprintf("r%d", r) } diff --git a/src/register/Named.go b/src/register/Named.go deleted file mode 100644 index 80d2ae7..0000000 --- a/src/register/Named.go +++ /dev/null @@ -1,12 +0,0 @@ -package register - -const ( - RAX = R0 - RCX = R1 - RDX = R2 - RBX = R3 - RSP = R4 - RBP = R5 - RSI = R6 - RDI = R7 -) diff --git a/src/syscall/syscall_linux.go b/src/syscall/syscall_linux.go index c2175b8..7c5d6f8 100644 --- a/src/syscall/syscall_linux.go +++ b/src/syscall/syscall_linux.go @@ -1,5 +1,6 @@ package syscall +// Linux syscalls const ( Read = iota Write From f11757d9e44dd4beca07c4931bda537aa736858c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 27 Oct 2023 22:14:20 +0200 Subject: [PATCH 0027/1012] Improved assembler --- src/asm/Address.go | 4 ++++ src/asm/Assembler.go | 25 +++++++++++++++++-------- src/asm/Instruction.go | 1 + 3 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 src/asm/Address.go diff --git a/src/asm/Address.go b/src/asm/Address.go new file mode 100644 index 0000000..14b073b --- /dev/null +++ b/src/asm/Address.go @@ -0,0 +1,4 @@ +package asm + +// Address represents a memory address. +type Address = uint32 diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index 8f0e6d3..a751e33 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -6,37 +6,46 @@ import ( // Assembler contains a list of instructions. type Assembler struct { - Instructions []Instruction + instructions []Instruction + labels map[string]int } // New creates a new assembler. func New() *Assembler { return &Assembler{ - Instructions: make([]Instruction, 0, 8), + instructions: make([]Instruction, 0, 8), + labels: map[string]int{}, } } // Finalize generates the final machine code. -func (list *Assembler) Finalize() *Result { +func (a *Assembler) Finalize() *Result { final := Result{} - for _, instr := range list.Instructions { + for _, instr := range a.instructions { instr.Write(&final.Code) } return &final } -func (list *Assembler) MoveRegisterNumber(reg register.ID, number uint64) { - list.Instructions = append(list.Instructions, Instruction{ +// AddLabel creates a new label at the current position. +func (a *Assembler) AddLabel(name string) { + a.labels[name] = len(a.instructions) +} + +// MoveRegisterNumber moves a number into the given register. +func (a *Assembler) MoveRegisterNumber(reg register.ID, number uint64) { + a.instructions = append(a.instructions, Instruction{ Mnemonic: MOV, Destination: reg, Number: number, }) } -func (list *Assembler) Syscall() { - list.Instructions = append(list.Instructions, Instruction{ +// Syscall executes a kernel function. +func (a *Assembler) Syscall() { + a.instructions = append(a.instructions, Instruction{ Mnemonic: SYSCALL, }) } diff --git a/src/asm/Instruction.go b/src/asm/Instruction.go index 210f253..1883cee 100644 --- a/src/asm/Instruction.go +++ b/src/asm/Instruction.go @@ -26,6 +26,7 @@ func (x *Instruction) Write(w io.ByteWriter) { } } +// String returns the assembler representation of the instruction. func (x *Instruction) String() string { switch x.Mnemonic { case MOV: From a5ba3163192c723072d8fab18566c67604325946 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 27 Oct 2023 22:14:20 +0200 Subject: [PATCH 0028/1012] Improved assembler --- src/asm/Address.go | 4 ++++ src/asm/Assembler.go | 25 +++++++++++++++++-------- src/asm/Instruction.go | 1 + 3 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 src/asm/Address.go diff --git a/src/asm/Address.go b/src/asm/Address.go new file mode 100644 index 0000000..14b073b --- /dev/null +++ b/src/asm/Address.go @@ -0,0 +1,4 @@ +package asm + +// Address represents a memory address. +type Address = uint32 diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index 8f0e6d3..a751e33 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -6,37 +6,46 @@ import ( // Assembler contains a list of instructions. type Assembler struct { - Instructions []Instruction + instructions []Instruction + labels map[string]int } // New creates a new assembler. func New() *Assembler { return &Assembler{ - Instructions: make([]Instruction, 0, 8), + instructions: make([]Instruction, 0, 8), + labels: map[string]int{}, } } // Finalize generates the final machine code. -func (list *Assembler) Finalize() *Result { +func (a *Assembler) Finalize() *Result { final := Result{} - for _, instr := range list.Instructions { + for _, instr := range a.instructions { instr.Write(&final.Code) } return &final } -func (list *Assembler) MoveRegisterNumber(reg register.ID, number uint64) { - list.Instructions = append(list.Instructions, Instruction{ +// AddLabel creates a new label at the current position. +func (a *Assembler) AddLabel(name string) { + a.labels[name] = len(a.instructions) +} + +// MoveRegisterNumber moves a number into the given register. +func (a *Assembler) MoveRegisterNumber(reg register.ID, number uint64) { + a.instructions = append(a.instructions, Instruction{ Mnemonic: MOV, Destination: reg, Number: number, }) } -func (list *Assembler) Syscall() { - list.Instructions = append(list.Instructions, Instruction{ +// Syscall executes a kernel function. +func (a *Assembler) Syscall() { + a.instructions = append(a.instructions, Instruction{ Mnemonic: SYSCALL, }) } diff --git a/src/asm/Instruction.go b/src/asm/Instruction.go index 210f253..1883cee 100644 --- a/src/asm/Instruction.go +++ b/src/asm/Instruction.go @@ -26,6 +26,7 @@ func (x *Instruction) Write(w io.ByteWriter) { } } +// String returns the assembler representation of the instruction. func (x *Instruction) String() string { switch x.Mnemonic { case MOV: From 549a27cdad368cdc6bdec748b4950f99d4ede5db Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 28 Oct 2023 12:50:56 +0200 Subject: [PATCH 0029/1012] Improved tests --- main_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main_test.go b/main_test.go index b25bf96..cc45c58 100644 --- a/main_test.go +++ b/main_test.go @@ -26,10 +26,10 @@ func TestCLI(t *testing.T) { {[]string{}, 2}, {[]string{"invalid"}, 2}, {[]string{"system"}, 0}, - // {[]string{"build", "non-existing-directory"}, 1}, - // {[]string{"build", "examples/hello/hello.q"}, 1}, - // {[]string{"build", "examples/hello", "--invalid"}, 2}, - // {[]string{"build", "examples/hello", "--dry"}, 0}, + {[]string{"build", "non-existing-directory"}, 1}, + {[]string{"build", "examples/hello/hello.q"}, 1}, + {[]string{"build", "examples/hello", "--dry"}, 0}, + {[]string{"build", "examples/hello", "--invalid"}, 2}, } for _, test := range tests { From 9a816ec7d74f2e50daae9e8f43e82a424edf7792 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 28 Oct 2023 12:50:56 +0200 Subject: [PATCH 0030/1012] Improved tests --- main_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main_test.go b/main_test.go index b25bf96..cc45c58 100644 --- a/main_test.go +++ b/main_test.go @@ -26,10 +26,10 @@ func TestCLI(t *testing.T) { {[]string{}, 2}, {[]string{"invalid"}, 2}, {[]string{"system"}, 0}, - // {[]string{"build", "non-existing-directory"}, 1}, - // {[]string{"build", "examples/hello/hello.q"}, 1}, - // {[]string{"build", "examples/hello", "--invalid"}, 2}, - // {[]string{"build", "examples/hello", "--dry"}, 0}, + {[]string{"build", "non-existing-directory"}, 1}, + {[]string{"build", "examples/hello/hello.q"}, 1}, + {[]string{"build", "examples/hello", "--dry"}, 0}, + {[]string{"build", "examples/hello", "--invalid"}, 2}, } for _, test := range tests { From 894c1ad6850fc2fa3f9908c9d5acc23b7fad34dc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 28 Oct 2023 12:51:19 +0200 Subject: [PATCH 0031/1012] Improved documentation --- README.md | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9cbc7db..7591367 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,44 @@ Build a Linux ELF executable from `examples/hello`: ./examples/hello/hello ``` -## Source +## Documentation -- [main.go](main.go) -- [src/cli/Main.go](src/cli/Main.go) -- [src/cli/Build.go](src/cli/Build.go) -- [src/build/Build.go](src/build/Build.go) +### [main.go](main.go) + +Entry point. It simply calls `cli.Main()` which we can use for testing. + +### [src/cli/Main.go](src/cli/Main.go) + +The command line interface expects a command like `build` as the first argument. +Commands are implemented as functions in the [src/cli](src/cli) directory. +Each command has its own set of parameters. + +### [src/cli/Build.go](src/cli/Build.go) + +The build command creates a new `Build` instance with the given directory and calls the `Run` method. + +If no directory is specified, it will use the current directory. + +If the `--dry` flag is specified, it will perform all tasks except the final write to disk. +This flag should be used in most tests and benchmarks to avoid needless disk writes. + +```shell +q build +q build examples/hello +q build examples/hello --dry +``` + +### [src/build/Build.go](src/build/Build.go) + +The `Build` type defines all the information needed to start building an executable file. +The name of the executable will be equal to the name of the build directory. + +`Run` starts the build which will scan all files in the build directory. +The functions found in the scan will be translated to generic assembler instructions. +All the functions that are required to run the program will be added to final assembler. +The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. + +The `Run` method is currently not fully implemented. ## Tests From 5b0d5c931fe8bd2d39c08b9cb684d6435e263b83 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 28 Oct 2023 12:51:19 +0200 Subject: [PATCH 0032/1012] Improved documentation --- README.md | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9cbc7db..7591367 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,44 @@ Build a Linux ELF executable from `examples/hello`: ./examples/hello/hello ``` -## Source +## Documentation -- [main.go](main.go) -- [src/cli/Main.go](src/cli/Main.go) -- [src/cli/Build.go](src/cli/Build.go) -- [src/build/Build.go](src/build/Build.go) +### [main.go](main.go) + +Entry point. It simply calls `cli.Main()` which we can use for testing. + +### [src/cli/Main.go](src/cli/Main.go) + +The command line interface expects a command like `build` as the first argument. +Commands are implemented as functions in the [src/cli](src/cli) directory. +Each command has its own set of parameters. + +### [src/cli/Build.go](src/cli/Build.go) + +The build command creates a new `Build` instance with the given directory and calls the `Run` method. + +If no directory is specified, it will use the current directory. + +If the `--dry` flag is specified, it will perform all tasks except the final write to disk. +This flag should be used in most tests and benchmarks to avoid needless disk writes. + +```shell +q build +q build examples/hello +q build examples/hello --dry +``` + +### [src/build/Build.go](src/build/Build.go) + +The `Build` type defines all the information needed to start building an executable file. +The name of the executable will be equal to the name of the build directory. + +`Run` starts the build which will scan all files in the build directory. +The functions found in the scan will be translated to generic assembler instructions. +All the functions that are required to run the program will be added to final assembler. +The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. + +The `Run` method is currently not fully implemented. ## Tests From 6f097c9eee7174d2b7df1634d6359b97edfeec73 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 28 Oct 2023 13:02:26 +0200 Subject: [PATCH 0033/1012] Improved consistency --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7591367..72a1f8b 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Build a Linux ELF executable from `examples/hello`: ### [main.go](main.go) -Entry point. It simply calls `cli.Main()` which we can use for testing. +Entry point. It simply calls `cli.Main` which we can use for testing. ### [src/cli/Main.go](src/cli/Main.go) From d6be76b85a61c400ed37b465259c3ff7a9c40da6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 28 Oct 2023 13:02:26 +0200 Subject: [PATCH 0034/1012] Improved consistency --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7591367..72a1f8b 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Build a Linux ELF executable from `examples/hello`: ### [main.go](main.go) -Entry point. It simply calls `cli.Main()` which we can use for testing. +Entry point. It simply calls `cli.Main` which we can use for testing. ### [src/cli/Main.go](src/cli/Main.go) From 23e8b67e8808d7908a3141dcdf02a2e209759de2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 29 Oct 2023 12:24:40 +0100 Subject: [PATCH 0035/1012] Added memory address patching --- src/asm/Address.go | 3 +++ src/asm/Assembler.go | 55 ++++++++++++++++++++++++++++++++++++------ src/asm/Instruction.go | 15 +++--------- src/asm/Mnemonic.go | 4 +++ src/asm/Result.go | 17 +++++++++++-- src/build/Build.go | 15 +++++++++--- src/config/config.go | 8 ++++++ src/elf/ELF.go | 15 +++++------- src/elf/Header.go | 1 - 9 files changed, 98 insertions(+), 35 deletions(-) create mode 100644 src/config/config.go diff --git a/src/asm/Address.go b/src/asm/Address.go index 14b073b..9c1651d 100644 --- a/src/asm/Address.go +++ b/src/asm/Address.go @@ -2,3 +2,6 @@ package asm // Address represents a memory address. type Address = uint32 + +// Index references an instruction by its position. +type Index = uint32 diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index a751e33..a92fc62 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -1,37 +1,78 @@ package asm import ( + "bytes" + "encoding/binary" + + "git.akyoto.dev/cli/q/src/asm/x64" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/log" "git.akyoto.dev/cli/q/src/register" ) // Assembler contains a list of instructions. type Assembler struct { instructions []Instruction - labels map[string]int + labels map[string]Index + verbose bool } // New creates a new assembler. func New() *Assembler { return &Assembler{ instructions: make([]Instruction, 0, 8), - labels: map[string]int{}, + labels: map[string]Index{}, + verbose: true, } } // Finalize generates the final machine code. func (a *Assembler) Finalize() *Result { - final := Result{} + result := &Result{} + pointers := map[Address]Address{} + code := bytes.NewBuffer(result.Code) - for _, instr := range a.instructions { - instr.Write(&final.Code) + for _, x := range a.instructions { + if a.verbose { + log.Info.Println(x.String()) + } + + switch x.Mnemonic { + case MOV: + x64.MoveRegNum32(code, uint8(x.Destination), uint32(x.Number)) + + case MOVDATA: + position := result.AddData(x.Data) + x64.MoveRegNum32(code, uint8(x.Destination), 0) + pointers[Address(code.Len()-4)] = position + + case SYSCALL: + x64.Syscall(code) + } } - return &final + result.Code = code.Bytes() + dataStart := config.BaseAddress + config.CodeOffset + Address(len(result.Code)) + + for codePos, dataPos := range pointers { + binary.LittleEndian.PutUint32(result.Code[codePos:codePos+4], dataStart+dataPos) + } + + return result } // AddLabel creates a new label at the current position. func (a *Assembler) AddLabel(name string) { - a.labels[name] = len(a.instructions) + a.labels[name] = Index(len(a.instructions)) +} + +// MoveRegisterData moves a data section address into the given register. +func (a *Assembler) MoveRegisterData(reg register.ID, data []byte) { + a.instructions = append(a.instructions, Instruction{ + Mnemonic: MOVDATA, + Destination: reg, + Data: data, + }) } // MoveRegisterNumber moves a number into the given register. diff --git a/src/asm/Instruction.go b/src/asm/Instruction.go index 1883cee..0c91ee1 100644 --- a/src/asm/Instruction.go +++ b/src/asm/Instruction.go @@ -2,9 +2,7 @@ package asm import ( "fmt" - "io" - "git.akyoto.dev/cli/q/src/asm/x64" "git.akyoto.dev/cli/q/src/register" ) @@ -14,16 +12,7 @@ type Instruction struct { Source register.ID Destination register.ID Number uint64 -} - -// Write writes the machine code of the instruction. -func (x *Instruction) Write(w io.ByteWriter) { - switch x.Mnemonic { - case MOV: - x64.MoveRegNum32(w, uint8(x.Destination), uint32(x.Number)) - case SYSCALL: - x64.Syscall(w) - } + Data []byte } // String returns the assembler representation of the instruction. @@ -31,6 +20,8 @@ func (x *Instruction) String() string { switch x.Mnemonic { case MOV: return fmt.Sprintf("%s %s, %x", x.Mnemonic, x.Destination, x.Number) + case MOVDATA: + return fmt.Sprintf("%s %s, %v", x.Mnemonic, x.Destination, x.Data) case SYSCALL: return x.Mnemonic.String() default: diff --git a/src/asm/Mnemonic.go b/src/asm/Mnemonic.go index dea828b..3d3a16c 100644 --- a/src/asm/Mnemonic.go +++ b/src/asm/Mnemonic.go @@ -5,6 +5,7 @@ type Mnemonic uint8 const ( NONE Mnemonic = iota MOV + MOVDATA SYSCALL ) @@ -13,6 +14,9 @@ func (m Mnemonic) String() string { case MOV: return "mov" + case MOVDATA: + return "mov" + case SYSCALL: return "syscall" } diff --git a/src/asm/Result.go b/src/asm/Result.go index 62eedad..8261d82 100644 --- a/src/asm/Result.go +++ b/src/asm/Result.go @@ -4,6 +4,19 @@ import "bytes" // Result is the compilation result and contains the machine code as well as the data. type Result struct { - Code bytes.Buffer - Data bytes.Buffer + Code []byte + Data []byte +} + +// AddData adds the given bytes to the data block and returns the address relative to the start of the data section. +func (result *Result) AddData(block []byte) Address { + position := bytes.Index(result.Data, block) + + if position != -1 { + return Address(position) + } + + address := Address(len(result.Data)) + result.Data = append(result.Data, block...) + return address } diff --git a/src/build/Build.go b/src/build/Build.go index 88d08d5..197665e 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -43,8 +43,16 @@ func (build *Build) Run() error { a.MoveRegisterNumber(register.Syscall0, syscall.Write) a.MoveRegisterNumber(register.Syscall1, 1) - a.MoveRegisterNumber(register.Syscall2, 0x4000a2) - a.MoveRegisterNumber(register.Syscall3, 6) + hello := []byte("Hello\n") + a.MoveRegisterData(register.Syscall2, hello) + a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) + a.Syscall() + + a.MoveRegisterNumber(register.Syscall0, syscall.Write) + a.MoveRegisterNumber(register.Syscall1, 1) + world := []byte("World\n") + a.MoveRegisterData(register.Syscall2, world) + a.MoveRegisterNumber(register.Syscall3, uint64(len(world))) a.Syscall() a.MoveRegisterNumber(register.Syscall0, syscall.Exit) @@ -52,13 +60,12 @@ func (build *Build) Run() error { a.Syscall() result := a.Finalize() - result.Data.WriteString("Hello\n") if !build.WriteExecutable { return nil } - return writeToDisk(build.Executable(), result.Code.Bytes(), result.Data.Bytes()) + return writeToDisk(build.Executable(), result.Code, result.Data) } // Compile compiles all the functions. diff --git a/src/config/config.go b/src/config/config.go new file mode 100644 index 0000000..42bc97f --- /dev/null +++ b/src/config/config.go @@ -0,0 +1,8 @@ +package config + +const ( + MinAddress = 0x10000 + BaseAddress = 0x40 * MinAddress + CodeOffset = 0x80 + Align = 0x10 +) diff --git a/src/elf/ELF.go b/src/elf/ELF.go index 30c1ce0..92d45c6 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -3,11 +3,8 @@ package elf import ( "encoding/binary" "io" -) -const ( - minAddress = 0x10000 - baseAddress = 0x40 * minAddress + "git.akyoto.dev/cli/q/src/config" ) // ELF represents an ELF file. @@ -31,7 +28,7 @@ func New(code []byte, data []byte) *ELF { Type: TypeExecutable, Architecture: ArchitectureAMD64, FileVersion: 1, - EntryPointInMemory: baseAddress + 0x80, + EntryPointInMemory: config.BaseAddress + config.CodeOffset, ProgramHeaderOffset: HeaderSize, SectionHeaderOffset: 0, Flags: 0, @@ -45,12 +42,12 @@ func New(code []byte, data []byte) *ELF { ProgramHeader: ProgramHeader{ Type: ProgramTypeLOAD, Flags: ProgramFlagsExecutable, - Offset: 0x80, - VirtualAddress: baseAddress + 0x80, - PhysicalAddress: baseAddress + 0x80, + Offset: config.CodeOffset, + VirtualAddress: config.BaseAddress + config.CodeOffset, + PhysicalAddress: config.BaseAddress + config.CodeOffset, SizeInFile: int64(len(code)), SizeInMemory: int64(len(code)), - Align: Align, + Align: config.Align, }, Code: code, Data: data, diff --git a/src/elf/Header.go b/src/elf/Header.go index 089bf00..672f065 100644 --- a/src/elf/Header.go +++ b/src/elf/Header.go @@ -5,7 +5,6 @@ const ( TypeExecutable = 2 ArchitectureAMD64 = 0x3E HeaderSize = 64 - Align = 16 ) // Header contains general information. From fbe6aa80bbd6ba31bdcb828e2dc9ca4028724718 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 29 Oct 2023 12:24:40 +0100 Subject: [PATCH 0036/1012] Added memory address patching --- src/asm/Address.go | 3 +++ src/asm/Assembler.go | 55 ++++++++++++++++++++++++++++++++++++------ src/asm/Instruction.go | 15 +++--------- src/asm/Mnemonic.go | 4 +++ src/asm/Result.go | 17 +++++++++++-- src/build/Build.go | 15 +++++++++--- src/config/config.go | 8 ++++++ src/elf/ELF.go | 15 +++++------- src/elf/Header.go | 1 - 9 files changed, 98 insertions(+), 35 deletions(-) create mode 100644 src/config/config.go diff --git a/src/asm/Address.go b/src/asm/Address.go index 14b073b..9c1651d 100644 --- a/src/asm/Address.go +++ b/src/asm/Address.go @@ -2,3 +2,6 @@ package asm // Address represents a memory address. type Address = uint32 + +// Index references an instruction by its position. +type Index = uint32 diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index a751e33..a92fc62 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -1,37 +1,78 @@ package asm import ( + "bytes" + "encoding/binary" + + "git.akyoto.dev/cli/q/src/asm/x64" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/log" "git.akyoto.dev/cli/q/src/register" ) // Assembler contains a list of instructions. type Assembler struct { instructions []Instruction - labels map[string]int + labels map[string]Index + verbose bool } // New creates a new assembler. func New() *Assembler { return &Assembler{ instructions: make([]Instruction, 0, 8), - labels: map[string]int{}, + labels: map[string]Index{}, + verbose: true, } } // Finalize generates the final machine code. func (a *Assembler) Finalize() *Result { - final := Result{} + result := &Result{} + pointers := map[Address]Address{} + code := bytes.NewBuffer(result.Code) - for _, instr := range a.instructions { - instr.Write(&final.Code) + for _, x := range a.instructions { + if a.verbose { + log.Info.Println(x.String()) + } + + switch x.Mnemonic { + case MOV: + x64.MoveRegNum32(code, uint8(x.Destination), uint32(x.Number)) + + case MOVDATA: + position := result.AddData(x.Data) + x64.MoveRegNum32(code, uint8(x.Destination), 0) + pointers[Address(code.Len()-4)] = position + + case SYSCALL: + x64.Syscall(code) + } } - return &final + result.Code = code.Bytes() + dataStart := config.BaseAddress + config.CodeOffset + Address(len(result.Code)) + + for codePos, dataPos := range pointers { + binary.LittleEndian.PutUint32(result.Code[codePos:codePos+4], dataStart+dataPos) + } + + return result } // AddLabel creates a new label at the current position. func (a *Assembler) AddLabel(name string) { - a.labels[name] = len(a.instructions) + a.labels[name] = Index(len(a.instructions)) +} + +// MoveRegisterData moves a data section address into the given register. +func (a *Assembler) MoveRegisterData(reg register.ID, data []byte) { + a.instructions = append(a.instructions, Instruction{ + Mnemonic: MOVDATA, + Destination: reg, + Data: data, + }) } // MoveRegisterNumber moves a number into the given register. diff --git a/src/asm/Instruction.go b/src/asm/Instruction.go index 1883cee..0c91ee1 100644 --- a/src/asm/Instruction.go +++ b/src/asm/Instruction.go @@ -2,9 +2,7 @@ package asm import ( "fmt" - "io" - "git.akyoto.dev/cli/q/src/asm/x64" "git.akyoto.dev/cli/q/src/register" ) @@ -14,16 +12,7 @@ type Instruction struct { Source register.ID Destination register.ID Number uint64 -} - -// Write writes the machine code of the instruction. -func (x *Instruction) Write(w io.ByteWriter) { - switch x.Mnemonic { - case MOV: - x64.MoveRegNum32(w, uint8(x.Destination), uint32(x.Number)) - case SYSCALL: - x64.Syscall(w) - } + Data []byte } // String returns the assembler representation of the instruction. @@ -31,6 +20,8 @@ func (x *Instruction) String() string { switch x.Mnemonic { case MOV: return fmt.Sprintf("%s %s, %x", x.Mnemonic, x.Destination, x.Number) + case MOVDATA: + return fmt.Sprintf("%s %s, %v", x.Mnemonic, x.Destination, x.Data) case SYSCALL: return x.Mnemonic.String() default: diff --git a/src/asm/Mnemonic.go b/src/asm/Mnemonic.go index dea828b..3d3a16c 100644 --- a/src/asm/Mnemonic.go +++ b/src/asm/Mnemonic.go @@ -5,6 +5,7 @@ type Mnemonic uint8 const ( NONE Mnemonic = iota MOV + MOVDATA SYSCALL ) @@ -13,6 +14,9 @@ func (m Mnemonic) String() string { case MOV: return "mov" + case MOVDATA: + return "mov" + case SYSCALL: return "syscall" } diff --git a/src/asm/Result.go b/src/asm/Result.go index 62eedad..8261d82 100644 --- a/src/asm/Result.go +++ b/src/asm/Result.go @@ -4,6 +4,19 @@ import "bytes" // Result is the compilation result and contains the machine code as well as the data. type Result struct { - Code bytes.Buffer - Data bytes.Buffer + Code []byte + Data []byte +} + +// AddData adds the given bytes to the data block and returns the address relative to the start of the data section. +func (result *Result) AddData(block []byte) Address { + position := bytes.Index(result.Data, block) + + if position != -1 { + return Address(position) + } + + address := Address(len(result.Data)) + result.Data = append(result.Data, block...) + return address } diff --git a/src/build/Build.go b/src/build/Build.go index 88d08d5..197665e 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -43,8 +43,16 @@ func (build *Build) Run() error { a.MoveRegisterNumber(register.Syscall0, syscall.Write) a.MoveRegisterNumber(register.Syscall1, 1) - a.MoveRegisterNumber(register.Syscall2, 0x4000a2) - a.MoveRegisterNumber(register.Syscall3, 6) + hello := []byte("Hello\n") + a.MoveRegisterData(register.Syscall2, hello) + a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) + a.Syscall() + + a.MoveRegisterNumber(register.Syscall0, syscall.Write) + a.MoveRegisterNumber(register.Syscall1, 1) + world := []byte("World\n") + a.MoveRegisterData(register.Syscall2, world) + a.MoveRegisterNumber(register.Syscall3, uint64(len(world))) a.Syscall() a.MoveRegisterNumber(register.Syscall0, syscall.Exit) @@ -52,13 +60,12 @@ func (build *Build) Run() error { a.Syscall() result := a.Finalize() - result.Data.WriteString("Hello\n") if !build.WriteExecutable { return nil } - return writeToDisk(build.Executable(), result.Code.Bytes(), result.Data.Bytes()) + return writeToDisk(build.Executable(), result.Code, result.Data) } // Compile compiles all the functions. diff --git a/src/config/config.go b/src/config/config.go new file mode 100644 index 0000000..42bc97f --- /dev/null +++ b/src/config/config.go @@ -0,0 +1,8 @@ +package config + +const ( + MinAddress = 0x10000 + BaseAddress = 0x40 * MinAddress + CodeOffset = 0x80 + Align = 0x10 +) diff --git a/src/elf/ELF.go b/src/elf/ELF.go index 30c1ce0..92d45c6 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -3,11 +3,8 @@ package elf import ( "encoding/binary" "io" -) -const ( - minAddress = 0x10000 - baseAddress = 0x40 * minAddress + "git.akyoto.dev/cli/q/src/config" ) // ELF represents an ELF file. @@ -31,7 +28,7 @@ func New(code []byte, data []byte) *ELF { Type: TypeExecutable, Architecture: ArchitectureAMD64, FileVersion: 1, - EntryPointInMemory: baseAddress + 0x80, + EntryPointInMemory: config.BaseAddress + config.CodeOffset, ProgramHeaderOffset: HeaderSize, SectionHeaderOffset: 0, Flags: 0, @@ -45,12 +42,12 @@ func New(code []byte, data []byte) *ELF { ProgramHeader: ProgramHeader{ Type: ProgramTypeLOAD, Flags: ProgramFlagsExecutable, - Offset: 0x80, - VirtualAddress: baseAddress + 0x80, - PhysicalAddress: baseAddress + 0x80, + Offset: config.CodeOffset, + VirtualAddress: config.BaseAddress + config.CodeOffset, + PhysicalAddress: config.BaseAddress + config.CodeOffset, SizeInFile: int64(len(code)), SizeInMemory: int64(len(code)), - Align: Align, + Align: config.Align, }, Code: code, Data: data, diff --git a/src/elf/Header.go b/src/elf/Header.go index 089bf00..672f065 100644 --- a/src/elf/Header.go +++ b/src/elf/Header.go @@ -5,7 +5,6 @@ const ( TypeExecutable = 2 ArchitectureAMD64 = 0x3E HeaderSize = 64 - Align = 16 ) // Header contains general information. From fc2600c7968dcf4a5266fd6bdde9a9c52b792a46 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 29 Oct 2023 16:16:36 +0100 Subject: [PATCH 0037/1012] Improved build performance --- go.mod | 2 - go.sum | 2 - main_test.go | 11 ++-- src/asm/Address.go | 2 +- src/asm/Assembler.go | 68 +++++++++++++------------ src/asm/Data.go | 19 +++++++ src/asm/Pointer.go | 9 ++++ src/asm/Result.go | 22 -------- src/asm/x64/Move.go | 11 ---- src/asm/x64/Syscall.go | 11 ---- src/asm/x64/x64.go | 11 ---- src/build/Build.go | 29 ++++++----- src/cli/Build.go | 3 ++ src/{syscall => linux}/syscall_linux.go | 3 +- src/{asm => }/x64/Call.go | 14 +++-- src/x64/Move.go | 13 +++++ src/{asm => }/x64/Return.go | 6 +-- src/x64/Syscall.go | 6 +++ 18 files changed, 122 insertions(+), 120 deletions(-) delete mode 100644 go.sum create mode 100644 src/asm/Data.go create mode 100644 src/asm/Pointer.go delete mode 100644 src/asm/Result.go delete mode 100644 src/asm/x64/Move.go delete mode 100644 src/asm/x64/Syscall.go delete mode 100644 src/asm/x64/x64.go rename src/{syscall => linux}/syscall_linux.go (99%) rename src/{asm => }/x64/Call.go (51%) create mode 100644 src/x64/Move.go rename src/{asm => }/x64/Return.go (73%) create mode 100644 src/x64/Syscall.go diff --git a/go.mod b/go.mod index e78e48b..2c2b156 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ module git.akyoto.dev/cli/q go 1.21 - -require git.akyoto.dev/go/assert v0.1.3 diff --git a/go.sum b/go.sum deleted file mode 100644 index 9fc2547..0000000 --- a/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= -git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= diff --git a/main_test.go b/main_test.go index cc45c58..4aac58f 100644 --- a/main_test.go +++ b/main_test.go @@ -7,7 +7,6 @@ import ( "git.akyoto.dev/cli/q/src/cli" "git.akyoto.dev/cli/q/src/log" - "git.akyoto.dev/go/assert" ) func TestMain(m *testing.M) { @@ -28,14 +27,18 @@ func TestCLI(t *testing.T) { {[]string{"system"}, 0}, {[]string{"build", "non-existing-directory"}, 1}, {[]string{"build", "examples/hello/hello.q"}, 1}, - {[]string{"build", "examples/hello", "--dry"}, 0}, {[]string{"build", "examples/hello", "--invalid"}, 2}, + {[]string{"build", "examples/hello", "--dry"}, 0}, } for _, test := range tests { - exitCode := cli.Main(test.arguments) t.Log(test.arguments) - assert.Equal(t, exitCode, test.expectedExitCode) + exitCode := cli.Main(test.arguments) + + if exitCode != test.expectedExitCode { + t.Errorf("exit code %d (expected %d)", exitCode, test.expectedExitCode) + t.FailNow() + } } } diff --git a/src/asm/Address.go b/src/asm/Address.go index 9c1651d..8e32c5a 100644 --- a/src/asm/Address.go +++ b/src/asm/Address.go @@ -4,4 +4,4 @@ package asm type Address = uint32 // Index references an instruction by its position. -type Index = uint32 +// type Index = uint32 diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index a92fc62..552ff91 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -1,74 +1,76 @@ package asm import ( - "bytes" "encoding/binary" - "git.akyoto.dev/cli/q/src/asm/x64" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/log" "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/cli/q/src/x64" ) // Assembler contains a list of instructions. type Assembler struct { - instructions []Instruction - labels map[string]Index - verbose bool + Instructions []Instruction + // labels map[string]Index + Verbose bool } // New creates a new assembler. func New() *Assembler { return &Assembler{ - instructions: make([]Instruction, 0, 8), - labels: map[string]Index{}, - verbose: true, + Instructions: make([]Instruction, 0, 8), + // labels: map[string]Index{}, } } // Finalize generates the final machine code. -func (a *Assembler) Finalize() *Result { - result := &Result{} - pointers := map[Address]Address{} - code := bytes.NewBuffer(result.Code) - - for _, x := range a.instructions { - if a.verbose { - log.Info.Println(x.String()) - } +func (a *Assembler) Finalize() ([]byte, []byte) { + code := make([]byte, 0, len(a.Instructions)*8) + data := make(Data, 0, 16) + pointers := []Pointer{} + for _, x := range a.Instructions { switch x.Mnemonic { case MOV: - x64.MoveRegNum32(code, uint8(x.Destination), uint32(x.Number)) + code = x64.MoveRegNum32(code, uint8(x.Destination), uint32(x.Number)) case MOVDATA: - position := result.AddData(x.Data) - x64.MoveRegNum32(code, uint8(x.Destination), 0) - pointers[Address(code.Len()-4)] = position + code = x64.MoveRegNum32(code, uint8(x.Destination), 0) + + pointers = append(pointers, Pointer{ + Position: Address(len(code) - 4), + Address: data.Add(x.Data), + }) case SYSCALL: - x64.Syscall(code) + code = x64.Syscall(code) + } + + if a.Verbose { + log.Info.Println(x.String()) } } - result.Code = code.Bytes() - dataStart := config.BaseAddress + config.CodeOffset + Address(len(result.Code)) + dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) - for codePos, dataPos := range pointers { - binary.LittleEndian.PutUint32(result.Code[codePos:codePos+4], dataStart+dataPos) + for _, pointer := range pointers { + slice := code[pointer.Position : pointer.Position+4] + address := dataStart + pointer.Address + binary.LittleEndian.PutUint32(slice, address) } - return result + return code, data } // AddLabel creates a new label at the current position. -func (a *Assembler) AddLabel(name string) { - a.labels[name] = Index(len(a.instructions)) -} +// func (a *Assembler) AddLabel(name string) { +// a.labels[name] = Index(len(a.instructions)) +// } // MoveRegisterData moves a data section address into the given register. func (a *Assembler) MoveRegisterData(reg register.ID, data []byte) { - a.instructions = append(a.instructions, Instruction{ + a.Instructions = append(a.Instructions, Instruction{ Mnemonic: MOVDATA, Destination: reg, Data: data, @@ -77,7 +79,7 @@ func (a *Assembler) MoveRegisterData(reg register.ID, data []byte) { // MoveRegisterNumber moves a number into the given register. func (a *Assembler) MoveRegisterNumber(reg register.ID, number uint64) { - a.instructions = append(a.instructions, Instruction{ + a.Instructions = append(a.Instructions, Instruction{ Mnemonic: MOV, Destination: reg, Number: number, @@ -86,7 +88,7 @@ func (a *Assembler) MoveRegisterNumber(reg register.ID, number uint64) { // Syscall executes a kernel function. func (a *Assembler) Syscall() { - a.instructions = append(a.instructions, Instruction{ + a.Instructions = append(a.Instructions, Instruction{ Mnemonic: SYSCALL, }) } diff --git a/src/asm/Data.go b/src/asm/Data.go new file mode 100644 index 0000000..9268c99 --- /dev/null +++ b/src/asm/Data.go @@ -0,0 +1,19 @@ +package asm + +import "bytes" + +// Data represents the static read-only data. +type Data []byte + +// Add adds the given bytes to the data block and returns the address relative to the start of the data section. +func (data *Data) Add(block []byte) Address { + position := bytes.Index(*data, block) + + if position != -1 { + return Address(position) + } + + address := Address(len(*data)) + *data = append(*data, block...) + return address +} diff --git a/src/asm/Pointer.go b/src/asm/Pointer.go new file mode 100644 index 0000000..5d13b4f --- /dev/null +++ b/src/asm/Pointer.go @@ -0,0 +1,9 @@ +package asm + +// Pointer stores a relative memory address that we can later turn into an absolute one. +// Position: The machine code offset where the address was inserted. +// Address: The offset inside the section. +type Pointer struct { + Position uint32 + Address uint32 +} diff --git a/src/asm/Result.go b/src/asm/Result.go deleted file mode 100644 index 8261d82..0000000 --- a/src/asm/Result.go +++ /dev/null @@ -1,22 +0,0 @@ -package asm - -import "bytes" - -// Result is the compilation result and contains the machine code as well as the data. -type Result struct { - Code []byte - Data []byte -} - -// AddData adds the given bytes to the data block and returns the address relative to the start of the data section. -func (result *Result) AddData(block []byte) Address { - position := bytes.Index(result.Data, block) - - if position != -1 { - return Address(position) - } - - address := Address(len(result.Data)) - result.Data = append(result.Data, block...) - return address -} diff --git a/src/asm/x64/Move.go b/src/asm/x64/Move.go deleted file mode 100644 index d130195..0000000 --- a/src/asm/x64/Move.go +++ /dev/null @@ -1,11 +0,0 @@ -package x64 - -import ( - "io" -) - -// MoveRegNum32 moves a 32 bit integer into the given register. -func MoveRegNum32(w io.ByteWriter, register uint8, number uint32) { - w.WriteByte(0xb8 + register) - appendUint32(w, number) -} diff --git a/src/asm/x64/Syscall.go b/src/asm/x64/Syscall.go deleted file mode 100644 index 145f5a3..0000000 --- a/src/asm/x64/Syscall.go +++ /dev/null @@ -1,11 +0,0 @@ -package x64 - -import ( - "io" -) - -// Syscall is the primary way to communicate with the OS kernel. -func Syscall(w io.ByteWriter) { - w.WriteByte(0x0f) - w.WriteByte(0x05) -} diff --git a/src/asm/x64/x64.go b/src/asm/x64/x64.go deleted file mode 100644 index f6ab914..0000000 --- a/src/asm/x64/x64.go +++ /dev/null @@ -1,11 +0,0 @@ -package x64 - -import "io" - -// appendUint32 appends a 32 bit integer in Little Endian to the given writer. -func appendUint32(w io.ByteWriter, number uint32) { - w.WriteByte(byte(number)) - w.WriteByte(byte(number >> 8)) - w.WriteByte(byte(number >> 16)) - w.WriteByte(byte(number >> 24)) -} diff --git a/src/build/Build.go b/src/build/Build.go index 197665e..80d16c5 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -10,27 +10,31 @@ import ( "git.akyoto.dev/cli/q/src/directory" "git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/linux" "git.akyoto.dev/cli/q/src/log" "git.akyoto.dev/cli/q/src/register" - "git.akyoto.dev/cli/q/src/syscall" ) // Build describes a compiler build. type Build struct { - Name string Directory string + Verbose bool WriteExecutable bool } // New creates a new build. func New(directory string) *Build { return &Build{ - Name: filepath.Base(directory), Directory: directory, WriteExecutable: true, } } +var ( + hello = []byte("Hello\n") + world = []byte("World\n") +) + // Run parses the input files and generates an executable file. func (build *Build) Run() error { // err := build.Compile() @@ -39,33 +43,34 @@ func (build *Build) Run() error { // return err // } - a := asm.New() + a := asm.Assembler{ + Instructions: make([]asm.Instruction, 0, 8), + Verbose: build.Verbose, + } - a.MoveRegisterNumber(register.Syscall0, syscall.Write) + a.MoveRegisterNumber(register.Syscall0, linux.Write) a.MoveRegisterNumber(register.Syscall1, 1) - hello := []byte("Hello\n") a.MoveRegisterData(register.Syscall2, hello) a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) a.Syscall() - a.MoveRegisterNumber(register.Syscall0, syscall.Write) + a.MoveRegisterNumber(register.Syscall0, linux.Write) a.MoveRegisterNumber(register.Syscall1, 1) - world := []byte("World\n") a.MoveRegisterData(register.Syscall2, world) a.MoveRegisterNumber(register.Syscall3, uint64(len(world))) a.Syscall() - a.MoveRegisterNumber(register.Syscall0, syscall.Exit) + a.MoveRegisterNumber(register.Syscall0, linux.Exit) a.MoveRegisterNumber(register.Syscall1, 0) a.Syscall() - result := a.Finalize() + code, data := a.Finalize() if !build.WriteExecutable { return nil } - return writeToDisk(build.Executable(), result.Code, result.Data) + return writeToDisk(build.Executable(), code, data) } // Compile compiles all the functions. @@ -93,7 +98,7 @@ func (build *Build) Compile() error { // Executable returns the path to the executable. func (build *Build) Executable() string { - return filepath.Join(build.Directory, build.Name) + return filepath.Join(build.Directory, filepath.Base(build.Directory)) } // writeToDisk writes the executable file to disk. diff --git a/src/cli/Build.go b/src/cli/Build.go index 47e76db..761d418 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -20,6 +20,9 @@ func Build(args []string) int { case "--dry": b.WriteExecutable = false + case "--verbose", "-v": + b.Verbose = true + default: log.Error.Printf("Unknown parameter: %s\n", args[i]) return 2 diff --git a/src/syscall/syscall_linux.go b/src/linux/syscall_linux.go similarity index 99% rename from src/syscall/syscall_linux.go rename to src/linux/syscall_linux.go index 7c5d6f8..c535786 100644 --- a/src/syscall/syscall_linux.go +++ b/src/linux/syscall_linux.go @@ -1,6 +1,5 @@ -package syscall +package linux -// Linux syscalls const ( Read = iota Write diff --git a/src/asm/x64/Call.go b/src/x64/Call.go similarity index 51% rename from src/asm/x64/Call.go rename to src/x64/Call.go index d3958bf..1ada6fd 100644 --- a/src/asm/x64/Call.go +++ b/src/x64/Call.go @@ -1,10 +1,14 @@ package x64 -import "io" - // Call places the return address on the top of the stack and continues // program flow at the new address. The address is relative to the next instruction. -func Call(w io.ByteWriter, address uint32) { - w.WriteByte(0xe8) - appendUint32(w, address) +func Call(code []byte, address uint32) []byte { + return append( + code, + 0xe8, + byte(address), + byte(address>>8), + byte(address>>16), + byte(address>>24), + ) } diff --git a/src/x64/Move.go b/src/x64/Move.go new file mode 100644 index 0000000..6d9fd57 --- /dev/null +++ b/src/x64/Move.go @@ -0,0 +1,13 @@ +package x64 + +// MoveRegNum32 moves a 32 bit integer into the given register. +func MoveRegNum32(code []byte, register uint8, number uint32) []byte { + return append( + code, + 0xb8+register, + byte(number), + byte(number>>8), + byte(number>>16), + byte(number>>24), + ) +} diff --git a/src/asm/x64/Return.go b/src/x64/Return.go similarity index 73% rename from src/asm/x64/Return.go rename to src/x64/Return.go index 47a5b34..dc185da 100644 --- a/src/asm/x64/Return.go +++ b/src/x64/Return.go @@ -1,9 +1,7 @@ package x64 -import "io" - // Return transfers program control to a return address located on the top of the stack. // The address is usually placed on the stack by a Call instruction. -func Return(w io.ByteWriter) { - w.WriteByte(0xc3) +func Return(code []byte) []byte { + return append(code, 0xc3) } diff --git a/src/x64/Syscall.go b/src/x64/Syscall.go new file mode 100644 index 0000000..94b07d2 --- /dev/null +++ b/src/x64/Syscall.go @@ -0,0 +1,6 @@ +package x64 + +// Syscall is the primary way to communicate with the OS kernel. +func Syscall(code []byte) []byte { + return append(code, 0x0f, 0x05) +} From 5fe83543fd7b9dcba87747ca2ccab12dfeea40c7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 29 Oct 2023 16:16:36 +0100 Subject: [PATCH 0038/1012] Improved build performance --- go.mod | 2 - go.sum | 2 - main_test.go | 11 ++-- src/asm/Address.go | 2 +- src/asm/Assembler.go | 68 +++++++++++++------------ src/asm/Data.go | 19 +++++++ src/asm/Pointer.go | 9 ++++ src/asm/Result.go | 22 -------- src/asm/x64/Move.go | 11 ---- src/asm/x64/Syscall.go | 11 ---- src/asm/x64/x64.go | 11 ---- src/build/Build.go | 29 ++++++----- src/cli/Build.go | 3 ++ src/{syscall => linux}/syscall_linux.go | 3 +- src/{asm => }/x64/Call.go | 14 +++-- src/x64/Move.go | 13 +++++ src/{asm => }/x64/Return.go | 6 +-- src/x64/Syscall.go | 6 +++ 18 files changed, 122 insertions(+), 120 deletions(-) delete mode 100644 go.sum create mode 100644 src/asm/Data.go create mode 100644 src/asm/Pointer.go delete mode 100644 src/asm/Result.go delete mode 100644 src/asm/x64/Move.go delete mode 100644 src/asm/x64/Syscall.go delete mode 100644 src/asm/x64/x64.go rename src/{syscall => linux}/syscall_linux.go (99%) rename src/{asm => }/x64/Call.go (51%) create mode 100644 src/x64/Move.go rename src/{asm => }/x64/Return.go (73%) create mode 100644 src/x64/Syscall.go diff --git a/go.mod b/go.mod index e78e48b..2c2b156 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ module git.akyoto.dev/cli/q go 1.21 - -require git.akyoto.dev/go/assert v0.1.3 diff --git a/go.sum b/go.sum deleted file mode 100644 index 9fc2547..0000000 --- a/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= -git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= diff --git a/main_test.go b/main_test.go index cc45c58..4aac58f 100644 --- a/main_test.go +++ b/main_test.go @@ -7,7 +7,6 @@ import ( "git.akyoto.dev/cli/q/src/cli" "git.akyoto.dev/cli/q/src/log" - "git.akyoto.dev/go/assert" ) func TestMain(m *testing.M) { @@ -28,14 +27,18 @@ func TestCLI(t *testing.T) { {[]string{"system"}, 0}, {[]string{"build", "non-existing-directory"}, 1}, {[]string{"build", "examples/hello/hello.q"}, 1}, - {[]string{"build", "examples/hello", "--dry"}, 0}, {[]string{"build", "examples/hello", "--invalid"}, 2}, + {[]string{"build", "examples/hello", "--dry"}, 0}, } for _, test := range tests { - exitCode := cli.Main(test.arguments) t.Log(test.arguments) - assert.Equal(t, exitCode, test.expectedExitCode) + exitCode := cli.Main(test.arguments) + + if exitCode != test.expectedExitCode { + t.Errorf("exit code %d (expected %d)", exitCode, test.expectedExitCode) + t.FailNow() + } } } diff --git a/src/asm/Address.go b/src/asm/Address.go index 9c1651d..8e32c5a 100644 --- a/src/asm/Address.go +++ b/src/asm/Address.go @@ -4,4 +4,4 @@ package asm type Address = uint32 // Index references an instruction by its position. -type Index = uint32 +// type Index = uint32 diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index a92fc62..552ff91 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -1,74 +1,76 @@ package asm import ( - "bytes" "encoding/binary" - "git.akyoto.dev/cli/q/src/asm/x64" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/log" "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/cli/q/src/x64" ) // Assembler contains a list of instructions. type Assembler struct { - instructions []Instruction - labels map[string]Index - verbose bool + Instructions []Instruction + // labels map[string]Index + Verbose bool } // New creates a new assembler. func New() *Assembler { return &Assembler{ - instructions: make([]Instruction, 0, 8), - labels: map[string]Index{}, - verbose: true, + Instructions: make([]Instruction, 0, 8), + // labels: map[string]Index{}, } } // Finalize generates the final machine code. -func (a *Assembler) Finalize() *Result { - result := &Result{} - pointers := map[Address]Address{} - code := bytes.NewBuffer(result.Code) - - for _, x := range a.instructions { - if a.verbose { - log.Info.Println(x.String()) - } +func (a *Assembler) Finalize() ([]byte, []byte) { + code := make([]byte, 0, len(a.Instructions)*8) + data := make(Data, 0, 16) + pointers := []Pointer{} + for _, x := range a.Instructions { switch x.Mnemonic { case MOV: - x64.MoveRegNum32(code, uint8(x.Destination), uint32(x.Number)) + code = x64.MoveRegNum32(code, uint8(x.Destination), uint32(x.Number)) case MOVDATA: - position := result.AddData(x.Data) - x64.MoveRegNum32(code, uint8(x.Destination), 0) - pointers[Address(code.Len()-4)] = position + code = x64.MoveRegNum32(code, uint8(x.Destination), 0) + + pointers = append(pointers, Pointer{ + Position: Address(len(code) - 4), + Address: data.Add(x.Data), + }) case SYSCALL: - x64.Syscall(code) + code = x64.Syscall(code) + } + + if a.Verbose { + log.Info.Println(x.String()) } } - result.Code = code.Bytes() - dataStart := config.BaseAddress + config.CodeOffset + Address(len(result.Code)) + dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) - for codePos, dataPos := range pointers { - binary.LittleEndian.PutUint32(result.Code[codePos:codePos+4], dataStart+dataPos) + for _, pointer := range pointers { + slice := code[pointer.Position : pointer.Position+4] + address := dataStart + pointer.Address + binary.LittleEndian.PutUint32(slice, address) } - return result + return code, data } // AddLabel creates a new label at the current position. -func (a *Assembler) AddLabel(name string) { - a.labels[name] = Index(len(a.instructions)) -} +// func (a *Assembler) AddLabel(name string) { +// a.labels[name] = Index(len(a.instructions)) +// } // MoveRegisterData moves a data section address into the given register. func (a *Assembler) MoveRegisterData(reg register.ID, data []byte) { - a.instructions = append(a.instructions, Instruction{ + a.Instructions = append(a.Instructions, Instruction{ Mnemonic: MOVDATA, Destination: reg, Data: data, @@ -77,7 +79,7 @@ func (a *Assembler) MoveRegisterData(reg register.ID, data []byte) { // MoveRegisterNumber moves a number into the given register. func (a *Assembler) MoveRegisterNumber(reg register.ID, number uint64) { - a.instructions = append(a.instructions, Instruction{ + a.Instructions = append(a.Instructions, Instruction{ Mnemonic: MOV, Destination: reg, Number: number, @@ -86,7 +88,7 @@ func (a *Assembler) MoveRegisterNumber(reg register.ID, number uint64) { // Syscall executes a kernel function. func (a *Assembler) Syscall() { - a.instructions = append(a.instructions, Instruction{ + a.Instructions = append(a.Instructions, Instruction{ Mnemonic: SYSCALL, }) } diff --git a/src/asm/Data.go b/src/asm/Data.go new file mode 100644 index 0000000..9268c99 --- /dev/null +++ b/src/asm/Data.go @@ -0,0 +1,19 @@ +package asm + +import "bytes" + +// Data represents the static read-only data. +type Data []byte + +// Add adds the given bytes to the data block and returns the address relative to the start of the data section. +func (data *Data) Add(block []byte) Address { + position := bytes.Index(*data, block) + + if position != -1 { + return Address(position) + } + + address := Address(len(*data)) + *data = append(*data, block...) + return address +} diff --git a/src/asm/Pointer.go b/src/asm/Pointer.go new file mode 100644 index 0000000..5d13b4f --- /dev/null +++ b/src/asm/Pointer.go @@ -0,0 +1,9 @@ +package asm + +// Pointer stores a relative memory address that we can later turn into an absolute one. +// Position: The machine code offset where the address was inserted. +// Address: The offset inside the section. +type Pointer struct { + Position uint32 + Address uint32 +} diff --git a/src/asm/Result.go b/src/asm/Result.go deleted file mode 100644 index 8261d82..0000000 --- a/src/asm/Result.go +++ /dev/null @@ -1,22 +0,0 @@ -package asm - -import "bytes" - -// Result is the compilation result and contains the machine code as well as the data. -type Result struct { - Code []byte - Data []byte -} - -// AddData adds the given bytes to the data block and returns the address relative to the start of the data section. -func (result *Result) AddData(block []byte) Address { - position := bytes.Index(result.Data, block) - - if position != -1 { - return Address(position) - } - - address := Address(len(result.Data)) - result.Data = append(result.Data, block...) - return address -} diff --git a/src/asm/x64/Move.go b/src/asm/x64/Move.go deleted file mode 100644 index d130195..0000000 --- a/src/asm/x64/Move.go +++ /dev/null @@ -1,11 +0,0 @@ -package x64 - -import ( - "io" -) - -// MoveRegNum32 moves a 32 bit integer into the given register. -func MoveRegNum32(w io.ByteWriter, register uint8, number uint32) { - w.WriteByte(0xb8 + register) - appendUint32(w, number) -} diff --git a/src/asm/x64/Syscall.go b/src/asm/x64/Syscall.go deleted file mode 100644 index 145f5a3..0000000 --- a/src/asm/x64/Syscall.go +++ /dev/null @@ -1,11 +0,0 @@ -package x64 - -import ( - "io" -) - -// Syscall is the primary way to communicate with the OS kernel. -func Syscall(w io.ByteWriter) { - w.WriteByte(0x0f) - w.WriteByte(0x05) -} diff --git a/src/asm/x64/x64.go b/src/asm/x64/x64.go deleted file mode 100644 index f6ab914..0000000 --- a/src/asm/x64/x64.go +++ /dev/null @@ -1,11 +0,0 @@ -package x64 - -import "io" - -// appendUint32 appends a 32 bit integer in Little Endian to the given writer. -func appendUint32(w io.ByteWriter, number uint32) { - w.WriteByte(byte(number)) - w.WriteByte(byte(number >> 8)) - w.WriteByte(byte(number >> 16)) - w.WriteByte(byte(number >> 24)) -} diff --git a/src/build/Build.go b/src/build/Build.go index 197665e..80d16c5 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -10,27 +10,31 @@ import ( "git.akyoto.dev/cli/q/src/directory" "git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/linux" "git.akyoto.dev/cli/q/src/log" "git.akyoto.dev/cli/q/src/register" - "git.akyoto.dev/cli/q/src/syscall" ) // Build describes a compiler build. type Build struct { - Name string Directory string + Verbose bool WriteExecutable bool } // New creates a new build. func New(directory string) *Build { return &Build{ - Name: filepath.Base(directory), Directory: directory, WriteExecutable: true, } } +var ( + hello = []byte("Hello\n") + world = []byte("World\n") +) + // Run parses the input files and generates an executable file. func (build *Build) Run() error { // err := build.Compile() @@ -39,33 +43,34 @@ func (build *Build) Run() error { // return err // } - a := asm.New() + a := asm.Assembler{ + Instructions: make([]asm.Instruction, 0, 8), + Verbose: build.Verbose, + } - a.MoveRegisterNumber(register.Syscall0, syscall.Write) + a.MoveRegisterNumber(register.Syscall0, linux.Write) a.MoveRegisterNumber(register.Syscall1, 1) - hello := []byte("Hello\n") a.MoveRegisterData(register.Syscall2, hello) a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) a.Syscall() - a.MoveRegisterNumber(register.Syscall0, syscall.Write) + a.MoveRegisterNumber(register.Syscall0, linux.Write) a.MoveRegisterNumber(register.Syscall1, 1) - world := []byte("World\n") a.MoveRegisterData(register.Syscall2, world) a.MoveRegisterNumber(register.Syscall3, uint64(len(world))) a.Syscall() - a.MoveRegisterNumber(register.Syscall0, syscall.Exit) + a.MoveRegisterNumber(register.Syscall0, linux.Exit) a.MoveRegisterNumber(register.Syscall1, 0) a.Syscall() - result := a.Finalize() + code, data := a.Finalize() if !build.WriteExecutable { return nil } - return writeToDisk(build.Executable(), result.Code, result.Data) + return writeToDisk(build.Executable(), code, data) } // Compile compiles all the functions. @@ -93,7 +98,7 @@ func (build *Build) Compile() error { // Executable returns the path to the executable. func (build *Build) Executable() string { - return filepath.Join(build.Directory, build.Name) + return filepath.Join(build.Directory, filepath.Base(build.Directory)) } // writeToDisk writes the executable file to disk. diff --git a/src/cli/Build.go b/src/cli/Build.go index 47e76db..761d418 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -20,6 +20,9 @@ func Build(args []string) int { case "--dry": b.WriteExecutable = false + case "--verbose", "-v": + b.Verbose = true + default: log.Error.Printf("Unknown parameter: %s\n", args[i]) return 2 diff --git a/src/syscall/syscall_linux.go b/src/linux/syscall_linux.go similarity index 99% rename from src/syscall/syscall_linux.go rename to src/linux/syscall_linux.go index 7c5d6f8..c535786 100644 --- a/src/syscall/syscall_linux.go +++ b/src/linux/syscall_linux.go @@ -1,6 +1,5 @@ -package syscall +package linux -// Linux syscalls const ( Read = iota Write diff --git a/src/asm/x64/Call.go b/src/x64/Call.go similarity index 51% rename from src/asm/x64/Call.go rename to src/x64/Call.go index d3958bf..1ada6fd 100644 --- a/src/asm/x64/Call.go +++ b/src/x64/Call.go @@ -1,10 +1,14 @@ package x64 -import "io" - // Call places the return address on the top of the stack and continues // program flow at the new address. The address is relative to the next instruction. -func Call(w io.ByteWriter, address uint32) { - w.WriteByte(0xe8) - appendUint32(w, address) +func Call(code []byte, address uint32) []byte { + return append( + code, + 0xe8, + byte(address), + byte(address>>8), + byte(address>>16), + byte(address>>24), + ) } diff --git a/src/x64/Move.go b/src/x64/Move.go new file mode 100644 index 0000000..6d9fd57 --- /dev/null +++ b/src/x64/Move.go @@ -0,0 +1,13 @@ +package x64 + +// MoveRegNum32 moves a 32 bit integer into the given register. +func MoveRegNum32(code []byte, register uint8, number uint32) []byte { + return append( + code, + 0xb8+register, + byte(number), + byte(number>>8), + byte(number>>16), + byte(number>>24), + ) +} diff --git a/src/asm/x64/Return.go b/src/x64/Return.go similarity index 73% rename from src/asm/x64/Return.go rename to src/x64/Return.go index 47a5b34..dc185da 100644 --- a/src/asm/x64/Return.go +++ b/src/x64/Return.go @@ -1,9 +1,7 @@ package x64 -import "io" - // Return transfers program control to a return address located on the top of the stack. // The address is usually placed on the stack by a Call instruction. -func Return(w io.ByteWriter) { - w.WriteByte(0xc3) +func Return(code []byte) []byte { + return append(code, 0xc3) } diff --git a/src/x64/Syscall.go b/src/x64/Syscall.go new file mode 100644 index 0000000..94b07d2 --- /dev/null +++ b/src/x64/Syscall.go @@ -0,0 +1,6 @@ +package x64 + +// Syscall is the primary way to communicate with the OS kernel. +func Syscall(code []byte) []byte { + return append(code, 0x0f, 0x05) +} From 16690d390f50a089aabfd64208bcdb144cc33e14 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 30 Oct 2023 16:17:41 +0100 Subject: [PATCH 0039/1012] Added file scanner --- go.mod | 2 + go.sum | 2 + main_test.go | 8 ++-- src/asm/Address.go | 3 -- src/asm/Assembler.go | 14 ++----- src/asm/Assembler_test.go | 41 ++++++++++++++++++++ src/build/Build.go | 82 +++++++++++++++++++-------------------- src/build/Function.go | 3 ++ src/build/Scan.go | 63 ++++++++++++++++++++++++++++++ src/cli/Build.go | 31 +++++++++------ 10 files changed, 178 insertions(+), 71 deletions(-) create mode 100644 go.sum create mode 100644 src/asm/Assembler_test.go create mode 100644 src/build/Function.go create mode 100644 src/build/Scan.go diff --git a/go.mod b/go.mod index 2c2b156..e78e48b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module git.akyoto.dev/cli/q go 1.21 + +require git.akyoto.dev/go/assert v0.1.3 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9fc2547 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= +git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= diff --git a/main_test.go b/main_test.go index 4aac58f..ab4ac62 100644 --- a/main_test.go +++ b/main_test.go @@ -7,6 +7,7 @@ import ( "git.akyoto.dev/cli/q/src/cli" "git.akyoto.dev/cli/q/src/log" + "git.akyoto.dev/go/assert" ) func TestMain(m *testing.M) { @@ -29,16 +30,13 @@ func TestCLI(t *testing.T) { {[]string{"build", "examples/hello/hello.q"}, 1}, {[]string{"build", "examples/hello", "--invalid"}, 2}, {[]string{"build", "examples/hello", "--dry"}, 0}, + {[]string{"build", "examples/hello", "--dry", "--verbose"}, 0}, } for _, test := range tests { t.Log(test.arguments) exitCode := cli.Main(test.arguments) - - if exitCode != test.expectedExitCode { - t.Errorf("exit code %d (expected %d)", exitCode, test.expectedExitCode) - t.FailNow() - } + assert.Equal(t, exitCode, test.expectedExitCode) } } diff --git a/src/asm/Address.go b/src/asm/Address.go index 8e32c5a..14b073b 100644 --- a/src/asm/Address.go +++ b/src/asm/Address.go @@ -2,6 +2,3 @@ package asm // Address represents a memory address. type Address = uint32 - -// Index references an instruction by its position. -// type Index = uint32 diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index 552ff91..4ed7552 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -12,20 +12,17 @@ import ( // Assembler contains a list of instructions. type Assembler struct { Instructions []Instruction - // labels map[string]Index - Verbose bool } // New creates a new assembler. func New() *Assembler { return &Assembler{ Instructions: make([]Instruction, 0, 8), - // labels: map[string]Index{}, } } // Finalize generates the final machine code. -func (a *Assembler) Finalize() ([]byte, []byte) { +func (a *Assembler) Finalize(verbose bool) ([]byte, []byte) { code := make([]byte, 0, len(a.Instructions)*8) data := make(Data, 0, 16) pointers := []Pointer{} @@ -46,8 +43,10 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case SYSCALL: code = x64.Syscall(code) } + } - if a.Verbose { + if verbose { + for _, x := range a.Instructions { log.Info.Println(x.String()) } } @@ -63,11 +62,6 @@ func (a *Assembler) Finalize() ([]byte, []byte) { return code, data } -// AddLabel creates a new label at the current position. -// func (a *Assembler) AddLabel(name string) { -// a.labels[name] = Index(len(a.instructions)) -// } - // MoveRegisterData moves a data section address into the given register. func (a *Assembler) MoveRegisterData(reg register.ID, data []byte) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/asm/Assembler_test.go b/src/asm/Assembler_test.go new file mode 100644 index 0000000..f702742 --- /dev/null +++ b/src/asm/Assembler_test.go @@ -0,0 +1,41 @@ +package asm_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/linux" + "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/go/assert" +) + +func TestHello(t *testing.T) { + a := asm.New() + + hello := []byte("Hello\n") + a.MoveRegisterNumber(register.Syscall0, linux.Write) + a.MoveRegisterNumber(register.Syscall1, 1) + a.MoveRegisterData(register.Syscall2, hello) + a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) + a.Syscall() + + a.MoveRegisterNumber(register.Syscall0, linux.Exit) + a.MoveRegisterNumber(register.Syscall1, 0) + a.Syscall() + + code, data := a.Finalize(false) + + assert.DeepEqual(t, code, []byte{ + 0xb8, 0x01, 0x00, 0x00, 0x00, + 0xbf, 0x01, 0x00, 0x00, 0x00, + 0xbe, 0xa2, 0x00, 0x40, 0x00, + 0xba, 0x06, 0x00, 0x00, 0x00, + 0x0f, 0x05, + + 0xb8, 0x3c, 0x00, 0x00, 0x00, + 0xbf, 0x00, 0x00, 0x00, 0x00, + 0x0f, 0x05, + }) + + assert.DeepEqual(t, data, hello) +} diff --git a/src/build/Build.go b/src/build/Build.go index 80d16c5..80a0e66 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -4,10 +4,8 @@ import ( "bufio" "os" "path/filepath" - "strings" "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/directory" "git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/linux" @@ -30,42 +28,14 @@ func New(directory string) *Build { } } -var ( - hello = []byte("Hello\n") - world = []byte("World\n") -) - // Run parses the input files and generates an executable file. func (build *Build) Run() error { - // err := build.Compile() + code, data, err := build.Compile() - // if err != nil { - // return err - // } - - a := asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 8), - Verbose: build.Verbose, + if err != nil { + return err } - a.MoveRegisterNumber(register.Syscall0, linux.Write) - a.MoveRegisterNumber(register.Syscall1, 1) - a.MoveRegisterData(register.Syscall2, hello) - a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) - a.Syscall() - - a.MoveRegisterNumber(register.Syscall0, linux.Write) - a.MoveRegisterNumber(register.Syscall1, 1) - a.MoveRegisterData(register.Syscall2, world) - a.MoveRegisterNumber(register.Syscall3, uint64(len(world))) - a.Syscall() - - a.MoveRegisterNumber(register.Syscall0, linux.Exit) - a.MoveRegisterNumber(register.Syscall1, 0) - a.Syscall() - - code, data := a.Finalize() - if !build.WriteExecutable { return nil } @@ -74,26 +44,54 @@ func (build *Build) Run() error { } // Compile compiles all the functions. -func (build *Build) Compile() error { +func (build *Build) Compile() ([]byte, []byte, error) { stat, err := os.Stat(build.Directory) if err != nil { - return err + return nil, nil, err } if !stat.IsDir() { - return &errors.InvalidDirectory{Path: build.Directory} + return nil, nil, &errors.InvalidDirectory{Path: build.Directory} } - directory.Walk(build.Directory, func(file string) { - if !strings.HasSuffix(file, ".q") { - return + functions, errors := Scan(build.Directory) + + for functions != nil || errors != nil { + select { + case err, ok := <-errors: + if !ok { + errors = nil + continue + } + + log.Info.Println(err) + + case function, ok := <-functions: + if !ok { + functions = nil + continue + } + + log.Info.Println(function) } + } - log.Info.Println(file) - }) + a := asm.New() - return nil + hello := []byte("Hello\n") + a.MoveRegisterNumber(register.Syscall0, linux.Write) + a.MoveRegisterNumber(register.Syscall1, 1) + a.MoveRegisterData(register.Syscall2, hello) + a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) + a.Syscall() + + a.MoveRegisterNumber(register.Syscall0, linux.Exit) + a.MoveRegisterNumber(register.Syscall1, 0) + a.Syscall() + + code, data := a.Finalize(build.Verbose) + return code, data, nil } // Executable returns the path to the executable. diff --git a/src/build/Function.go b/src/build/Function.go new file mode 100644 index 0000000..d3734e3 --- /dev/null +++ b/src/build/Function.go @@ -0,0 +1,3 @@ +package build + +type Function struct{} diff --git a/src/build/Scan.go b/src/build/Scan.go new file mode 100644 index 0000000..9475727 --- /dev/null +++ b/src/build/Scan.go @@ -0,0 +1,63 @@ +package build + +import ( + "os" + "path/filepath" + "strings" + "sync" + + "git.akyoto.dev/cli/q/src/directory" + "git.akyoto.dev/cli/q/src/log" +) + +// Scan scans the directory. +func Scan(path string) (<-chan *Function, <-chan error) { + functions := make(chan *Function, 16) + errors := make(chan error) + + go func() { + scanDirectory(path, functions, errors) + close(functions) + close(errors) + }() + + return functions, errors +} + +// scanDirectory scans the directory without channel allocations. +func scanDirectory(path string, functions chan<- *Function, errors chan<- error) { + wg := sync.WaitGroup{} + + directory.Walk(path, func(name string) { + if !strings.HasSuffix(name, ".q") { + return + } + + fullPath := filepath.Join(path, name) + wg.Add(1) + + go func() { + defer wg.Done() + err := scanFile(fullPath, functions) + + if err != nil { + errors <- err + } + }() + }) + + wg.Wait() +} + +// scanFile scans a single file. +func scanFile(path string, functions chan<- *Function) error { + log.Info.Println(path) + contents, err := os.ReadFile(path) + + if err != nil { + return err + } + + log.Info.Println(string(contents)) + return nil +} diff --git a/src/cli/Build.go b/src/cli/Build.go index 761d418..fefbe24 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -1,21 +1,18 @@ package cli import ( + "path/filepath" + "strings" + "git.akyoto.dev/cli/q/src/build" "git.akyoto.dev/cli/q/src/log" ) // Build builds an executable. func Build(args []string) int { - directory := "." + b := build.New(".") - if len(args) > 0 { - directory = args[0] - } - - b := build.New(directory) - - for i := 1; i < len(args); i++ { + for i := 0; i < len(args); i++ { switch args[i] { case "--dry": b.WriteExecutable = false @@ -24,12 +21,24 @@ func Build(args []string) int { b.Verbose = true default: - log.Error.Printf("Unknown parameter: %s\n", args[i]) - return 2 + if strings.HasPrefix(args[i], "-") { + log.Error.Printf("Unknown parameter: %s\n", args[i]) + return 2 + } + + b.Directory = args[i] } } - err := b.Run() + fullPath, err := filepath.Abs(b.Directory) + + if err != nil { + log.Error.Println(err) + return 1 + } + + b.Directory = fullPath + err = b.Run() if err != nil { log.Error.Println(err) From 8b1998937261bcb5b712bc7ddb28abce1fdbeb43 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 30 Oct 2023 16:17:41 +0100 Subject: [PATCH 0040/1012] Added file scanner --- go.mod | 2 + go.sum | 2 + main_test.go | 8 ++-- src/asm/Address.go | 3 -- src/asm/Assembler.go | 14 ++----- src/asm/Assembler_test.go | 41 ++++++++++++++++++++ src/build/Build.go | 82 +++++++++++++++++++-------------------- src/build/Function.go | 3 ++ src/build/Scan.go | 63 ++++++++++++++++++++++++++++++ src/cli/Build.go | 31 +++++++++------ 10 files changed, 178 insertions(+), 71 deletions(-) create mode 100644 go.sum create mode 100644 src/asm/Assembler_test.go create mode 100644 src/build/Function.go create mode 100644 src/build/Scan.go diff --git a/go.mod b/go.mod index 2c2b156..e78e48b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module git.akyoto.dev/cli/q go 1.21 + +require git.akyoto.dev/go/assert v0.1.3 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9fc2547 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= +git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= diff --git a/main_test.go b/main_test.go index 4aac58f..ab4ac62 100644 --- a/main_test.go +++ b/main_test.go @@ -7,6 +7,7 @@ import ( "git.akyoto.dev/cli/q/src/cli" "git.akyoto.dev/cli/q/src/log" + "git.akyoto.dev/go/assert" ) func TestMain(m *testing.M) { @@ -29,16 +30,13 @@ func TestCLI(t *testing.T) { {[]string{"build", "examples/hello/hello.q"}, 1}, {[]string{"build", "examples/hello", "--invalid"}, 2}, {[]string{"build", "examples/hello", "--dry"}, 0}, + {[]string{"build", "examples/hello", "--dry", "--verbose"}, 0}, } for _, test := range tests { t.Log(test.arguments) exitCode := cli.Main(test.arguments) - - if exitCode != test.expectedExitCode { - t.Errorf("exit code %d (expected %d)", exitCode, test.expectedExitCode) - t.FailNow() - } + assert.Equal(t, exitCode, test.expectedExitCode) } } diff --git a/src/asm/Address.go b/src/asm/Address.go index 8e32c5a..14b073b 100644 --- a/src/asm/Address.go +++ b/src/asm/Address.go @@ -2,6 +2,3 @@ package asm // Address represents a memory address. type Address = uint32 - -// Index references an instruction by its position. -// type Index = uint32 diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index 552ff91..4ed7552 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -12,20 +12,17 @@ import ( // Assembler contains a list of instructions. type Assembler struct { Instructions []Instruction - // labels map[string]Index - Verbose bool } // New creates a new assembler. func New() *Assembler { return &Assembler{ Instructions: make([]Instruction, 0, 8), - // labels: map[string]Index{}, } } // Finalize generates the final machine code. -func (a *Assembler) Finalize() ([]byte, []byte) { +func (a *Assembler) Finalize(verbose bool) ([]byte, []byte) { code := make([]byte, 0, len(a.Instructions)*8) data := make(Data, 0, 16) pointers := []Pointer{} @@ -46,8 +43,10 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case SYSCALL: code = x64.Syscall(code) } + } - if a.Verbose { + if verbose { + for _, x := range a.Instructions { log.Info.Println(x.String()) } } @@ -63,11 +62,6 @@ func (a *Assembler) Finalize() ([]byte, []byte) { return code, data } -// AddLabel creates a new label at the current position. -// func (a *Assembler) AddLabel(name string) { -// a.labels[name] = Index(len(a.instructions)) -// } - // MoveRegisterData moves a data section address into the given register. func (a *Assembler) MoveRegisterData(reg register.ID, data []byte) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/asm/Assembler_test.go b/src/asm/Assembler_test.go new file mode 100644 index 0000000..f702742 --- /dev/null +++ b/src/asm/Assembler_test.go @@ -0,0 +1,41 @@ +package asm_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/linux" + "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/go/assert" +) + +func TestHello(t *testing.T) { + a := asm.New() + + hello := []byte("Hello\n") + a.MoveRegisterNumber(register.Syscall0, linux.Write) + a.MoveRegisterNumber(register.Syscall1, 1) + a.MoveRegisterData(register.Syscall2, hello) + a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) + a.Syscall() + + a.MoveRegisterNumber(register.Syscall0, linux.Exit) + a.MoveRegisterNumber(register.Syscall1, 0) + a.Syscall() + + code, data := a.Finalize(false) + + assert.DeepEqual(t, code, []byte{ + 0xb8, 0x01, 0x00, 0x00, 0x00, + 0xbf, 0x01, 0x00, 0x00, 0x00, + 0xbe, 0xa2, 0x00, 0x40, 0x00, + 0xba, 0x06, 0x00, 0x00, 0x00, + 0x0f, 0x05, + + 0xb8, 0x3c, 0x00, 0x00, 0x00, + 0xbf, 0x00, 0x00, 0x00, 0x00, + 0x0f, 0x05, + }) + + assert.DeepEqual(t, data, hello) +} diff --git a/src/build/Build.go b/src/build/Build.go index 80d16c5..80a0e66 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -4,10 +4,8 @@ import ( "bufio" "os" "path/filepath" - "strings" "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/directory" "git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/linux" @@ -30,42 +28,14 @@ func New(directory string) *Build { } } -var ( - hello = []byte("Hello\n") - world = []byte("World\n") -) - // Run parses the input files and generates an executable file. func (build *Build) Run() error { - // err := build.Compile() + code, data, err := build.Compile() - // if err != nil { - // return err - // } - - a := asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 8), - Verbose: build.Verbose, + if err != nil { + return err } - a.MoveRegisterNumber(register.Syscall0, linux.Write) - a.MoveRegisterNumber(register.Syscall1, 1) - a.MoveRegisterData(register.Syscall2, hello) - a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) - a.Syscall() - - a.MoveRegisterNumber(register.Syscall0, linux.Write) - a.MoveRegisterNumber(register.Syscall1, 1) - a.MoveRegisterData(register.Syscall2, world) - a.MoveRegisterNumber(register.Syscall3, uint64(len(world))) - a.Syscall() - - a.MoveRegisterNumber(register.Syscall0, linux.Exit) - a.MoveRegisterNumber(register.Syscall1, 0) - a.Syscall() - - code, data := a.Finalize() - if !build.WriteExecutable { return nil } @@ -74,26 +44,54 @@ func (build *Build) Run() error { } // Compile compiles all the functions. -func (build *Build) Compile() error { +func (build *Build) Compile() ([]byte, []byte, error) { stat, err := os.Stat(build.Directory) if err != nil { - return err + return nil, nil, err } if !stat.IsDir() { - return &errors.InvalidDirectory{Path: build.Directory} + return nil, nil, &errors.InvalidDirectory{Path: build.Directory} } - directory.Walk(build.Directory, func(file string) { - if !strings.HasSuffix(file, ".q") { - return + functions, errors := Scan(build.Directory) + + for functions != nil || errors != nil { + select { + case err, ok := <-errors: + if !ok { + errors = nil + continue + } + + log.Info.Println(err) + + case function, ok := <-functions: + if !ok { + functions = nil + continue + } + + log.Info.Println(function) } + } - log.Info.Println(file) - }) + a := asm.New() - return nil + hello := []byte("Hello\n") + a.MoveRegisterNumber(register.Syscall0, linux.Write) + a.MoveRegisterNumber(register.Syscall1, 1) + a.MoveRegisterData(register.Syscall2, hello) + a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) + a.Syscall() + + a.MoveRegisterNumber(register.Syscall0, linux.Exit) + a.MoveRegisterNumber(register.Syscall1, 0) + a.Syscall() + + code, data := a.Finalize(build.Verbose) + return code, data, nil } // Executable returns the path to the executable. diff --git a/src/build/Function.go b/src/build/Function.go new file mode 100644 index 0000000..d3734e3 --- /dev/null +++ b/src/build/Function.go @@ -0,0 +1,3 @@ +package build + +type Function struct{} diff --git a/src/build/Scan.go b/src/build/Scan.go new file mode 100644 index 0000000..9475727 --- /dev/null +++ b/src/build/Scan.go @@ -0,0 +1,63 @@ +package build + +import ( + "os" + "path/filepath" + "strings" + "sync" + + "git.akyoto.dev/cli/q/src/directory" + "git.akyoto.dev/cli/q/src/log" +) + +// Scan scans the directory. +func Scan(path string) (<-chan *Function, <-chan error) { + functions := make(chan *Function, 16) + errors := make(chan error) + + go func() { + scanDirectory(path, functions, errors) + close(functions) + close(errors) + }() + + return functions, errors +} + +// scanDirectory scans the directory without channel allocations. +func scanDirectory(path string, functions chan<- *Function, errors chan<- error) { + wg := sync.WaitGroup{} + + directory.Walk(path, func(name string) { + if !strings.HasSuffix(name, ".q") { + return + } + + fullPath := filepath.Join(path, name) + wg.Add(1) + + go func() { + defer wg.Done() + err := scanFile(fullPath, functions) + + if err != nil { + errors <- err + } + }() + }) + + wg.Wait() +} + +// scanFile scans a single file. +func scanFile(path string, functions chan<- *Function) error { + log.Info.Println(path) + contents, err := os.ReadFile(path) + + if err != nil { + return err + } + + log.Info.Println(string(contents)) + return nil +} diff --git a/src/cli/Build.go b/src/cli/Build.go index 761d418..fefbe24 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -1,21 +1,18 @@ package cli import ( + "path/filepath" + "strings" + "git.akyoto.dev/cli/q/src/build" "git.akyoto.dev/cli/q/src/log" ) // Build builds an executable. func Build(args []string) int { - directory := "." + b := build.New(".") - if len(args) > 0 { - directory = args[0] - } - - b := build.New(directory) - - for i := 1; i < len(args); i++ { + for i := 0; i < len(args); i++ { switch args[i] { case "--dry": b.WriteExecutable = false @@ -24,12 +21,24 @@ func Build(args []string) int { b.Verbose = true default: - log.Error.Printf("Unknown parameter: %s\n", args[i]) - return 2 + if strings.HasPrefix(args[i], "-") { + log.Error.Printf("Unknown parameter: %s\n", args[i]) + return 2 + } + + b.Directory = args[i] } } - err := b.Run() + fullPath, err := filepath.Abs(b.Directory) + + if err != nil { + log.Error.Println(err) + return 1 + } + + b.Directory = fullPath + err = b.Run() if err != nil { log.Error.Println(err) From 21cf4b5c6dfc50929e3a65e59940db7ff47b4c1f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 31 Oct 2023 11:57:37 +0100 Subject: [PATCH 0041/1012] Added a tokenizer --- src/build/Scan.go | 9 +++- src/cli/Build.go | 11 +--- src/keywords/All.go | 6 +++ src/token/Kind.go | 116 ++++++++++++++++++++++++++++++++++++++++++ src/token/List.go | 17 +++++++ src/token/Token.go | 15 ++++++ src/token/Tokenize.go | 112 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 274 insertions(+), 12 deletions(-) create mode 100644 src/keywords/All.go create mode 100644 src/token/Kind.go create mode 100644 src/token/List.go create mode 100644 src/token/Token.go create mode 100644 src/token/Tokenize.go diff --git a/src/build/Scan.go b/src/build/Scan.go index 9475727..229cead 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -8,6 +8,7 @@ import ( "git.akyoto.dev/cli/q/src/directory" "git.akyoto.dev/cli/q/src/log" + "git.akyoto.dev/cli/q/src/token" ) // Scan scans the directory. @@ -51,13 +52,17 @@ func scanDirectory(path string, functions chan<- *Function, errors chan<- error) // scanFile scans a single file. func scanFile(path string, functions chan<- *Function) error { - log.Info.Println(path) contents, err := os.ReadFile(path) if err != nil { return err } - log.Info.Println(string(contents)) + tokens := token.Tokenize(contents) + + for _, t := range tokens { + log.Info.Println(t.Kind, t.Position, strings.TrimSpace(t.String())) + } + return nil } diff --git a/src/cli/Build.go b/src/cli/Build.go index fefbe24..ec1bf6a 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -1,7 +1,6 @@ package cli import ( - "path/filepath" "strings" "git.akyoto.dev/cli/q/src/build" @@ -30,15 +29,7 @@ func Build(args []string) int { } } - fullPath, err := filepath.Abs(b.Directory) - - if err != nil { - log.Error.Println(err) - return 1 - } - - b.Directory = fullPath - err = b.Run() + err := b.Run() if err != nil { log.Error.Println(err) diff --git a/src/keywords/All.go b/src/keywords/All.go new file mode 100644 index 0000000..5df0a39 --- /dev/null +++ b/src/keywords/All.go @@ -0,0 +1,6 @@ +package keywords + +// All defines the keywords used in the language. +var All = map[string]bool{ + "return": true, +} diff --git a/src/token/Kind.go b/src/token/Kind.go new file mode 100644 index 0000000..742c381 --- /dev/null +++ b/src/token/Kind.go @@ -0,0 +1,116 @@ +package token + +// Kind represents the type of token. +type Kind uint8 + +const ( + // Invalid represents an invalid token. + Invalid Kind = iota + + // NewLine represents the newline character. + NewLine + + // Identifier represents a series of characters used to identify a variable or function. + Identifier + + // Keyword represents a language keyword. + Keyword + + // Text represents an uninterpreted series of characters in the source code. + Text + + // Number represents a series of numerical characters. + Number + + // Operator represents a mathematical operator. + Operator + + // Separator represents a comma. + Separator + + // Range represents '..'. + Range + + // Question represents '?'. + Question + + // Comment represents a comment. + Comment + + // GroupStart represents '('. + GroupStart + + // GroupEnd represents ')'. + GroupEnd + + // BlockStart represents '{'. + BlockStart + + // BlockEnd represents '}'. + BlockEnd + + // ArrayStart represents '['. + ArrayStart + + // ArrayEnd represents ']'. + ArrayEnd +) + +// String returns the text representation. +func (kind Kind) String() string { + switch kind { + case NewLine: + return "NewLine" + + case Identifier: + return "Identifier" + + case Keyword: + return "Keyword" + + case Text: + return "Text" + + case Number: + return "Number" + + case Operator: + return "Operator" + + case Separator: + return "Separator" + + case Range: + return "Range" + + case Question: + return "Question" + + case Comment: + return "Comment" + + case GroupStart: + return "GroupStart" + + case GroupEnd: + return "GroupEnd" + + case BlockStart: + return "BlockStart" + + case BlockEnd: + return "BlockEnd" + + case ArrayStart: + return "ArrayStart" + + case ArrayEnd: + return "ArrayEnd" + + case Invalid: + return "Invalid" + + default: + return "" + } +} diff --git a/src/token/List.go b/src/token/List.go new file mode 100644 index 0000000..d5ea0ad --- /dev/null +++ b/src/token/List.go @@ -0,0 +1,17 @@ +package token + +import "strings" + +// List is a slice of tokens. +type List []Token + +// String implements string serialization. +func (list List) String() string { + builder := strings.Builder{} + + for _, t := range list { + builder.WriteString(t.String()) + } + + return builder.String() +} diff --git a/src/token/Token.go b/src/token/Token.go new file mode 100644 index 0000000..f88b69c --- /dev/null +++ b/src/token/Token.go @@ -0,0 +1,15 @@ +package token + +// Token represents a single element in a source file. +// The characters that make up an identifier are grouped into a single token. +// This makes parsing easier and allows us to do better syntax checks. +type Token struct { + Kind Kind + Position int + Bytes []byte +} + +// String returns the token text. +func (t Token) String() string { + return string(t.Bytes) +} diff --git a/src/token/Tokenize.go b/src/token/Tokenize.go new file mode 100644 index 0000000..9d359b8 --- /dev/null +++ b/src/token/Tokenize.go @@ -0,0 +1,112 @@ +package token + +import "git.akyoto.dev/cli/q/src/keywords" + +// Pre-allocate these byte buffers so we can re-use them +// instead of allocating a new buffer every time. +var ( + groupStartBytes = []byte{'('} + groupEndBytes = []byte{')'} + blockStartBytes = []byte{'{'} + blockEndBytes = []byte{'}'} + arrayStartBytes = []byte{'['} + arrayEndBytes = []byte{']'} + separatorBytes = []byte{','} + newLineBytes = []byte{'\n'} +) + +// Tokenize turns the file contents into a list of tokens. +func Tokenize(buffer []byte) List { + var ( + i int + c byte + tokens = make(List, 0, len(buffer)/2) + ) + + for i < len(buffer) { + c = buffer[i] + + switch { + // Identifiers + case isIdentifierStart(c): + position := i + i++ + + for i < len(buffer) && isIdentifier(buffer[i]) { + i++ + } + + token := Token{ + Identifier, + position, + buffer[position:i], + } + + if keywords.All[string(token.Bytes)] { + token.Kind = Keyword + } + + tokens = append(tokens, token) + i-- + + // Texts + case c == '"': + i++ + position := i + + for i < len(buffer) && buffer[i] != '"' { + i++ + } + + tokens = append(tokens, Token{ + Text, + position, + buffer[position:i], + }) + + // Parentheses start + case c == '(': + tokens = append(tokens, Token{GroupStart, i, groupStartBytes}) + + // Parentheses end + case c == ')': + tokens = append(tokens, Token{GroupEnd, i, groupEndBytes}) + + // Block start + case c == '{': + tokens = append(tokens, Token{BlockStart, i, blockStartBytes}) + + // Block end + case c == '}': + tokens = append(tokens, Token{BlockEnd, i, blockEndBytes}) + + // Array start + case c == '[': + tokens = append(tokens, Token{ArrayStart, i, arrayStartBytes}) + + // Array end + case c == ']': + tokens = append(tokens, Token{ArrayEnd, i, arrayEndBytes}) + + // Separator + case c == ',': + tokens = append(tokens, Token{Separator, i, separatorBytes}) + + // New line + case c == '\n': + tokens = append(tokens, Token{NewLine, i, newLineBytes}) + } + + i++ + } + + return tokens +} + +func isIdentifierStart(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' +} + +func isIdentifier(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (c >= '0' && c <= '9') +} From ac157e580cc7f2e3fb94dc3507c6d0df6a8fec66 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 31 Oct 2023 11:57:37 +0100 Subject: [PATCH 0042/1012] Added a tokenizer --- src/build/Scan.go | 9 +++- src/cli/Build.go | 11 +--- src/keywords/All.go | 6 +++ src/token/Kind.go | 116 ++++++++++++++++++++++++++++++++++++++++++ src/token/List.go | 17 +++++++ src/token/Token.go | 15 ++++++ src/token/Tokenize.go | 112 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 274 insertions(+), 12 deletions(-) create mode 100644 src/keywords/All.go create mode 100644 src/token/Kind.go create mode 100644 src/token/List.go create mode 100644 src/token/Token.go create mode 100644 src/token/Tokenize.go diff --git a/src/build/Scan.go b/src/build/Scan.go index 9475727..229cead 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -8,6 +8,7 @@ import ( "git.akyoto.dev/cli/q/src/directory" "git.akyoto.dev/cli/q/src/log" + "git.akyoto.dev/cli/q/src/token" ) // Scan scans the directory. @@ -51,13 +52,17 @@ func scanDirectory(path string, functions chan<- *Function, errors chan<- error) // scanFile scans a single file. func scanFile(path string, functions chan<- *Function) error { - log.Info.Println(path) contents, err := os.ReadFile(path) if err != nil { return err } - log.Info.Println(string(contents)) + tokens := token.Tokenize(contents) + + for _, t := range tokens { + log.Info.Println(t.Kind, t.Position, strings.TrimSpace(t.String())) + } + return nil } diff --git a/src/cli/Build.go b/src/cli/Build.go index fefbe24..ec1bf6a 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -1,7 +1,6 @@ package cli import ( - "path/filepath" "strings" "git.akyoto.dev/cli/q/src/build" @@ -30,15 +29,7 @@ func Build(args []string) int { } } - fullPath, err := filepath.Abs(b.Directory) - - if err != nil { - log.Error.Println(err) - return 1 - } - - b.Directory = fullPath - err = b.Run() + err := b.Run() if err != nil { log.Error.Println(err) diff --git a/src/keywords/All.go b/src/keywords/All.go new file mode 100644 index 0000000..5df0a39 --- /dev/null +++ b/src/keywords/All.go @@ -0,0 +1,6 @@ +package keywords + +// All defines the keywords used in the language. +var All = map[string]bool{ + "return": true, +} diff --git a/src/token/Kind.go b/src/token/Kind.go new file mode 100644 index 0000000..742c381 --- /dev/null +++ b/src/token/Kind.go @@ -0,0 +1,116 @@ +package token + +// Kind represents the type of token. +type Kind uint8 + +const ( + // Invalid represents an invalid token. + Invalid Kind = iota + + // NewLine represents the newline character. + NewLine + + // Identifier represents a series of characters used to identify a variable or function. + Identifier + + // Keyword represents a language keyword. + Keyword + + // Text represents an uninterpreted series of characters in the source code. + Text + + // Number represents a series of numerical characters. + Number + + // Operator represents a mathematical operator. + Operator + + // Separator represents a comma. + Separator + + // Range represents '..'. + Range + + // Question represents '?'. + Question + + // Comment represents a comment. + Comment + + // GroupStart represents '('. + GroupStart + + // GroupEnd represents ')'. + GroupEnd + + // BlockStart represents '{'. + BlockStart + + // BlockEnd represents '}'. + BlockEnd + + // ArrayStart represents '['. + ArrayStart + + // ArrayEnd represents ']'. + ArrayEnd +) + +// String returns the text representation. +func (kind Kind) String() string { + switch kind { + case NewLine: + return "NewLine" + + case Identifier: + return "Identifier" + + case Keyword: + return "Keyword" + + case Text: + return "Text" + + case Number: + return "Number" + + case Operator: + return "Operator" + + case Separator: + return "Separator" + + case Range: + return "Range" + + case Question: + return "Question" + + case Comment: + return "Comment" + + case GroupStart: + return "GroupStart" + + case GroupEnd: + return "GroupEnd" + + case BlockStart: + return "BlockStart" + + case BlockEnd: + return "BlockEnd" + + case ArrayStart: + return "ArrayStart" + + case ArrayEnd: + return "ArrayEnd" + + case Invalid: + return "Invalid" + + default: + return "" + } +} diff --git a/src/token/List.go b/src/token/List.go new file mode 100644 index 0000000..d5ea0ad --- /dev/null +++ b/src/token/List.go @@ -0,0 +1,17 @@ +package token + +import "strings" + +// List is a slice of tokens. +type List []Token + +// String implements string serialization. +func (list List) String() string { + builder := strings.Builder{} + + for _, t := range list { + builder.WriteString(t.String()) + } + + return builder.String() +} diff --git a/src/token/Token.go b/src/token/Token.go new file mode 100644 index 0000000..f88b69c --- /dev/null +++ b/src/token/Token.go @@ -0,0 +1,15 @@ +package token + +// Token represents a single element in a source file. +// The characters that make up an identifier are grouped into a single token. +// This makes parsing easier and allows us to do better syntax checks. +type Token struct { + Kind Kind + Position int + Bytes []byte +} + +// String returns the token text. +func (t Token) String() string { + return string(t.Bytes) +} diff --git a/src/token/Tokenize.go b/src/token/Tokenize.go new file mode 100644 index 0000000..9d359b8 --- /dev/null +++ b/src/token/Tokenize.go @@ -0,0 +1,112 @@ +package token + +import "git.akyoto.dev/cli/q/src/keywords" + +// Pre-allocate these byte buffers so we can re-use them +// instead of allocating a new buffer every time. +var ( + groupStartBytes = []byte{'('} + groupEndBytes = []byte{')'} + blockStartBytes = []byte{'{'} + blockEndBytes = []byte{'}'} + arrayStartBytes = []byte{'['} + arrayEndBytes = []byte{']'} + separatorBytes = []byte{','} + newLineBytes = []byte{'\n'} +) + +// Tokenize turns the file contents into a list of tokens. +func Tokenize(buffer []byte) List { + var ( + i int + c byte + tokens = make(List, 0, len(buffer)/2) + ) + + for i < len(buffer) { + c = buffer[i] + + switch { + // Identifiers + case isIdentifierStart(c): + position := i + i++ + + for i < len(buffer) && isIdentifier(buffer[i]) { + i++ + } + + token := Token{ + Identifier, + position, + buffer[position:i], + } + + if keywords.All[string(token.Bytes)] { + token.Kind = Keyword + } + + tokens = append(tokens, token) + i-- + + // Texts + case c == '"': + i++ + position := i + + for i < len(buffer) && buffer[i] != '"' { + i++ + } + + tokens = append(tokens, Token{ + Text, + position, + buffer[position:i], + }) + + // Parentheses start + case c == '(': + tokens = append(tokens, Token{GroupStart, i, groupStartBytes}) + + // Parentheses end + case c == ')': + tokens = append(tokens, Token{GroupEnd, i, groupEndBytes}) + + // Block start + case c == '{': + tokens = append(tokens, Token{BlockStart, i, blockStartBytes}) + + // Block end + case c == '}': + tokens = append(tokens, Token{BlockEnd, i, blockEndBytes}) + + // Array start + case c == '[': + tokens = append(tokens, Token{ArrayStart, i, arrayStartBytes}) + + // Array end + case c == ']': + tokens = append(tokens, Token{ArrayEnd, i, arrayEndBytes}) + + // Separator + case c == ',': + tokens = append(tokens, Token{Separator, i, separatorBytes}) + + // New line + case c == '\n': + tokens = append(tokens, Token{NewLine, i, newLineBytes}) + } + + i++ + } + + return tokens +} + +func isIdentifierStart(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' +} + +func isIdentifier(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (c >= '0' && c <= '9') +} From 92b7c22c6180f1b2f7cd1abc06c8b0b1c2b869bf Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 31 Oct 2023 12:17:03 +0100 Subject: [PATCH 0043/1012] Added a function scanner --- src/build/Function.go | 6 +++++- src/build/Scan.go | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/build/Function.go b/src/build/Function.go index d3734e3..8bb5108 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -1,3 +1,7 @@ package build -type Function struct{} +import "git.akyoto.dev/cli/q/src/token" + +type Function struct { + Tokens token.List +} diff --git a/src/build/Scan.go b/src/build/Scan.go index 229cead..d1cba26 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -7,7 +7,6 @@ import ( "sync" "git.akyoto.dev/cli/q/src/directory" - "git.akyoto.dev/cli/q/src/log" "git.akyoto.dev/cli/q/src/token" ) @@ -60,8 +59,39 @@ func scanFile(path string, functions chan<- *Function) error { tokens := token.Tokenize(contents) - for _, t := range tokens { - log.Info.Println(t.Kind, t.Position, strings.TrimSpace(t.String())) + var ( + groupLevel = 0 + blockLevel = 0 + functionStart = -1 + ) + + for i, t := range tokens { + switch t.Kind { + case token.Identifier: + if blockLevel == 0 && groupLevel == 0 { + functionStart = i + } + + case token.GroupStart: + groupLevel++ + + case token.GroupEnd: + groupLevel-- + + case token.BlockStart: + blockLevel++ + + case token.BlockEnd: + blockLevel-- + + if blockLevel == 0 { + function := &Function{ + Tokens: tokens[functionStart : i+1], + } + + functions <- function + } + } } return nil From b6be9357bfe1d4775a6c72e401d884ff283a7ed1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 31 Oct 2023 12:17:03 +0100 Subject: [PATCH 0044/1012] Added a function scanner --- src/build/Function.go | 6 +++++- src/build/Scan.go | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/build/Function.go b/src/build/Function.go index d3734e3..8bb5108 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -1,3 +1,7 @@ package build -type Function struct{} +import "git.akyoto.dev/cli/q/src/token" + +type Function struct { + Tokens token.List +} diff --git a/src/build/Scan.go b/src/build/Scan.go index 229cead..d1cba26 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -7,7 +7,6 @@ import ( "sync" "git.akyoto.dev/cli/q/src/directory" - "git.akyoto.dev/cli/q/src/log" "git.akyoto.dev/cli/q/src/token" ) @@ -60,8 +59,39 @@ func scanFile(path string, functions chan<- *Function) error { tokens := token.Tokenize(contents) - for _, t := range tokens { - log.Info.Println(t.Kind, t.Position, strings.TrimSpace(t.String())) + var ( + groupLevel = 0 + blockLevel = 0 + functionStart = -1 + ) + + for i, t := range tokens { + switch t.Kind { + case token.Identifier: + if blockLevel == 0 && groupLevel == 0 { + functionStart = i + } + + case token.GroupStart: + groupLevel++ + + case token.GroupEnd: + groupLevel-- + + case token.BlockStart: + blockLevel++ + + case token.BlockEnd: + blockLevel-- + + if blockLevel == 0 { + function := &Function{ + Tokens: tokens[functionStart : i+1], + } + + functions <- function + } + } } return nil From 8549aedc6093b53c2572ee6f7cdd7e934a607b7a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 31 Oct 2023 14:50:53 +0100 Subject: [PATCH 0045/1012] Added assembler merging --- src/asm/Assembler.go | 5 ++++ src/build/Build.go | 68 ++++++++----------------------------------- src/build/Compile.go | 40 +++++++++++++++++++++++++ src/build/Finalize.go | 23 +++++++++++++++ src/build/Function.go | 32 ++++++++++++++++++-- src/build/Scan.go | 17 +++++++---- 6 files changed, 122 insertions(+), 63 deletions(-) create mode 100644 src/build/Compile.go create mode 100644 src/build/Finalize.go diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index 4ed7552..0fb1c49 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -62,6 +62,11 @@ func (a *Assembler) Finalize(verbose bool) ([]byte, []byte) { return code, data } +// Merge combines the contents of this assembler with another one. +func (a *Assembler) Merge(b *Assembler) { + a.Instructions = append(a.Instructions, b.Instructions...) +} + // MoveRegisterData moves a data section address into the given register. func (a *Assembler) MoveRegisterData(reg register.ID, data []byte) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/Build.go b/src/build/Build.go index 80a0e66..a77bcf2 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -5,12 +5,8 @@ import ( "os" "path/filepath" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/linux" - "git.akyoto.dev/cli/q/src/log" - "git.akyoto.dev/cli/q/src/register" ) // Build describes a compiler build. @@ -30,7 +26,17 @@ func New(directory string) *Build { // Run parses the input files and generates an executable file. func (build *Build) Run() error { - code, data, err := build.Compile() + stat, err := os.Stat(build.Directory) + + if err != nil { + return err + } + + if !stat.IsDir() { + return &errors.InvalidDirectory{Path: build.Directory} + } + + functions, err := build.Compile() if err != nil { return err @@ -40,60 +46,10 @@ func (build *Build) Run() error { return nil } + code, data := build.Finalize(functions) return writeToDisk(build.Executable(), code, data) } -// Compile compiles all the functions. -func (build *Build) Compile() ([]byte, []byte, error) { - stat, err := os.Stat(build.Directory) - - if err != nil { - return nil, nil, err - } - - if !stat.IsDir() { - return nil, nil, &errors.InvalidDirectory{Path: build.Directory} - } - - functions, errors := Scan(build.Directory) - - for functions != nil || errors != nil { - select { - case err, ok := <-errors: - if !ok { - errors = nil - continue - } - - log.Info.Println(err) - - case function, ok := <-functions: - if !ok { - functions = nil - continue - } - - log.Info.Println(function) - } - } - - a := asm.New() - - hello := []byte("Hello\n") - a.MoveRegisterNumber(register.Syscall0, linux.Write) - a.MoveRegisterNumber(register.Syscall1, 1) - a.MoveRegisterData(register.Syscall2, hello) - a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) - a.Syscall() - - a.MoveRegisterNumber(register.Syscall0, linux.Exit) - a.MoveRegisterNumber(register.Syscall1, 0) - a.Syscall() - - code, data := a.Finalize(build.Verbose) - return code, data, nil -} - // Executable returns the path to the executable. func (build *Build) Executable() string { return filepath.Join(build.Directory, filepath.Base(build.Directory)) diff --git a/src/build/Compile.go b/src/build/Compile.go new file mode 100644 index 0000000..0fd5f9a --- /dev/null +++ b/src/build/Compile.go @@ -0,0 +1,40 @@ +package build + +import "sync" + +// Compile compiles all the functions. +func (build *Build) Compile() (map[string]*Function, error) { + functions, errors := Scan(build.Directory) + wg := sync.WaitGroup{} + allFunctions := map[string]*Function{} + + for functions != nil || errors != nil { + select { + case err, ok := <-errors: + if !ok { + errors = nil + continue + } + + return nil, err + + case function, ok := <-functions: + if !ok { + functions = nil + continue + } + + wg.Add(1) + + go func() { + defer wg.Done() + function.Compile() + }() + + allFunctions[function.Name] = function + } + } + + wg.Wait() + return allFunctions, nil +} diff --git a/src/build/Finalize.go b/src/build/Finalize.go new file mode 100644 index 0000000..0f7a247 --- /dev/null +++ b/src/build/Finalize.go @@ -0,0 +1,23 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/linux" + "git.akyoto.dev/cli/q/src/register" +) + +// Finalize generates the final machine code. +func (build *Build) Finalize(functions map[string]*Function) ([]byte, []byte) { + a := asm.New() + + for _, f := range functions { + a.Merge(&f.Assembler) + } + + a.MoveRegisterNumber(register.Syscall0, linux.Exit) + a.MoveRegisterNumber(register.Syscall1, 0) + a.Syscall() + + code, data := a.Finalize(build.Verbose) + return code, data +} diff --git a/src/build/Function.go b/src/build/Function.go index 8bb5108..902b2c5 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -1,7 +1,35 @@ package build -import "git.akyoto.dev/cli/q/src/token" +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/linux" + "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/cli/q/src/token" +) +// Function represents a function. type Function struct { - Tokens token.List + Name string + Head token.List + Body token.List + Assembler asm.Assembler +} + +// Compile turns a function into machine code. +func (f *Function) Compile() { + for i, t := range f.Body { + if t.Kind == token.Identifier && t.String() == "print" { + message := f.Body[i+2].Bytes + f.Assembler.MoveRegisterNumber(register.Syscall0, linux.Write) + f.Assembler.MoveRegisterNumber(register.Syscall1, 1) + f.Assembler.MoveRegisterData(register.Syscall2, message) + f.Assembler.MoveRegisterNumber(register.Syscall3, uint64(len(message))) + f.Assembler.Syscall() + } + } +} + +// String returns the function name. +func (f *Function) String() string { + return f.Name } diff --git a/src/build/Scan.go b/src/build/Scan.go index d1cba26..f7b5c51 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -60,16 +60,17 @@ func scanFile(path string, functions chan<- *Function) error { tokens := token.Tokenize(contents) var ( - groupLevel = 0 - blockLevel = 0 - functionStart = -1 + groupLevel = 0 + blockLevel = 0 + headerStart = -1 + bodyStart = -1 ) for i, t := range tokens { switch t.Kind { case token.Identifier: if blockLevel == 0 && groupLevel == 0 { - functionStart = i + headerStart = i } case token.GroupStart: @@ -81,12 +82,18 @@ func scanFile(path string, functions chan<- *Function) error { case token.BlockStart: blockLevel++ + if blockLevel == 1 { + bodyStart = i + } + case token.BlockEnd: blockLevel-- if blockLevel == 0 { function := &Function{ - Tokens: tokens[functionStart : i+1], + Name: tokens[headerStart].String(), + Head: tokens[headerStart:bodyStart], + Body: tokens[bodyStart : i+1], } functions <- function From 5c12992fca782e8e394ab10b16d0209747e8841b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 31 Oct 2023 14:50:53 +0100 Subject: [PATCH 0046/1012] Added assembler merging --- src/asm/Assembler.go | 5 ++++ src/build/Build.go | 68 ++++++++----------------------------------- src/build/Compile.go | 40 +++++++++++++++++++++++++ src/build/Finalize.go | 23 +++++++++++++++ src/build/Function.go | 32 ++++++++++++++++++-- src/build/Scan.go | 17 +++++++---- 6 files changed, 122 insertions(+), 63 deletions(-) create mode 100644 src/build/Compile.go create mode 100644 src/build/Finalize.go diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index 4ed7552..0fb1c49 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -62,6 +62,11 @@ func (a *Assembler) Finalize(verbose bool) ([]byte, []byte) { return code, data } +// Merge combines the contents of this assembler with another one. +func (a *Assembler) Merge(b *Assembler) { + a.Instructions = append(a.Instructions, b.Instructions...) +} + // MoveRegisterData moves a data section address into the given register. func (a *Assembler) MoveRegisterData(reg register.ID, data []byte) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/Build.go b/src/build/Build.go index 80a0e66..a77bcf2 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -5,12 +5,8 @@ import ( "os" "path/filepath" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/linux" - "git.akyoto.dev/cli/q/src/log" - "git.akyoto.dev/cli/q/src/register" ) // Build describes a compiler build. @@ -30,7 +26,17 @@ func New(directory string) *Build { // Run parses the input files and generates an executable file. func (build *Build) Run() error { - code, data, err := build.Compile() + stat, err := os.Stat(build.Directory) + + if err != nil { + return err + } + + if !stat.IsDir() { + return &errors.InvalidDirectory{Path: build.Directory} + } + + functions, err := build.Compile() if err != nil { return err @@ -40,60 +46,10 @@ func (build *Build) Run() error { return nil } + code, data := build.Finalize(functions) return writeToDisk(build.Executable(), code, data) } -// Compile compiles all the functions. -func (build *Build) Compile() ([]byte, []byte, error) { - stat, err := os.Stat(build.Directory) - - if err != nil { - return nil, nil, err - } - - if !stat.IsDir() { - return nil, nil, &errors.InvalidDirectory{Path: build.Directory} - } - - functions, errors := Scan(build.Directory) - - for functions != nil || errors != nil { - select { - case err, ok := <-errors: - if !ok { - errors = nil - continue - } - - log.Info.Println(err) - - case function, ok := <-functions: - if !ok { - functions = nil - continue - } - - log.Info.Println(function) - } - } - - a := asm.New() - - hello := []byte("Hello\n") - a.MoveRegisterNumber(register.Syscall0, linux.Write) - a.MoveRegisterNumber(register.Syscall1, 1) - a.MoveRegisterData(register.Syscall2, hello) - a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) - a.Syscall() - - a.MoveRegisterNumber(register.Syscall0, linux.Exit) - a.MoveRegisterNumber(register.Syscall1, 0) - a.Syscall() - - code, data := a.Finalize(build.Verbose) - return code, data, nil -} - // Executable returns the path to the executable. func (build *Build) Executable() string { return filepath.Join(build.Directory, filepath.Base(build.Directory)) diff --git a/src/build/Compile.go b/src/build/Compile.go new file mode 100644 index 0000000..0fd5f9a --- /dev/null +++ b/src/build/Compile.go @@ -0,0 +1,40 @@ +package build + +import "sync" + +// Compile compiles all the functions. +func (build *Build) Compile() (map[string]*Function, error) { + functions, errors := Scan(build.Directory) + wg := sync.WaitGroup{} + allFunctions := map[string]*Function{} + + for functions != nil || errors != nil { + select { + case err, ok := <-errors: + if !ok { + errors = nil + continue + } + + return nil, err + + case function, ok := <-functions: + if !ok { + functions = nil + continue + } + + wg.Add(1) + + go func() { + defer wg.Done() + function.Compile() + }() + + allFunctions[function.Name] = function + } + } + + wg.Wait() + return allFunctions, nil +} diff --git a/src/build/Finalize.go b/src/build/Finalize.go new file mode 100644 index 0000000..0f7a247 --- /dev/null +++ b/src/build/Finalize.go @@ -0,0 +1,23 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/linux" + "git.akyoto.dev/cli/q/src/register" +) + +// Finalize generates the final machine code. +func (build *Build) Finalize(functions map[string]*Function) ([]byte, []byte) { + a := asm.New() + + for _, f := range functions { + a.Merge(&f.Assembler) + } + + a.MoveRegisterNumber(register.Syscall0, linux.Exit) + a.MoveRegisterNumber(register.Syscall1, 0) + a.Syscall() + + code, data := a.Finalize(build.Verbose) + return code, data +} diff --git a/src/build/Function.go b/src/build/Function.go index 8bb5108..902b2c5 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -1,7 +1,35 @@ package build -import "git.akyoto.dev/cli/q/src/token" +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/linux" + "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/cli/q/src/token" +) +// Function represents a function. type Function struct { - Tokens token.List + Name string + Head token.List + Body token.List + Assembler asm.Assembler +} + +// Compile turns a function into machine code. +func (f *Function) Compile() { + for i, t := range f.Body { + if t.Kind == token.Identifier && t.String() == "print" { + message := f.Body[i+2].Bytes + f.Assembler.MoveRegisterNumber(register.Syscall0, linux.Write) + f.Assembler.MoveRegisterNumber(register.Syscall1, 1) + f.Assembler.MoveRegisterData(register.Syscall2, message) + f.Assembler.MoveRegisterNumber(register.Syscall3, uint64(len(message))) + f.Assembler.Syscall() + } + } +} + +// String returns the function name. +func (f *Function) String() string { + return f.Name } diff --git a/src/build/Scan.go b/src/build/Scan.go index d1cba26..f7b5c51 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -60,16 +60,17 @@ func scanFile(path string, functions chan<- *Function) error { tokens := token.Tokenize(contents) var ( - groupLevel = 0 - blockLevel = 0 - functionStart = -1 + groupLevel = 0 + blockLevel = 0 + headerStart = -1 + bodyStart = -1 ) for i, t := range tokens { switch t.Kind { case token.Identifier: if blockLevel == 0 && groupLevel == 0 { - functionStart = i + headerStart = i } case token.GroupStart: @@ -81,12 +82,18 @@ func scanFile(path string, functions chan<- *Function) error { case token.BlockStart: blockLevel++ + if blockLevel == 1 { + bodyStart = i + } + case token.BlockEnd: blockLevel-- if blockLevel == 0 { function := &Function{ - Tokens: tokens[functionStart : i+1], + Name: tokens[headerStart].String(), + Head: tokens[headerStart:bodyStart], + Body: tokens[bodyStart : i+1], } functions <- function From 8de28852cdf186393451e27877a77603b88d4d26 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 31 Oct 2023 21:13:14 +0100 Subject: [PATCH 0047/1012] Improved tokenizer --- src/asm/Assembler.go | 4 +- src/asm/Assembler_test.go | 2 +- src/build/Build.go | 6 +-- src/cli/Build.go | 3 +- src/{build => compiler}/Compile.go | 6 +-- src/{build => compiler}/Finalize.go | 6 +-- src/{build => compiler}/Function.go | 2 +- src/{build => compiler}/Scan.go | 8 ++-- src/config/config.go | 4 ++ src/token/Tokenize.go | 69 ++++++++++++++--------------- 10 files changed, 57 insertions(+), 53 deletions(-) rename src/{build => compiler}/Compile.go (81%) rename src/{build => compiler}/Finalize.go (73%) rename src/{build => compiler}/Function.go (98%) rename src/{build => compiler}/Scan.go (88%) diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index 0fb1c49..470d310 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -22,7 +22,7 @@ func New() *Assembler { } // Finalize generates the final machine code. -func (a *Assembler) Finalize(verbose bool) ([]byte, []byte) { +func (a *Assembler) Finalize() ([]byte, []byte) { code := make([]byte, 0, len(a.Instructions)*8) data := make(Data, 0, 16) pointers := []Pointer{} @@ -45,7 +45,7 @@ func (a *Assembler) Finalize(verbose bool) ([]byte, []byte) { } } - if verbose { + if config.Verbose { for _, x := range a.Instructions { log.Info.Println(x.String()) } diff --git a/src/asm/Assembler_test.go b/src/asm/Assembler_test.go index f702742..581da9b 100644 --- a/src/asm/Assembler_test.go +++ b/src/asm/Assembler_test.go @@ -23,7 +23,7 @@ func TestHello(t *testing.T) { a.MoveRegisterNumber(register.Syscall1, 0) a.Syscall() - code, data := a.Finalize(false) + code, data := a.Finalize() assert.DeepEqual(t, code, []byte{ 0xb8, 0x01, 0x00, 0x00, 0x00, diff --git a/src/build/Build.go b/src/build/Build.go index a77bcf2..eea57f0 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" + "git.akyoto.dev/cli/q/src/compiler" "git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/errors" ) @@ -12,7 +13,6 @@ import ( // Build describes a compiler build. type Build struct { Directory string - Verbose bool WriteExecutable bool } @@ -36,7 +36,7 @@ func (build *Build) Run() error { return &errors.InvalidDirectory{Path: build.Directory} } - functions, err := build.Compile() + functions, err := compiler.Compile(build.Directory) if err != nil { return err @@ -46,7 +46,7 @@ func (build *Build) Run() error { return nil } - code, data := build.Finalize(functions) + code, data := compiler.Finalize(functions) return writeToDisk(build.Executable(), code, data) } diff --git a/src/cli/Build.go b/src/cli/Build.go index ec1bf6a..2974257 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -4,6 +4,7 @@ import ( "strings" "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/log" ) @@ -17,7 +18,7 @@ func Build(args []string) int { b.WriteExecutable = false case "--verbose", "-v": - b.Verbose = true + config.Verbose = true default: if strings.HasPrefix(args[i], "-") { diff --git a/src/build/Compile.go b/src/compiler/Compile.go similarity index 81% rename from src/build/Compile.go rename to src/compiler/Compile.go index 0fd5f9a..5311d01 100644 --- a/src/build/Compile.go +++ b/src/compiler/Compile.go @@ -1,10 +1,10 @@ -package build +package compiler import "sync" // Compile compiles all the functions. -func (build *Build) Compile() (map[string]*Function, error) { - functions, errors := Scan(build.Directory) +func Compile(directory string) (map[string]*Function, error) { + functions, errors := Scan(directory) wg := sync.WaitGroup{} allFunctions := map[string]*Function{} diff --git a/src/build/Finalize.go b/src/compiler/Finalize.go similarity index 73% rename from src/build/Finalize.go rename to src/compiler/Finalize.go index 0f7a247..ccae1b3 100644 --- a/src/build/Finalize.go +++ b/src/compiler/Finalize.go @@ -1,4 +1,4 @@ -package build +package compiler import ( "git.akyoto.dev/cli/q/src/asm" @@ -7,7 +7,7 @@ import ( ) // Finalize generates the final machine code. -func (build *Build) Finalize(functions map[string]*Function) ([]byte, []byte) { +func Finalize(functions map[string]*Function) ([]byte, []byte) { a := asm.New() for _, f := range functions { @@ -18,6 +18,6 @@ func (build *Build) Finalize(functions map[string]*Function) ([]byte, []byte) { a.MoveRegisterNumber(register.Syscall1, 0) a.Syscall() - code, data := a.Finalize(build.Verbose) + code, data := a.Finalize() return code, data } diff --git a/src/build/Function.go b/src/compiler/Function.go similarity index 98% rename from src/build/Function.go rename to src/compiler/Function.go index 902b2c5..5c4742f 100644 --- a/src/build/Function.go +++ b/src/compiler/Function.go @@ -1,4 +1,4 @@ -package build +package compiler import ( "git.akyoto.dev/cli/q/src/asm" diff --git a/src/build/Scan.go b/src/compiler/Scan.go similarity index 88% rename from src/build/Scan.go rename to src/compiler/Scan.go index f7b5c51..4e55afd 100644 --- a/src/build/Scan.go +++ b/src/compiler/Scan.go @@ -1,4 +1,4 @@ -package build +package compiler import ( "os" @@ -16,7 +16,7 @@ func Scan(path string) (<-chan *Function, <-chan error) { errors := make(chan error) go func() { - scanDirectory(path, functions, errors) + scan(path, functions, errors) close(functions) close(errors) }() @@ -24,8 +24,8 @@ func Scan(path string) (<-chan *Function, <-chan error) { return functions, errors } -// scanDirectory scans the directory without channel allocations. -func scanDirectory(path string, functions chan<- *Function, errors chan<- error) { +// scan scans the directory without channel allocations. +func scan(path string, functions chan<- *Function, errors chan<- error) { wg := sync.WaitGroup{} directory.Walk(path, func(name string) { diff --git a/src/config/config.go b/src/config/config.go index 42bc97f..baaf72b 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -6,3 +6,7 @@ const ( CodeOffset = 0x80 Align = 0x10 ) + +var ( + Verbose = false +) diff --git a/src/token/Tokenize.go b/src/token/Tokenize.go index 9d359b8..5e336d8 100644 --- a/src/token/Tokenize.go +++ b/src/token/Tokenize.go @@ -19,38 +19,13 @@ var ( func Tokenize(buffer []byte) List { var ( i int - c byte tokens = make(List, 0, len(buffer)/2) ) for i < len(buffer) { - c = buffer[i] - - switch { - // Identifiers - case isIdentifierStart(c): - position := i - i++ - - for i < len(buffer) && isIdentifier(buffer[i]) { - i++ - } - - token := Token{ - Identifier, - position, - buffer[position:i], - } - - if keywords.All[string(token.Bytes)] { - token.Kind = Keyword - } - - tokens = append(tokens, token) - i-- - + switch buffer[i] { // Texts - case c == '"': + case '"': i++ position := i @@ -65,36 +40,60 @@ func Tokenize(buffer []byte) List { }) // Parentheses start - case c == '(': + case '(': tokens = append(tokens, Token{GroupStart, i, groupStartBytes}) // Parentheses end - case c == ')': + case ')': tokens = append(tokens, Token{GroupEnd, i, groupEndBytes}) // Block start - case c == '{': + case '{': tokens = append(tokens, Token{BlockStart, i, blockStartBytes}) // Block end - case c == '}': + case '}': tokens = append(tokens, Token{BlockEnd, i, blockEndBytes}) // Array start - case c == '[': + case '[': tokens = append(tokens, Token{ArrayStart, i, arrayStartBytes}) // Array end - case c == ']': + case ']': tokens = append(tokens, Token{ArrayEnd, i, arrayEndBytes}) // Separator - case c == ',': + case ',': tokens = append(tokens, Token{Separator, i, separatorBytes}) // New line - case c == '\n': + case '\n': tokens = append(tokens, Token{NewLine, i, newLineBytes}) + + default: + // Identifiers + if isIdentifierStart(buffer[i]) { + position := i + i++ + + for i < len(buffer) && isIdentifier(buffer[i]) { + i++ + } + + token := Token{ + Identifier, + position, + buffer[position:i], + } + + if keywords.All[string(token.Bytes)] { + token.Kind = Keyword + } + + tokens = append(tokens, token) + i-- + } } i++ From c4b28fb66e48eb8ed10b3edaa817c04c913ca5ee Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 31 Oct 2023 21:13:14 +0100 Subject: [PATCH 0048/1012] Improved tokenizer --- src/asm/Assembler.go | 4 +- src/asm/Assembler_test.go | 2 +- src/build/Build.go | 6 +-- src/cli/Build.go | 3 +- src/{build => compiler}/Compile.go | 6 +-- src/{build => compiler}/Finalize.go | 6 +-- src/{build => compiler}/Function.go | 2 +- src/{build => compiler}/Scan.go | 8 ++-- src/config/config.go | 4 ++ src/token/Tokenize.go | 69 ++++++++++++++--------------- 10 files changed, 57 insertions(+), 53 deletions(-) rename src/{build => compiler}/Compile.go (81%) rename src/{build => compiler}/Finalize.go (73%) rename src/{build => compiler}/Function.go (98%) rename src/{build => compiler}/Scan.go (88%) diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index 0fb1c49..470d310 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -22,7 +22,7 @@ func New() *Assembler { } // Finalize generates the final machine code. -func (a *Assembler) Finalize(verbose bool) ([]byte, []byte) { +func (a *Assembler) Finalize() ([]byte, []byte) { code := make([]byte, 0, len(a.Instructions)*8) data := make(Data, 0, 16) pointers := []Pointer{} @@ -45,7 +45,7 @@ func (a *Assembler) Finalize(verbose bool) ([]byte, []byte) { } } - if verbose { + if config.Verbose { for _, x := range a.Instructions { log.Info.Println(x.String()) } diff --git a/src/asm/Assembler_test.go b/src/asm/Assembler_test.go index f702742..581da9b 100644 --- a/src/asm/Assembler_test.go +++ b/src/asm/Assembler_test.go @@ -23,7 +23,7 @@ func TestHello(t *testing.T) { a.MoveRegisterNumber(register.Syscall1, 0) a.Syscall() - code, data := a.Finalize(false) + code, data := a.Finalize() assert.DeepEqual(t, code, []byte{ 0xb8, 0x01, 0x00, 0x00, 0x00, diff --git a/src/build/Build.go b/src/build/Build.go index a77bcf2..eea57f0 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" + "git.akyoto.dev/cli/q/src/compiler" "git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/errors" ) @@ -12,7 +13,6 @@ import ( // Build describes a compiler build. type Build struct { Directory string - Verbose bool WriteExecutable bool } @@ -36,7 +36,7 @@ func (build *Build) Run() error { return &errors.InvalidDirectory{Path: build.Directory} } - functions, err := build.Compile() + functions, err := compiler.Compile(build.Directory) if err != nil { return err @@ -46,7 +46,7 @@ func (build *Build) Run() error { return nil } - code, data := build.Finalize(functions) + code, data := compiler.Finalize(functions) return writeToDisk(build.Executable(), code, data) } diff --git a/src/cli/Build.go b/src/cli/Build.go index ec1bf6a..2974257 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -4,6 +4,7 @@ import ( "strings" "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/log" ) @@ -17,7 +18,7 @@ func Build(args []string) int { b.WriteExecutable = false case "--verbose", "-v": - b.Verbose = true + config.Verbose = true default: if strings.HasPrefix(args[i], "-") { diff --git a/src/build/Compile.go b/src/compiler/Compile.go similarity index 81% rename from src/build/Compile.go rename to src/compiler/Compile.go index 0fd5f9a..5311d01 100644 --- a/src/build/Compile.go +++ b/src/compiler/Compile.go @@ -1,10 +1,10 @@ -package build +package compiler import "sync" // Compile compiles all the functions. -func (build *Build) Compile() (map[string]*Function, error) { - functions, errors := Scan(build.Directory) +func Compile(directory string) (map[string]*Function, error) { + functions, errors := Scan(directory) wg := sync.WaitGroup{} allFunctions := map[string]*Function{} diff --git a/src/build/Finalize.go b/src/compiler/Finalize.go similarity index 73% rename from src/build/Finalize.go rename to src/compiler/Finalize.go index 0f7a247..ccae1b3 100644 --- a/src/build/Finalize.go +++ b/src/compiler/Finalize.go @@ -1,4 +1,4 @@ -package build +package compiler import ( "git.akyoto.dev/cli/q/src/asm" @@ -7,7 +7,7 @@ import ( ) // Finalize generates the final machine code. -func (build *Build) Finalize(functions map[string]*Function) ([]byte, []byte) { +func Finalize(functions map[string]*Function) ([]byte, []byte) { a := asm.New() for _, f := range functions { @@ -18,6 +18,6 @@ func (build *Build) Finalize(functions map[string]*Function) ([]byte, []byte) { a.MoveRegisterNumber(register.Syscall1, 0) a.Syscall() - code, data := a.Finalize(build.Verbose) + code, data := a.Finalize() return code, data } diff --git a/src/build/Function.go b/src/compiler/Function.go similarity index 98% rename from src/build/Function.go rename to src/compiler/Function.go index 902b2c5..5c4742f 100644 --- a/src/build/Function.go +++ b/src/compiler/Function.go @@ -1,4 +1,4 @@ -package build +package compiler import ( "git.akyoto.dev/cli/q/src/asm" diff --git a/src/build/Scan.go b/src/compiler/Scan.go similarity index 88% rename from src/build/Scan.go rename to src/compiler/Scan.go index f7b5c51..4e55afd 100644 --- a/src/build/Scan.go +++ b/src/compiler/Scan.go @@ -1,4 +1,4 @@ -package build +package compiler import ( "os" @@ -16,7 +16,7 @@ func Scan(path string) (<-chan *Function, <-chan error) { errors := make(chan error) go func() { - scanDirectory(path, functions, errors) + scan(path, functions, errors) close(functions) close(errors) }() @@ -24,8 +24,8 @@ func Scan(path string) (<-chan *Function, <-chan error) { return functions, errors } -// scanDirectory scans the directory without channel allocations. -func scanDirectory(path string, functions chan<- *Function, errors chan<- error) { +// scan scans the directory without channel allocations. +func scan(path string, functions chan<- *Function, errors chan<- error) { wg := sync.WaitGroup{} directory.Walk(path, func(name string) { diff --git a/src/config/config.go b/src/config/config.go index 42bc97f..baaf72b 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -6,3 +6,7 @@ const ( CodeOffset = 0x80 Align = 0x10 ) + +var ( + Verbose = false +) diff --git a/src/token/Tokenize.go b/src/token/Tokenize.go index 9d359b8..5e336d8 100644 --- a/src/token/Tokenize.go +++ b/src/token/Tokenize.go @@ -19,38 +19,13 @@ var ( func Tokenize(buffer []byte) List { var ( i int - c byte tokens = make(List, 0, len(buffer)/2) ) for i < len(buffer) { - c = buffer[i] - - switch { - // Identifiers - case isIdentifierStart(c): - position := i - i++ - - for i < len(buffer) && isIdentifier(buffer[i]) { - i++ - } - - token := Token{ - Identifier, - position, - buffer[position:i], - } - - if keywords.All[string(token.Bytes)] { - token.Kind = Keyword - } - - tokens = append(tokens, token) - i-- - + switch buffer[i] { // Texts - case c == '"': + case '"': i++ position := i @@ -65,36 +40,60 @@ func Tokenize(buffer []byte) List { }) // Parentheses start - case c == '(': + case '(': tokens = append(tokens, Token{GroupStart, i, groupStartBytes}) // Parentheses end - case c == ')': + case ')': tokens = append(tokens, Token{GroupEnd, i, groupEndBytes}) // Block start - case c == '{': + case '{': tokens = append(tokens, Token{BlockStart, i, blockStartBytes}) // Block end - case c == '}': + case '}': tokens = append(tokens, Token{BlockEnd, i, blockEndBytes}) // Array start - case c == '[': + case '[': tokens = append(tokens, Token{ArrayStart, i, arrayStartBytes}) // Array end - case c == ']': + case ']': tokens = append(tokens, Token{ArrayEnd, i, arrayEndBytes}) // Separator - case c == ',': + case ',': tokens = append(tokens, Token{Separator, i, separatorBytes}) // New line - case c == '\n': + case '\n': tokens = append(tokens, Token{NewLine, i, newLineBytes}) + + default: + // Identifiers + if isIdentifierStart(buffer[i]) { + position := i + i++ + + for i < len(buffer) && isIdentifier(buffer[i]) { + i++ + } + + token := Token{ + Identifier, + position, + buffer[position:i], + } + + if keywords.All[string(token.Bytes)] { + token.Kind = Keyword + } + + tokens = append(tokens, token) + i-- + } } i++ From b72e3943a44c0409f29c9912b3f15f5569d0ba03 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 3 Nov 2023 10:42:10 +0100 Subject: [PATCH 0049/1012] Improved error handling --- src/build/Build.go | 11 ----------- src/compiler/Scan.go | 6 +++++- src/directory/Walk.go | 8 +++++--- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/build/Build.go b/src/build/Build.go index eea57f0..f73045a 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -7,7 +7,6 @@ import ( "git.akyoto.dev/cli/q/src/compiler" "git.akyoto.dev/cli/q/src/elf" - "git.akyoto.dev/cli/q/src/errors" ) // Build describes a compiler build. @@ -26,16 +25,6 @@ func New(directory string) *Build { // Run parses the input files and generates an executable file. func (build *Build) Run() error { - stat, err := os.Stat(build.Directory) - - if err != nil { - return err - } - - if !stat.IsDir() { - return &errors.InvalidDirectory{Path: build.Directory} - } - functions, err := compiler.Compile(build.Directory) if err != nil { diff --git a/src/compiler/Scan.go b/src/compiler/Scan.go index 4e55afd..1c52ba4 100644 --- a/src/compiler/Scan.go +++ b/src/compiler/Scan.go @@ -28,7 +28,7 @@ func Scan(path string) (<-chan *Function, <-chan error) { func scan(path string, functions chan<- *Function, errors chan<- error) { wg := sync.WaitGroup{} - directory.Walk(path, func(name string) { + err := directory.Walk(path, func(name string) { if !strings.HasSuffix(name, ".q") { return } @@ -46,6 +46,10 @@ func scan(path string, functions chan<- *Function, errors chan<- error) { }() }) + if err != nil { + errors <- err + } + wg.Wait() } diff --git a/src/directory/Walk.go b/src/directory/Walk.go index f871c64..e2cc495 100644 --- a/src/directory/Walk.go +++ b/src/directory/Walk.go @@ -9,11 +9,11 @@ const blockSize = 4096 // Walk calls your callback function for every file name inside the directory. // It doesn't distinguish between files and directories. -func Walk(directory string, callBack func(string)) { +func Walk(directory string, callBack func(string)) error { fd, err := syscall.Open(directory, 0, 0) if err != nil { - panic(err) + return err } defer syscall.Close(fd) @@ -23,7 +23,7 @@ func Walk(directory string, callBack func(string)) { n, err := syscall.ReadDirent(fd, buffer) if err != nil { - panic(err) + return err } if n <= 0 { @@ -58,4 +58,6 @@ func Walk(directory string, callBack func(string)) { } } } + + return nil } From a7afd680da55b178d805fb856f60f8a82114fb4c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 3 Nov 2023 10:42:10 +0100 Subject: [PATCH 0050/1012] Improved error handling --- src/build/Build.go | 11 ----------- src/compiler/Scan.go | 6 +++++- src/directory/Walk.go | 8 +++++--- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/build/Build.go b/src/build/Build.go index eea57f0..f73045a 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -7,7 +7,6 @@ import ( "git.akyoto.dev/cli/q/src/compiler" "git.akyoto.dev/cli/q/src/elf" - "git.akyoto.dev/cli/q/src/errors" ) // Build describes a compiler build. @@ -26,16 +25,6 @@ func New(directory string) *Build { // Run parses the input files and generates an executable file. func (build *Build) Run() error { - stat, err := os.Stat(build.Directory) - - if err != nil { - return err - } - - if !stat.IsDir() { - return &errors.InvalidDirectory{Path: build.Directory} - } - functions, err := compiler.Compile(build.Directory) if err != nil { diff --git a/src/compiler/Scan.go b/src/compiler/Scan.go index 4e55afd..1c52ba4 100644 --- a/src/compiler/Scan.go +++ b/src/compiler/Scan.go @@ -28,7 +28,7 @@ func Scan(path string) (<-chan *Function, <-chan error) { func scan(path string, functions chan<- *Function, errors chan<- error) { wg := sync.WaitGroup{} - directory.Walk(path, func(name string) { + err := directory.Walk(path, func(name string) { if !strings.HasSuffix(name, ".q") { return } @@ -46,6 +46,10 @@ func scan(path string, functions chan<- *Function, errors chan<- error) { }() }) + if err != nil { + errors <- err + } + wg.Wait() } diff --git a/src/directory/Walk.go b/src/directory/Walk.go index f871c64..e2cc495 100644 --- a/src/directory/Walk.go +++ b/src/directory/Walk.go @@ -9,11 +9,11 @@ const blockSize = 4096 // Walk calls your callback function for every file name inside the directory. // It doesn't distinguish between files and directories. -func Walk(directory string, callBack func(string)) { +func Walk(directory string, callBack func(string)) error { fd, err := syscall.Open(directory, 0, 0) if err != nil { - panic(err) + return err } defer syscall.Close(fd) @@ -23,7 +23,7 @@ func Walk(directory string, callBack func(string)) { n, err := syscall.ReadDirent(fd, buffer) if err != nil { - panic(err) + return err } if n <= 0 { @@ -58,4 +58,6 @@ func Walk(directory string, callBack func(string)) { } } } + + return nil } From ec20fd3d3a61493f467e7b78254b493047380013 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 2 Jun 2024 15:20:05 +0200 Subject: [PATCH 0051/1012] Improved documentation --- README.md | 12 +++++++++--- go.mod | 2 +- src/compiler/Scan.go | 2 +- src/errors/InvalidDirectory.go | 1 + 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 72a1f8b..2b6cfb4 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,15 @@ q build examples/hello --dry The `Build` type defines all the information needed to start building an executable file. The name of the executable will be equal to the name of the build directory. -`Run` starts the build which will scan all files in the build directory. -The functions found in the scan will be translated to generic assembler instructions. -All the functions that are required to run the program will be added to final assembler. +`Run` starts the build which will scan all `.q` source files in the build directory. +Every source file is scanned in its own goroutine for performance reasons. +Parallelization here is possible because the order of code in a directory is not significant. + +The main function is meanwhile waiting for new function objects to arrive from the scanners. +Once a function has arrived, it will create another goroutine for the function compilation. +The function will then be translated to generic assembler instructions. + +All the functions that are required to run the program will be added to the final assembler. The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. The `Run` method is currently not fully implemented. diff --git a/go.mod b/go.mod index e78e48b..b286207 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module git.akyoto.dev/cli/q -go 1.21 +go 1.22 require git.akyoto.dev/go/assert v0.1.3 diff --git a/src/compiler/Scan.go b/src/compiler/Scan.go index 1c52ba4..deef107 100644 --- a/src/compiler/Scan.go +++ b/src/compiler/Scan.go @@ -12,7 +12,7 @@ import ( // Scan scans the directory. func Scan(path string) (<-chan *Function, <-chan error) { - functions := make(chan *Function, 16) + functions := make(chan *Function) errors := make(chan error) go func() { diff --git a/src/errors/InvalidDirectory.go b/src/errors/InvalidDirectory.go index 161cdf0..90beadd 100644 --- a/src/errors/InvalidDirectory.go +++ b/src/errors/InvalidDirectory.go @@ -7,6 +7,7 @@ type InvalidDirectory struct { Path string } +// Error implements the text representation. func (err *InvalidDirectory) Error() string { if err.Path == "" { return "Invalid directory" From 8974b8b0aac9d5c1a4b7f914c22af391c7e38ab8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 2 Jun 2024 15:20:05 +0200 Subject: [PATCH 0052/1012] Improved documentation --- README.md | 12 +++++++++--- go.mod | 2 +- src/compiler/Scan.go | 2 +- src/errors/InvalidDirectory.go | 1 + 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 72a1f8b..2b6cfb4 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,15 @@ q build examples/hello --dry The `Build` type defines all the information needed to start building an executable file. The name of the executable will be equal to the name of the build directory. -`Run` starts the build which will scan all files in the build directory. -The functions found in the scan will be translated to generic assembler instructions. -All the functions that are required to run the program will be added to final assembler. +`Run` starts the build which will scan all `.q` source files in the build directory. +Every source file is scanned in its own goroutine for performance reasons. +Parallelization here is possible because the order of code in a directory is not significant. + +The main function is meanwhile waiting for new function objects to arrive from the scanners. +Once a function has arrived, it will create another goroutine for the function compilation. +The function will then be translated to generic assembler instructions. + +All the functions that are required to run the program will be added to the final assembler. The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. The `Run` method is currently not fully implemented. diff --git a/go.mod b/go.mod index e78e48b..b286207 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module git.akyoto.dev/cli/q -go 1.21 +go 1.22 require git.akyoto.dev/go/assert v0.1.3 diff --git a/src/compiler/Scan.go b/src/compiler/Scan.go index 1c52ba4..deef107 100644 --- a/src/compiler/Scan.go +++ b/src/compiler/Scan.go @@ -12,7 +12,7 @@ import ( // Scan scans the directory. func Scan(path string) (<-chan *Function, <-chan error) { - functions := make(chan *Function, 16) + functions := make(chan *Function) errors := make(chan error) go func() { diff --git a/src/errors/InvalidDirectory.go b/src/errors/InvalidDirectory.go index 161cdf0..90beadd 100644 --- a/src/errors/InvalidDirectory.go +++ b/src/errors/InvalidDirectory.go @@ -7,6 +7,7 @@ type InvalidDirectory struct { Path string } +// Error implements the text representation. func (err *InvalidDirectory) Error() string { if err.Path == "" { return "Invalid directory" From b6400cd77efb269e4ffe1ec94f07ca27f258a0bc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 4 Jun 2024 15:51:31 +0200 Subject: [PATCH 0053/1012] Added more tests --- examples/fibonacci/fibonacci.q | 19 ------------------- src/asm/Data.go | 3 ++- main_test.go => src/cli/Main_test.go | 21 ++++++++------------- src/directory/Walk_test.go | 19 +++++++++++++++++++ src/elf/ELF_test.go | 13 +++++++++++++ 5 files changed, 42 insertions(+), 33 deletions(-) delete mode 100644 examples/fibonacci/fibonacci.q rename main_test.go => src/cli/Main_test.go (62%) create mode 100644 src/directory/Walk_test.go create mode 100644 src/elf/ELF_test.go diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q deleted file mode 100644 index c188e38..0000000 --- a/examples/fibonacci/fibonacci.q +++ /dev/null @@ -1,19 +0,0 @@ -import sys - -main() { - let f = fibonacci(11) - sys.exit(f) -} - -fibonacci(n Int) -> Int { - mut b = 0 - mut c = 1 - - for 0..n { - let a = b - b = c - c = a + b - } - - return b -} diff --git a/src/asm/Data.go b/src/asm/Data.go index 9268c99..418a898 100644 --- a/src/asm/Data.go +++ b/src/asm/Data.go @@ -5,7 +5,8 @@ import "bytes" // Data represents the static read-only data. type Data []byte -// Add adds the given bytes to the data block and returns the address relative to the start of the data section. +// Add adds the given bytes to the data block if this sequence of bytes doesn't exist yet. +// It returns the address relative to the start of the data section. func (data *Data) Add(block []byte) Address { position := bytes.Index(*data, block) diff --git a/main_test.go b/src/cli/Main_test.go similarity index 62% rename from main_test.go rename to src/cli/Main_test.go index ab4ac62..95fcf55 100644 --- a/main_test.go +++ b/src/cli/Main_test.go @@ -1,6 +1,7 @@ -package main_test +package cli_test import ( + "fmt" "io" "os" "testing" @@ -27,23 +28,17 @@ func TestCLI(t *testing.T) { {[]string{"invalid"}, 2}, {[]string{"system"}, 0}, {[]string{"build", "non-existing-directory"}, 1}, - {[]string{"build", "examples/hello/hello.q"}, 1}, - {[]string{"build", "examples/hello", "--invalid"}, 2}, - {[]string{"build", "examples/hello", "--dry"}, 0}, - {[]string{"build", "examples/hello", "--dry", "--verbose"}, 0}, + {[]string{"build", "../../examples/hello/hello.q"}, 1}, + {[]string{"build", "../../examples/hello", "--invalid"}, 2}, + {[]string{"build", "../../examples/hello", "--dry"}, 0}, + {[]string{"build", "../../examples/hello", "--dry", "--verbose"}, 0}, } for _, test := range tests { t.Log(test.arguments) + directory, _ := os.Getwd() + fmt.Println(directory) exitCode := cli.Main(test.arguments) assert.Equal(t, exitCode, test.expectedExitCode) } } - -func BenchmarkBuild(b *testing.B) { - args := []string{"build", "examples/hello", "--dry"} - - for i := 0; i < b.N; i++ { - cli.Main(args) - } -} diff --git a/src/directory/Walk_test.go b/src/directory/Walk_test.go new file mode 100644 index 0000000..a4fcda8 --- /dev/null +++ b/src/directory/Walk_test.go @@ -0,0 +1,19 @@ +package directory_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/directory" + "git.akyoto.dev/go/assert" +) + +func TestWalk(t *testing.T) { + var files []string + + directory.Walk(".", func(file string) { + files = append(files, file) + }) + + assert.Contains(t, files, "Walk.go") + assert.Contains(t, files, "Walk_test.go") +} diff --git a/src/elf/ELF_test.go b/src/elf/ELF_test.go new file mode 100644 index 0000000..45a9aa2 --- /dev/null +++ b/src/elf/ELF_test.go @@ -0,0 +1,13 @@ +package elf_test + +import ( + "io" + "testing" + + "git.akyoto.dev/cli/q/src/elf" +) + +func TestELF(t *testing.T) { + exe := elf.New(nil, nil) + exe.Write(io.Discard) +} From 453b4d89565a00e6e73b17d22cababa14662518f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 4 Jun 2024 15:51:31 +0200 Subject: [PATCH 0054/1012] Added more tests --- examples/fibonacci/fibonacci.q | 19 ------------------- src/asm/Data.go | 3 ++- main_test.go => src/cli/Main_test.go | 21 ++++++++------------- src/directory/Walk_test.go | 19 +++++++++++++++++++ src/elf/ELF_test.go | 13 +++++++++++++ 5 files changed, 42 insertions(+), 33 deletions(-) delete mode 100644 examples/fibonacci/fibonacci.q rename main_test.go => src/cli/Main_test.go (62%) create mode 100644 src/directory/Walk_test.go create mode 100644 src/elf/ELF_test.go diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q deleted file mode 100644 index c188e38..0000000 --- a/examples/fibonacci/fibonacci.q +++ /dev/null @@ -1,19 +0,0 @@ -import sys - -main() { - let f = fibonacci(11) - sys.exit(f) -} - -fibonacci(n Int) -> Int { - mut b = 0 - mut c = 1 - - for 0..n { - let a = b - b = c - c = a + b - } - - return b -} diff --git a/src/asm/Data.go b/src/asm/Data.go index 9268c99..418a898 100644 --- a/src/asm/Data.go +++ b/src/asm/Data.go @@ -5,7 +5,8 @@ import "bytes" // Data represents the static read-only data. type Data []byte -// Add adds the given bytes to the data block and returns the address relative to the start of the data section. +// Add adds the given bytes to the data block if this sequence of bytes doesn't exist yet. +// It returns the address relative to the start of the data section. func (data *Data) Add(block []byte) Address { position := bytes.Index(*data, block) diff --git a/main_test.go b/src/cli/Main_test.go similarity index 62% rename from main_test.go rename to src/cli/Main_test.go index ab4ac62..95fcf55 100644 --- a/main_test.go +++ b/src/cli/Main_test.go @@ -1,6 +1,7 @@ -package main_test +package cli_test import ( + "fmt" "io" "os" "testing" @@ -27,23 +28,17 @@ func TestCLI(t *testing.T) { {[]string{"invalid"}, 2}, {[]string{"system"}, 0}, {[]string{"build", "non-existing-directory"}, 1}, - {[]string{"build", "examples/hello/hello.q"}, 1}, - {[]string{"build", "examples/hello", "--invalid"}, 2}, - {[]string{"build", "examples/hello", "--dry"}, 0}, - {[]string{"build", "examples/hello", "--dry", "--verbose"}, 0}, + {[]string{"build", "../../examples/hello/hello.q"}, 1}, + {[]string{"build", "../../examples/hello", "--invalid"}, 2}, + {[]string{"build", "../../examples/hello", "--dry"}, 0}, + {[]string{"build", "../../examples/hello", "--dry", "--verbose"}, 0}, } for _, test := range tests { t.Log(test.arguments) + directory, _ := os.Getwd() + fmt.Println(directory) exitCode := cli.Main(test.arguments) assert.Equal(t, exitCode, test.expectedExitCode) } } - -func BenchmarkBuild(b *testing.B) { - args := []string{"build", "examples/hello", "--dry"} - - for i := 0; i < b.N; i++ { - cli.Main(args) - } -} diff --git a/src/directory/Walk_test.go b/src/directory/Walk_test.go new file mode 100644 index 0000000..a4fcda8 --- /dev/null +++ b/src/directory/Walk_test.go @@ -0,0 +1,19 @@ +package directory_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/directory" + "git.akyoto.dev/go/assert" +) + +func TestWalk(t *testing.T) { + var files []string + + directory.Walk(".", func(file string) { + files = append(files, file) + }) + + assert.Contains(t, files, "Walk.go") + assert.Contains(t, files, "Walk_test.go") +} diff --git a/src/elf/ELF_test.go b/src/elf/ELF_test.go new file mode 100644 index 0000000..45a9aa2 --- /dev/null +++ b/src/elf/ELF_test.go @@ -0,0 +1,13 @@ +package elf_test + +import ( + "io" + "testing" + + "git.akyoto.dev/cli/q/src/elf" +) + +func TestELF(t *testing.T) { + exe := elf.New(nil, nil) + exe.Write(io.Discard) +} From c0f399df7f04f507b48786b649d8154d5db36fe8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 5 Jun 2024 15:51:05 +0200 Subject: [PATCH 0055/1012] Improved file structure --- src/arch/arm64/Syscall.go | 17 ++ src/{ => arch}/x64/Call.go | 0 src/{ => arch}/x64/Move.go | 0 src/{ => arch}/x64/Return.go | 0 src/arch/x64/Syscall.go | 22 +++ src/arch/x64/x64_test.go | 16 ++ src/asm/Assembler.go | 2 +- src/asm/Assembler_test.go | 16 +- src/compiler/Finalize.go | 8 +- src/compiler/Function.go | 12 +- src/keywords/All.go | 6 - src/linux/syscall_linux.go | 333 ---------------------------------- src/os/linux/Syscall.go | 10 + src/register/ID.go | 15 ++ src/register/Syscall_amd64.go | 11 -- src/token/Keywords.go | 6 + src/token/Tokenize.go | 4 +- src/x64/Syscall.go | 6 - 18 files changed, 106 insertions(+), 378 deletions(-) create mode 100644 src/arch/arm64/Syscall.go rename src/{ => arch}/x64/Call.go (100%) rename src/{ => arch}/x64/Move.go (100%) rename src/{ => arch}/x64/Return.go (100%) create mode 100644 src/arch/x64/Syscall.go create mode 100644 src/arch/x64/x64_test.go delete mode 100644 src/keywords/All.go delete mode 100644 src/linux/syscall_linux.go create mode 100644 src/os/linux/Syscall.go delete mode 100644 src/register/Syscall_amd64.go create mode 100644 src/token/Keywords.go delete mode 100644 src/x64/Syscall.go diff --git a/src/arch/arm64/Syscall.go b/src/arch/arm64/Syscall.go new file mode 100644 index 0000000..37e0b9e --- /dev/null +++ b/src/arch/arm64/Syscall.go @@ -0,0 +1,17 @@ +package register + +import "git.akyoto.dev/cli/q/src/register" + +const ( + SyscallNumber = register.R8 + SyscallReturn = register.R0 +) + +var SyscallArgs = []register.ID{ + register.R0, + register.R1, + register.R2, + register.R3, + register.R4, + register.R5, +} diff --git a/src/x64/Call.go b/src/arch/x64/Call.go similarity index 100% rename from src/x64/Call.go rename to src/arch/x64/Call.go diff --git a/src/x64/Move.go b/src/arch/x64/Move.go similarity index 100% rename from src/x64/Move.go rename to src/arch/x64/Move.go diff --git a/src/x64/Return.go b/src/arch/x64/Return.go similarity index 100% rename from src/x64/Return.go rename to src/arch/x64/Return.go diff --git a/src/arch/x64/Syscall.go b/src/arch/x64/Syscall.go new file mode 100644 index 0000000..132e560 --- /dev/null +++ b/src/arch/x64/Syscall.go @@ -0,0 +1,22 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/register" + +const ( + SyscallNumber = register.R0 // rax + SyscallReturn = register.R0 // rax +) + +var SyscallArgs = []register.ID{ + register.R7, // rdi + register.R6, // rsi + register.R2, // rdx + register.R10, + register.R8, + register.R9, +} + +// Syscall is the primary way to communicate with the OS kernel. +func Syscall(code []byte) []byte { + return append(code, 0x0f, 0x05) +} diff --git a/src/arch/x64/x64_test.go b/src/arch/x64/x64_test.go new file mode 100644 index 0000000..8fb7c21 --- /dev/null +++ b/src/arch/x64/x64_test.go @@ -0,0 +1,16 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/go/assert" +) + +func TestX64(t *testing.T) { + assert.DeepEqual(t, x64.Call([]byte{}, 1), []byte{0xe8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegNum32([]byte{}, 0, 1), []byte{0xb8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegNum32([]byte{}, 1, 1), []byte{0xb9, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.Return([]byte{}), []byte{0xc3}) + assert.DeepEqual(t, x64.Syscall([]byte{}), []byte{0x0f, 0x05}) +} diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index 470d310..5de2ff9 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -3,10 +3,10 @@ package asm import ( "encoding/binary" + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/log" "git.akyoto.dev/cli/q/src/register" - "git.akyoto.dev/cli/q/src/x64" ) // Assembler contains a list of instructions. diff --git a/src/asm/Assembler_test.go b/src/asm/Assembler_test.go index 581da9b..ed47f32 100644 --- a/src/asm/Assembler_test.go +++ b/src/asm/Assembler_test.go @@ -3,9 +3,9 @@ package asm_test import ( "testing" + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/linux" - "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/cli/q/src/os/linux" "git.akyoto.dev/go/assert" ) @@ -13,14 +13,14 @@ func TestHello(t *testing.T) { a := asm.New() hello := []byte("Hello\n") - a.MoveRegisterNumber(register.Syscall0, linux.Write) - a.MoveRegisterNumber(register.Syscall1, 1) - a.MoveRegisterData(register.Syscall2, hello) - a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) + a.MoveRegisterNumber(x64.SyscallNumber, linux.Write) + a.MoveRegisterNumber(x64.SyscallArgs[0], 1) + a.MoveRegisterData(x64.SyscallArgs[1], hello) + a.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(hello))) a.Syscall() - a.MoveRegisterNumber(register.Syscall0, linux.Exit) - a.MoveRegisterNumber(register.Syscall1, 0) + a.MoveRegisterNumber(x64.SyscallNumber, linux.Exit) + a.MoveRegisterNumber(x64.SyscallArgs[0], 0) a.Syscall() code, data := a.Finalize() diff --git a/src/compiler/Finalize.go b/src/compiler/Finalize.go index ccae1b3..56095f4 100644 --- a/src/compiler/Finalize.go +++ b/src/compiler/Finalize.go @@ -1,9 +1,9 @@ package compiler import ( + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/linux" - "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/cli/q/src/os/linux" ) // Finalize generates the final machine code. @@ -14,8 +14,8 @@ func Finalize(functions map[string]*Function) ([]byte, []byte) { a.Merge(&f.Assembler) } - a.MoveRegisterNumber(register.Syscall0, linux.Exit) - a.MoveRegisterNumber(register.Syscall1, 0) + a.MoveRegisterNumber(x64.SyscallNumber, linux.Exit) + a.MoveRegisterNumber(x64.SyscallArgs[0], 0) a.Syscall() code, data := a.Finalize() diff --git a/src/compiler/Function.go b/src/compiler/Function.go index 5c4742f..1b168d1 100644 --- a/src/compiler/Function.go +++ b/src/compiler/Function.go @@ -1,9 +1,9 @@ package compiler import ( + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/linux" - "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/cli/q/src/os/linux" "git.akyoto.dev/cli/q/src/token" ) @@ -20,10 +20,10 @@ func (f *Function) Compile() { for i, t := range f.Body { if t.Kind == token.Identifier && t.String() == "print" { message := f.Body[i+2].Bytes - f.Assembler.MoveRegisterNumber(register.Syscall0, linux.Write) - f.Assembler.MoveRegisterNumber(register.Syscall1, 1) - f.Assembler.MoveRegisterData(register.Syscall2, message) - f.Assembler.MoveRegisterNumber(register.Syscall3, uint64(len(message))) + f.Assembler.MoveRegisterNumber(x64.SyscallNumber, linux.Write) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[0], 1) + f.Assembler.MoveRegisterData(x64.SyscallArgs[1], message) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(message))) f.Assembler.Syscall() } } diff --git a/src/keywords/All.go b/src/keywords/All.go deleted file mode 100644 index 5df0a39..0000000 --- a/src/keywords/All.go +++ /dev/null @@ -1,6 +0,0 @@ -package keywords - -// All defines the keywords used in the language. -var All = map[string]bool{ - "return": true, -} diff --git a/src/linux/syscall_linux.go b/src/linux/syscall_linux.go deleted file mode 100644 index c535786..0000000 --- a/src/linux/syscall_linux.go +++ /dev/null @@ -1,333 +0,0 @@ -package linux - -const ( - Read = iota - Write - Open - Close - Stat - Fstat - Lstat - Poll - Lseek - Mmap - Mprotect - Munmap - Brk - Rt_sigaction - Rt_sigprocmask - Rt_sigreturn - Ioctl - Pread64 - Pwrite64 - Readv - Writev - Access - Pipe - Select - Sched_yield - Mremap - Msync - Mincore - Madvise - Shmget - Shmat - Shmctl - Dup - Dup2 - Pause - Nanosleep - Getitimer - Alarm - Setitimer - Getpid - Sendfile - Socket - Connect - Accept - Sendto - Recvfrom - Sendmsg - Recvmsg - Shutdown - Bind - Listen - Getsockname - Getpeername - Socketpair - Setsockopt - Getsockopt - Clone - Fork - Vfork - Execve - Exit - Wait4 - Kill - Uname - Semget - Semop - Semctl - Shmdt - Msgget - Msgsnd - Msgrcv - Msgctl - Fcntl - Flock - Fsync - Fdatasync - Truncate - Ftruncate - Getdents - Getcwd - Chdir - Fchdir - Rename - Mkdir - Rmdir - Creat - Link - Unlink - Symlink - Readlink - Chmod - Fchmod - Chown - Fchown - Lchown - Umask - Gettimeofday - Getrlimit - Getrusage - Sysinfo - Times - Ptrace - Getuid - Syslog - Getgid - Setuid - Setgid - Geteuid - Getegid - Setpgid - Getppid - Getpgrp - Setsid - Setreuid - Setregid - Getgroups - Setgroups - Setresuid - Getresuid - Setresgid - Getresgid - Getpgid - Setfsuid - Setfsgid - Getsid - Capget - Capset - Rt_sigpending - Rt_sigtimedwait - Rt_sigqueueinfo - Rt_sigsuspend - Sigaltstack - Utime - Mknod - Uselib - Personality - Ustat - Statfs - Fstatfs - Sysfs - Getpriority - Setpriority - Sched_setparam - Sched_getparam - Sched_setscheduler - Sched_getscheduler - Sched_get_priority_max - Sched_get_priority_min - Sched_rr_get_interval - Mlock - Munlock - Mlockall - Munlockall - Vhangup - Modify_ldt - Pivot_root - Sysctl - Prctl - Arch_prctl - Adjtimex - Setrlimit - Chroot - Sync - Acct - Settimeofday - Mount - Umount2 - Swapon - Swapoff - Reboot - Sethostname - Setdomainname - Iopl - Ioperm - Create_module - Init_module - Delete_module - Get_kernel_syms - Query_module - Quotactl - Nfsservctl - Getpmsg - Putpmsg - Afs_syscall - Tuxcall - Security - Gettid - Readahead - Setxattr - Lsetxattr - Fsetxattr - Getxattr - Lgetxattr - Fgetxattr - Listxattr - Llistxattr - Flistxattr - Removexattr - Lremovexattr - Fremovexattr - Tkill - Time - Futex - Sched_setaffinity - Sched_getaffinity - Set_thread_area - Io_setup - Io_destroy - Io_getevents - Io_submit - Io_cancel - Get_thread_area - Lookup_dcookie - Epoll_create - Epoll_ctl_old - Epoll_wait_old - Remap_file_pages - Getdents64 - Set_tid_address - Restart_syscall - Semtimedop - Fadvise64 - Timer_create - Timer_settime - Timer_gettime - Timer_getoverrun - Timer_delete - Clock_settime - Clock_gettime - Clock_getres - Clock_nanosleep - Exit_group - Epoll_wait - Epoll_ctl - Tgkill - Utimes - Vserver - Mbind - Set_mempolicy - Get_mempolicy - Mq_open - Mq_unlink - Mq_timedsend - Mq_timedreceive - Mq_notify - Mq_getsetattr - Kexec_load - Waitid - Add_key - Request_key - Keyctl - Ioprio_set - Ioprio_get - Inotify_init - Inotify_add_watch - Inotify_rm_watch - Migrate_pages - Openat - Mkdirat - Mknodat - Fchownat - Futimesat - Newfstatat - Unlinkat - Renameat - Linkat - Symlinkat - Readlinkat - Fchmodat - Faccessat - Pselect6 - Ppoll - Unshare - Set_robust_list - Get_robust_list - Splice - Tee - Sync_file_range - Vmsplice - Move_pages - Utimensat - Epoll_pwait - Signalfd - Timerfd_create - Eventfd - Fallocate - Timerfd_settime - Timerfd_gettime - Accept4 - Signalfd4 - Eventfd2 - Epoll_create1 - Dup3 - Pipe2 - Inotify_init1 - Preadv - Pwritev - Rt_tgsigqueueinfo - Perf_event_open - Recvmmsg - Fanotify_init - Fanotify_mark - Prlimit64 - Name_to_handle_at - Open_by_handle_at - Clock_adjtime - Syncfs - Sendmmsg - Setns - Getcpu - Process_vm_readv - Process_vm_writev - Kcmp - Finit_module - Sched_setattr - Sched_getattr - Renameat2 - Seccomp - Getrandom - Memfd_create - Kexec_file_load - Bpf - Stub_execveat - Userfaultfd - Membarrier - Mlock2 - Copy_file_range - Preadv2 - Pwritev2 -) diff --git a/src/os/linux/Syscall.go b/src/os/linux/Syscall.go new file mode 100644 index 0000000..7c94f6a --- /dev/null +++ b/src/os/linux/Syscall.go @@ -0,0 +1,10 @@ +package linux + +// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/entry/syscalls/syscall_64.tbl +const ( + Read = 0 + Write = 1 + Open = 2 + Close = 3 + Exit = 60 +) diff --git a/src/register/ID.go b/src/register/ID.go index 08ac82b..bc2fe1c 100644 --- a/src/register/ID.go +++ b/src/register/ID.go @@ -22,6 +22,21 @@ const ( R13 R14 R15 + R16 + R17 + R18 + R19 + R20 + R21 + R22 + R23 + R24 + R25 + R26 + R27 + R28 + R29 + R30 ) func (r ID) String() string { diff --git a/src/register/Syscall_amd64.go b/src/register/Syscall_amd64.go deleted file mode 100644 index 62cc6e5..0000000 --- a/src/register/Syscall_amd64.go +++ /dev/null @@ -1,11 +0,0 @@ -package register - -const ( - Syscall0 = R0 - Syscall1 = R7 - Syscall2 = R6 - Syscall3 = R2 - Syscall4 = R10 - Syscall5 = R8 - Syscall6 = R9 -) diff --git a/src/token/Keywords.go b/src/token/Keywords.go new file mode 100644 index 0000000..adb8de7 --- /dev/null +++ b/src/token/Keywords.go @@ -0,0 +1,6 @@ +package token + +// Keywords defines the keywords used in the language. +var Keywords = map[string]bool{ + "return": true, +} diff --git a/src/token/Tokenize.go b/src/token/Tokenize.go index 5e336d8..9229d6c 100644 --- a/src/token/Tokenize.go +++ b/src/token/Tokenize.go @@ -1,7 +1,5 @@ package token -import "git.akyoto.dev/cli/q/src/keywords" - // Pre-allocate these byte buffers so we can re-use them // instead of allocating a new buffer every time. var ( @@ -87,7 +85,7 @@ func Tokenize(buffer []byte) List { buffer[position:i], } - if keywords.All[string(token.Bytes)] { + if Keywords[string(token.Bytes)] { token.Kind = Keyword } diff --git a/src/x64/Syscall.go b/src/x64/Syscall.go deleted file mode 100644 index 94b07d2..0000000 --- a/src/x64/Syscall.go +++ /dev/null @@ -1,6 +0,0 @@ -package x64 - -// Syscall is the primary way to communicate with the OS kernel. -func Syscall(code []byte) []byte { - return append(code, 0x0f, 0x05) -} From 42a212a3f4b41c6ab8e38f81eb03e588f7631f07 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 5 Jun 2024 15:51:05 +0200 Subject: [PATCH 0056/1012] Improved file structure --- src/arch/arm64/Syscall.go | 17 ++ src/{ => arch}/x64/Call.go | 0 src/{ => arch}/x64/Move.go | 0 src/{ => arch}/x64/Return.go | 0 src/arch/x64/Syscall.go | 22 +++ src/arch/x64/x64_test.go | 16 ++ src/asm/Assembler.go | 2 +- src/asm/Assembler_test.go | 16 +- src/compiler/Finalize.go | 8 +- src/compiler/Function.go | 12 +- src/keywords/All.go | 6 - src/linux/syscall_linux.go | 333 ---------------------------------- src/os/linux/Syscall.go | 10 + src/register/ID.go | 15 ++ src/register/Syscall_amd64.go | 11 -- src/token/Keywords.go | 6 + src/token/Tokenize.go | 4 +- src/x64/Syscall.go | 6 - 18 files changed, 106 insertions(+), 378 deletions(-) create mode 100644 src/arch/arm64/Syscall.go rename src/{ => arch}/x64/Call.go (100%) rename src/{ => arch}/x64/Move.go (100%) rename src/{ => arch}/x64/Return.go (100%) create mode 100644 src/arch/x64/Syscall.go create mode 100644 src/arch/x64/x64_test.go delete mode 100644 src/keywords/All.go delete mode 100644 src/linux/syscall_linux.go create mode 100644 src/os/linux/Syscall.go delete mode 100644 src/register/Syscall_amd64.go create mode 100644 src/token/Keywords.go delete mode 100644 src/x64/Syscall.go diff --git a/src/arch/arm64/Syscall.go b/src/arch/arm64/Syscall.go new file mode 100644 index 0000000..37e0b9e --- /dev/null +++ b/src/arch/arm64/Syscall.go @@ -0,0 +1,17 @@ +package register + +import "git.akyoto.dev/cli/q/src/register" + +const ( + SyscallNumber = register.R8 + SyscallReturn = register.R0 +) + +var SyscallArgs = []register.ID{ + register.R0, + register.R1, + register.R2, + register.R3, + register.R4, + register.R5, +} diff --git a/src/x64/Call.go b/src/arch/x64/Call.go similarity index 100% rename from src/x64/Call.go rename to src/arch/x64/Call.go diff --git a/src/x64/Move.go b/src/arch/x64/Move.go similarity index 100% rename from src/x64/Move.go rename to src/arch/x64/Move.go diff --git a/src/x64/Return.go b/src/arch/x64/Return.go similarity index 100% rename from src/x64/Return.go rename to src/arch/x64/Return.go diff --git a/src/arch/x64/Syscall.go b/src/arch/x64/Syscall.go new file mode 100644 index 0000000..132e560 --- /dev/null +++ b/src/arch/x64/Syscall.go @@ -0,0 +1,22 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/register" + +const ( + SyscallNumber = register.R0 // rax + SyscallReturn = register.R0 // rax +) + +var SyscallArgs = []register.ID{ + register.R7, // rdi + register.R6, // rsi + register.R2, // rdx + register.R10, + register.R8, + register.R9, +} + +// Syscall is the primary way to communicate with the OS kernel. +func Syscall(code []byte) []byte { + return append(code, 0x0f, 0x05) +} diff --git a/src/arch/x64/x64_test.go b/src/arch/x64/x64_test.go new file mode 100644 index 0000000..8fb7c21 --- /dev/null +++ b/src/arch/x64/x64_test.go @@ -0,0 +1,16 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/go/assert" +) + +func TestX64(t *testing.T) { + assert.DeepEqual(t, x64.Call([]byte{}, 1), []byte{0xe8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegNum32([]byte{}, 0, 1), []byte{0xb8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegNum32([]byte{}, 1, 1), []byte{0xb9, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.Return([]byte{}), []byte{0xc3}) + assert.DeepEqual(t, x64.Syscall([]byte{}), []byte{0x0f, 0x05}) +} diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index 470d310..5de2ff9 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -3,10 +3,10 @@ package asm import ( "encoding/binary" + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/log" "git.akyoto.dev/cli/q/src/register" - "git.akyoto.dev/cli/q/src/x64" ) // Assembler contains a list of instructions. diff --git a/src/asm/Assembler_test.go b/src/asm/Assembler_test.go index 581da9b..ed47f32 100644 --- a/src/asm/Assembler_test.go +++ b/src/asm/Assembler_test.go @@ -3,9 +3,9 @@ package asm_test import ( "testing" + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/linux" - "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/cli/q/src/os/linux" "git.akyoto.dev/go/assert" ) @@ -13,14 +13,14 @@ func TestHello(t *testing.T) { a := asm.New() hello := []byte("Hello\n") - a.MoveRegisterNumber(register.Syscall0, linux.Write) - a.MoveRegisterNumber(register.Syscall1, 1) - a.MoveRegisterData(register.Syscall2, hello) - a.MoveRegisterNumber(register.Syscall3, uint64(len(hello))) + a.MoveRegisterNumber(x64.SyscallNumber, linux.Write) + a.MoveRegisterNumber(x64.SyscallArgs[0], 1) + a.MoveRegisterData(x64.SyscallArgs[1], hello) + a.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(hello))) a.Syscall() - a.MoveRegisterNumber(register.Syscall0, linux.Exit) - a.MoveRegisterNumber(register.Syscall1, 0) + a.MoveRegisterNumber(x64.SyscallNumber, linux.Exit) + a.MoveRegisterNumber(x64.SyscallArgs[0], 0) a.Syscall() code, data := a.Finalize() diff --git a/src/compiler/Finalize.go b/src/compiler/Finalize.go index ccae1b3..56095f4 100644 --- a/src/compiler/Finalize.go +++ b/src/compiler/Finalize.go @@ -1,9 +1,9 @@ package compiler import ( + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/linux" - "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/cli/q/src/os/linux" ) // Finalize generates the final machine code. @@ -14,8 +14,8 @@ func Finalize(functions map[string]*Function) ([]byte, []byte) { a.Merge(&f.Assembler) } - a.MoveRegisterNumber(register.Syscall0, linux.Exit) - a.MoveRegisterNumber(register.Syscall1, 0) + a.MoveRegisterNumber(x64.SyscallNumber, linux.Exit) + a.MoveRegisterNumber(x64.SyscallArgs[0], 0) a.Syscall() code, data := a.Finalize() diff --git a/src/compiler/Function.go b/src/compiler/Function.go index 5c4742f..1b168d1 100644 --- a/src/compiler/Function.go +++ b/src/compiler/Function.go @@ -1,9 +1,9 @@ package compiler import ( + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/linux" - "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/cli/q/src/os/linux" "git.akyoto.dev/cli/q/src/token" ) @@ -20,10 +20,10 @@ func (f *Function) Compile() { for i, t := range f.Body { if t.Kind == token.Identifier && t.String() == "print" { message := f.Body[i+2].Bytes - f.Assembler.MoveRegisterNumber(register.Syscall0, linux.Write) - f.Assembler.MoveRegisterNumber(register.Syscall1, 1) - f.Assembler.MoveRegisterData(register.Syscall2, message) - f.Assembler.MoveRegisterNumber(register.Syscall3, uint64(len(message))) + f.Assembler.MoveRegisterNumber(x64.SyscallNumber, linux.Write) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[0], 1) + f.Assembler.MoveRegisterData(x64.SyscallArgs[1], message) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(message))) f.Assembler.Syscall() } } diff --git a/src/keywords/All.go b/src/keywords/All.go deleted file mode 100644 index 5df0a39..0000000 --- a/src/keywords/All.go +++ /dev/null @@ -1,6 +0,0 @@ -package keywords - -// All defines the keywords used in the language. -var All = map[string]bool{ - "return": true, -} diff --git a/src/linux/syscall_linux.go b/src/linux/syscall_linux.go deleted file mode 100644 index c535786..0000000 --- a/src/linux/syscall_linux.go +++ /dev/null @@ -1,333 +0,0 @@ -package linux - -const ( - Read = iota - Write - Open - Close - Stat - Fstat - Lstat - Poll - Lseek - Mmap - Mprotect - Munmap - Brk - Rt_sigaction - Rt_sigprocmask - Rt_sigreturn - Ioctl - Pread64 - Pwrite64 - Readv - Writev - Access - Pipe - Select - Sched_yield - Mremap - Msync - Mincore - Madvise - Shmget - Shmat - Shmctl - Dup - Dup2 - Pause - Nanosleep - Getitimer - Alarm - Setitimer - Getpid - Sendfile - Socket - Connect - Accept - Sendto - Recvfrom - Sendmsg - Recvmsg - Shutdown - Bind - Listen - Getsockname - Getpeername - Socketpair - Setsockopt - Getsockopt - Clone - Fork - Vfork - Execve - Exit - Wait4 - Kill - Uname - Semget - Semop - Semctl - Shmdt - Msgget - Msgsnd - Msgrcv - Msgctl - Fcntl - Flock - Fsync - Fdatasync - Truncate - Ftruncate - Getdents - Getcwd - Chdir - Fchdir - Rename - Mkdir - Rmdir - Creat - Link - Unlink - Symlink - Readlink - Chmod - Fchmod - Chown - Fchown - Lchown - Umask - Gettimeofday - Getrlimit - Getrusage - Sysinfo - Times - Ptrace - Getuid - Syslog - Getgid - Setuid - Setgid - Geteuid - Getegid - Setpgid - Getppid - Getpgrp - Setsid - Setreuid - Setregid - Getgroups - Setgroups - Setresuid - Getresuid - Setresgid - Getresgid - Getpgid - Setfsuid - Setfsgid - Getsid - Capget - Capset - Rt_sigpending - Rt_sigtimedwait - Rt_sigqueueinfo - Rt_sigsuspend - Sigaltstack - Utime - Mknod - Uselib - Personality - Ustat - Statfs - Fstatfs - Sysfs - Getpriority - Setpriority - Sched_setparam - Sched_getparam - Sched_setscheduler - Sched_getscheduler - Sched_get_priority_max - Sched_get_priority_min - Sched_rr_get_interval - Mlock - Munlock - Mlockall - Munlockall - Vhangup - Modify_ldt - Pivot_root - Sysctl - Prctl - Arch_prctl - Adjtimex - Setrlimit - Chroot - Sync - Acct - Settimeofday - Mount - Umount2 - Swapon - Swapoff - Reboot - Sethostname - Setdomainname - Iopl - Ioperm - Create_module - Init_module - Delete_module - Get_kernel_syms - Query_module - Quotactl - Nfsservctl - Getpmsg - Putpmsg - Afs_syscall - Tuxcall - Security - Gettid - Readahead - Setxattr - Lsetxattr - Fsetxattr - Getxattr - Lgetxattr - Fgetxattr - Listxattr - Llistxattr - Flistxattr - Removexattr - Lremovexattr - Fremovexattr - Tkill - Time - Futex - Sched_setaffinity - Sched_getaffinity - Set_thread_area - Io_setup - Io_destroy - Io_getevents - Io_submit - Io_cancel - Get_thread_area - Lookup_dcookie - Epoll_create - Epoll_ctl_old - Epoll_wait_old - Remap_file_pages - Getdents64 - Set_tid_address - Restart_syscall - Semtimedop - Fadvise64 - Timer_create - Timer_settime - Timer_gettime - Timer_getoverrun - Timer_delete - Clock_settime - Clock_gettime - Clock_getres - Clock_nanosleep - Exit_group - Epoll_wait - Epoll_ctl - Tgkill - Utimes - Vserver - Mbind - Set_mempolicy - Get_mempolicy - Mq_open - Mq_unlink - Mq_timedsend - Mq_timedreceive - Mq_notify - Mq_getsetattr - Kexec_load - Waitid - Add_key - Request_key - Keyctl - Ioprio_set - Ioprio_get - Inotify_init - Inotify_add_watch - Inotify_rm_watch - Migrate_pages - Openat - Mkdirat - Mknodat - Fchownat - Futimesat - Newfstatat - Unlinkat - Renameat - Linkat - Symlinkat - Readlinkat - Fchmodat - Faccessat - Pselect6 - Ppoll - Unshare - Set_robust_list - Get_robust_list - Splice - Tee - Sync_file_range - Vmsplice - Move_pages - Utimensat - Epoll_pwait - Signalfd - Timerfd_create - Eventfd - Fallocate - Timerfd_settime - Timerfd_gettime - Accept4 - Signalfd4 - Eventfd2 - Epoll_create1 - Dup3 - Pipe2 - Inotify_init1 - Preadv - Pwritev - Rt_tgsigqueueinfo - Perf_event_open - Recvmmsg - Fanotify_init - Fanotify_mark - Prlimit64 - Name_to_handle_at - Open_by_handle_at - Clock_adjtime - Syncfs - Sendmmsg - Setns - Getcpu - Process_vm_readv - Process_vm_writev - Kcmp - Finit_module - Sched_setattr - Sched_getattr - Renameat2 - Seccomp - Getrandom - Memfd_create - Kexec_file_load - Bpf - Stub_execveat - Userfaultfd - Membarrier - Mlock2 - Copy_file_range - Preadv2 - Pwritev2 -) diff --git a/src/os/linux/Syscall.go b/src/os/linux/Syscall.go new file mode 100644 index 0000000..7c94f6a --- /dev/null +++ b/src/os/linux/Syscall.go @@ -0,0 +1,10 @@ +package linux + +// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/entry/syscalls/syscall_64.tbl +const ( + Read = 0 + Write = 1 + Open = 2 + Close = 3 + Exit = 60 +) diff --git a/src/register/ID.go b/src/register/ID.go index 08ac82b..bc2fe1c 100644 --- a/src/register/ID.go +++ b/src/register/ID.go @@ -22,6 +22,21 @@ const ( R13 R14 R15 + R16 + R17 + R18 + R19 + R20 + R21 + R22 + R23 + R24 + R25 + R26 + R27 + R28 + R29 + R30 ) func (r ID) String() string { diff --git a/src/register/Syscall_amd64.go b/src/register/Syscall_amd64.go deleted file mode 100644 index 62cc6e5..0000000 --- a/src/register/Syscall_amd64.go +++ /dev/null @@ -1,11 +0,0 @@ -package register - -const ( - Syscall0 = R0 - Syscall1 = R7 - Syscall2 = R6 - Syscall3 = R2 - Syscall4 = R10 - Syscall5 = R8 - Syscall6 = R9 -) diff --git a/src/token/Keywords.go b/src/token/Keywords.go new file mode 100644 index 0000000..adb8de7 --- /dev/null +++ b/src/token/Keywords.go @@ -0,0 +1,6 @@ +package token + +// Keywords defines the keywords used in the language. +var Keywords = map[string]bool{ + "return": true, +} diff --git a/src/token/Tokenize.go b/src/token/Tokenize.go index 5e336d8..9229d6c 100644 --- a/src/token/Tokenize.go +++ b/src/token/Tokenize.go @@ -1,7 +1,5 @@ package token -import "git.akyoto.dev/cli/q/src/keywords" - // Pre-allocate these byte buffers so we can re-use them // instead of allocating a new buffer every time. var ( @@ -87,7 +85,7 @@ func Tokenize(buffer []byte) List { buffer[position:i], } - if keywords.All[string(token.Bytes)] { + if Keywords[string(token.Bytes)] { token.Kind = Keyword } diff --git a/src/x64/Syscall.go b/src/x64/Syscall.go deleted file mode 100644 index 94b07d2..0000000 --- a/src/x64/Syscall.go +++ /dev/null @@ -1,6 +0,0 @@ -package x64 - -// Syscall is the primary way to communicate with the OS kernel. -func Syscall(code []byte) []byte { - return append(code, 0x0f, 0x05) -} From 18993e085509e781bbc111f75832216dcaac4c47 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Jun 2024 21:35:14 +0200 Subject: [PATCH 0057/1012] Added token tests --- src/compiler/Function.go | 2 +- src/compiler/Scan.go | 2 +- src/token/Kind.go | 82 ++++------------ src/token/List.go | 14 ++- src/token/Token.go | 4 +- src/token/Token_test.go | 196 +++++++++++++++++++++++++++++++++++++++ src/token/Tokenize.go | 16 +++- 7 files changed, 241 insertions(+), 75 deletions(-) create mode 100644 src/token/Token_test.go diff --git a/src/compiler/Function.go b/src/compiler/Function.go index 1b168d1..d9e65a3 100644 --- a/src/compiler/Function.go +++ b/src/compiler/Function.go @@ -18,7 +18,7 @@ type Function struct { // Compile turns a function into machine code. func (f *Function) Compile() { for i, t := range f.Body { - if t.Kind == token.Identifier && t.String() == "print" { + if t.Kind == token.Identifier && t.Text() == "print" { message := f.Body[i+2].Bytes f.Assembler.MoveRegisterNumber(x64.SyscallNumber, linux.Write) f.Assembler.MoveRegisterNumber(x64.SyscallArgs[0], 1) diff --git a/src/compiler/Scan.go b/src/compiler/Scan.go index deef107..d0ef625 100644 --- a/src/compiler/Scan.go +++ b/src/compiler/Scan.go @@ -95,7 +95,7 @@ func scanFile(path string, functions chan<- *Function) error { if blockLevel == 0 { function := &Function{ - Name: tokens[headerStart].String(), + Name: tokens[headerStart].Text(), Head: tokens[headerStart:bodyStart], Body: tokens[bodyStart : i+1], } diff --git a/src/token/Kind.go b/src/token/Kind.go index 742c381..70065cd 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -16,8 +16,8 @@ const ( // Keyword represents a language keyword. Keyword - // Text represents an uninterpreted series of characters in the source code. - Text + // String represents an uninterpreted series of characters in the source code. + String // Number represents a series of numerical characters. Number @@ -28,12 +28,6 @@ const ( // Separator represents a comma. Separator - // Range represents '..'. - Range - - // Question represents '?'. - Question - // Comment represents a comment. Comment @@ -58,59 +52,21 @@ const ( // String returns the text representation. func (kind Kind) String() string { - switch kind { - case NewLine: - return "NewLine" - - case Identifier: - return "Identifier" - - case Keyword: - return "Keyword" - - case Text: - return "Text" - - case Number: - return "Number" - - case Operator: - return "Operator" - - case Separator: - return "Separator" - - case Range: - return "Range" - - case Question: - return "Question" - - case Comment: - return "Comment" - - case GroupStart: - return "GroupStart" - - case GroupEnd: - return "GroupEnd" - - case BlockStart: - return "BlockStart" - - case BlockEnd: - return "BlockEnd" - - case ArrayStart: - return "ArrayStart" - - case ArrayEnd: - return "ArrayEnd" - - case Invalid: - return "Invalid" - - default: - return "" - } + return [...]string{ + "Invalid", + "NewLine", + "Identifier", + "Keyword", + "String", + "Number", + "Operator", + "Separator", + "Comment", + "GroupStart", + "GroupEnd", + "BlockStart", + "BlockEnd", + "ArrayStart", + "ArrayEnd", + }[kind] } diff --git a/src/token/List.go b/src/token/List.go index d5ea0ad..4c5953e 100644 --- a/src/token/List.go +++ b/src/token/List.go @@ -1,16 +1,24 @@ package token -import "strings" +import ( + "bytes" +) // List is a slice of tokens. type List []Token // String implements string serialization. func (list List) String() string { - builder := strings.Builder{} + builder := bytes.Buffer{} + var last Token for _, t := range list { - builder.WriteString(t.String()) + if t.Kind == Identifier && last.Kind == Separator { + builder.WriteByte(' ') + } + + builder.Write(t.Bytes) + last = t } return builder.String() diff --git a/src/token/Token.go b/src/token/Token.go index f88b69c..78d6005 100644 --- a/src/token/Token.go +++ b/src/token/Token.go @@ -9,7 +9,7 @@ type Token struct { Bytes []byte } -// String returns the token text. -func (t Token) String() string { +// Text returns the token text. +func (t Token) Text() string { return string(t.Bytes) } diff --git a/src/token/Token_test.go b/src/token/Token_test.go new file mode 100644 index 0000000..8bcac13 --- /dev/null +++ b/src/token/Token_test.go @@ -0,0 +1,196 @@ +package token_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/go/assert" +) + +func TestFunction(t *testing.T) { + tokens := token.Tokenize([]byte("main(){}")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Identifier, + Bytes: []byte("main"), + Position: 0, + }, + { + Kind: token.GroupStart, + Bytes: []byte("("), + Position: 4, + }, + { + Kind: token.GroupEnd, + Bytes: []byte(")"), + Position: 5, + }, + { + Kind: token.BlockStart, + Bytes: []byte("{"), + Position: 6, + }, + { + Kind: token.BlockEnd, + Bytes: []byte("}"), + Position: 7, + }, + }) +} + +func TestKeyword(t *testing.T) { + tokens := token.Tokenize([]byte("return x")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Keyword, + Bytes: []byte("return"), + Position: 0, + }, + { + Kind: token.Identifier, + Bytes: []byte("x"), + Position: 7, + }, + }) +} + +func TestArray(t *testing.T) { + tokens := token.Tokenize([]byte("array[i]")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Identifier, + Bytes: []byte("array"), + Position: 0, + }, + { + Kind: token.ArrayStart, + Bytes: []byte("["), + Position: 5, + }, + { + Kind: token.Identifier, + Bytes: []byte("i"), + Position: 6, + }, + { + Kind: token.ArrayEnd, + Bytes: []byte("]"), + Position: 7, + }, + }) +} + +func TestNewline(t *testing.T) { + tokens := token.Tokenize([]byte("\n\n")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.NewLine, + Bytes: []byte("\n"), + Position: 0, + }, + { + Kind: token.NewLine, + Bytes: []byte("\n"), + Position: 1, + }, + }) +} + +func TestSeparator(t *testing.T) { + tokens := token.Tokenize([]byte("a,b,c")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Identifier, + Bytes: []byte("a"), + Position: 0, + }, + { + Kind: token.Separator, + Bytes: []byte(","), + Position: 1, + }, + { + Kind: token.Identifier, + Bytes: []byte("b"), + Position: 2, + }, + { + Kind: token.Separator, + Bytes: []byte(","), + Position: 3, + }, + { + Kind: token.Identifier, + Bytes: []byte("c"), + Position: 4, + }, + }) +} + +func TestString(t *testing.T) { + tokens := token.Tokenize([]byte(`"Hello" "World"`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.String, + Bytes: []byte(`"Hello"`), + Position: 0, + }, + { + Kind: token.String, + Bytes: []byte(`"World"`), + Position: 8, + }, + }) +} + +func TestStringMultiline(t *testing.T) { + tokens := token.Tokenize([]byte("\"Hello\nWorld\"")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.String, + Bytes: []byte("\"Hello\nWorld\""), + Position: 0, + }, + }) +} + +func TestStringEOF(t *testing.T) { + tokens := token.Tokenize([]byte(`"EOF`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.String, + Bytes: []byte(`"EOF`), + Position: 0, + }, + }) +} + +func TestTokenText(t *testing.T) { + hello := token.Token{Kind: token.Identifier, Bytes: []byte("hello"), Position: 0} + comma := token.Token{Kind: token.Separator, Bytes: []byte(","), Position: 5} + world := token.Token{Kind: token.Identifier, Bytes: []byte("world"), Position: 7} + + assert.Equal(t, hello.Text(), "hello") + assert.Equal(t, world.Text(), "world") + + list := token.List{hello, comma, world} + assert.Equal(t, list.String(), "hello, world") +} + +func TestTokenKind(t *testing.T) { + assert.Equal(t, token.Invalid.String(), "Invalid") + assert.Equal(t, token.NewLine.String(), "NewLine") + assert.Equal(t, token.Identifier.String(), "Identifier") + assert.Equal(t, token.Keyword.String(), "Keyword") + assert.Equal(t, token.String.String(), "String") + assert.Equal(t, token.Number.String(), "Number") + assert.Equal(t, token.Operator.String(), "Operator") + assert.Equal(t, token.Separator.String(), "Separator") + assert.Equal(t, token.Comment.String(), "Comment") + assert.Equal(t, token.GroupStart.String(), "GroupStart") + assert.Equal(t, token.GroupEnd.String(), "GroupEnd") + assert.Equal(t, token.BlockStart.String(), "BlockStart") + assert.Equal(t, token.BlockEnd.String(), "BlockEnd") + assert.Equal(t, token.ArrayStart.String(), "ArrayStart") + assert.Equal(t, token.ArrayEnd.String(), "ArrayEnd") +} diff --git a/src/token/Tokenize.go b/src/token/Tokenize.go index 9229d6c..4692791 100644 --- a/src/token/Tokenize.go +++ b/src/token/Tokenize.go @@ -24,17 +24,23 @@ func Tokenize(buffer []byte) List { switch buffer[i] { // Texts case '"': + start := i + end := len(buffer) i++ - position := i - for i < len(buffer) && buffer[i] != '"' { + for i < len(buffer) { + if buffer[i] == '"' { + end = i + 1 + break + } + i++ } tokens = append(tokens, Token{ - Text, - position, - buffer[position:i], + String, + start, + buffer[start:end], }) // Parentheses start From e93b797dc6b905a8e89ca283382fb1d0754aa954 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Jun 2024 21:35:14 +0200 Subject: [PATCH 0058/1012] Added token tests --- src/compiler/Function.go | 2 +- src/compiler/Scan.go | 2 +- src/token/Kind.go | 82 ++++------------ src/token/List.go | 14 ++- src/token/Token.go | 4 +- src/token/Token_test.go | 196 +++++++++++++++++++++++++++++++++++++++ src/token/Tokenize.go | 16 +++- 7 files changed, 241 insertions(+), 75 deletions(-) create mode 100644 src/token/Token_test.go diff --git a/src/compiler/Function.go b/src/compiler/Function.go index 1b168d1..d9e65a3 100644 --- a/src/compiler/Function.go +++ b/src/compiler/Function.go @@ -18,7 +18,7 @@ type Function struct { // Compile turns a function into machine code. func (f *Function) Compile() { for i, t := range f.Body { - if t.Kind == token.Identifier && t.String() == "print" { + if t.Kind == token.Identifier && t.Text() == "print" { message := f.Body[i+2].Bytes f.Assembler.MoveRegisterNumber(x64.SyscallNumber, linux.Write) f.Assembler.MoveRegisterNumber(x64.SyscallArgs[0], 1) diff --git a/src/compiler/Scan.go b/src/compiler/Scan.go index deef107..d0ef625 100644 --- a/src/compiler/Scan.go +++ b/src/compiler/Scan.go @@ -95,7 +95,7 @@ func scanFile(path string, functions chan<- *Function) error { if blockLevel == 0 { function := &Function{ - Name: tokens[headerStart].String(), + Name: tokens[headerStart].Text(), Head: tokens[headerStart:bodyStart], Body: tokens[bodyStart : i+1], } diff --git a/src/token/Kind.go b/src/token/Kind.go index 742c381..70065cd 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -16,8 +16,8 @@ const ( // Keyword represents a language keyword. Keyword - // Text represents an uninterpreted series of characters in the source code. - Text + // String represents an uninterpreted series of characters in the source code. + String // Number represents a series of numerical characters. Number @@ -28,12 +28,6 @@ const ( // Separator represents a comma. Separator - // Range represents '..'. - Range - - // Question represents '?'. - Question - // Comment represents a comment. Comment @@ -58,59 +52,21 @@ const ( // String returns the text representation. func (kind Kind) String() string { - switch kind { - case NewLine: - return "NewLine" - - case Identifier: - return "Identifier" - - case Keyword: - return "Keyword" - - case Text: - return "Text" - - case Number: - return "Number" - - case Operator: - return "Operator" - - case Separator: - return "Separator" - - case Range: - return "Range" - - case Question: - return "Question" - - case Comment: - return "Comment" - - case GroupStart: - return "GroupStart" - - case GroupEnd: - return "GroupEnd" - - case BlockStart: - return "BlockStart" - - case BlockEnd: - return "BlockEnd" - - case ArrayStart: - return "ArrayStart" - - case ArrayEnd: - return "ArrayEnd" - - case Invalid: - return "Invalid" - - default: - return "" - } + return [...]string{ + "Invalid", + "NewLine", + "Identifier", + "Keyword", + "String", + "Number", + "Operator", + "Separator", + "Comment", + "GroupStart", + "GroupEnd", + "BlockStart", + "BlockEnd", + "ArrayStart", + "ArrayEnd", + }[kind] } diff --git a/src/token/List.go b/src/token/List.go index d5ea0ad..4c5953e 100644 --- a/src/token/List.go +++ b/src/token/List.go @@ -1,16 +1,24 @@ package token -import "strings" +import ( + "bytes" +) // List is a slice of tokens. type List []Token // String implements string serialization. func (list List) String() string { - builder := strings.Builder{} + builder := bytes.Buffer{} + var last Token for _, t := range list { - builder.WriteString(t.String()) + if t.Kind == Identifier && last.Kind == Separator { + builder.WriteByte(' ') + } + + builder.Write(t.Bytes) + last = t } return builder.String() diff --git a/src/token/Token.go b/src/token/Token.go index f88b69c..78d6005 100644 --- a/src/token/Token.go +++ b/src/token/Token.go @@ -9,7 +9,7 @@ type Token struct { Bytes []byte } -// String returns the token text. -func (t Token) String() string { +// Text returns the token text. +func (t Token) Text() string { return string(t.Bytes) } diff --git a/src/token/Token_test.go b/src/token/Token_test.go new file mode 100644 index 0000000..8bcac13 --- /dev/null +++ b/src/token/Token_test.go @@ -0,0 +1,196 @@ +package token_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/go/assert" +) + +func TestFunction(t *testing.T) { + tokens := token.Tokenize([]byte("main(){}")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Identifier, + Bytes: []byte("main"), + Position: 0, + }, + { + Kind: token.GroupStart, + Bytes: []byte("("), + Position: 4, + }, + { + Kind: token.GroupEnd, + Bytes: []byte(")"), + Position: 5, + }, + { + Kind: token.BlockStart, + Bytes: []byte("{"), + Position: 6, + }, + { + Kind: token.BlockEnd, + Bytes: []byte("}"), + Position: 7, + }, + }) +} + +func TestKeyword(t *testing.T) { + tokens := token.Tokenize([]byte("return x")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Keyword, + Bytes: []byte("return"), + Position: 0, + }, + { + Kind: token.Identifier, + Bytes: []byte("x"), + Position: 7, + }, + }) +} + +func TestArray(t *testing.T) { + tokens := token.Tokenize([]byte("array[i]")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Identifier, + Bytes: []byte("array"), + Position: 0, + }, + { + Kind: token.ArrayStart, + Bytes: []byte("["), + Position: 5, + }, + { + Kind: token.Identifier, + Bytes: []byte("i"), + Position: 6, + }, + { + Kind: token.ArrayEnd, + Bytes: []byte("]"), + Position: 7, + }, + }) +} + +func TestNewline(t *testing.T) { + tokens := token.Tokenize([]byte("\n\n")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.NewLine, + Bytes: []byte("\n"), + Position: 0, + }, + { + Kind: token.NewLine, + Bytes: []byte("\n"), + Position: 1, + }, + }) +} + +func TestSeparator(t *testing.T) { + tokens := token.Tokenize([]byte("a,b,c")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Identifier, + Bytes: []byte("a"), + Position: 0, + }, + { + Kind: token.Separator, + Bytes: []byte(","), + Position: 1, + }, + { + Kind: token.Identifier, + Bytes: []byte("b"), + Position: 2, + }, + { + Kind: token.Separator, + Bytes: []byte(","), + Position: 3, + }, + { + Kind: token.Identifier, + Bytes: []byte("c"), + Position: 4, + }, + }) +} + +func TestString(t *testing.T) { + tokens := token.Tokenize([]byte(`"Hello" "World"`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.String, + Bytes: []byte(`"Hello"`), + Position: 0, + }, + { + Kind: token.String, + Bytes: []byte(`"World"`), + Position: 8, + }, + }) +} + +func TestStringMultiline(t *testing.T) { + tokens := token.Tokenize([]byte("\"Hello\nWorld\"")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.String, + Bytes: []byte("\"Hello\nWorld\""), + Position: 0, + }, + }) +} + +func TestStringEOF(t *testing.T) { + tokens := token.Tokenize([]byte(`"EOF`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.String, + Bytes: []byte(`"EOF`), + Position: 0, + }, + }) +} + +func TestTokenText(t *testing.T) { + hello := token.Token{Kind: token.Identifier, Bytes: []byte("hello"), Position: 0} + comma := token.Token{Kind: token.Separator, Bytes: []byte(","), Position: 5} + world := token.Token{Kind: token.Identifier, Bytes: []byte("world"), Position: 7} + + assert.Equal(t, hello.Text(), "hello") + assert.Equal(t, world.Text(), "world") + + list := token.List{hello, comma, world} + assert.Equal(t, list.String(), "hello, world") +} + +func TestTokenKind(t *testing.T) { + assert.Equal(t, token.Invalid.String(), "Invalid") + assert.Equal(t, token.NewLine.String(), "NewLine") + assert.Equal(t, token.Identifier.String(), "Identifier") + assert.Equal(t, token.Keyword.String(), "Keyword") + assert.Equal(t, token.String.String(), "String") + assert.Equal(t, token.Number.String(), "Number") + assert.Equal(t, token.Operator.String(), "Operator") + assert.Equal(t, token.Separator.String(), "Separator") + assert.Equal(t, token.Comment.String(), "Comment") + assert.Equal(t, token.GroupStart.String(), "GroupStart") + assert.Equal(t, token.GroupEnd.String(), "GroupEnd") + assert.Equal(t, token.BlockStart.String(), "BlockStart") + assert.Equal(t, token.BlockEnd.String(), "BlockEnd") + assert.Equal(t, token.ArrayStart.String(), "ArrayStart") + assert.Equal(t, token.ArrayEnd.String(), "ArrayEnd") +} diff --git a/src/token/Tokenize.go b/src/token/Tokenize.go index 9229d6c..4692791 100644 --- a/src/token/Tokenize.go +++ b/src/token/Tokenize.go @@ -24,17 +24,23 @@ func Tokenize(buffer []byte) List { switch buffer[i] { // Texts case '"': + start := i + end := len(buffer) i++ - position := i - for i < len(buffer) && buffer[i] != '"' { + for i < len(buffer) { + if buffer[i] == '"' { + end = i + 1 + break + } + i++ } tokens = append(tokens, Token{ - Text, - position, - buffer[position:i], + String, + start, + buffer[start:end], }) // Parentheses start From 8b595ef3ce061f8fffca2cea24c2ba733dec7d8f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Jun 2024 14:35:23 +0200 Subject: [PATCH 0059/1012] Added directory test --- src/directory/Walk_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/directory/Walk_test.go b/src/directory/Walk_test.go index a4fcda8..f3f64cf 100644 --- a/src/directory/Walk_test.go +++ b/src/directory/Walk_test.go @@ -17,3 +17,8 @@ func TestWalk(t *testing.T) { assert.Contains(t, files, "Walk.go") assert.Contains(t, files, "Walk_test.go") } + +func TestNonExisting(t *testing.T) { + err := directory.Walk("does-not-exist", func(file string) {}) + assert.NotNil(t, err) +} From c7354b8613b323861473398498a4062f59b5cbe0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Jun 2024 14:35:23 +0200 Subject: [PATCH 0060/1012] Added directory test --- src/directory/Walk_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/directory/Walk_test.go b/src/directory/Walk_test.go index a4fcda8..f3f64cf 100644 --- a/src/directory/Walk_test.go +++ b/src/directory/Walk_test.go @@ -17,3 +17,8 @@ func TestWalk(t *testing.T) { assert.Contains(t, files, "Walk.go") assert.Contains(t, files, "Walk_test.go") } + +func TestNonExisting(t *testing.T) { + err := directory.Walk("does-not-exist", func(file string) {}) + assert.NotNil(t, err) +} From 722d07c3214e642ca3847a8f17d6118dbf76d3a1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 10 Jun 2024 15:51:39 +0200 Subject: [PATCH 0061/1012] Reorganized file structure --- examples/hello/hello.q | 2 +- src/arch/arm64/Syscall.go | 17 ---- src/arch/x64/Syscall.go | 22 ----- src/asm/Address.go | 4 - src/asm/Assembler.go | 93 ---------------------- src/asm/Assembler_test.go | 41 ---------- src/asm/Data.go | 20 ----- src/asm/Instruction.go | 30 ------- src/build/Build.go | 34 +------- src/build/Build_test.go | 24 ++++++ src/{compiler => build}/Compile.go | 6 +- src/{compiler => build}/Finalize.go | 10 +-- src/build/Function.go | 96 ++++++++++++++++++++++ src/{compiler => build}/Scan.go | 6 +- src/build/Write.go | 29 +++++++ src/build/arch/arm64/Syscall.go | 9 +++ src/{ => build}/arch/x64/Call.go | 0 src/{ => build}/arch/x64/Move.go | 0 src/{ => build}/arch/x64/Return.go | 0 src/build/arch/x64/Syscall.go | 14 ++++ src/{ => build}/arch/x64/x64_test.go | 2 +- src/build/asm/Assembler.go | 66 +++++++++++++++ src/build/asm/Instruction.go | 19 +++++ src/build/asm/Instructions.go | 32 ++++++++ src/{ => build}/asm/Mnemonic.go | 11 +-- src/{ => build}/asm/Pointer.go | 3 + src/build/asm/RegisterNumber.go | 10 +++ src/{ => build}/config/config.go | 0 src/build/cpu/Register.go | 11 +++ src/{ => build}/directory/Walk.go | 0 src/{ => build}/directory/Walk_test.go | 2 +- src/{ => build}/elf/ELF.go | 2 +- src/{ => build}/elf/ELF_test.go | 2 +- src/{ => build}/elf/Header.go | 0 src/{ => build}/elf/ProgramHeader.go | 0 src/{ => build}/elf/SectionHeader.go | 0 src/{ => build}/elf/elf.md | 0 src/{ => build}/os/linux/Syscall.go | 0 src/build/output/Compiler.go | 27 +++++++ src/{ => build}/token/Keywords.go | 0 src/{ => build}/token/Kind.go | 0 src/{ => build}/token/List.go | 0 src/{ => build}/token/Token.go | 0 src/{ => build}/token/Token_test.go | 18 ++++- src/{ => build}/token/Tokenize.go | 40 ++++++++-- src/cli/Build.go | 8 +- src/cli/Help.go | 12 ++- src/cli/Main_test.go | 8 -- src/cli/System.go | 17 ++-- src/compiler/Function.go | 35 -------- src/cpu/CPU.go | 106 ------------------------- src/cpu/List.go | 29 ------- src/cpu/Register.go | 39 --------- src/errors/InvalidDirectory.go | 17 ---- src/errors/RegisterInUse.go | 14 ---- src/log/log.go | 14 ---- src/register/ID.go | 44 ---------- 57 files changed, 431 insertions(+), 614 deletions(-) delete mode 100644 src/arch/arm64/Syscall.go delete mode 100644 src/arch/x64/Syscall.go delete mode 100644 src/asm/Address.go delete mode 100644 src/asm/Assembler.go delete mode 100644 src/asm/Assembler_test.go delete mode 100644 src/asm/Data.go delete mode 100644 src/asm/Instruction.go create mode 100644 src/build/Build_test.go rename src/{compiler => build}/Compile.go (94%) rename src/{compiler => build}/Finalize.go (51%) create mode 100644 src/build/Function.go rename src/{compiler => build}/Scan.go (94%) create mode 100644 src/build/Write.go create mode 100644 src/build/arch/arm64/Syscall.go rename src/{ => build}/arch/x64/Call.go (100%) rename src/{ => build}/arch/x64/Move.go (100%) rename src/{ => build}/arch/x64/Return.go (100%) create mode 100644 src/build/arch/x64/Syscall.go rename src/{ => build}/arch/x64/x64_test.go (91%) create mode 100644 src/build/asm/Assembler.go create mode 100644 src/build/asm/Instruction.go create mode 100644 src/build/asm/Instructions.go rename src/{ => build}/asm/Mnemonic.go (69%) rename src/{ => build}/asm/Pointer.go (81%) create mode 100644 src/build/asm/RegisterNumber.go rename src/{ => build}/config/config.go (100%) create mode 100644 src/build/cpu/Register.go rename src/{ => build}/directory/Walk.go (100%) rename src/{ => build}/directory/Walk_test.go (90%) rename src/{ => build}/elf/ELF.go (97%) rename src/{ => build}/elf/ELF_test.go (77%) rename src/{ => build}/elf/Header.go (100%) rename src/{ => build}/elf/ProgramHeader.go (100%) rename src/{ => build}/elf/SectionHeader.go (100%) rename src/{ => build}/elf/elf.md (100%) rename src/{ => build}/os/linux/Syscall.go (100%) create mode 100644 src/build/output/Compiler.go rename src/{ => build}/token/Keywords.go (100%) rename src/{ => build}/token/Kind.go (100%) rename src/{ => build}/token/List.go (100%) rename src/{ => build}/token/Token.go (100%) rename src/{ => build}/token/Token_test.go (92%) rename src/{ => build}/token/Tokenize.go (78%) delete mode 100644 src/compiler/Function.go delete mode 100644 src/cpu/CPU.go delete mode 100644 src/cpu/List.go delete mode 100644 src/cpu/Register.go delete mode 100644 src/errors/InvalidDirectory.go delete mode 100644 src/errors/RegisterInUse.go delete mode 100644 src/log/log.go delete mode 100644 src/register/ID.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 4f723f0..76d8d21 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,3 +1,3 @@ main() { - print("Hello") + syscall(60, 0) } diff --git a/src/arch/arm64/Syscall.go b/src/arch/arm64/Syscall.go deleted file mode 100644 index 37e0b9e..0000000 --- a/src/arch/arm64/Syscall.go +++ /dev/null @@ -1,17 +0,0 @@ -package register - -import "git.akyoto.dev/cli/q/src/register" - -const ( - SyscallNumber = register.R8 - SyscallReturn = register.R0 -) - -var SyscallArgs = []register.ID{ - register.R0, - register.R1, - register.R2, - register.R3, - register.R4, - register.R5, -} diff --git a/src/arch/x64/Syscall.go b/src/arch/x64/Syscall.go deleted file mode 100644 index 132e560..0000000 --- a/src/arch/x64/Syscall.go +++ /dev/null @@ -1,22 +0,0 @@ -package x64 - -import "git.akyoto.dev/cli/q/src/register" - -const ( - SyscallNumber = register.R0 // rax - SyscallReturn = register.R0 // rax -) - -var SyscallArgs = []register.ID{ - register.R7, // rdi - register.R6, // rsi - register.R2, // rdx - register.R10, - register.R8, - register.R9, -} - -// Syscall is the primary way to communicate with the OS kernel. -func Syscall(code []byte) []byte { - return append(code, 0x0f, 0x05) -} diff --git a/src/asm/Address.go b/src/asm/Address.go deleted file mode 100644 index 14b073b..0000000 --- a/src/asm/Address.go +++ /dev/null @@ -1,4 +0,0 @@ -package asm - -// Address represents a memory address. -type Address = uint32 diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go deleted file mode 100644 index 5de2ff9..0000000 --- a/src/asm/Assembler.go +++ /dev/null @@ -1,93 +0,0 @@ -package asm - -import ( - "encoding/binary" - - "git.akyoto.dev/cli/q/src/arch/x64" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/log" - "git.akyoto.dev/cli/q/src/register" -) - -// Assembler contains a list of instructions. -type Assembler struct { - Instructions []Instruction -} - -// New creates a new assembler. -func New() *Assembler { - return &Assembler{ - Instructions: make([]Instruction, 0, 8), - } -} - -// Finalize generates the final machine code. -func (a *Assembler) Finalize() ([]byte, []byte) { - code := make([]byte, 0, len(a.Instructions)*8) - data := make(Data, 0, 16) - pointers := []Pointer{} - - for _, x := range a.Instructions { - switch x.Mnemonic { - case MOV: - code = x64.MoveRegNum32(code, uint8(x.Destination), uint32(x.Number)) - - case MOVDATA: - code = x64.MoveRegNum32(code, uint8(x.Destination), 0) - - pointers = append(pointers, Pointer{ - Position: Address(len(code) - 4), - Address: data.Add(x.Data), - }) - - case SYSCALL: - code = x64.Syscall(code) - } - } - - if config.Verbose { - for _, x := range a.Instructions { - log.Info.Println(x.String()) - } - } - - dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) - - for _, pointer := range pointers { - slice := code[pointer.Position : pointer.Position+4] - address := dataStart + pointer.Address - binary.LittleEndian.PutUint32(slice, address) - } - - return code, data -} - -// Merge combines the contents of this assembler with another one. -func (a *Assembler) Merge(b *Assembler) { - a.Instructions = append(a.Instructions, b.Instructions...) -} - -// MoveRegisterData moves a data section address into the given register. -func (a *Assembler) MoveRegisterData(reg register.ID, data []byte) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: MOVDATA, - Destination: reg, - Data: data, - }) -} - -// MoveRegisterNumber moves a number into the given register. -func (a *Assembler) MoveRegisterNumber(reg register.ID, number uint64) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: MOV, - Destination: reg, - Number: number, - }) -} - -// Syscall executes a kernel function. -func (a *Assembler) Syscall() { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: SYSCALL, - }) -} diff --git a/src/asm/Assembler_test.go b/src/asm/Assembler_test.go deleted file mode 100644 index ed47f32..0000000 --- a/src/asm/Assembler_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package asm_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/arch/x64" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/os/linux" - "git.akyoto.dev/go/assert" -) - -func TestHello(t *testing.T) { - a := asm.New() - - hello := []byte("Hello\n") - a.MoveRegisterNumber(x64.SyscallNumber, linux.Write) - a.MoveRegisterNumber(x64.SyscallArgs[0], 1) - a.MoveRegisterData(x64.SyscallArgs[1], hello) - a.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(hello))) - a.Syscall() - - a.MoveRegisterNumber(x64.SyscallNumber, linux.Exit) - a.MoveRegisterNumber(x64.SyscallArgs[0], 0) - a.Syscall() - - code, data := a.Finalize() - - assert.DeepEqual(t, code, []byte{ - 0xb8, 0x01, 0x00, 0x00, 0x00, - 0xbf, 0x01, 0x00, 0x00, 0x00, - 0xbe, 0xa2, 0x00, 0x40, 0x00, - 0xba, 0x06, 0x00, 0x00, 0x00, - 0x0f, 0x05, - - 0xb8, 0x3c, 0x00, 0x00, 0x00, - 0xbf, 0x00, 0x00, 0x00, 0x00, - 0x0f, 0x05, - }) - - assert.DeepEqual(t, data, hello) -} diff --git a/src/asm/Data.go b/src/asm/Data.go deleted file mode 100644 index 418a898..0000000 --- a/src/asm/Data.go +++ /dev/null @@ -1,20 +0,0 @@ -package asm - -import "bytes" - -// Data represents the static read-only data. -type Data []byte - -// Add adds the given bytes to the data block if this sequence of bytes doesn't exist yet. -// It returns the address relative to the start of the data section. -func (data *Data) Add(block []byte) Address { - position := bytes.Index(*data, block) - - if position != -1 { - return Address(position) - } - - address := Address(len(*data)) - *data = append(*data, block...) - return address -} diff --git a/src/asm/Instruction.go b/src/asm/Instruction.go deleted file mode 100644 index 0c91ee1..0000000 --- a/src/asm/Instruction.go +++ /dev/null @@ -1,30 +0,0 @@ -package asm - -import ( - "fmt" - - "git.akyoto.dev/cli/q/src/register" -) - -// Instruction represents a single instruction which can be converted to machine code. -type Instruction struct { - Mnemonic Mnemonic - Source register.ID - Destination register.ID - Number uint64 - Data []byte -} - -// String returns the assembler representation of the instruction. -func (x *Instruction) String() string { - switch x.Mnemonic { - case MOV: - return fmt.Sprintf("%s %s, %x", x.Mnemonic, x.Destination, x.Number) - case MOVDATA: - return fmt.Sprintf("%s %s, %v", x.Mnemonic, x.Destination, x.Data) - case SYSCALL: - return x.Mnemonic.String() - default: - return "" - } -} diff --git a/src/build/Build.go b/src/build/Build.go index f73045a..c7029cb 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -1,12 +1,7 @@ package build import ( - "bufio" - "os" "path/filepath" - - "git.akyoto.dev/cli/q/src/compiler" - "git.akyoto.dev/cli/q/src/elf" ) // Build describes a compiler build. @@ -25,7 +20,7 @@ func New(directory string) *Build { // Run parses the input files and generates an executable file. func (build *Build) Run() error { - functions, err := compiler.Compile(build.Directory) + functions, err := Compile(build.Directory) if err != nil { return err @@ -35,33 +30,12 @@ func (build *Build) Run() error { return nil } - code, data := compiler.Finalize(functions) - return writeToDisk(build.Executable(), code, data) + path := build.Executable() + code, data := Finalize(functions) + return Write(path, code, data) } // Executable returns the path to the executable. func (build *Build) Executable() string { return filepath.Join(build.Directory, filepath.Base(build.Directory)) } - -// writeToDisk writes the executable file to disk. -func writeToDisk(filePath string, code []byte, data []byte) error { - file, err := os.Create(filePath) - - if err != nil { - return err - } - - buffer := bufio.NewWriter(file) - executable := elf.New(code, data) - executable.Write(buffer) - buffer.Flush() - - err = file.Close() - - if err != nil { - return err - } - - return os.Chmod(filePath, 0755) -} diff --git a/src/build/Build_test.go b/src/build/Build_test.go new file mode 100644 index 0000000..7c91f0b --- /dev/null +++ b/src/build/Build_test.go @@ -0,0 +1,24 @@ +package build_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/go/assert" +) + +func TestBuild(t *testing.T) { + b := build.New("../../examples/hello") + assert.Nil(t, b.Run()) +} + +func TestSkipExecutable(t *testing.T) { + b := build.New("../../examples/hello") + b.WriteExecutable = false + assert.Nil(t, b.Run()) +} + +func TestNonExisting(t *testing.T) { + b := build.New("does-not-exist") + assert.NotNil(t, b.Run()) +} diff --git a/src/compiler/Compile.go b/src/build/Compile.go similarity index 94% rename from src/compiler/Compile.go rename to src/build/Compile.go index 5311d01..1d1ffc1 100644 --- a/src/compiler/Compile.go +++ b/src/build/Compile.go @@ -1,6 +1,8 @@ -package compiler +package build -import "sync" +import ( + "sync" +) // Compile compiles all the functions. func Compile(directory string) (map[string]*Function, error) { diff --git a/src/compiler/Finalize.go b/src/build/Finalize.go similarity index 51% rename from src/compiler/Finalize.go rename to src/build/Finalize.go index 56095f4..413ce71 100644 --- a/src/compiler/Finalize.go +++ b/src/build/Finalize.go @@ -1,9 +1,7 @@ -package compiler +package build import ( - "git.akyoto.dev/cli/q/src/arch/x64" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/os/linux" + "git.akyoto.dev/cli/q/src/build/asm" ) // Finalize generates the final machine code. @@ -14,10 +12,6 @@ func Finalize(functions map[string]*Function) ([]byte, []byte) { a.Merge(&f.Assembler) } - a.MoveRegisterNumber(x64.SyscallNumber, linux.Exit) - a.MoveRegisterNumber(x64.SyscallArgs[0], 0) - a.Syscall() - code, data := a.Finalize() return code, data } diff --git a/src/build/Function.go b/src/build/Function.go new file mode 100644 index 0000000..11c84ed --- /dev/null +++ b/src/build/Function.go @@ -0,0 +1,96 @@ +package build + +import ( + "fmt" + "strconv" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/go/color/ansi" +) + +// Function represents a function. +type Function struct { + Name string + Head token.List + Body token.List + Assembler asm.Assembler +} + +// Compile turns a function into machine code. +func (f *Function) Compile() { + if config.Verbose { + ansi.Underline.Println(f.Name) + } + + for _, line := range f.Lines() { + if config.Verbose { + fmt.Println("[line]", line) + } + + if len(line) == 0 { + continue + } + + if line[0].Kind == token.Identifier && line[0].Text() == "syscall" { + paramTokens := line[2 : len(line)-1] + start := 0 + i := 0 + var parameters []token.List + + for i < len(paramTokens) { + if paramTokens[i].Kind == token.Separator { + parameters = append(parameters, paramTokens[start:i]) + start = i + 1 + } + + i++ + } + + if i != start { + parameters = append(parameters, paramTokens[start:i]) + } + + for i, list := range parameters { + if list[0].Kind == token.Number { + numAsText := list[0].Text() + n, _ := strconv.Atoi(numAsText) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + } + } + + f.Assembler.Syscall() + } + } +} + +// Lines returns the lines in the function body. +func (f *Function) Lines() []token.List { + var ( + lines []token.List + start = 0 + i = 0 + ) + + for i < len(f.Body) { + if f.Body[i].Kind == token.NewLine { + lines = append(lines, f.Body[start:i]) + start = i + 1 + } + + i++ + } + + if i != start { + lines = append(lines, f.Body[start:i]) + } + + return lines +} + +// String returns the function name. +func (f *Function) String() string { + return f.Name +} diff --git a/src/compiler/Scan.go b/src/build/Scan.go similarity index 94% rename from src/compiler/Scan.go rename to src/build/Scan.go index d0ef625..ae24220 100644 --- a/src/compiler/Scan.go +++ b/src/build/Scan.go @@ -1,4 +1,4 @@ -package compiler +package build import ( "os" @@ -6,8 +6,8 @@ import ( "strings" "sync" - "git.akyoto.dev/cli/q/src/directory" - "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/build/directory" + "git.akyoto.dev/cli/q/src/build/token" ) // Scan scans the directory. diff --git a/src/build/Write.go b/src/build/Write.go new file mode 100644 index 0000000..f6385bb --- /dev/null +++ b/src/build/Write.go @@ -0,0 +1,29 @@ +package build + +import ( + "bufio" + "os" + + "git.akyoto.dev/cli/q/src/build/elf" +) + +// Write writes the executable file to disk. +func Write(filePath string, code []byte, data []byte) error { + file, err := os.Create(filePath) + + if err != nil { + return err + } + + buffer := bufio.NewWriter(file) + executable := elf.New(code, data) + executable.Write(buffer) + buffer.Flush() + err = file.Close() + + if err != nil { + return err + } + + return os.Chmod(filePath, 0755) +} diff --git a/src/build/arch/arm64/Syscall.go b/src/build/arch/arm64/Syscall.go new file mode 100644 index 0000000..fb6495b --- /dev/null +++ b/src/build/arch/arm64/Syscall.go @@ -0,0 +1,9 @@ +package register + +import "git.akyoto.dev/cli/q/src/build/cpu" + +const ( + SyscallReturn = 0 +) + +var SyscallArgs = []cpu.Register{8, 0, 1, 2, 3, 4, 5} diff --git a/src/arch/x64/Call.go b/src/build/arch/x64/Call.go similarity index 100% rename from src/arch/x64/Call.go rename to src/build/arch/x64/Call.go diff --git a/src/arch/x64/Move.go b/src/build/arch/x64/Move.go similarity index 100% rename from src/arch/x64/Move.go rename to src/build/arch/x64/Move.go diff --git a/src/arch/x64/Return.go b/src/build/arch/x64/Return.go similarity index 100% rename from src/arch/x64/Return.go rename to src/build/arch/x64/Return.go diff --git a/src/build/arch/x64/Syscall.go b/src/build/arch/x64/Syscall.go new file mode 100644 index 0000000..6251de9 --- /dev/null +++ b/src/build/arch/x64/Syscall.go @@ -0,0 +1,14 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +const ( + SyscallReturn = 0 // rax +) + +var SyscallArgs = []cpu.Register{0, 7, 6, 2, 10, 8, 9} + +// Syscall is the primary way to communicate with the OS kernel. +func Syscall(code []byte) []byte { + return append(code, 0x0f, 0x05) +} diff --git a/src/arch/x64/x64_test.go b/src/build/arch/x64/x64_test.go similarity index 91% rename from src/arch/x64/x64_test.go rename to src/build/arch/x64/x64_test.go index 8fb7c21..b39570d 100644 --- a/src/arch/x64/x64_test.go +++ b/src/build/arch/x64/x64_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go new file mode 100644 index 0000000..94a4ce7 --- /dev/null +++ b/src/build/asm/Assembler.go @@ -0,0 +1,66 @@ +package asm + +import ( + "encoding/binary" + "fmt" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/config" +) + +// Assembler contains a list of instructions. +type Assembler struct { + Instructions []Instruction +} + +// New creates a new assembler. +func New() *Assembler { + return &Assembler{ + Instructions: make([]Instruction, 0, 8), + } +} + +// Finalize generates the final machine code. +func (a *Assembler) Finalize() ([]byte, []byte) { + code := make([]byte, 0, len(a.Instructions)*8) + data := make([]byte, 0, 16) + pointers := []Pointer{} + + for _, x := range a.Instructions { + switch x.Mnemonic { + case MOVE: + code = x64.MoveRegNum32(code, uint8(x.Data.(RegisterNumber).Register), uint32(x.Data.(RegisterNumber).Number)) + + if x.Data.(RegisterNumber).IsPointer { + pointers = append(pointers, Pointer{ + Position: Address(len(code) - 4), + Address: Address(x.Data.(RegisterNumber).Number), + }) + } + + case SYSCALL: + code = x64.Syscall(code) + } + } + + if config.Verbose { + for _, x := range a.Instructions { + fmt.Println("[asm]", x.String()) + } + } + + dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) + + for _, pointer := range pointers { + slice := code[pointer.Position : pointer.Position+4] + address := dataStart + pointer.Address + binary.LittleEndian.PutUint32(slice, address) + } + + return code, data +} + +// Merge combines the contents of this assembler with another one. +func (a *Assembler) Merge(b *Assembler) { + a.Instructions = append(a.Instructions, b.Instructions...) +} diff --git a/src/build/asm/Instruction.go b/src/build/asm/Instruction.go new file mode 100644 index 0000000..966ce75 --- /dev/null +++ b/src/build/asm/Instruction.go @@ -0,0 +1,19 @@ +package asm + +import "fmt" + +// Instruction represents a single instruction which can be converted to machine code. +type Instruction struct { + Mnemonic Mnemonic + Data interface{} +} + +// String returns a human readable version. +func (x *Instruction) String() string { + switch data := x.Data.(type) { + case RegisterNumber: + return fmt.Sprintf("%s %s, %x", x.Mnemonic, data.Register, data.Number) + default: + return x.Mnemonic.String() + } +} diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go new file mode 100644 index 0000000..0cd9014 --- /dev/null +++ b/src/build/asm/Instructions.go @@ -0,0 +1,32 @@ +package asm + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// MoveRegisterNumber moves a number into the given register. +func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: MOVE, + Data: RegisterNumber{ + Register: reg, + Number: number, + IsPointer: false, + }, + }) +} + +// MoveRegisterAddress moves an address into the given register. +func (a *Assembler) MoveRegisterAddress(reg cpu.Register, address Address) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: MOVE, + Data: RegisterNumber{ + Register: reg, + Number: uint64(address), + IsPointer: true, + }, + }) +} + +// Syscall executes a kernel function. +func (a *Assembler) Syscall() { + a.Instructions = append(a.Instructions, Instruction{Mnemonic: SYSCALL}) +} diff --git a/src/asm/Mnemonic.go b/src/build/asm/Mnemonic.go similarity index 69% rename from src/asm/Mnemonic.go rename to src/build/asm/Mnemonic.go index 3d3a16c..cd7f033 100644 --- a/src/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -4,18 +4,15 @@ type Mnemonic uint8 const ( NONE Mnemonic = iota - MOV - MOVDATA + MOVE SYSCALL ) +// String returns a human readable version. func (m Mnemonic) String() string { switch m { - case MOV: - return "mov" - - case MOVDATA: - return "mov" + case MOVE: + return "move" case SYSCALL: return "syscall" diff --git a/src/asm/Pointer.go b/src/build/asm/Pointer.go similarity index 81% rename from src/asm/Pointer.go rename to src/build/asm/Pointer.go index 5d13b4f..7063ec5 100644 --- a/src/asm/Pointer.go +++ b/src/build/asm/Pointer.go @@ -1,5 +1,8 @@ package asm +// Address represents a memory address. +type Address = uint32 + // Pointer stores a relative memory address that we can later turn into an absolute one. // Position: The machine code offset where the address was inserted. // Address: The offset inside the section. diff --git a/src/build/asm/RegisterNumber.go b/src/build/asm/RegisterNumber.go new file mode 100644 index 0000000..445acd1 --- /dev/null +++ b/src/build/asm/RegisterNumber.go @@ -0,0 +1,10 @@ +package asm + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// RegisterNumber operates with a register and a number. +type RegisterNumber struct { + Register cpu.Register + Number uint64 + IsPointer bool +} diff --git a/src/config/config.go b/src/build/config/config.go similarity index 100% rename from src/config/config.go rename to src/build/config/config.go diff --git a/src/build/cpu/Register.go b/src/build/cpu/Register.go new file mode 100644 index 0000000..006fc68 --- /dev/null +++ b/src/build/cpu/Register.go @@ -0,0 +1,11 @@ +package cpu + +import "fmt" + +// Register represents the number of the register. +type Register uint8 + +// String returns the human readable name of the register. +func (r Register) String() string { + return fmt.Sprintf("r%d", r) +} diff --git a/src/directory/Walk.go b/src/build/directory/Walk.go similarity index 100% rename from src/directory/Walk.go rename to src/build/directory/Walk.go diff --git a/src/directory/Walk_test.go b/src/build/directory/Walk_test.go similarity index 90% rename from src/directory/Walk_test.go rename to src/build/directory/Walk_test.go index f3f64cf..00c663f 100644 --- a/src/directory/Walk_test.go +++ b/src/build/directory/Walk_test.go @@ -3,7 +3,7 @@ package directory_test import ( "testing" - "git.akyoto.dev/cli/q/src/directory" + "git.akyoto.dev/cli/q/src/build/directory" "git.akyoto.dev/go/assert" ) diff --git a/src/elf/ELF.go b/src/build/elf/ELF.go similarity index 97% rename from src/elf/ELF.go rename to src/build/elf/ELF.go index 92d45c6..f284dbc 100644 --- a/src/elf/ELF.go +++ b/src/build/elf/ELF.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "io" - "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/build/config" ) // ELF represents an ELF file. diff --git a/src/elf/ELF_test.go b/src/build/elf/ELF_test.go similarity index 77% rename from src/elf/ELF_test.go rename to src/build/elf/ELF_test.go index 45a9aa2..b609d60 100644 --- a/src/elf/ELF_test.go +++ b/src/build/elf/ELF_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/build/elf" ) func TestELF(t *testing.T) { diff --git a/src/elf/Header.go b/src/build/elf/Header.go similarity index 100% rename from src/elf/Header.go rename to src/build/elf/Header.go diff --git a/src/elf/ProgramHeader.go b/src/build/elf/ProgramHeader.go similarity index 100% rename from src/elf/ProgramHeader.go rename to src/build/elf/ProgramHeader.go diff --git a/src/elf/SectionHeader.go b/src/build/elf/SectionHeader.go similarity index 100% rename from src/elf/SectionHeader.go rename to src/build/elf/SectionHeader.go diff --git a/src/elf/elf.md b/src/build/elf/elf.md similarity index 100% rename from src/elf/elf.md rename to src/build/elf/elf.md diff --git a/src/os/linux/Syscall.go b/src/build/os/linux/Syscall.go similarity index 100% rename from src/os/linux/Syscall.go rename to src/build/os/linux/Syscall.go diff --git a/src/build/output/Compiler.go b/src/build/output/Compiler.go new file mode 100644 index 0000000..dbbff06 --- /dev/null +++ b/src/build/output/Compiler.go @@ -0,0 +1,27 @@ +package output + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Compiler implements the arch.Output interface. +type Compiler struct{} + +// Compile turns a function into machine code. +func (c Compiler) Compile(f *build.Function) { + for i, t := range f.Body { + if t.Kind == token.Identifier && t.Text() == "print" { + // message := f.Body[i+2].Bytes + // f.Assembler.MoveRegisterNumber(x64.SyscallNumber, linux.Write) + // f.Assembler.MoveRegisterNumber(x64.SyscallArgs[0], 1) + // f.Assembler.MoveRegisterData(x64.SyscallArgs[1], message) + // f.Assembler.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(message))) + // f.Assembler.Syscall() + message := f.Body[i+2].Bytes + fmt.Println(message) + } + } +} diff --git a/src/token/Keywords.go b/src/build/token/Keywords.go similarity index 100% rename from src/token/Keywords.go rename to src/build/token/Keywords.go diff --git a/src/token/Kind.go b/src/build/token/Kind.go similarity index 100% rename from src/token/Kind.go rename to src/build/token/Kind.go diff --git a/src/token/List.go b/src/build/token/List.go similarity index 100% rename from src/token/List.go rename to src/build/token/List.go diff --git a/src/token/Token.go b/src/build/token/Token.go similarity index 100% rename from src/token/Token.go rename to src/build/token/Token.go diff --git a/src/token/Token_test.go b/src/build/token/Token_test.go similarity index 92% rename from src/token/Token_test.go rename to src/build/token/Token_test.go index 8bcac13..1bc125e 100644 --- a/src/token/Token_test.go +++ b/src/build/token/Token_test.go @@ -3,7 +3,7 @@ package token_test import ( "testing" - "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/go/assert" ) @@ -96,6 +96,22 @@ func TestNewline(t *testing.T) { }) } +func TestNumber(t *testing.T) { + tokens := token.Tokenize([]byte(`123 -456`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Number, + Bytes: []byte("123"), + Position: 0, + }, + { + Kind: token.Number, + Bytes: []byte("-456"), + Position: 4, + }, + }) +} + func TestSeparator(t *testing.T) { tokens := token.Tokenize([]byte("a,b,c")) assert.DeepEqual(t, tokens, token.List{ diff --git a/src/token/Tokenize.go b/src/build/token/Tokenize.go similarity index 78% rename from src/token/Tokenize.go rename to src/build/token/Tokenize.go index 4692791..d0402e3 100644 --- a/src/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -96,7 +96,25 @@ func Tokenize(buffer []byte) List { } tokens = append(tokens, token) - i-- + continue + } + + // Numbers + if isNumberStart(buffer[i]) { + position := i + i++ + + for i < len(buffer) && isNumber(buffer[i]) { + i++ + } + + tokens = append(tokens, Token{ + Number, + position, + buffer[position:i], + }) + + continue } } @@ -106,10 +124,22 @@ func Tokenize(buffer []byte) List { return tokens } -func isIdentifierStart(c byte) bool { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' +func isIdentifier(c byte) bool { + return isLetter(c) || isNumber(c) || c == '_' } -func isIdentifier(c byte) bool { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (c >= '0' && c <= '9') +func isIdentifierStart(c byte) bool { + return isLetter(c) || c == '_' +} + +func isLetter(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') +} + +func isNumber(c byte) bool { + return (c >= '0' && c <= '9') +} + +func isNumberStart(c byte) bool { + return isNumber(c) || c == '-' } diff --git a/src/cli/Build.go b/src/cli/Build.go index 2974257..2eb6cf2 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -1,11 +1,11 @@ package cli import ( + "fmt" "strings" "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/log" + "git.akyoto.dev/cli/q/src/build/config" ) // Build builds an executable. @@ -22,7 +22,7 @@ func Build(args []string) int { default: if strings.HasPrefix(args[i], "-") { - log.Error.Printf("Unknown parameter: %s\n", args[i]) + fmt.Printf("Unknown parameter: %s\n", args[i]) return 2 } @@ -33,7 +33,7 @@ func Build(args []string) int { err := b.Run() if err != nil { - log.Error.Println(err) + fmt.Println(err) return 1 } diff --git a/src/cli/Help.go b/src/cli/Help.go index 35fa8cd..6ca3b3c 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -1,14 +1,12 @@ package cli -import ( - "git.akyoto.dev/cli/q/src/log" -) +import "fmt" // Help shows the command line argument usage. func Help(args []string) int { - log.Error.Println("Usage: q [command] [options]") - log.Error.Println("") - log.Error.Println(" build [directory]") - log.Error.Println(" system") + fmt.Println("Usage: q [command] [options]") + fmt.Println("") + fmt.Println(" build [directory]") + fmt.Println(" system") return 2 } diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index 95fcf55..f5ae0a4 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -2,21 +2,13 @@ package cli_test import ( "fmt" - "io" "os" "testing" "git.akyoto.dev/cli/q/src/cli" - "git.akyoto.dev/cli/q/src/log" "git.akyoto.dev/go/assert" ) -func TestMain(m *testing.M) { - log.Info.SetOutput(io.Discard) - log.Error.SetOutput(io.Discard) - os.Exit(m.Run()) -} - func TestCLI(t *testing.T) { type cliTest struct { arguments []string diff --git a/src/cli/System.go b/src/cli/System.go index d76f565..831576c 100644 --- a/src/cli/System.go +++ b/src/cli/System.go @@ -1,36 +1,35 @@ package cli import ( + "fmt" "os" "runtime" - - "git.akyoto.dev/cli/q/src/log" ) // System shows system information. func System(args []string) int { line := "%-19s%s\n" - log.Info.Printf(line, "Platform:", runtime.GOOS) - log.Info.Printf(line, "Architecture:", runtime.GOARCH) - log.Info.Printf(line, "Go:", runtime.Version()) + fmt.Printf(line, "Platform:", runtime.GOOS) + fmt.Printf(line, "Architecture:", runtime.GOARCH) + fmt.Printf(line, "Go:", runtime.Version()) // Directory directory, err := os.Getwd() if err == nil { - log.Info.Printf(line, "Directory:", directory) + fmt.Printf(line, "Directory:", directory) } else { - log.Info.Printf(line, "Directory:", err.Error()) + fmt.Printf(line, "Directory:", err.Error()) } // Compiler executable, err := os.Executable() if err == nil { - log.Info.Printf(line, "Compiler:", executable) + fmt.Printf(line, "Compiler:", executable) } else { - log.Info.Printf(line, "Compiler:", err.Error()) + fmt.Printf(line, "Compiler:", err.Error()) } return 0 diff --git a/src/compiler/Function.go b/src/compiler/Function.go deleted file mode 100644 index d9e65a3..0000000 --- a/src/compiler/Function.go +++ /dev/null @@ -1,35 +0,0 @@ -package compiler - -import ( - "git.akyoto.dev/cli/q/src/arch/x64" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/os/linux" - "git.akyoto.dev/cli/q/src/token" -) - -// Function represents a function. -type Function struct { - Name string - Head token.List - Body token.List - Assembler asm.Assembler -} - -// Compile turns a function into machine code. -func (f *Function) Compile() { - for i, t := range f.Body { - if t.Kind == token.Identifier && t.Text() == "print" { - message := f.Body[i+2].Bytes - f.Assembler.MoveRegisterNumber(x64.SyscallNumber, linux.Write) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[0], 1) - f.Assembler.MoveRegisterData(x64.SyscallArgs[1], message) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(message))) - f.Assembler.Syscall() - } - } -} - -// String returns the function name. -func (f *Function) String() string { - return f.Name -} diff --git a/src/cpu/CPU.go b/src/cpu/CPU.go deleted file mode 100644 index ea16af0..0000000 --- a/src/cpu/CPU.go +++ /dev/null @@ -1,106 +0,0 @@ -package cpu - -import "git.akyoto.dev/cli/q/src/register" - -// CPU manages the allocation state of registers. -type CPU struct { - All List - General List - Call List - Syscall List -} - -// New creates a new CPU state. -func New() *CPU { - // Rather than doing lots of mini allocations - // we'll allocate memory for all registers at once. - registers := [16]Register{ - {ID: register.R0}, - {ID: register.R1}, - {ID: register.R2}, - {ID: register.R3}, - {ID: register.R4}, - {ID: register.R5}, - {ID: register.R6}, - {ID: register.R7}, - {ID: register.R8}, - {ID: register.R9}, - {ID: register.R10}, - {ID: register.R11}, - {ID: register.R12}, - {ID: register.R13}, - {ID: register.R14}, - {ID: register.R15}, - } - - rax := ®isters[0] - rcx := ®isters[1] - rdx := ®isters[2] - rbx := ®isters[3] - rsp := ®isters[4] - rbp := ®isters[5] - rsi := ®isters[6] - rdi := ®isters[7] - r8 := ®isters[8] - r9 := ®isters[9] - r10 := ®isters[10] - r11 := ®isters[11] - r12 := ®isters[12] - r13 := ®isters[13] - r14 := ®isters[14] - r15 := ®isters[15] - - // Register configuration - return &CPU{ - All: List{ - rax, - rcx, - rdx, - rbx, - rsp, - rbp, - rsi, - rdi, - r8, - r9, - r10, - r11, - r12, - r13, - r14, - r15, - }, - General: List{ - rcx, - rbx, - rbp, - r11, - r12, - r13, - r14, - r15, - }, - Call: List{ - rdi, - rsi, - rdx, - r10, - r8, - r9, - }, - Syscall: List{ - rax, - rdi, - rsi, - rdx, - r10, - r8, - r9, - }, - } -} - -// ByID returns the register with the given ID. -func (cpu *CPU) ByID(id register.ID) *Register { - return cpu.All[id] -} diff --git a/src/cpu/List.go b/src/cpu/List.go deleted file mode 100644 index e9234aa..0000000 --- a/src/cpu/List.go +++ /dev/null @@ -1,29 +0,0 @@ -package cpu - -// List is a list of registers. -type List []*Register - -// FindFree tries to find a free register -// and returns nil when all are currently occupied. -func (registers List) FindFree() *Register { - for _, register := range registers { - if register.IsFree() { - return register - } - } - - return nil -} - -// InUse returns a list of registers that are currently in use. -func (registers List) InUse() List { - var inUse List - - for _, register := range registers { - if !register.IsFree() { - inUse = append(inUse, register) - } - } - - return inUse -} diff --git a/src/cpu/Register.go b/src/cpu/Register.go deleted file mode 100644 index b11add9..0000000 --- a/src/cpu/Register.go +++ /dev/null @@ -1,39 +0,0 @@ -package cpu - -import ( - "fmt" - - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/register" -) - -// Register represents a single CPU register. -type Register struct { - ID register.ID - user fmt.Stringer -} - -// Use marks the register as used by the given object. -func (register *Register) Use(obj fmt.Stringer) error { - if register.user != nil { - return &errors.RegisterInUse{Register: register.ID.String(), User: register.user.String()} - } - - register.user = obj - return nil -} - -// Free frees the register so that it can be used for new calculations. -func (register *Register) Free() { - register.user = nil -} - -// IsFree returns true if the register is not in use. -func (register *Register) IsFree() bool { - return register.user == nil -} - -// String returns a human-readable representation of the register. -func (register *Register) String() string { - return fmt.Sprintf("%s%s%v", register.ID, "=", register.user) -} diff --git a/src/errors/InvalidDirectory.go b/src/errors/InvalidDirectory.go deleted file mode 100644 index 90beadd..0000000 --- a/src/errors/InvalidDirectory.go +++ /dev/null @@ -1,17 +0,0 @@ -package errors - -import "fmt" - -// InvalidDirectory errors are returned when the specified path is not a directory. -type InvalidDirectory struct { - Path string -} - -// Error implements the text representation. -func (err *InvalidDirectory) Error() string { - if err.Path == "" { - return "Invalid directory" - } - - return fmt.Sprintf("Invalid directory '%s'", err.Path) -} diff --git a/src/errors/RegisterInUse.go b/src/errors/RegisterInUse.go deleted file mode 100644 index 27f40a5..0000000 --- a/src/errors/RegisterInUse.go +++ /dev/null @@ -1,14 +0,0 @@ -package errors - -import "fmt" - -// RegisterInUse errors are returned when a register is already in use. -type RegisterInUse struct { - Register string - User string -} - -// Error implements the text representation. -func (err *RegisterInUse) Error() string { - return fmt.Sprintf("Register '%s' already used by '%s'", err.Register, err.User) -} diff --git a/src/log/log.go b/src/log/log.go deleted file mode 100644 index 395f6ab..0000000 --- a/src/log/log.go +++ /dev/null @@ -1,14 +0,0 @@ -package log - -import ( - "log" - "os" -) - -var ( - // Info is used for general info messages. - Info = log.New(os.Stdout, "", 0) - - // Error is used for error messages. - Error = log.New(os.Stderr, "", 0) -) diff --git a/src/register/ID.go b/src/register/ID.go deleted file mode 100644 index bc2fe1c..0000000 --- a/src/register/ID.go +++ /dev/null @@ -1,44 +0,0 @@ -package register - -import "fmt" - -// ID represents the number of the register. -type ID uint8 - -const ( - R0 ID = iota - R1 - R2 - R3 - R4 - R5 - R6 - R7 - R8 - R9 - R10 - R11 - R12 - R13 - R14 - R15 - R16 - R17 - R18 - R19 - R20 - R21 - R22 - R23 - R24 - R25 - R26 - R27 - R28 - R29 - R30 -) - -func (r ID) String() string { - return fmt.Sprintf("r%d", r) -} From 6fe30f31da7a8cd6f41d5285c640a23fee3a9db7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 10 Jun 2024 15:51:39 +0200 Subject: [PATCH 0062/1012] Reorganized file structure --- examples/hello/hello.q | 2 +- src/arch/arm64/Syscall.go | 17 ---- src/arch/x64/Syscall.go | 22 ----- src/asm/Address.go | 4 - src/asm/Assembler.go | 93 ---------------------- src/asm/Assembler_test.go | 41 ---------- src/asm/Data.go | 20 ----- src/asm/Instruction.go | 30 ------- src/build/Build.go | 34 +------- src/build/Build_test.go | 24 ++++++ src/{compiler => build}/Compile.go | 6 +- src/{compiler => build}/Finalize.go | 10 +-- src/build/Function.go | 96 ++++++++++++++++++++++ src/{compiler => build}/Scan.go | 6 +- src/build/Write.go | 29 +++++++ src/build/arch/arm64/Syscall.go | 9 +++ src/{ => build}/arch/x64/Call.go | 0 src/{ => build}/arch/x64/Move.go | 0 src/{ => build}/arch/x64/Return.go | 0 src/build/arch/x64/Syscall.go | 14 ++++ src/{ => build}/arch/x64/x64_test.go | 2 +- src/build/asm/Assembler.go | 66 +++++++++++++++ src/build/asm/Instruction.go | 19 +++++ src/build/asm/Instructions.go | 32 ++++++++ src/{ => build}/asm/Mnemonic.go | 11 +-- src/{ => build}/asm/Pointer.go | 3 + src/build/asm/RegisterNumber.go | 10 +++ src/{ => build}/config/config.go | 0 src/build/cpu/Register.go | 11 +++ src/{ => build}/directory/Walk.go | 0 src/{ => build}/directory/Walk_test.go | 2 +- src/{ => build}/elf/ELF.go | 2 +- src/{ => build}/elf/ELF_test.go | 2 +- src/{ => build}/elf/Header.go | 0 src/{ => build}/elf/ProgramHeader.go | 0 src/{ => build}/elf/SectionHeader.go | 0 src/{ => build}/elf/elf.md | 0 src/{ => build}/os/linux/Syscall.go | 0 src/build/output/Compiler.go | 27 +++++++ src/{ => build}/token/Keywords.go | 0 src/{ => build}/token/Kind.go | 0 src/{ => build}/token/List.go | 0 src/{ => build}/token/Token.go | 0 src/{ => build}/token/Token_test.go | 18 ++++- src/{ => build}/token/Tokenize.go | 40 ++++++++-- src/cli/Build.go | 8 +- src/cli/Help.go | 12 ++- src/cli/Main_test.go | 8 -- src/cli/System.go | 17 ++-- src/compiler/Function.go | 35 -------- src/cpu/CPU.go | 106 ------------------------- src/cpu/List.go | 29 ------- src/cpu/Register.go | 39 --------- src/errors/InvalidDirectory.go | 17 ---- src/errors/RegisterInUse.go | 14 ---- src/log/log.go | 14 ---- src/register/ID.go | 44 ---------- 57 files changed, 431 insertions(+), 614 deletions(-) delete mode 100644 src/arch/arm64/Syscall.go delete mode 100644 src/arch/x64/Syscall.go delete mode 100644 src/asm/Address.go delete mode 100644 src/asm/Assembler.go delete mode 100644 src/asm/Assembler_test.go delete mode 100644 src/asm/Data.go delete mode 100644 src/asm/Instruction.go create mode 100644 src/build/Build_test.go rename src/{compiler => build}/Compile.go (94%) rename src/{compiler => build}/Finalize.go (51%) create mode 100644 src/build/Function.go rename src/{compiler => build}/Scan.go (94%) create mode 100644 src/build/Write.go create mode 100644 src/build/arch/arm64/Syscall.go rename src/{ => build}/arch/x64/Call.go (100%) rename src/{ => build}/arch/x64/Move.go (100%) rename src/{ => build}/arch/x64/Return.go (100%) create mode 100644 src/build/arch/x64/Syscall.go rename src/{ => build}/arch/x64/x64_test.go (91%) create mode 100644 src/build/asm/Assembler.go create mode 100644 src/build/asm/Instruction.go create mode 100644 src/build/asm/Instructions.go rename src/{ => build}/asm/Mnemonic.go (69%) rename src/{ => build}/asm/Pointer.go (81%) create mode 100644 src/build/asm/RegisterNumber.go rename src/{ => build}/config/config.go (100%) create mode 100644 src/build/cpu/Register.go rename src/{ => build}/directory/Walk.go (100%) rename src/{ => build}/directory/Walk_test.go (90%) rename src/{ => build}/elf/ELF.go (97%) rename src/{ => build}/elf/ELF_test.go (77%) rename src/{ => build}/elf/Header.go (100%) rename src/{ => build}/elf/ProgramHeader.go (100%) rename src/{ => build}/elf/SectionHeader.go (100%) rename src/{ => build}/elf/elf.md (100%) rename src/{ => build}/os/linux/Syscall.go (100%) create mode 100644 src/build/output/Compiler.go rename src/{ => build}/token/Keywords.go (100%) rename src/{ => build}/token/Kind.go (100%) rename src/{ => build}/token/List.go (100%) rename src/{ => build}/token/Token.go (100%) rename src/{ => build}/token/Token_test.go (92%) rename src/{ => build}/token/Tokenize.go (78%) delete mode 100644 src/compiler/Function.go delete mode 100644 src/cpu/CPU.go delete mode 100644 src/cpu/List.go delete mode 100644 src/cpu/Register.go delete mode 100644 src/errors/InvalidDirectory.go delete mode 100644 src/errors/RegisterInUse.go delete mode 100644 src/log/log.go delete mode 100644 src/register/ID.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 4f723f0..76d8d21 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,3 +1,3 @@ main() { - print("Hello") + syscall(60, 0) } diff --git a/src/arch/arm64/Syscall.go b/src/arch/arm64/Syscall.go deleted file mode 100644 index 37e0b9e..0000000 --- a/src/arch/arm64/Syscall.go +++ /dev/null @@ -1,17 +0,0 @@ -package register - -import "git.akyoto.dev/cli/q/src/register" - -const ( - SyscallNumber = register.R8 - SyscallReturn = register.R0 -) - -var SyscallArgs = []register.ID{ - register.R0, - register.R1, - register.R2, - register.R3, - register.R4, - register.R5, -} diff --git a/src/arch/x64/Syscall.go b/src/arch/x64/Syscall.go deleted file mode 100644 index 132e560..0000000 --- a/src/arch/x64/Syscall.go +++ /dev/null @@ -1,22 +0,0 @@ -package x64 - -import "git.akyoto.dev/cli/q/src/register" - -const ( - SyscallNumber = register.R0 // rax - SyscallReturn = register.R0 // rax -) - -var SyscallArgs = []register.ID{ - register.R7, // rdi - register.R6, // rsi - register.R2, // rdx - register.R10, - register.R8, - register.R9, -} - -// Syscall is the primary way to communicate with the OS kernel. -func Syscall(code []byte) []byte { - return append(code, 0x0f, 0x05) -} diff --git a/src/asm/Address.go b/src/asm/Address.go deleted file mode 100644 index 14b073b..0000000 --- a/src/asm/Address.go +++ /dev/null @@ -1,4 +0,0 @@ -package asm - -// Address represents a memory address. -type Address = uint32 diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go deleted file mode 100644 index 5de2ff9..0000000 --- a/src/asm/Assembler.go +++ /dev/null @@ -1,93 +0,0 @@ -package asm - -import ( - "encoding/binary" - - "git.akyoto.dev/cli/q/src/arch/x64" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/log" - "git.akyoto.dev/cli/q/src/register" -) - -// Assembler contains a list of instructions. -type Assembler struct { - Instructions []Instruction -} - -// New creates a new assembler. -func New() *Assembler { - return &Assembler{ - Instructions: make([]Instruction, 0, 8), - } -} - -// Finalize generates the final machine code. -func (a *Assembler) Finalize() ([]byte, []byte) { - code := make([]byte, 0, len(a.Instructions)*8) - data := make(Data, 0, 16) - pointers := []Pointer{} - - for _, x := range a.Instructions { - switch x.Mnemonic { - case MOV: - code = x64.MoveRegNum32(code, uint8(x.Destination), uint32(x.Number)) - - case MOVDATA: - code = x64.MoveRegNum32(code, uint8(x.Destination), 0) - - pointers = append(pointers, Pointer{ - Position: Address(len(code) - 4), - Address: data.Add(x.Data), - }) - - case SYSCALL: - code = x64.Syscall(code) - } - } - - if config.Verbose { - for _, x := range a.Instructions { - log.Info.Println(x.String()) - } - } - - dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) - - for _, pointer := range pointers { - slice := code[pointer.Position : pointer.Position+4] - address := dataStart + pointer.Address - binary.LittleEndian.PutUint32(slice, address) - } - - return code, data -} - -// Merge combines the contents of this assembler with another one. -func (a *Assembler) Merge(b *Assembler) { - a.Instructions = append(a.Instructions, b.Instructions...) -} - -// MoveRegisterData moves a data section address into the given register. -func (a *Assembler) MoveRegisterData(reg register.ID, data []byte) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: MOVDATA, - Destination: reg, - Data: data, - }) -} - -// MoveRegisterNumber moves a number into the given register. -func (a *Assembler) MoveRegisterNumber(reg register.ID, number uint64) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: MOV, - Destination: reg, - Number: number, - }) -} - -// Syscall executes a kernel function. -func (a *Assembler) Syscall() { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: SYSCALL, - }) -} diff --git a/src/asm/Assembler_test.go b/src/asm/Assembler_test.go deleted file mode 100644 index ed47f32..0000000 --- a/src/asm/Assembler_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package asm_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/arch/x64" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/os/linux" - "git.akyoto.dev/go/assert" -) - -func TestHello(t *testing.T) { - a := asm.New() - - hello := []byte("Hello\n") - a.MoveRegisterNumber(x64.SyscallNumber, linux.Write) - a.MoveRegisterNumber(x64.SyscallArgs[0], 1) - a.MoveRegisterData(x64.SyscallArgs[1], hello) - a.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(hello))) - a.Syscall() - - a.MoveRegisterNumber(x64.SyscallNumber, linux.Exit) - a.MoveRegisterNumber(x64.SyscallArgs[0], 0) - a.Syscall() - - code, data := a.Finalize() - - assert.DeepEqual(t, code, []byte{ - 0xb8, 0x01, 0x00, 0x00, 0x00, - 0xbf, 0x01, 0x00, 0x00, 0x00, - 0xbe, 0xa2, 0x00, 0x40, 0x00, - 0xba, 0x06, 0x00, 0x00, 0x00, - 0x0f, 0x05, - - 0xb8, 0x3c, 0x00, 0x00, 0x00, - 0xbf, 0x00, 0x00, 0x00, 0x00, - 0x0f, 0x05, - }) - - assert.DeepEqual(t, data, hello) -} diff --git a/src/asm/Data.go b/src/asm/Data.go deleted file mode 100644 index 418a898..0000000 --- a/src/asm/Data.go +++ /dev/null @@ -1,20 +0,0 @@ -package asm - -import "bytes" - -// Data represents the static read-only data. -type Data []byte - -// Add adds the given bytes to the data block if this sequence of bytes doesn't exist yet. -// It returns the address relative to the start of the data section. -func (data *Data) Add(block []byte) Address { - position := bytes.Index(*data, block) - - if position != -1 { - return Address(position) - } - - address := Address(len(*data)) - *data = append(*data, block...) - return address -} diff --git a/src/asm/Instruction.go b/src/asm/Instruction.go deleted file mode 100644 index 0c91ee1..0000000 --- a/src/asm/Instruction.go +++ /dev/null @@ -1,30 +0,0 @@ -package asm - -import ( - "fmt" - - "git.akyoto.dev/cli/q/src/register" -) - -// Instruction represents a single instruction which can be converted to machine code. -type Instruction struct { - Mnemonic Mnemonic - Source register.ID - Destination register.ID - Number uint64 - Data []byte -} - -// String returns the assembler representation of the instruction. -func (x *Instruction) String() string { - switch x.Mnemonic { - case MOV: - return fmt.Sprintf("%s %s, %x", x.Mnemonic, x.Destination, x.Number) - case MOVDATA: - return fmt.Sprintf("%s %s, %v", x.Mnemonic, x.Destination, x.Data) - case SYSCALL: - return x.Mnemonic.String() - default: - return "" - } -} diff --git a/src/build/Build.go b/src/build/Build.go index f73045a..c7029cb 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -1,12 +1,7 @@ package build import ( - "bufio" - "os" "path/filepath" - - "git.akyoto.dev/cli/q/src/compiler" - "git.akyoto.dev/cli/q/src/elf" ) // Build describes a compiler build. @@ -25,7 +20,7 @@ func New(directory string) *Build { // Run parses the input files and generates an executable file. func (build *Build) Run() error { - functions, err := compiler.Compile(build.Directory) + functions, err := Compile(build.Directory) if err != nil { return err @@ -35,33 +30,12 @@ func (build *Build) Run() error { return nil } - code, data := compiler.Finalize(functions) - return writeToDisk(build.Executable(), code, data) + path := build.Executable() + code, data := Finalize(functions) + return Write(path, code, data) } // Executable returns the path to the executable. func (build *Build) Executable() string { return filepath.Join(build.Directory, filepath.Base(build.Directory)) } - -// writeToDisk writes the executable file to disk. -func writeToDisk(filePath string, code []byte, data []byte) error { - file, err := os.Create(filePath) - - if err != nil { - return err - } - - buffer := bufio.NewWriter(file) - executable := elf.New(code, data) - executable.Write(buffer) - buffer.Flush() - - err = file.Close() - - if err != nil { - return err - } - - return os.Chmod(filePath, 0755) -} diff --git a/src/build/Build_test.go b/src/build/Build_test.go new file mode 100644 index 0000000..7c91f0b --- /dev/null +++ b/src/build/Build_test.go @@ -0,0 +1,24 @@ +package build_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/go/assert" +) + +func TestBuild(t *testing.T) { + b := build.New("../../examples/hello") + assert.Nil(t, b.Run()) +} + +func TestSkipExecutable(t *testing.T) { + b := build.New("../../examples/hello") + b.WriteExecutable = false + assert.Nil(t, b.Run()) +} + +func TestNonExisting(t *testing.T) { + b := build.New("does-not-exist") + assert.NotNil(t, b.Run()) +} diff --git a/src/compiler/Compile.go b/src/build/Compile.go similarity index 94% rename from src/compiler/Compile.go rename to src/build/Compile.go index 5311d01..1d1ffc1 100644 --- a/src/compiler/Compile.go +++ b/src/build/Compile.go @@ -1,6 +1,8 @@ -package compiler +package build -import "sync" +import ( + "sync" +) // Compile compiles all the functions. func Compile(directory string) (map[string]*Function, error) { diff --git a/src/compiler/Finalize.go b/src/build/Finalize.go similarity index 51% rename from src/compiler/Finalize.go rename to src/build/Finalize.go index 56095f4..413ce71 100644 --- a/src/compiler/Finalize.go +++ b/src/build/Finalize.go @@ -1,9 +1,7 @@ -package compiler +package build import ( - "git.akyoto.dev/cli/q/src/arch/x64" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/os/linux" + "git.akyoto.dev/cli/q/src/build/asm" ) // Finalize generates the final machine code. @@ -14,10 +12,6 @@ func Finalize(functions map[string]*Function) ([]byte, []byte) { a.Merge(&f.Assembler) } - a.MoveRegisterNumber(x64.SyscallNumber, linux.Exit) - a.MoveRegisterNumber(x64.SyscallArgs[0], 0) - a.Syscall() - code, data := a.Finalize() return code, data } diff --git a/src/build/Function.go b/src/build/Function.go new file mode 100644 index 0000000..11c84ed --- /dev/null +++ b/src/build/Function.go @@ -0,0 +1,96 @@ +package build + +import ( + "fmt" + "strconv" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/go/color/ansi" +) + +// Function represents a function. +type Function struct { + Name string + Head token.List + Body token.List + Assembler asm.Assembler +} + +// Compile turns a function into machine code. +func (f *Function) Compile() { + if config.Verbose { + ansi.Underline.Println(f.Name) + } + + for _, line := range f.Lines() { + if config.Verbose { + fmt.Println("[line]", line) + } + + if len(line) == 0 { + continue + } + + if line[0].Kind == token.Identifier && line[0].Text() == "syscall" { + paramTokens := line[2 : len(line)-1] + start := 0 + i := 0 + var parameters []token.List + + for i < len(paramTokens) { + if paramTokens[i].Kind == token.Separator { + parameters = append(parameters, paramTokens[start:i]) + start = i + 1 + } + + i++ + } + + if i != start { + parameters = append(parameters, paramTokens[start:i]) + } + + for i, list := range parameters { + if list[0].Kind == token.Number { + numAsText := list[0].Text() + n, _ := strconv.Atoi(numAsText) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + } + } + + f.Assembler.Syscall() + } + } +} + +// Lines returns the lines in the function body. +func (f *Function) Lines() []token.List { + var ( + lines []token.List + start = 0 + i = 0 + ) + + for i < len(f.Body) { + if f.Body[i].Kind == token.NewLine { + lines = append(lines, f.Body[start:i]) + start = i + 1 + } + + i++ + } + + if i != start { + lines = append(lines, f.Body[start:i]) + } + + return lines +} + +// String returns the function name. +func (f *Function) String() string { + return f.Name +} diff --git a/src/compiler/Scan.go b/src/build/Scan.go similarity index 94% rename from src/compiler/Scan.go rename to src/build/Scan.go index d0ef625..ae24220 100644 --- a/src/compiler/Scan.go +++ b/src/build/Scan.go @@ -1,4 +1,4 @@ -package compiler +package build import ( "os" @@ -6,8 +6,8 @@ import ( "strings" "sync" - "git.akyoto.dev/cli/q/src/directory" - "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/build/directory" + "git.akyoto.dev/cli/q/src/build/token" ) // Scan scans the directory. diff --git a/src/build/Write.go b/src/build/Write.go new file mode 100644 index 0000000..f6385bb --- /dev/null +++ b/src/build/Write.go @@ -0,0 +1,29 @@ +package build + +import ( + "bufio" + "os" + + "git.akyoto.dev/cli/q/src/build/elf" +) + +// Write writes the executable file to disk. +func Write(filePath string, code []byte, data []byte) error { + file, err := os.Create(filePath) + + if err != nil { + return err + } + + buffer := bufio.NewWriter(file) + executable := elf.New(code, data) + executable.Write(buffer) + buffer.Flush() + err = file.Close() + + if err != nil { + return err + } + + return os.Chmod(filePath, 0755) +} diff --git a/src/build/arch/arm64/Syscall.go b/src/build/arch/arm64/Syscall.go new file mode 100644 index 0000000..fb6495b --- /dev/null +++ b/src/build/arch/arm64/Syscall.go @@ -0,0 +1,9 @@ +package register + +import "git.akyoto.dev/cli/q/src/build/cpu" + +const ( + SyscallReturn = 0 +) + +var SyscallArgs = []cpu.Register{8, 0, 1, 2, 3, 4, 5} diff --git a/src/arch/x64/Call.go b/src/build/arch/x64/Call.go similarity index 100% rename from src/arch/x64/Call.go rename to src/build/arch/x64/Call.go diff --git a/src/arch/x64/Move.go b/src/build/arch/x64/Move.go similarity index 100% rename from src/arch/x64/Move.go rename to src/build/arch/x64/Move.go diff --git a/src/arch/x64/Return.go b/src/build/arch/x64/Return.go similarity index 100% rename from src/arch/x64/Return.go rename to src/build/arch/x64/Return.go diff --git a/src/build/arch/x64/Syscall.go b/src/build/arch/x64/Syscall.go new file mode 100644 index 0000000..6251de9 --- /dev/null +++ b/src/build/arch/x64/Syscall.go @@ -0,0 +1,14 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +const ( + SyscallReturn = 0 // rax +) + +var SyscallArgs = []cpu.Register{0, 7, 6, 2, 10, 8, 9} + +// Syscall is the primary way to communicate with the OS kernel. +func Syscall(code []byte) []byte { + return append(code, 0x0f, 0x05) +} diff --git a/src/arch/x64/x64_test.go b/src/build/arch/x64/x64_test.go similarity index 91% rename from src/arch/x64/x64_test.go rename to src/build/arch/x64/x64_test.go index 8fb7c21..b39570d 100644 --- a/src/arch/x64/x64_test.go +++ b/src/build/arch/x64/x64_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go new file mode 100644 index 0000000..94a4ce7 --- /dev/null +++ b/src/build/asm/Assembler.go @@ -0,0 +1,66 @@ +package asm + +import ( + "encoding/binary" + "fmt" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/config" +) + +// Assembler contains a list of instructions. +type Assembler struct { + Instructions []Instruction +} + +// New creates a new assembler. +func New() *Assembler { + return &Assembler{ + Instructions: make([]Instruction, 0, 8), + } +} + +// Finalize generates the final machine code. +func (a *Assembler) Finalize() ([]byte, []byte) { + code := make([]byte, 0, len(a.Instructions)*8) + data := make([]byte, 0, 16) + pointers := []Pointer{} + + for _, x := range a.Instructions { + switch x.Mnemonic { + case MOVE: + code = x64.MoveRegNum32(code, uint8(x.Data.(RegisterNumber).Register), uint32(x.Data.(RegisterNumber).Number)) + + if x.Data.(RegisterNumber).IsPointer { + pointers = append(pointers, Pointer{ + Position: Address(len(code) - 4), + Address: Address(x.Data.(RegisterNumber).Number), + }) + } + + case SYSCALL: + code = x64.Syscall(code) + } + } + + if config.Verbose { + for _, x := range a.Instructions { + fmt.Println("[asm]", x.String()) + } + } + + dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) + + for _, pointer := range pointers { + slice := code[pointer.Position : pointer.Position+4] + address := dataStart + pointer.Address + binary.LittleEndian.PutUint32(slice, address) + } + + return code, data +} + +// Merge combines the contents of this assembler with another one. +func (a *Assembler) Merge(b *Assembler) { + a.Instructions = append(a.Instructions, b.Instructions...) +} diff --git a/src/build/asm/Instruction.go b/src/build/asm/Instruction.go new file mode 100644 index 0000000..966ce75 --- /dev/null +++ b/src/build/asm/Instruction.go @@ -0,0 +1,19 @@ +package asm + +import "fmt" + +// Instruction represents a single instruction which can be converted to machine code. +type Instruction struct { + Mnemonic Mnemonic + Data interface{} +} + +// String returns a human readable version. +func (x *Instruction) String() string { + switch data := x.Data.(type) { + case RegisterNumber: + return fmt.Sprintf("%s %s, %x", x.Mnemonic, data.Register, data.Number) + default: + return x.Mnemonic.String() + } +} diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go new file mode 100644 index 0000000..0cd9014 --- /dev/null +++ b/src/build/asm/Instructions.go @@ -0,0 +1,32 @@ +package asm + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// MoveRegisterNumber moves a number into the given register. +func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: MOVE, + Data: RegisterNumber{ + Register: reg, + Number: number, + IsPointer: false, + }, + }) +} + +// MoveRegisterAddress moves an address into the given register. +func (a *Assembler) MoveRegisterAddress(reg cpu.Register, address Address) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: MOVE, + Data: RegisterNumber{ + Register: reg, + Number: uint64(address), + IsPointer: true, + }, + }) +} + +// Syscall executes a kernel function. +func (a *Assembler) Syscall() { + a.Instructions = append(a.Instructions, Instruction{Mnemonic: SYSCALL}) +} diff --git a/src/asm/Mnemonic.go b/src/build/asm/Mnemonic.go similarity index 69% rename from src/asm/Mnemonic.go rename to src/build/asm/Mnemonic.go index 3d3a16c..cd7f033 100644 --- a/src/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -4,18 +4,15 @@ type Mnemonic uint8 const ( NONE Mnemonic = iota - MOV - MOVDATA + MOVE SYSCALL ) +// String returns a human readable version. func (m Mnemonic) String() string { switch m { - case MOV: - return "mov" - - case MOVDATA: - return "mov" + case MOVE: + return "move" case SYSCALL: return "syscall" diff --git a/src/asm/Pointer.go b/src/build/asm/Pointer.go similarity index 81% rename from src/asm/Pointer.go rename to src/build/asm/Pointer.go index 5d13b4f..7063ec5 100644 --- a/src/asm/Pointer.go +++ b/src/build/asm/Pointer.go @@ -1,5 +1,8 @@ package asm +// Address represents a memory address. +type Address = uint32 + // Pointer stores a relative memory address that we can later turn into an absolute one. // Position: The machine code offset where the address was inserted. // Address: The offset inside the section. diff --git a/src/build/asm/RegisterNumber.go b/src/build/asm/RegisterNumber.go new file mode 100644 index 0000000..445acd1 --- /dev/null +++ b/src/build/asm/RegisterNumber.go @@ -0,0 +1,10 @@ +package asm + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// RegisterNumber operates with a register and a number. +type RegisterNumber struct { + Register cpu.Register + Number uint64 + IsPointer bool +} diff --git a/src/config/config.go b/src/build/config/config.go similarity index 100% rename from src/config/config.go rename to src/build/config/config.go diff --git a/src/build/cpu/Register.go b/src/build/cpu/Register.go new file mode 100644 index 0000000..006fc68 --- /dev/null +++ b/src/build/cpu/Register.go @@ -0,0 +1,11 @@ +package cpu + +import "fmt" + +// Register represents the number of the register. +type Register uint8 + +// String returns the human readable name of the register. +func (r Register) String() string { + return fmt.Sprintf("r%d", r) +} diff --git a/src/directory/Walk.go b/src/build/directory/Walk.go similarity index 100% rename from src/directory/Walk.go rename to src/build/directory/Walk.go diff --git a/src/directory/Walk_test.go b/src/build/directory/Walk_test.go similarity index 90% rename from src/directory/Walk_test.go rename to src/build/directory/Walk_test.go index f3f64cf..00c663f 100644 --- a/src/directory/Walk_test.go +++ b/src/build/directory/Walk_test.go @@ -3,7 +3,7 @@ package directory_test import ( "testing" - "git.akyoto.dev/cli/q/src/directory" + "git.akyoto.dev/cli/q/src/build/directory" "git.akyoto.dev/go/assert" ) diff --git a/src/elf/ELF.go b/src/build/elf/ELF.go similarity index 97% rename from src/elf/ELF.go rename to src/build/elf/ELF.go index 92d45c6..f284dbc 100644 --- a/src/elf/ELF.go +++ b/src/build/elf/ELF.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "io" - "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/build/config" ) // ELF represents an ELF file. diff --git a/src/elf/ELF_test.go b/src/build/elf/ELF_test.go similarity index 77% rename from src/elf/ELF_test.go rename to src/build/elf/ELF_test.go index 45a9aa2..b609d60 100644 --- a/src/elf/ELF_test.go +++ b/src/build/elf/ELF_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/build/elf" ) func TestELF(t *testing.T) { diff --git a/src/elf/Header.go b/src/build/elf/Header.go similarity index 100% rename from src/elf/Header.go rename to src/build/elf/Header.go diff --git a/src/elf/ProgramHeader.go b/src/build/elf/ProgramHeader.go similarity index 100% rename from src/elf/ProgramHeader.go rename to src/build/elf/ProgramHeader.go diff --git a/src/elf/SectionHeader.go b/src/build/elf/SectionHeader.go similarity index 100% rename from src/elf/SectionHeader.go rename to src/build/elf/SectionHeader.go diff --git a/src/elf/elf.md b/src/build/elf/elf.md similarity index 100% rename from src/elf/elf.md rename to src/build/elf/elf.md diff --git a/src/os/linux/Syscall.go b/src/build/os/linux/Syscall.go similarity index 100% rename from src/os/linux/Syscall.go rename to src/build/os/linux/Syscall.go diff --git a/src/build/output/Compiler.go b/src/build/output/Compiler.go new file mode 100644 index 0000000..dbbff06 --- /dev/null +++ b/src/build/output/Compiler.go @@ -0,0 +1,27 @@ +package output + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Compiler implements the arch.Output interface. +type Compiler struct{} + +// Compile turns a function into machine code. +func (c Compiler) Compile(f *build.Function) { + for i, t := range f.Body { + if t.Kind == token.Identifier && t.Text() == "print" { + // message := f.Body[i+2].Bytes + // f.Assembler.MoveRegisterNumber(x64.SyscallNumber, linux.Write) + // f.Assembler.MoveRegisterNumber(x64.SyscallArgs[0], 1) + // f.Assembler.MoveRegisterData(x64.SyscallArgs[1], message) + // f.Assembler.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(message))) + // f.Assembler.Syscall() + message := f.Body[i+2].Bytes + fmt.Println(message) + } + } +} diff --git a/src/token/Keywords.go b/src/build/token/Keywords.go similarity index 100% rename from src/token/Keywords.go rename to src/build/token/Keywords.go diff --git a/src/token/Kind.go b/src/build/token/Kind.go similarity index 100% rename from src/token/Kind.go rename to src/build/token/Kind.go diff --git a/src/token/List.go b/src/build/token/List.go similarity index 100% rename from src/token/List.go rename to src/build/token/List.go diff --git a/src/token/Token.go b/src/build/token/Token.go similarity index 100% rename from src/token/Token.go rename to src/build/token/Token.go diff --git a/src/token/Token_test.go b/src/build/token/Token_test.go similarity index 92% rename from src/token/Token_test.go rename to src/build/token/Token_test.go index 8bcac13..1bc125e 100644 --- a/src/token/Token_test.go +++ b/src/build/token/Token_test.go @@ -3,7 +3,7 @@ package token_test import ( "testing" - "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/go/assert" ) @@ -96,6 +96,22 @@ func TestNewline(t *testing.T) { }) } +func TestNumber(t *testing.T) { + tokens := token.Tokenize([]byte(`123 -456`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Number, + Bytes: []byte("123"), + Position: 0, + }, + { + Kind: token.Number, + Bytes: []byte("-456"), + Position: 4, + }, + }) +} + func TestSeparator(t *testing.T) { tokens := token.Tokenize([]byte("a,b,c")) assert.DeepEqual(t, tokens, token.List{ diff --git a/src/token/Tokenize.go b/src/build/token/Tokenize.go similarity index 78% rename from src/token/Tokenize.go rename to src/build/token/Tokenize.go index 4692791..d0402e3 100644 --- a/src/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -96,7 +96,25 @@ func Tokenize(buffer []byte) List { } tokens = append(tokens, token) - i-- + continue + } + + // Numbers + if isNumberStart(buffer[i]) { + position := i + i++ + + for i < len(buffer) && isNumber(buffer[i]) { + i++ + } + + tokens = append(tokens, Token{ + Number, + position, + buffer[position:i], + }) + + continue } } @@ -106,10 +124,22 @@ func Tokenize(buffer []byte) List { return tokens } -func isIdentifierStart(c byte) bool { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' +func isIdentifier(c byte) bool { + return isLetter(c) || isNumber(c) || c == '_' } -func isIdentifier(c byte) bool { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (c >= '0' && c <= '9') +func isIdentifierStart(c byte) bool { + return isLetter(c) || c == '_' +} + +func isLetter(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') +} + +func isNumber(c byte) bool { + return (c >= '0' && c <= '9') +} + +func isNumberStart(c byte) bool { + return isNumber(c) || c == '-' } diff --git a/src/cli/Build.go b/src/cli/Build.go index 2974257..2eb6cf2 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -1,11 +1,11 @@ package cli import ( + "fmt" "strings" "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/log" + "git.akyoto.dev/cli/q/src/build/config" ) // Build builds an executable. @@ -22,7 +22,7 @@ func Build(args []string) int { default: if strings.HasPrefix(args[i], "-") { - log.Error.Printf("Unknown parameter: %s\n", args[i]) + fmt.Printf("Unknown parameter: %s\n", args[i]) return 2 } @@ -33,7 +33,7 @@ func Build(args []string) int { err := b.Run() if err != nil { - log.Error.Println(err) + fmt.Println(err) return 1 } diff --git a/src/cli/Help.go b/src/cli/Help.go index 35fa8cd..6ca3b3c 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -1,14 +1,12 @@ package cli -import ( - "git.akyoto.dev/cli/q/src/log" -) +import "fmt" // Help shows the command line argument usage. func Help(args []string) int { - log.Error.Println("Usage: q [command] [options]") - log.Error.Println("") - log.Error.Println(" build [directory]") - log.Error.Println(" system") + fmt.Println("Usage: q [command] [options]") + fmt.Println("") + fmt.Println(" build [directory]") + fmt.Println(" system") return 2 } diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index 95fcf55..f5ae0a4 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -2,21 +2,13 @@ package cli_test import ( "fmt" - "io" "os" "testing" "git.akyoto.dev/cli/q/src/cli" - "git.akyoto.dev/cli/q/src/log" "git.akyoto.dev/go/assert" ) -func TestMain(m *testing.M) { - log.Info.SetOutput(io.Discard) - log.Error.SetOutput(io.Discard) - os.Exit(m.Run()) -} - func TestCLI(t *testing.T) { type cliTest struct { arguments []string diff --git a/src/cli/System.go b/src/cli/System.go index d76f565..831576c 100644 --- a/src/cli/System.go +++ b/src/cli/System.go @@ -1,36 +1,35 @@ package cli import ( + "fmt" "os" "runtime" - - "git.akyoto.dev/cli/q/src/log" ) // System shows system information. func System(args []string) int { line := "%-19s%s\n" - log.Info.Printf(line, "Platform:", runtime.GOOS) - log.Info.Printf(line, "Architecture:", runtime.GOARCH) - log.Info.Printf(line, "Go:", runtime.Version()) + fmt.Printf(line, "Platform:", runtime.GOOS) + fmt.Printf(line, "Architecture:", runtime.GOARCH) + fmt.Printf(line, "Go:", runtime.Version()) // Directory directory, err := os.Getwd() if err == nil { - log.Info.Printf(line, "Directory:", directory) + fmt.Printf(line, "Directory:", directory) } else { - log.Info.Printf(line, "Directory:", err.Error()) + fmt.Printf(line, "Directory:", err.Error()) } // Compiler executable, err := os.Executable() if err == nil { - log.Info.Printf(line, "Compiler:", executable) + fmt.Printf(line, "Compiler:", executable) } else { - log.Info.Printf(line, "Compiler:", err.Error()) + fmt.Printf(line, "Compiler:", err.Error()) } return 0 diff --git a/src/compiler/Function.go b/src/compiler/Function.go deleted file mode 100644 index d9e65a3..0000000 --- a/src/compiler/Function.go +++ /dev/null @@ -1,35 +0,0 @@ -package compiler - -import ( - "git.akyoto.dev/cli/q/src/arch/x64" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/os/linux" - "git.akyoto.dev/cli/q/src/token" -) - -// Function represents a function. -type Function struct { - Name string - Head token.List - Body token.List - Assembler asm.Assembler -} - -// Compile turns a function into machine code. -func (f *Function) Compile() { - for i, t := range f.Body { - if t.Kind == token.Identifier && t.Text() == "print" { - message := f.Body[i+2].Bytes - f.Assembler.MoveRegisterNumber(x64.SyscallNumber, linux.Write) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[0], 1) - f.Assembler.MoveRegisterData(x64.SyscallArgs[1], message) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(message))) - f.Assembler.Syscall() - } - } -} - -// String returns the function name. -func (f *Function) String() string { - return f.Name -} diff --git a/src/cpu/CPU.go b/src/cpu/CPU.go deleted file mode 100644 index ea16af0..0000000 --- a/src/cpu/CPU.go +++ /dev/null @@ -1,106 +0,0 @@ -package cpu - -import "git.akyoto.dev/cli/q/src/register" - -// CPU manages the allocation state of registers. -type CPU struct { - All List - General List - Call List - Syscall List -} - -// New creates a new CPU state. -func New() *CPU { - // Rather than doing lots of mini allocations - // we'll allocate memory for all registers at once. - registers := [16]Register{ - {ID: register.R0}, - {ID: register.R1}, - {ID: register.R2}, - {ID: register.R3}, - {ID: register.R4}, - {ID: register.R5}, - {ID: register.R6}, - {ID: register.R7}, - {ID: register.R8}, - {ID: register.R9}, - {ID: register.R10}, - {ID: register.R11}, - {ID: register.R12}, - {ID: register.R13}, - {ID: register.R14}, - {ID: register.R15}, - } - - rax := ®isters[0] - rcx := ®isters[1] - rdx := ®isters[2] - rbx := ®isters[3] - rsp := ®isters[4] - rbp := ®isters[5] - rsi := ®isters[6] - rdi := ®isters[7] - r8 := ®isters[8] - r9 := ®isters[9] - r10 := ®isters[10] - r11 := ®isters[11] - r12 := ®isters[12] - r13 := ®isters[13] - r14 := ®isters[14] - r15 := ®isters[15] - - // Register configuration - return &CPU{ - All: List{ - rax, - rcx, - rdx, - rbx, - rsp, - rbp, - rsi, - rdi, - r8, - r9, - r10, - r11, - r12, - r13, - r14, - r15, - }, - General: List{ - rcx, - rbx, - rbp, - r11, - r12, - r13, - r14, - r15, - }, - Call: List{ - rdi, - rsi, - rdx, - r10, - r8, - r9, - }, - Syscall: List{ - rax, - rdi, - rsi, - rdx, - r10, - r8, - r9, - }, - } -} - -// ByID returns the register with the given ID. -func (cpu *CPU) ByID(id register.ID) *Register { - return cpu.All[id] -} diff --git a/src/cpu/List.go b/src/cpu/List.go deleted file mode 100644 index e9234aa..0000000 --- a/src/cpu/List.go +++ /dev/null @@ -1,29 +0,0 @@ -package cpu - -// List is a list of registers. -type List []*Register - -// FindFree tries to find a free register -// and returns nil when all are currently occupied. -func (registers List) FindFree() *Register { - for _, register := range registers { - if register.IsFree() { - return register - } - } - - return nil -} - -// InUse returns a list of registers that are currently in use. -func (registers List) InUse() List { - var inUse List - - for _, register := range registers { - if !register.IsFree() { - inUse = append(inUse, register) - } - } - - return inUse -} diff --git a/src/cpu/Register.go b/src/cpu/Register.go deleted file mode 100644 index b11add9..0000000 --- a/src/cpu/Register.go +++ /dev/null @@ -1,39 +0,0 @@ -package cpu - -import ( - "fmt" - - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/register" -) - -// Register represents a single CPU register. -type Register struct { - ID register.ID - user fmt.Stringer -} - -// Use marks the register as used by the given object. -func (register *Register) Use(obj fmt.Stringer) error { - if register.user != nil { - return &errors.RegisterInUse{Register: register.ID.String(), User: register.user.String()} - } - - register.user = obj - return nil -} - -// Free frees the register so that it can be used for new calculations. -func (register *Register) Free() { - register.user = nil -} - -// IsFree returns true if the register is not in use. -func (register *Register) IsFree() bool { - return register.user == nil -} - -// String returns a human-readable representation of the register. -func (register *Register) String() string { - return fmt.Sprintf("%s%s%v", register.ID, "=", register.user) -} diff --git a/src/errors/InvalidDirectory.go b/src/errors/InvalidDirectory.go deleted file mode 100644 index 90beadd..0000000 --- a/src/errors/InvalidDirectory.go +++ /dev/null @@ -1,17 +0,0 @@ -package errors - -import "fmt" - -// InvalidDirectory errors are returned when the specified path is not a directory. -type InvalidDirectory struct { - Path string -} - -// Error implements the text representation. -func (err *InvalidDirectory) Error() string { - if err.Path == "" { - return "Invalid directory" - } - - return fmt.Sprintf("Invalid directory '%s'", err.Path) -} diff --git a/src/errors/RegisterInUse.go b/src/errors/RegisterInUse.go deleted file mode 100644 index 27f40a5..0000000 --- a/src/errors/RegisterInUse.go +++ /dev/null @@ -1,14 +0,0 @@ -package errors - -import "fmt" - -// RegisterInUse errors are returned when a register is already in use. -type RegisterInUse struct { - Register string - User string -} - -// Error implements the text representation. -func (err *RegisterInUse) Error() string { - return fmt.Sprintf("Register '%s' already used by '%s'", err.Register, err.User) -} diff --git a/src/log/log.go b/src/log/log.go deleted file mode 100644 index 395f6ab..0000000 --- a/src/log/log.go +++ /dev/null @@ -1,14 +0,0 @@ -package log - -import ( - "log" - "os" -) - -var ( - // Info is used for general info messages. - Info = log.New(os.Stdout, "", 0) - - // Error is used for error messages. - Error = log.New(os.Stderr, "", 0) -) diff --git a/src/register/ID.go b/src/register/ID.go deleted file mode 100644 index bc2fe1c..0000000 --- a/src/register/ID.go +++ /dev/null @@ -1,44 +0,0 @@ -package register - -import "fmt" - -// ID represents the number of the register. -type ID uint8 - -const ( - R0 ID = iota - R1 - R2 - R3 - R4 - R5 - R6 - R7 - R8 - R9 - R10 - R11 - R12 - R13 - R14 - R15 - R16 - R17 - R18 - R19 - R20 - R21 - R22 - R23 - R24 - R25 - R26 - R27 - R28 - R29 - R30 -) - -func (r ID) String() string { - return fmt.Sprintf("r%d", r) -} From 1e4ab7d1babde4ab33f63c3860735debff22a14d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 11 Jun 2024 15:49:27 +0200 Subject: [PATCH 0063/1012] Updated dependencies --- examples/hello/hello.q | 1 + go.mod | 9 +++++++-- go.sum | 4 ++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 76d8d21..5688704 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,3 +1,4 @@ main() { + syscall(1, 1, 4194305, 3) syscall(60, 0) } diff --git a/go.mod b/go.mod index b286207..97c0628 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,10 @@ module git.akyoto.dev/cli/q -go 1.22 +go 1.22.4 -require git.akyoto.dev/go/assert v0.1.3 +require ( + git.akyoto.dev/go/assert v0.1.3 + git.akyoto.dev/go/color v0.1.1 +) + +require golang.org/x/sys v0.21.0 // indirect diff --git a/go.sum b/go.sum index 9fc2547..c3e0b31 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,6 @@ git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= +git.akyoto.dev/go/color v0.1.1 h1:mMAoMIwLBPNy7ocRSxdsCFs7onPC3GfDEiJErCneqRE= +git.akyoto.dev/go/color v0.1.1/go.mod h1:ywOjoD0O0sk6bIn92uAJf7mErlEFCuQInL84y4Lqi3Q= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= From e951170423bebeee67bd07bf5842b4e7f753344b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 11 Jun 2024 15:49:27 +0200 Subject: [PATCH 0064/1012] Updated dependencies --- examples/hello/hello.q | 1 + go.mod | 9 +++++++-- go.sum | 4 ++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 76d8d21..5688704 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,3 +1,4 @@ main() { + syscall(1, 1, 4194305, 3) syscall(60, 0) } diff --git a/go.mod b/go.mod index b286207..97c0628 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,10 @@ module git.akyoto.dev/cli/q -go 1.22 +go 1.22.4 -require git.akyoto.dev/go/assert v0.1.3 +require ( + git.akyoto.dev/go/assert v0.1.3 + git.akyoto.dev/go/color v0.1.1 +) + +require golang.org/x/sys v0.21.0 // indirect diff --git a/go.sum b/go.sum index 9fc2547..c3e0b31 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,6 @@ git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= +git.akyoto.dev/go/color v0.1.1 h1:mMAoMIwLBPNy7ocRSxdsCFs7onPC3GfDEiJErCneqRE= +git.akyoto.dev/go/color v0.1.1/go.mod h1:ywOjoD0O0sk6bIn92uAJf7mErlEFCuQInL84y4Lqi3Q= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= From d04c216dc26fcb686d3df75f0ec6424e2418362b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 11 Jun 2024 23:54:19 +0200 Subject: [PATCH 0065/1012] Removed outdated code --- src/build/output/Compiler.go | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 src/build/output/Compiler.go diff --git a/src/build/output/Compiler.go b/src/build/output/Compiler.go deleted file mode 100644 index dbbff06..0000000 --- a/src/build/output/Compiler.go +++ /dev/null @@ -1,27 +0,0 @@ -package output - -import ( - "fmt" - - "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/build/token" -) - -// Compiler implements the arch.Output interface. -type Compiler struct{} - -// Compile turns a function into machine code. -func (c Compiler) Compile(f *build.Function) { - for i, t := range f.Body { - if t.Kind == token.Identifier && t.Text() == "print" { - // message := f.Body[i+2].Bytes - // f.Assembler.MoveRegisterNumber(x64.SyscallNumber, linux.Write) - // f.Assembler.MoveRegisterNumber(x64.SyscallArgs[0], 1) - // f.Assembler.MoveRegisterData(x64.SyscallArgs[1], message) - // f.Assembler.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(message))) - // f.Assembler.Syscall() - message := f.Body[i+2].Bytes - fmt.Println(message) - } - } -} From ef11656e478c4d15c872b57534a28dd6e59c5e0d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 11 Jun 2024 23:54:19 +0200 Subject: [PATCH 0066/1012] Removed outdated code --- src/build/output/Compiler.go | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 src/build/output/Compiler.go diff --git a/src/build/output/Compiler.go b/src/build/output/Compiler.go deleted file mode 100644 index dbbff06..0000000 --- a/src/build/output/Compiler.go +++ /dev/null @@ -1,27 +0,0 @@ -package output - -import ( - "fmt" - - "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/build/token" -) - -// Compiler implements the arch.Output interface. -type Compiler struct{} - -// Compile turns a function into machine code. -func (c Compiler) Compile(f *build.Function) { - for i, t := range f.Body { - if t.Kind == token.Identifier && t.Text() == "print" { - // message := f.Body[i+2].Bytes - // f.Assembler.MoveRegisterNumber(x64.SyscallNumber, linux.Write) - // f.Assembler.MoveRegisterNumber(x64.SyscallArgs[0], 1) - // f.Assembler.MoveRegisterData(x64.SyscallArgs[1], message) - // f.Assembler.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(message))) - // f.Assembler.Syscall() - message := f.Body[i+2].Bytes - fmt.Println(message) - } - } -} From 423526e567242ea434570d3460aab33f40de4df6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Jun 2024 12:10:29 +0200 Subject: [PATCH 0067/1012] Added support for single file builds --- src/build/Build.go | 20 ++++++++++++------ src/build/Compile.go | 3 +-- src/build/Scan.go | 50 +++++++++++++++++++++++++++++++------------- src/cli/Build.go | 8 +++++-- 4 files changed, 57 insertions(+), 24 deletions(-) diff --git a/src/build/Build.go b/src/build/Build.go index c7029cb..0132eb4 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -2,25 +2,27 @@ package build import ( "path/filepath" + "strings" ) // Build describes a compiler build. type Build struct { - Directory string + Files []string WriteExecutable bool } // New creates a new build. -func New(directory string) *Build { +func New(files ...string) *Build { return &Build{ - Directory: directory, + Files: files, WriteExecutable: true, } } // Run parses the input files and generates an executable file. func (build *Build) Run() error { - functions, err := Compile(build.Directory) + functions, errors := Scan(build.Files) + allFunctions, err := Compile(functions, errors) if err != nil { return err @@ -31,11 +33,17 @@ func (build *Build) Run() error { } path := build.Executable() - code, data := Finalize(functions) + code, data := Finalize(allFunctions) return Write(path, code, data) } // Executable returns the path to the executable. func (build *Build) Executable() string { - return filepath.Join(build.Directory, filepath.Base(build.Directory)) + directory, _ := filepath.Abs(build.Files[0]) + + if strings.HasSuffix(directory, ".q") { + directory = filepath.Dir(directory) + } + + return filepath.Join(directory, filepath.Base(directory)) } diff --git a/src/build/Compile.go b/src/build/Compile.go index 1d1ffc1..a40ab2c 100644 --- a/src/build/Compile.go +++ b/src/build/Compile.go @@ -5,8 +5,7 @@ import ( ) // Compile compiles all the functions. -func Compile(directory string) (map[string]*Function, error) { - functions, errors := Scan(directory) +func Compile(functions <-chan *Function, errors <-chan error) (map[string]*Function, error) { wg := sync.WaitGroup{} allFunctions := map[string]*Function{} diff --git a/src/build/Scan.go b/src/build/Scan.go index ae24220..86f161e 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -11,12 +11,12 @@ import ( ) // Scan scans the directory. -func Scan(path string) (<-chan *Function, <-chan error) { +func Scan(files []string) (<-chan *Function, <-chan error) { functions := make(chan *Function) errors := make(chan error) go func() { - scan(path, functions, errors) + scan(files, functions, errors) close(functions) close(errors) }() @@ -25,29 +25,51 @@ func Scan(path string) (<-chan *Function, <-chan error) { } // scan scans the directory without channel allocations. -func scan(path string, functions chan<- *Function, errors chan<- error) { +func scan(files []string, functions chan<- *Function, errors chan<- error) { wg := sync.WaitGroup{} - err := directory.Walk(path, func(name string) { - if !strings.HasSuffix(name, ".q") { + for _, file := range files { + stat, err := os.Stat(file) + + if err != nil { + errors <- err return } - fullPath := filepath.Join(path, name) - wg.Add(1) + if stat.IsDir() { + err = directory.Walk(file, func(name string) { + if !strings.HasSuffix(name, ".q") { + return + } - go func() { - defer wg.Done() - err := scanFile(fullPath, functions) + fullPath := filepath.Join(file, name) + wg.Add(1) + + go func() { + defer wg.Done() + err := scanFile(fullPath, functions) + + if err != nil { + errors <- err + } + }() + }) if err != nil { errors <- err } - }() - }) + } else { + wg.Add(1) - if err != nil { - errors <- err + go func() { + defer wg.Done() + err := scanFile(file, functions) + + if err != nil { + errors <- err + } + }() + } } wg.Wait() diff --git a/src/cli/Build.go b/src/cli/Build.go index 2eb6cf2..8f53e6a 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -10,7 +10,7 @@ import ( // Build builds an executable. func Build(args []string) int { - b := build.New(".") + b := build.New() for i := 0; i < len(args); i++ { switch args[i] { @@ -26,10 +26,14 @@ func Build(args []string) int { return 2 } - b.Directory = args[i] + b.Files = append(b.Files, args[i]) } } + if len(b.Files) == 0 { + b.Files = append(b.Files, ".") + } + err := b.Run() if err != nil { From 2d990b0bee998597b154e59f3e125fd183c7b0e7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Jun 2024 12:10:29 +0200 Subject: [PATCH 0068/1012] Added support for single file builds --- src/build/Build.go | 20 ++++++++++++------ src/build/Compile.go | 3 +-- src/build/Scan.go | 50 +++++++++++++++++++++++++++++++------------- src/cli/Build.go | 8 +++++-- 4 files changed, 57 insertions(+), 24 deletions(-) diff --git a/src/build/Build.go b/src/build/Build.go index c7029cb..0132eb4 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -2,25 +2,27 @@ package build import ( "path/filepath" + "strings" ) // Build describes a compiler build. type Build struct { - Directory string + Files []string WriteExecutable bool } // New creates a new build. -func New(directory string) *Build { +func New(files ...string) *Build { return &Build{ - Directory: directory, + Files: files, WriteExecutable: true, } } // Run parses the input files and generates an executable file. func (build *Build) Run() error { - functions, err := Compile(build.Directory) + functions, errors := Scan(build.Files) + allFunctions, err := Compile(functions, errors) if err != nil { return err @@ -31,11 +33,17 @@ func (build *Build) Run() error { } path := build.Executable() - code, data := Finalize(functions) + code, data := Finalize(allFunctions) return Write(path, code, data) } // Executable returns the path to the executable. func (build *Build) Executable() string { - return filepath.Join(build.Directory, filepath.Base(build.Directory)) + directory, _ := filepath.Abs(build.Files[0]) + + if strings.HasSuffix(directory, ".q") { + directory = filepath.Dir(directory) + } + + return filepath.Join(directory, filepath.Base(directory)) } diff --git a/src/build/Compile.go b/src/build/Compile.go index 1d1ffc1..a40ab2c 100644 --- a/src/build/Compile.go +++ b/src/build/Compile.go @@ -5,8 +5,7 @@ import ( ) // Compile compiles all the functions. -func Compile(directory string) (map[string]*Function, error) { - functions, errors := Scan(directory) +func Compile(functions <-chan *Function, errors <-chan error) (map[string]*Function, error) { wg := sync.WaitGroup{} allFunctions := map[string]*Function{} diff --git a/src/build/Scan.go b/src/build/Scan.go index ae24220..86f161e 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -11,12 +11,12 @@ import ( ) // Scan scans the directory. -func Scan(path string) (<-chan *Function, <-chan error) { +func Scan(files []string) (<-chan *Function, <-chan error) { functions := make(chan *Function) errors := make(chan error) go func() { - scan(path, functions, errors) + scan(files, functions, errors) close(functions) close(errors) }() @@ -25,29 +25,51 @@ func Scan(path string) (<-chan *Function, <-chan error) { } // scan scans the directory without channel allocations. -func scan(path string, functions chan<- *Function, errors chan<- error) { +func scan(files []string, functions chan<- *Function, errors chan<- error) { wg := sync.WaitGroup{} - err := directory.Walk(path, func(name string) { - if !strings.HasSuffix(name, ".q") { + for _, file := range files { + stat, err := os.Stat(file) + + if err != nil { + errors <- err return } - fullPath := filepath.Join(path, name) - wg.Add(1) + if stat.IsDir() { + err = directory.Walk(file, func(name string) { + if !strings.HasSuffix(name, ".q") { + return + } - go func() { - defer wg.Done() - err := scanFile(fullPath, functions) + fullPath := filepath.Join(file, name) + wg.Add(1) + + go func() { + defer wg.Done() + err := scanFile(fullPath, functions) + + if err != nil { + errors <- err + } + }() + }) if err != nil { errors <- err } - }() - }) + } else { + wg.Add(1) - if err != nil { - errors <- err + go func() { + defer wg.Done() + err := scanFile(file, functions) + + if err != nil { + errors <- err + } + }() + } } wg.Wait() diff --git a/src/cli/Build.go b/src/cli/Build.go index 2eb6cf2..8f53e6a 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -10,7 +10,7 @@ import ( // Build builds an executable. func Build(args []string) int { - b := build.New(".") + b := build.New() for i := 0; i < len(args); i++ { switch args[i] { @@ -26,10 +26,14 @@ func Build(args []string) int { return 2 } - b.Directory = args[i] + b.Files = append(b.Files, args[i]) } } + if len(b.Files) == 0 { + b.Files = append(b.Files, ".") + } + err := b.Run() if err != nil { From e3b26c79f4d534360b2c4ed3060cfe091a77284c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 13 Jun 2024 12:13:32 +0200 Subject: [PATCH 0069/1012] Implemented error messages --- src/build/Build.go | 22 +-- src/build/Build_test.go | 12 +- src/build/Scan.go | 152 ++++++++++++++---- src/build/token/Kind.go | 4 + src/build/token/Token_test.go | 46 ++++++ src/build/token/Tokenize.go | 4 + src/cli/Build.go | 18 ++- src/cli/Help.go | 2 +- src/cli/Main_test.go | 9 +- src/errors/Base.go | 11 ++ src/errors/Error.go | 67 ++++++++ src/errors/Error_test.go | 32 ++++ src/errors/ScanErrors.go | 11 ++ src/errors/Stack.go | 30 ++++ .../testdata/ExpectedFunctionDefinition.q | 1 + .../testdata/ExpectedFunctionParameters.q | 1 + 16 files changed, 362 insertions(+), 60 deletions(-) create mode 100644 src/errors/Base.go create mode 100644 src/errors/Error.go create mode 100644 src/errors/Error_test.go create mode 100644 src/errors/ScanErrors.go create mode 100644 src/errors/Stack.go create mode 100644 src/errors/testdata/ExpectedFunctionDefinition.q create mode 100644 src/errors/testdata/ExpectedFunctionParameters.q diff --git a/src/build/Build.go b/src/build/Build.go index 0132eb4..fcf1698 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -7,34 +7,20 @@ import ( // Build describes a compiler build. type Build struct { - Files []string - WriteExecutable bool + Files []string } // New creates a new build. func New(files ...string) *Build { return &Build{ - Files: files, - WriteExecutable: true, + Files: files, } } // Run parses the input files and generates an executable file. -func (build *Build) Run() error { +func (build *Build) Run() (map[string]*Function, error) { functions, errors := Scan(build.Files) - allFunctions, err := Compile(functions, errors) - - if err != nil { - return err - } - - if !build.WriteExecutable { - return nil - } - - path := build.Executable() - code, data := Finalize(allFunctions) - return Write(path, code, data) + return Compile(functions, errors) } // Executable returns the path to the executable. diff --git a/src/build/Build_test.go b/src/build/Build_test.go index 7c91f0b..88d8e52 100644 --- a/src/build/Build_test.go +++ b/src/build/Build_test.go @@ -1,6 +1,7 @@ package build_test import ( + "path/filepath" "testing" "git.akyoto.dev/cli/q/src/build" @@ -9,16 +10,17 @@ import ( func TestBuild(t *testing.T) { b := build.New("../../examples/hello") - assert.Nil(t, b.Run()) + _, err := b.Run() + assert.Nil(t, err) } -func TestSkipExecutable(t *testing.T) { +func TestExecutable(t *testing.T) { b := build.New("../../examples/hello") - b.WriteExecutable = false - assert.Nil(t, b.Run()) + assert.Equal(t, filepath.Base(b.Executable()), "hello") } func TestNonExisting(t *testing.T) { b := build.New("does-not-exist") - assert.NotNil(t, b.Run()) + _, err := b.Run() + assert.NotNil(t, err) } diff --git a/src/build/Scan.go b/src/build/Scan.go index 86f161e..6c56628 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -8,6 +8,7 @@ import ( "git.akyoto.dev/cli/q/src/build/directory" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" ) // Scan scans the directory. @@ -86,46 +87,141 @@ func scanFile(path string, functions chan<- *Function) error { tokens := token.Tokenize(contents) var ( + i = 0 groupLevel = 0 blockLevel = 0 - headerStart = -1 + nameStart = -1 + paramsStart = -1 bodyStart = -1 ) - for i, t := range tokens { - switch t.Kind { - case token.Identifier: - if blockLevel == 0 && groupLevel == 0 { - headerStart = i + for { + // Function name + for i < len(tokens) { + if tokens[i].Kind == token.Identifier { + nameStart = i + i++ + break } - case token.GroupStart: - groupLevel++ - - case token.GroupEnd: - groupLevel-- - - case token.BlockStart: - blockLevel++ - - if blockLevel == 1 { - bodyStart = i + if tokens[i].Kind == token.NewLine { + i++ + continue } - case token.BlockEnd: - blockLevel-- + if tokens[i].Kind == token.EOF { + return nil + } - if blockLevel == 0 { - function := &Function{ - Name: tokens[headerStart].Text(), - Head: tokens[headerStart:bodyStart], - Body: tokens[bodyStart : i+1], + return errors.New(errors.ExpectedFunctionName, path, tokens, i) + } + + // Function parameters + for i < len(tokens) { + if tokens[i].Kind == token.GroupStart { + groupLevel++ + + if groupLevel == 1 { + paramsStart = i } - functions <- function + i++ + continue } - } - } - return nil + if tokens[i].Kind == token.GroupEnd { + groupLevel-- + + if groupLevel < 0 { + return errors.New(errors.MissingGroupStart, path, tokens, i) + } + + i++ + + if groupLevel == 0 { + break + } + + continue + } + + if tokens[i].Kind == token.EOF { + if groupLevel > 0 { + return errors.New(errors.MissingGroupEnd, path, tokens, i) + } + + if paramsStart == -1 { + return errors.New(errors.ExpectedFunctionParameters, path, tokens, i) + } + + return nil + } + + if groupLevel > 0 { + i++ + continue + } + + return errors.New(errors.ExpectedFunctionParameters, path, tokens, i) + } + + // Function definition + for i < len(tokens) { + if tokens[i].Kind == token.BlockStart { + blockLevel++ + + if blockLevel == 1 { + bodyStart = i + } + + i++ + continue + } + + if tokens[i].Kind == token.BlockEnd { + blockLevel-- + + if blockLevel < 0 { + return errors.New(errors.MissingBlockStart, path, tokens, i) + } + + i++ + + if blockLevel == 0 { + break + } + + continue + } + + if tokens[i].Kind == token.EOF { + if blockLevel > 0 { + return errors.New(errors.MissingBlockEnd, path, tokens, i) + } + + if bodyStart == -1 { + return errors.New(errors.ExpectedFunctionDefinition, path, tokens, i) + } + + return nil + } + + if blockLevel > 0 { + i++ + continue + } + + return errors.New(errors.ExpectedFunctionDefinition, path, tokens, i) + } + + functions <- &Function{ + Name: tokens[nameStart].Text(), + Head: tokens[paramsStart:bodyStart], + Body: tokens[bodyStart : i+1], + } + + nameStart = -1 + paramsStart = -1 + bodyStart = -1 + } } diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 70065cd..b221442 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -7,6 +7,9 @@ const ( // Invalid represents an invalid token. Invalid Kind = iota + // EOF represents the end of file. + EOF + // NewLine represents the newline character. NewLine @@ -54,6 +57,7 @@ const ( func (kind Kind) String() string { return [...]string{ "Invalid", + "EOF", "NewLine", "Identifier", "Keyword", diff --git a/src/build/token/Token_test.go b/src/build/token/Token_test.go index 1bc125e..e1f66f5 100644 --- a/src/build/token/Token_test.go +++ b/src/build/token/Token_test.go @@ -35,6 +35,11 @@ func TestFunction(t *testing.T) { Bytes: []byte("}"), Position: 7, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, }) } @@ -51,6 +56,11 @@ func TestKeyword(t *testing.T) { Bytes: []byte("x"), Position: 7, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, }) } @@ -77,6 +87,11 @@ func TestArray(t *testing.T) { Bytes: []byte("]"), Position: 7, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, }) } @@ -93,6 +108,11 @@ func TestNewline(t *testing.T) { Bytes: []byte("\n"), Position: 1, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 2, + }, }) } @@ -109,6 +129,11 @@ func TestNumber(t *testing.T) { Bytes: []byte("-456"), Position: 4, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, }) } @@ -140,6 +165,11 @@ func TestSeparator(t *testing.T) { Bytes: []byte("c"), Position: 4, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 5, + }, }) } @@ -156,6 +186,11 @@ func TestString(t *testing.T) { Bytes: []byte(`"World"`), Position: 8, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 15, + }, }) } @@ -167,6 +202,11 @@ func TestStringMultiline(t *testing.T) { Bytes: []byte("\"Hello\nWorld\""), Position: 0, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 13, + }, }) } @@ -178,6 +218,11 @@ func TestStringEOF(t *testing.T) { Bytes: []byte(`"EOF`), Position: 0, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 4, + }, }) } @@ -195,6 +240,7 @@ func TestTokenText(t *testing.T) { func TestTokenKind(t *testing.T) { assert.Equal(t, token.Invalid.String(), "Invalid") + assert.Equal(t, token.EOF.String(), "EOF") assert.Equal(t, token.NewLine.String(), "NewLine") assert.Equal(t, token.Identifier.String(), "Identifier") assert.Equal(t, token.Keyword.String(), "Keyword") diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index d0402e3..213adb6 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -31,6 +31,7 @@ func Tokenize(buffer []byte) List { for i < len(buffer) { if buffer[i] == '"' { end = i + 1 + i++ break } @@ -43,6 +44,8 @@ func Tokenize(buffer []byte) List { buffer[start:end], }) + continue + // Parentheses start case '(': tokens = append(tokens, Token{GroupStart, i, groupStartBytes}) @@ -121,6 +124,7 @@ func Tokenize(buffer []byte) List { i++ } + tokens = append(tokens, Token{EOF, i, nil}) return tokens } diff --git a/src/cli/Build.go b/src/cli/Build.go index 8f53e6a..697ec7e 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -11,11 +11,12 @@ import ( // Build builds an executable. func Build(args []string) int { b := build.New() + writeExecutable := true for i := 0; i < len(args); i++ { switch args[i] { case "--dry": - b.WriteExecutable = false + writeExecutable = false case "--verbose", "-v": config.Verbose = true @@ -34,7 +35,20 @@ func Build(args []string) int { b.Files = append(b.Files, ".") } - err := b.Run() + result, err := b.Run() + + if err != nil { + fmt.Println(err) + return 1 + } + + if !writeExecutable { + return 0 + } + + path := b.Executable() + code, data := build.Finalize(result) + err = build.Write(path, code, data) if err != nil { fmt.Println(err) diff --git a/src/cli/Help.go b/src/cli/Help.go index 6ca3b3c..ace2380 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -6,7 +6,7 @@ import "fmt" func Help(args []string) int { fmt.Println("Usage: q [command] [options]") fmt.Println("") - fmt.Println(" build [directory]") + fmt.Println(" build [directory] [file]") fmt.Println(" system") return 2 } diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index f5ae0a4..63b5366 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -1,8 +1,6 @@ package cli_test import ( - "fmt" - "os" "testing" "git.akyoto.dev/cli/q/src/cli" @@ -19,17 +17,16 @@ func TestCLI(t *testing.T) { {[]string{}, 2}, {[]string{"invalid"}, 2}, {[]string{"system"}, 0}, - {[]string{"build", "non-existing-directory"}, 1}, - {[]string{"build", "../../examples/hello/hello.q"}, 1}, + {[]string{"build", "invalid-directory"}, 1}, + {[]string{"build", "--invalid-parameter"}, 2}, {[]string{"build", "../../examples/hello", "--invalid"}, 2}, {[]string{"build", "../../examples/hello", "--dry"}, 0}, {[]string{"build", "../../examples/hello", "--dry", "--verbose"}, 0}, + {[]string{"build", "../../examples/hello/hello.q", "--dry"}, 0}, } for _, test := range tests { t.Log(test.arguments) - directory, _ := os.Getwd() - fmt.Println(directory) exitCode := cli.Main(test.arguments) assert.Equal(t, exitCode, test.expectedExitCode) } diff --git a/src/errors/Base.go b/src/errors/Base.go new file mode 100644 index 0000000..a019d58 --- /dev/null +++ b/src/errors/Base.go @@ -0,0 +1,11 @@ +package errors + +// Base is the base class for errors that have no parameters. +type Base struct { + Message string +} + +// Error generates the string representation. +func (err *Base) Error() string { + return err.Message +} diff --git a/src/errors/Error.go b/src/errors/Error.go new file mode 100644 index 0000000..c06bd20 --- /dev/null +++ b/src/errors/Error.go @@ -0,0 +1,67 @@ +package errors + +import ( + "fmt" + "os" + "path/filepath" + + "git.akyoto.dev/cli/q/src/build/token" +) + +// Error is a compiler error at a given line and column. +type Error struct { + Path string + Line int + Column int + Err error + Stack string +} + +// New generates an error message at the current token position. +// The error message is clickable in popular editors and leads you +// directly to the faulty file at the given line and position. +func New(err error, path string, tokens []token.Token, cursor int) *Error { + var ( + lineCount = 1 + lineStart = -1 + ) + + for i := range cursor { + if tokens[i].Kind == token.NewLine { + lineCount++ + lineStart = int(tokens[i].Position) + } + } + + var column int + + if cursor < len(tokens) { + column = tokens[cursor].Position - lineStart + } else { + lastToken := tokens[len(tokens)-1] + column = lastToken.Position - lineStart + len(lastToken.Text()) + } + + return &Error{path, lineCount, column, err, Stack()} +} + +// Error generates the string representation. +func (e *Error) Error() string { + path := e.Path + cwd, err := os.Getwd() + + if err == nil { + relativePath, err := filepath.Rel(cwd, e.Path) + + if err == nil { + path = relativePath + } + } + + return fmt.Sprintf("%s:%d:%d: %s\n\n%s", path, e.Line, e.Column, e.Err, e.Stack) +} + +// Unwrap returns the wrapped error. +func (e *Error) Unwrap() error { + return e.Err +} diff --git a/src/errors/Error_test.go b/src/errors/Error_test.go new file mode 100644 index 0000000..b518e1a --- /dev/null +++ b/src/errors/Error_test.go @@ -0,0 +1,32 @@ +package errors_test + +import ( + "path/filepath" + "strings" + "testing" + + "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/go/assert" +) + +func TestErrors(t *testing.T) { + tests := []struct { + File string + ExpectedError error + }{ + {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, + {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, + } + + for _, test := range tests { + name := strings.TrimSuffix(test.File, ".q") + + t.Run(name, func(t *testing.T) { + b := build.New(filepath.Join("testdata", test.File)) + _, err := b.Run() + assert.NotNil(t, err) + assert.Contains(t, err.Error(), test.ExpectedError.Error()) + }) + } +} diff --git a/src/errors/ScanErrors.go b/src/errors/ScanErrors.go new file mode 100644 index 0000000..7fe07f2 --- /dev/null +++ b/src/errors/ScanErrors.go @@ -0,0 +1,11 @@ +package errors + +var ( + MissingBlockStart = &Base{"Missing '{'"} + MissingBlockEnd = &Base{"Missing '}'"} + MissingGroupStart = &Base{"Missing '('"} + MissingGroupEnd = &Base{"Missing ')'"} + ExpectedFunctionName = &Base{"Expected function name"} + ExpectedFunctionParameters = &Base{"Expected function parameters"} + ExpectedFunctionDefinition = &Base{"Expected function definition"} +) diff --git a/src/errors/Stack.go b/src/errors/Stack.go new file mode 100644 index 0000000..90a26ae --- /dev/null +++ b/src/errors/Stack.go @@ -0,0 +1,30 @@ +package errors + +import ( + "runtime" + "strings" +) + +// Stack generates a stack trace. +func Stack() string { + var ( + final []string + buffer = make([]byte, 4096) + n = runtime.Stack(buffer, false) + stack = string(buffer[:n]) + lines = strings.Split(stack, "\n") + ) + + for i := 6; i < len(lines); i += 2 { + line := strings.TrimSpace(lines[i]) + space := strings.LastIndex(line, " ") + + if space != -1 { + line = line[:space] + } + + final = append(final, line) + } + + return strings.Join(final, "\n") +} diff --git a/src/errors/testdata/ExpectedFunctionDefinition.q b/src/errors/testdata/ExpectedFunctionDefinition.q new file mode 100644 index 0000000..ac72fa7 --- /dev/null +++ b/src/errors/testdata/ExpectedFunctionDefinition.q @@ -0,0 +1 @@ +main() \ No newline at end of file diff --git a/src/errors/testdata/ExpectedFunctionParameters.q b/src/errors/testdata/ExpectedFunctionParameters.q new file mode 100644 index 0000000..88d050b --- /dev/null +++ b/src/errors/testdata/ExpectedFunctionParameters.q @@ -0,0 +1 @@ +main \ No newline at end of file From 9458253f312d7a943b3c83772a16f1cec71472a4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 13 Jun 2024 12:13:32 +0200 Subject: [PATCH 0070/1012] Implemented error messages --- src/build/Build.go | 22 +-- src/build/Build_test.go | 12 +- src/build/Scan.go | 152 ++++++++++++++---- src/build/token/Kind.go | 4 + src/build/token/Token_test.go | 46 ++++++ src/build/token/Tokenize.go | 4 + src/cli/Build.go | 18 ++- src/cli/Help.go | 2 +- src/cli/Main_test.go | 9 +- src/errors/Base.go | 11 ++ src/errors/Error.go | 67 ++++++++ src/errors/Error_test.go | 32 ++++ src/errors/ScanErrors.go | 11 ++ src/errors/Stack.go | 30 ++++ .../testdata/ExpectedFunctionDefinition.q | 1 + .../testdata/ExpectedFunctionParameters.q | 1 + 16 files changed, 362 insertions(+), 60 deletions(-) create mode 100644 src/errors/Base.go create mode 100644 src/errors/Error.go create mode 100644 src/errors/Error_test.go create mode 100644 src/errors/ScanErrors.go create mode 100644 src/errors/Stack.go create mode 100644 src/errors/testdata/ExpectedFunctionDefinition.q create mode 100644 src/errors/testdata/ExpectedFunctionParameters.q diff --git a/src/build/Build.go b/src/build/Build.go index 0132eb4..fcf1698 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -7,34 +7,20 @@ import ( // Build describes a compiler build. type Build struct { - Files []string - WriteExecutable bool + Files []string } // New creates a new build. func New(files ...string) *Build { return &Build{ - Files: files, - WriteExecutable: true, + Files: files, } } // Run parses the input files and generates an executable file. -func (build *Build) Run() error { +func (build *Build) Run() (map[string]*Function, error) { functions, errors := Scan(build.Files) - allFunctions, err := Compile(functions, errors) - - if err != nil { - return err - } - - if !build.WriteExecutable { - return nil - } - - path := build.Executable() - code, data := Finalize(allFunctions) - return Write(path, code, data) + return Compile(functions, errors) } // Executable returns the path to the executable. diff --git a/src/build/Build_test.go b/src/build/Build_test.go index 7c91f0b..88d8e52 100644 --- a/src/build/Build_test.go +++ b/src/build/Build_test.go @@ -1,6 +1,7 @@ package build_test import ( + "path/filepath" "testing" "git.akyoto.dev/cli/q/src/build" @@ -9,16 +10,17 @@ import ( func TestBuild(t *testing.T) { b := build.New("../../examples/hello") - assert.Nil(t, b.Run()) + _, err := b.Run() + assert.Nil(t, err) } -func TestSkipExecutable(t *testing.T) { +func TestExecutable(t *testing.T) { b := build.New("../../examples/hello") - b.WriteExecutable = false - assert.Nil(t, b.Run()) + assert.Equal(t, filepath.Base(b.Executable()), "hello") } func TestNonExisting(t *testing.T) { b := build.New("does-not-exist") - assert.NotNil(t, b.Run()) + _, err := b.Run() + assert.NotNil(t, err) } diff --git a/src/build/Scan.go b/src/build/Scan.go index 86f161e..6c56628 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -8,6 +8,7 @@ import ( "git.akyoto.dev/cli/q/src/build/directory" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" ) // Scan scans the directory. @@ -86,46 +87,141 @@ func scanFile(path string, functions chan<- *Function) error { tokens := token.Tokenize(contents) var ( + i = 0 groupLevel = 0 blockLevel = 0 - headerStart = -1 + nameStart = -1 + paramsStart = -1 bodyStart = -1 ) - for i, t := range tokens { - switch t.Kind { - case token.Identifier: - if blockLevel == 0 && groupLevel == 0 { - headerStart = i + for { + // Function name + for i < len(tokens) { + if tokens[i].Kind == token.Identifier { + nameStart = i + i++ + break } - case token.GroupStart: - groupLevel++ - - case token.GroupEnd: - groupLevel-- - - case token.BlockStart: - blockLevel++ - - if blockLevel == 1 { - bodyStart = i + if tokens[i].Kind == token.NewLine { + i++ + continue } - case token.BlockEnd: - blockLevel-- + if tokens[i].Kind == token.EOF { + return nil + } - if blockLevel == 0 { - function := &Function{ - Name: tokens[headerStart].Text(), - Head: tokens[headerStart:bodyStart], - Body: tokens[bodyStart : i+1], + return errors.New(errors.ExpectedFunctionName, path, tokens, i) + } + + // Function parameters + for i < len(tokens) { + if tokens[i].Kind == token.GroupStart { + groupLevel++ + + if groupLevel == 1 { + paramsStart = i } - functions <- function + i++ + continue } - } - } - return nil + if tokens[i].Kind == token.GroupEnd { + groupLevel-- + + if groupLevel < 0 { + return errors.New(errors.MissingGroupStart, path, tokens, i) + } + + i++ + + if groupLevel == 0 { + break + } + + continue + } + + if tokens[i].Kind == token.EOF { + if groupLevel > 0 { + return errors.New(errors.MissingGroupEnd, path, tokens, i) + } + + if paramsStart == -1 { + return errors.New(errors.ExpectedFunctionParameters, path, tokens, i) + } + + return nil + } + + if groupLevel > 0 { + i++ + continue + } + + return errors.New(errors.ExpectedFunctionParameters, path, tokens, i) + } + + // Function definition + for i < len(tokens) { + if tokens[i].Kind == token.BlockStart { + blockLevel++ + + if blockLevel == 1 { + bodyStart = i + } + + i++ + continue + } + + if tokens[i].Kind == token.BlockEnd { + blockLevel-- + + if blockLevel < 0 { + return errors.New(errors.MissingBlockStart, path, tokens, i) + } + + i++ + + if blockLevel == 0 { + break + } + + continue + } + + if tokens[i].Kind == token.EOF { + if blockLevel > 0 { + return errors.New(errors.MissingBlockEnd, path, tokens, i) + } + + if bodyStart == -1 { + return errors.New(errors.ExpectedFunctionDefinition, path, tokens, i) + } + + return nil + } + + if blockLevel > 0 { + i++ + continue + } + + return errors.New(errors.ExpectedFunctionDefinition, path, tokens, i) + } + + functions <- &Function{ + Name: tokens[nameStart].Text(), + Head: tokens[paramsStart:bodyStart], + Body: tokens[bodyStart : i+1], + } + + nameStart = -1 + paramsStart = -1 + bodyStart = -1 + } } diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 70065cd..b221442 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -7,6 +7,9 @@ const ( // Invalid represents an invalid token. Invalid Kind = iota + // EOF represents the end of file. + EOF + // NewLine represents the newline character. NewLine @@ -54,6 +57,7 @@ const ( func (kind Kind) String() string { return [...]string{ "Invalid", + "EOF", "NewLine", "Identifier", "Keyword", diff --git a/src/build/token/Token_test.go b/src/build/token/Token_test.go index 1bc125e..e1f66f5 100644 --- a/src/build/token/Token_test.go +++ b/src/build/token/Token_test.go @@ -35,6 +35,11 @@ func TestFunction(t *testing.T) { Bytes: []byte("}"), Position: 7, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, }) } @@ -51,6 +56,11 @@ func TestKeyword(t *testing.T) { Bytes: []byte("x"), Position: 7, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, }) } @@ -77,6 +87,11 @@ func TestArray(t *testing.T) { Bytes: []byte("]"), Position: 7, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, }) } @@ -93,6 +108,11 @@ func TestNewline(t *testing.T) { Bytes: []byte("\n"), Position: 1, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 2, + }, }) } @@ -109,6 +129,11 @@ func TestNumber(t *testing.T) { Bytes: []byte("-456"), Position: 4, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, }) } @@ -140,6 +165,11 @@ func TestSeparator(t *testing.T) { Bytes: []byte("c"), Position: 4, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 5, + }, }) } @@ -156,6 +186,11 @@ func TestString(t *testing.T) { Bytes: []byte(`"World"`), Position: 8, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 15, + }, }) } @@ -167,6 +202,11 @@ func TestStringMultiline(t *testing.T) { Bytes: []byte("\"Hello\nWorld\""), Position: 0, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 13, + }, }) } @@ -178,6 +218,11 @@ func TestStringEOF(t *testing.T) { Bytes: []byte(`"EOF`), Position: 0, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 4, + }, }) } @@ -195,6 +240,7 @@ func TestTokenText(t *testing.T) { func TestTokenKind(t *testing.T) { assert.Equal(t, token.Invalid.String(), "Invalid") + assert.Equal(t, token.EOF.String(), "EOF") assert.Equal(t, token.NewLine.String(), "NewLine") assert.Equal(t, token.Identifier.String(), "Identifier") assert.Equal(t, token.Keyword.String(), "Keyword") diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index d0402e3..213adb6 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -31,6 +31,7 @@ func Tokenize(buffer []byte) List { for i < len(buffer) { if buffer[i] == '"' { end = i + 1 + i++ break } @@ -43,6 +44,8 @@ func Tokenize(buffer []byte) List { buffer[start:end], }) + continue + // Parentheses start case '(': tokens = append(tokens, Token{GroupStart, i, groupStartBytes}) @@ -121,6 +124,7 @@ func Tokenize(buffer []byte) List { i++ } + tokens = append(tokens, Token{EOF, i, nil}) return tokens } diff --git a/src/cli/Build.go b/src/cli/Build.go index 8f53e6a..697ec7e 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -11,11 +11,12 @@ import ( // Build builds an executable. func Build(args []string) int { b := build.New() + writeExecutable := true for i := 0; i < len(args); i++ { switch args[i] { case "--dry": - b.WriteExecutable = false + writeExecutable = false case "--verbose", "-v": config.Verbose = true @@ -34,7 +35,20 @@ func Build(args []string) int { b.Files = append(b.Files, ".") } - err := b.Run() + result, err := b.Run() + + if err != nil { + fmt.Println(err) + return 1 + } + + if !writeExecutable { + return 0 + } + + path := b.Executable() + code, data := build.Finalize(result) + err = build.Write(path, code, data) if err != nil { fmt.Println(err) diff --git a/src/cli/Help.go b/src/cli/Help.go index 6ca3b3c..ace2380 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -6,7 +6,7 @@ import "fmt" func Help(args []string) int { fmt.Println("Usage: q [command] [options]") fmt.Println("") - fmt.Println(" build [directory]") + fmt.Println(" build [directory] [file]") fmt.Println(" system") return 2 } diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index f5ae0a4..63b5366 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -1,8 +1,6 @@ package cli_test import ( - "fmt" - "os" "testing" "git.akyoto.dev/cli/q/src/cli" @@ -19,17 +17,16 @@ func TestCLI(t *testing.T) { {[]string{}, 2}, {[]string{"invalid"}, 2}, {[]string{"system"}, 0}, - {[]string{"build", "non-existing-directory"}, 1}, - {[]string{"build", "../../examples/hello/hello.q"}, 1}, + {[]string{"build", "invalid-directory"}, 1}, + {[]string{"build", "--invalid-parameter"}, 2}, {[]string{"build", "../../examples/hello", "--invalid"}, 2}, {[]string{"build", "../../examples/hello", "--dry"}, 0}, {[]string{"build", "../../examples/hello", "--dry", "--verbose"}, 0}, + {[]string{"build", "../../examples/hello/hello.q", "--dry"}, 0}, } for _, test := range tests { t.Log(test.arguments) - directory, _ := os.Getwd() - fmt.Println(directory) exitCode := cli.Main(test.arguments) assert.Equal(t, exitCode, test.expectedExitCode) } diff --git a/src/errors/Base.go b/src/errors/Base.go new file mode 100644 index 0000000..a019d58 --- /dev/null +++ b/src/errors/Base.go @@ -0,0 +1,11 @@ +package errors + +// Base is the base class for errors that have no parameters. +type Base struct { + Message string +} + +// Error generates the string representation. +func (err *Base) Error() string { + return err.Message +} diff --git a/src/errors/Error.go b/src/errors/Error.go new file mode 100644 index 0000000..c06bd20 --- /dev/null +++ b/src/errors/Error.go @@ -0,0 +1,67 @@ +package errors + +import ( + "fmt" + "os" + "path/filepath" + + "git.akyoto.dev/cli/q/src/build/token" +) + +// Error is a compiler error at a given line and column. +type Error struct { + Path string + Line int + Column int + Err error + Stack string +} + +// New generates an error message at the current token position. +// The error message is clickable in popular editors and leads you +// directly to the faulty file at the given line and position. +func New(err error, path string, tokens []token.Token, cursor int) *Error { + var ( + lineCount = 1 + lineStart = -1 + ) + + for i := range cursor { + if tokens[i].Kind == token.NewLine { + lineCount++ + lineStart = int(tokens[i].Position) + } + } + + var column int + + if cursor < len(tokens) { + column = tokens[cursor].Position - lineStart + } else { + lastToken := tokens[len(tokens)-1] + column = lastToken.Position - lineStart + len(lastToken.Text()) + } + + return &Error{path, lineCount, column, err, Stack()} +} + +// Error generates the string representation. +func (e *Error) Error() string { + path := e.Path + cwd, err := os.Getwd() + + if err == nil { + relativePath, err := filepath.Rel(cwd, e.Path) + + if err == nil { + path = relativePath + } + } + + return fmt.Sprintf("%s:%d:%d: %s\n\n%s", path, e.Line, e.Column, e.Err, e.Stack) +} + +// Unwrap returns the wrapped error. +func (e *Error) Unwrap() error { + return e.Err +} diff --git a/src/errors/Error_test.go b/src/errors/Error_test.go new file mode 100644 index 0000000..b518e1a --- /dev/null +++ b/src/errors/Error_test.go @@ -0,0 +1,32 @@ +package errors_test + +import ( + "path/filepath" + "strings" + "testing" + + "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/go/assert" +) + +func TestErrors(t *testing.T) { + tests := []struct { + File string + ExpectedError error + }{ + {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, + {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, + } + + for _, test := range tests { + name := strings.TrimSuffix(test.File, ".q") + + t.Run(name, func(t *testing.T) { + b := build.New(filepath.Join("testdata", test.File)) + _, err := b.Run() + assert.NotNil(t, err) + assert.Contains(t, err.Error(), test.ExpectedError.Error()) + }) + } +} diff --git a/src/errors/ScanErrors.go b/src/errors/ScanErrors.go new file mode 100644 index 0000000..7fe07f2 --- /dev/null +++ b/src/errors/ScanErrors.go @@ -0,0 +1,11 @@ +package errors + +var ( + MissingBlockStart = &Base{"Missing '{'"} + MissingBlockEnd = &Base{"Missing '}'"} + MissingGroupStart = &Base{"Missing '('"} + MissingGroupEnd = &Base{"Missing ')'"} + ExpectedFunctionName = &Base{"Expected function name"} + ExpectedFunctionParameters = &Base{"Expected function parameters"} + ExpectedFunctionDefinition = &Base{"Expected function definition"} +) diff --git a/src/errors/Stack.go b/src/errors/Stack.go new file mode 100644 index 0000000..90a26ae --- /dev/null +++ b/src/errors/Stack.go @@ -0,0 +1,30 @@ +package errors + +import ( + "runtime" + "strings" +) + +// Stack generates a stack trace. +func Stack() string { + var ( + final []string + buffer = make([]byte, 4096) + n = runtime.Stack(buffer, false) + stack = string(buffer[:n]) + lines = strings.Split(stack, "\n") + ) + + for i := 6; i < len(lines); i += 2 { + line := strings.TrimSpace(lines[i]) + space := strings.LastIndex(line, " ") + + if space != -1 { + line = line[:space] + } + + final = append(final, line) + } + + return strings.Join(final, "\n") +} diff --git a/src/errors/testdata/ExpectedFunctionDefinition.q b/src/errors/testdata/ExpectedFunctionDefinition.q new file mode 100644 index 0000000..ac72fa7 --- /dev/null +++ b/src/errors/testdata/ExpectedFunctionDefinition.q @@ -0,0 +1 @@ +main() \ No newline at end of file diff --git a/src/errors/testdata/ExpectedFunctionParameters.q b/src/errors/testdata/ExpectedFunctionParameters.q new file mode 100644 index 0000000..88d050b --- /dev/null +++ b/src/errors/testdata/ExpectedFunctionParameters.q @@ -0,0 +1 @@ +main \ No newline at end of file From 80e195167c16e13ab5543ae8579e66314199a9dd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Jun 2024 09:23:21 +0200 Subject: [PATCH 0071/1012] Added more tests --- src/errors/Error_test.go | 6 +++++- src/errors/testdata/MissingBlockEnd.q | 1 + src/errors/testdata/MissingBlockStart.q | 1 + src/errors/testdata/MissingGroupEnd.q | 1 + src/errors/testdata/MissingGroupStart.q | 1 + 5 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/errors/testdata/MissingBlockEnd.q create mode 100644 src/errors/testdata/MissingBlockStart.q create mode 100644 src/errors/testdata/MissingGroupEnd.q create mode 100644 src/errors/testdata/MissingGroupStart.q diff --git a/src/errors/Error_test.go b/src/errors/Error_test.go index b518e1a..055f187 100644 --- a/src/errors/Error_test.go +++ b/src/errors/Error_test.go @@ -15,8 +15,12 @@ func TestErrors(t *testing.T) { File string ExpectedError error }{ - {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, + {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, + {"MissingBlockEnd.q", errors.MissingBlockEnd}, + {"MissingBlockStart.q", errors.MissingBlockStart}, + {"MissingGroupEnd.q", errors.MissingGroupEnd}, + {"MissingGroupStart.q", errors.MissingGroupStart}, } for _, test := range tests { diff --git a/src/errors/testdata/MissingBlockEnd.q b/src/errors/testdata/MissingBlockEnd.q new file mode 100644 index 0000000..48a9f79 --- /dev/null +++ b/src/errors/testdata/MissingBlockEnd.q @@ -0,0 +1 @@ +main(){ \ No newline at end of file diff --git a/src/errors/testdata/MissingBlockStart.q b/src/errors/testdata/MissingBlockStart.q new file mode 100644 index 0000000..006988a --- /dev/null +++ b/src/errors/testdata/MissingBlockStart.q @@ -0,0 +1 @@ +main()} \ No newline at end of file diff --git a/src/errors/testdata/MissingGroupEnd.q b/src/errors/testdata/MissingGroupEnd.q new file mode 100644 index 0000000..189300d --- /dev/null +++ b/src/errors/testdata/MissingGroupEnd.q @@ -0,0 +1 @@ +main( \ No newline at end of file diff --git a/src/errors/testdata/MissingGroupStart.q b/src/errors/testdata/MissingGroupStart.q new file mode 100644 index 0000000..83e9712 --- /dev/null +++ b/src/errors/testdata/MissingGroupStart.q @@ -0,0 +1 @@ +main) \ No newline at end of file From d026010d326b6354e2dd0a28e44e301512983fba Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Jun 2024 09:23:21 +0200 Subject: [PATCH 0072/1012] Added more tests --- src/errors/Error_test.go | 6 +++++- src/errors/testdata/MissingBlockEnd.q | 1 + src/errors/testdata/MissingBlockStart.q | 1 + src/errors/testdata/MissingGroupEnd.q | 1 + src/errors/testdata/MissingGroupStart.q | 1 + 5 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/errors/testdata/MissingBlockEnd.q create mode 100644 src/errors/testdata/MissingBlockStart.q create mode 100644 src/errors/testdata/MissingGroupEnd.q create mode 100644 src/errors/testdata/MissingGroupStart.q diff --git a/src/errors/Error_test.go b/src/errors/Error_test.go index b518e1a..055f187 100644 --- a/src/errors/Error_test.go +++ b/src/errors/Error_test.go @@ -15,8 +15,12 @@ func TestErrors(t *testing.T) { File string ExpectedError error }{ - {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, + {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, + {"MissingBlockEnd.q", errors.MissingBlockEnd}, + {"MissingBlockStart.q", errors.MissingBlockStart}, + {"MissingGroupEnd.q", errors.MissingGroupEnd}, + {"MissingGroupStart.q", errors.MissingGroupStart}, } for _, test := range tests { diff --git a/src/errors/testdata/MissingBlockEnd.q b/src/errors/testdata/MissingBlockEnd.q new file mode 100644 index 0000000..48a9f79 --- /dev/null +++ b/src/errors/testdata/MissingBlockEnd.q @@ -0,0 +1 @@ +main(){ \ No newline at end of file diff --git a/src/errors/testdata/MissingBlockStart.q b/src/errors/testdata/MissingBlockStart.q new file mode 100644 index 0000000..006988a --- /dev/null +++ b/src/errors/testdata/MissingBlockStart.q @@ -0,0 +1 @@ +main()} \ No newline at end of file diff --git a/src/errors/testdata/MissingGroupEnd.q b/src/errors/testdata/MissingGroupEnd.q new file mode 100644 index 0000000..189300d --- /dev/null +++ b/src/errors/testdata/MissingGroupEnd.q @@ -0,0 +1 @@ +main( \ No newline at end of file diff --git a/src/errors/testdata/MissingGroupStart.q b/src/errors/testdata/MissingGroupStart.q new file mode 100644 index 0000000..83e9712 --- /dev/null +++ b/src/errors/testdata/MissingGroupStart.q @@ -0,0 +1 @@ +main) \ No newline at end of file From 7bbfa8914f1e950e1d6926cd45ef2219d0b816d6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Jun 2024 10:36:01 +0200 Subject: [PATCH 0073/1012] Moved tests to a separate directory --- examples/hello/hello.q | 2 +- src/errors/Error_test.go => main_test.go | 5 +++-- .../testdata => tests/errors}/ExpectedFunctionDefinition.q | 0 tests/errors/ExpectedFunctionName.q | 1 + .../testdata => tests/errors}/ExpectedFunctionParameters.q | 0 {src/errors/testdata => tests/errors}/MissingBlockEnd.q | 0 {src/errors/testdata => tests/errors}/MissingBlockStart.q | 0 {src/errors/testdata => tests/errors}/MissingGroupEnd.q | 0 {src/errors/testdata => tests/errors}/MissingGroupStart.q | 0 9 files changed, 5 insertions(+), 3 deletions(-) rename src/errors/Error_test.go => main_test.go (85%) rename {src/errors/testdata => tests/errors}/ExpectedFunctionDefinition.q (100%) create mode 100644 tests/errors/ExpectedFunctionName.q rename {src/errors/testdata => tests/errors}/ExpectedFunctionParameters.q (100%) rename {src/errors/testdata => tests/errors}/MissingBlockEnd.q (100%) rename {src/errors/testdata => tests/errors}/MissingBlockStart.q (100%) rename {src/errors/testdata => tests/errors}/MissingGroupEnd.q (100%) rename {src/errors/testdata => tests/errors}/MissingGroupStart.q (100%) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 5688704..e5101a2 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,4 +1,4 @@ main() { syscall(1, 1, 4194305, 3) syscall(60, 0) -} +} \ No newline at end of file diff --git a/src/errors/Error_test.go b/main_test.go similarity index 85% rename from src/errors/Error_test.go rename to main_test.go index 055f187..d7c4539 100644 --- a/src/errors/Error_test.go +++ b/main_test.go @@ -1,4 +1,4 @@ -package errors_test +package main_test import ( "path/filepath" @@ -16,6 +16,7 @@ func TestErrors(t *testing.T) { ExpectedError error }{ {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, + {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, @@ -27,7 +28,7 @@ func TestErrors(t *testing.T) { name := strings.TrimSuffix(test.File, ".q") t.Run(name, func(t *testing.T) { - b := build.New(filepath.Join("testdata", test.File)) + b := build.New(filepath.Join("tests", "errors", test.File)) _, err := b.Run() assert.NotNil(t, err) assert.Contains(t, err.Error(), test.ExpectedError.Error()) diff --git a/src/errors/testdata/ExpectedFunctionDefinition.q b/tests/errors/ExpectedFunctionDefinition.q similarity index 100% rename from src/errors/testdata/ExpectedFunctionDefinition.q rename to tests/errors/ExpectedFunctionDefinition.q diff --git a/tests/errors/ExpectedFunctionName.q b/tests/errors/ExpectedFunctionName.q new file mode 100644 index 0000000..d800886 --- /dev/null +++ b/tests/errors/ExpectedFunctionName.q @@ -0,0 +1 @@ +123 \ No newline at end of file diff --git a/src/errors/testdata/ExpectedFunctionParameters.q b/tests/errors/ExpectedFunctionParameters.q similarity index 100% rename from src/errors/testdata/ExpectedFunctionParameters.q rename to tests/errors/ExpectedFunctionParameters.q diff --git a/src/errors/testdata/MissingBlockEnd.q b/tests/errors/MissingBlockEnd.q similarity index 100% rename from src/errors/testdata/MissingBlockEnd.q rename to tests/errors/MissingBlockEnd.q diff --git a/src/errors/testdata/MissingBlockStart.q b/tests/errors/MissingBlockStart.q similarity index 100% rename from src/errors/testdata/MissingBlockStart.q rename to tests/errors/MissingBlockStart.q diff --git a/src/errors/testdata/MissingGroupEnd.q b/tests/errors/MissingGroupEnd.q similarity index 100% rename from src/errors/testdata/MissingGroupEnd.q rename to tests/errors/MissingGroupEnd.q diff --git a/src/errors/testdata/MissingGroupStart.q b/tests/errors/MissingGroupStart.q similarity index 100% rename from src/errors/testdata/MissingGroupStart.q rename to tests/errors/MissingGroupStart.q From f13f7c2800b199b87b25ab4a1092d8d750ec9285 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Jun 2024 10:36:01 +0200 Subject: [PATCH 0074/1012] Moved tests to a separate directory --- examples/hello/hello.q | 2 +- src/errors/Error_test.go => main_test.go | 5 +++-- .../testdata => tests/errors}/ExpectedFunctionDefinition.q | 0 tests/errors/ExpectedFunctionName.q | 1 + .../testdata => tests/errors}/ExpectedFunctionParameters.q | 0 {src/errors/testdata => tests/errors}/MissingBlockEnd.q | 0 {src/errors/testdata => tests/errors}/MissingBlockStart.q | 0 {src/errors/testdata => tests/errors}/MissingGroupEnd.q | 0 {src/errors/testdata => tests/errors}/MissingGroupStart.q | 0 9 files changed, 5 insertions(+), 3 deletions(-) rename src/errors/Error_test.go => main_test.go (85%) rename {src/errors/testdata => tests/errors}/ExpectedFunctionDefinition.q (100%) create mode 100644 tests/errors/ExpectedFunctionName.q rename {src/errors/testdata => tests/errors}/ExpectedFunctionParameters.q (100%) rename {src/errors/testdata => tests/errors}/MissingBlockEnd.q (100%) rename {src/errors/testdata => tests/errors}/MissingBlockStart.q (100%) rename {src/errors/testdata => tests/errors}/MissingGroupEnd.q (100%) rename {src/errors/testdata => tests/errors}/MissingGroupStart.q (100%) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 5688704..e5101a2 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,4 +1,4 @@ main() { syscall(1, 1, 4194305, 3) syscall(60, 0) -} +} \ No newline at end of file diff --git a/src/errors/Error_test.go b/main_test.go similarity index 85% rename from src/errors/Error_test.go rename to main_test.go index 055f187..d7c4539 100644 --- a/src/errors/Error_test.go +++ b/main_test.go @@ -1,4 +1,4 @@ -package errors_test +package main_test import ( "path/filepath" @@ -16,6 +16,7 @@ func TestErrors(t *testing.T) { ExpectedError error }{ {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, + {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, @@ -27,7 +28,7 @@ func TestErrors(t *testing.T) { name := strings.TrimSuffix(test.File, ".q") t.Run(name, func(t *testing.T) { - b := build.New(filepath.Join("testdata", test.File)) + b := build.New(filepath.Join("tests", "errors", test.File)) _, err := b.Run() assert.NotNil(t, err) assert.Contains(t, err.Error(), test.ExpectedError.Error()) diff --git a/src/errors/testdata/ExpectedFunctionDefinition.q b/tests/errors/ExpectedFunctionDefinition.q similarity index 100% rename from src/errors/testdata/ExpectedFunctionDefinition.q rename to tests/errors/ExpectedFunctionDefinition.q diff --git a/tests/errors/ExpectedFunctionName.q b/tests/errors/ExpectedFunctionName.q new file mode 100644 index 0000000..d800886 --- /dev/null +++ b/tests/errors/ExpectedFunctionName.q @@ -0,0 +1 @@ +123 \ No newline at end of file diff --git a/src/errors/testdata/ExpectedFunctionParameters.q b/tests/errors/ExpectedFunctionParameters.q similarity index 100% rename from src/errors/testdata/ExpectedFunctionParameters.q rename to tests/errors/ExpectedFunctionParameters.q diff --git a/src/errors/testdata/MissingBlockEnd.q b/tests/errors/MissingBlockEnd.q similarity index 100% rename from src/errors/testdata/MissingBlockEnd.q rename to tests/errors/MissingBlockEnd.q diff --git a/src/errors/testdata/MissingBlockStart.q b/tests/errors/MissingBlockStart.q similarity index 100% rename from src/errors/testdata/MissingBlockStart.q rename to tests/errors/MissingBlockStart.q diff --git a/src/errors/testdata/MissingGroupEnd.q b/tests/errors/MissingGroupEnd.q similarity index 100% rename from src/errors/testdata/MissingGroupEnd.q rename to tests/errors/MissingGroupEnd.q diff --git a/src/errors/testdata/MissingGroupStart.q b/tests/errors/MissingGroupStart.q similarity index 100% rename from src/errors/testdata/MissingGroupStart.q rename to tests/errors/MissingGroupStart.q From f603c3a479f77ad793efeca5d6394d8230ad6a28 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Jun 2024 10:46:45 +0200 Subject: [PATCH 0075/1012] Implemented return keyword --- src/build/Function.go | 7 +++++++ src/build/asm/Assembler.go | 6 ++++++ src/build/asm/Instructions.go | 5 +++++ src/build/asm/Mnemonic.go | 4 ++++ 4 files changed, 22 insertions(+) diff --git a/src/build/Function.go b/src/build/Function.go index 11c84ed..729c5c5 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -34,6 +34,13 @@ func (f *Function) Compile() { continue } + if line[0].Kind == token.Keyword { + switch line[0].Text() { + case "return": + f.Assembler.Return() + } + } + if line[0].Kind == token.Identifier && line[0].Text() == "syscall" { paramTokens := line[2 : len(line)-1] start := 0 diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 94a4ce7..01379f9 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -38,8 +38,14 @@ func (a *Assembler) Finalize() ([]byte, []byte) { }) } + case RETURN: + code = x64.Return(code) + case SYSCALL: code = x64.Syscall(code) + + default: + panic("Unknown mnemonic: " + x.Mnemonic.String()) } } diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 0cd9014..f201af9 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -26,6 +26,11 @@ func (a *Assembler) MoveRegisterAddress(reg cpu.Register, address Address) { }) } +// Return returns back to the caller. +func (a *Assembler) Return() { + a.Instructions = append(a.Instructions, Instruction{Mnemonic: RETURN}) +} + // Syscall executes a kernel function. func (a *Assembler) Syscall() { a.Instructions = append(a.Instructions, Instruction{Mnemonic: SYSCALL}) diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index cd7f033..7fd1a06 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -5,6 +5,7 @@ type Mnemonic uint8 const ( NONE Mnemonic = iota MOVE + RETURN SYSCALL ) @@ -14,6 +15,9 @@ func (m Mnemonic) String() string { case MOVE: return "move" + case RETURN: + return "return" + case SYSCALL: return "syscall" } From f04e9d7a60eb4e5a452fc844ed96714664aec8be Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Jun 2024 10:46:45 +0200 Subject: [PATCH 0076/1012] Implemented return keyword --- src/build/Function.go | 7 +++++++ src/build/asm/Assembler.go | 6 ++++++ src/build/asm/Instructions.go | 5 +++++ src/build/asm/Mnemonic.go | 4 ++++ 4 files changed, 22 insertions(+) diff --git a/src/build/Function.go b/src/build/Function.go index 11c84ed..729c5c5 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -34,6 +34,13 @@ func (f *Function) Compile() { continue } + if line[0].Kind == token.Keyword { + switch line[0].Text() { + case "return": + f.Assembler.Return() + } + } + if line[0].Kind == token.Identifier && line[0].Text() == "syscall" { paramTokens := line[2 : len(line)-1] start := 0 diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 94a4ce7..01379f9 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -38,8 +38,14 @@ func (a *Assembler) Finalize() ([]byte, []byte) { }) } + case RETURN: + code = x64.Return(code) + case SYSCALL: code = x64.Syscall(code) + + default: + panic("Unknown mnemonic: " + x.Mnemonic.String()) } } diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 0cd9014..f201af9 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -26,6 +26,11 @@ func (a *Assembler) MoveRegisterAddress(reg cpu.Register, address Address) { }) } +// Return returns back to the caller. +func (a *Assembler) Return() { + a.Instructions = append(a.Instructions, Instruction{Mnemonic: RETURN}) +} + // Syscall executes a kernel function. func (a *Assembler) Syscall() { a.Instructions = append(a.Instructions, Instruction{Mnemonic: SYSCALL}) diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index cd7f033..7fd1a06 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -5,6 +5,7 @@ type Mnemonic uint8 const ( NONE Mnemonic = iota MOVE + RETURN SYSCALL ) @@ -14,6 +15,9 @@ func (m Mnemonic) String() string { case MOVE: return "move" + case RETURN: + return "return" + case SYSCALL: return "syscall" } From 3da77d22d7ecf03b390623bfb827f904ff839392 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Jun 2024 11:48:28 +0200 Subject: [PATCH 0077/1012] Implemented numeric constants --- examples/hello/hello.q | 8 ++++++-- src/build/Function.go | 34 +++++++++++++++++++++++++++++++--- src/build/Scan.go | 7 ++++--- src/build/Variable.go | 9 +++++++++ src/build/token/Keywords.go | 1 + src/build/token/List.go | 2 +- src/build/token/Tokenize.go | 7 +++++++ 7 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 src/build/Variable.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index e5101a2..2981aa1 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,4 +1,8 @@ main() { - syscall(1, 1, 4194305, 3) - syscall(60, 0) + let write = 1 + let exit = 60 + let stdout = 1 + + syscall(write, stdout, 4194305, 3) + syscall(exit, 0) } \ No newline at end of file diff --git a/src/build/Function.go b/src/build/Function.go index 729c5c5..8044009 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -16,6 +16,7 @@ type Function struct { Name string Head token.List Body token.List + Variables map[string]*Variable Assembler asm.Assembler } @@ -36,6 +37,16 @@ func (f *Function) Compile() { if line[0].Kind == token.Keyword { switch line[0].Text() { + case "let": + name := line[1].Text() + value := line[3:] + fmt.Println("[variable]", name, value) + + f.Variables[name] = &Variable{ + Value: line[3:], + IsConst: true, + } + case "return": f.Assembler.Return() } @@ -61,16 +72,33 @@ func (f *Function) Compile() { } for i, list := range parameters { - if list[0].Kind == token.Number { - numAsText := list[0].Text() - n, _ := strconv.Atoi(numAsText) + switch list[0].Kind { + case token.Identifier: + name := list[0].Text() + variable := f.Variables[name] + + if !variable.IsConst { + panic("Not implemented yet") + } + + n, _ := strconv.Atoi(variable.Value[0].Text()) f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + + case token.Number: + value := list[0].Text() + n, _ := strconv.Atoi(value) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + + default: + panic("Unknown expression") } } f.Assembler.Syscall() } } + + f.Assembler.Return() } // Lines returns the lines in the function body. diff --git a/src/build/Scan.go b/src/build/Scan.go index 6c56628..318c598 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -215,9 +215,10 @@ func scanFile(path string, functions chan<- *Function) error { } functions <- &Function{ - Name: tokens[nameStart].Text(), - Head: tokens[paramsStart:bodyStart], - Body: tokens[bodyStart : i+1], + Name: tokens[nameStart].Text(), + Head: tokens[paramsStart:bodyStart], + Body: tokens[bodyStart : i+1], + Variables: map[string]*Variable{}, } nameStart = -1 diff --git a/src/build/Variable.go b/src/build/Variable.go new file mode 100644 index 0000000..14cece9 --- /dev/null +++ b/src/build/Variable.go @@ -0,0 +1,9 @@ +package build + +import "git.akyoto.dev/cli/q/src/build/token" + +// Variable represents a variable in a function. +type Variable struct { + Value token.List + IsConst bool +} diff --git a/src/build/token/Keywords.go b/src/build/token/Keywords.go index adb8de7..a8eec3c 100644 --- a/src/build/token/Keywords.go +++ b/src/build/token/Keywords.go @@ -2,5 +2,6 @@ package token // Keywords defines the keywords used in the language. var Keywords = map[string]bool{ + "let": true, "return": true, } diff --git a/src/build/token/List.go b/src/build/token/List.go index 4c5953e..9cef514 100644 --- a/src/build/token/List.go +++ b/src/build/token/List.go @@ -13,7 +13,7 @@ func (list List) String() string { var last Token for _, t := range list { - if t.Kind == Identifier && last.Kind == Separator { + if last.Kind == Keyword || last.Kind == Separator { builder.WriteByte(' ') } diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 213adb6..53cdbb6 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -70,6 +70,9 @@ func Tokenize(buffer []byte) List { case ']': tokens = append(tokens, Token{ArrayEnd, i, arrayEndBytes}) + case '=', ':', '+', '-', '*', '/', '<', '>', '!': + tokens = append(tokens, Token{Operator, i, buffer[i : i+1]}) + // Separator case ',': tokens = append(tokens, Token{Separator, i, separatorBytes}) @@ -147,3 +150,7 @@ func isNumber(c byte) bool { func isNumberStart(c byte) bool { return isNumber(c) || c == '-' } + +func isOperator(c byte) bool { + return c == '=' || c == ':' || c == '+' || c == '-' || c == '*' || c == '/' || c == '<' || c == '>' || c == '!' +} From 19489d7a9aeabb20dce6268d14d5b8f346a1c202 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Jun 2024 11:48:28 +0200 Subject: [PATCH 0078/1012] Implemented numeric constants --- examples/hello/hello.q | 8 ++++++-- src/build/Function.go | 34 +++++++++++++++++++++++++++++++--- src/build/Scan.go | 7 ++++--- src/build/Variable.go | 9 +++++++++ src/build/token/Keywords.go | 1 + src/build/token/List.go | 2 +- src/build/token/Tokenize.go | 7 +++++++ 7 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 src/build/Variable.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index e5101a2..2981aa1 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,4 +1,8 @@ main() { - syscall(1, 1, 4194305, 3) - syscall(60, 0) + let write = 1 + let exit = 60 + let stdout = 1 + + syscall(write, stdout, 4194305, 3) + syscall(exit, 0) } \ No newline at end of file diff --git a/src/build/Function.go b/src/build/Function.go index 729c5c5..8044009 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -16,6 +16,7 @@ type Function struct { Name string Head token.List Body token.List + Variables map[string]*Variable Assembler asm.Assembler } @@ -36,6 +37,16 @@ func (f *Function) Compile() { if line[0].Kind == token.Keyword { switch line[0].Text() { + case "let": + name := line[1].Text() + value := line[3:] + fmt.Println("[variable]", name, value) + + f.Variables[name] = &Variable{ + Value: line[3:], + IsConst: true, + } + case "return": f.Assembler.Return() } @@ -61,16 +72,33 @@ func (f *Function) Compile() { } for i, list := range parameters { - if list[0].Kind == token.Number { - numAsText := list[0].Text() - n, _ := strconv.Atoi(numAsText) + switch list[0].Kind { + case token.Identifier: + name := list[0].Text() + variable := f.Variables[name] + + if !variable.IsConst { + panic("Not implemented yet") + } + + n, _ := strconv.Atoi(variable.Value[0].Text()) f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + + case token.Number: + value := list[0].Text() + n, _ := strconv.Atoi(value) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + + default: + panic("Unknown expression") } } f.Assembler.Syscall() } } + + f.Assembler.Return() } // Lines returns the lines in the function body. diff --git a/src/build/Scan.go b/src/build/Scan.go index 6c56628..318c598 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -215,9 +215,10 @@ func scanFile(path string, functions chan<- *Function) error { } functions <- &Function{ - Name: tokens[nameStart].Text(), - Head: tokens[paramsStart:bodyStart], - Body: tokens[bodyStart : i+1], + Name: tokens[nameStart].Text(), + Head: tokens[paramsStart:bodyStart], + Body: tokens[bodyStart : i+1], + Variables: map[string]*Variable{}, } nameStart = -1 diff --git a/src/build/Variable.go b/src/build/Variable.go new file mode 100644 index 0000000..14cece9 --- /dev/null +++ b/src/build/Variable.go @@ -0,0 +1,9 @@ +package build + +import "git.akyoto.dev/cli/q/src/build/token" + +// Variable represents a variable in a function. +type Variable struct { + Value token.List + IsConst bool +} diff --git a/src/build/token/Keywords.go b/src/build/token/Keywords.go index adb8de7..a8eec3c 100644 --- a/src/build/token/Keywords.go +++ b/src/build/token/Keywords.go @@ -2,5 +2,6 @@ package token // Keywords defines the keywords used in the language. var Keywords = map[string]bool{ + "let": true, "return": true, } diff --git a/src/build/token/List.go b/src/build/token/List.go index 4c5953e..9cef514 100644 --- a/src/build/token/List.go +++ b/src/build/token/List.go @@ -13,7 +13,7 @@ func (list List) String() string { var last Token for _, t := range list { - if t.Kind == Identifier && last.Kind == Separator { + if last.Kind == Keyword || last.Kind == Separator { builder.WriteByte(' ') } diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 213adb6..53cdbb6 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -70,6 +70,9 @@ func Tokenize(buffer []byte) List { case ']': tokens = append(tokens, Token{ArrayEnd, i, arrayEndBytes}) + case '=', ':', '+', '-', '*', '/', '<', '>', '!': + tokens = append(tokens, Token{Operator, i, buffer[i : i+1]}) + // Separator case ',': tokens = append(tokens, Token{Separator, i, separatorBytes}) @@ -147,3 +150,7 @@ func isNumber(c byte) bool { func isNumberStart(c byte) bool { return isNumber(c) || c == '-' } + +func isOperator(c byte) bool { + return c == '=' || c == ':' || c == '+' || c == '-' || c == '*' || c == '/' || c == '<' || c == '>' || c == '!' +} From 4a2e306e808cae593f8a001f495d6185c46c5ce4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Jun 2024 11:56:02 +0200 Subject: [PATCH 0079/1012] Added missing flag check --- src/build/Function.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/build/Function.go b/src/build/Function.go index 8044009..2a2d670 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -40,7 +40,10 @@ func (f *Function) Compile() { case "let": name := line[1].Text() value := line[3:] - fmt.Println("[variable]", name, value) + + if config.Verbose { + fmt.Println("[variable]", name, value) + } f.Variables[name] = &Variable{ Value: line[3:], From 6de8ac7b9f0f41634617d83569972dc332fc35cc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Jun 2024 11:56:02 +0200 Subject: [PATCH 0080/1012] Added missing flag check --- src/build/Function.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/build/Function.go b/src/build/Function.go index 8044009..2a2d670 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -40,7 +40,10 @@ func (f *Function) Compile() { case "let": name := line[1].Text() value := line[3:] - fmt.Println("[variable]", name, value) + + if config.Verbose { + fmt.Println("[variable]", name, value) + } f.Variables[name] = &Variable{ Value: line[3:], From 06d16b48da69dc97c45b403199350b92e13709ca Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Jun 2024 18:03:54 +0200 Subject: [PATCH 0081/1012] Added short form for variable definitions --- README.md | 6 +++ examples/hello/hello.q | 6 +-- src/build/Function.go | 73 ++++++++++++++++++++++++------------- src/build/token/Keywords.go | 1 - src/build/token/List.go | 2 +- src/build/token/Tokenize.go | 21 +++++++++-- 6 files changed, 76 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 2b6cfb4..bd50be9 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,12 @@ Build a Linux ELF executable from `examples/hello`: ./examples/hello/hello ``` +To produce verbose output, add the `-v` flag: + +```shell +./q build examples/hello -v +``` + ## Documentation ### [main.go](main.go) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 2981aa1..6c3365a 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,7 +1,7 @@ main() { - let write = 1 - let exit = 60 - let stdout = 1 + write := 1 + exit := 60 + stdout := 1 syscall(write, stdout, 4194305, 3) syscall(exit, 0) diff --git a/src/build/Function.go b/src/build/Function.go index 2a2d670..83367de 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -18,6 +18,7 @@ type Function struct { Body token.List Variables map[string]*Variable Assembler asm.Assembler + Error error } // Compile turns a function into machine code. @@ -27,35 +28,53 @@ func (f *Function) Compile() { } for _, line := range f.Lines() { - if config.Verbose { - fmt.Println("[line]", line) - } - if len(line) == 0 { continue } - if line[0].Kind == token.Keyword { - switch line[0].Text() { - case "let": - name := line[1].Text() - value := line[3:] - - if config.Verbose { - fmt.Println("[variable]", name, value) - } - - f.Variables[name] = &Variable{ - Value: line[3:], - IsConst: true, - } - - case "return": - f.Assembler.Return() - } + if config.Verbose { + fmt.Println("[line]", line) } - if line[0].Kind == token.Identifier && line[0].Text() == "syscall" { + err := f.compileInstruction(line) + + if err != nil { + ansi.Red.Println(err) + return + } + } + + f.Assembler.Return() +} + +// compileInstruction compiles a single instruction. +func (f *Function) compileInstruction(line token.List) error { + switch line[0].Kind { + case token.Keyword: + switch line[0].Text() { + case "return": + f.Assembler.Return() + } + + case token.Identifier: + if len(line) >= 2 && line[1].Kind == token.Operator && line[1].Text() == ":=" { + name := line[0].Text() + value := line[2:] + + if config.Verbose { + fmt.Println("[variable]", name, value) + } + + f.Variables[name] = &Variable{ + Value: line[2:], + IsConst: true, + } + + return nil + } + + switch line[0].Text() { + case "syscall": paramTokens := line[2 : len(line)-1] start := 0 i := 0 @@ -78,7 +97,11 @@ func (f *Function) Compile() { switch list[0].Kind { case token.Identifier: name := list[0].Text() - variable := f.Variables[name] + variable, exists := f.Variables[name] + + if !exists { + return fmt.Errorf("Unknown identifier '%s'", name) + } if !variable.IsConst { panic("Not implemented yet") @@ -101,7 +124,7 @@ func (f *Function) Compile() { } } - f.Assembler.Return() + return nil } // Lines returns the lines in the function body. diff --git a/src/build/token/Keywords.go b/src/build/token/Keywords.go index a8eec3c..adb8de7 100644 --- a/src/build/token/Keywords.go +++ b/src/build/token/Keywords.go @@ -2,6 +2,5 @@ package token // Keywords defines the keywords used in the language. var Keywords = map[string]bool{ - "let": true, "return": true, } diff --git a/src/build/token/List.go b/src/build/token/List.go index 9cef514..be3a1ff 100644 --- a/src/build/token/List.go +++ b/src/build/token/List.go @@ -13,7 +13,7 @@ func (list List) String() string { var last Token for _, t := range list { - if last.Kind == Keyword || last.Kind == Separator { + if last.Kind == Keyword || last.Kind == Separator || last.Kind == Operator || t.Kind == Operator { builder.WriteByte(' ') } diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 53cdbb6..ac5d8b0 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -70,9 +70,6 @@ func Tokenize(buffer []byte) List { case ']': tokens = append(tokens, Token{ArrayEnd, i, arrayEndBytes}) - case '=', ':', '+', '-', '*', '/', '<', '>', '!': - tokens = append(tokens, Token{Operator, i, buffer[i : i+1]}) - // Separator case ',': tokens = append(tokens, Token{Separator, i, separatorBytes}) @@ -122,6 +119,24 @@ func Tokenize(buffer []byte) List { continue } + + // Operators + if isOperator(buffer[i]) { + position := i + i++ + + for i < len(buffer) && isOperator(buffer[i]) { + i++ + } + + tokens = append(tokens, Token{ + Operator, + position, + buffer[position:i], + }) + + continue + } } i++ From 65791ea5a160d2c1763429d0fba42aa161ec9ad7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Jun 2024 18:03:54 +0200 Subject: [PATCH 0082/1012] Added short form for variable definitions --- README.md | 6 +++ examples/hello/hello.q | 6 +-- src/build/Function.go | 73 ++++++++++++++++++++++++------------- src/build/token/Keywords.go | 1 - src/build/token/List.go | 2 +- src/build/token/Tokenize.go | 21 +++++++++-- 6 files changed, 76 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 2b6cfb4..bd50be9 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,12 @@ Build a Linux ELF executable from `examples/hello`: ./examples/hello/hello ``` +To produce verbose output, add the `-v` flag: + +```shell +./q build examples/hello -v +``` + ## Documentation ### [main.go](main.go) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 2981aa1..6c3365a 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,7 +1,7 @@ main() { - let write = 1 - let exit = 60 - let stdout = 1 + write := 1 + exit := 60 + stdout := 1 syscall(write, stdout, 4194305, 3) syscall(exit, 0) diff --git a/src/build/Function.go b/src/build/Function.go index 2a2d670..83367de 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -18,6 +18,7 @@ type Function struct { Body token.List Variables map[string]*Variable Assembler asm.Assembler + Error error } // Compile turns a function into machine code. @@ -27,35 +28,53 @@ func (f *Function) Compile() { } for _, line := range f.Lines() { - if config.Verbose { - fmt.Println("[line]", line) - } - if len(line) == 0 { continue } - if line[0].Kind == token.Keyword { - switch line[0].Text() { - case "let": - name := line[1].Text() - value := line[3:] - - if config.Verbose { - fmt.Println("[variable]", name, value) - } - - f.Variables[name] = &Variable{ - Value: line[3:], - IsConst: true, - } - - case "return": - f.Assembler.Return() - } + if config.Verbose { + fmt.Println("[line]", line) } - if line[0].Kind == token.Identifier && line[0].Text() == "syscall" { + err := f.compileInstruction(line) + + if err != nil { + ansi.Red.Println(err) + return + } + } + + f.Assembler.Return() +} + +// compileInstruction compiles a single instruction. +func (f *Function) compileInstruction(line token.List) error { + switch line[0].Kind { + case token.Keyword: + switch line[0].Text() { + case "return": + f.Assembler.Return() + } + + case token.Identifier: + if len(line) >= 2 && line[1].Kind == token.Operator && line[1].Text() == ":=" { + name := line[0].Text() + value := line[2:] + + if config.Verbose { + fmt.Println("[variable]", name, value) + } + + f.Variables[name] = &Variable{ + Value: line[2:], + IsConst: true, + } + + return nil + } + + switch line[0].Text() { + case "syscall": paramTokens := line[2 : len(line)-1] start := 0 i := 0 @@ -78,7 +97,11 @@ func (f *Function) Compile() { switch list[0].Kind { case token.Identifier: name := list[0].Text() - variable := f.Variables[name] + variable, exists := f.Variables[name] + + if !exists { + return fmt.Errorf("Unknown identifier '%s'", name) + } if !variable.IsConst { panic("Not implemented yet") @@ -101,7 +124,7 @@ func (f *Function) Compile() { } } - f.Assembler.Return() + return nil } // Lines returns the lines in the function body. diff --git a/src/build/token/Keywords.go b/src/build/token/Keywords.go index a8eec3c..adb8de7 100644 --- a/src/build/token/Keywords.go +++ b/src/build/token/Keywords.go @@ -2,6 +2,5 @@ package token // Keywords defines the keywords used in the language. var Keywords = map[string]bool{ - "let": true, "return": true, } diff --git a/src/build/token/List.go b/src/build/token/List.go index 9cef514..be3a1ff 100644 --- a/src/build/token/List.go +++ b/src/build/token/List.go @@ -13,7 +13,7 @@ func (list List) String() string { var last Token for _, t := range list { - if last.Kind == Keyword || last.Kind == Separator { + if last.Kind == Keyword || last.Kind == Separator || last.Kind == Operator || t.Kind == Operator { builder.WriteByte(' ') } diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 53cdbb6..ac5d8b0 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -70,9 +70,6 @@ func Tokenize(buffer []byte) List { case ']': tokens = append(tokens, Token{ArrayEnd, i, arrayEndBytes}) - case '=', ':', '+', '-', '*', '/', '<', '>', '!': - tokens = append(tokens, Token{Operator, i, buffer[i : i+1]}) - // Separator case ',': tokens = append(tokens, Token{Separator, i, separatorBytes}) @@ -122,6 +119,24 @@ func Tokenize(buffer []byte) List { continue } + + // Operators + if isOperator(buffer[i]) { + position := i + i++ + + for i < len(buffer) && isOperator(buffer[i]) { + i++ + } + + tokens = append(tokens, Token{ + Operator, + position, + buffer[position:i], + }) + + continue + } } i++ From a4ecaf062216b1aa576a3444c838f14211a713cb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Jun 2024 11:36:57 +0200 Subject: [PATCH 0083/1012] Added define operator --- examples/hello/hello.q | 2 +- src/build/Function.go | 21 ++++++++++++++++++--- src/build/asm/Assembler.go | 13 +++---------- src/build/asm/Instruction.go | 12 +----------- src/build/asm/Instructions.go | 4 ++-- src/build/asm/RegisterNumber.go | 11 ++++++++++- src/build/token/Kind.go | 4 ++++ src/build/token/List.go | 2 +- src/build/token/Tokenize.go | 33 +++++++++++---------------------- 9 files changed, 51 insertions(+), 51 deletions(-) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 6c3365a..8754a01 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,7 +1,7 @@ main() { write := 1 - exit := 60 stdout := 1 + exit := 60 syscall(write, stdout, 4194305, 3) syscall(exit, 0) diff --git a/src/build/Function.go b/src/build/Function.go index 83367de..5fb3f25 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -33,7 +33,8 @@ func (f *Function) Compile() { } if config.Verbose { - fmt.Println("[line]", line) + ansi.Dim.Print("[ ] ") + fmt.Println(line) } err := f.compileInstruction(line) @@ -45,6 +46,19 @@ func (f *Function) Compile() { } f.Assembler.Return() + + if config.Verbose { + for _, x := range f.Assembler.Instructions { + ansi.Dim.Print("[asm] ") + fmt.Print(x.Mnemonic.String()) + + if x.Data != nil { + fmt.Print(" " + x.Data.String()) + } + + fmt.Print("\n") + } + } } // compileInstruction compiles a single instruction. @@ -57,12 +71,13 @@ func (f *Function) compileInstruction(line token.List) error { } case token.Identifier: - if len(line) >= 2 && line[1].Kind == token.Operator && line[1].Text() == ":=" { + if len(line) >= 2 && line[1].Kind == token.Define { name := line[0].Text() value := line[2:] if config.Verbose { - fmt.Println("[variable]", name, value) + ansi.Dim.Printf("[var] ") + fmt.Println(name, value) } f.Variables[name] = &Variable{ diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 01379f9..d0186e7 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -2,7 +2,6 @@ package asm import ( "encoding/binary" - "fmt" "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/config" @@ -29,12 +28,12 @@ func (a *Assembler) Finalize() ([]byte, []byte) { for _, x := range a.Instructions { switch x.Mnemonic { case MOVE: - code = x64.MoveRegNum32(code, uint8(x.Data.(RegisterNumber).Register), uint32(x.Data.(RegisterNumber).Number)) + code = x64.MoveRegNum32(code, uint8(x.Data.(*RegisterNumber).Register), uint32(x.Data.(*RegisterNumber).Number)) - if x.Data.(RegisterNumber).IsPointer { + if x.Data.(*RegisterNumber).IsPointer { pointers = append(pointers, Pointer{ Position: Address(len(code) - 4), - Address: Address(x.Data.(RegisterNumber).Number), + Address: Address(x.Data.(*RegisterNumber).Number), }) } @@ -49,12 +48,6 @@ func (a *Assembler) Finalize() ([]byte, []byte) { } } - if config.Verbose { - for _, x := range a.Instructions { - fmt.Println("[asm]", x.String()) - } - } - dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) for _, pointer := range pointers { diff --git a/src/build/asm/Instruction.go b/src/build/asm/Instruction.go index 966ce75..5b2bbce 100644 --- a/src/build/asm/Instruction.go +++ b/src/build/asm/Instruction.go @@ -5,15 +5,5 @@ import "fmt" // Instruction represents a single instruction which can be converted to machine code. type Instruction struct { Mnemonic Mnemonic - Data interface{} -} - -// String returns a human readable version. -func (x *Instruction) String() string { - switch data := x.Data.(type) { - case RegisterNumber: - return fmt.Sprintf("%s %s, %x", x.Mnemonic, data.Register, data.Number) - default: - return x.Mnemonic.String() - } + Data fmt.Stringer } diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index f201af9..de5f5d8 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -6,7 +6,7 @@ import "git.akyoto.dev/cli/q/src/build/cpu" func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: MOVE, - Data: RegisterNumber{ + Data: &RegisterNumber{ Register: reg, Number: number, IsPointer: false, @@ -18,7 +18,7 @@ func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) { func (a *Assembler) MoveRegisterAddress(reg cpu.Register, address Address) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: MOVE, - Data: RegisterNumber{ + Data: &RegisterNumber{ Register: reg, Number: uint64(address), IsPointer: true, diff --git a/src/build/asm/RegisterNumber.go b/src/build/asm/RegisterNumber.go index 445acd1..bfd7321 100644 --- a/src/build/asm/RegisterNumber.go +++ b/src/build/asm/RegisterNumber.go @@ -1,6 +1,10 @@ package asm -import "git.akyoto.dev/cli/q/src/build/cpu" +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/cpu" +) // RegisterNumber operates with a register and a number. type RegisterNumber struct { @@ -8,3 +12,8 @@ type RegisterNumber struct { Number uint64 IsPointer bool } + +// String returns a human readable version. +func (data *RegisterNumber) String() string { + return fmt.Sprintf("%s, %x", data.Register, data.Number) +} diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index b221442..f7b8e80 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -25,6 +25,9 @@ const ( // Number represents a series of numerical characters. Number + // Define represents the assignment operator `:=` for a new variable. + Define + // Operator represents a mathematical operator. Operator @@ -63,6 +66,7 @@ func (kind Kind) String() string { "Keyword", "String", "Number", + "Define", "Operator", "Separator", "Comment", diff --git a/src/build/token/List.go b/src/build/token/List.go index be3a1ff..30f344b 100644 --- a/src/build/token/List.go +++ b/src/build/token/List.go @@ -13,7 +13,7 @@ func (list List) String() string { var last Token for _, t := range list { - if last.Kind == Keyword || last.Kind == Separator || last.Kind == Operator || t.Kind == Operator { + if last.Kind == Keyword || last.Kind == Separator || last.Kind == Define || t.Kind == Define { builder.WriteByte(' ') } diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index ac5d8b0..c89ddba 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -1,5 +1,7 @@ package token +import "bytes" + // Pre-allocate these byte buffers so we can re-use them // instead of allocating a new buffer every time. var ( @@ -10,6 +12,7 @@ var ( arrayStartBytes = []byte{'['} arrayEndBytes = []byte{']'} separatorBytes = []byte{','} + defineBytes = []byte{':', '='} newLineBytes = []byte{'\n'} ) @@ -38,12 +41,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{ - String, - start, - buffer[start:end], - }) - + tokens = append(tokens, Token{String, start, buffer[start:end]}) continue // Parentheses start @@ -88,11 +86,7 @@ func Tokenize(buffer []byte) List { i++ } - token := Token{ - Identifier, - position, - buffer[position:i], - } + token := Token{Identifier, position, buffer[position:i]} if Keywords[string(token.Bytes)] { token.Kind = Keyword @@ -111,12 +105,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{ - Number, - position, - buffer[position:i], - }) - + tokens = append(tokens, Token{Number, position, buffer[position:i]}) continue } @@ -129,12 +118,12 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{ - Operator, - position, - buffer[position:i], - }) + if bytes.Equal(buffer[position:i], defineBytes) { + tokens = append(tokens, Token{Define, position, defineBytes}) + continue + } + tokens = append(tokens, Token{Operator, position, buffer[position:i]}) continue } } From cf696a6f1068a56dd8b0c5464ba19ae220948959 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Jun 2024 11:36:57 +0200 Subject: [PATCH 0084/1012] Added define operator --- examples/hello/hello.q | 2 +- src/build/Function.go | 21 ++++++++++++++++++--- src/build/asm/Assembler.go | 13 +++---------- src/build/asm/Instruction.go | 12 +----------- src/build/asm/Instructions.go | 4 ++-- src/build/asm/RegisterNumber.go | 11 ++++++++++- src/build/token/Kind.go | 4 ++++ src/build/token/List.go | 2 +- src/build/token/Tokenize.go | 33 +++++++++++---------------------- 9 files changed, 51 insertions(+), 51 deletions(-) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 6c3365a..8754a01 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,7 +1,7 @@ main() { write := 1 - exit := 60 stdout := 1 + exit := 60 syscall(write, stdout, 4194305, 3) syscall(exit, 0) diff --git a/src/build/Function.go b/src/build/Function.go index 83367de..5fb3f25 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -33,7 +33,8 @@ func (f *Function) Compile() { } if config.Verbose { - fmt.Println("[line]", line) + ansi.Dim.Print("[ ] ") + fmt.Println(line) } err := f.compileInstruction(line) @@ -45,6 +46,19 @@ func (f *Function) Compile() { } f.Assembler.Return() + + if config.Verbose { + for _, x := range f.Assembler.Instructions { + ansi.Dim.Print("[asm] ") + fmt.Print(x.Mnemonic.String()) + + if x.Data != nil { + fmt.Print(" " + x.Data.String()) + } + + fmt.Print("\n") + } + } } // compileInstruction compiles a single instruction. @@ -57,12 +71,13 @@ func (f *Function) compileInstruction(line token.List) error { } case token.Identifier: - if len(line) >= 2 && line[1].Kind == token.Operator && line[1].Text() == ":=" { + if len(line) >= 2 && line[1].Kind == token.Define { name := line[0].Text() value := line[2:] if config.Verbose { - fmt.Println("[variable]", name, value) + ansi.Dim.Printf("[var] ") + fmt.Println(name, value) } f.Variables[name] = &Variable{ diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 01379f9..d0186e7 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -2,7 +2,6 @@ package asm import ( "encoding/binary" - "fmt" "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/config" @@ -29,12 +28,12 @@ func (a *Assembler) Finalize() ([]byte, []byte) { for _, x := range a.Instructions { switch x.Mnemonic { case MOVE: - code = x64.MoveRegNum32(code, uint8(x.Data.(RegisterNumber).Register), uint32(x.Data.(RegisterNumber).Number)) + code = x64.MoveRegNum32(code, uint8(x.Data.(*RegisterNumber).Register), uint32(x.Data.(*RegisterNumber).Number)) - if x.Data.(RegisterNumber).IsPointer { + if x.Data.(*RegisterNumber).IsPointer { pointers = append(pointers, Pointer{ Position: Address(len(code) - 4), - Address: Address(x.Data.(RegisterNumber).Number), + Address: Address(x.Data.(*RegisterNumber).Number), }) } @@ -49,12 +48,6 @@ func (a *Assembler) Finalize() ([]byte, []byte) { } } - if config.Verbose { - for _, x := range a.Instructions { - fmt.Println("[asm]", x.String()) - } - } - dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) for _, pointer := range pointers { diff --git a/src/build/asm/Instruction.go b/src/build/asm/Instruction.go index 966ce75..5b2bbce 100644 --- a/src/build/asm/Instruction.go +++ b/src/build/asm/Instruction.go @@ -5,15 +5,5 @@ import "fmt" // Instruction represents a single instruction which can be converted to machine code. type Instruction struct { Mnemonic Mnemonic - Data interface{} -} - -// String returns a human readable version. -func (x *Instruction) String() string { - switch data := x.Data.(type) { - case RegisterNumber: - return fmt.Sprintf("%s %s, %x", x.Mnemonic, data.Register, data.Number) - default: - return x.Mnemonic.String() - } + Data fmt.Stringer } diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index f201af9..de5f5d8 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -6,7 +6,7 @@ import "git.akyoto.dev/cli/q/src/build/cpu" func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: MOVE, - Data: RegisterNumber{ + Data: &RegisterNumber{ Register: reg, Number: number, IsPointer: false, @@ -18,7 +18,7 @@ func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) { func (a *Assembler) MoveRegisterAddress(reg cpu.Register, address Address) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: MOVE, - Data: RegisterNumber{ + Data: &RegisterNumber{ Register: reg, Number: uint64(address), IsPointer: true, diff --git a/src/build/asm/RegisterNumber.go b/src/build/asm/RegisterNumber.go index 445acd1..bfd7321 100644 --- a/src/build/asm/RegisterNumber.go +++ b/src/build/asm/RegisterNumber.go @@ -1,6 +1,10 @@ package asm -import "git.akyoto.dev/cli/q/src/build/cpu" +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/cpu" +) // RegisterNumber operates with a register and a number. type RegisterNumber struct { @@ -8,3 +12,8 @@ type RegisterNumber struct { Number uint64 IsPointer bool } + +// String returns a human readable version. +func (data *RegisterNumber) String() string { + return fmt.Sprintf("%s, %x", data.Register, data.Number) +} diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index b221442..f7b8e80 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -25,6 +25,9 @@ const ( // Number represents a series of numerical characters. Number + // Define represents the assignment operator `:=` for a new variable. + Define + // Operator represents a mathematical operator. Operator @@ -63,6 +66,7 @@ func (kind Kind) String() string { "Keyword", "String", "Number", + "Define", "Operator", "Separator", "Comment", diff --git a/src/build/token/List.go b/src/build/token/List.go index be3a1ff..30f344b 100644 --- a/src/build/token/List.go +++ b/src/build/token/List.go @@ -13,7 +13,7 @@ func (list List) String() string { var last Token for _, t := range list { - if last.Kind == Keyword || last.Kind == Separator || last.Kind == Operator || t.Kind == Operator { + if last.Kind == Keyword || last.Kind == Separator || last.Kind == Define || t.Kind == Define { builder.WriteByte(' ') } diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index ac5d8b0..c89ddba 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -1,5 +1,7 @@ package token +import "bytes" + // Pre-allocate these byte buffers so we can re-use them // instead of allocating a new buffer every time. var ( @@ -10,6 +12,7 @@ var ( arrayStartBytes = []byte{'['} arrayEndBytes = []byte{']'} separatorBytes = []byte{','} + defineBytes = []byte{':', '='} newLineBytes = []byte{'\n'} ) @@ -38,12 +41,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{ - String, - start, - buffer[start:end], - }) - + tokens = append(tokens, Token{String, start, buffer[start:end]}) continue // Parentheses start @@ -88,11 +86,7 @@ func Tokenize(buffer []byte) List { i++ } - token := Token{ - Identifier, - position, - buffer[position:i], - } + token := Token{Identifier, position, buffer[position:i]} if Keywords[string(token.Bytes)] { token.Kind = Keyword @@ -111,12 +105,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{ - Number, - position, - buffer[position:i], - }) - + tokens = append(tokens, Token{Number, position, buffer[position:i]}) continue } @@ -129,12 +118,12 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{ - Operator, - position, - buffer[position:i], - }) + if bytes.Equal(buffer[position:i], defineBytes) { + tokens = append(tokens, Token{Define, position, defineBytes}) + continue + } + tokens = append(tokens, Token{Operator, position, buffer[position:i]}) continue } } From 78cde0d0bdffc50ffef2ac2a0bf8ea6d8273e0e7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Jun 2024 14:46:44 +0200 Subject: [PATCH 0085/1012] Improved instruction parser --- src/build/Compile.go | 26 +++++--- src/build/File.go | 9 +++ src/build/Function.go | 114 +++++++++++++++++++++--------------- src/build/Scan.go | 6 ++ src/build/Variable.go | 1 + src/build/token/Token.go | 7 +++ src/errors/CompileErrors.go | 5 ++ 7 files changed, 114 insertions(+), 54 deletions(-) create mode 100644 src/build/File.go create mode 100644 src/errors/CompileErrors.go diff --git a/src/build/Compile.go b/src/build/Compile.go index a40ab2c..f6f1736 100644 --- a/src/build/Compile.go +++ b/src/build/Compile.go @@ -6,7 +6,6 @@ import ( // Compile compiles all the functions. func Compile(functions <-chan *Function, errors <-chan error) (map[string]*Function, error) { - wg := sync.WaitGroup{} allFunctions := map[string]*Function{} for functions != nil || errors != nil { @@ -25,17 +24,28 @@ func Compile(functions <-chan *Function, errors <-chan error) (map[string]*Funct continue } - wg.Add(1) - - go func() { - defer wg.Done() - function.Compile() - }() - allFunctions[function.Name] = function } } + wg := sync.WaitGroup{} + + for _, function := range allFunctions { + wg.Add(1) + + go func() { + defer wg.Done() + function.Compile() + }() + } + wg.Wait() + + for _, function := range allFunctions { + if function.Error != nil { + return nil, function.Error + } + } + return allFunctions, nil } diff --git a/src/build/File.go b/src/build/File.go new file mode 100644 index 0000000..760bfcc --- /dev/null +++ b/src/build/File.go @@ -0,0 +1,9 @@ +package build + +import "git.akyoto.dev/cli/q/src/build/token" + +// File represents a single source file. +type File struct { + Tokens token.List + Path string +} diff --git a/src/build/Function.go b/src/build/Function.go index 5fb3f25..18131fd 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -14,6 +14,7 @@ import ( // Function represents a function. type Function struct { Name string + File *File Head token.List Body token.List Variables map[string]*Variable @@ -24,32 +25,60 @@ type Function struct { // Compile turns a function into machine code. func (f *Function) Compile() { if config.Verbose { - ansi.Underline.Println(f.Name) + ansi.Bold.Println(f.Name) } - for _, line := range f.Lines() { - if len(line) == 0 { + start := 0 + groupLevel := 0 + + for i, t := range f.Body { + if start == i && (t.Kind == token.NewLine || t.Kind == token.BlockStart || t.Kind == token.BlockEnd) { + start = i + 1 continue } - if config.Verbose { - ansi.Dim.Print("[ ] ") - fmt.Println(line) - } + switch t.Kind { + case token.NewLine: + if groupLevel > 0 { + continue + } - err := f.compileInstruction(line) + if start != -1 { + instruction := f.Body[start:i] + err := f.CompileInstruction(instruction) - if err != nil { - ansi.Red.Println(err) - return + if err != nil { + f.Error = err + return + } + + start = -1 + } + + start = i + 1 + + case token.GroupStart: + groupLevel++ + + case token.GroupEnd: + groupLevel-- + + case token.BlockStart: + // Add scope + + case token.BlockEnd: + // Remove scope } } f.Assembler.Return() if config.Verbose { + fmt.Println() + ansi.Bold.Println(f.Name + ".asm") + for _, x := range f.Assembler.Instructions { - ansi.Dim.Print("[asm] ") + ansi.Dim.Print("│ ") fmt.Print(x.Mnemonic.String()) if x.Data != nil { @@ -61,27 +90,44 @@ func (f *Function) Compile() { } } -// compileInstruction compiles a single instruction. -func (f *Function) compileInstruction(line token.List) error { - switch line[0].Kind { - case token.Keyword: +// CompileInstruction compiles a single instruction. +func (f *Function) CompileInstruction(line token.List) error { + if config.Verbose { + ansi.Dim.Print("│ ") + fmt.Println(line) + } + + if line[0].Kind == token.Keyword { switch line[0].Text() { case "return": f.Assembler.Return() } - case token.Identifier: - if len(line) >= 2 && line[1].Kind == token.Define { + return nil + } + + if line[0].Kind == token.Identifier { + if len(line) < 2 { + return fmt.Errorf("error to be implemented") + } + + if line[1].Kind == token.Define { name := line[0].Text() value := line[2:] + if len(value) == 0 { + return fmt.Errorf("error to be implemented") + // return errors.New(errors.MissingAssignmentValue, f.File.Path, f.File.Tokens, f.Cursor) + } + if config.Verbose { - ansi.Dim.Printf("[var] ") - fmt.Println(name, value) + ansi.Dim.Printf("├── var ") + fmt.Println(name, "=", value) } f.Variables[name] = &Variable{ - Value: line[2:], + Name: name, + Value: value, IsConst: true, } @@ -115,7 +161,7 @@ func (f *Function) compileInstruction(line token.List) error { variable, exists := f.Variables[name] if !exists { - return fmt.Errorf("Unknown identifier '%s'", name) + panic("Unknown identifier " + name) } if !variable.IsConst { @@ -142,30 +188,6 @@ func (f *Function) compileInstruction(line token.List) error { return nil } -// Lines returns the lines in the function body. -func (f *Function) Lines() []token.List { - var ( - lines []token.List - start = 0 - i = 0 - ) - - for i < len(f.Body) { - if f.Body[i].Kind == token.NewLine { - lines = append(lines, f.Body[start:i]) - start = i + 1 - } - - i++ - } - - if i != start { - lines = append(lines, f.Body[start:i]) - } - - return lines -} - // String returns the function name. func (f *Function) String() string { return f.Name diff --git a/src/build/Scan.go b/src/build/Scan.go index 318c598..87f6033 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -86,6 +86,11 @@ func scanFile(path string, functions chan<- *Function) error { tokens := token.Tokenize(contents) + file := &File{ + Tokens: tokens, + Path: path, + } + var ( i = 0 groupLevel = 0 @@ -216,6 +221,7 @@ func scanFile(path string, functions chan<- *Function) error { functions <- &Function{ Name: tokens[nameStart].Text(), + File: file, Head: tokens[paramsStart:bodyStart], Body: tokens[bodyStart : i+1], Variables: map[string]*Variable{}, diff --git a/src/build/Variable.go b/src/build/Variable.go index 14cece9..46190e2 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -4,6 +4,7 @@ import "git.akyoto.dev/cli/q/src/build/token" // Variable represents a variable in a function. type Variable struct { + Name string Value token.List IsConst bool } diff --git a/src/build/token/Token.go b/src/build/token/Token.go index 78d6005..7c10db2 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -1,5 +1,7 @@ package token +import "fmt" + // Token represents a single element in a source file. // The characters that make up an identifier are grouped into a single token. // This makes parsing easier and allows us to do better syntax checks. @@ -9,6 +11,11 @@ type Token struct { Bytes []byte } +// String creates a human readable representation for debugging purposes. +func (t Token) String() string { + return fmt.Sprintf("%s %s", t.Kind, t.Text()) +} + // Text returns the token text. func (t Token) Text() string { return string(t.Bytes) diff --git a/src/errors/CompileErrors.go b/src/errors/CompileErrors.go new file mode 100644 index 0000000..2c5ac46 --- /dev/null +++ b/src/errors/CompileErrors.go @@ -0,0 +1,5 @@ +package errors + +var ( + MissingAssignmentValue = &Base{"Missing assignment value"} +) From 57f1da10fe49570dc9939c89f40cde9db5bd695d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Jun 2024 14:46:44 +0200 Subject: [PATCH 0086/1012] Improved instruction parser --- src/build/Compile.go | 26 +++++--- src/build/File.go | 9 +++ src/build/Function.go | 114 +++++++++++++++++++++--------------- src/build/Scan.go | 6 ++ src/build/Variable.go | 1 + src/build/token/Token.go | 7 +++ src/errors/CompileErrors.go | 5 ++ 7 files changed, 114 insertions(+), 54 deletions(-) create mode 100644 src/build/File.go create mode 100644 src/errors/CompileErrors.go diff --git a/src/build/Compile.go b/src/build/Compile.go index a40ab2c..f6f1736 100644 --- a/src/build/Compile.go +++ b/src/build/Compile.go @@ -6,7 +6,6 @@ import ( // Compile compiles all the functions. func Compile(functions <-chan *Function, errors <-chan error) (map[string]*Function, error) { - wg := sync.WaitGroup{} allFunctions := map[string]*Function{} for functions != nil || errors != nil { @@ -25,17 +24,28 @@ func Compile(functions <-chan *Function, errors <-chan error) (map[string]*Funct continue } - wg.Add(1) - - go func() { - defer wg.Done() - function.Compile() - }() - allFunctions[function.Name] = function } } + wg := sync.WaitGroup{} + + for _, function := range allFunctions { + wg.Add(1) + + go func() { + defer wg.Done() + function.Compile() + }() + } + wg.Wait() + + for _, function := range allFunctions { + if function.Error != nil { + return nil, function.Error + } + } + return allFunctions, nil } diff --git a/src/build/File.go b/src/build/File.go new file mode 100644 index 0000000..760bfcc --- /dev/null +++ b/src/build/File.go @@ -0,0 +1,9 @@ +package build + +import "git.akyoto.dev/cli/q/src/build/token" + +// File represents a single source file. +type File struct { + Tokens token.List + Path string +} diff --git a/src/build/Function.go b/src/build/Function.go index 5fb3f25..18131fd 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -14,6 +14,7 @@ import ( // Function represents a function. type Function struct { Name string + File *File Head token.List Body token.List Variables map[string]*Variable @@ -24,32 +25,60 @@ type Function struct { // Compile turns a function into machine code. func (f *Function) Compile() { if config.Verbose { - ansi.Underline.Println(f.Name) + ansi.Bold.Println(f.Name) } - for _, line := range f.Lines() { - if len(line) == 0 { + start := 0 + groupLevel := 0 + + for i, t := range f.Body { + if start == i && (t.Kind == token.NewLine || t.Kind == token.BlockStart || t.Kind == token.BlockEnd) { + start = i + 1 continue } - if config.Verbose { - ansi.Dim.Print("[ ] ") - fmt.Println(line) - } + switch t.Kind { + case token.NewLine: + if groupLevel > 0 { + continue + } - err := f.compileInstruction(line) + if start != -1 { + instruction := f.Body[start:i] + err := f.CompileInstruction(instruction) - if err != nil { - ansi.Red.Println(err) - return + if err != nil { + f.Error = err + return + } + + start = -1 + } + + start = i + 1 + + case token.GroupStart: + groupLevel++ + + case token.GroupEnd: + groupLevel-- + + case token.BlockStart: + // Add scope + + case token.BlockEnd: + // Remove scope } } f.Assembler.Return() if config.Verbose { + fmt.Println() + ansi.Bold.Println(f.Name + ".asm") + for _, x := range f.Assembler.Instructions { - ansi.Dim.Print("[asm] ") + ansi.Dim.Print("│ ") fmt.Print(x.Mnemonic.String()) if x.Data != nil { @@ -61,27 +90,44 @@ func (f *Function) Compile() { } } -// compileInstruction compiles a single instruction. -func (f *Function) compileInstruction(line token.List) error { - switch line[0].Kind { - case token.Keyword: +// CompileInstruction compiles a single instruction. +func (f *Function) CompileInstruction(line token.List) error { + if config.Verbose { + ansi.Dim.Print("│ ") + fmt.Println(line) + } + + if line[0].Kind == token.Keyword { switch line[0].Text() { case "return": f.Assembler.Return() } - case token.Identifier: - if len(line) >= 2 && line[1].Kind == token.Define { + return nil + } + + if line[0].Kind == token.Identifier { + if len(line) < 2 { + return fmt.Errorf("error to be implemented") + } + + if line[1].Kind == token.Define { name := line[0].Text() value := line[2:] + if len(value) == 0 { + return fmt.Errorf("error to be implemented") + // return errors.New(errors.MissingAssignmentValue, f.File.Path, f.File.Tokens, f.Cursor) + } + if config.Verbose { - ansi.Dim.Printf("[var] ") - fmt.Println(name, value) + ansi.Dim.Printf("├── var ") + fmt.Println(name, "=", value) } f.Variables[name] = &Variable{ - Value: line[2:], + Name: name, + Value: value, IsConst: true, } @@ -115,7 +161,7 @@ func (f *Function) compileInstruction(line token.List) error { variable, exists := f.Variables[name] if !exists { - return fmt.Errorf("Unknown identifier '%s'", name) + panic("Unknown identifier " + name) } if !variable.IsConst { @@ -142,30 +188,6 @@ func (f *Function) compileInstruction(line token.List) error { return nil } -// Lines returns the lines in the function body. -func (f *Function) Lines() []token.List { - var ( - lines []token.List - start = 0 - i = 0 - ) - - for i < len(f.Body) { - if f.Body[i].Kind == token.NewLine { - lines = append(lines, f.Body[start:i]) - start = i + 1 - } - - i++ - } - - if i != start { - lines = append(lines, f.Body[start:i]) - } - - return lines -} - // String returns the function name. func (f *Function) String() string { return f.Name diff --git a/src/build/Scan.go b/src/build/Scan.go index 318c598..87f6033 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -86,6 +86,11 @@ func scanFile(path string, functions chan<- *Function) error { tokens := token.Tokenize(contents) + file := &File{ + Tokens: tokens, + Path: path, + } + var ( i = 0 groupLevel = 0 @@ -216,6 +221,7 @@ func scanFile(path string, functions chan<- *Function) error { functions <- &Function{ Name: tokens[nameStart].Text(), + File: file, Head: tokens[paramsStart:bodyStart], Body: tokens[bodyStart : i+1], Variables: map[string]*Variable{}, diff --git a/src/build/Variable.go b/src/build/Variable.go index 14cece9..46190e2 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -4,6 +4,7 @@ import "git.akyoto.dev/cli/q/src/build/token" // Variable represents a variable in a function. type Variable struct { + Name string Value token.List IsConst bool } diff --git a/src/build/token/Token.go b/src/build/token/Token.go index 78d6005..7c10db2 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -1,5 +1,7 @@ package token +import "fmt" + // Token represents a single element in a source file. // The characters that make up an identifier are grouped into a single token. // This makes parsing easier and allows us to do better syntax checks. @@ -9,6 +11,11 @@ type Token struct { Bytes []byte } +// String creates a human readable representation for debugging purposes. +func (t Token) String() string { + return fmt.Sprintf("%s %s", t.Kind, t.Text()) +} + // Text returns the token text. func (t Token) Text() string { return string(t.Bytes) diff --git a/src/errors/CompileErrors.go b/src/errors/CompileErrors.go new file mode 100644 index 0000000..2c5ac46 --- /dev/null +++ b/src/errors/CompileErrors.go @@ -0,0 +1,5 @@ +package errors + +var ( + MissingAssignmentValue = &Base{"Missing assignment value"} +) From a48f2868fb29471dc499e0ca92d5dbab3509b284 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Jun 2024 18:42:31 +0200 Subject: [PATCH 0087/1012] Improved error handling --- src/build/Function.go | 30 +++++++----- src/build/Scan.go | 24 +++++----- src/build/{ => fs}/File.go | 2 +- src/build/{directory => fs}/Walk.go | 2 +- src/build/{directory => fs}/Walk_test.go | 8 ++-- src/build/token/Token.go | 5 ++ src/errors/Error.go | 60 ++++++++++++------------ 7 files changed, 71 insertions(+), 60 deletions(-) rename src/build/{ => fs}/File.go (91%) rename src/build/{directory => fs}/Walk.go (98%) rename src/build/{directory => fs}/Walk_test.go (62%) diff --git a/src/build/Function.go b/src/build/Function.go index 18131fd..cc5e787 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -7,14 +7,16 @@ import ( "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/go/color/ansi" ) // Function represents a function. type Function struct { Name string - File *File + File *fs.File Head token.List Body token.List Variables map[string]*Variable @@ -74,19 +76,24 @@ func (f *Function) Compile() { f.Assembler.Return() if config.Verbose { - fmt.Println() - ansi.Bold.Println(f.Name + ".asm") + f.PrintAsm() + } +} - for _, x := range f.Assembler.Instructions { - ansi.Dim.Print("│ ") - fmt.Print(x.Mnemonic.String()) +// PrintAsm shows the assembly instructions. +func (f *Function) PrintAsm() { + fmt.Println() + ansi.Bold.Println(f.Name + ".asm") - if x.Data != nil { - fmt.Print(" " + x.Data.String()) - } + for _, x := range f.Assembler.Instructions { + ansi.Dim.Print("│ ") + fmt.Print(x.Mnemonic.String()) - fmt.Print("\n") + if x.Data != nil { + fmt.Print(" " + x.Data.String()) } + + fmt.Print("\n") } } @@ -116,8 +123,7 @@ func (f *Function) CompileInstruction(line token.List) error { value := line[2:] if len(value) == 0 { - return fmt.Errorf("error to be implemented") - // return errors.New(errors.MissingAssignmentValue, f.File.Path, f.File.Tokens, f.Cursor) + return errors.New(errors.MissingAssignmentValue, f.File, line[1].After()) } if config.Verbose { diff --git a/src/build/Scan.go b/src/build/Scan.go index 87f6033..0a2c903 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -6,7 +6,7 @@ import ( "strings" "sync" - "git.akyoto.dev/cli/q/src/build/directory" + "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" ) @@ -38,7 +38,7 @@ func scan(files []string, functions chan<- *Function, errors chan<- error) { } if stat.IsDir() { - err = directory.Walk(file, func(name string) { + err = fs.Walk(file, func(name string) { if !strings.HasSuffix(name, ".q") { return } @@ -86,7 +86,7 @@ func scanFile(path string, functions chan<- *Function) error { tokens := token.Tokenize(contents) - file := &File{ + file := &fs.File{ Tokens: tokens, Path: path, } @@ -118,7 +118,7 @@ func scanFile(path string, functions chan<- *Function) error { return nil } - return errors.New(errors.ExpectedFunctionName, path, tokens, i) + return errors.New(errors.ExpectedFunctionName, file, tokens[i].Position) } // Function parameters @@ -138,7 +138,7 @@ func scanFile(path string, functions chan<- *Function) error { groupLevel-- if groupLevel < 0 { - return errors.New(errors.MissingGroupStart, path, tokens, i) + return errors.New(errors.MissingGroupStart, file, tokens[i].Position) } i++ @@ -152,11 +152,11 @@ func scanFile(path string, functions chan<- *Function) error { if tokens[i].Kind == token.EOF { if groupLevel > 0 { - return errors.New(errors.MissingGroupEnd, path, tokens, i) + return errors.New(errors.MissingGroupEnd, file, tokens[i].Position) } if paramsStart == -1 { - return errors.New(errors.ExpectedFunctionParameters, path, tokens, i) + return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) } return nil @@ -167,7 +167,7 @@ func scanFile(path string, functions chan<- *Function) error { continue } - return errors.New(errors.ExpectedFunctionParameters, path, tokens, i) + return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) } // Function definition @@ -187,7 +187,7 @@ func scanFile(path string, functions chan<- *Function) error { blockLevel-- if blockLevel < 0 { - return errors.New(errors.MissingBlockStart, path, tokens, i) + return errors.New(errors.MissingBlockStart, file, tokens[i].Position) } i++ @@ -201,11 +201,11 @@ func scanFile(path string, functions chan<- *Function) error { if tokens[i].Kind == token.EOF { if blockLevel > 0 { - return errors.New(errors.MissingBlockEnd, path, tokens, i) + return errors.New(errors.MissingBlockEnd, file, tokens[i].Position) } if bodyStart == -1 { - return errors.New(errors.ExpectedFunctionDefinition, path, tokens, i) + return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) } return nil @@ -216,7 +216,7 @@ func scanFile(path string, functions chan<- *Function) error { continue } - return errors.New(errors.ExpectedFunctionDefinition, path, tokens, i) + return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) } functions <- &Function{ diff --git a/src/build/File.go b/src/build/fs/File.go similarity index 91% rename from src/build/File.go rename to src/build/fs/File.go index 760bfcc..53d49c2 100644 --- a/src/build/File.go +++ b/src/build/fs/File.go @@ -1,4 +1,4 @@ -package build +package fs import "git.akyoto.dev/cli/q/src/build/token" diff --git a/src/build/directory/Walk.go b/src/build/fs/Walk.go similarity index 98% rename from src/build/directory/Walk.go rename to src/build/fs/Walk.go index e2cc495..78d6043 100644 --- a/src/build/directory/Walk.go +++ b/src/build/fs/Walk.go @@ -1,4 +1,4 @@ -package directory +package fs import ( "syscall" diff --git a/src/build/directory/Walk_test.go b/src/build/fs/Walk_test.go similarity index 62% rename from src/build/directory/Walk_test.go rename to src/build/fs/Walk_test.go index 00c663f..ae55b3d 100644 --- a/src/build/directory/Walk_test.go +++ b/src/build/fs/Walk_test.go @@ -1,16 +1,16 @@ -package directory_test +package fs_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/directory" + "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/go/assert" ) func TestWalk(t *testing.T) { var files []string - directory.Walk(".", func(file string) { + fs.Walk(".", func(file string) { files = append(files, file) }) @@ -19,6 +19,6 @@ func TestWalk(t *testing.T) { } func TestNonExisting(t *testing.T) { - err := directory.Walk("does-not-exist", func(file string) {}) + err := fs.Walk("does-not-exist", func(file string) {}) assert.NotNil(t, err) } diff --git a/src/build/token/Token.go b/src/build/token/Token.go index 7c10db2..c01d302 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -11,6 +11,11 @@ type Token struct { Bytes []byte } +// After returns the position after the token. +func (t Token) After() int { + return t.Position + len(t.Bytes) +} + // String creates a human readable representation for debugging purposes. func (t Token) String() string { return fmt.Sprintf("%s %s", t.Kind, t.Text()) diff --git a/src/errors/Error.go b/src/errors/Error.go index c06bd20..ad434d3 100644 --- a/src/errors/Error.go +++ b/src/errors/Error.go @@ -5,60 +5,60 @@ import ( "os" "path/filepath" + "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" ) // Error is a compiler error at a given line and column. type Error struct { - Path string - Line int - Column int - Err error - Stack string + Err error + File *fs.File + Position int + Stack string } // New generates an error message at the current token position. // The error message is clickable in popular editors and leads you // directly to the faulty file at the given line and position. -func New(err error, path string, tokens []token.Token, cursor int) *Error { - var ( - lineCount = 1 - lineStart = -1 - ) - - for i := range cursor { - if tokens[i].Kind == token.NewLine { - lineCount++ - lineStart = int(tokens[i].Position) - } +func New(err error, file *fs.File, position int) *Error { + return &Error{ + Err: err, + File: file, + Position: position, + Stack: Stack(), } - - var column int - - if cursor < len(tokens) { - column = tokens[cursor].Position - lineStart - } else { - lastToken := tokens[len(tokens)-1] - column = lastToken.Position - lineStart + len(lastToken.Text()) - } - - return &Error{path, lineCount, column, err, Stack()} } // Error generates the string representation. func (e *Error) Error() string { - path := e.Path + path := e.File.Path cwd, err := os.Getwd() if err == nil { - relativePath, err := filepath.Rel(cwd, e.Path) + relativePath, err := filepath.Rel(cwd, e.File.Path) if err == nil { path = relativePath } } - return fmt.Sprintf("%s:%d:%d: %s\n\n%s", path, e.Line, e.Column, e.Err, e.Stack) + line := 1 + column := 1 + lineStart := -1 + + for _, t := range e.File.Tokens { + if t.Position >= e.Position { + column = e.Position - lineStart + break + } + + if t.Kind == token.NewLine { + lineStart = t.Position + line++ + } + } + + return fmt.Sprintf("%s:%d:%d: %s\n\n%s", path, line, column, e.Err, e.Stack) } // Unwrap returns the wrapped error. From 4776b4c14c235fbe635a06f5e1f4364113ef31ba Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Jun 2024 18:42:31 +0200 Subject: [PATCH 0088/1012] Improved error handling --- src/build/Function.go | 30 +++++++----- src/build/Scan.go | 24 +++++----- src/build/{ => fs}/File.go | 2 +- src/build/{directory => fs}/Walk.go | 2 +- src/build/{directory => fs}/Walk_test.go | 8 ++-- src/build/token/Token.go | 5 ++ src/errors/Error.go | 60 ++++++++++++------------ 7 files changed, 71 insertions(+), 60 deletions(-) rename src/build/{ => fs}/File.go (91%) rename src/build/{directory => fs}/Walk.go (98%) rename src/build/{directory => fs}/Walk_test.go (62%) diff --git a/src/build/Function.go b/src/build/Function.go index 18131fd..cc5e787 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -7,14 +7,16 @@ import ( "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/go/color/ansi" ) // Function represents a function. type Function struct { Name string - File *File + File *fs.File Head token.List Body token.List Variables map[string]*Variable @@ -74,19 +76,24 @@ func (f *Function) Compile() { f.Assembler.Return() if config.Verbose { - fmt.Println() - ansi.Bold.Println(f.Name + ".asm") + f.PrintAsm() + } +} - for _, x := range f.Assembler.Instructions { - ansi.Dim.Print("│ ") - fmt.Print(x.Mnemonic.String()) +// PrintAsm shows the assembly instructions. +func (f *Function) PrintAsm() { + fmt.Println() + ansi.Bold.Println(f.Name + ".asm") - if x.Data != nil { - fmt.Print(" " + x.Data.String()) - } + for _, x := range f.Assembler.Instructions { + ansi.Dim.Print("│ ") + fmt.Print(x.Mnemonic.String()) - fmt.Print("\n") + if x.Data != nil { + fmt.Print(" " + x.Data.String()) } + + fmt.Print("\n") } } @@ -116,8 +123,7 @@ func (f *Function) CompileInstruction(line token.List) error { value := line[2:] if len(value) == 0 { - return fmt.Errorf("error to be implemented") - // return errors.New(errors.MissingAssignmentValue, f.File.Path, f.File.Tokens, f.Cursor) + return errors.New(errors.MissingAssignmentValue, f.File, line[1].After()) } if config.Verbose { diff --git a/src/build/Scan.go b/src/build/Scan.go index 87f6033..0a2c903 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -6,7 +6,7 @@ import ( "strings" "sync" - "git.akyoto.dev/cli/q/src/build/directory" + "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" ) @@ -38,7 +38,7 @@ func scan(files []string, functions chan<- *Function, errors chan<- error) { } if stat.IsDir() { - err = directory.Walk(file, func(name string) { + err = fs.Walk(file, func(name string) { if !strings.HasSuffix(name, ".q") { return } @@ -86,7 +86,7 @@ func scanFile(path string, functions chan<- *Function) error { tokens := token.Tokenize(contents) - file := &File{ + file := &fs.File{ Tokens: tokens, Path: path, } @@ -118,7 +118,7 @@ func scanFile(path string, functions chan<- *Function) error { return nil } - return errors.New(errors.ExpectedFunctionName, path, tokens, i) + return errors.New(errors.ExpectedFunctionName, file, tokens[i].Position) } // Function parameters @@ -138,7 +138,7 @@ func scanFile(path string, functions chan<- *Function) error { groupLevel-- if groupLevel < 0 { - return errors.New(errors.MissingGroupStart, path, tokens, i) + return errors.New(errors.MissingGroupStart, file, tokens[i].Position) } i++ @@ -152,11 +152,11 @@ func scanFile(path string, functions chan<- *Function) error { if tokens[i].Kind == token.EOF { if groupLevel > 0 { - return errors.New(errors.MissingGroupEnd, path, tokens, i) + return errors.New(errors.MissingGroupEnd, file, tokens[i].Position) } if paramsStart == -1 { - return errors.New(errors.ExpectedFunctionParameters, path, tokens, i) + return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) } return nil @@ -167,7 +167,7 @@ func scanFile(path string, functions chan<- *Function) error { continue } - return errors.New(errors.ExpectedFunctionParameters, path, tokens, i) + return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) } // Function definition @@ -187,7 +187,7 @@ func scanFile(path string, functions chan<- *Function) error { blockLevel-- if blockLevel < 0 { - return errors.New(errors.MissingBlockStart, path, tokens, i) + return errors.New(errors.MissingBlockStart, file, tokens[i].Position) } i++ @@ -201,11 +201,11 @@ func scanFile(path string, functions chan<- *Function) error { if tokens[i].Kind == token.EOF { if blockLevel > 0 { - return errors.New(errors.MissingBlockEnd, path, tokens, i) + return errors.New(errors.MissingBlockEnd, file, tokens[i].Position) } if bodyStart == -1 { - return errors.New(errors.ExpectedFunctionDefinition, path, tokens, i) + return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) } return nil @@ -216,7 +216,7 @@ func scanFile(path string, functions chan<- *Function) error { continue } - return errors.New(errors.ExpectedFunctionDefinition, path, tokens, i) + return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) } functions <- &Function{ diff --git a/src/build/File.go b/src/build/fs/File.go similarity index 91% rename from src/build/File.go rename to src/build/fs/File.go index 760bfcc..53d49c2 100644 --- a/src/build/File.go +++ b/src/build/fs/File.go @@ -1,4 +1,4 @@ -package build +package fs import "git.akyoto.dev/cli/q/src/build/token" diff --git a/src/build/directory/Walk.go b/src/build/fs/Walk.go similarity index 98% rename from src/build/directory/Walk.go rename to src/build/fs/Walk.go index e2cc495..78d6043 100644 --- a/src/build/directory/Walk.go +++ b/src/build/fs/Walk.go @@ -1,4 +1,4 @@ -package directory +package fs import ( "syscall" diff --git a/src/build/directory/Walk_test.go b/src/build/fs/Walk_test.go similarity index 62% rename from src/build/directory/Walk_test.go rename to src/build/fs/Walk_test.go index 00c663f..ae55b3d 100644 --- a/src/build/directory/Walk_test.go +++ b/src/build/fs/Walk_test.go @@ -1,16 +1,16 @@ -package directory_test +package fs_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/directory" + "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/go/assert" ) func TestWalk(t *testing.T) { var files []string - directory.Walk(".", func(file string) { + fs.Walk(".", func(file string) { files = append(files, file) }) @@ -19,6 +19,6 @@ func TestWalk(t *testing.T) { } func TestNonExisting(t *testing.T) { - err := directory.Walk("does-not-exist", func(file string) {}) + err := fs.Walk("does-not-exist", func(file string) {}) assert.NotNil(t, err) } diff --git a/src/build/token/Token.go b/src/build/token/Token.go index 7c10db2..c01d302 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -11,6 +11,11 @@ type Token struct { Bytes []byte } +// After returns the position after the token. +func (t Token) After() int { + return t.Position + len(t.Bytes) +} + // String creates a human readable representation for debugging purposes. func (t Token) String() string { return fmt.Sprintf("%s %s", t.Kind, t.Text()) diff --git a/src/errors/Error.go b/src/errors/Error.go index c06bd20..ad434d3 100644 --- a/src/errors/Error.go +++ b/src/errors/Error.go @@ -5,60 +5,60 @@ import ( "os" "path/filepath" + "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" ) // Error is a compiler error at a given line and column. type Error struct { - Path string - Line int - Column int - Err error - Stack string + Err error + File *fs.File + Position int + Stack string } // New generates an error message at the current token position. // The error message is clickable in popular editors and leads you // directly to the faulty file at the given line and position. -func New(err error, path string, tokens []token.Token, cursor int) *Error { - var ( - lineCount = 1 - lineStart = -1 - ) - - for i := range cursor { - if tokens[i].Kind == token.NewLine { - lineCount++ - lineStart = int(tokens[i].Position) - } +func New(err error, file *fs.File, position int) *Error { + return &Error{ + Err: err, + File: file, + Position: position, + Stack: Stack(), } - - var column int - - if cursor < len(tokens) { - column = tokens[cursor].Position - lineStart - } else { - lastToken := tokens[len(tokens)-1] - column = lastToken.Position - lineStart + len(lastToken.Text()) - } - - return &Error{path, lineCount, column, err, Stack()} } // Error generates the string representation. func (e *Error) Error() string { - path := e.Path + path := e.File.Path cwd, err := os.Getwd() if err == nil { - relativePath, err := filepath.Rel(cwd, e.Path) + relativePath, err := filepath.Rel(cwd, e.File.Path) if err == nil { path = relativePath } } - return fmt.Sprintf("%s:%d:%d: %s\n\n%s", path, e.Line, e.Column, e.Err, e.Stack) + line := 1 + column := 1 + lineStart := -1 + + for _, t := range e.File.Tokens { + if t.Position >= e.Position { + column = e.Position - lineStart + break + } + + if t.Kind == token.NewLine { + lineStart = t.Position + line++ + } + } + + return fmt.Sprintf("%s:%d:%d: %s\n\n%s", path, line, column, e.Err, e.Stack) } // Unwrap returns the wrapped error. From 1b99722cd897e0baab0125bd2d8ffa8b2faa8cd8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Jun 2024 20:25:37 +0200 Subject: [PATCH 0089/1012] Added more tests --- main_test.go | 3 +++ src/build/Function.go | 10 +++++----- src/errors/CompileErrors.go | 3 ++- src/errors/InvalidInstruction.go | 13 +++++++++++++ tests/errors/InvalidInstruction-Identifier.q | 3 +++ tests/errors/InvalidInstruction-Number.q | 3 +++ tests/errors/MissingAssignValue.q | 3 +++ 7 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 src/errors/InvalidInstruction.go create mode 100644 tests/errors/InvalidInstruction-Identifier.q create mode 100644 tests/errors/InvalidInstruction-Number.q create mode 100644 tests/errors/MissingAssignValue.q diff --git a/main_test.go b/main_test.go index d7c4539..be2aa55 100644 --- a/main_test.go +++ b/main_test.go @@ -18,6 +18,9 @@ func TestErrors(t *testing.T) { {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, + {"InvalidInstruction-Identifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, + {"InvalidInstruction-Number.q", &errors.InvalidInstruction{Instruction: "123"}}, + {"MissingAssignValue.q", errors.MissingAssignValue}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, diff --git a/src/build/Function.go b/src/build/Function.go index cc5e787..ce8660c 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -113,17 +113,17 @@ func (f *Function) CompileInstruction(line token.List) error { return nil } - if line[0].Kind == token.Identifier { - if len(line) < 2 { - return fmt.Errorf("error to be implemented") - } + if len(line) < 2 { + return errors.New(&errors.InvalidInstruction{Instruction: line[0].Text()}, f.File, line[0].Position) + } + if line[0].Kind == token.Identifier { if line[1].Kind == token.Define { name := line[0].Text() value := line[2:] if len(value) == 0 { - return errors.New(errors.MissingAssignmentValue, f.File, line[1].After()) + return errors.New(errors.MissingAssignValue, f.File, line[1].After()) } if config.Verbose { diff --git a/src/errors/CompileErrors.go b/src/errors/CompileErrors.go index 2c5ac46..9f43de2 100644 --- a/src/errors/CompileErrors.go +++ b/src/errors/CompileErrors.go @@ -1,5 +1,6 @@ package errors var ( - MissingAssignmentValue = &Base{"Missing assignment value"} + InvalidStatement = &Base{"Invalid statement"} + MissingAssignValue = &Base{"Missing assignment value"} ) diff --git a/src/errors/InvalidInstruction.go b/src/errors/InvalidInstruction.go new file mode 100644 index 0000000..bb6904b --- /dev/null +++ b/src/errors/InvalidInstruction.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// InvalidInstruction error is created when an instruction is not valid. +type InvalidInstruction struct { + Instruction string +} + +// Error generates the string representation. +func (err *InvalidInstruction) Error() string { + return fmt.Sprintf("Invalid instruction '%s'", err.Instruction) +} diff --git a/tests/errors/InvalidInstruction-Identifier.q b/tests/errors/InvalidInstruction-Identifier.q new file mode 100644 index 0000000..54ff7db --- /dev/null +++ b/tests/errors/InvalidInstruction-Identifier.q @@ -0,0 +1,3 @@ +main() { + abc +} \ No newline at end of file diff --git a/tests/errors/InvalidInstruction-Number.q b/tests/errors/InvalidInstruction-Number.q new file mode 100644 index 0000000..5a97c64 --- /dev/null +++ b/tests/errors/InvalidInstruction-Number.q @@ -0,0 +1,3 @@ +main() { + 123 +} \ No newline at end of file diff --git a/tests/errors/MissingAssignValue.q b/tests/errors/MissingAssignValue.q new file mode 100644 index 0000000..ae9e1eb --- /dev/null +++ b/tests/errors/MissingAssignValue.q @@ -0,0 +1,3 @@ +main() { + a := +} \ No newline at end of file From 864c9c7b43c533ea785c9518a5d7b5dade5082a5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Jun 2024 20:25:37 +0200 Subject: [PATCH 0090/1012] Added more tests --- main_test.go | 3 +++ src/build/Function.go | 10 +++++----- src/errors/CompileErrors.go | 3 ++- src/errors/InvalidInstruction.go | 13 +++++++++++++ tests/errors/InvalidInstruction-Identifier.q | 3 +++ tests/errors/InvalidInstruction-Number.q | 3 +++ tests/errors/MissingAssignValue.q | 3 +++ 7 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 src/errors/InvalidInstruction.go create mode 100644 tests/errors/InvalidInstruction-Identifier.q create mode 100644 tests/errors/InvalidInstruction-Number.q create mode 100644 tests/errors/MissingAssignValue.q diff --git a/main_test.go b/main_test.go index d7c4539..be2aa55 100644 --- a/main_test.go +++ b/main_test.go @@ -18,6 +18,9 @@ func TestErrors(t *testing.T) { {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, + {"InvalidInstruction-Identifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, + {"InvalidInstruction-Number.q", &errors.InvalidInstruction{Instruction: "123"}}, + {"MissingAssignValue.q", errors.MissingAssignValue}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, diff --git a/src/build/Function.go b/src/build/Function.go index cc5e787..ce8660c 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -113,17 +113,17 @@ func (f *Function) CompileInstruction(line token.List) error { return nil } - if line[0].Kind == token.Identifier { - if len(line) < 2 { - return fmt.Errorf("error to be implemented") - } + if len(line) < 2 { + return errors.New(&errors.InvalidInstruction{Instruction: line[0].Text()}, f.File, line[0].Position) + } + if line[0].Kind == token.Identifier { if line[1].Kind == token.Define { name := line[0].Text() value := line[2:] if len(value) == 0 { - return errors.New(errors.MissingAssignmentValue, f.File, line[1].After()) + return errors.New(errors.MissingAssignValue, f.File, line[1].After()) } if config.Verbose { diff --git a/src/errors/CompileErrors.go b/src/errors/CompileErrors.go index 2c5ac46..9f43de2 100644 --- a/src/errors/CompileErrors.go +++ b/src/errors/CompileErrors.go @@ -1,5 +1,6 @@ package errors var ( - MissingAssignmentValue = &Base{"Missing assignment value"} + InvalidStatement = &Base{"Invalid statement"} + MissingAssignValue = &Base{"Missing assignment value"} ) diff --git a/src/errors/InvalidInstruction.go b/src/errors/InvalidInstruction.go new file mode 100644 index 0000000..bb6904b --- /dev/null +++ b/src/errors/InvalidInstruction.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// InvalidInstruction error is created when an instruction is not valid. +type InvalidInstruction struct { + Instruction string +} + +// Error generates the string representation. +func (err *InvalidInstruction) Error() string { + return fmt.Sprintf("Invalid instruction '%s'", err.Instruction) +} diff --git a/tests/errors/InvalidInstruction-Identifier.q b/tests/errors/InvalidInstruction-Identifier.q new file mode 100644 index 0000000..54ff7db --- /dev/null +++ b/tests/errors/InvalidInstruction-Identifier.q @@ -0,0 +1,3 @@ +main() { + abc +} \ No newline at end of file diff --git a/tests/errors/InvalidInstruction-Number.q b/tests/errors/InvalidInstruction-Number.q new file mode 100644 index 0000000..5a97c64 --- /dev/null +++ b/tests/errors/InvalidInstruction-Number.q @@ -0,0 +1,3 @@ +main() { + 123 +} \ No newline at end of file diff --git a/tests/errors/MissingAssignValue.q b/tests/errors/MissingAssignValue.q new file mode 100644 index 0000000..ae9e1eb --- /dev/null +++ b/tests/errors/MissingAssignValue.q @@ -0,0 +1,3 @@ +main() { + a := +} \ No newline at end of file From 0ed071a7eed2f92ed046ef2873fc5828e1b51480 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Jun 2024 16:57:33 +0200 Subject: [PATCH 0091/1012] Implemented expression parsing --- bench_test.go | 23 ++++ src/build/Function.go | 146 +++++++++++++----------- src/build/Scan.go | 6 +- src/build/Variable.go | 4 +- src/build/expression/Expression.go | 113 ++++++++++++++++++ src/build/expression/Expression_test.go | 104 +++++++++++++++++ src/build/expression/List.go | 41 +++++++ src/build/expression/Operator.go | 65 +++++++++++ src/build/expression/Parse.go | 142 +++++++++++++++++++++++ src/build/expression/pool.go | 9 ++ src/build/token/Kind.go | 4 - src/build/token/List.go | 2 +- src/build/token/Token.go | 13 ++- src/build/token/Token_test.go | 6 +- src/build/token/Tokenize.go | 16 +-- src/errors/KeywordNotImplemented.go | 13 +++ tests/benchmarks/empty.q | 3 + tests/benchmarks/expressions.q | 7 ++ 18 files changed, 618 insertions(+), 99 deletions(-) create mode 100644 bench_test.go create mode 100644 src/build/expression/Expression.go create mode 100644 src/build/expression/Expression_test.go create mode 100644 src/build/expression/List.go create mode 100644 src/build/expression/Operator.go create mode 100644 src/build/expression/Parse.go create mode 100644 src/build/expression/pool.go create mode 100644 src/errors/KeywordNotImplemented.go create mode 100644 tests/benchmarks/empty.q create mode 100644 tests/benchmarks/expressions.q diff --git a/bench_test.go b/bench_test.go new file mode 100644 index 0000000..46f5f83 --- /dev/null +++ b/bench_test.go @@ -0,0 +1,23 @@ +package main_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build" +) + +func BenchmarkEmpty(b *testing.B) { + compiler := build.New("tests/benchmarks/empty.q") + + for i := 0; i < b.N; i++ { + compiler.Run() + } +} + +func BenchmarkExpressions(b *testing.B) { + compiler := build.New("tests/benchmarks/expressions.q") + + for i := 0; i < b.N; i++ { + compiler.Run() + } +} diff --git a/src/build/Function.go b/src/build/Function.go index ce8660c..838a8d7 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -7,6 +7,7 @@ import ( "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" @@ -28,6 +29,7 @@ type Function struct { func (f *Function) Compile() { if config.Verbose { ansi.Bold.Println(f.Name) + ansi.Dim.Println("╭────────────────────────────────────────────────────────────") } start := 0 @@ -76,6 +78,7 @@ func (f *Function) Compile() { f.Assembler.Return() if config.Verbose { + ansi.Dim.Println("╰────────────────────────────────────────────────────────────") f.PrintAsm() } } @@ -84,6 +87,7 @@ func (f *Function) Compile() { func (f *Function) PrintAsm() { fmt.Println() ansi.Bold.Println(f.Name + ".asm") + ansi.Dim.Println("╭────────────────────────────────────────────────────────────") for _, x := range f.Assembler.Instructions { ansi.Dim.Print("│ ") @@ -95,6 +99,8 @@ func (f *Function) PrintAsm() { fmt.Print("\n") } + + ansi.Dim.Println("╰────────────────────────────────────────────────────────────") } // CompileInstruction compiles a single instruction. @@ -104,91 +110,93 @@ func (f *Function) CompileInstruction(line token.List) error { fmt.Println(line) } + if len(line) == 0 { + return nil + } + if line[0].Kind == token.Keyword { switch line[0].Text() { case "return": f.Assembler.Return() + + default: + return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) + } + } + + expr := expression.Parse(line) + + if expr == nil { + return nil + } + + defer expr.Close() + + if config.Verbose { + ansi.Dim.Print("├───○ exp ") + fmt.Println(expr) + } + + if expr.Token.Kind == token.Number || expr.Token.Kind == token.Identifier { + return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) + } + + if expr.Token.Text() == ":=" { + if len(expr.Children) < 2 { + return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.After()) + } + + name := expr.Children[0] + value := expr.Children[1] + + if config.Verbose { + ansi.Dim.Print("├───○ var ") + fmt.Println(name, value) + } + + expr.RemoveChild(value) + + f.Variables[name.Token.Text()] = &Variable{ + Name: name.Token.Text(), + Value: value, + IsConst: true, } return nil } - if len(line) < 2 { - return errors.New(&errors.InvalidInstruction{Instruction: line[0].Text()}, f.File, line[0].Position) - } + if expr.Token.Text() == "call" && expr.Children[0].Token.Text() == "syscall" { + parameters := expr.Children[1:] - if line[0].Kind == token.Identifier { - if line[1].Kind == token.Define { - name := line[0].Text() - value := line[2:] + for i, parameter := range parameters { + switch parameter.Token.Kind { + case token.Identifier: + name := parameter.Token.Text() + variable, exists := f.Variables[name] - if len(value) == 0 { - return errors.New(errors.MissingAssignValue, f.File, line[1].After()) - } - - if config.Verbose { - ansi.Dim.Printf("├── var ") - fmt.Println(name, "=", value) - } - - f.Variables[name] = &Variable{ - Name: name, - Value: value, - IsConst: true, - } - - return nil - } - - switch line[0].Text() { - case "syscall": - paramTokens := line[2 : len(line)-1] - start := 0 - i := 0 - var parameters []token.List - - for i < len(paramTokens) { - if paramTokens[i].Kind == token.Separator { - parameters = append(parameters, paramTokens[start:i]) - start = i + 1 + if !exists { + panic("Unknown identifier " + name) } - i++ - } - - if i != start { - parameters = append(parameters, paramTokens[start:i]) - } - - for i, list := range parameters { - switch list[0].Kind { - case token.Identifier: - name := list[0].Text() - variable, exists := f.Variables[name] - - if !exists { - panic("Unknown identifier " + name) - } - - if !variable.IsConst { - panic("Not implemented yet") - } - - n, _ := strconv.Atoi(variable.Value[0].Text()) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) - - case token.Number: - value := list[0].Text() - n, _ := strconv.Atoi(value) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) - - default: - panic("Unknown expression") + if !variable.IsConst { + panic("Not implemented yet") } - } - f.Assembler.Syscall() + n, _ := strconv.Atoi(variable.Value.Token.Text()) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + + case token.Number: + value := parameter.Token.Text() + n, _ := strconv.Atoi(value) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + + default: + panic("Unknown expression") + } } + + f.Assembler.Syscall() + return nil } return nil diff --git a/src/build/Scan.go b/src/build/Scan.go index 0a2c903..b3e808a 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -17,7 +17,7 @@ func Scan(files []string) (<-chan *Function, <-chan error) { errors := make(chan error) go func() { - scan(files, functions, errors) + scanFiles(files, functions, errors) close(functions) close(errors) }() @@ -25,8 +25,8 @@ func Scan(files []string) (<-chan *Function, <-chan error) { return functions, errors } -// scan scans the directory without channel allocations. -func scan(files []string, functions chan<- *Function, errors chan<- error) { +// scanFiles scans the list of files without channel allocations. +func scanFiles(files []string, functions chan<- *Function, errors chan<- error) { wg := sync.WaitGroup{} for _, file := range files { diff --git a/src/build/Variable.go b/src/build/Variable.go index 46190e2..b5f7338 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -1,10 +1,10 @@ package build -import "git.akyoto.dev/cli/q/src/build/token" +import "git.akyoto.dev/cli/q/src/build/expression" // Variable represents a variable in a function. type Variable struct { Name string - Value token.List + Value *expression.Expression IsConst bool } diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go new file mode 100644 index 0000000..167ca83 --- /dev/null +++ b/src/build/expression/Expression.go @@ -0,0 +1,113 @@ +package expression + +import ( + "strings" + + "git.akyoto.dev/cli/q/src/build/token" +) + +// Expression is a binary tree with an operator on each node. +type Expression struct { + Token token.Token + Parent *Expression + Children []*Expression +} + +// New creates a new expression. +func New() *Expression { + return pool.Get().(*Expression) +} + +// NewLeaf creates a new leaf node. +func NewLeaf(t token.Token) *Expression { + expr := New() + expr.Token = t + return expr +} + +// NewBinary creates a new binary operator expression. +func NewBinary(left *Expression, operator token.Token, right *Expression) *Expression { + expr := New() + expr.Token = operator + expr.AddChild(left) + expr.AddChild(right) + return expr +} + +// AddChild adds a child to the expression. +func (expr *Expression) AddChild(child *Expression) { + expr.Children = append(expr.Children, child) + child.Parent = expr +} + +// Close puts the expression back into the memory pool. +func (expr *Expression) Close() { + for _, child := range expr.Children { + child.Close() + } + + expr.Token.Reset() + expr.Parent = nil + expr.Children = expr.Children[:0] + pool.Put(expr) +} + +// RemoveChild removes a child from the expression. +func (expr *Expression) RemoveChild(child *Expression) { + for i, c := range expr.Children { + if c == child { + expr.Children = append(expr.Children[:i], expr.Children[i+1:]...) + child.Parent = nil + return + } + } +} + +// Replace replaces the tree with the new expression and adds the previous expression to it. +func (expr *Expression) Replace(tree *Expression) { + if expr.Parent != nil { + expr.Parent.Children[len(expr.Parent.Children)-1] = tree + tree.Parent = expr.Parent + } + + tree.AddChild(expr) +} + +// IsLeaf returns true if the expression has no children. +func (expr *Expression) IsLeaf() bool { + return len(expr.Children) == 0 +} + +// LastChild returns the last child. +func (expr *Expression) LastChild() *Expression { + return expr.Children[len(expr.Children)-1] +} + +// String generates a textual representation of the expression. +func (expr *Expression) String() string { + builder := strings.Builder{} + expr.write(&builder) + return builder.String() +} + +// write generates a textual representation of the expression. +func (expr *Expression) write(builder *strings.Builder) { + if expr.IsLeaf() { + builder.WriteString(expr.Token.Text()) + return + } + + builder.WriteByte('(') + builder.WriteString(expr.Token.Text()) + builder.WriteByte(' ') + + for i, child := range expr.Children { + child.write(builder) + + if i != len(expr.Children)-1 { + builder.WriteByte(' ') + } + } + + builder.WriteByte(')') +} diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go new file mode 100644 index 0000000..2a39366 --- /dev/null +++ b/src/build/expression/Expression_test.go @@ -0,0 +1,104 @@ +package expression_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/go/assert" +) + +func TestExpressionFromTokens(t *testing.T) { + tests := []struct { + Name string + Expression string + Result string + }{ + {"Empty", "", ""}, + {"Identity", "1", "1"}, + {"Basic calculation", "1+2", "(1+2)"}, + {"Same operator", "1+2+3", "((1+2)+3)"}, + {"Same operator 2", "1+2+3+4", "(((1+2)+3)+4)"}, + {"Different operator", "1+2-3", "((1+2)-3)"}, + {"Different operator 2", "1+2-3+4", "(((1+2)-3)+4)"}, + {"Different operator 3", "1+2-3+4-5", "((((1+2)-3)+4)-5)"}, + {"Grouped identity", "(1)", "1"}, + {"Grouped identity 2", "((1))", "1"}, + {"Grouped identity 3", "(((1)))", "1"}, + {"Adding identity", "(1)+(2)", "(1+2)"}, + {"Adding identity 2", "(1)+(2)+(3)", "((1+2)+3)"}, + {"Adding identity 3", "(1)+(2)+(3)+(4)", "(((1+2)+3)+4)"}, + {"Grouping", "(1+2)", "(1+2)"}, + {"Grouping 2", "(1+2+3)", "((1+2)+3)"}, + {"Grouping 3", "((1)+(2)+(3))", "((1+2)+3)"}, + {"Grouping left", "(1+2)*3", "((1+2)*3)"}, + {"Grouping right", "1*(2+3)", "(1*(2+3))"}, + {"Grouping same operator", "1+(2+3)", "(1+(2+3))"}, + {"Grouping same operator 2", "1+(2+3)+(4+5)", "((1+(2+3))+(4+5))"}, + {"Two groups", "(1+2)*(3+4)", "((1+2)*(3+4))"}, + {"Two groups 2", "(1+2-3)*(3+4-5)", "(((1+2)-3)*((3+4)-5))"}, + {"Two groups 3", "(1+2)*(3+4-5)", "((1+2)*((3+4)-5))"}, + {"Operator priority", "1+2*3", "(1+(2*3))"}, + {"Operator priority 2", "1*2+3", "((1*2)+3)"}, + {"Operator priority 3", "1+2*3+4", "((1+(2*3))+4)"}, + {"Operator priority 4", "1+2*(3+4)+5", "((1+(2*(3+4)))+5)"}, + {"Operator priority 5", "1+2*3*4", "(1+((2*3)*4))"}, + {"Operator priority 6", "1+2*3+4*5", "((1+(2*3))+(4*5))"}, + {"Operator priority 7", "1+2*3*4*5*6", "(1+((((2*3)*4)*5)*6))"}, + {"Operator priority 8", "1*2*3+4*5*6", "(((1*2)*3)+((4*5)*6))"}, + {"Complex", "(1+2-3*4)*(5+6-7*8)", "(((1+2)-(3*4))*((5+6)-(7*8)))"}, + {"Complex 2", "(1+2*3-4)*(5+6*7-8)", "(((1+(2*3))-4)*((5+(6*7))-8))"}, + {"Complex 3", "(1+2*3-4)*(5+6*7-8)+9-10*11", "(((((1+(2*3))-4)*((5+(6*7))-8))+9)-(10*11))"}, + {"Function calls", "a()", "a()"}, + {"Function calls 2", "a(1)", "a(1)"}, + {"Function calls 3", "a(1,2)", "a(1,2)"}, + {"Function calls 4", "a(1,2,3)", "a(1,2,3)"}, + {"Function calls 5", "a(1,2+2,3)", "a(1,(2+2),3)"}, + {"Function calls 6", "a(1,2+2,3+3)", "a(1,(2+2),(3+3))"}, + {"Function calls 7", "a(1+1,2,3)", "a((1+1),2,3)"}, + {"Function calls 8", "a(1+1,2+2,3+3)", "a((1+1),(2+2),(3+3))"}, + {"Function calls 9", "a(b())", "a(b())"}, + {"Function calls 10", "a(b(),c())", "a(b(),c())"}, + {"Function calls 11", "a(b(),c(),d())", "a(b(),c(),d())"}, + {"Function calls 12", "a(b(1),c(2),d(3))", "a(b(1),c(2),d(3))"}, + {"Function calls 13", "a(b(1)+1)", "a((b(1)+1))"}, + {"Function calls 14", "a(b(1)+1,c(2),d(3))", "a((b(1)+1),c(2),d(3))"}, + {"Function calls 15", "a(b(1)*c(2))", "a((b(1)*c(2)))"}, + {"Function calls 16", "a(b(1)*c(2),d(3)+e(4),f(5)/f(6))", "a((b(1)*c(2)),(d(3)+e(4)),(f(5)/f(6)))"}, + {"Function calls 17", "a((b(1,2)+c(3,4))*d(5,6))", "a(((b(1,2)+c(3,4))*d(5,6)))"}, + {"Function calls 18", "a((b(1,2)+c(3,4))*d(5,6),e())", "a(((b(1,2)+c(3,4))*d(5,6)),e())"}, + {"Function calls 19", "a((b(1,2)+c(3,4))*d(5,6),e(7+8,9-10*11,12))", "a(((b(1,2)+c(3,4))*d(5,6)),e((7+8),(9-(10*11)),12))"}, + {"Function calls 20", "a((b(1,2,bb())+c(3,4,cc(0)))*d(5,6,dd(0)),e(7+8,9-10*11,12,ee(0)))", "a(((b(1,2,bb())+c(3,4,cc(0)))*d(5,6,dd(0))),e((7+8),(9-(10*11)),12,ee(0)))"}, + {"Function calls 21", "a(1-2*3)", "a((1-(2*3)))"}, + {"Function calls 22", "1+2*a()+4", "((1+(2*a()))+4)"}, + {"Function calls 23", "sum(a,b)*2+15*4", "((sum(a,b)*2)+(15*4))"}, + {"Package function calls", "math.sum(a,b)", "(math.sum(a,b))"}, + {"Package function calls 2", "generic.math.sum(a,b)", "((generic.math).sum(a,b))"}, + } + + for _, test := range tests { + test := test + + t.Run(test.Name, func(t *testing.T) { + src := []byte(test.Expression + "\n") + tokens := token.Tokenize(src) + expr := expression.Parse(tokens) + assert.NotNil(t, expr) + t.Log(expr) + // assert.Equal(t, expr.String(), test.Result) + }) + } +} + +func BenchmarkExpression(b *testing.B) { + src := []byte("(1+2-3*4)*(5+6-7*8)\n") + tokens := token.Tokenize(src) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + expr := expression.Parse(tokens) + expr.Close() + } +} diff --git a/src/build/expression/List.go b/src/build/expression/List.go new file mode 100644 index 0000000..b6fe4a6 --- /dev/null +++ b/src/build/expression/List.go @@ -0,0 +1,41 @@ +package expression + +import ( + "git.akyoto.dev/cli/q/src/build/token" +) + +// List generates a list of expressions from comma separated parameters. +func List(tokens []token.Token) []*Expression { + var list []*Expression + + start := 0 + groupLevel := 0 + + for i, t := range tokens { + switch t.Kind { + case token.GroupStart, token.ArrayStart, token.BlockStart: + groupLevel++ + + case token.GroupEnd, token.ArrayEnd, token.BlockEnd: + groupLevel-- + + case token.Separator: + if groupLevel > 0 { + continue + } + + parameter := tokens[start:i] + expression := Parse(parameter) + list = append(list, expression) + start = i + 1 + } + } + + if start != len(tokens) { + parameter := tokens[start:] + expression := Parse(parameter) + list = append(list, expression) + } + + return list +} diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go new file mode 100644 index 0000000..2edc304 --- /dev/null +++ b/src/build/expression/Operator.go @@ -0,0 +1,65 @@ +package expression + +import "git.akyoto.dev/cli/q/src/build/token" + +// Operator represents an operator for mathematical expressions. +type Operator struct { + Symbol string + Precedence int + Operands int +} + +// Operators defines the operators used in the language. +// The number corresponds to the operator priority and can not be zero. +var Operators = map[string]*Operator{ + ".": {".", 12, 2}, + "*": {"*", 11, 2}, + "/": {"/", 11, 2}, + "%": {"%", 11, 2}, + "+": {"+", 10, 2}, + "-": {"-", 10, 2}, + ">>": {">>", 9, 2}, + "<<": {"<<", 9, 2}, + ">": {">", 8, 2}, + "<": {"<", 8, 2}, + ">=": {">=", 8, 2}, + "<=": {"<=", 8, 2}, + "==": {"==", 7, 2}, + "!=": {"!=", 7, 2}, + "&": {"&", 6, 2}, + "^": {"^", 5, 2}, + "|": {"|", 4, 2}, + "&&": {"&&", 3, 2}, + "||": {"||", 2, 2}, + "=": {"=", 1, 2}, + "+=": {"+=", 1, 2}, + "-=": {"-=", 1, 2}, + "*=": {"*=", 1, 2}, + "/=": {"/=", 1, 2}, + ">>=": {">>=", 1, 2}, + "<<=": {"<<=", 1, 2}, +} + +func isComplete(expr *Expression) bool { + if expr == nil { + return false + } + + if expr.Token.Kind == token.Identifier { + return true + } + + if expr.Token.Kind == token.Operator && len(expr.Children) == numOperands(expr.Token.Text()) { + return true + } + + return false +} + +func numOperands(symbol string) int { + return Operators[symbol].Operands +} + +func precedence(symbol string) int { + return Operators[symbol].Precedence +} diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go new file mode 100644 index 0000000..0e9b844 --- /dev/null +++ b/src/build/expression/Parse.go @@ -0,0 +1,142 @@ +package expression + +import ( + "git.akyoto.dev/cli/q/src/build/token" +) + +var call = []byte("call") + +// Parse generates an expression tree from tokens. +func Parse(tokens token.List) *Expression { + var ( + cursor *Expression + root *Expression + i = 0 + groupLevel = 0 + groupPosition = 0 + ) + + for i < len(tokens) { + switch tokens[i].Kind { + case token.GroupStart: + groupLevel++ + + if groupLevel == 1 { + groupPosition = i + 1 + } + + case token.GroupEnd: + groupLevel-- + + if groupLevel == 0 { + isFunctionCall := isComplete(cursor) + + if isFunctionCall { + parameters := List(tokens[groupPosition:i]) + + node := New() + node.Token.Kind = token.Operator + node.Token.Position = tokens[groupPosition].Position + node.Token.Bytes = call + cursor.Replace(node) + + for _, param := range parameters { + node.AddChild(param) + } + + if cursor == root { + root = node + } + + i++ + continue + } + + group := Parse(tokens[groupPosition:i]) + + if group == nil { + i++ + continue + } + + if cursor == nil { + cursor = group + root = group + } else { + cursor.AddChild(group) + } + } + } + + if groupLevel != 0 { + i++ + continue + } + + switch tokens[i].Kind { + case token.Operator: + if cursor == nil { + cursor = NewLeaf(tokens[i]) + root = cursor + i++ + continue + } + + node := NewLeaf(tokens[i]) + + if cursor.Token.Kind == token.Operator { + oldPrecedence := precedence(cursor.Token.Text()) + newPrecedence := precedence(node.Token.Text()) + + if newPrecedence > oldPrecedence { + cursor.LastChild().Replace(node) + } else { + start := cursor + + for start != nil { + precedence := precedence(start.Token.Text()) + + if precedence < newPrecedence { + start.LastChild().Replace(node) + break + } + + if precedence == newPrecedence { + if start == root { + root = node + } + + start.Replace(node) + break + } + + start = start.Parent + } + + if start == nil { + root.Replace(node) + root = node + } + } + } else { + node.AddChild(cursor) + root = node + } + + cursor = node + + case token.Identifier, token.Number, token.String: + if cursor == nil { + cursor = NewLeaf(tokens[i]) + root = cursor + } else { + node := NewLeaf(tokens[i]) + cursor.AddChild(node) + } + } + + i++ + } + + return root +} diff --git a/src/build/expression/pool.go b/src/build/expression/pool.go new file mode 100644 index 0000000..42b571d --- /dev/null +++ b/src/build/expression/pool.go @@ -0,0 +1,9 @@ +package expression + +import "sync" + +var pool = sync.Pool{ + New: func() interface{} { + return &Expression{} + }, +} diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index f7b8e80..b221442 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -25,9 +25,6 @@ const ( // Number represents a series of numerical characters. Number - // Define represents the assignment operator `:=` for a new variable. - Define - // Operator represents a mathematical operator. Operator @@ -66,7 +63,6 @@ func (kind Kind) String() string { "Keyword", "String", "Number", - "Define", "Operator", "Separator", "Comment", diff --git a/src/build/token/List.go b/src/build/token/List.go index 30f344b..be3a1ff 100644 --- a/src/build/token/List.go +++ b/src/build/token/List.go @@ -13,7 +13,7 @@ func (list List) String() string { var last Token for _, t := range list { - if last.Kind == Keyword || last.Kind == Separator || last.Kind == Define || t.Kind == Define { + if last.Kind == Keyword || last.Kind == Separator || last.Kind == Operator || t.Kind == Operator { builder.WriteByte(' ') } diff --git a/src/build/token/Token.go b/src/build/token/Token.go index c01d302..66c77b1 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -12,16 +12,23 @@ type Token struct { } // After returns the position after the token. -func (t Token) After() int { +func (t *Token) After() int { return t.Position + len(t.Bytes) } // String creates a human readable representation for debugging purposes. -func (t Token) String() string { +func (t *Token) String() string { return fmt.Sprintf("%s %s", t.Kind, t.Text()) } +// Reset resets the token to default values. +func (t *Token) Reset() { + t.Kind = Invalid + t.Position = 0 + t.Bytes = nil +} + // Text returns the token text. -func (t Token) Text() string { +func (t *Token) Text() string { return string(t.Bytes) } diff --git a/src/build/token/Token_test.go b/src/build/token/Token_test.go index e1f66f5..b083cfd 100644 --- a/src/build/token/Token_test.go +++ b/src/build/token/Token_test.go @@ -117,7 +117,7 @@ func TestNewline(t *testing.T) { } func TestNumber(t *testing.T) { - tokens := token.Tokenize([]byte(`123 -456`)) + tokens := token.Tokenize([]byte(`123 456`)) assert.DeepEqual(t, tokens, token.List{ { Kind: token.Number, @@ -126,13 +126,13 @@ func TestNumber(t *testing.T) { }, { Kind: token.Number, - Bytes: []byte("-456"), + Bytes: []byte("456"), Position: 4, }, { Kind: token.EOF, Bytes: nil, - Position: 8, + Position: 7, }, }) } diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index c89ddba..0e51758 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -1,7 +1,5 @@ package token -import "bytes" - // Pre-allocate these byte buffers so we can re-use them // instead of allocating a new buffer every time. var ( @@ -12,7 +10,6 @@ var ( arrayStartBytes = []byte{'['} arrayEndBytes = []byte{']'} separatorBytes = []byte{','} - defineBytes = []byte{':', '='} newLineBytes = []byte{'\n'} ) @@ -97,7 +94,7 @@ func Tokenize(buffer []byte) List { } // Numbers - if isNumberStart(buffer[i]) { + if isNumber(buffer[i]) { position := i i++ @@ -118,11 +115,6 @@ func Tokenize(buffer []byte) List { i++ } - if bytes.Equal(buffer[position:i], defineBytes) { - tokens = append(tokens, Token{Define, position, defineBytes}) - continue - } - tokens = append(tokens, Token{Operator, position, buffer[position:i]}) continue } @@ -151,10 +143,6 @@ func isNumber(c byte) bool { return (c >= '0' && c <= '9') } -func isNumberStart(c byte) bool { - return isNumber(c) || c == '-' -} - func isOperator(c byte) bool { - return c == '=' || c == ':' || c == '+' || c == '-' || c == '*' || c == '/' || c == '<' || c == '>' || c == '!' + return c == '=' || c == ':' || c == '+' || c == '-' || c == '*' || c == '/' || c == '<' || c == '>' || c == '!' || c == '&' || c == '|' || c == '^' || c == '%' || c == '.' } diff --git a/src/errors/KeywordNotImplemented.go b/src/errors/KeywordNotImplemented.go new file mode 100644 index 0000000..d652457 --- /dev/null +++ b/src/errors/KeywordNotImplemented.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// KeywordNotImplemented error is created when we find a keyword without an implementation. +type KeywordNotImplemented struct { + Keyword string +} + +// Error generates the string representation. +func (err *KeywordNotImplemented) Error() string { + return fmt.Sprintf("Keyword not implemented: '%s'", err.Keyword) +} diff --git a/tests/benchmarks/empty.q b/tests/benchmarks/empty.q new file mode 100644 index 0000000..66bf3bc --- /dev/null +++ b/tests/benchmarks/empty.q @@ -0,0 +1,3 @@ +main() { + +} \ No newline at end of file diff --git a/tests/benchmarks/expressions.q b/tests/benchmarks/expressions.q new file mode 100644 index 0000000..b563384 --- /dev/null +++ b/tests/benchmarks/expressions.q @@ -0,0 +1,7 @@ +main() { + () + 1+(2*3) + (1+2) + f(x) + (a+b)(c) +} \ No newline at end of file From ef16bdb4c71290e83ef3768260c72b682d791cd9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Jun 2024 16:57:33 +0200 Subject: [PATCH 0092/1012] Implemented expression parsing --- bench_test.go | 23 ++++ src/build/Function.go | 146 +++++++++++++----------- src/build/Scan.go | 6 +- src/build/Variable.go | 4 +- src/build/expression/Expression.go | 113 ++++++++++++++++++ src/build/expression/Expression_test.go | 104 +++++++++++++++++ src/build/expression/List.go | 41 +++++++ src/build/expression/Operator.go | 65 +++++++++++ src/build/expression/Parse.go | 142 +++++++++++++++++++++++ src/build/expression/pool.go | 9 ++ src/build/token/Kind.go | 4 - src/build/token/List.go | 2 +- src/build/token/Token.go | 13 ++- src/build/token/Token_test.go | 6 +- src/build/token/Tokenize.go | 16 +-- src/errors/KeywordNotImplemented.go | 13 +++ tests/benchmarks/empty.q | 3 + tests/benchmarks/expressions.q | 7 ++ 18 files changed, 618 insertions(+), 99 deletions(-) create mode 100644 bench_test.go create mode 100644 src/build/expression/Expression.go create mode 100644 src/build/expression/Expression_test.go create mode 100644 src/build/expression/List.go create mode 100644 src/build/expression/Operator.go create mode 100644 src/build/expression/Parse.go create mode 100644 src/build/expression/pool.go create mode 100644 src/errors/KeywordNotImplemented.go create mode 100644 tests/benchmarks/empty.q create mode 100644 tests/benchmarks/expressions.q diff --git a/bench_test.go b/bench_test.go new file mode 100644 index 0000000..46f5f83 --- /dev/null +++ b/bench_test.go @@ -0,0 +1,23 @@ +package main_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build" +) + +func BenchmarkEmpty(b *testing.B) { + compiler := build.New("tests/benchmarks/empty.q") + + for i := 0; i < b.N; i++ { + compiler.Run() + } +} + +func BenchmarkExpressions(b *testing.B) { + compiler := build.New("tests/benchmarks/expressions.q") + + for i := 0; i < b.N; i++ { + compiler.Run() + } +} diff --git a/src/build/Function.go b/src/build/Function.go index ce8660c..838a8d7 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -7,6 +7,7 @@ import ( "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" @@ -28,6 +29,7 @@ type Function struct { func (f *Function) Compile() { if config.Verbose { ansi.Bold.Println(f.Name) + ansi.Dim.Println("╭────────────────────────────────────────────────────────────") } start := 0 @@ -76,6 +78,7 @@ func (f *Function) Compile() { f.Assembler.Return() if config.Verbose { + ansi.Dim.Println("╰────────────────────────────────────────────────────────────") f.PrintAsm() } } @@ -84,6 +87,7 @@ func (f *Function) Compile() { func (f *Function) PrintAsm() { fmt.Println() ansi.Bold.Println(f.Name + ".asm") + ansi.Dim.Println("╭────────────────────────────────────────────────────────────") for _, x := range f.Assembler.Instructions { ansi.Dim.Print("│ ") @@ -95,6 +99,8 @@ func (f *Function) PrintAsm() { fmt.Print("\n") } + + ansi.Dim.Println("╰────────────────────────────────────────────────────────────") } // CompileInstruction compiles a single instruction. @@ -104,91 +110,93 @@ func (f *Function) CompileInstruction(line token.List) error { fmt.Println(line) } + if len(line) == 0 { + return nil + } + if line[0].Kind == token.Keyword { switch line[0].Text() { case "return": f.Assembler.Return() + + default: + return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) + } + } + + expr := expression.Parse(line) + + if expr == nil { + return nil + } + + defer expr.Close() + + if config.Verbose { + ansi.Dim.Print("├───○ exp ") + fmt.Println(expr) + } + + if expr.Token.Kind == token.Number || expr.Token.Kind == token.Identifier { + return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) + } + + if expr.Token.Text() == ":=" { + if len(expr.Children) < 2 { + return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.After()) + } + + name := expr.Children[0] + value := expr.Children[1] + + if config.Verbose { + ansi.Dim.Print("├───○ var ") + fmt.Println(name, value) + } + + expr.RemoveChild(value) + + f.Variables[name.Token.Text()] = &Variable{ + Name: name.Token.Text(), + Value: value, + IsConst: true, } return nil } - if len(line) < 2 { - return errors.New(&errors.InvalidInstruction{Instruction: line[0].Text()}, f.File, line[0].Position) - } + if expr.Token.Text() == "call" && expr.Children[0].Token.Text() == "syscall" { + parameters := expr.Children[1:] - if line[0].Kind == token.Identifier { - if line[1].Kind == token.Define { - name := line[0].Text() - value := line[2:] + for i, parameter := range parameters { + switch parameter.Token.Kind { + case token.Identifier: + name := parameter.Token.Text() + variable, exists := f.Variables[name] - if len(value) == 0 { - return errors.New(errors.MissingAssignValue, f.File, line[1].After()) - } - - if config.Verbose { - ansi.Dim.Printf("├── var ") - fmt.Println(name, "=", value) - } - - f.Variables[name] = &Variable{ - Name: name, - Value: value, - IsConst: true, - } - - return nil - } - - switch line[0].Text() { - case "syscall": - paramTokens := line[2 : len(line)-1] - start := 0 - i := 0 - var parameters []token.List - - for i < len(paramTokens) { - if paramTokens[i].Kind == token.Separator { - parameters = append(parameters, paramTokens[start:i]) - start = i + 1 + if !exists { + panic("Unknown identifier " + name) } - i++ - } - - if i != start { - parameters = append(parameters, paramTokens[start:i]) - } - - for i, list := range parameters { - switch list[0].Kind { - case token.Identifier: - name := list[0].Text() - variable, exists := f.Variables[name] - - if !exists { - panic("Unknown identifier " + name) - } - - if !variable.IsConst { - panic("Not implemented yet") - } - - n, _ := strconv.Atoi(variable.Value[0].Text()) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) - - case token.Number: - value := list[0].Text() - n, _ := strconv.Atoi(value) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) - - default: - panic("Unknown expression") + if !variable.IsConst { + panic("Not implemented yet") } - } - f.Assembler.Syscall() + n, _ := strconv.Atoi(variable.Value.Token.Text()) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + + case token.Number: + value := parameter.Token.Text() + n, _ := strconv.Atoi(value) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + + default: + panic("Unknown expression") + } } + + f.Assembler.Syscall() + return nil } return nil diff --git a/src/build/Scan.go b/src/build/Scan.go index 0a2c903..b3e808a 100644 --- a/src/build/Scan.go +++ b/src/build/Scan.go @@ -17,7 +17,7 @@ func Scan(files []string) (<-chan *Function, <-chan error) { errors := make(chan error) go func() { - scan(files, functions, errors) + scanFiles(files, functions, errors) close(functions) close(errors) }() @@ -25,8 +25,8 @@ func Scan(files []string) (<-chan *Function, <-chan error) { return functions, errors } -// scan scans the directory without channel allocations. -func scan(files []string, functions chan<- *Function, errors chan<- error) { +// scanFiles scans the list of files without channel allocations. +func scanFiles(files []string, functions chan<- *Function, errors chan<- error) { wg := sync.WaitGroup{} for _, file := range files { diff --git a/src/build/Variable.go b/src/build/Variable.go index 46190e2..b5f7338 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -1,10 +1,10 @@ package build -import "git.akyoto.dev/cli/q/src/build/token" +import "git.akyoto.dev/cli/q/src/build/expression" // Variable represents a variable in a function. type Variable struct { Name string - Value token.List + Value *expression.Expression IsConst bool } diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go new file mode 100644 index 0000000..167ca83 --- /dev/null +++ b/src/build/expression/Expression.go @@ -0,0 +1,113 @@ +package expression + +import ( + "strings" + + "git.akyoto.dev/cli/q/src/build/token" +) + +// Expression is a binary tree with an operator on each node. +type Expression struct { + Token token.Token + Parent *Expression + Children []*Expression +} + +// New creates a new expression. +func New() *Expression { + return pool.Get().(*Expression) +} + +// NewLeaf creates a new leaf node. +func NewLeaf(t token.Token) *Expression { + expr := New() + expr.Token = t + return expr +} + +// NewBinary creates a new binary operator expression. +func NewBinary(left *Expression, operator token.Token, right *Expression) *Expression { + expr := New() + expr.Token = operator + expr.AddChild(left) + expr.AddChild(right) + return expr +} + +// AddChild adds a child to the expression. +func (expr *Expression) AddChild(child *Expression) { + expr.Children = append(expr.Children, child) + child.Parent = expr +} + +// Close puts the expression back into the memory pool. +func (expr *Expression) Close() { + for _, child := range expr.Children { + child.Close() + } + + expr.Token.Reset() + expr.Parent = nil + expr.Children = expr.Children[:0] + pool.Put(expr) +} + +// RemoveChild removes a child from the expression. +func (expr *Expression) RemoveChild(child *Expression) { + for i, c := range expr.Children { + if c == child { + expr.Children = append(expr.Children[:i], expr.Children[i+1:]...) + child.Parent = nil + return + } + } +} + +// Replace replaces the tree with the new expression and adds the previous expression to it. +func (expr *Expression) Replace(tree *Expression) { + if expr.Parent != nil { + expr.Parent.Children[len(expr.Parent.Children)-1] = tree + tree.Parent = expr.Parent + } + + tree.AddChild(expr) +} + +// IsLeaf returns true if the expression has no children. +func (expr *Expression) IsLeaf() bool { + return len(expr.Children) == 0 +} + +// LastChild returns the last child. +func (expr *Expression) LastChild() *Expression { + return expr.Children[len(expr.Children)-1] +} + +// String generates a textual representation of the expression. +func (expr *Expression) String() string { + builder := strings.Builder{} + expr.write(&builder) + return builder.String() +} + +// write generates a textual representation of the expression. +func (expr *Expression) write(builder *strings.Builder) { + if expr.IsLeaf() { + builder.WriteString(expr.Token.Text()) + return + } + + builder.WriteByte('(') + builder.WriteString(expr.Token.Text()) + builder.WriteByte(' ') + + for i, child := range expr.Children { + child.write(builder) + + if i != len(expr.Children)-1 { + builder.WriteByte(' ') + } + } + + builder.WriteByte(')') +} diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go new file mode 100644 index 0000000..2a39366 --- /dev/null +++ b/src/build/expression/Expression_test.go @@ -0,0 +1,104 @@ +package expression_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/go/assert" +) + +func TestExpressionFromTokens(t *testing.T) { + tests := []struct { + Name string + Expression string + Result string + }{ + {"Empty", "", ""}, + {"Identity", "1", "1"}, + {"Basic calculation", "1+2", "(1+2)"}, + {"Same operator", "1+2+3", "((1+2)+3)"}, + {"Same operator 2", "1+2+3+4", "(((1+2)+3)+4)"}, + {"Different operator", "1+2-3", "((1+2)-3)"}, + {"Different operator 2", "1+2-3+4", "(((1+2)-3)+4)"}, + {"Different operator 3", "1+2-3+4-5", "((((1+2)-3)+4)-5)"}, + {"Grouped identity", "(1)", "1"}, + {"Grouped identity 2", "((1))", "1"}, + {"Grouped identity 3", "(((1)))", "1"}, + {"Adding identity", "(1)+(2)", "(1+2)"}, + {"Adding identity 2", "(1)+(2)+(3)", "((1+2)+3)"}, + {"Adding identity 3", "(1)+(2)+(3)+(4)", "(((1+2)+3)+4)"}, + {"Grouping", "(1+2)", "(1+2)"}, + {"Grouping 2", "(1+2+3)", "((1+2)+3)"}, + {"Grouping 3", "((1)+(2)+(3))", "((1+2)+3)"}, + {"Grouping left", "(1+2)*3", "((1+2)*3)"}, + {"Grouping right", "1*(2+3)", "(1*(2+3))"}, + {"Grouping same operator", "1+(2+3)", "(1+(2+3))"}, + {"Grouping same operator 2", "1+(2+3)+(4+5)", "((1+(2+3))+(4+5))"}, + {"Two groups", "(1+2)*(3+4)", "((1+2)*(3+4))"}, + {"Two groups 2", "(1+2-3)*(3+4-5)", "(((1+2)-3)*((3+4)-5))"}, + {"Two groups 3", "(1+2)*(3+4-5)", "((1+2)*((3+4)-5))"}, + {"Operator priority", "1+2*3", "(1+(2*3))"}, + {"Operator priority 2", "1*2+3", "((1*2)+3)"}, + {"Operator priority 3", "1+2*3+4", "((1+(2*3))+4)"}, + {"Operator priority 4", "1+2*(3+4)+5", "((1+(2*(3+4)))+5)"}, + {"Operator priority 5", "1+2*3*4", "(1+((2*3)*4))"}, + {"Operator priority 6", "1+2*3+4*5", "((1+(2*3))+(4*5))"}, + {"Operator priority 7", "1+2*3*4*5*6", "(1+((((2*3)*4)*5)*6))"}, + {"Operator priority 8", "1*2*3+4*5*6", "(((1*2)*3)+((4*5)*6))"}, + {"Complex", "(1+2-3*4)*(5+6-7*8)", "(((1+2)-(3*4))*((5+6)-(7*8)))"}, + {"Complex 2", "(1+2*3-4)*(5+6*7-8)", "(((1+(2*3))-4)*((5+(6*7))-8))"}, + {"Complex 3", "(1+2*3-4)*(5+6*7-8)+9-10*11", "(((((1+(2*3))-4)*((5+(6*7))-8))+9)-(10*11))"}, + {"Function calls", "a()", "a()"}, + {"Function calls 2", "a(1)", "a(1)"}, + {"Function calls 3", "a(1,2)", "a(1,2)"}, + {"Function calls 4", "a(1,2,3)", "a(1,2,3)"}, + {"Function calls 5", "a(1,2+2,3)", "a(1,(2+2),3)"}, + {"Function calls 6", "a(1,2+2,3+3)", "a(1,(2+2),(3+3))"}, + {"Function calls 7", "a(1+1,2,3)", "a((1+1),2,3)"}, + {"Function calls 8", "a(1+1,2+2,3+3)", "a((1+1),(2+2),(3+3))"}, + {"Function calls 9", "a(b())", "a(b())"}, + {"Function calls 10", "a(b(),c())", "a(b(),c())"}, + {"Function calls 11", "a(b(),c(),d())", "a(b(),c(),d())"}, + {"Function calls 12", "a(b(1),c(2),d(3))", "a(b(1),c(2),d(3))"}, + {"Function calls 13", "a(b(1)+1)", "a((b(1)+1))"}, + {"Function calls 14", "a(b(1)+1,c(2),d(3))", "a((b(1)+1),c(2),d(3))"}, + {"Function calls 15", "a(b(1)*c(2))", "a((b(1)*c(2)))"}, + {"Function calls 16", "a(b(1)*c(2),d(3)+e(4),f(5)/f(6))", "a((b(1)*c(2)),(d(3)+e(4)),(f(5)/f(6)))"}, + {"Function calls 17", "a((b(1,2)+c(3,4))*d(5,6))", "a(((b(1,2)+c(3,4))*d(5,6)))"}, + {"Function calls 18", "a((b(1,2)+c(3,4))*d(5,6),e())", "a(((b(1,2)+c(3,4))*d(5,6)),e())"}, + {"Function calls 19", "a((b(1,2)+c(3,4))*d(5,6),e(7+8,9-10*11,12))", "a(((b(1,2)+c(3,4))*d(5,6)),e((7+8),(9-(10*11)),12))"}, + {"Function calls 20", "a((b(1,2,bb())+c(3,4,cc(0)))*d(5,6,dd(0)),e(7+8,9-10*11,12,ee(0)))", "a(((b(1,2,bb())+c(3,4,cc(0)))*d(5,6,dd(0))),e((7+8),(9-(10*11)),12,ee(0)))"}, + {"Function calls 21", "a(1-2*3)", "a((1-(2*3)))"}, + {"Function calls 22", "1+2*a()+4", "((1+(2*a()))+4)"}, + {"Function calls 23", "sum(a,b)*2+15*4", "((sum(a,b)*2)+(15*4))"}, + {"Package function calls", "math.sum(a,b)", "(math.sum(a,b))"}, + {"Package function calls 2", "generic.math.sum(a,b)", "((generic.math).sum(a,b))"}, + } + + for _, test := range tests { + test := test + + t.Run(test.Name, func(t *testing.T) { + src := []byte(test.Expression + "\n") + tokens := token.Tokenize(src) + expr := expression.Parse(tokens) + assert.NotNil(t, expr) + t.Log(expr) + // assert.Equal(t, expr.String(), test.Result) + }) + } +} + +func BenchmarkExpression(b *testing.B) { + src := []byte("(1+2-3*4)*(5+6-7*8)\n") + tokens := token.Tokenize(src) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + expr := expression.Parse(tokens) + expr.Close() + } +} diff --git a/src/build/expression/List.go b/src/build/expression/List.go new file mode 100644 index 0000000..b6fe4a6 --- /dev/null +++ b/src/build/expression/List.go @@ -0,0 +1,41 @@ +package expression + +import ( + "git.akyoto.dev/cli/q/src/build/token" +) + +// List generates a list of expressions from comma separated parameters. +func List(tokens []token.Token) []*Expression { + var list []*Expression + + start := 0 + groupLevel := 0 + + for i, t := range tokens { + switch t.Kind { + case token.GroupStart, token.ArrayStart, token.BlockStart: + groupLevel++ + + case token.GroupEnd, token.ArrayEnd, token.BlockEnd: + groupLevel-- + + case token.Separator: + if groupLevel > 0 { + continue + } + + parameter := tokens[start:i] + expression := Parse(parameter) + list = append(list, expression) + start = i + 1 + } + } + + if start != len(tokens) { + parameter := tokens[start:] + expression := Parse(parameter) + list = append(list, expression) + } + + return list +} diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go new file mode 100644 index 0000000..2edc304 --- /dev/null +++ b/src/build/expression/Operator.go @@ -0,0 +1,65 @@ +package expression + +import "git.akyoto.dev/cli/q/src/build/token" + +// Operator represents an operator for mathematical expressions. +type Operator struct { + Symbol string + Precedence int + Operands int +} + +// Operators defines the operators used in the language. +// The number corresponds to the operator priority and can not be zero. +var Operators = map[string]*Operator{ + ".": {".", 12, 2}, + "*": {"*", 11, 2}, + "/": {"/", 11, 2}, + "%": {"%", 11, 2}, + "+": {"+", 10, 2}, + "-": {"-", 10, 2}, + ">>": {">>", 9, 2}, + "<<": {"<<", 9, 2}, + ">": {">", 8, 2}, + "<": {"<", 8, 2}, + ">=": {">=", 8, 2}, + "<=": {"<=", 8, 2}, + "==": {"==", 7, 2}, + "!=": {"!=", 7, 2}, + "&": {"&", 6, 2}, + "^": {"^", 5, 2}, + "|": {"|", 4, 2}, + "&&": {"&&", 3, 2}, + "||": {"||", 2, 2}, + "=": {"=", 1, 2}, + "+=": {"+=", 1, 2}, + "-=": {"-=", 1, 2}, + "*=": {"*=", 1, 2}, + "/=": {"/=", 1, 2}, + ">>=": {">>=", 1, 2}, + "<<=": {"<<=", 1, 2}, +} + +func isComplete(expr *Expression) bool { + if expr == nil { + return false + } + + if expr.Token.Kind == token.Identifier { + return true + } + + if expr.Token.Kind == token.Operator && len(expr.Children) == numOperands(expr.Token.Text()) { + return true + } + + return false +} + +func numOperands(symbol string) int { + return Operators[symbol].Operands +} + +func precedence(symbol string) int { + return Operators[symbol].Precedence +} diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go new file mode 100644 index 0000000..0e9b844 --- /dev/null +++ b/src/build/expression/Parse.go @@ -0,0 +1,142 @@ +package expression + +import ( + "git.akyoto.dev/cli/q/src/build/token" +) + +var call = []byte("call") + +// Parse generates an expression tree from tokens. +func Parse(tokens token.List) *Expression { + var ( + cursor *Expression + root *Expression + i = 0 + groupLevel = 0 + groupPosition = 0 + ) + + for i < len(tokens) { + switch tokens[i].Kind { + case token.GroupStart: + groupLevel++ + + if groupLevel == 1 { + groupPosition = i + 1 + } + + case token.GroupEnd: + groupLevel-- + + if groupLevel == 0 { + isFunctionCall := isComplete(cursor) + + if isFunctionCall { + parameters := List(tokens[groupPosition:i]) + + node := New() + node.Token.Kind = token.Operator + node.Token.Position = tokens[groupPosition].Position + node.Token.Bytes = call + cursor.Replace(node) + + for _, param := range parameters { + node.AddChild(param) + } + + if cursor == root { + root = node + } + + i++ + continue + } + + group := Parse(tokens[groupPosition:i]) + + if group == nil { + i++ + continue + } + + if cursor == nil { + cursor = group + root = group + } else { + cursor.AddChild(group) + } + } + } + + if groupLevel != 0 { + i++ + continue + } + + switch tokens[i].Kind { + case token.Operator: + if cursor == nil { + cursor = NewLeaf(tokens[i]) + root = cursor + i++ + continue + } + + node := NewLeaf(tokens[i]) + + if cursor.Token.Kind == token.Operator { + oldPrecedence := precedence(cursor.Token.Text()) + newPrecedence := precedence(node.Token.Text()) + + if newPrecedence > oldPrecedence { + cursor.LastChild().Replace(node) + } else { + start := cursor + + for start != nil { + precedence := precedence(start.Token.Text()) + + if precedence < newPrecedence { + start.LastChild().Replace(node) + break + } + + if precedence == newPrecedence { + if start == root { + root = node + } + + start.Replace(node) + break + } + + start = start.Parent + } + + if start == nil { + root.Replace(node) + root = node + } + } + } else { + node.AddChild(cursor) + root = node + } + + cursor = node + + case token.Identifier, token.Number, token.String: + if cursor == nil { + cursor = NewLeaf(tokens[i]) + root = cursor + } else { + node := NewLeaf(tokens[i]) + cursor.AddChild(node) + } + } + + i++ + } + + return root +} diff --git a/src/build/expression/pool.go b/src/build/expression/pool.go new file mode 100644 index 0000000..42b571d --- /dev/null +++ b/src/build/expression/pool.go @@ -0,0 +1,9 @@ +package expression + +import "sync" + +var pool = sync.Pool{ + New: func() interface{} { + return &Expression{} + }, +} diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index f7b8e80..b221442 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -25,9 +25,6 @@ const ( // Number represents a series of numerical characters. Number - // Define represents the assignment operator `:=` for a new variable. - Define - // Operator represents a mathematical operator. Operator @@ -66,7 +63,6 @@ func (kind Kind) String() string { "Keyword", "String", "Number", - "Define", "Operator", "Separator", "Comment", diff --git a/src/build/token/List.go b/src/build/token/List.go index 30f344b..be3a1ff 100644 --- a/src/build/token/List.go +++ b/src/build/token/List.go @@ -13,7 +13,7 @@ func (list List) String() string { var last Token for _, t := range list { - if last.Kind == Keyword || last.Kind == Separator || last.Kind == Define || t.Kind == Define { + if last.Kind == Keyword || last.Kind == Separator || last.Kind == Operator || t.Kind == Operator { builder.WriteByte(' ') } diff --git a/src/build/token/Token.go b/src/build/token/Token.go index c01d302..66c77b1 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -12,16 +12,23 @@ type Token struct { } // After returns the position after the token. -func (t Token) After() int { +func (t *Token) After() int { return t.Position + len(t.Bytes) } // String creates a human readable representation for debugging purposes. -func (t Token) String() string { +func (t *Token) String() string { return fmt.Sprintf("%s %s", t.Kind, t.Text()) } +// Reset resets the token to default values. +func (t *Token) Reset() { + t.Kind = Invalid + t.Position = 0 + t.Bytes = nil +} + // Text returns the token text. -func (t Token) Text() string { +func (t *Token) Text() string { return string(t.Bytes) } diff --git a/src/build/token/Token_test.go b/src/build/token/Token_test.go index e1f66f5..b083cfd 100644 --- a/src/build/token/Token_test.go +++ b/src/build/token/Token_test.go @@ -117,7 +117,7 @@ func TestNewline(t *testing.T) { } func TestNumber(t *testing.T) { - tokens := token.Tokenize([]byte(`123 -456`)) + tokens := token.Tokenize([]byte(`123 456`)) assert.DeepEqual(t, tokens, token.List{ { Kind: token.Number, @@ -126,13 +126,13 @@ func TestNumber(t *testing.T) { }, { Kind: token.Number, - Bytes: []byte("-456"), + Bytes: []byte("456"), Position: 4, }, { Kind: token.EOF, Bytes: nil, - Position: 8, + Position: 7, }, }) } diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index c89ddba..0e51758 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -1,7 +1,5 @@ package token -import "bytes" - // Pre-allocate these byte buffers so we can re-use them // instead of allocating a new buffer every time. var ( @@ -12,7 +10,6 @@ var ( arrayStartBytes = []byte{'['} arrayEndBytes = []byte{']'} separatorBytes = []byte{','} - defineBytes = []byte{':', '='} newLineBytes = []byte{'\n'} ) @@ -97,7 +94,7 @@ func Tokenize(buffer []byte) List { } // Numbers - if isNumberStart(buffer[i]) { + if isNumber(buffer[i]) { position := i i++ @@ -118,11 +115,6 @@ func Tokenize(buffer []byte) List { i++ } - if bytes.Equal(buffer[position:i], defineBytes) { - tokens = append(tokens, Token{Define, position, defineBytes}) - continue - } - tokens = append(tokens, Token{Operator, position, buffer[position:i]}) continue } @@ -151,10 +143,6 @@ func isNumber(c byte) bool { return (c >= '0' && c <= '9') } -func isNumberStart(c byte) bool { - return isNumber(c) || c == '-' -} - func isOperator(c byte) bool { - return c == '=' || c == ':' || c == '+' || c == '-' || c == '*' || c == '/' || c == '<' || c == '>' || c == '!' + return c == '=' || c == ':' || c == '+' || c == '-' || c == '*' || c == '/' || c == '<' || c == '>' || c == '!' || c == '&' || c == '|' || c == '^' || c == '%' || c == '.' } diff --git a/src/errors/KeywordNotImplemented.go b/src/errors/KeywordNotImplemented.go new file mode 100644 index 0000000..d652457 --- /dev/null +++ b/src/errors/KeywordNotImplemented.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// KeywordNotImplemented error is created when we find a keyword without an implementation. +type KeywordNotImplemented struct { + Keyword string +} + +// Error generates the string representation. +func (err *KeywordNotImplemented) Error() string { + return fmt.Sprintf("Keyword not implemented: '%s'", err.Keyword) +} diff --git a/tests/benchmarks/empty.q b/tests/benchmarks/empty.q new file mode 100644 index 0000000..66bf3bc --- /dev/null +++ b/tests/benchmarks/empty.q @@ -0,0 +1,3 @@ +main() { + +} \ No newline at end of file diff --git a/tests/benchmarks/expressions.q b/tests/benchmarks/expressions.q new file mode 100644 index 0000000..b563384 --- /dev/null +++ b/tests/benchmarks/expressions.q @@ -0,0 +1,7 @@ +main() { + () + 1+(2*3) + (1+2) + f(x) + (a+b)(c) +} \ No newline at end of file From bb9fac143069f6cf9f977f6a970703bccbc0e61c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Jun 2024 22:48:14 +0200 Subject: [PATCH 0093/1012] Improved expression parser --- src/build/Function.go | 10 +- src/build/expression/Expression.go | 8 +- src/build/expression/Expression_test.go | 128 +++++++++++----------- src/build/expression/Operator.go | 9 +- src/build/expression/Parse.go | 135 +++++++++++++----------- 5 files changed, 152 insertions(+), 138 deletions(-) diff --git a/src/build/Function.go b/src/build/Function.go index 838a8d7..5da9cc6 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -133,8 +133,7 @@ func (f *Function) CompileInstruction(line token.List) error { defer expr.Close() if config.Verbose { - ansi.Dim.Print("├───○ exp ") - fmt.Println(expr) + ansi.Dim.Printf("│ %s\n", expr) } if expr.Token.Kind == token.Number || expr.Token.Kind == token.Identifier { @@ -149,11 +148,6 @@ func (f *Function) CompileInstruction(line token.List) error { name := expr.Children[0] value := expr.Children[1] - if config.Verbose { - ansi.Dim.Print("├───○ var ") - fmt.Println(name, value) - } - expr.RemoveChild(value) f.Variables[name.Token.Text()] = &Variable{ @@ -165,7 +159,7 @@ func (f *Function) CompileInstruction(line token.List) error { return nil } - if expr.Token.Text() == "call" && expr.Children[0].Token.Text() == "syscall" { + if expr.Token.Text() == "λ" && expr.Children[0].Token.Text() == "syscall" { parameters := expr.Children[1:] for i, parameter := range parameters { diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 167ca83..3cfbb97 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -8,9 +8,10 @@ import ( // Expression is a binary tree with an operator on each node. type Expression struct { - Token token.Token - Parent *Expression - Children []*Expression + Token token.Token + Parent *Expression + Children []*Expression + Precedence int } // New creates a new expression. @@ -49,6 +50,7 @@ func (expr *Expression) Close() { expr.Token.Reset() expr.Parent = nil expr.Children = expr.Children[:0] + expr.Precedence = 0 pool.Put(expr) } diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index 2a39366..99f2ac2 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -8,95 +8,95 @@ import ( "git.akyoto.dev/go/assert" ) -func TestExpressionFromTokens(t *testing.T) { +func TestExpressionParse(t *testing.T) { tests := []struct { Name string Expression string Result string }{ - {"Empty", "", ""}, {"Identity", "1", "1"}, - {"Basic calculation", "1+2", "(1+2)"}, - {"Same operator", "1+2+3", "((1+2)+3)"}, - {"Same operator 2", "1+2+3+4", "(((1+2)+3)+4)"}, - {"Different operator", "1+2-3", "((1+2)-3)"}, - {"Different operator 2", "1+2-3+4", "(((1+2)-3)+4)"}, - {"Different operator 3", "1+2-3+4-5", "((((1+2)-3)+4)-5)"}, + {"Basic calculation", "1+2", "(+ 1 2)"}, + {"Same operator", "1+2+3", "(+ (+ 1 2) 3)"}, + {"Same operator 2", "1+2+3+4", "(+ (+ (+ 1 2) 3) 4)"}, + {"Different operator", "1+2-3", "(- (+ 1 2) 3)"}, + {"Different operator 2", "1+2-3+4", "(+ (- (+ 1 2) 3) 4)"}, + {"Different operator 3", "1+2-3+4-5", "(- (+ (- (+ 1 2) 3) 4) 5)"}, {"Grouped identity", "(1)", "1"}, {"Grouped identity 2", "((1))", "1"}, {"Grouped identity 3", "(((1)))", "1"}, - {"Adding identity", "(1)+(2)", "(1+2)"}, - {"Adding identity 2", "(1)+(2)+(3)", "((1+2)+3)"}, - {"Adding identity 3", "(1)+(2)+(3)+(4)", "(((1+2)+3)+4)"}, - {"Grouping", "(1+2)", "(1+2)"}, - {"Grouping 2", "(1+2+3)", "((1+2)+3)"}, - {"Grouping 3", "((1)+(2)+(3))", "((1+2)+3)"}, - {"Grouping left", "(1+2)*3", "((1+2)*3)"}, - {"Grouping right", "1*(2+3)", "(1*(2+3))"}, - {"Grouping same operator", "1+(2+3)", "(1+(2+3))"}, - {"Grouping same operator 2", "1+(2+3)+(4+5)", "((1+(2+3))+(4+5))"}, - {"Two groups", "(1+2)*(3+4)", "((1+2)*(3+4))"}, - {"Two groups 2", "(1+2-3)*(3+4-5)", "(((1+2)-3)*((3+4)-5))"}, - {"Two groups 3", "(1+2)*(3+4-5)", "((1+2)*((3+4)-5))"}, - {"Operator priority", "1+2*3", "(1+(2*3))"}, - {"Operator priority 2", "1*2+3", "((1*2)+3)"}, - {"Operator priority 3", "1+2*3+4", "((1+(2*3))+4)"}, - {"Operator priority 4", "1+2*(3+4)+5", "((1+(2*(3+4)))+5)"}, - {"Operator priority 5", "1+2*3*4", "(1+((2*3)*4))"}, - {"Operator priority 6", "1+2*3+4*5", "((1+(2*3))+(4*5))"}, - {"Operator priority 7", "1+2*3*4*5*6", "(1+((((2*3)*4)*5)*6))"}, - {"Operator priority 8", "1*2*3+4*5*6", "(((1*2)*3)+((4*5)*6))"}, - {"Complex", "(1+2-3*4)*(5+6-7*8)", "(((1+2)-(3*4))*((5+6)-(7*8)))"}, - {"Complex 2", "(1+2*3-4)*(5+6*7-8)", "(((1+(2*3))-4)*((5+(6*7))-8))"}, - {"Complex 3", "(1+2*3-4)*(5+6*7-8)+9-10*11", "(((((1+(2*3))-4)*((5+(6*7))-8))+9)-(10*11))"}, - {"Function calls", "a()", "a()"}, - {"Function calls 2", "a(1)", "a(1)"}, - {"Function calls 3", "a(1,2)", "a(1,2)"}, - {"Function calls 4", "a(1,2,3)", "a(1,2,3)"}, - {"Function calls 5", "a(1,2+2,3)", "a(1,(2+2),3)"}, - {"Function calls 6", "a(1,2+2,3+3)", "a(1,(2+2),(3+3))"}, - {"Function calls 7", "a(1+1,2,3)", "a((1+1),2,3)"}, - {"Function calls 8", "a(1+1,2+2,3+3)", "a((1+1),(2+2),(3+3))"}, - {"Function calls 9", "a(b())", "a(b())"}, - {"Function calls 10", "a(b(),c())", "a(b(),c())"}, - {"Function calls 11", "a(b(),c(),d())", "a(b(),c(),d())"}, - {"Function calls 12", "a(b(1),c(2),d(3))", "a(b(1),c(2),d(3))"}, - {"Function calls 13", "a(b(1)+1)", "a((b(1)+1))"}, - {"Function calls 14", "a(b(1)+1,c(2),d(3))", "a((b(1)+1),c(2),d(3))"}, - {"Function calls 15", "a(b(1)*c(2))", "a((b(1)*c(2)))"}, - {"Function calls 16", "a(b(1)*c(2),d(3)+e(4),f(5)/f(6))", "a((b(1)*c(2)),(d(3)+e(4)),(f(5)/f(6)))"}, - {"Function calls 17", "a((b(1,2)+c(3,4))*d(5,6))", "a(((b(1,2)+c(3,4))*d(5,6)))"}, - {"Function calls 18", "a((b(1,2)+c(3,4))*d(5,6),e())", "a(((b(1,2)+c(3,4))*d(5,6)),e())"}, - {"Function calls 19", "a((b(1,2)+c(3,4))*d(5,6),e(7+8,9-10*11,12))", "a(((b(1,2)+c(3,4))*d(5,6)),e((7+8),(9-(10*11)),12))"}, - {"Function calls 20", "a((b(1,2,bb())+c(3,4,cc(0)))*d(5,6,dd(0)),e(7+8,9-10*11,12,ee(0)))", "a(((b(1,2,bb())+c(3,4,cc(0)))*d(5,6,dd(0))),e((7+8),(9-(10*11)),12,ee(0)))"}, - {"Function calls 21", "a(1-2*3)", "a((1-(2*3)))"}, - {"Function calls 22", "1+2*a()+4", "((1+(2*a()))+4)"}, - {"Function calls 23", "sum(a,b)*2+15*4", "((sum(a,b)*2)+(15*4))"}, - {"Package function calls", "math.sum(a,b)", "(math.sum(a,b))"}, - {"Package function calls 2", "generic.math.sum(a,b)", "((generic.math).sum(a,b))"}, + {"Adding identity", "(1)+(2)", "(+ 1 2)"}, + {"Adding identity 2", "(1)+(2)+(3)", "(+ (+ 1 2) 3)"}, + {"Adding identity 3", "(1)+(2)+(3)+(4)", "(+ (+ (+ 1 2) 3) 4)"}, + {"Grouping", "(1+2)", "(+ 1 2)"}, + {"Grouping 2", "(1+2+3)", "(+ (+ 1 2) 3)"}, + {"Grouping 3", "((1)+(2)+(3))", "(+ (+ 1 2) 3)"}, + {"Grouping left", "(1+2)*3", "(* (+ 1 2) 3)"}, + {"Grouping right", "1*(2+3)", "(* 1 (+ 2 3))"}, + {"Grouping same operator", "1+(2+3)", "(+ 1 (+ 2 3))"}, + {"Grouping same operator 2", "1+(2+3)+(4+5)", "(+ (+ 1 (+ 2 3)) (+ 4 5))"}, + {"Two groups", "(1+2)*(3+4)", "(* (+ 1 2) (+ 3 4))"}, + {"Two groups 2", "(1+2-3)*(3+4-5)", "(* (- (+ 1 2) 3) (- (+ 3 4) 5))"}, + {"Two groups 3", "(1+2)*(3+4-5)", "(* (+ 1 2) (- (+ 3 4) 5))"}, + {"Operator priority", "1+2*3", "(+ 1 (* 2 3))"}, + {"Operator priority 2", "1*2+3", "(+ (* 1 2) 3)"}, + {"Operator priority 3", "1+2*3+4", "(+ (+ 1 (* 2 3)) 4)"}, + {"Operator priority 4", "1+2*(3+4)+5", "(+ (+ 1 (* 2 (+ 3 4))) 5)"}, + {"Operator priority 5", "1+2*3*4", "(+ 1 (* (* 2 3) 4))"}, + {"Operator priority 6", "1+2*3+4*5", "(+ (+ 1 (* 2 3)) (* 4 5))"}, + {"Operator priority 7", "1+2*3*4*5*6", "(+ 1 (* (* (* (* 2 3) 4) 5) 6))"}, + {"Operator priority 8", "1*2*3+4*5*6", "(+ (* (* 1 2) 3) (* (* 4 5) 6))"}, + {"Complex", "(1+2-3*4)*(5+6-7*8)", "(* (- (+ 1 2) (* 3 4)) (- (+ 5 6) (* 7 8)))"}, + {"Complex 2", "(1+2*3-4)*(5+6*7-8)", "(* (- (+ 1 (* 2 3)) 4) (- (+ 5 (* 6 7)) 8))"}, + {"Complex 3", "(1+2*3-4)*(5+6*7-8)+9-10*11", "(- (+ (* (- (+ 1 (* 2 3)) 4) (- (+ 5 (* 6 7)) 8)) 9) (* 10 11))"}, + {"Unary", "!", "!"}, + {"Function calls", "a()", "(λ a)"}, + {"Function calls 2", "a(1)", "(λ a 1)"}, + {"Function calls 3", "a(1)+1", "(+ (λ a 1) 1)"}, + {"Function calls 4", "1+a(1)", "(+ 1 (λ a 1))"}, + {"Function calls 5", "a(1,2)", "(λ a 1 2)"}, + {"Function calls 6", "a(1,2,3)", "(λ a 1 2 3)"}, + {"Function calls 7", "a(1,2+2,3)", "(λ a 1 (+ 2 2) 3)"}, + {"Function calls 8", "a(1,2+2,3+3)", "(λ a 1 (+ 2 2) (+ 3 3))"}, + {"Function calls 9", "a(1+1,2,3)", "(λ a (+ 1 1) 2 3)"}, + {"Function calls 10", "a(1+1,2+2,3+3)", "(λ a (+ 1 1) (+ 2 2) (+ 3 3))"}, + {"Function calls 11", "a(b())", "(λ a (λ b))"}, + {"Function calls 12", "a(b(),c())", "(λ a (λ b) (λ c))"}, + {"Function calls 13", "a(b(),c(),d())", "(λ a (λ b) (λ c) (λ d))"}, + {"Function calls 14", "a(b(1))", "(λ a (λ b 1))"}, + {"Function calls 15", "a(b(1),c(2),d(3))", "(λ a (λ b 1) (λ c 2) (λ d 3))"}, + {"Function calls 16", "a(b(1)+1)", "(λ a (+ (λ b 1) 1))"}, + {"Function calls 17", "a(b(1)+1,c(2),d(3))", "(λ a (+ (λ b 1) 1) (λ c 2) (λ d 3))"}, + {"Function calls 18", "a(b(1)*c(2))", "(λ a (* (λ b 1) (λ c 2)))"}, + {"Function calls 19", "a(b(1)*c(2),d(3)+e(4),f(5)/f(6))", "(λ a (* (λ b 1) (λ c 2)) (+ (λ d 3) (λ e 4)) (/ (λ f 5) (λ f 6)))"}, + {"Function calls 20", "a(b(1,2)+c(3,4)*d(5,6))", "(λ a (+ (λ b 1 2) (* (λ c 3 4) (λ d 5 6))))"}, + {"Function calls 21", "a((b(1,2)+c(3,4))*d(5,6))", "(λ a (* (+ (λ b 1 2) (λ c 3 4)) (λ d 5 6)))"}, + {"Function calls 22", "a((b(1,2)+c(3,4))*d(5,6),e())", "(λ a (* (+ (λ b 1 2) (λ c 3 4)) (λ d 5 6)) (λ e))"}, + {"Function calls 23", "a((b(1,2)+c(3,4))*d(5,6),e(7+8,9-10*11,12))", "(λ a (* (+ (λ b 1 2) (λ c 3 4)) (λ d 5 6)) (λ e (+ 7 8) (- 9 (* 10 11)) 12))"}, + {"Function calls 24", "a((b(1,2,bb())+c(3,4,cc(0)))*d(5,6,dd(0)),e(7+8,9-10*11,12,ee(0)))", "(λ a (* (+ (λ b 1 2 (λ bb)) (λ c 3 4 (λ cc 0))) (λ d 5 6 (λ dd 0))) (λ e (+ 7 8) (- 9 (* 10 11)) 12 (λ ee 0)))"}, + {"Function calls 25", "a(1-2*3)", "(λ a (- 1 (* 2 3)))"}, + {"Function calls 26", "1+2*a()+4", "(+ (+ 1 (* 2 (λ a))) 4)"}, + {"Function calls 27", "sum(a,b)*2+15*4", "(+ (* (λ sum a b) 2) (* 15 4))"}, + {"Package function calls", "math.sum(a,b)", "(λ (. math sum) a b)"}, + {"Package function calls 2", "generic.math.sum(a,b)", "(λ (. (. generic math) sum) a b)"}, } for _, test := range tests { test := test t.Run(test.Name, func(t *testing.T) { - src := []byte(test.Expression + "\n") + src := []byte(test.Expression) tokens := token.Tokenize(src) expr := expression.Parse(tokens) assert.NotNil(t, expr) - t.Log(expr) - // assert.Equal(t, expr.String(), test.Result) + assert.Equal(t, expr.String(), test.Result) }) } } func BenchmarkExpression(b *testing.B) { - src := []byte("(1+2-3*4)*(5+6-7*8)\n") + src := []byte("(1+2-3*4)!=(5*6-7+8)\n") tokens := token.Tokenize(src) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { expr := expression.Parse(tokens) expr.Close() diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index 2edc304..5a48a19 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -12,7 +12,9 @@ type Operator struct { // Operators defines the operators used in the language. // The number corresponds to the operator priority and can not be zero. var Operators = map[string]*Operator{ - ".": {".", 12, 2}, + ".": {".", 14, 2}, + "λ": {"λ", 13, 1}, + "!": {"λ", 12, 1}, "*": {"*", 11, 2}, "/": {"/", 11, 2}, "%": {"%", 11, 2}, @@ -32,6 +34,7 @@ var Operators = map[string]*Operator{ "&&": {"&&", 3, 2}, "||": {"||", 2, 2}, "=": {"=", 1, 2}, + ":=": {":=", 1, 2}, "+=": {"+=", 1, 2}, "-=": {"-=", 1, 2}, "*=": {"*=", 1, 2}, @@ -45,11 +48,11 @@ func isComplete(expr *Expression) bool { return false } - if expr.Token.Kind == token.Identifier { + if expr.Token.Kind == token.Identifier || expr.Token.Kind == token.Number || expr.Token.Kind == token.String { return true } - if expr.Token.Kind == token.Operator && len(expr.Children) == numOperands(expr.Token.Text()) { + if expr.Token.Kind == token.Operator && len(expr.Children) >= numOperands(expr.Token.Text()) { return true } diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index 0e9b844..536718b 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -1,92 +1,117 @@ package expression import ( + "math" + "git.akyoto.dev/cli/q/src/build/token" ) -var call = []byte("call") +var call = []byte("λ") // Parse generates an expression tree from tokens. func Parse(tokens token.List) *Expression { var ( cursor *Expression root *Expression - i = 0 groupLevel = 0 groupPosition = 0 ) - for i < len(tokens) { - switch tokens[i].Kind { - case token.GroupStart: + for i, t := range tokens { + if t.Kind == token.GroupStart { groupLevel++ if groupLevel == 1 { groupPosition = i + 1 } - case token.GroupEnd: + continue + } + + if t.Kind == token.GroupEnd { groupLevel-- - if groupLevel == 0 { - isFunctionCall := isComplete(cursor) + if groupLevel != 0 { + continue + } - if isFunctionCall { - parameters := List(tokens[groupPosition:i]) + isFunctionCall := isComplete(cursor) - node := New() - node.Token.Kind = token.Operator - node.Token.Position = tokens[groupPosition].Position - node.Token.Bytes = call - cursor.Replace(node) + if isFunctionCall { + parameters := List(tokens[groupPosition:i]) - for _, param := range parameters { - node.AddChild(param) - } + node := New() + node.Token.Kind = token.Operator + node.Token.Position = tokens[groupPosition].Position + node.Token.Bytes = call + node.Precedence = precedence("λ") + if cursor.Token.Kind == token.Operator && node.Precedence > cursor.Precedence { + cursor.LastChild().Replace(node) + } else { if cursor == root { root = node } - i++ - continue + cursor.Replace(node) } - group := Parse(tokens[groupPosition:i]) - - if group == nil { - i++ - continue + for _, param := range parameters { + node.AddChild(param) } - if cursor == nil { - cursor = group - root = group - } else { - cursor.AddChild(group) - } - } - } - - if groupLevel != 0 { - i++ - continue - } - - switch tokens[i].Kind { - case token.Operator: - if cursor == nil { - cursor = NewLeaf(tokens[i]) - root = cursor - i++ + cursor = node continue } - node := NewLeaf(tokens[i]) + group := Parse(tokens[groupPosition:i]) + + if group == nil { + continue + } + + group.Precedence = math.MaxInt + + if cursor == nil { + cursor = group + root = group + } else { + cursor.AddChild(group) + } + + continue + } + + if groupLevel > 0 { + continue + } + + if t.Kind == token.Identifier || t.Kind == token.Number || t.Kind == token.String { + if cursor != nil { + node := NewLeaf(t) + cursor.AddChild(node) + } else { + cursor = NewLeaf(t) + root = cursor + } + + continue + } + + if t.Kind == token.Operator { + if cursor == nil { + cursor = NewLeaf(t) + cursor.Precedence = precedence(t.Text()) + root = cursor + continue + } + + node := NewLeaf(t) + node.Precedence = precedence(t.Text()) if cursor.Token.Kind == token.Operator { - oldPrecedence := precedence(cursor.Token.Text()) - newPrecedence := precedence(node.Token.Text()) + oldPrecedence := cursor.Precedence + newPrecedence := node.Precedence if newPrecedence > oldPrecedence { cursor.LastChild().Replace(node) @@ -94,7 +119,7 @@ func Parse(tokens token.List) *Expression { start := cursor for start != nil { - precedence := precedence(start.Token.Text()) + precedence := start.Precedence if precedence < newPrecedence { start.LastChild().Replace(node) @@ -124,18 +149,8 @@ func Parse(tokens token.List) *Expression { } cursor = node - - case token.Identifier, token.Number, token.String: - if cursor == nil { - cursor = NewLeaf(tokens[i]) - root = cursor - } else { - node := NewLeaf(tokens[i]) - cursor.AddChild(node) - } + continue } - - i++ } return root From 23c6134d9aefca46bb1cde6c7a7b1b0289201d5c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Jun 2024 22:48:14 +0200 Subject: [PATCH 0094/1012] Improved expression parser --- src/build/Function.go | 10 +- src/build/expression/Expression.go | 8 +- src/build/expression/Expression_test.go | 128 +++++++++++----------- src/build/expression/Operator.go | 9 +- src/build/expression/Parse.go | 135 +++++++++++++----------- 5 files changed, 152 insertions(+), 138 deletions(-) diff --git a/src/build/Function.go b/src/build/Function.go index 838a8d7..5da9cc6 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -133,8 +133,7 @@ func (f *Function) CompileInstruction(line token.List) error { defer expr.Close() if config.Verbose { - ansi.Dim.Print("├───○ exp ") - fmt.Println(expr) + ansi.Dim.Printf("│ %s\n", expr) } if expr.Token.Kind == token.Number || expr.Token.Kind == token.Identifier { @@ -149,11 +148,6 @@ func (f *Function) CompileInstruction(line token.List) error { name := expr.Children[0] value := expr.Children[1] - if config.Verbose { - ansi.Dim.Print("├───○ var ") - fmt.Println(name, value) - } - expr.RemoveChild(value) f.Variables[name.Token.Text()] = &Variable{ @@ -165,7 +159,7 @@ func (f *Function) CompileInstruction(line token.List) error { return nil } - if expr.Token.Text() == "call" && expr.Children[0].Token.Text() == "syscall" { + if expr.Token.Text() == "λ" && expr.Children[0].Token.Text() == "syscall" { parameters := expr.Children[1:] for i, parameter := range parameters { diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 167ca83..3cfbb97 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -8,9 +8,10 @@ import ( // Expression is a binary tree with an operator on each node. type Expression struct { - Token token.Token - Parent *Expression - Children []*Expression + Token token.Token + Parent *Expression + Children []*Expression + Precedence int } // New creates a new expression. @@ -49,6 +50,7 @@ func (expr *Expression) Close() { expr.Token.Reset() expr.Parent = nil expr.Children = expr.Children[:0] + expr.Precedence = 0 pool.Put(expr) } diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index 2a39366..99f2ac2 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -8,95 +8,95 @@ import ( "git.akyoto.dev/go/assert" ) -func TestExpressionFromTokens(t *testing.T) { +func TestExpressionParse(t *testing.T) { tests := []struct { Name string Expression string Result string }{ - {"Empty", "", ""}, {"Identity", "1", "1"}, - {"Basic calculation", "1+2", "(1+2)"}, - {"Same operator", "1+2+3", "((1+2)+3)"}, - {"Same operator 2", "1+2+3+4", "(((1+2)+3)+4)"}, - {"Different operator", "1+2-3", "((1+2)-3)"}, - {"Different operator 2", "1+2-3+4", "(((1+2)-3)+4)"}, - {"Different operator 3", "1+2-3+4-5", "((((1+2)-3)+4)-5)"}, + {"Basic calculation", "1+2", "(+ 1 2)"}, + {"Same operator", "1+2+3", "(+ (+ 1 2) 3)"}, + {"Same operator 2", "1+2+3+4", "(+ (+ (+ 1 2) 3) 4)"}, + {"Different operator", "1+2-3", "(- (+ 1 2) 3)"}, + {"Different operator 2", "1+2-3+4", "(+ (- (+ 1 2) 3) 4)"}, + {"Different operator 3", "1+2-3+4-5", "(- (+ (- (+ 1 2) 3) 4) 5)"}, {"Grouped identity", "(1)", "1"}, {"Grouped identity 2", "((1))", "1"}, {"Grouped identity 3", "(((1)))", "1"}, - {"Adding identity", "(1)+(2)", "(1+2)"}, - {"Adding identity 2", "(1)+(2)+(3)", "((1+2)+3)"}, - {"Adding identity 3", "(1)+(2)+(3)+(4)", "(((1+2)+3)+4)"}, - {"Grouping", "(1+2)", "(1+2)"}, - {"Grouping 2", "(1+2+3)", "((1+2)+3)"}, - {"Grouping 3", "((1)+(2)+(3))", "((1+2)+3)"}, - {"Grouping left", "(1+2)*3", "((1+2)*3)"}, - {"Grouping right", "1*(2+3)", "(1*(2+3))"}, - {"Grouping same operator", "1+(2+3)", "(1+(2+3))"}, - {"Grouping same operator 2", "1+(2+3)+(4+5)", "((1+(2+3))+(4+5))"}, - {"Two groups", "(1+2)*(3+4)", "((1+2)*(3+4))"}, - {"Two groups 2", "(1+2-3)*(3+4-5)", "(((1+2)-3)*((3+4)-5))"}, - {"Two groups 3", "(1+2)*(3+4-5)", "((1+2)*((3+4)-5))"}, - {"Operator priority", "1+2*3", "(1+(2*3))"}, - {"Operator priority 2", "1*2+3", "((1*2)+3)"}, - {"Operator priority 3", "1+2*3+4", "((1+(2*3))+4)"}, - {"Operator priority 4", "1+2*(3+4)+5", "((1+(2*(3+4)))+5)"}, - {"Operator priority 5", "1+2*3*4", "(1+((2*3)*4))"}, - {"Operator priority 6", "1+2*3+4*5", "((1+(2*3))+(4*5))"}, - {"Operator priority 7", "1+2*3*4*5*6", "(1+((((2*3)*4)*5)*6))"}, - {"Operator priority 8", "1*2*3+4*5*6", "(((1*2)*3)+((4*5)*6))"}, - {"Complex", "(1+2-3*4)*(5+6-7*8)", "(((1+2)-(3*4))*((5+6)-(7*8)))"}, - {"Complex 2", "(1+2*3-4)*(5+6*7-8)", "(((1+(2*3))-4)*((5+(6*7))-8))"}, - {"Complex 3", "(1+2*3-4)*(5+6*7-8)+9-10*11", "(((((1+(2*3))-4)*((5+(6*7))-8))+9)-(10*11))"}, - {"Function calls", "a()", "a()"}, - {"Function calls 2", "a(1)", "a(1)"}, - {"Function calls 3", "a(1,2)", "a(1,2)"}, - {"Function calls 4", "a(1,2,3)", "a(1,2,3)"}, - {"Function calls 5", "a(1,2+2,3)", "a(1,(2+2),3)"}, - {"Function calls 6", "a(1,2+2,3+3)", "a(1,(2+2),(3+3))"}, - {"Function calls 7", "a(1+1,2,3)", "a((1+1),2,3)"}, - {"Function calls 8", "a(1+1,2+2,3+3)", "a((1+1),(2+2),(3+3))"}, - {"Function calls 9", "a(b())", "a(b())"}, - {"Function calls 10", "a(b(),c())", "a(b(),c())"}, - {"Function calls 11", "a(b(),c(),d())", "a(b(),c(),d())"}, - {"Function calls 12", "a(b(1),c(2),d(3))", "a(b(1),c(2),d(3))"}, - {"Function calls 13", "a(b(1)+1)", "a((b(1)+1))"}, - {"Function calls 14", "a(b(1)+1,c(2),d(3))", "a((b(1)+1),c(2),d(3))"}, - {"Function calls 15", "a(b(1)*c(2))", "a((b(1)*c(2)))"}, - {"Function calls 16", "a(b(1)*c(2),d(3)+e(4),f(5)/f(6))", "a((b(1)*c(2)),(d(3)+e(4)),(f(5)/f(6)))"}, - {"Function calls 17", "a((b(1,2)+c(3,4))*d(5,6))", "a(((b(1,2)+c(3,4))*d(5,6)))"}, - {"Function calls 18", "a((b(1,2)+c(3,4))*d(5,6),e())", "a(((b(1,2)+c(3,4))*d(5,6)),e())"}, - {"Function calls 19", "a((b(1,2)+c(3,4))*d(5,6),e(7+8,9-10*11,12))", "a(((b(1,2)+c(3,4))*d(5,6)),e((7+8),(9-(10*11)),12))"}, - {"Function calls 20", "a((b(1,2,bb())+c(3,4,cc(0)))*d(5,6,dd(0)),e(7+8,9-10*11,12,ee(0)))", "a(((b(1,2,bb())+c(3,4,cc(0)))*d(5,6,dd(0))),e((7+8),(9-(10*11)),12,ee(0)))"}, - {"Function calls 21", "a(1-2*3)", "a((1-(2*3)))"}, - {"Function calls 22", "1+2*a()+4", "((1+(2*a()))+4)"}, - {"Function calls 23", "sum(a,b)*2+15*4", "((sum(a,b)*2)+(15*4))"}, - {"Package function calls", "math.sum(a,b)", "(math.sum(a,b))"}, - {"Package function calls 2", "generic.math.sum(a,b)", "((generic.math).sum(a,b))"}, + {"Adding identity", "(1)+(2)", "(+ 1 2)"}, + {"Adding identity 2", "(1)+(2)+(3)", "(+ (+ 1 2) 3)"}, + {"Adding identity 3", "(1)+(2)+(3)+(4)", "(+ (+ (+ 1 2) 3) 4)"}, + {"Grouping", "(1+2)", "(+ 1 2)"}, + {"Grouping 2", "(1+2+3)", "(+ (+ 1 2) 3)"}, + {"Grouping 3", "((1)+(2)+(3))", "(+ (+ 1 2) 3)"}, + {"Grouping left", "(1+2)*3", "(* (+ 1 2) 3)"}, + {"Grouping right", "1*(2+3)", "(* 1 (+ 2 3))"}, + {"Grouping same operator", "1+(2+3)", "(+ 1 (+ 2 3))"}, + {"Grouping same operator 2", "1+(2+3)+(4+5)", "(+ (+ 1 (+ 2 3)) (+ 4 5))"}, + {"Two groups", "(1+2)*(3+4)", "(* (+ 1 2) (+ 3 4))"}, + {"Two groups 2", "(1+2-3)*(3+4-5)", "(* (- (+ 1 2) 3) (- (+ 3 4) 5))"}, + {"Two groups 3", "(1+2)*(3+4-5)", "(* (+ 1 2) (- (+ 3 4) 5))"}, + {"Operator priority", "1+2*3", "(+ 1 (* 2 3))"}, + {"Operator priority 2", "1*2+3", "(+ (* 1 2) 3)"}, + {"Operator priority 3", "1+2*3+4", "(+ (+ 1 (* 2 3)) 4)"}, + {"Operator priority 4", "1+2*(3+4)+5", "(+ (+ 1 (* 2 (+ 3 4))) 5)"}, + {"Operator priority 5", "1+2*3*4", "(+ 1 (* (* 2 3) 4))"}, + {"Operator priority 6", "1+2*3+4*5", "(+ (+ 1 (* 2 3)) (* 4 5))"}, + {"Operator priority 7", "1+2*3*4*5*6", "(+ 1 (* (* (* (* 2 3) 4) 5) 6))"}, + {"Operator priority 8", "1*2*3+4*5*6", "(+ (* (* 1 2) 3) (* (* 4 5) 6))"}, + {"Complex", "(1+2-3*4)*(5+6-7*8)", "(* (- (+ 1 2) (* 3 4)) (- (+ 5 6) (* 7 8)))"}, + {"Complex 2", "(1+2*3-4)*(5+6*7-8)", "(* (- (+ 1 (* 2 3)) 4) (- (+ 5 (* 6 7)) 8))"}, + {"Complex 3", "(1+2*3-4)*(5+6*7-8)+9-10*11", "(- (+ (* (- (+ 1 (* 2 3)) 4) (- (+ 5 (* 6 7)) 8)) 9) (* 10 11))"}, + {"Unary", "!", "!"}, + {"Function calls", "a()", "(λ a)"}, + {"Function calls 2", "a(1)", "(λ a 1)"}, + {"Function calls 3", "a(1)+1", "(+ (λ a 1) 1)"}, + {"Function calls 4", "1+a(1)", "(+ 1 (λ a 1))"}, + {"Function calls 5", "a(1,2)", "(λ a 1 2)"}, + {"Function calls 6", "a(1,2,3)", "(λ a 1 2 3)"}, + {"Function calls 7", "a(1,2+2,3)", "(λ a 1 (+ 2 2) 3)"}, + {"Function calls 8", "a(1,2+2,3+3)", "(λ a 1 (+ 2 2) (+ 3 3))"}, + {"Function calls 9", "a(1+1,2,3)", "(λ a (+ 1 1) 2 3)"}, + {"Function calls 10", "a(1+1,2+2,3+3)", "(λ a (+ 1 1) (+ 2 2) (+ 3 3))"}, + {"Function calls 11", "a(b())", "(λ a (λ b))"}, + {"Function calls 12", "a(b(),c())", "(λ a (λ b) (λ c))"}, + {"Function calls 13", "a(b(),c(),d())", "(λ a (λ b) (λ c) (λ d))"}, + {"Function calls 14", "a(b(1))", "(λ a (λ b 1))"}, + {"Function calls 15", "a(b(1),c(2),d(3))", "(λ a (λ b 1) (λ c 2) (λ d 3))"}, + {"Function calls 16", "a(b(1)+1)", "(λ a (+ (λ b 1) 1))"}, + {"Function calls 17", "a(b(1)+1,c(2),d(3))", "(λ a (+ (λ b 1) 1) (λ c 2) (λ d 3))"}, + {"Function calls 18", "a(b(1)*c(2))", "(λ a (* (λ b 1) (λ c 2)))"}, + {"Function calls 19", "a(b(1)*c(2),d(3)+e(4),f(5)/f(6))", "(λ a (* (λ b 1) (λ c 2)) (+ (λ d 3) (λ e 4)) (/ (λ f 5) (λ f 6)))"}, + {"Function calls 20", "a(b(1,2)+c(3,4)*d(5,6))", "(λ a (+ (λ b 1 2) (* (λ c 3 4) (λ d 5 6))))"}, + {"Function calls 21", "a((b(1,2)+c(3,4))*d(5,6))", "(λ a (* (+ (λ b 1 2) (λ c 3 4)) (λ d 5 6)))"}, + {"Function calls 22", "a((b(1,2)+c(3,4))*d(5,6),e())", "(λ a (* (+ (λ b 1 2) (λ c 3 4)) (λ d 5 6)) (λ e))"}, + {"Function calls 23", "a((b(1,2)+c(3,4))*d(5,6),e(7+8,9-10*11,12))", "(λ a (* (+ (λ b 1 2) (λ c 3 4)) (λ d 5 6)) (λ e (+ 7 8) (- 9 (* 10 11)) 12))"}, + {"Function calls 24", "a((b(1,2,bb())+c(3,4,cc(0)))*d(5,6,dd(0)),e(7+8,9-10*11,12,ee(0)))", "(λ a (* (+ (λ b 1 2 (λ bb)) (λ c 3 4 (λ cc 0))) (λ d 5 6 (λ dd 0))) (λ e (+ 7 8) (- 9 (* 10 11)) 12 (λ ee 0)))"}, + {"Function calls 25", "a(1-2*3)", "(λ a (- 1 (* 2 3)))"}, + {"Function calls 26", "1+2*a()+4", "(+ (+ 1 (* 2 (λ a))) 4)"}, + {"Function calls 27", "sum(a,b)*2+15*4", "(+ (* (λ sum a b) 2) (* 15 4))"}, + {"Package function calls", "math.sum(a,b)", "(λ (. math sum) a b)"}, + {"Package function calls 2", "generic.math.sum(a,b)", "(λ (. (. generic math) sum) a b)"}, } for _, test := range tests { test := test t.Run(test.Name, func(t *testing.T) { - src := []byte(test.Expression + "\n") + src := []byte(test.Expression) tokens := token.Tokenize(src) expr := expression.Parse(tokens) assert.NotNil(t, expr) - t.Log(expr) - // assert.Equal(t, expr.String(), test.Result) + assert.Equal(t, expr.String(), test.Result) }) } } func BenchmarkExpression(b *testing.B) { - src := []byte("(1+2-3*4)*(5+6-7*8)\n") + src := []byte("(1+2-3*4)!=(5*6-7+8)\n") tokens := token.Tokenize(src) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { expr := expression.Parse(tokens) expr.Close() diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index 2edc304..5a48a19 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -12,7 +12,9 @@ type Operator struct { // Operators defines the operators used in the language. // The number corresponds to the operator priority and can not be zero. var Operators = map[string]*Operator{ - ".": {".", 12, 2}, + ".": {".", 14, 2}, + "λ": {"λ", 13, 1}, + "!": {"λ", 12, 1}, "*": {"*", 11, 2}, "/": {"/", 11, 2}, "%": {"%", 11, 2}, @@ -32,6 +34,7 @@ var Operators = map[string]*Operator{ "&&": {"&&", 3, 2}, "||": {"||", 2, 2}, "=": {"=", 1, 2}, + ":=": {":=", 1, 2}, "+=": {"+=", 1, 2}, "-=": {"-=", 1, 2}, "*=": {"*=", 1, 2}, @@ -45,11 +48,11 @@ func isComplete(expr *Expression) bool { return false } - if expr.Token.Kind == token.Identifier { + if expr.Token.Kind == token.Identifier || expr.Token.Kind == token.Number || expr.Token.Kind == token.String { return true } - if expr.Token.Kind == token.Operator && len(expr.Children) == numOperands(expr.Token.Text()) { + if expr.Token.Kind == token.Operator && len(expr.Children) >= numOperands(expr.Token.Text()) { return true } diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index 0e9b844..536718b 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -1,92 +1,117 @@ package expression import ( + "math" + "git.akyoto.dev/cli/q/src/build/token" ) -var call = []byte("call") +var call = []byte("λ") // Parse generates an expression tree from tokens. func Parse(tokens token.List) *Expression { var ( cursor *Expression root *Expression - i = 0 groupLevel = 0 groupPosition = 0 ) - for i < len(tokens) { - switch tokens[i].Kind { - case token.GroupStart: + for i, t := range tokens { + if t.Kind == token.GroupStart { groupLevel++ if groupLevel == 1 { groupPosition = i + 1 } - case token.GroupEnd: + continue + } + + if t.Kind == token.GroupEnd { groupLevel-- - if groupLevel == 0 { - isFunctionCall := isComplete(cursor) + if groupLevel != 0 { + continue + } - if isFunctionCall { - parameters := List(tokens[groupPosition:i]) + isFunctionCall := isComplete(cursor) - node := New() - node.Token.Kind = token.Operator - node.Token.Position = tokens[groupPosition].Position - node.Token.Bytes = call - cursor.Replace(node) + if isFunctionCall { + parameters := List(tokens[groupPosition:i]) - for _, param := range parameters { - node.AddChild(param) - } + node := New() + node.Token.Kind = token.Operator + node.Token.Position = tokens[groupPosition].Position + node.Token.Bytes = call + node.Precedence = precedence("λ") + if cursor.Token.Kind == token.Operator && node.Precedence > cursor.Precedence { + cursor.LastChild().Replace(node) + } else { if cursor == root { root = node } - i++ - continue + cursor.Replace(node) } - group := Parse(tokens[groupPosition:i]) - - if group == nil { - i++ - continue + for _, param := range parameters { + node.AddChild(param) } - if cursor == nil { - cursor = group - root = group - } else { - cursor.AddChild(group) - } - } - } - - if groupLevel != 0 { - i++ - continue - } - - switch tokens[i].Kind { - case token.Operator: - if cursor == nil { - cursor = NewLeaf(tokens[i]) - root = cursor - i++ + cursor = node continue } - node := NewLeaf(tokens[i]) + group := Parse(tokens[groupPosition:i]) + + if group == nil { + continue + } + + group.Precedence = math.MaxInt + + if cursor == nil { + cursor = group + root = group + } else { + cursor.AddChild(group) + } + + continue + } + + if groupLevel > 0 { + continue + } + + if t.Kind == token.Identifier || t.Kind == token.Number || t.Kind == token.String { + if cursor != nil { + node := NewLeaf(t) + cursor.AddChild(node) + } else { + cursor = NewLeaf(t) + root = cursor + } + + continue + } + + if t.Kind == token.Operator { + if cursor == nil { + cursor = NewLeaf(t) + cursor.Precedence = precedence(t.Text()) + root = cursor + continue + } + + node := NewLeaf(t) + node.Precedence = precedence(t.Text()) if cursor.Token.Kind == token.Operator { - oldPrecedence := precedence(cursor.Token.Text()) - newPrecedence := precedence(node.Token.Text()) + oldPrecedence := cursor.Precedence + newPrecedence := node.Precedence if newPrecedence > oldPrecedence { cursor.LastChild().Replace(node) @@ -94,7 +119,7 @@ func Parse(tokens token.List) *Expression { start := cursor for start != nil { - precedence := precedence(start.Token.Text()) + precedence := start.Precedence if precedence < newPrecedence { start.LastChild().Replace(node) @@ -124,18 +149,8 @@ func Parse(tokens token.List) *Expression { } cursor = node - - case token.Identifier, token.Number, token.String: - if cursor == nil { - cursor = NewLeaf(tokens[i]) - root = cursor - } else { - node := NewLeaf(tokens[i]) - cursor.AddChild(node) - } + continue } - - i++ } return root From 50fe55705680eb71b2ff910d9a5955ad88edfd9d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Jun 2024 22:55:12 +0200 Subject: [PATCH 0095/1012] Improved expression tests --- src/build/expression/Expression_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index 99f2ac2..62ce2d9 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -87,6 +87,8 @@ func TestExpressionParse(t *testing.T) { src := []byte(test.Expression) tokens := token.Tokenize(src) expr := expression.Parse(tokens) + defer expr.Close() + assert.NotNil(t, expr) assert.Equal(t, expr.String(), test.Result) }) @@ -94,7 +96,7 @@ func TestExpressionParse(t *testing.T) { } func BenchmarkExpression(b *testing.B) { - src := []byte("(1+2-3*4)!=(5*6-7+8)\n") + src := []byte("(1+2-3*4)+(5*6-7+8)\n") tokens := token.Tokenize(src) for i := 0; i < b.N; i++ { From 8b49200a877ae1dd4108c92077f84ce109b04d45 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Jun 2024 22:55:12 +0200 Subject: [PATCH 0096/1012] Improved expression tests --- src/build/expression/Expression_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index 99f2ac2..62ce2d9 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -87,6 +87,8 @@ func TestExpressionParse(t *testing.T) { src := []byte(test.Expression) tokens := token.Tokenize(src) expr := expression.Parse(tokens) + defer expr.Close() + assert.NotNil(t, expr) assert.Equal(t, expr.String(), test.Result) }) @@ -94,7 +96,7 @@ func TestExpressionParse(t *testing.T) { } func BenchmarkExpression(b *testing.B) { - src := []byte("(1+2-3*4)!=(5*6-7+8)\n") + src := []byte("(1+2-3*4)+(5*6-7+8)\n") tokens := token.Tokenize(src) for i := 0; i < b.N; i++ { From cd1fe5eebbfd048989bd2d848d714c7a92c7a185 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Jun 2024 23:06:21 +0200 Subject: [PATCH 0097/1012] Fixed incorrect operator symbol --- src/build/expression/Operator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index 5a48a19..a1823e1 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -14,7 +14,7 @@ type Operator struct { var Operators = map[string]*Operator{ ".": {".", 14, 2}, "λ": {"λ", 13, 1}, - "!": {"λ", 12, 1}, + "!": {"!", 12, 1}, "*": {"*", 11, 2}, "/": {"/", 11, 2}, "%": {"%", 11, 2}, From 086998a0c3d858104092009226b86c8e9500162e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Jun 2024 23:06:21 +0200 Subject: [PATCH 0098/1012] Fixed incorrect operator symbol --- src/build/expression/Operator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index 5a48a19..a1823e1 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -14,7 +14,7 @@ type Operator struct { var Operators = map[string]*Operator{ ".": {".", 14, 2}, "λ": {"λ", 13, 1}, - "!": {"λ", 12, 1}, + "!": {"!", 12, 1}, "*": {"*", 11, 2}, "/": {"/", 11, 2}, "%": {"%", 11, 2}, From 6043baee48387fffd176b47991091e458856478f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 18 Jun 2024 16:42:56 +0200 Subject: [PATCH 0099/1012] Implemented function calls --- examples/hello/hello.q | 10 +++++--- src/build/Finalize.go | 21 ++++++++++++++++- src/build/Function.go | 42 +++++++++++++-------------------- src/build/asm/Assembler.go | 34 ++++++++++++++++---------- src/build/asm/Instructions.go | 27 +++++++++++++-------- src/build/asm/Label.go | 11 +++++++++ src/build/asm/Mnemonic.go | 8 +++++++ src/build/asm/Pointer.go | 6 ++--- src/build/asm/RegisterNumber.go | 5 ++-- src/cli/Build.go | 6 +++++ tests/benchmarks/expressions.q | 6 +---- 11 files changed, 114 insertions(+), 62 deletions(-) create mode 100644 src/build/asm/Label.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 8754a01..60f65e4 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,8 +1,12 @@ main() { + hello() +} + +hello() { write := 1 stdout := 1 - exit := 60 + address := 4194305 + length := 3 - syscall(write, stdout, 4194305, 3) - syscall(exit, 0) + syscall(write, stdout, address, length) } \ No newline at end of file diff --git a/src/build/Finalize.go b/src/build/Finalize.go index 413ce71..333b849 100644 --- a/src/build/Finalize.go +++ b/src/build/Finalize.go @@ -1,12 +1,18 @@ package build import ( + "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/os/linux" ) // Finalize generates the final machine code. func Finalize(functions map[string]*Function) ([]byte, []byte) { - a := asm.New() + a := entry() + + main := functions["main"] + delete(functions, "main") + a.Merge(&main.Assembler) for _, f := range functions { a.Merge(&f.Assembler) @@ -15,3 +21,16 @@ func Finalize(functions map[string]*Function) ([]byte, []byte) { code, data := a.Finalize() return code, data } + +// entry returns the entry point of the executable. +// The only job of the entry function is to call `main` and exit cleanly. +// The reason we call `main` instead of using `main` itself is to place +// a return address on the stack, which allows return statements in `main`. +func entry() *asm.Assembler { + entry := asm.New() + entry.Call("main") + entry.MoveRegisterNumber(x64.SyscallArgs[0], linux.Exit) + entry.MoveRegisterNumber(x64.SyscallArgs[1], 0) + entry.Syscall() + return entry +} diff --git a/src/build/Function.go b/src/build/Function.go index 5da9cc6..f303968 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -6,7 +6,6 @@ import ( "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" @@ -27,11 +26,7 @@ type Function struct { // Compile turns a function into machine code. func (f *Function) Compile() { - if config.Verbose { - ansi.Bold.Println(f.Name) - ansi.Dim.Println("╭────────────────────────────────────────────────────────────") - } - + f.Assembler.Label(f.Name) start := 0 groupLevel := 0 @@ -76,17 +71,11 @@ func (f *Function) Compile() { } f.Assembler.Return() - - if config.Verbose { - ansi.Dim.Println("╰────────────────────────────────────────────────────────────") - f.PrintAsm() - } } // PrintAsm shows the assembly instructions. func (f *Function) PrintAsm() { - fmt.Println() - ansi.Bold.Println(f.Name + ".asm") + ansi.Bold.Println(f.Name) ansi.Dim.Println("╭────────────────────────────────────────────────────────────") for _, x := range f.Assembler.Instructions { @@ -105,11 +94,6 @@ func (f *Function) PrintAsm() { // CompileInstruction compiles a single instruction. func (f *Function) CompileInstruction(line token.List) error { - if config.Verbose { - ansi.Dim.Print("│ ") - fmt.Println(line) - } - if len(line) == 0 { return nil } @@ -117,6 +101,12 @@ func (f *Function) CompileInstruction(line token.List) error { if line[0].Kind == token.Keyword { switch line[0].Text() { case "return": + if len(line) > 1 { + value := expression.Parse(line[1:]) + defer value.Close() + // TODO: Set the return value + } + f.Assembler.Return() default: @@ -132,11 +122,7 @@ func (f *Function) CompileInstruction(line token.List) error { defer expr.Close() - if config.Verbose { - ansi.Dim.Printf("│ %s\n", expr) - } - - if expr.Token.Kind == token.Number || expr.Token.Kind == token.Identifier { + if expr.Token.Kind == token.Number || expr.Token.Kind == token.Identifier || expr.Token.Kind == token.String { return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) } @@ -159,7 +145,8 @@ func (f *Function) CompileInstruction(line token.List) error { return nil } - if expr.Token.Text() == "λ" && expr.Children[0].Token.Text() == "syscall" { + if expr.Token.Text() == "λ" { + funcName := expr.Children[0].Token.Text() parameters := expr.Children[1:] for i, parameter := range parameters { @@ -189,7 +176,12 @@ func (f *Function) CompileInstruction(line token.List) error { } } - f.Assembler.Syscall() + if funcName == "syscall" { + f.Assembler.Syscall() + } else { + f.Assembler.Call(funcName) + } + return nil } diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index d0186e7..5af72a6 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -4,7 +4,6 @@ import ( "encoding/binary" "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/config" ) // Assembler contains a list of instructions. @@ -23,19 +22,14 @@ func New() *Assembler { func (a *Assembler) Finalize() ([]byte, []byte) { code := make([]byte, 0, len(a.Instructions)*8) data := make([]byte, 0, 16) + labels := map[string]Address{} pointers := []Pointer{} for _, x := range a.Instructions { switch x.Mnemonic { case MOVE: - code = x64.MoveRegNum32(code, uint8(x.Data.(*RegisterNumber).Register), uint32(x.Data.(*RegisterNumber).Number)) - - if x.Data.(*RegisterNumber).IsPointer { - pointers = append(pointers, Pointer{ - Position: Address(len(code) - 4), - Address: Address(x.Data.(*RegisterNumber).Number), - }) - } + regNum := x.Data.(*RegisterNumber) + code = x64.MoveRegNum32(code, uint8(regNum.Register), uint32(regNum.Number)) case RETURN: code = x64.Return(code) @@ -43,17 +37,33 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case SYSCALL: code = x64.Syscall(code) + case CALL: + code = x64.Call(code, 0x00_00_00_00) + label := x.Data.(*Label) + nextInstructionAddress := len(code) + + pointers = append(pointers, Pointer{ + Position: Address(len(code) - 4), + Resolve: func() Address { + destination := labels[label.Name] + distance := int32(destination) - int32(nextInstructionAddress) + return Address(distance) + }, + }) + + case LABEL: + labels[x.Data.(*Label).Name] = Address(len(code)) + default: panic("Unknown mnemonic: " + x.Mnemonic.String()) } } - dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) + // dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) for _, pointer := range pointers { slice := code[pointer.Position : pointer.Position+4] - address := dataStart + pointer.Address - binary.LittleEndian.PutUint32(slice, address) + binary.LittleEndian.PutUint32(slice, pointer.Resolve()) } return code, data diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index de5f5d8..aa74d0e 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -7,21 +7,28 @@ func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: MOVE, Data: &RegisterNumber{ - Register: reg, - Number: number, - IsPointer: false, + Register: reg, + Number: number, }, }) } -// MoveRegisterAddress moves an address into the given register. -func (a *Assembler) MoveRegisterAddress(reg cpu.Register, address Address) { +// Label adds a label at the current position. +func (a *Assembler) Label(name string) { a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: MOVE, - Data: &RegisterNumber{ - Register: reg, - Number: uint64(address), - IsPointer: true, + Mnemonic: LABEL, + Data: &Label{ + Name: name, + }, + }) +} + +// Call calls a function whose position is identified by a label. +func (a *Assembler) Call(name string) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: CALL, + Data: &Label{ + Name: name, }, }) } diff --git a/src/build/asm/Label.go b/src/build/asm/Label.go new file mode 100644 index 0000000..1636fb8 --- /dev/null +++ b/src/build/asm/Label.go @@ -0,0 +1,11 @@ +package asm + +// Label represents a jump label. +type Label struct { + Name string +} + +// String returns a human readable version. +func (data *Label) String() string { + return data.Name +} diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 7fd1a06..ad071c6 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -7,6 +7,8 @@ const ( MOVE RETURN SYSCALL + LABEL + CALL ) // String returns a human readable version. @@ -20,6 +22,12 @@ func (m Mnemonic) String() string { case SYSCALL: return "syscall" + + case LABEL: + return "label" + + case CALL: + return "call" } return "NONE" diff --git a/src/build/asm/Pointer.go b/src/build/asm/Pointer.go index 7063ec5..6499ce9 100644 --- a/src/build/asm/Pointer.go +++ b/src/build/asm/Pointer.go @@ -5,8 +5,8 @@ type Address = uint32 // Pointer stores a relative memory address that we can later turn into an absolute one. // Position: The machine code offset where the address was inserted. -// Address: The offset inside the section. +// Resolve: The function that will return the final address. type Pointer struct { - Position uint32 - Address uint32 + Position Address + Resolve func() Address } diff --git a/src/build/asm/RegisterNumber.go b/src/build/asm/RegisterNumber.go index bfd7321..6484e08 100644 --- a/src/build/asm/RegisterNumber.go +++ b/src/build/asm/RegisterNumber.go @@ -8,9 +8,8 @@ import ( // RegisterNumber operates with a register and a number. type RegisterNumber struct { - Register cpu.Register - Number uint64 - IsPointer bool + Register cpu.Register + Number uint64 } // String returns a human readable version. diff --git a/src/cli/Build.go b/src/cli/Build.go index 697ec7e..6120250 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -42,6 +42,12 @@ func Build(args []string) int { return 1 } + if config.Verbose { + for _, function := range result { + function.PrintAsm() + } + } + if !writeExecutable { return 0 } diff --git a/tests/benchmarks/expressions.q b/tests/benchmarks/expressions.q index b563384..194f47d 100644 --- a/tests/benchmarks/expressions.q +++ b/tests/benchmarks/expressions.q @@ -1,7 +1,3 @@ main() { - () - 1+(2*3) - (1+2) - f(x) - (a+b)(c) + return 1+2*3 } \ No newline at end of file From 76db8feee3f7f1643a68140918bc1d7c141fa289 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 18 Jun 2024 16:42:56 +0200 Subject: [PATCH 0100/1012] Implemented function calls --- examples/hello/hello.q | 10 +++++--- src/build/Finalize.go | 21 ++++++++++++++++- src/build/Function.go | 42 +++++++++++++-------------------- src/build/asm/Assembler.go | 34 ++++++++++++++++---------- src/build/asm/Instructions.go | 27 +++++++++++++-------- src/build/asm/Label.go | 11 +++++++++ src/build/asm/Mnemonic.go | 8 +++++++ src/build/asm/Pointer.go | 6 ++--- src/build/asm/RegisterNumber.go | 5 ++-- src/cli/Build.go | 6 +++++ tests/benchmarks/expressions.q | 6 +---- 11 files changed, 114 insertions(+), 62 deletions(-) create mode 100644 src/build/asm/Label.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 8754a01..60f65e4 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,8 +1,12 @@ main() { + hello() +} + +hello() { write := 1 stdout := 1 - exit := 60 + address := 4194305 + length := 3 - syscall(write, stdout, 4194305, 3) - syscall(exit, 0) + syscall(write, stdout, address, length) } \ No newline at end of file diff --git a/src/build/Finalize.go b/src/build/Finalize.go index 413ce71..333b849 100644 --- a/src/build/Finalize.go +++ b/src/build/Finalize.go @@ -1,12 +1,18 @@ package build import ( + "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/os/linux" ) // Finalize generates the final machine code. func Finalize(functions map[string]*Function) ([]byte, []byte) { - a := asm.New() + a := entry() + + main := functions["main"] + delete(functions, "main") + a.Merge(&main.Assembler) for _, f := range functions { a.Merge(&f.Assembler) @@ -15,3 +21,16 @@ func Finalize(functions map[string]*Function) ([]byte, []byte) { code, data := a.Finalize() return code, data } + +// entry returns the entry point of the executable. +// The only job of the entry function is to call `main` and exit cleanly. +// The reason we call `main` instead of using `main` itself is to place +// a return address on the stack, which allows return statements in `main`. +func entry() *asm.Assembler { + entry := asm.New() + entry.Call("main") + entry.MoveRegisterNumber(x64.SyscallArgs[0], linux.Exit) + entry.MoveRegisterNumber(x64.SyscallArgs[1], 0) + entry.Syscall() + return entry +} diff --git a/src/build/Function.go b/src/build/Function.go index 5da9cc6..f303968 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -6,7 +6,6 @@ import ( "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" @@ -27,11 +26,7 @@ type Function struct { // Compile turns a function into machine code. func (f *Function) Compile() { - if config.Verbose { - ansi.Bold.Println(f.Name) - ansi.Dim.Println("╭────────────────────────────────────────────────────────────") - } - + f.Assembler.Label(f.Name) start := 0 groupLevel := 0 @@ -76,17 +71,11 @@ func (f *Function) Compile() { } f.Assembler.Return() - - if config.Verbose { - ansi.Dim.Println("╰────────────────────────────────────────────────────────────") - f.PrintAsm() - } } // PrintAsm shows the assembly instructions. func (f *Function) PrintAsm() { - fmt.Println() - ansi.Bold.Println(f.Name + ".asm") + ansi.Bold.Println(f.Name) ansi.Dim.Println("╭────────────────────────────────────────────────────────────") for _, x := range f.Assembler.Instructions { @@ -105,11 +94,6 @@ func (f *Function) PrintAsm() { // CompileInstruction compiles a single instruction. func (f *Function) CompileInstruction(line token.List) error { - if config.Verbose { - ansi.Dim.Print("│ ") - fmt.Println(line) - } - if len(line) == 0 { return nil } @@ -117,6 +101,12 @@ func (f *Function) CompileInstruction(line token.List) error { if line[0].Kind == token.Keyword { switch line[0].Text() { case "return": + if len(line) > 1 { + value := expression.Parse(line[1:]) + defer value.Close() + // TODO: Set the return value + } + f.Assembler.Return() default: @@ -132,11 +122,7 @@ func (f *Function) CompileInstruction(line token.List) error { defer expr.Close() - if config.Verbose { - ansi.Dim.Printf("│ %s\n", expr) - } - - if expr.Token.Kind == token.Number || expr.Token.Kind == token.Identifier { + if expr.Token.Kind == token.Number || expr.Token.Kind == token.Identifier || expr.Token.Kind == token.String { return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) } @@ -159,7 +145,8 @@ func (f *Function) CompileInstruction(line token.List) error { return nil } - if expr.Token.Text() == "λ" && expr.Children[0].Token.Text() == "syscall" { + if expr.Token.Text() == "λ" { + funcName := expr.Children[0].Token.Text() parameters := expr.Children[1:] for i, parameter := range parameters { @@ -189,7 +176,12 @@ func (f *Function) CompileInstruction(line token.List) error { } } - f.Assembler.Syscall() + if funcName == "syscall" { + f.Assembler.Syscall() + } else { + f.Assembler.Call(funcName) + } + return nil } diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index d0186e7..5af72a6 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -4,7 +4,6 @@ import ( "encoding/binary" "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/config" ) // Assembler contains a list of instructions. @@ -23,19 +22,14 @@ func New() *Assembler { func (a *Assembler) Finalize() ([]byte, []byte) { code := make([]byte, 0, len(a.Instructions)*8) data := make([]byte, 0, 16) + labels := map[string]Address{} pointers := []Pointer{} for _, x := range a.Instructions { switch x.Mnemonic { case MOVE: - code = x64.MoveRegNum32(code, uint8(x.Data.(*RegisterNumber).Register), uint32(x.Data.(*RegisterNumber).Number)) - - if x.Data.(*RegisterNumber).IsPointer { - pointers = append(pointers, Pointer{ - Position: Address(len(code) - 4), - Address: Address(x.Data.(*RegisterNumber).Number), - }) - } + regNum := x.Data.(*RegisterNumber) + code = x64.MoveRegNum32(code, uint8(regNum.Register), uint32(regNum.Number)) case RETURN: code = x64.Return(code) @@ -43,17 +37,33 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case SYSCALL: code = x64.Syscall(code) + case CALL: + code = x64.Call(code, 0x00_00_00_00) + label := x.Data.(*Label) + nextInstructionAddress := len(code) + + pointers = append(pointers, Pointer{ + Position: Address(len(code) - 4), + Resolve: func() Address { + destination := labels[label.Name] + distance := int32(destination) - int32(nextInstructionAddress) + return Address(distance) + }, + }) + + case LABEL: + labels[x.Data.(*Label).Name] = Address(len(code)) + default: panic("Unknown mnemonic: " + x.Mnemonic.String()) } } - dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) + // dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) for _, pointer := range pointers { slice := code[pointer.Position : pointer.Position+4] - address := dataStart + pointer.Address - binary.LittleEndian.PutUint32(slice, address) + binary.LittleEndian.PutUint32(slice, pointer.Resolve()) } return code, data diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index de5f5d8..aa74d0e 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -7,21 +7,28 @@ func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: MOVE, Data: &RegisterNumber{ - Register: reg, - Number: number, - IsPointer: false, + Register: reg, + Number: number, }, }) } -// MoveRegisterAddress moves an address into the given register. -func (a *Assembler) MoveRegisterAddress(reg cpu.Register, address Address) { +// Label adds a label at the current position. +func (a *Assembler) Label(name string) { a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: MOVE, - Data: &RegisterNumber{ - Register: reg, - Number: uint64(address), - IsPointer: true, + Mnemonic: LABEL, + Data: &Label{ + Name: name, + }, + }) +} + +// Call calls a function whose position is identified by a label. +func (a *Assembler) Call(name string) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: CALL, + Data: &Label{ + Name: name, }, }) } diff --git a/src/build/asm/Label.go b/src/build/asm/Label.go new file mode 100644 index 0000000..1636fb8 --- /dev/null +++ b/src/build/asm/Label.go @@ -0,0 +1,11 @@ +package asm + +// Label represents a jump label. +type Label struct { + Name string +} + +// String returns a human readable version. +func (data *Label) String() string { + return data.Name +} diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 7fd1a06..ad071c6 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -7,6 +7,8 @@ const ( MOVE RETURN SYSCALL + LABEL + CALL ) // String returns a human readable version. @@ -20,6 +22,12 @@ func (m Mnemonic) String() string { case SYSCALL: return "syscall" + + case LABEL: + return "label" + + case CALL: + return "call" } return "NONE" diff --git a/src/build/asm/Pointer.go b/src/build/asm/Pointer.go index 7063ec5..6499ce9 100644 --- a/src/build/asm/Pointer.go +++ b/src/build/asm/Pointer.go @@ -5,8 +5,8 @@ type Address = uint32 // Pointer stores a relative memory address that we can later turn into an absolute one. // Position: The machine code offset where the address was inserted. -// Address: The offset inside the section. +// Resolve: The function that will return the final address. type Pointer struct { - Position uint32 - Address uint32 + Position Address + Resolve func() Address } diff --git a/src/build/asm/RegisterNumber.go b/src/build/asm/RegisterNumber.go index bfd7321..6484e08 100644 --- a/src/build/asm/RegisterNumber.go +++ b/src/build/asm/RegisterNumber.go @@ -8,9 +8,8 @@ import ( // RegisterNumber operates with a register and a number. type RegisterNumber struct { - Register cpu.Register - Number uint64 - IsPointer bool + Register cpu.Register + Number uint64 } // String returns a human readable version. diff --git a/src/cli/Build.go b/src/cli/Build.go index 697ec7e..6120250 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -42,6 +42,12 @@ func Build(args []string) int { return 1 } + if config.Verbose { + for _, function := range result { + function.PrintAsm() + } + } + if !writeExecutable { return 0 } diff --git a/tests/benchmarks/expressions.q b/tests/benchmarks/expressions.q index b563384..194f47d 100644 --- a/tests/benchmarks/expressions.q +++ b/tests/benchmarks/expressions.q @@ -1,7 +1,3 @@ main() { - () - 1+(2*3) - (1+2) - f(x) - (a+b)(c) + return 1+2*3 } \ No newline at end of file From 82608394271df447a68ea69d622390d44d12059c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 19 Jun 2024 12:19:32 +0200 Subject: [PATCH 0101/1012] Improved documentation --- README.md | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index bd50be9..3f36e89 100644 --- a/README.md +++ b/README.md @@ -17,19 +17,13 @@ go build ## Usage -Build a Linux ELF executable from `examples/hello`: +Build a Linux x86-64 ELF executable from `examples/hello`: ```shell ./q build examples/hello ./examples/hello/hello ``` -To produce verbose output, add the `-v` flag: - -```shell -./q build examples/hello -v -``` - ## Documentation ### [main.go](main.go) @@ -57,6 +51,12 @@ q build examples/hello q build examples/hello --dry ``` +To produce verbose output, add the `-v` flag which shows the generated assembly instructions: + +```shell +q build examples/hello -v +``` + ### [src/build/Build.go](src/build/Build.go) The `Build` type defines all the information needed to start building an executable file. @@ -64,16 +64,24 @@ The name of the executable will be equal to the name of the build directory. `Run` starts the build which will scan all `.q` source files in the build directory. Every source file is scanned in its own goroutine for performance reasons. -Parallelization here is possible because the order of code in a directory is not significant. +Parallelization here is possible because the order of files in a directory is not significant. -The main function is meanwhile waiting for new function objects to arrive from the scanners. -Once a function has arrived, it will create another goroutine for the function compilation. -The function will then be translated to generic assembler instructions. +The main thread is meanwhile waiting for new function objects to arrive from the scanners. +Once a function has arrived, it will be stored for compilation later. +We need to wait with the compilation step until we have enough information about all identifiers from the scan. + +Then all the functions that were scanned will be compiled in parallel. +We create a separate goroutine for each function compilation. +Each function will then be translated to generic assembler instructions. All the functions that are required to run the program will be added to the final assembler. The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. -The `Run` method is currently not fully implemented. +### [src/build/Function.go](src/build/Function.go) + +This is the "heart" of the compiler. +Each function runs `f.Compile()` which organizes the source code into instructions that are then compiled via `f.CompileInstruction`. +You can think of instructions as the individual lines in your source code, but instructions can also span over multiple lines. ## Tests From 9b50c917e954a246c64019197cc6e3519e9533c6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 19 Jun 2024 12:19:32 +0200 Subject: [PATCH 0102/1012] Improved documentation --- README.md | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index bd50be9..3f36e89 100644 --- a/README.md +++ b/README.md @@ -17,19 +17,13 @@ go build ## Usage -Build a Linux ELF executable from `examples/hello`: +Build a Linux x86-64 ELF executable from `examples/hello`: ```shell ./q build examples/hello ./examples/hello/hello ``` -To produce verbose output, add the `-v` flag: - -```shell -./q build examples/hello -v -``` - ## Documentation ### [main.go](main.go) @@ -57,6 +51,12 @@ q build examples/hello q build examples/hello --dry ``` +To produce verbose output, add the `-v` flag which shows the generated assembly instructions: + +```shell +q build examples/hello -v +``` + ### [src/build/Build.go](src/build/Build.go) The `Build` type defines all the information needed to start building an executable file. @@ -64,16 +64,24 @@ The name of the executable will be equal to the name of the build directory. `Run` starts the build which will scan all `.q` source files in the build directory. Every source file is scanned in its own goroutine for performance reasons. -Parallelization here is possible because the order of code in a directory is not significant. +Parallelization here is possible because the order of files in a directory is not significant. -The main function is meanwhile waiting for new function objects to arrive from the scanners. -Once a function has arrived, it will create another goroutine for the function compilation. -The function will then be translated to generic assembler instructions. +The main thread is meanwhile waiting for new function objects to arrive from the scanners. +Once a function has arrived, it will be stored for compilation later. +We need to wait with the compilation step until we have enough information about all identifiers from the scan. + +Then all the functions that were scanned will be compiled in parallel. +We create a separate goroutine for each function compilation. +Each function will then be translated to generic assembler instructions. All the functions that are required to run the program will be added to the final assembler. The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. -The `Run` method is currently not fully implemented. +### [src/build/Function.go](src/build/Function.go) + +This is the "heart" of the compiler. +Each function runs `f.Compile()` which organizes the source code into instructions that are then compiled via `f.CompileInstruction`. +You can think of instructions as the individual lines in your source code, but instructions can also span over multiple lines. ## Tests From e6ae755e603c61c4ed2cf5af08a7399ab1becb9e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 20 Jun 2024 11:29:39 +0200 Subject: [PATCH 0103/1012] Reduced memory usage --- bench_test.go | 8 ++++++++ src/build/fs/Walk.go | 4 +--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/bench_test.go b/bench_test.go index 46f5f83..d091d84 100644 --- a/bench_test.go +++ b/bench_test.go @@ -21,3 +21,11 @@ func BenchmarkExpressions(b *testing.B) { compiler.Run() } } + +func BenchmarkHello(b *testing.B) { + compiler := build.New("examples/hello") + + for i := 0; i < b.N; i++ { + compiler.Run() + } +} diff --git a/src/build/fs/Walk.go b/src/build/fs/Walk.go index 78d6043..ce22ac5 100644 --- a/src/build/fs/Walk.go +++ b/src/build/fs/Walk.go @@ -5,8 +5,6 @@ import ( "unsafe" ) -const blockSize = 4096 - // Walk calls your callback function for every file name inside the directory. // It doesn't distinguish between files and directories. func Walk(directory string, callBack func(string)) error { @@ -17,7 +15,7 @@ func Walk(directory string, callBack func(string)) error { } defer syscall.Close(fd) - buffer := make([]byte, blockSize) + buffer := make([]byte, 1024) for { n, err := syscall.ReadDirent(fd, buffer) From ff2a43e593956b8b90c26607a553a042d7793fe4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 20 Jun 2024 11:29:39 +0200 Subject: [PATCH 0104/1012] Reduced memory usage --- bench_test.go | 8 ++++++++ src/build/fs/Walk.go | 4 +--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/bench_test.go b/bench_test.go index 46f5f83..d091d84 100644 --- a/bench_test.go +++ b/bench_test.go @@ -21,3 +21,11 @@ func BenchmarkExpressions(b *testing.B) { compiler.Run() } } + +func BenchmarkHello(b *testing.B) { + compiler := build.New("examples/hello") + + for i := 0; i < b.N; i++ { + compiler.Run() + } +} diff --git a/src/build/fs/Walk.go b/src/build/fs/Walk.go index 78d6043..ce22ac5 100644 --- a/src/build/fs/Walk.go +++ b/src/build/fs/Walk.go @@ -5,8 +5,6 @@ import ( "unsafe" ) -const blockSize = 4096 - // Walk calls your callback function for every file name inside the directory. // It doesn't distinguish between files and directories. func Walk(directory string, callBack func(string)) error { @@ -17,7 +15,7 @@ func Walk(directory string, callBack func(string)) error { } defer syscall.Close(fd) - buffer := make([]byte, blockSize) + buffer := make([]byte, 1024) for { n, err := syscall.ReadDirent(fd, buffer) From 5cedb516c500be5e826874bac5fe55bcfbf23d68 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Jun 2024 10:17:26 +0200 Subject: [PATCH 0105/1012] Added more tests --- main_test.go => errors_test.go | 1 + src/build/Function.go | 14 +++++++++++--- src/errors/VariableAlreadyExists.go | 13 +++++++++++++ tests/errors/VariableAlreadyExists.q | 4 ++++ 4 files changed, 29 insertions(+), 3 deletions(-) rename main_test.go => errors_test.go (94%) create mode 100644 src/errors/VariableAlreadyExists.go create mode 100644 tests/errors/VariableAlreadyExists.q diff --git a/main_test.go b/errors_test.go similarity index 94% rename from main_test.go rename to errors_test.go index be2aa55..5512f08 100644 --- a/main_test.go +++ b/errors_test.go @@ -25,6 +25,7 @@ func TestErrors(t *testing.T) { {"MissingBlockStart.q", errors.MissingBlockStart}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, {"MissingGroupStart.q", errors.MissingGroupStart}, + {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "a"}}, } for _, test := range tests { diff --git a/src/build/Function.go b/src/build/Function.go index f303968..251d5e1 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -131,13 +131,21 @@ func (f *Function) CompileInstruction(line token.List) error { return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.After()) } - name := expr.Children[0] + name := expr.Children[0].Token.Text() + _, exists := f.Variables[name] + + if exists { + return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) + } + value := expr.Children[1] + // All expressions are returned to the memory pool. + // To avoid losing variable values, we will remove it from the expression. expr.RemoveChild(value) - f.Variables[name.Token.Text()] = &Variable{ - Name: name.Token.Text(), + f.Variables[name] = &Variable{ + Name: name, Value: value, IsConst: true, } diff --git a/src/errors/VariableAlreadyExists.go b/src/errors/VariableAlreadyExists.go new file mode 100644 index 0000000..4cad219 --- /dev/null +++ b/src/errors/VariableAlreadyExists.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// VariableAlreadyExists is used when existing variables are used for new variable declarations. +type VariableAlreadyExists struct { + Name string +} + +// Error generates the string representation. +func (err *VariableAlreadyExists) Error() string { + return fmt.Sprintf("Variable '%s' already exists", err.Name) +} diff --git a/tests/errors/VariableAlreadyExists.q b/tests/errors/VariableAlreadyExists.q new file mode 100644 index 0000000..e55bbe6 --- /dev/null +++ b/tests/errors/VariableAlreadyExists.q @@ -0,0 +1,4 @@ +main() { + a := 1 + a := 2 +} \ No newline at end of file From 2c6999040df2dc8ecfd8bbaac60d0de555ada474 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Jun 2024 10:17:26 +0200 Subject: [PATCH 0106/1012] Added more tests --- main_test.go => errors_test.go | 1 + src/build/Function.go | 14 +++++++++++--- src/errors/VariableAlreadyExists.go | 13 +++++++++++++ tests/errors/VariableAlreadyExists.q | 4 ++++ 4 files changed, 29 insertions(+), 3 deletions(-) rename main_test.go => errors_test.go (94%) create mode 100644 src/errors/VariableAlreadyExists.go create mode 100644 tests/errors/VariableAlreadyExists.q diff --git a/main_test.go b/errors_test.go similarity index 94% rename from main_test.go rename to errors_test.go index be2aa55..5512f08 100644 --- a/main_test.go +++ b/errors_test.go @@ -25,6 +25,7 @@ func TestErrors(t *testing.T) { {"MissingBlockStart.q", errors.MissingBlockStart}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, {"MissingGroupStart.q", errors.MissingGroupStart}, + {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "a"}}, } for _, test := range tests { diff --git a/src/build/Function.go b/src/build/Function.go index f303968..251d5e1 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -131,13 +131,21 @@ func (f *Function) CompileInstruction(line token.List) error { return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.After()) } - name := expr.Children[0] + name := expr.Children[0].Token.Text() + _, exists := f.Variables[name] + + if exists { + return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) + } + value := expr.Children[1] + // All expressions are returned to the memory pool. + // To avoid losing variable values, we will remove it from the expression. expr.RemoveChild(value) - f.Variables[name.Token.Text()] = &Variable{ - Name: name.Token.Text(), + f.Variables[name] = &Variable{ + Name: name, Value: value, IsConst: true, } diff --git a/src/errors/VariableAlreadyExists.go b/src/errors/VariableAlreadyExists.go new file mode 100644 index 0000000..4cad219 --- /dev/null +++ b/src/errors/VariableAlreadyExists.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// VariableAlreadyExists is used when existing variables are used for new variable declarations. +type VariableAlreadyExists struct { + Name string +} + +// Error generates the string representation. +func (err *VariableAlreadyExists) Error() string { + return fmt.Sprintf("Variable '%s' already exists", err.Name) +} diff --git a/tests/errors/VariableAlreadyExists.q b/tests/errors/VariableAlreadyExists.q new file mode 100644 index 0000000..e55bbe6 --- /dev/null +++ b/tests/errors/VariableAlreadyExists.q @@ -0,0 +1,4 @@ +main() { + a := 1 + a := 2 +} \ No newline at end of file From 890f782af85271e9b127612f762f0279d9bec119 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Jun 2024 12:48:01 +0200 Subject: [PATCH 0107/1012] Improved code layout --- src/build/Build.go | 4 +- src/build/Function.go | 231 +++++++++++++++------------ src/build/{Compile.go => compile.go} | 4 +- src/build/{Scan.go => scan.go} | 4 +- 4 files changed, 133 insertions(+), 110 deletions(-) rename src/build/{Compile.go => compile.go} (84%) rename src/build/{Scan.go => scan.go} (97%) diff --git a/src/build/Build.go b/src/build/Build.go index fcf1698..1efb881 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -19,8 +19,8 @@ func New(files ...string) *Build { // Run parses the input files and generates an executable file. func (build *Build) Run() (map[string]*Function, error) { - functions, errors := Scan(build.Files) - return Compile(functions, errors) + functions, errors := scan(build.Files) + return compile(functions, errors) } // Executable returns the path to the executable. diff --git a/src/build/Function.go b/src/build/Function.go index 251d5e1..9b70b36 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -73,6 +73,123 @@ func (f *Function) Compile() { f.Assembler.Return() } +// CompileInstruction compiles a single instruction. +func (f *Function) CompileInstruction(line token.List) error { + if len(line) == 0 { + return nil + } + + if line[0].Kind == token.Keyword { + return f.CompileKeyword(line) + } + + expr := expression.Parse(line) + + if expr == nil { + return nil + } + + defer expr.Close() + + if isVariableDefinition(expr) { + return f.CompileVariableDefinition(expr) + } + + if isFunctionCall(expr) { + return f.CompileFunctionCall(expr) + } + + return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) +} + +// CompileKeyword compiles an instruction that starts with a keyword. +func (f *Function) CompileKeyword(line token.List) error { + switch line[0].Text() { + case "return": + if len(line) > 1 { + value := expression.Parse(line[1:]) + defer value.Close() + // TODO: Set the return value + } + + f.Assembler.Return() + + default: + return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) + } + + return nil +} + +// CompileVariableDefinition compiles a variable definition. +func (f *Function) CompileVariableDefinition(expr *expression.Expression) error { + if len(expr.Children) < 2 { + return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.After()) + } + + name := expr.Children[0].Token.Text() + _, exists := f.Variables[name] + + if exists { + return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) + } + + value := expr.Children[1] + + // All expressions are returned to the memory pool. + // To avoid losing variable values, we will detach it from the expression. + expr.RemoveChild(value) + + f.Variables[name] = &Variable{ + Name: name, + Value: value, + IsConst: true, + } + + return nil +} + +// CompileFunctionCall compiles a function call. +func (f *Function) CompileFunctionCall(expr *expression.Expression) error { + funcName := expr.Children[0].Token.Text() + parameters := expr.Children[1:] + + for i, parameter := range parameters { + switch parameter.Token.Kind { + case token.Identifier: + name := parameter.Token.Text() + variable, exists := f.Variables[name] + + if !exists { + panic("Unknown identifier " + name) + } + + if !variable.IsConst { + panic("Not implemented yet") + } + + n, _ := strconv.Atoi(variable.Value.Token.Text()) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + + case token.Number: + value := parameter.Token.Text() + n, _ := strconv.Atoi(value) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + + default: + panic("Unknown expression") + } + } + + if funcName == "syscall" { + f.Assembler.Syscall() + } else { + f.Assembler.Call(funcName) + } + + return nil +} + // PrintAsm shows the assembly instructions. func (f *Function) PrintAsm() { ansi.Bold.Println(f.Name) @@ -92,111 +209,17 @@ func (f *Function) PrintAsm() { ansi.Dim.Println("╰────────────────────────────────────────────────────────────") } -// CompileInstruction compiles a single instruction. -func (f *Function) CompileInstruction(line token.List) error { - if len(line) == 0 { - return nil - } - - if line[0].Kind == token.Keyword { - switch line[0].Text() { - case "return": - if len(line) > 1 { - value := expression.Parse(line[1:]) - defer value.Close() - // TODO: Set the return value - } - - f.Assembler.Return() - - default: - return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) - } - } - - expr := expression.Parse(line) - - if expr == nil { - return nil - } - - defer expr.Close() - - if expr.Token.Kind == token.Number || expr.Token.Kind == token.Identifier || expr.Token.Kind == token.String { - return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) - } - - if expr.Token.Text() == ":=" { - if len(expr.Children) < 2 { - return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.After()) - } - - name := expr.Children[0].Token.Text() - _, exists := f.Variables[name] - - if exists { - return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) - } - - value := expr.Children[1] - - // All expressions are returned to the memory pool. - // To avoid losing variable values, we will remove it from the expression. - expr.RemoveChild(value) - - f.Variables[name] = &Variable{ - Name: name, - Value: value, - IsConst: true, - } - - return nil - } - - if expr.Token.Text() == "λ" { - funcName := expr.Children[0].Token.Text() - parameters := expr.Children[1:] - - for i, parameter := range parameters { - switch parameter.Token.Kind { - case token.Identifier: - name := parameter.Token.Text() - variable, exists := f.Variables[name] - - if !exists { - panic("Unknown identifier " + name) - } - - if !variable.IsConst { - panic("Not implemented yet") - } - - n, _ := strconv.Atoi(variable.Value.Token.Text()) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) - - case token.Number: - value := parameter.Token.Text() - n, _ := strconv.Atoi(value) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) - - default: - panic("Unknown expression") - } - } - - if funcName == "syscall" { - f.Assembler.Syscall() - } else { - f.Assembler.Call(funcName) - } - - return nil - } - - return nil -} - // String returns the function name. func (f *Function) String() string { return f.Name } + +// isVariableDefinition returns true if the expression is a variable definition. +func isVariableDefinition(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" +} + +// isFunctionCall returns true if the expression is a function call. +func isFunctionCall(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" +} diff --git a/src/build/Compile.go b/src/build/compile.go similarity index 84% rename from src/build/Compile.go rename to src/build/compile.go index f6f1736..528fa43 100644 --- a/src/build/Compile.go +++ b/src/build/compile.go @@ -4,8 +4,8 @@ import ( "sync" ) -// Compile compiles all the functions. -func Compile(functions <-chan *Function, errors <-chan error) (map[string]*Function, error) { +// compile waits for all functions to finish compilation. +func compile(functions <-chan *Function, errors <-chan error) (map[string]*Function, error) { allFunctions := map[string]*Function{} for functions != nil || errors != nil { diff --git a/src/build/Scan.go b/src/build/scan.go similarity index 97% rename from src/build/Scan.go rename to src/build/scan.go index b3e808a..4d16bb0 100644 --- a/src/build/Scan.go +++ b/src/build/scan.go @@ -11,8 +11,8 @@ import ( "git.akyoto.dev/cli/q/src/errors" ) -// Scan scans the directory. -func Scan(files []string) (<-chan *Function, <-chan error) { +// scan scans the directory. +func scan(files []string) (<-chan *Function, <-chan error) { functions := make(chan *Function) errors := make(chan error) From 1058970be352f69c0263b4c8ab72f148af5e1067 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Jun 2024 12:48:01 +0200 Subject: [PATCH 0108/1012] Improved code layout --- src/build/Build.go | 4 +- src/build/Function.go | 231 +++++++++++++++------------ src/build/{Compile.go => compile.go} | 4 +- src/build/{Scan.go => scan.go} | 4 +- 4 files changed, 133 insertions(+), 110 deletions(-) rename src/build/{Compile.go => compile.go} (84%) rename src/build/{Scan.go => scan.go} (97%) diff --git a/src/build/Build.go b/src/build/Build.go index fcf1698..1efb881 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -19,8 +19,8 @@ func New(files ...string) *Build { // Run parses the input files and generates an executable file. func (build *Build) Run() (map[string]*Function, error) { - functions, errors := Scan(build.Files) - return Compile(functions, errors) + functions, errors := scan(build.Files) + return compile(functions, errors) } // Executable returns the path to the executable. diff --git a/src/build/Function.go b/src/build/Function.go index 251d5e1..9b70b36 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -73,6 +73,123 @@ func (f *Function) Compile() { f.Assembler.Return() } +// CompileInstruction compiles a single instruction. +func (f *Function) CompileInstruction(line token.List) error { + if len(line) == 0 { + return nil + } + + if line[0].Kind == token.Keyword { + return f.CompileKeyword(line) + } + + expr := expression.Parse(line) + + if expr == nil { + return nil + } + + defer expr.Close() + + if isVariableDefinition(expr) { + return f.CompileVariableDefinition(expr) + } + + if isFunctionCall(expr) { + return f.CompileFunctionCall(expr) + } + + return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) +} + +// CompileKeyword compiles an instruction that starts with a keyword. +func (f *Function) CompileKeyword(line token.List) error { + switch line[0].Text() { + case "return": + if len(line) > 1 { + value := expression.Parse(line[1:]) + defer value.Close() + // TODO: Set the return value + } + + f.Assembler.Return() + + default: + return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) + } + + return nil +} + +// CompileVariableDefinition compiles a variable definition. +func (f *Function) CompileVariableDefinition(expr *expression.Expression) error { + if len(expr.Children) < 2 { + return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.After()) + } + + name := expr.Children[0].Token.Text() + _, exists := f.Variables[name] + + if exists { + return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) + } + + value := expr.Children[1] + + // All expressions are returned to the memory pool. + // To avoid losing variable values, we will detach it from the expression. + expr.RemoveChild(value) + + f.Variables[name] = &Variable{ + Name: name, + Value: value, + IsConst: true, + } + + return nil +} + +// CompileFunctionCall compiles a function call. +func (f *Function) CompileFunctionCall(expr *expression.Expression) error { + funcName := expr.Children[0].Token.Text() + parameters := expr.Children[1:] + + for i, parameter := range parameters { + switch parameter.Token.Kind { + case token.Identifier: + name := parameter.Token.Text() + variable, exists := f.Variables[name] + + if !exists { + panic("Unknown identifier " + name) + } + + if !variable.IsConst { + panic("Not implemented yet") + } + + n, _ := strconv.Atoi(variable.Value.Token.Text()) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + + case token.Number: + value := parameter.Token.Text() + n, _ := strconv.Atoi(value) + f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + + default: + panic("Unknown expression") + } + } + + if funcName == "syscall" { + f.Assembler.Syscall() + } else { + f.Assembler.Call(funcName) + } + + return nil +} + // PrintAsm shows the assembly instructions. func (f *Function) PrintAsm() { ansi.Bold.Println(f.Name) @@ -92,111 +209,17 @@ func (f *Function) PrintAsm() { ansi.Dim.Println("╰────────────────────────────────────────────────────────────") } -// CompileInstruction compiles a single instruction. -func (f *Function) CompileInstruction(line token.List) error { - if len(line) == 0 { - return nil - } - - if line[0].Kind == token.Keyword { - switch line[0].Text() { - case "return": - if len(line) > 1 { - value := expression.Parse(line[1:]) - defer value.Close() - // TODO: Set the return value - } - - f.Assembler.Return() - - default: - return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) - } - } - - expr := expression.Parse(line) - - if expr == nil { - return nil - } - - defer expr.Close() - - if expr.Token.Kind == token.Number || expr.Token.Kind == token.Identifier || expr.Token.Kind == token.String { - return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) - } - - if expr.Token.Text() == ":=" { - if len(expr.Children) < 2 { - return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.After()) - } - - name := expr.Children[0].Token.Text() - _, exists := f.Variables[name] - - if exists { - return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) - } - - value := expr.Children[1] - - // All expressions are returned to the memory pool. - // To avoid losing variable values, we will remove it from the expression. - expr.RemoveChild(value) - - f.Variables[name] = &Variable{ - Name: name, - Value: value, - IsConst: true, - } - - return nil - } - - if expr.Token.Text() == "λ" { - funcName := expr.Children[0].Token.Text() - parameters := expr.Children[1:] - - for i, parameter := range parameters { - switch parameter.Token.Kind { - case token.Identifier: - name := parameter.Token.Text() - variable, exists := f.Variables[name] - - if !exists { - panic("Unknown identifier " + name) - } - - if !variable.IsConst { - panic("Not implemented yet") - } - - n, _ := strconv.Atoi(variable.Value.Token.Text()) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) - - case token.Number: - value := parameter.Token.Text() - n, _ := strconv.Atoi(value) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) - - default: - panic("Unknown expression") - } - } - - if funcName == "syscall" { - f.Assembler.Syscall() - } else { - f.Assembler.Call(funcName) - } - - return nil - } - - return nil -} - // String returns the function name. func (f *Function) String() string { return f.Name } + +// isVariableDefinition returns true if the expression is a variable definition. +func isVariableDefinition(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" +} + +// isFunctionCall returns true if the expression is a function call. +func isFunctionCall(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" +} diff --git a/src/build/Compile.go b/src/build/compile.go similarity index 84% rename from src/build/Compile.go rename to src/build/compile.go index f6f1736..528fa43 100644 --- a/src/build/Compile.go +++ b/src/build/compile.go @@ -4,8 +4,8 @@ import ( "sync" ) -// Compile compiles all the functions. -func Compile(functions <-chan *Function, errors <-chan error) (map[string]*Function, error) { +// compile waits for all functions to finish compilation. +func compile(functions <-chan *Function, errors <-chan error) (map[string]*Function, error) { allFunctions := map[string]*Function{} for functions != nil || errors != nil { diff --git a/src/build/Scan.go b/src/build/scan.go similarity index 97% rename from src/build/Scan.go rename to src/build/scan.go index b3e808a..4d16bb0 100644 --- a/src/build/Scan.go +++ b/src/build/scan.go @@ -11,8 +11,8 @@ import ( "git.akyoto.dev/cli/q/src/errors" ) -// Scan scans the directory. -func Scan(files []string) (<-chan *Function, <-chan error) { +// scan scans the directory. +func scan(files []string) (<-chan *Function, <-chan error) { functions := make(chan *Function) errors := make(chan error) From 8c74b4b05fe85853e9265ed3fb16ea015bfce56f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Jun 2024 22:16:42 +0200 Subject: [PATCH 0109/1012] Added CPU type --- errors_test.go | 4 +-- src/build/Finalize.go | 4 +-- src/build/Function.go | 7 +++-- src/build/arch/x64/Registers.go | 28 +++++++++++++++++ src/build/arch/x64/Syscall.go | 8 ----- src/build/compile.go | 31 +++++++++++-------- src/build/cpu/CPU.go | 31 +++++++++++++++++++ src/build/scan.go | 7 +++++ ...ifier.q => InvalidInstructionIdentifier.q} | 0 ...on-Number.q => InvalidInstructionNumber.q} | 0 10 files changed, 92 insertions(+), 28 deletions(-) create mode 100644 src/build/arch/x64/Registers.go create mode 100644 src/build/cpu/CPU.go rename tests/errors/{InvalidInstruction-Identifier.q => InvalidInstructionIdentifier.q} (100%) rename tests/errors/{InvalidInstruction-Number.q => InvalidInstructionNumber.q} (100%) diff --git a/errors_test.go b/errors_test.go index 5512f08..c38a382 100644 --- a/errors_test.go +++ b/errors_test.go @@ -18,8 +18,8 @@ func TestErrors(t *testing.T) { {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, - {"InvalidInstruction-Identifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, - {"InvalidInstruction-Number.q", &errors.InvalidInstruction{Instruction: "123"}}, + {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, + {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, {"MissingAssignValue.q", errors.MissingAssignValue}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, diff --git a/src/build/Finalize.go b/src/build/Finalize.go index 333b849..915b42b 100644 --- a/src/build/Finalize.go +++ b/src/build/Finalize.go @@ -29,8 +29,8 @@ func Finalize(functions map[string]*Function) ([]byte, []byte) { func entry() *asm.Assembler { entry := asm.New() entry.Call("main") - entry.MoveRegisterNumber(x64.SyscallArgs[0], linux.Exit) - entry.MoveRegisterNumber(x64.SyscallArgs[1], 0) + entry.MoveRegisterNumber(x64.SyscallRegisters[0], linux.Exit) + entry.MoveRegisterNumber(x64.SyscallRegisters[1], 0) entry.Syscall() return entry } diff --git a/src/build/Function.go b/src/build/Function.go index 9b70b36..1c2dc0c 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -4,8 +4,8 @@ import ( "fmt" "strconv" - "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" @@ -21,6 +21,7 @@ type Function struct { Body token.List Variables map[string]*Variable Assembler asm.Assembler + CPU cpu.CPU Error error } @@ -169,12 +170,12 @@ func (f *Function) CompileFunctionCall(expr *expression.Expression) error { } n, _ := strconv.Atoi(variable.Value.Token.Text()) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + f.Assembler.MoveRegisterNumber(f.CPU.Syscall[i], uint64(n)) case token.Number: value := parameter.Token.Text() n, _ := strconv.Atoi(value) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + f.Assembler.MoveRegisterNumber(f.CPU.Syscall[i], uint64(n)) default: panic("Unknown expression") diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go new file mode 100644 index 0000000..7aaa3c8 --- /dev/null +++ b/src/build/arch/x64/Registers.go @@ -0,0 +1,28 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +const ( + rax = iota + rcx + rdx + rbx + rsp + rbp + rsi + rdi + r8 + r9 + r10 + r11 + r12 + r13 + r14 + r15 +) + +const SyscallReturn = rax + +var GeneralRegisters = []cpu.Register{rbx, rbp, r12, r13, r14, r15} +var SyscallRegisters = []cpu.Register{rax, rdi, rsi, rdx, r10, r8, r9} +var ReturnValueRegisters = []cpu.Register{rax, rcx, r11} diff --git a/src/build/arch/x64/Syscall.go b/src/build/arch/x64/Syscall.go index 6251de9..94b07d2 100644 --- a/src/build/arch/x64/Syscall.go +++ b/src/build/arch/x64/Syscall.go @@ -1,13 +1,5 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" - -const ( - SyscallReturn = 0 // rax -) - -var SyscallArgs = []cpu.Register{0, 7, 6, 2, 10, 8, 9} - // Syscall is the primary way to communicate with the OS kernel. func Syscall(code []byte) []byte { return append(code, 0x0f, 0x05) diff --git a/src/build/compile.go b/src/build/compile.go index 528fa43..6088e5a 100644 --- a/src/build/compile.go +++ b/src/build/compile.go @@ -4,7 +4,7 @@ import ( "sync" ) -// compile waits for all functions to finish compilation. +// compile waits for the scan to finish and compiles all functions. func compile(functions <-chan *Function, errors <-chan error) (map[string]*Function, error) { allFunctions := map[string]*Function{} @@ -28,18 +28,7 @@ func compile(functions <-chan *Function, errors <-chan error) (map[string]*Funct } } - wg := sync.WaitGroup{} - - for _, function := range allFunctions { - wg.Add(1) - - go func() { - defer wg.Done() - function.Compile() - }() - } - - wg.Wait() + compileFunctions(allFunctions) for _, function := range allFunctions { if function.Error != nil { @@ -49,3 +38,19 @@ func compile(functions <-chan *Function, errors <-chan error) (map[string]*Funct return allFunctions, nil } + +// compileFunctions starts a goroutine for each function compilation and waits for completion. +func compileFunctions(functions map[string]*Function) { + wg := sync.WaitGroup{} + + for _, function := range functions { + wg.Add(1) + + go func() { + defer wg.Done() + function.Compile() + }() + } + + wg.Wait() +} diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go new file mode 100644 index 0000000..7e28e22 --- /dev/null +++ b/src/build/cpu/CPU.go @@ -0,0 +1,31 @@ +package cpu + +// CPU represents the processor. +type CPU struct { + General []Register + Syscall []Register + Return []Register + usage uint64 +} + +func (c *CPU) Use(reg Register) { + c.usage |= (1 << reg) +} + +func (c *CPU) Free(reg Register) { + c.usage &= ^(1 << reg) +} + +func (c *CPU) IsFree(reg Register) bool { + return c.usage&(1< Date: Fri, 21 Jun 2024 22:16:42 +0200 Subject: [PATCH 0110/1012] Added CPU type --- errors_test.go | 4 +-- src/build/Finalize.go | 4 +-- src/build/Function.go | 7 +++-- src/build/arch/x64/Registers.go | 28 +++++++++++++++++ src/build/arch/x64/Syscall.go | 8 ----- src/build/compile.go | 31 +++++++++++-------- src/build/cpu/CPU.go | 31 +++++++++++++++++++ src/build/scan.go | 7 +++++ ...ifier.q => InvalidInstructionIdentifier.q} | 0 ...on-Number.q => InvalidInstructionNumber.q} | 0 10 files changed, 92 insertions(+), 28 deletions(-) create mode 100644 src/build/arch/x64/Registers.go create mode 100644 src/build/cpu/CPU.go rename tests/errors/{InvalidInstruction-Identifier.q => InvalidInstructionIdentifier.q} (100%) rename tests/errors/{InvalidInstruction-Number.q => InvalidInstructionNumber.q} (100%) diff --git a/errors_test.go b/errors_test.go index 5512f08..c38a382 100644 --- a/errors_test.go +++ b/errors_test.go @@ -18,8 +18,8 @@ func TestErrors(t *testing.T) { {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, - {"InvalidInstruction-Identifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, - {"InvalidInstruction-Number.q", &errors.InvalidInstruction{Instruction: "123"}}, + {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, + {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, {"MissingAssignValue.q", errors.MissingAssignValue}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, diff --git a/src/build/Finalize.go b/src/build/Finalize.go index 333b849..915b42b 100644 --- a/src/build/Finalize.go +++ b/src/build/Finalize.go @@ -29,8 +29,8 @@ func Finalize(functions map[string]*Function) ([]byte, []byte) { func entry() *asm.Assembler { entry := asm.New() entry.Call("main") - entry.MoveRegisterNumber(x64.SyscallArgs[0], linux.Exit) - entry.MoveRegisterNumber(x64.SyscallArgs[1], 0) + entry.MoveRegisterNumber(x64.SyscallRegisters[0], linux.Exit) + entry.MoveRegisterNumber(x64.SyscallRegisters[1], 0) entry.Syscall() return entry } diff --git a/src/build/Function.go b/src/build/Function.go index 9b70b36..1c2dc0c 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -4,8 +4,8 @@ import ( "fmt" "strconv" - "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" @@ -21,6 +21,7 @@ type Function struct { Body token.List Variables map[string]*Variable Assembler asm.Assembler + CPU cpu.CPU Error error } @@ -169,12 +170,12 @@ func (f *Function) CompileFunctionCall(expr *expression.Expression) error { } n, _ := strconv.Atoi(variable.Value.Token.Text()) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + f.Assembler.MoveRegisterNumber(f.CPU.Syscall[i], uint64(n)) case token.Number: value := parameter.Token.Text() n, _ := strconv.Atoi(value) - f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n)) + f.Assembler.MoveRegisterNumber(f.CPU.Syscall[i], uint64(n)) default: panic("Unknown expression") diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go new file mode 100644 index 0000000..7aaa3c8 --- /dev/null +++ b/src/build/arch/x64/Registers.go @@ -0,0 +1,28 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +const ( + rax = iota + rcx + rdx + rbx + rsp + rbp + rsi + rdi + r8 + r9 + r10 + r11 + r12 + r13 + r14 + r15 +) + +const SyscallReturn = rax + +var GeneralRegisters = []cpu.Register{rbx, rbp, r12, r13, r14, r15} +var SyscallRegisters = []cpu.Register{rax, rdi, rsi, rdx, r10, r8, r9} +var ReturnValueRegisters = []cpu.Register{rax, rcx, r11} diff --git a/src/build/arch/x64/Syscall.go b/src/build/arch/x64/Syscall.go index 6251de9..94b07d2 100644 --- a/src/build/arch/x64/Syscall.go +++ b/src/build/arch/x64/Syscall.go @@ -1,13 +1,5 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" - -const ( - SyscallReturn = 0 // rax -) - -var SyscallArgs = []cpu.Register{0, 7, 6, 2, 10, 8, 9} - // Syscall is the primary way to communicate with the OS kernel. func Syscall(code []byte) []byte { return append(code, 0x0f, 0x05) diff --git a/src/build/compile.go b/src/build/compile.go index 528fa43..6088e5a 100644 --- a/src/build/compile.go +++ b/src/build/compile.go @@ -4,7 +4,7 @@ import ( "sync" ) -// compile waits for all functions to finish compilation. +// compile waits for the scan to finish and compiles all functions. func compile(functions <-chan *Function, errors <-chan error) (map[string]*Function, error) { allFunctions := map[string]*Function{} @@ -28,18 +28,7 @@ func compile(functions <-chan *Function, errors <-chan error) (map[string]*Funct } } - wg := sync.WaitGroup{} - - for _, function := range allFunctions { - wg.Add(1) - - go func() { - defer wg.Done() - function.Compile() - }() - } - - wg.Wait() + compileFunctions(allFunctions) for _, function := range allFunctions { if function.Error != nil { @@ -49,3 +38,19 @@ func compile(functions <-chan *Function, errors <-chan error) (map[string]*Funct return allFunctions, nil } + +// compileFunctions starts a goroutine for each function compilation and waits for completion. +func compileFunctions(functions map[string]*Function) { + wg := sync.WaitGroup{} + + for _, function := range functions { + wg.Add(1) + + go func() { + defer wg.Done() + function.Compile() + }() + } + + wg.Wait() +} diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go new file mode 100644 index 0000000..7e28e22 --- /dev/null +++ b/src/build/cpu/CPU.go @@ -0,0 +1,31 @@ +package cpu + +// CPU represents the processor. +type CPU struct { + General []Register + Syscall []Register + Return []Register + usage uint64 +} + +func (c *CPU) Use(reg Register) { + c.usage |= (1 << reg) +} + +func (c *CPU) Free(reg Register) { + c.usage &= ^(1 << reg) +} + +func (c *CPU) IsFree(reg Register) bool { + return c.usage&(1< Date: Sat, 22 Jun 2024 13:20:11 +0200 Subject: [PATCH 0111/1012] Added more tests --- errors_test.go | 2 + src/build/Function.go | 78 +++++++++++++++++++++++--------- src/errors/CompileErrors.go | 2 + src/errors/UnknownIdentifier.go | 18 ++++++++ tests/errors/InvalidExpression.q | 3 ++ tests/errors/UnknownIdentifier.q | 3 ++ 6 files changed, 84 insertions(+), 22 deletions(-) create mode 100644 src/errors/UnknownIdentifier.go create mode 100644 tests/errors/InvalidExpression.q create mode 100644 tests/errors/UnknownIdentifier.q diff --git a/errors_test.go b/errors_test.go index c38a382..38e1cdb 100644 --- a/errors_test.go +++ b/errors_test.go @@ -20,12 +20,14 @@ func TestErrors(t *testing.T) { {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, + {"InvalidExpression.q", errors.InvalidExpression}, {"MissingAssignValue.q", errors.MissingAssignValue}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, {"MissingGroupStart.q", errors.MissingGroupStart}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "a"}}, + {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, } for _, test := range tests { diff --git a/src/build/Function.go b/src/build/Function.go index 1c2dc0c..671c4e0 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -156,29 +156,10 @@ func (f *Function) CompileFunctionCall(expr *expression.Expression) error { parameters := expr.Children[1:] for i, parameter := range parameters { - switch parameter.Token.Kind { - case token.Identifier: - name := parameter.Token.Text() - variable, exists := f.Variables[name] + err := f.ExpressionToRegister(parameter, f.CPU.Syscall[i]) - if !exists { - panic("Unknown identifier " + name) - } - - if !variable.IsConst { - panic("Not implemented yet") - } - - n, _ := strconv.Atoi(variable.Value.Token.Text()) - f.Assembler.MoveRegisterNumber(f.CPU.Syscall[i], uint64(n)) - - case token.Number: - value := parameter.Token.Text() - n, _ := strconv.Atoi(value) - f.Assembler.MoveRegisterNumber(f.CPU.Syscall[i], uint64(n)) - - default: - panic("Unknown expression") + if err != nil { + return err } } @@ -191,6 +172,59 @@ func (f *Function) CompileFunctionCall(expr *expression.Expression) error { return nil } +// ExpressionToRegister moves the result of an expression into the given register. +func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { + if root.IsLeaf() { + return f.TokenToRegister(root.Token, register) + } + + return errors.New(errors.NotImplemented, f.File, root.Token.Position) +} + +// TokenToRegister moves a token into a register. +// It only works with identifiers, numbers and strings. +func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { + switch t.Kind { + case token.Identifier: + name := t.Text() + variable, exists := f.Variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) + } + + if !variable.IsConst { + return errors.New(errors.NotImplemented, f.File, t.Position) + } + + n, err := strconv.Atoi(variable.Value.Token.Text()) + + if err != nil { + return err + } + + f.Assembler.MoveRegisterNumber(register, uint64(n)) + return nil + + case token.Number: + value := t.Text() + n, err := strconv.Atoi(value) + + if err != nil { + return err + } + + f.Assembler.MoveRegisterNumber(register, uint64(n)) + return nil + + case token.String: + return errors.New(errors.NotImplemented, f.File, t.Position) + + default: + return errors.New(errors.InvalidExpression, f.File, t.Position) + } +} + // PrintAsm shows the assembly instructions. func (f *Function) PrintAsm() { ansi.Bold.Println(f.Name) diff --git a/src/errors/CompileErrors.go b/src/errors/CompileErrors.go index 9f43de2..34515d0 100644 --- a/src/errors/CompileErrors.go +++ b/src/errors/CompileErrors.go @@ -2,5 +2,7 @@ package errors var ( InvalidStatement = &Base{"Invalid statement"} + InvalidExpression = &Base{"Invalid expression"} MissingAssignValue = &Base{"Missing assignment value"} + NotImplemented = &Base{"Not implemented"} ) diff --git a/src/errors/UnknownIdentifier.go b/src/errors/UnknownIdentifier.go new file mode 100644 index 0000000..d6f8dc8 --- /dev/null +++ b/src/errors/UnknownIdentifier.go @@ -0,0 +1,18 @@ +package errors + +import "fmt" + +// UnknownIdentifier represents unknown variables. +type UnknownIdentifier struct { + Name string + CorrectName string +} + +// Error generates the string representation. +func (err *UnknownIdentifier) Error() string { + if err.CorrectName != "" { + return fmt.Sprintf("Unknown variable '%s', did you mean '%s'?", err.Name, err.CorrectName) + } + + return fmt.Sprintf("Unknown variable '%s'", err.Name) +} diff --git a/tests/errors/InvalidExpression.q b/tests/errors/InvalidExpression.q new file mode 100644 index 0000000..77e99f8 --- /dev/null +++ b/tests/errors/InvalidExpression.q @@ -0,0 +1,3 @@ +main() { + syscall(+, -, *, /) +} \ No newline at end of file diff --git a/tests/errors/UnknownIdentifier.q b/tests/errors/UnknownIdentifier.q new file mode 100644 index 0000000..9b13514 --- /dev/null +++ b/tests/errors/UnknownIdentifier.q @@ -0,0 +1,3 @@ +main() { + syscall(x) +} \ No newline at end of file From 2f7319e6a0b7234d9f8cf4bfad352a9aa2b8a009 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 22 Jun 2024 13:20:11 +0200 Subject: [PATCH 0112/1012] Added more tests --- errors_test.go | 2 + src/build/Function.go | 78 +++++++++++++++++++++++--------- src/errors/CompileErrors.go | 2 + src/errors/UnknownIdentifier.go | 18 ++++++++ tests/errors/InvalidExpression.q | 3 ++ tests/errors/UnknownIdentifier.q | 3 ++ 6 files changed, 84 insertions(+), 22 deletions(-) create mode 100644 src/errors/UnknownIdentifier.go create mode 100644 tests/errors/InvalidExpression.q create mode 100644 tests/errors/UnknownIdentifier.q diff --git a/errors_test.go b/errors_test.go index c38a382..38e1cdb 100644 --- a/errors_test.go +++ b/errors_test.go @@ -20,12 +20,14 @@ func TestErrors(t *testing.T) { {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, + {"InvalidExpression.q", errors.InvalidExpression}, {"MissingAssignValue.q", errors.MissingAssignValue}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, {"MissingGroupStart.q", errors.MissingGroupStart}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "a"}}, + {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, } for _, test := range tests { diff --git a/src/build/Function.go b/src/build/Function.go index 1c2dc0c..671c4e0 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -156,29 +156,10 @@ func (f *Function) CompileFunctionCall(expr *expression.Expression) error { parameters := expr.Children[1:] for i, parameter := range parameters { - switch parameter.Token.Kind { - case token.Identifier: - name := parameter.Token.Text() - variable, exists := f.Variables[name] + err := f.ExpressionToRegister(parameter, f.CPU.Syscall[i]) - if !exists { - panic("Unknown identifier " + name) - } - - if !variable.IsConst { - panic("Not implemented yet") - } - - n, _ := strconv.Atoi(variable.Value.Token.Text()) - f.Assembler.MoveRegisterNumber(f.CPU.Syscall[i], uint64(n)) - - case token.Number: - value := parameter.Token.Text() - n, _ := strconv.Atoi(value) - f.Assembler.MoveRegisterNumber(f.CPU.Syscall[i], uint64(n)) - - default: - panic("Unknown expression") + if err != nil { + return err } } @@ -191,6 +172,59 @@ func (f *Function) CompileFunctionCall(expr *expression.Expression) error { return nil } +// ExpressionToRegister moves the result of an expression into the given register. +func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { + if root.IsLeaf() { + return f.TokenToRegister(root.Token, register) + } + + return errors.New(errors.NotImplemented, f.File, root.Token.Position) +} + +// TokenToRegister moves a token into a register. +// It only works with identifiers, numbers and strings. +func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { + switch t.Kind { + case token.Identifier: + name := t.Text() + variable, exists := f.Variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) + } + + if !variable.IsConst { + return errors.New(errors.NotImplemented, f.File, t.Position) + } + + n, err := strconv.Atoi(variable.Value.Token.Text()) + + if err != nil { + return err + } + + f.Assembler.MoveRegisterNumber(register, uint64(n)) + return nil + + case token.Number: + value := t.Text() + n, err := strconv.Atoi(value) + + if err != nil { + return err + } + + f.Assembler.MoveRegisterNumber(register, uint64(n)) + return nil + + case token.String: + return errors.New(errors.NotImplemented, f.File, t.Position) + + default: + return errors.New(errors.InvalidExpression, f.File, t.Position) + } +} + // PrintAsm shows the assembly instructions. func (f *Function) PrintAsm() { ansi.Bold.Println(f.Name) diff --git a/src/errors/CompileErrors.go b/src/errors/CompileErrors.go index 9f43de2..34515d0 100644 --- a/src/errors/CompileErrors.go +++ b/src/errors/CompileErrors.go @@ -2,5 +2,7 @@ package errors var ( InvalidStatement = &Base{"Invalid statement"} + InvalidExpression = &Base{"Invalid expression"} MissingAssignValue = &Base{"Missing assignment value"} + NotImplemented = &Base{"Not implemented"} ) diff --git a/src/errors/UnknownIdentifier.go b/src/errors/UnknownIdentifier.go new file mode 100644 index 0000000..d6f8dc8 --- /dev/null +++ b/src/errors/UnknownIdentifier.go @@ -0,0 +1,18 @@ +package errors + +import "fmt" + +// UnknownIdentifier represents unknown variables. +type UnknownIdentifier struct { + Name string + CorrectName string +} + +// Error generates the string representation. +func (err *UnknownIdentifier) Error() string { + if err.CorrectName != "" { + return fmt.Sprintf("Unknown variable '%s', did you mean '%s'?", err.Name, err.CorrectName) + } + + return fmt.Sprintf("Unknown variable '%s'", err.Name) +} diff --git a/tests/errors/InvalidExpression.q b/tests/errors/InvalidExpression.q new file mode 100644 index 0000000..77e99f8 --- /dev/null +++ b/tests/errors/InvalidExpression.q @@ -0,0 +1,3 @@ +main() { + syscall(+, -, *, /) +} \ No newline at end of file diff --git a/tests/errors/UnknownIdentifier.q b/tests/errors/UnknownIdentifier.q new file mode 100644 index 0000000..9b13514 --- /dev/null +++ b/tests/errors/UnknownIdentifier.q @@ -0,0 +1,3 @@ +main() { + syscall(x) +} \ No newline at end of file From 57c9fc22d183abf3db95f0f6dfd0d5341b1fac7d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 22 Jun 2024 20:18:13 +0200 Subject: [PATCH 0113/1012] Improved variable definitions --- errors_test.go | 1 + examples/hello/hello.q | 5 +++-- src/build/Function.go | 35 +++++++++++++++++------------- src/build/Variable.go | 5 ++--- src/build/expression/Expression.go | 17 +++++++++++++++ src/errors/UnknownIdentifier.go | 4 ++-- tests/errors/UnknownIdentifier2.q | 3 +++ 7 files changed, 48 insertions(+), 22 deletions(-) create mode 100644 tests/errors/UnknownIdentifier2.q diff --git a/errors_test.go b/errors_test.go index 38e1cdb..efd2ae7 100644 --- a/errors_test.go +++ b/errors_test.go @@ -28,6 +28,7 @@ func TestErrors(t *testing.T) { {"MissingGroupStart.q", errors.MissingGroupStart}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "a"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, + {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, } for _, test := range tests { diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 60f65e4..d3e8c5c 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -3,8 +3,9 @@ main() { } hello() { - write := 1 - stdout := 1 + one := 1 + write := one + stdout := one address := 4194305 length := 3 diff --git a/src/build/Function.go b/src/build/Function.go index 671c4e0..3ec442f 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -137,19 +137,35 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error value := expr.Children[1] + err := value.EachLeaf(func(leaf *expression.Expression) error { + if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) { + return errors.New(&errors.UnknownIdentifier{Name: leaf.Token.Text()}, f.File, leaf.Token.Position) + } + + return nil + }) + + if err != nil { + return err + } + // All expressions are returned to the memory pool. // To avoid losing variable values, we will detach it from the expression. expr.RemoveChild(value) f.Variables[name] = &Variable{ - Name: name, - Value: value, - IsConst: true, + Name: name, + Value: value, } return nil } +func (f *Function) identifierExists(name string) bool { + _, exists := f.Variables[name] + return exists +} + // CompileFunctionCall compiles a function call. func (f *Function) CompileFunctionCall(expr *expression.Expression) error { funcName := expr.Children[0].Token.Text() @@ -193,18 +209,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - if !variable.IsConst { - return errors.New(errors.NotImplemented, f.File, t.Position) - } - - n, err := strconv.Atoi(variable.Value.Token.Text()) - - if err != nil { - return err - } - - f.Assembler.MoveRegisterNumber(register, uint64(n)) - return nil + return f.ExpressionToRegister(variable.Value, register) case token.Number: value := t.Text() diff --git a/src/build/Variable.go b/src/build/Variable.go index b5f7338..a37b2b5 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -4,7 +4,6 @@ import "git.akyoto.dev/cli/q/src/build/expression" // Variable represents a variable in a function. type Variable struct { - Name string - Value *expression.Expression - IsConst bool + Name string + Value *expression.Expression } diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 3cfbb97..37b7719 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -54,6 +54,23 @@ func (expr *Expression) Close() { pool.Put(expr) } +// EachLeaf iterates through all leaves in the tree. +func (expr *Expression) EachLeaf(callBack func(*Expression) error) error { + if expr.IsLeaf() { + return callBack(expr) + } + + for _, child := range expr.Children { + err := child.EachLeaf(callBack) + + if err != nil { + return err + } + } + + return nil +} + // RemoveChild removes a child from the expression. func (expr *Expression) RemoveChild(child *Expression) { for i, c := range expr.Children { diff --git a/src/errors/UnknownIdentifier.go b/src/errors/UnknownIdentifier.go index d6f8dc8..fe1fc1f 100644 --- a/src/errors/UnknownIdentifier.go +++ b/src/errors/UnknownIdentifier.go @@ -11,8 +11,8 @@ type UnknownIdentifier struct { // Error generates the string representation. func (err *UnknownIdentifier) Error() string { if err.CorrectName != "" { - return fmt.Sprintf("Unknown variable '%s', did you mean '%s'?", err.Name, err.CorrectName) + return fmt.Sprintf("Unknown identifier '%s', did you mean '%s'?", err.Name, err.CorrectName) } - return fmt.Sprintf("Unknown variable '%s'", err.Name) + return fmt.Sprintf("Unknown identifier '%s'", err.Name) } diff --git a/tests/errors/UnknownIdentifier2.q b/tests/errors/UnknownIdentifier2.q new file mode 100644 index 0000000..4c60d29 --- /dev/null +++ b/tests/errors/UnknownIdentifier2.q @@ -0,0 +1,3 @@ +main() { + x := x +} \ No newline at end of file From 4f6750dc8eaf20f04a51e0e46a93de524e858d07 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 22 Jun 2024 20:18:13 +0200 Subject: [PATCH 0114/1012] Improved variable definitions --- errors_test.go | 1 + examples/hello/hello.q | 5 +++-- src/build/Function.go | 35 +++++++++++++++++------------- src/build/Variable.go | 5 ++--- src/build/expression/Expression.go | 17 +++++++++++++++ src/errors/UnknownIdentifier.go | 4 ++-- tests/errors/UnknownIdentifier2.q | 3 +++ 7 files changed, 48 insertions(+), 22 deletions(-) create mode 100644 tests/errors/UnknownIdentifier2.q diff --git a/errors_test.go b/errors_test.go index 38e1cdb..efd2ae7 100644 --- a/errors_test.go +++ b/errors_test.go @@ -28,6 +28,7 @@ func TestErrors(t *testing.T) { {"MissingGroupStart.q", errors.MissingGroupStart}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "a"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, + {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, } for _, test := range tests { diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 60f65e4..d3e8c5c 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -3,8 +3,9 @@ main() { } hello() { - write := 1 - stdout := 1 + one := 1 + write := one + stdout := one address := 4194305 length := 3 diff --git a/src/build/Function.go b/src/build/Function.go index 671c4e0..3ec442f 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -137,19 +137,35 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error value := expr.Children[1] + err := value.EachLeaf(func(leaf *expression.Expression) error { + if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) { + return errors.New(&errors.UnknownIdentifier{Name: leaf.Token.Text()}, f.File, leaf.Token.Position) + } + + return nil + }) + + if err != nil { + return err + } + // All expressions are returned to the memory pool. // To avoid losing variable values, we will detach it from the expression. expr.RemoveChild(value) f.Variables[name] = &Variable{ - Name: name, - Value: value, - IsConst: true, + Name: name, + Value: value, } return nil } +func (f *Function) identifierExists(name string) bool { + _, exists := f.Variables[name] + return exists +} + // CompileFunctionCall compiles a function call. func (f *Function) CompileFunctionCall(expr *expression.Expression) error { funcName := expr.Children[0].Token.Text() @@ -193,18 +209,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - if !variable.IsConst { - return errors.New(errors.NotImplemented, f.File, t.Position) - } - - n, err := strconv.Atoi(variable.Value.Token.Text()) - - if err != nil { - return err - } - - f.Assembler.MoveRegisterNumber(register, uint64(n)) - return nil + return f.ExpressionToRegister(variable.Value, register) case token.Number: value := t.Text() diff --git a/src/build/Variable.go b/src/build/Variable.go index b5f7338..a37b2b5 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -4,7 +4,6 @@ import "git.akyoto.dev/cli/q/src/build/expression" // Variable represents a variable in a function. type Variable struct { - Name string - Value *expression.Expression - IsConst bool + Name string + Value *expression.Expression } diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 3cfbb97..37b7719 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -54,6 +54,23 @@ func (expr *Expression) Close() { pool.Put(expr) } +// EachLeaf iterates through all leaves in the tree. +func (expr *Expression) EachLeaf(callBack func(*Expression) error) error { + if expr.IsLeaf() { + return callBack(expr) + } + + for _, child := range expr.Children { + err := child.EachLeaf(callBack) + + if err != nil { + return err + } + } + + return nil +} + // RemoveChild removes a child from the expression. func (expr *Expression) RemoveChild(child *Expression) { for i, c := range expr.Children { diff --git a/src/errors/UnknownIdentifier.go b/src/errors/UnknownIdentifier.go index d6f8dc8..fe1fc1f 100644 --- a/src/errors/UnknownIdentifier.go +++ b/src/errors/UnknownIdentifier.go @@ -11,8 +11,8 @@ type UnknownIdentifier struct { // Error generates the string representation. func (err *UnknownIdentifier) Error() string { if err.CorrectName != "" { - return fmt.Sprintf("Unknown variable '%s', did you mean '%s'?", err.Name, err.CorrectName) + return fmt.Sprintf("Unknown identifier '%s', did you mean '%s'?", err.Name, err.CorrectName) } - return fmt.Sprintf("Unknown variable '%s'", err.Name) + return fmt.Sprintf("Unknown identifier '%s'", err.Name) } diff --git a/tests/errors/UnknownIdentifier2.q b/tests/errors/UnknownIdentifier2.q new file mode 100644 index 0000000..4c60d29 --- /dev/null +++ b/tests/errors/UnknownIdentifier2.q @@ -0,0 +1,3 @@ +main() { + x := x +} \ No newline at end of file From 1c27f0cad2bbac0f989335094fd77c6aa66ecb82 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 23 Jun 2024 14:46:04 +0200 Subject: [PATCH 0115/1012] Reduced number of tokens processed --- src/build/Function.go | 20 +++++++------------- src/build/scan.go | 4 ++-- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/build/Function.go b/src/build/Function.go index 3ec442f..3ba0646 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -62,12 +62,6 @@ func (f *Function) Compile() { case token.GroupEnd: groupLevel-- - - case token.BlockStart: - // Add scope - - case token.BlockEnd: - // Remove scope } } @@ -129,9 +123,8 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error } name := expr.Children[0].Token.Text() - _, exists := f.Variables[name] - if exists { + if f.identifierExists(name) { return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) } @@ -161,11 +154,6 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return nil } -func (f *Function) identifierExists(name string) bool { - _, exists := f.Variables[name] - return exists -} - // CompileFunctionCall compiles a function call. func (f *Function) CompileFunctionCall(expr *expression.Expression) error { funcName := expr.Children[0].Token.Text() @@ -254,6 +242,12 @@ func (f *Function) String() string { return f.Name } +// identifierExists returns true if the identifier has been defined. +func (f *Function) identifierExists(name string) bool { + _, exists := f.Variables[name] + return exists +} + // isVariableDefinition returns true if the expression is a variable definition. func isVariableDefinition(expr *expression.Expression) bool { return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" diff --git a/src/build/scan.go b/src/build/scan.go index 710e53c..219c132 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -176,12 +176,12 @@ func scanFile(path string, functions chan<- *Function) error { for i < len(tokens) { if tokens[i].Kind == token.BlockStart { blockLevel++ + i++ if blockLevel == 1 { bodyStart = i } - i++ continue } @@ -225,7 +225,7 @@ func scanFile(path string, functions chan<- *Function) error { Name: tokens[nameStart].Text(), File: file, Head: tokens[paramsStart:bodyStart], - Body: tokens[bodyStart : i+1], + Body: tokens[bodyStart:i], Variables: map[string]*Variable{}, CPU: cpu.CPU{ General: x64.GeneralRegisters, From 31845dbc4838ee32fa7348a3a6565e8e98e5063d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 23 Jun 2024 14:46:04 +0200 Subject: [PATCH 0116/1012] Reduced number of tokens processed --- src/build/Function.go | 20 +++++++------------- src/build/scan.go | 4 ++-- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/build/Function.go b/src/build/Function.go index 3ec442f..3ba0646 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -62,12 +62,6 @@ func (f *Function) Compile() { case token.GroupEnd: groupLevel-- - - case token.BlockStart: - // Add scope - - case token.BlockEnd: - // Remove scope } } @@ -129,9 +123,8 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error } name := expr.Children[0].Token.Text() - _, exists := f.Variables[name] - if exists { + if f.identifierExists(name) { return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) } @@ -161,11 +154,6 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return nil } -func (f *Function) identifierExists(name string) bool { - _, exists := f.Variables[name] - return exists -} - // CompileFunctionCall compiles a function call. func (f *Function) CompileFunctionCall(expr *expression.Expression) error { funcName := expr.Children[0].Token.Text() @@ -254,6 +242,12 @@ func (f *Function) String() string { return f.Name } +// identifierExists returns true if the identifier has been defined. +func (f *Function) identifierExists(name string) bool { + _, exists := f.Variables[name] + return exists +} + // isVariableDefinition returns true if the expression is a variable definition. func isVariableDefinition(expr *expression.Expression) bool { return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" diff --git a/src/build/scan.go b/src/build/scan.go index 710e53c..219c132 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -176,12 +176,12 @@ func scanFile(path string, functions chan<- *Function) error { for i < len(tokens) { if tokens[i].Kind == token.BlockStart { blockLevel++ + i++ if blockLevel == 1 { bodyStart = i } - i++ continue } @@ -225,7 +225,7 @@ func scanFile(path string, functions chan<- *Function) error { Name: tokens[nameStart].Text(), File: file, Head: tokens[paramsStart:bodyStart], - Body: tokens[bodyStart : i+1], + Body: tokens[bodyStart:i], Variables: map[string]*Variable{}, CPU: cpu.CPU{ General: x64.GeneralRegisters, From a9d43a8716130d3b5d697446de2e6b5f10c38895 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 23 Jun 2024 20:26:53 +0200 Subject: [PATCH 0117/1012] Implemented register to register instructions --- examples/hello/hello.q | 5 ++--- src/build/Function.go | 18 ++++++++++------ src/build/Variable.go | 8 +++++--- src/build/arch/x64/ModRM.go | 9 ++++++++ src/build/arch/x64/ModRM_test.go | 34 +++++++++++++++++++++++++++++++ src/build/arch/x64/Move.go | 34 +++++++++++++++++++++++++++++-- src/build/arch/x64/REX.go | 7 +++++++ src/build/arch/x64/REX_test.go | 34 +++++++++++++++++++++++++++++++ src/build/asm/Assembler.go | 9 ++++++-- src/build/asm/Instructions.go | 11 ++++++++++ src/build/asm/RegisterRegister.go | 18 ++++++++++++++++ 11 files changed, 171 insertions(+), 16 deletions(-) create mode 100644 src/build/arch/x64/ModRM.go create mode 100644 src/build/arch/x64/ModRM_test.go create mode 100644 src/build/arch/x64/REX.go create mode 100644 src/build/arch/x64/REX_test.go create mode 100644 src/build/asm/RegisterRegister.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index d3e8c5c..60f65e4 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -3,9 +3,8 @@ main() { } hello() { - one := 1 - write := one - stdout := one + write := 1 + stdout := 1 address := 4194305 length := 3 diff --git a/src/build/Function.go b/src/build/Function.go index 3ba0646..efdac96 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -142,13 +142,18 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return err } - // All expressions are returned to the memory pool. - // To avoid losing variable values, we will detach it from the expression. - expr.RemoveChild(value) + reg, exists := f.CPU.FindFree() + + if !exists { + panic("no free registers") + } + + f.ExpressionToRegister(value, reg) + f.CPU.Use(reg) f.Variables[name] = &Variable{ - Name: name, - Value: value, + Name: name, + Register: reg, } return nil @@ -197,7 +202,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - return f.ExpressionToRegister(variable.Value, register) + f.Assembler.MoveRegisterRegister(register, variable.Register) + return nil case token.Number: value := t.Text() diff --git a/src/build/Variable.go b/src/build/Variable.go index a37b2b5..c8189f1 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -1,9 +1,11 @@ package build -import "git.akyoto.dev/cli/q/src/build/expression" +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) // Variable represents a variable in a function. type Variable struct { - Name string - Value *expression.Expression + Name string + Register cpu.Register } diff --git a/src/build/arch/x64/ModRM.go b/src/build/arch/x64/ModRM.go new file mode 100644 index 0000000..b6ab57f --- /dev/null +++ b/src/build/arch/x64/ModRM.go @@ -0,0 +1,9 @@ +package x64 + +// ModRM is used to generate a ModRM suffix. +// - mod: 2 bits +// - reg: 3 bits +// - rm: 3 bits +func ModRM(mod byte, reg byte, rm byte) byte { + return (mod << 6) | (reg << 3) | rm +} diff --git a/src/build/arch/x64/ModRM_test.go b/src/build/arch/x64/ModRM_test.go new file mode 100644 index 0000000..e9d470f --- /dev/null +++ b/src/build/arch/x64/ModRM_test.go @@ -0,0 +1,34 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/go/assert" +) + +func TestModRM(t *testing.T) { + testData := []struct{ mod, reg, rm, expected byte }{ + {0b_00, 0b_111, 0b_000, 0b_00_111_000}, + {0b_00, 0b_110, 0b_001, 0b_00_110_001}, + {0b_00, 0b_101, 0b_010, 0b_00_101_010}, + {0b_00, 0b_100, 0b_011, 0b_00_100_011}, + {0b_00, 0b_011, 0b_100, 0b_00_011_100}, + {0b_00, 0b_010, 0b_101, 0b_00_010_101}, + {0b_00, 0b_001, 0b_110, 0b_00_001_110}, + {0b_00, 0b_000, 0b_111, 0b_00_000_111}, + {0b_11, 0b_111, 0b_000, 0b_11_111_000}, + {0b_11, 0b_110, 0b_001, 0b_11_110_001}, + {0b_11, 0b_101, 0b_010, 0b_11_101_010}, + {0b_11, 0b_100, 0b_011, 0b_11_100_011}, + {0b_11, 0b_011, 0b_100, 0b_11_011_100}, + {0b_11, 0b_010, 0b_101, 0b_11_010_101}, + {0b_11, 0b_001, 0b_110, 0b_11_001_110}, + {0b_11, 0b_000, 0b_111, 0b_11_000_111}, + } + + for _, test := range testData { + modRM := x64.ModRM(test.mod, test.reg, test.rm) + assert.Equal(t, modRM, test.expected) + } +} diff --git a/src/build/arch/x64/Move.go b/src/build/arch/x64/Move.go index 6d9fd57..63ffaf0 100644 --- a/src/build/arch/x64/Move.go +++ b/src/build/arch/x64/Move.go @@ -1,13 +1,43 @@ package x64 +import "git.akyoto.dev/cli/q/src/build/cpu" + // MoveRegNum32 moves a 32 bit integer into the given register. -func MoveRegNum32(code []byte, register uint8, number uint32) []byte { +func MoveRegNum32(code []byte, destination cpu.Register, number uint32) []byte { + if destination >= 8 { + code = append(code, REX(0, 0, 0, 1)) + destination -= 8 + } + return append( code, - 0xb8+register, + 0xb8+byte(destination), byte(number), byte(number>>8), byte(number>>16), byte(number>>24), ) } + +// MoveRegReg64 moves a register value into another register. +func MoveRegReg64(code []byte, destination cpu.Register, source cpu.Register) []byte { + r := byte(0) // Extension to the "reg" field in ModRM. + b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). + + if source >= 8 { + r = 1 + source -= 8 + } + + if destination >= 8 { + b = 1 + destination -= 8 + } + + return append( + code, + REX(1, r, 0, b), + 0x89, + ModRM(0b11, byte(source), byte(destination)), + ) +} diff --git a/src/build/arch/x64/REX.go b/src/build/arch/x64/REX.go new file mode 100644 index 0000000..ba1fa1a --- /dev/null +++ b/src/build/arch/x64/REX.go @@ -0,0 +1,7 @@ +package x64 + +// REX is used to generate a REX prefix. +// w, r, x and b can only be set to either 0 or 1. +func REX(w, r, x, b byte) byte { + return 0b_0100_0000 | (w << 3) | (r << 2) | (x << 1) | b +} diff --git a/src/build/arch/x64/REX_test.go b/src/build/arch/x64/REX_test.go new file mode 100644 index 0000000..e48e3c5 --- /dev/null +++ b/src/build/arch/x64/REX_test.go @@ -0,0 +1,34 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/go/assert" +) + +func TestREX(t *testing.T) { + testData := []struct{ w, r, x, b, expected byte }{ + {0, 0, 0, 0, 0b_0100_0000}, + {0, 0, 0, 1, 0b_0100_0001}, + {0, 0, 1, 0, 0b_0100_0010}, + {0, 0, 1, 1, 0b_0100_0011}, + {0, 1, 0, 0, 0b_0100_0100}, + {0, 1, 0, 1, 0b_0100_0101}, + {0, 1, 1, 0, 0b_0100_0110}, + {0, 1, 1, 1, 0b_0100_0111}, + {1, 0, 0, 0, 0b_0100_1000}, + {1, 0, 0, 1, 0b_0100_1001}, + {1, 0, 1, 0, 0b_0100_1010}, + {1, 0, 1, 1, 0b_0100_1011}, + {1, 1, 0, 0, 0b_0100_1100}, + {1, 1, 0, 1, 0b_0100_1101}, + {1, 1, 1, 0, 0b_0100_1110}, + {1, 1, 1, 1, 0b_0100_1111}, + } + + for _, test := range testData { + rex := x64.REX(test.w, test.r, test.x, test.b) + assert.Equal(t, rex, test.expected) + } +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 5af72a6..f159bc4 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -28,8 +28,13 @@ func (a *Assembler) Finalize() ([]byte, []byte) { for _, x := range a.Instructions { switch x.Mnemonic { case MOVE: - regNum := x.Data.(*RegisterNumber) - code = x64.MoveRegNum32(code, uint8(regNum.Register), uint32(regNum.Number)) + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number)) + + case *RegisterRegister: + code = x64.MoveRegReg64(code, operands.Destination, operands.Source) + } case RETURN: code = x64.Return(code) diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index aa74d0e..82aebdb 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -13,6 +13,17 @@ func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) { }) } +// MoveRegisterRegister moves a register value into another register. +func (a *Assembler) MoveRegisterRegister(destination cpu.Register, source cpu.Register) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: MOVE, + Data: &RegisterRegister{ + Destination: destination, + Source: source, + }, + }) +} + // Label adds a label at the current position. func (a *Assembler) Label(name string) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/asm/RegisterRegister.go b/src/build/asm/RegisterRegister.go new file mode 100644 index 0000000..5acb084 --- /dev/null +++ b/src/build/asm/RegisterRegister.go @@ -0,0 +1,18 @@ +package asm + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// RegisterRegister operates with two registers. +type RegisterRegister struct { + Destination cpu.Register + Source cpu.Register +} + +// String returns a human readable version. +func (data *RegisterRegister) String() string { + return fmt.Sprintf("%s, %s", data.Destination, data.Source) +} From 4fc1935183fc5142e7de7f28f52047b81f429703 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 23 Jun 2024 20:26:53 +0200 Subject: [PATCH 0118/1012] Implemented register to register instructions --- examples/hello/hello.q | 5 ++--- src/build/Function.go | 18 ++++++++++------ src/build/Variable.go | 8 +++++--- src/build/arch/x64/ModRM.go | 9 ++++++++ src/build/arch/x64/ModRM_test.go | 34 +++++++++++++++++++++++++++++++ src/build/arch/x64/Move.go | 34 +++++++++++++++++++++++++++++-- src/build/arch/x64/REX.go | 7 +++++++ src/build/arch/x64/REX_test.go | 34 +++++++++++++++++++++++++++++++ src/build/asm/Assembler.go | 9 ++++++-- src/build/asm/Instructions.go | 11 ++++++++++ src/build/asm/RegisterRegister.go | 18 ++++++++++++++++ 11 files changed, 171 insertions(+), 16 deletions(-) create mode 100644 src/build/arch/x64/ModRM.go create mode 100644 src/build/arch/x64/ModRM_test.go create mode 100644 src/build/arch/x64/REX.go create mode 100644 src/build/arch/x64/REX_test.go create mode 100644 src/build/asm/RegisterRegister.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index d3e8c5c..60f65e4 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -3,9 +3,8 @@ main() { } hello() { - one := 1 - write := one - stdout := one + write := 1 + stdout := 1 address := 4194305 length := 3 diff --git a/src/build/Function.go b/src/build/Function.go index 3ba0646..efdac96 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -142,13 +142,18 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return err } - // All expressions are returned to the memory pool. - // To avoid losing variable values, we will detach it from the expression. - expr.RemoveChild(value) + reg, exists := f.CPU.FindFree() + + if !exists { + panic("no free registers") + } + + f.ExpressionToRegister(value, reg) + f.CPU.Use(reg) f.Variables[name] = &Variable{ - Name: name, - Value: value, + Name: name, + Register: reg, } return nil @@ -197,7 +202,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - return f.ExpressionToRegister(variable.Value, register) + f.Assembler.MoveRegisterRegister(register, variable.Register) + return nil case token.Number: value := t.Text() diff --git a/src/build/Variable.go b/src/build/Variable.go index a37b2b5..c8189f1 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -1,9 +1,11 @@ package build -import "git.akyoto.dev/cli/q/src/build/expression" +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) // Variable represents a variable in a function. type Variable struct { - Name string - Value *expression.Expression + Name string + Register cpu.Register } diff --git a/src/build/arch/x64/ModRM.go b/src/build/arch/x64/ModRM.go new file mode 100644 index 0000000..b6ab57f --- /dev/null +++ b/src/build/arch/x64/ModRM.go @@ -0,0 +1,9 @@ +package x64 + +// ModRM is used to generate a ModRM suffix. +// - mod: 2 bits +// - reg: 3 bits +// - rm: 3 bits +func ModRM(mod byte, reg byte, rm byte) byte { + return (mod << 6) | (reg << 3) | rm +} diff --git a/src/build/arch/x64/ModRM_test.go b/src/build/arch/x64/ModRM_test.go new file mode 100644 index 0000000..e9d470f --- /dev/null +++ b/src/build/arch/x64/ModRM_test.go @@ -0,0 +1,34 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/go/assert" +) + +func TestModRM(t *testing.T) { + testData := []struct{ mod, reg, rm, expected byte }{ + {0b_00, 0b_111, 0b_000, 0b_00_111_000}, + {0b_00, 0b_110, 0b_001, 0b_00_110_001}, + {0b_00, 0b_101, 0b_010, 0b_00_101_010}, + {0b_00, 0b_100, 0b_011, 0b_00_100_011}, + {0b_00, 0b_011, 0b_100, 0b_00_011_100}, + {0b_00, 0b_010, 0b_101, 0b_00_010_101}, + {0b_00, 0b_001, 0b_110, 0b_00_001_110}, + {0b_00, 0b_000, 0b_111, 0b_00_000_111}, + {0b_11, 0b_111, 0b_000, 0b_11_111_000}, + {0b_11, 0b_110, 0b_001, 0b_11_110_001}, + {0b_11, 0b_101, 0b_010, 0b_11_101_010}, + {0b_11, 0b_100, 0b_011, 0b_11_100_011}, + {0b_11, 0b_011, 0b_100, 0b_11_011_100}, + {0b_11, 0b_010, 0b_101, 0b_11_010_101}, + {0b_11, 0b_001, 0b_110, 0b_11_001_110}, + {0b_11, 0b_000, 0b_111, 0b_11_000_111}, + } + + for _, test := range testData { + modRM := x64.ModRM(test.mod, test.reg, test.rm) + assert.Equal(t, modRM, test.expected) + } +} diff --git a/src/build/arch/x64/Move.go b/src/build/arch/x64/Move.go index 6d9fd57..63ffaf0 100644 --- a/src/build/arch/x64/Move.go +++ b/src/build/arch/x64/Move.go @@ -1,13 +1,43 @@ package x64 +import "git.akyoto.dev/cli/q/src/build/cpu" + // MoveRegNum32 moves a 32 bit integer into the given register. -func MoveRegNum32(code []byte, register uint8, number uint32) []byte { +func MoveRegNum32(code []byte, destination cpu.Register, number uint32) []byte { + if destination >= 8 { + code = append(code, REX(0, 0, 0, 1)) + destination -= 8 + } + return append( code, - 0xb8+register, + 0xb8+byte(destination), byte(number), byte(number>>8), byte(number>>16), byte(number>>24), ) } + +// MoveRegReg64 moves a register value into another register. +func MoveRegReg64(code []byte, destination cpu.Register, source cpu.Register) []byte { + r := byte(0) // Extension to the "reg" field in ModRM. + b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). + + if source >= 8 { + r = 1 + source -= 8 + } + + if destination >= 8 { + b = 1 + destination -= 8 + } + + return append( + code, + REX(1, r, 0, b), + 0x89, + ModRM(0b11, byte(source), byte(destination)), + ) +} diff --git a/src/build/arch/x64/REX.go b/src/build/arch/x64/REX.go new file mode 100644 index 0000000..ba1fa1a --- /dev/null +++ b/src/build/arch/x64/REX.go @@ -0,0 +1,7 @@ +package x64 + +// REX is used to generate a REX prefix. +// w, r, x and b can only be set to either 0 or 1. +func REX(w, r, x, b byte) byte { + return 0b_0100_0000 | (w << 3) | (r << 2) | (x << 1) | b +} diff --git a/src/build/arch/x64/REX_test.go b/src/build/arch/x64/REX_test.go new file mode 100644 index 0000000..e48e3c5 --- /dev/null +++ b/src/build/arch/x64/REX_test.go @@ -0,0 +1,34 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/go/assert" +) + +func TestREX(t *testing.T) { + testData := []struct{ w, r, x, b, expected byte }{ + {0, 0, 0, 0, 0b_0100_0000}, + {0, 0, 0, 1, 0b_0100_0001}, + {0, 0, 1, 0, 0b_0100_0010}, + {0, 0, 1, 1, 0b_0100_0011}, + {0, 1, 0, 0, 0b_0100_0100}, + {0, 1, 0, 1, 0b_0100_0101}, + {0, 1, 1, 0, 0b_0100_0110}, + {0, 1, 1, 1, 0b_0100_0111}, + {1, 0, 0, 0, 0b_0100_1000}, + {1, 0, 0, 1, 0b_0100_1001}, + {1, 0, 1, 0, 0b_0100_1010}, + {1, 0, 1, 1, 0b_0100_1011}, + {1, 1, 0, 0, 0b_0100_1100}, + {1, 1, 0, 1, 0b_0100_1101}, + {1, 1, 1, 0, 0b_0100_1110}, + {1, 1, 1, 1, 0b_0100_1111}, + } + + for _, test := range testData { + rex := x64.REX(test.w, test.r, test.x, test.b) + assert.Equal(t, rex, test.expected) + } +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 5af72a6..f159bc4 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -28,8 +28,13 @@ func (a *Assembler) Finalize() ([]byte, []byte) { for _, x := range a.Instructions { switch x.Mnemonic { case MOVE: - regNum := x.Data.(*RegisterNumber) - code = x64.MoveRegNum32(code, uint8(regNum.Register), uint32(regNum.Number)) + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number)) + + case *RegisterRegister: + code = x64.MoveRegReg64(code, operands.Destination, operands.Source) + } case RETURN: code = x64.Return(code) diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index aa74d0e..82aebdb 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -13,6 +13,17 @@ func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) { }) } +// MoveRegisterRegister moves a register value into another register. +func (a *Assembler) MoveRegisterRegister(destination cpu.Register, source cpu.Register) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: MOVE, + Data: &RegisterRegister{ + Destination: destination, + Source: source, + }, + }) +} + // Label adds a label at the current position. func (a *Assembler) Label(name string) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/asm/RegisterRegister.go b/src/build/asm/RegisterRegister.go new file mode 100644 index 0000000..5acb084 --- /dev/null +++ b/src/build/asm/RegisterRegister.go @@ -0,0 +1,18 @@ +package asm + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// RegisterRegister operates with two registers. +type RegisterRegister struct { + Destination cpu.Register + Source cpu.Register +} + +// String returns a human readable version. +func (data *RegisterRegister) String() string { + return fmt.Sprintf("%s, %s", data.Destination, data.Source) +} From 121f77fe767c76463bfad464c60a235a5b52a6a9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 24 Jun 2024 00:03:26 +0200 Subject: [PATCH 0119/1012] Implemented block instruction parsing --- examples/hello/hello.q | 4 +++- src/build/Function.go | 48 ++++++++++++++++++++++++++++++------- src/build/scan.go | 4 ++-- src/build/token/Keywords.go | 1 + src/build/token/List.go | 22 +++++++++++++++++ src/build/token/Token.go | 4 ++-- 6 files changed, 70 insertions(+), 13 deletions(-) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 60f65e4..a9ef287 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -8,5 +8,7 @@ hello() { address := 4194305 length := 3 - syscall(write, stdout, address, length) + loop { + syscall(write, stdout, address, length) + } } \ No newline at end of file diff --git a/src/build/Function.go b/src/build/Function.go index efdac96..7cffba6 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -28,28 +28,40 @@ type Function struct { // Compile turns a function into machine code. func (f *Function) Compile() { f.Assembler.Label(f.Name) + err := f.CompileTokens(f.Body) + + if err != nil { + f.Error = err + return + } + + f.Assembler.Return() +} + +// CompileTokens compiles a token list. +func (f *Function) CompileTokens(body token.List) error { start := 0 groupLevel := 0 + blockLevel := 0 - for i, t := range f.Body { - if start == i && (t.Kind == token.NewLine || t.Kind == token.BlockStart || t.Kind == token.BlockEnd) { + for i, t := range body { + if start == i && t.Kind == token.NewLine { start = i + 1 continue } switch t.Kind { case token.NewLine: - if groupLevel > 0 { + if groupLevel > 0 || blockLevel > 0 { continue } if start != -1 { - instruction := f.Body[start:i] + instruction := body[start:i] err := f.CompileInstruction(instruction) if err != nil { - f.Error = err - return + return err } start = -1 @@ -62,10 +74,16 @@ func (f *Function) Compile() { case token.GroupEnd: groupLevel-- + + case token.BlockStart: + blockLevel++ + + case token.BlockEnd: + blockLevel-- } } - f.Assembler.Return() + return nil } // CompileInstruction compiles a single instruction. @@ -109,6 +127,20 @@ func (f *Function) CompileKeyword(line token.List) error { f.Assembler.Return() + case "loop": + blockStart := line.IndexKind(token.BlockStart) + 1 + blockEnd := line.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return errors.New(errors.MissingBlockStart, f.File, line[0].End()) + } + + if blockEnd == -1 { + return errors.New(errors.MissingBlockEnd, f.File, line[len(line)-1].End()) + } + + return f.CompileTokens(line[blockStart:blockEnd]) + default: return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) } @@ -119,7 +151,7 @@ func (f *Function) CompileKeyword(line token.List) error { // CompileVariableDefinition compiles a variable definition. func (f *Function) CompileVariableDefinition(expr *expression.Expression) error { if len(expr.Children) < 2 { - return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.After()) + return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) } name := expr.Children[0].Token.Text() diff --git a/src/build/scan.go b/src/build/scan.go index 219c132..a78ce93 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -192,12 +192,11 @@ func scanFile(path string, functions chan<- *Function) error { return errors.New(errors.MissingBlockStart, file, tokens[i].Position) } - i++ - if blockLevel == 0 { break } + i++ continue } @@ -237,5 +236,6 @@ func scanFile(path string, functions chan<- *Function) error { nameStart = -1 paramsStart = -1 bodyStart = -1 + i++ } } diff --git a/src/build/token/Keywords.go b/src/build/token/Keywords.go index adb8de7..d5f6231 100644 --- a/src/build/token/Keywords.go +++ b/src/build/token/Keywords.go @@ -3,4 +3,5 @@ package token // Keywords defines the keywords used in the language. var Keywords = map[string]bool{ "return": true, + "loop": true, } diff --git a/src/build/token/List.go b/src/build/token/List.go index be3a1ff..1b725b6 100644 --- a/src/build/token/List.go +++ b/src/build/token/List.go @@ -7,6 +7,28 @@ import ( // List is a slice of tokens. type List []Token +// IndexKind returns the position of a token kind within a token list. +func (list List) IndexKind(kind Kind) int { + for i, token := range list { + if token.Kind == kind { + return i + } + } + + return -1 +} + +// LastIndexKind returns the position of the last token kind within a token list. +func (list List) LastIndexKind(kind Kind) int { + for i := len(list) - 1; i >= 0; i-- { + if list[i].Kind == kind { + return i + } + } + + return -1 +} + // String implements string serialization. func (list List) String() string { builder := bytes.Buffer{} diff --git a/src/build/token/Token.go b/src/build/token/Token.go index 66c77b1..fadf760 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -11,8 +11,8 @@ type Token struct { Bytes []byte } -// After returns the position after the token. -func (t *Token) After() int { +// End returns the position after the token. +func (t *Token) End() int { return t.Position + len(t.Bytes) } From dd495fab4ef4fddde943e1827504a0f600e534a1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 24 Jun 2024 00:03:26 +0200 Subject: [PATCH 0120/1012] Implemented block instruction parsing --- examples/hello/hello.q | 4 +++- src/build/Function.go | 48 ++++++++++++++++++++++++++++++------- src/build/scan.go | 4 ++-- src/build/token/Keywords.go | 1 + src/build/token/List.go | 22 +++++++++++++++++ src/build/token/Token.go | 4 ++-- 6 files changed, 70 insertions(+), 13 deletions(-) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 60f65e4..a9ef287 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -8,5 +8,7 @@ hello() { address := 4194305 length := 3 - syscall(write, stdout, address, length) + loop { + syscall(write, stdout, address, length) + } } \ No newline at end of file diff --git a/src/build/Function.go b/src/build/Function.go index efdac96..7cffba6 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -28,28 +28,40 @@ type Function struct { // Compile turns a function into machine code. func (f *Function) Compile() { f.Assembler.Label(f.Name) + err := f.CompileTokens(f.Body) + + if err != nil { + f.Error = err + return + } + + f.Assembler.Return() +} + +// CompileTokens compiles a token list. +func (f *Function) CompileTokens(body token.List) error { start := 0 groupLevel := 0 + blockLevel := 0 - for i, t := range f.Body { - if start == i && (t.Kind == token.NewLine || t.Kind == token.BlockStart || t.Kind == token.BlockEnd) { + for i, t := range body { + if start == i && t.Kind == token.NewLine { start = i + 1 continue } switch t.Kind { case token.NewLine: - if groupLevel > 0 { + if groupLevel > 0 || blockLevel > 0 { continue } if start != -1 { - instruction := f.Body[start:i] + instruction := body[start:i] err := f.CompileInstruction(instruction) if err != nil { - f.Error = err - return + return err } start = -1 @@ -62,10 +74,16 @@ func (f *Function) Compile() { case token.GroupEnd: groupLevel-- + + case token.BlockStart: + blockLevel++ + + case token.BlockEnd: + blockLevel-- } } - f.Assembler.Return() + return nil } // CompileInstruction compiles a single instruction. @@ -109,6 +127,20 @@ func (f *Function) CompileKeyword(line token.List) error { f.Assembler.Return() + case "loop": + blockStart := line.IndexKind(token.BlockStart) + 1 + blockEnd := line.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return errors.New(errors.MissingBlockStart, f.File, line[0].End()) + } + + if blockEnd == -1 { + return errors.New(errors.MissingBlockEnd, f.File, line[len(line)-1].End()) + } + + return f.CompileTokens(line[blockStart:blockEnd]) + default: return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) } @@ -119,7 +151,7 @@ func (f *Function) CompileKeyword(line token.List) error { // CompileVariableDefinition compiles a variable definition. func (f *Function) CompileVariableDefinition(expr *expression.Expression) error { if len(expr.Children) < 2 { - return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.After()) + return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) } name := expr.Children[0].Token.Text() diff --git a/src/build/scan.go b/src/build/scan.go index 219c132..a78ce93 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -192,12 +192,11 @@ func scanFile(path string, functions chan<- *Function) error { return errors.New(errors.MissingBlockStart, file, tokens[i].Position) } - i++ - if blockLevel == 0 { break } + i++ continue } @@ -237,5 +236,6 @@ func scanFile(path string, functions chan<- *Function) error { nameStart = -1 paramsStart = -1 bodyStart = -1 + i++ } } diff --git a/src/build/token/Keywords.go b/src/build/token/Keywords.go index adb8de7..d5f6231 100644 --- a/src/build/token/Keywords.go +++ b/src/build/token/Keywords.go @@ -3,4 +3,5 @@ package token // Keywords defines the keywords used in the language. var Keywords = map[string]bool{ "return": true, + "loop": true, } diff --git a/src/build/token/List.go b/src/build/token/List.go index be3a1ff..1b725b6 100644 --- a/src/build/token/List.go +++ b/src/build/token/List.go @@ -7,6 +7,28 @@ import ( // List is a slice of tokens. type List []Token +// IndexKind returns the position of a token kind within a token list. +func (list List) IndexKind(kind Kind) int { + for i, token := range list { + if token.Kind == kind { + return i + } + } + + return -1 +} + +// LastIndexKind returns the position of the last token kind within a token list. +func (list List) LastIndexKind(kind Kind) int { + for i := len(list) - 1; i >= 0; i-- { + if list[i].Kind == kind { + return i + } + } + + return -1 +} + // String implements string serialization. func (list List) String() string { builder := bytes.Buffer{} diff --git a/src/build/token/Token.go b/src/build/token/Token.go index 66c77b1..fadf760 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -11,8 +11,8 @@ type Token struct { Bytes []byte } -// After returns the position after the token. -func (t *Token) After() int { +// End returns the position after the token. +func (t *Token) End() int { return t.Position + len(t.Bytes) } From b6722c54821eedf66ad185f95fbe272ac82317c4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 24 Jun 2024 11:00:32 +0200 Subject: [PATCH 0121/1012] Implemented infinite loops --- src/build/Function.go | 109 +------------------------------- src/build/FunctionCall.go | 33 ++++++++++ src/build/Keyword.go | 46 ++++++++++++++ src/build/VariableDefinition.go | 55 ++++++++++++++++ src/build/arch/x64/Jump.go | 11 ++++ src/build/asm/Assembler.go | 42 ++++++++++-- src/build/asm/Instructions.go | 10 +++ src/build/asm/Mnemonic.go | 4 ++ src/build/asm/Pointer.go | 1 + 9 files changed, 198 insertions(+), 113 deletions(-) create mode 100644 src/build/FunctionCall.go create mode 100644 src/build/Keyword.go create mode 100644 src/build/VariableDefinition.go create mode 100644 src/build/arch/x64/Jump.go diff --git a/src/build/Function.go b/src/build/Function.go index 7cffba6..dfce655 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -23,6 +23,7 @@ type Function struct { Assembler asm.Assembler CPU cpu.CPU Error error + count struct{ loop int } } // Compile turns a function into machine code. @@ -115,104 +116,6 @@ func (f *Function) CompileInstruction(line token.List) error { return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) } -// CompileKeyword compiles an instruction that starts with a keyword. -func (f *Function) CompileKeyword(line token.List) error { - switch line[0].Text() { - case "return": - if len(line) > 1 { - value := expression.Parse(line[1:]) - defer value.Close() - // TODO: Set the return value - } - - f.Assembler.Return() - - case "loop": - blockStart := line.IndexKind(token.BlockStart) + 1 - blockEnd := line.LastIndexKind(token.BlockEnd) - - if blockStart == -1 { - return errors.New(errors.MissingBlockStart, f.File, line[0].End()) - } - - if blockEnd == -1 { - return errors.New(errors.MissingBlockEnd, f.File, line[len(line)-1].End()) - } - - return f.CompileTokens(line[blockStart:blockEnd]) - - default: - return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) - } - - return nil -} - -// CompileVariableDefinition compiles a variable definition. -func (f *Function) CompileVariableDefinition(expr *expression.Expression) error { - if len(expr.Children) < 2 { - return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) - } - - name := expr.Children[0].Token.Text() - - if f.identifierExists(name) { - return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) - } - - value := expr.Children[1] - - err := value.EachLeaf(func(leaf *expression.Expression) error { - if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) { - return errors.New(&errors.UnknownIdentifier{Name: leaf.Token.Text()}, f.File, leaf.Token.Position) - } - - return nil - }) - - if err != nil { - return err - } - - reg, exists := f.CPU.FindFree() - - if !exists { - panic("no free registers") - } - - f.ExpressionToRegister(value, reg) - f.CPU.Use(reg) - - f.Variables[name] = &Variable{ - Name: name, - Register: reg, - } - - return nil -} - -// CompileFunctionCall compiles a function call. -func (f *Function) CompileFunctionCall(expr *expression.Expression) error { - funcName := expr.Children[0].Token.Text() - parameters := expr.Children[1:] - - for i, parameter := range parameters { - err := f.ExpressionToRegister(parameter, f.CPU.Syscall[i]) - - if err != nil { - return err - } - } - - if funcName == "syscall" { - f.Assembler.Syscall() - } else { - f.Assembler.Call(funcName) - } - - return nil -} - // ExpressionToRegister moves the result of an expression into the given register. func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { if root.IsLeaf() { @@ -285,13 +188,3 @@ func (f *Function) identifierExists(name string) bool { _, exists := f.Variables[name] return exists } - -// isVariableDefinition returns true if the expression is a variable definition. -func isVariableDefinition(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" -} - -// isFunctionCall returns true if the expression is a function call. -func isFunctionCall(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" -} diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go new file mode 100644 index 0000000..387fe43 --- /dev/null +++ b/src/build/FunctionCall.go @@ -0,0 +1,33 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// CompileFunctionCall compiles a top-level function call. +func (f *Function) CompileFunctionCall(expr *expression.Expression) error { + funcName := expr.Children[0].Token.Text() + parameters := expr.Children[1:] + + for i, parameter := range parameters { + err := f.ExpressionToRegister(parameter, f.CPU.Syscall[i]) + + if err != nil { + return err + } + } + + if funcName == "syscall" { + f.Assembler.Syscall() + } else { + f.Assembler.Call(funcName) + } + + return nil +} + +// isFunctionCall returns true if the expression is a function call. +func isFunctionCall(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" +} diff --git a/src/build/Keyword.go b/src/build/Keyword.go new file mode 100644 index 0000000..5913124 --- /dev/null +++ b/src/build/Keyword.go @@ -0,0 +1,46 @@ +package build + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// CompileKeyword compiles an instruction that starts with a keyword. +func (f *Function) CompileKeyword(line token.List) error { + switch line[0].Text() { + case "return": + if len(line) > 1 { + value := expression.Parse(line[1:]) + defer value.Close() + // TODO: Set the return value + } + + f.Assembler.Return() + + case "loop": + blockStart := line.IndexKind(token.BlockStart) + 1 + blockEnd := line.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return errors.New(errors.MissingBlockStart, f.File, line[0].End()) + } + + if blockEnd == -1 { + return errors.New(errors.MissingBlockEnd, f.File, line[len(line)-1].End()) + } + + loop := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) + f.Assembler.Label(loop) + defer f.Assembler.Jump(loop) + f.count.loop++ + return f.CompileTokens(line[blockStart:blockEnd]) + + default: + return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) + } + + return nil +} diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go new file mode 100644 index 0000000..9eca8e9 --- /dev/null +++ b/src/build/VariableDefinition.go @@ -0,0 +1,55 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// CompileVariableDefinition compiles a variable definition. +func (f *Function) CompileVariableDefinition(expr *expression.Expression) error { + if len(expr.Children) < 2 { + return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) + } + + name := expr.Children[0].Token.Text() + + if f.identifierExists(name) { + return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) + } + + value := expr.Children[1] + + err := value.EachLeaf(func(leaf *expression.Expression) error { + if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) { + return errors.New(&errors.UnknownIdentifier{Name: leaf.Token.Text()}, f.File, leaf.Token.Position) + } + + return nil + }) + + if err != nil { + return err + } + + reg, exists := f.CPU.FindFree() + + if !exists { + panic("no free registers") + } + + f.ExpressionToRegister(value, reg) + f.CPU.Use(reg) + + f.Variables[name] = &Variable{ + Name: name, + Register: reg, + } + + return nil +} + +// isVariableDefinition returns true if the expression is a variable definition. +func isVariableDefinition(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" +} diff --git a/src/build/arch/x64/Jump.go b/src/build/arch/x64/Jump.go new file mode 100644 index 0000000..32b8b98 --- /dev/null +++ b/src/build/arch/x64/Jump.go @@ -0,0 +1,11 @@ +package x64 + +// Jump continues program flow at the new address. +// The address is relative to the next instruction. +func Jump8(code []byte, address int8) []byte { + return append( + code, + 0xeb, + byte(address), + ) +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index f159bc4..f14ca7c 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -44,14 +44,32 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case CALL: code = x64.Call(code, 0x00_00_00_00) + size := 4 label := x.Data.(*Label) - nextInstructionAddress := len(code) + nextInstructionAddress := Address(len(code)) pointers = append(pointers, Pointer{ - Position: Address(len(code) - 4), + Position: Address(len(code) - size), + Size: uint8(size), Resolve: func() Address { destination := labels[label.Name] - distance := int32(destination) - int32(nextInstructionAddress) + distance := destination - nextInstructionAddress + return Address(distance) + }, + }) + + case JUMP: + code = x64.Jump8(code, 0x00) + size := 1 + label := x.Data.(*Label) + nextInstructionAddress := Address(len(code)) + + pointers = append(pointers, Pointer{ + Position: Address(len(code) - size), + Size: uint8(size), + Resolve: func() Address { + destination := labels[label.Name] + distance := destination - nextInstructionAddress return Address(distance) }, }) @@ -67,8 +85,22 @@ func (a *Assembler) Finalize() ([]byte, []byte) { // dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) for _, pointer := range pointers { - slice := code[pointer.Position : pointer.Position+4] - binary.LittleEndian.PutUint32(slice, pointer.Resolve()) + slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] + address := pointer.Resolve() + + switch pointer.Size { + case 1: + slice[0] = uint8(address) + + case 2: + binary.LittleEndian.PutUint16(slice, uint16(address)) + + case 4: + binary.LittleEndian.PutUint32(slice, uint32(address)) + + case 8: + binary.LittleEndian.PutUint64(slice, uint64(address)) + } } return code, data diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 82aebdb..3662595 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -44,6 +44,16 @@ func (a *Assembler) Call(name string) { }) } +// Jump jumps to a position that is identified by a label. +func (a *Assembler) Jump(name string) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: JUMP, + Data: &Label{ + Name: name, + }, + }) +} + // Return returns back to the caller. func (a *Assembler) Return() { a.Instructions = append(a.Instructions, Instruction{Mnemonic: RETURN}) diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index ad071c6..a711a0b 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -9,6 +9,7 @@ const ( SYSCALL LABEL CALL + JUMP ) // String returns a human readable version. @@ -28,6 +29,9 @@ func (m Mnemonic) String() string { case CALL: return "call" + + case JUMP: + return "jump" } return "NONE" diff --git a/src/build/asm/Pointer.go b/src/build/asm/Pointer.go index 6499ce9..9be746f 100644 --- a/src/build/asm/Pointer.go +++ b/src/build/asm/Pointer.go @@ -8,5 +8,6 @@ type Address = uint32 // Resolve: The function that will return the final address. type Pointer struct { Position Address + Size uint8 Resolve func() Address } From 41f5dcbe62bcfc759deeb56bf4377205655ceb73 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 24 Jun 2024 11:00:32 +0200 Subject: [PATCH 0122/1012] Implemented infinite loops --- src/build/Function.go | 109 +------------------------------- src/build/FunctionCall.go | 33 ++++++++++ src/build/Keyword.go | 46 ++++++++++++++ src/build/VariableDefinition.go | 55 ++++++++++++++++ src/build/arch/x64/Jump.go | 11 ++++ src/build/asm/Assembler.go | 42 ++++++++++-- src/build/asm/Instructions.go | 10 +++ src/build/asm/Mnemonic.go | 4 ++ src/build/asm/Pointer.go | 1 + 9 files changed, 198 insertions(+), 113 deletions(-) create mode 100644 src/build/FunctionCall.go create mode 100644 src/build/Keyword.go create mode 100644 src/build/VariableDefinition.go create mode 100644 src/build/arch/x64/Jump.go diff --git a/src/build/Function.go b/src/build/Function.go index 7cffba6..dfce655 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -23,6 +23,7 @@ type Function struct { Assembler asm.Assembler CPU cpu.CPU Error error + count struct{ loop int } } // Compile turns a function into machine code. @@ -115,104 +116,6 @@ func (f *Function) CompileInstruction(line token.List) error { return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) } -// CompileKeyword compiles an instruction that starts with a keyword. -func (f *Function) CompileKeyword(line token.List) error { - switch line[0].Text() { - case "return": - if len(line) > 1 { - value := expression.Parse(line[1:]) - defer value.Close() - // TODO: Set the return value - } - - f.Assembler.Return() - - case "loop": - blockStart := line.IndexKind(token.BlockStart) + 1 - blockEnd := line.LastIndexKind(token.BlockEnd) - - if blockStart == -1 { - return errors.New(errors.MissingBlockStart, f.File, line[0].End()) - } - - if blockEnd == -1 { - return errors.New(errors.MissingBlockEnd, f.File, line[len(line)-1].End()) - } - - return f.CompileTokens(line[blockStart:blockEnd]) - - default: - return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) - } - - return nil -} - -// CompileVariableDefinition compiles a variable definition. -func (f *Function) CompileVariableDefinition(expr *expression.Expression) error { - if len(expr.Children) < 2 { - return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) - } - - name := expr.Children[0].Token.Text() - - if f.identifierExists(name) { - return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) - } - - value := expr.Children[1] - - err := value.EachLeaf(func(leaf *expression.Expression) error { - if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) { - return errors.New(&errors.UnknownIdentifier{Name: leaf.Token.Text()}, f.File, leaf.Token.Position) - } - - return nil - }) - - if err != nil { - return err - } - - reg, exists := f.CPU.FindFree() - - if !exists { - panic("no free registers") - } - - f.ExpressionToRegister(value, reg) - f.CPU.Use(reg) - - f.Variables[name] = &Variable{ - Name: name, - Register: reg, - } - - return nil -} - -// CompileFunctionCall compiles a function call. -func (f *Function) CompileFunctionCall(expr *expression.Expression) error { - funcName := expr.Children[0].Token.Text() - parameters := expr.Children[1:] - - for i, parameter := range parameters { - err := f.ExpressionToRegister(parameter, f.CPU.Syscall[i]) - - if err != nil { - return err - } - } - - if funcName == "syscall" { - f.Assembler.Syscall() - } else { - f.Assembler.Call(funcName) - } - - return nil -} - // ExpressionToRegister moves the result of an expression into the given register. func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { if root.IsLeaf() { @@ -285,13 +188,3 @@ func (f *Function) identifierExists(name string) bool { _, exists := f.Variables[name] return exists } - -// isVariableDefinition returns true if the expression is a variable definition. -func isVariableDefinition(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" -} - -// isFunctionCall returns true if the expression is a function call. -func isFunctionCall(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" -} diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go new file mode 100644 index 0000000..387fe43 --- /dev/null +++ b/src/build/FunctionCall.go @@ -0,0 +1,33 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// CompileFunctionCall compiles a top-level function call. +func (f *Function) CompileFunctionCall(expr *expression.Expression) error { + funcName := expr.Children[0].Token.Text() + parameters := expr.Children[1:] + + for i, parameter := range parameters { + err := f.ExpressionToRegister(parameter, f.CPU.Syscall[i]) + + if err != nil { + return err + } + } + + if funcName == "syscall" { + f.Assembler.Syscall() + } else { + f.Assembler.Call(funcName) + } + + return nil +} + +// isFunctionCall returns true if the expression is a function call. +func isFunctionCall(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" +} diff --git a/src/build/Keyword.go b/src/build/Keyword.go new file mode 100644 index 0000000..5913124 --- /dev/null +++ b/src/build/Keyword.go @@ -0,0 +1,46 @@ +package build + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// CompileKeyword compiles an instruction that starts with a keyword. +func (f *Function) CompileKeyword(line token.List) error { + switch line[0].Text() { + case "return": + if len(line) > 1 { + value := expression.Parse(line[1:]) + defer value.Close() + // TODO: Set the return value + } + + f.Assembler.Return() + + case "loop": + blockStart := line.IndexKind(token.BlockStart) + 1 + blockEnd := line.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return errors.New(errors.MissingBlockStart, f.File, line[0].End()) + } + + if blockEnd == -1 { + return errors.New(errors.MissingBlockEnd, f.File, line[len(line)-1].End()) + } + + loop := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) + f.Assembler.Label(loop) + defer f.Assembler.Jump(loop) + f.count.loop++ + return f.CompileTokens(line[blockStart:blockEnd]) + + default: + return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) + } + + return nil +} diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go new file mode 100644 index 0000000..9eca8e9 --- /dev/null +++ b/src/build/VariableDefinition.go @@ -0,0 +1,55 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// CompileVariableDefinition compiles a variable definition. +func (f *Function) CompileVariableDefinition(expr *expression.Expression) error { + if len(expr.Children) < 2 { + return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) + } + + name := expr.Children[0].Token.Text() + + if f.identifierExists(name) { + return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) + } + + value := expr.Children[1] + + err := value.EachLeaf(func(leaf *expression.Expression) error { + if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) { + return errors.New(&errors.UnknownIdentifier{Name: leaf.Token.Text()}, f.File, leaf.Token.Position) + } + + return nil + }) + + if err != nil { + return err + } + + reg, exists := f.CPU.FindFree() + + if !exists { + panic("no free registers") + } + + f.ExpressionToRegister(value, reg) + f.CPU.Use(reg) + + f.Variables[name] = &Variable{ + Name: name, + Register: reg, + } + + return nil +} + +// isVariableDefinition returns true if the expression is a variable definition. +func isVariableDefinition(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" +} diff --git a/src/build/arch/x64/Jump.go b/src/build/arch/x64/Jump.go new file mode 100644 index 0000000..32b8b98 --- /dev/null +++ b/src/build/arch/x64/Jump.go @@ -0,0 +1,11 @@ +package x64 + +// Jump continues program flow at the new address. +// The address is relative to the next instruction. +func Jump8(code []byte, address int8) []byte { + return append( + code, + 0xeb, + byte(address), + ) +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index f159bc4..f14ca7c 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -44,14 +44,32 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case CALL: code = x64.Call(code, 0x00_00_00_00) + size := 4 label := x.Data.(*Label) - nextInstructionAddress := len(code) + nextInstructionAddress := Address(len(code)) pointers = append(pointers, Pointer{ - Position: Address(len(code) - 4), + Position: Address(len(code) - size), + Size: uint8(size), Resolve: func() Address { destination := labels[label.Name] - distance := int32(destination) - int32(nextInstructionAddress) + distance := destination - nextInstructionAddress + return Address(distance) + }, + }) + + case JUMP: + code = x64.Jump8(code, 0x00) + size := 1 + label := x.Data.(*Label) + nextInstructionAddress := Address(len(code)) + + pointers = append(pointers, Pointer{ + Position: Address(len(code) - size), + Size: uint8(size), + Resolve: func() Address { + destination := labels[label.Name] + distance := destination - nextInstructionAddress return Address(distance) }, }) @@ -67,8 +85,22 @@ func (a *Assembler) Finalize() ([]byte, []byte) { // dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) for _, pointer := range pointers { - slice := code[pointer.Position : pointer.Position+4] - binary.LittleEndian.PutUint32(slice, pointer.Resolve()) + slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] + address := pointer.Resolve() + + switch pointer.Size { + case 1: + slice[0] = uint8(address) + + case 2: + binary.LittleEndian.PutUint16(slice, uint16(address)) + + case 4: + binary.LittleEndian.PutUint32(slice, uint32(address)) + + case 8: + binary.LittleEndian.PutUint64(slice, uint64(address)) + } } return code, data diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 82aebdb..3662595 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -44,6 +44,16 @@ func (a *Assembler) Call(name string) { }) } +// Jump jumps to a position that is identified by a label. +func (a *Assembler) Jump(name string) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: JUMP, + Data: &Label{ + Name: name, + }, + }) +} + // Return returns back to the caller. func (a *Assembler) Return() { a.Instructions = append(a.Instructions, Instruction{Mnemonic: RETURN}) diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index ad071c6..a711a0b 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -9,6 +9,7 @@ const ( SYSCALL LABEL CALL + JUMP ) // String returns a human readable version. @@ -28,6 +29,9 @@ func (m Mnemonic) String() string { case CALL: return "call" + + case JUMP: + return "jump" } return "NONE" diff --git a/src/build/asm/Pointer.go b/src/build/asm/Pointer.go index 6499ce9..9be746f 100644 --- a/src/build/asm/Pointer.go +++ b/src/build/asm/Pointer.go @@ -8,5 +8,6 @@ type Address = uint32 // Resolve: The function that will return the final address. type Pointer struct { Position Address + Size uint8 Resolve func() Address } From 0657b88945cc1f71ae597bdc7e52f2e6a4f37f1a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 24 Jun 2024 14:14:07 +0200 Subject: [PATCH 0123/1012] Implemented addition --- examples/hello/hello.q | 7 +++++-- src/build/Function.go | 10 ++++++++++ src/build/arch/x64/Add.go | 18 ++++++++++++++++++ src/build/asm/Assembler.go | 27 ++++++++++++++++++--------- src/build/asm/Instructions.go | 11 +++++++++++ src/build/asm/Mnemonic.go | 28 ++++++++++++++++------------ 6 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 src/build/arch/x64/Add.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index a9ef287..126c5b5 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -5,8 +5,11 @@ main() { hello() { write := 1 stdout := 1 - address := 4194305 - length := 3 + address := 4194304 + length := 0 + + address += 1 + length += 3 loop { syscall(write, stdout, address, length) diff --git a/src/build/Function.go b/src/build/Function.go index dfce655..2e82577 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -113,6 +113,16 @@ func (f *Function) CompileInstruction(line token.List) error { return f.CompileFunctionCall(expr) } + if expr.Token.Kind == token.Operator { + switch expr.Token.Text() { + case "+=": + name := expr.Children[0].Token.Text() + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) + f.Assembler.AddRegisterNumber(f.Variables[name].Register, uint64(number)) + return nil + } + } + return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) } diff --git a/src/build/arch/x64/Add.go b/src/build/arch/x64/Add.go new file mode 100644 index 0000000..234142b --- /dev/null +++ b/src/build/arch/x64/Add.go @@ -0,0 +1,18 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// AddRegNum8 adds a byte to the given register. +func AddRegNum8(code []byte, destination cpu.Register, number uint8) []byte { + if destination >= 8 { + code = append(code, REX(0, 0, 0, 1)) + destination -= 8 + } + + return append( + code, + 0x83, + ModRM(0b11, 0b000, byte(destination)), + byte(number), + ) +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index f14ca7c..5eb7b2b 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -27,21 +27,15 @@ func (a *Assembler) Finalize() ([]byte, []byte) { for _, x := range a.Instructions { switch x.Mnemonic { - case MOVE: + case ADD: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number)) + code = x64.AddRegNum8(code, operands.Register, uint8(operands.Number)) case *RegisterRegister: - code = x64.MoveRegReg64(code, operands.Destination, operands.Source) + // code = x64.AddRegReg64(code, operands.Destination, operands.Source) } - case RETURN: - code = x64.Return(code) - - case SYSCALL: - code = x64.Syscall(code) - case CALL: code = x64.Call(code, 0x00_00_00_00) size := 4 @@ -77,6 +71,21 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case LABEL: labels[x.Data.(*Label).Name] = Address(len(code)) + case MOVE: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number)) + + case *RegisterRegister: + code = x64.MoveRegReg64(code, operands.Destination, operands.Source) + } + + case RETURN: + code = x64.Return(code) + + case SYSCALL: + code = x64.Syscall(code) + default: panic("Unknown mnemonic: " + x.Mnemonic.String()) } diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 3662595..091b9d4 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -2,6 +2,17 @@ package asm import "git.akyoto.dev/cli/q/src/build/cpu" +// AddRegisterNumber adds a number to the given register. +func (a *Assembler) AddRegisterNumber(reg cpu.Register, number uint64) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: ADD, + Data: &RegisterNumber{ + Register: reg, + Number: number, + }, + }) +} + // MoveRegisterNumber moves a number into the given register. func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index a711a0b..55f1dba 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -4,17 +4,30 @@ type Mnemonic uint8 const ( NONE Mnemonic = iota + ADD + CALL + JUMP + LABEL MOVE RETURN SYSCALL - LABEL - CALL - JUMP ) // String returns a human readable version. func (m Mnemonic) String() string { switch m { + case ADD: + return "add" + + case CALL: + return "call" + + case JUMP: + return "jump" + + case LABEL: + return "label" + case MOVE: return "move" @@ -23,15 +36,6 @@ func (m Mnemonic) String() string { case SYSCALL: return "syscall" - - case LABEL: - return "label" - - case CALL: - return "call" - - case JUMP: - return "jump" } return "NONE" From 597cb9abed201793a88fc67942dc56d6e451b039 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 24 Jun 2024 14:14:07 +0200 Subject: [PATCH 0124/1012] Implemented addition --- examples/hello/hello.q | 7 +++++-- src/build/Function.go | 10 ++++++++++ src/build/arch/x64/Add.go | 18 ++++++++++++++++++ src/build/asm/Assembler.go | 27 ++++++++++++++++++--------- src/build/asm/Instructions.go | 11 +++++++++++ src/build/asm/Mnemonic.go | 28 ++++++++++++++++------------ 6 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 src/build/arch/x64/Add.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index a9ef287..126c5b5 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -5,8 +5,11 @@ main() { hello() { write := 1 stdout := 1 - address := 4194305 - length := 3 + address := 4194304 + length := 0 + + address += 1 + length += 3 loop { syscall(write, stdout, address, length) diff --git a/src/build/Function.go b/src/build/Function.go index dfce655..2e82577 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -113,6 +113,16 @@ func (f *Function) CompileInstruction(line token.List) error { return f.CompileFunctionCall(expr) } + if expr.Token.Kind == token.Operator { + switch expr.Token.Text() { + case "+=": + name := expr.Children[0].Token.Text() + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) + f.Assembler.AddRegisterNumber(f.Variables[name].Register, uint64(number)) + return nil + } + } + return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) } diff --git a/src/build/arch/x64/Add.go b/src/build/arch/x64/Add.go new file mode 100644 index 0000000..234142b --- /dev/null +++ b/src/build/arch/x64/Add.go @@ -0,0 +1,18 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// AddRegNum8 adds a byte to the given register. +func AddRegNum8(code []byte, destination cpu.Register, number uint8) []byte { + if destination >= 8 { + code = append(code, REX(0, 0, 0, 1)) + destination -= 8 + } + + return append( + code, + 0x83, + ModRM(0b11, 0b000, byte(destination)), + byte(number), + ) +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index f14ca7c..5eb7b2b 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -27,21 +27,15 @@ func (a *Assembler) Finalize() ([]byte, []byte) { for _, x := range a.Instructions { switch x.Mnemonic { - case MOVE: + case ADD: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number)) + code = x64.AddRegNum8(code, operands.Register, uint8(operands.Number)) case *RegisterRegister: - code = x64.MoveRegReg64(code, operands.Destination, operands.Source) + // code = x64.AddRegReg64(code, operands.Destination, operands.Source) } - case RETURN: - code = x64.Return(code) - - case SYSCALL: - code = x64.Syscall(code) - case CALL: code = x64.Call(code, 0x00_00_00_00) size := 4 @@ -77,6 +71,21 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case LABEL: labels[x.Data.(*Label).Name] = Address(len(code)) + case MOVE: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number)) + + case *RegisterRegister: + code = x64.MoveRegReg64(code, operands.Destination, operands.Source) + } + + case RETURN: + code = x64.Return(code) + + case SYSCALL: + code = x64.Syscall(code) + default: panic("Unknown mnemonic: " + x.Mnemonic.String()) } diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 3662595..091b9d4 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -2,6 +2,17 @@ package asm import "git.akyoto.dev/cli/q/src/build/cpu" +// AddRegisterNumber adds a number to the given register. +func (a *Assembler) AddRegisterNumber(reg cpu.Register, number uint64) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: ADD, + Data: &RegisterNumber{ + Register: reg, + Number: number, + }, + }) +} + // MoveRegisterNumber moves a number into the given register. func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index a711a0b..55f1dba 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -4,17 +4,30 @@ type Mnemonic uint8 const ( NONE Mnemonic = iota + ADD + CALL + JUMP + LABEL MOVE RETURN SYSCALL - LABEL - CALL - JUMP ) // String returns a human readable version. func (m Mnemonic) String() string { switch m { + case ADD: + return "add" + + case CALL: + return "call" + + case JUMP: + return "jump" + + case LABEL: + return "label" + case MOVE: return "move" @@ -23,15 +36,6 @@ func (m Mnemonic) String() string { case SYSCALL: return "syscall" - - case LABEL: - return "label" - - case CALL: - return "call" - - case JUMP: - return "jump" } return "NONE" From 9e3af3b01cad50f1741d48608d35d7915988d54a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 24 Jun 2024 16:55:15 +0200 Subject: [PATCH 0125/1012] Implemented subtraction --- examples/hello/hello.q | 6 ++-- src/build/Function.go | 10 +++++-- src/build/arch/x64/Add.go | 18 ----------- src/build/arch/x64/Math.go | 15 ++++++++++ src/build/arch/x64/numberToRegister.go | 41 ++++++++++++++++++++++++++ src/build/asm/Assembler.go | 9 ++++-- src/build/asm/Instructions.go | 15 ++++++++-- src/build/asm/Mnemonic.go | 4 +++ src/build/asm/RegisterNumber.go | 2 +- 9 files changed, 92 insertions(+), 28 deletions(-) delete mode 100644 src/build/arch/x64/Add.go create mode 100644 src/build/arch/x64/Math.go create mode 100644 src/build/arch/x64/numberToRegister.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 126c5b5..c5324ab 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -5,11 +5,13 @@ main() { hello() { write := 1 stdout := 1 - address := 4194304 + address := 0 length := 0 + address += 4194304 address += 1 - length += 3 + length += 5 + length -= 2 loop { syscall(write, stdout, address, length) diff --git a/src/build/Function.go b/src/build/Function.go index 2e82577..58e7cee 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -118,7 +118,13 @@ func (f *Function) CompileInstruction(line token.List) error { case "+=": name := expr.Children[0].Token.Text() number, _ := strconv.Atoi(expr.Children[1].Token.Text()) - f.Assembler.AddRegisterNumber(f.Variables[name].Register, uint64(number)) + f.Assembler.AddRegisterNumber(f.Variables[name].Register, number) + return nil + + case "-=": + name := expr.Children[0].Token.Text() + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) + f.Assembler.SubRegisterNumber(f.Variables[name].Register, number) return nil } } @@ -158,7 +164,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return err } - f.Assembler.MoveRegisterNumber(register, uint64(n)) + f.Assembler.MoveRegisterNumber(register, n) return nil case token.String: diff --git a/src/build/arch/x64/Add.go b/src/build/arch/x64/Add.go deleted file mode 100644 index 234142b..0000000 --- a/src/build/arch/x64/Add.go +++ /dev/null @@ -1,18 +0,0 @@ -package x64 - -import "git.akyoto.dev/cli/q/src/build/cpu" - -// AddRegNum8 adds a byte to the given register. -func AddRegNum8(code []byte, destination cpu.Register, number uint8) []byte { - if destination >= 8 { - code = append(code, REX(0, 0, 0, 1)) - destination -= 8 - } - - return append( - code, - 0x83, - ModRM(0b11, 0b000, byte(destination)), - byte(number), - ) -} diff --git a/src/build/arch/x64/Math.go b/src/build/arch/x64/Math.go new file mode 100644 index 0000000..92a7137 --- /dev/null +++ b/src/build/arch/x64/Math.go @@ -0,0 +1,15 @@ +package x64 + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// AddRegNum adds a number to the given register. +func AddRegNum(code []byte, destination cpu.Register, number int) []byte { + return numberToRegister(0x83, 0x81, 0b000, code, destination, number) +} + +// SubRegNum subtracts a number from the given register. +func SubRegNum(code []byte, destination cpu.Register, number int) []byte { + return numberToRegister(0x83, 0x81, 0b101, code, destination, number) +} diff --git a/src/build/arch/x64/numberToRegister.go b/src/build/arch/x64/numberToRegister.go new file mode 100644 index 0000000..025a2ec --- /dev/null +++ b/src/build/arch/x64/numberToRegister.go @@ -0,0 +1,41 @@ +package x64 + +import ( + "math" + + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// numberToRegister encodes an instruction with a register and a number parameter. +func numberToRegister(opCode8 byte, opCode32 byte, reg byte, code []byte, destination cpu.Register, number int) []byte { + b := byte(0) + + if destination >= 8 { + b = 1 + destination -= 8 + } + + rex := REX(1, 0, 0, b) + modRM := ModRM(0b11, reg, byte(destination)) + + if number >= math.MinInt8 && number <= math.MaxInt8 { + return append( + code, + rex, + opCode8, + modRM, + byte(number), + ) + } + + return append( + code, + rex, + opCode32, + modRM, + byte(number), + byte(number>>8), + byte(number>>16), + byte(number>>24), + ) +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 5eb7b2b..6dbb8bd 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -30,10 +30,13 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case ADD: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.AddRegNum8(code, operands.Register, uint8(operands.Number)) + code = x64.AddRegNum(code, operands.Register, operands.Number) + } - case *RegisterRegister: - // code = x64.AddRegReg64(code, operands.Destination, operands.Source) + case SUB: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.SubRegNum(code, operands.Register, operands.Number) } case CALL: diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 091b9d4..efd6920 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -3,7 +3,7 @@ package asm import "git.akyoto.dev/cli/q/src/build/cpu" // AddRegisterNumber adds a number to the given register. -func (a *Assembler) AddRegisterNumber(reg cpu.Register, number uint64) { +func (a *Assembler) AddRegisterNumber(reg cpu.Register, number int) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: ADD, Data: &RegisterNumber{ @@ -13,8 +13,19 @@ func (a *Assembler) AddRegisterNumber(reg cpu.Register, number uint64) { }) } +// SubRegisterNumber subtracts a number from the given register. +func (a *Assembler) SubRegisterNumber(reg cpu.Register, number int) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: SUB, + Data: &RegisterNumber{ + Register: reg, + Number: number, + }, + }) +} + // MoveRegisterNumber moves a number into the given register. -func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) { +func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number int) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: MOVE, Data: &RegisterNumber{ diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 55f1dba..c5309eb 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -10,6 +10,7 @@ const ( LABEL MOVE RETURN + SUB SYSCALL ) @@ -34,6 +35,9 @@ func (m Mnemonic) String() string { case RETURN: return "return" + case SUB: + return "sub" + case SYSCALL: return "syscall" } diff --git a/src/build/asm/RegisterNumber.go b/src/build/asm/RegisterNumber.go index 6484e08..5040b53 100644 --- a/src/build/asm/RegisterNumber.go +++ b/src/build/asm/RegisterNumber.go @@ -9,7 +9,7 @@ import ( // RegisterNumber operates with a register and a number. type RegisterNumber struct { Register cpu.Register - Number uint64 + Number int } // String returns a human readable version. From b018d8de61771cd8954f580f9b208eeca0a77ae3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 24 Jun 2024 16:55:15 +0200 Subject: [PATCH 0126/1012] Implemented subtraction --- examples/hello/hello.q | 6 ++-- src/build/Function.go | 10 +++++-- src/build/arch/x64/Add.go | 18 ----------- src/build/arch/x64/Math.go | 15 ++++++++++ src/build/arch/x64/numberToRegister.go | 41 ++++++++++++++++++++++++++ src/build/asm/Assembler.go | 9 ++++-- src/build/asm/Instructions.go | 15 ++++++++-- src/build/asm/Mnemonic.go | 4 +++ src/build/asm/RegisterNumber.go | 2 +- 9 files changed, 92 insertions(+), 28 deletions(-) delete mode 100644 src/build/arch/x64/Add.go create mode 100644 src/build/arch/x64/Math.go create mode 100644 src/build/arch/x64/numberToRegister.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 126c5b5..c5324ab 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -5,11 +5,13 @@ main() { hello() { write := 1 stdout := 1 - address := 4194304 + address := 0 length := 0 + address += 4194304 address += 1 - length += 3 + length += 5 + length -= 2 loop { syscall(write, stdout, address, length) diff --git a/src/build/Function.go b/src/build/Function.go index 2e82577..58e7cee 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -118,7 +118,13 @@ func (f *Function) CompileInstruction(line token.List) error { case "+=": name := expr.Children[0].Token.Text() number, _ := strconv.Atoi(expr.Children[1].Token.Text()) - f.Assembler.AddRegisterNumber(f.Variables[name].Register, uint64(number)) + f.Assembler.AddRegisterNumber(f.Variables[name].Register, number) + return nil + + case "-=": + name := expr.Children[0].Token.Text() + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) + f.Assembler.SubRegisterNumber(f.Variables[name].Register, number) return nil } } @@ -158,7 +164,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return err } - f.Assembler.MoveRegisterNumber(register, uint64(n)) + f.Assembler.MoveRegisterNumber(register, n) return nil case token.String: diff --git a/src/build/arch/x64/Add.go b/src/build/arch/x64/Add.go deleted file mode 100644 index 234142b..0000000 --- a/src/build/arch/x64/Add.go +++ /dev/null @@ -1,18 +0,0 @@ -package x64 - -import "git.akyoto.dev/cli/q/src/build/cpu" - -// AddRegNum8 adds a byte to the given register. -func AddRegNum8(code []byte, destination cpu.Register, number uint8) []byte { - if destination >= 8 { - code = append(code, REX(0, 0, 0, 1)) - destination -= 8 - } - - return append( - code, - 0x83, - ModRM(0b11, 0b000, byte(destination)), - byte(number), - ) -} diff --git a/src/build/arch/x64/Math.go b/src/build/arch/x64/Math.go new file mode 100644 index 0000000..92a7137 --- /dev/null +++ b/src/build/arch/x64/Math.go @@ -0,0 +1,15 @@ +package x64 + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// AddRegNum adds a number to the given register. +func AddRegNum(code []byte, destination cpu.Register, number int) []byte { + return numberToRegister(0x83, 0x81, 0b000, code, destination, number) +} + +// SubRegNum subtracts a number from the given register. +func SubRegNum(code []byte, destination cpu.Register, number int) []byte { + return numberToRegister(0x83, 0x81, 0b101, code, destination, number) +} diff --git a/src/build/arch/x64/numberToRegister.go b/src/build/arch/x64/numberToRegister.go new file mode 100644 index 0000000..025a2ec --- /dev/null +++ b/src/build/arch/x64/numberToRegister.go @@ -0,0 +1,41 @@ +package x64 + +import ( + "math" + + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// numberToRegister encodes an instruction with a register and a number parameter. +func numberToRegister(opCode8 byte, opCode32 byte, reg byte, code []byte, destination cpu.Register, number int) []byte { + b := byte(0) + + if destination >= 8 { + b = 1 + destination -= 8 + } + + rex := REX(1, 0, 0, b) + modRM := ModRM(0b11, reg, byte(destination)) + + if number >= math.MinInt8 && number <= math.MaxInt8 { + return append( + code, + rex, + opCode8, + modRM, + byte(number), + ) + } + + return append( + code, + rex, + opCode32, + modRM, + byte(number), + byte(number>>8), + byte(number>>16), + byte(number>>24), + ) +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 5eb7b2b..6dbb8bd 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -30,10 +30,13 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case ADD: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.AddRegNum8(code, operands.Register, uint8(operands.Number)) + code = x64.AddRegNum(code, operands.Register, operands.Number) + } - case *RegisterRegister: - // code = x64.AddRegReg64(code, operands.Destination, operands.Source) + case SUB: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.SubRegNum(code, operands.Register, operands.Number) } case CALL: diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 091b9d4..efd6920 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -3,7 +3,7 @@ package asm import "git.akyoto.dev/cli/q/src/build/cpu" // AddRegisterNumber adds a number to the given register. -func (a *Assembler) AddRegisterNumber(reg cpu.Register, number uint64) { +func (a *Assembler) AddRegisterNumber(reg cpu.Register, number int) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: ADD, Data: &RegisterNumber{ @@ -13,8 +13,19 @@ func (a *Assembler) AddRegisterNumber(reg cpu.Register, number uint64) { }) } +// SubRegisterNumber subtracts a number from the given register. +func (a *Assembler) SubRegisterNumber(reg cpu.Register, number int) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: SUB, + Data: &RegisterNumber{ + Register: reg, + Number: number, + }, + }) +} + // MoveRegisterNumber moves a number into the given register. -func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) { +func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number int) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: MOVE, Data: &RegisterNumber{ diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 55f1dba..c5309eb 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -10,6 +10,7 @@ const ( LABEL MOVE RETURN + SUB SYSCALL ) @@ -34,6 +35,9 @@ func (m Mnemonic) String() string { case RETURN: return "return" + case SUB: + return "sub" + case SYSCALL: return "syscall" } diff --git a/src/build/asm/RegisterNumber.go b/src/build/asm/RegisterNumber.go index 6484e08..5040b53 100644 --- a/src/build/asm/RegisterNumber.go +++ b/src/build/asm/RegisterNumber.go @@ -9,7 +9,7 @@ import ( // RegisterNumber operates with a register and a number. type RegisterNumber struct { Register cpu.Register - Number uint64 + Number int } // String returns a human readable version. From c1d3e0d11b526a294c4087403a88a7bab3b40255 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 24 Jun 2024 17:51:24 +0200 Subject: [PATCH 0127/1012] Added more tests --- src/build/arch/x64/Add.go | 10 +++++ src/build/arch/x64/Add_test.go | 56 ++++++++++++++++++++++++++ src/build/arch/x64/Registers.go | 40 +++++++++--------- src/build/arch/x64/{Math.go => Sub.go} | 5 --- src/build/arch/x64/Sub_test.go | 56 ++++++++++++++++++++++++++ 5 files changed, 142 insertions(+), 25 deletions(-) create mode 100644 src/build/arch/x64/Add.go create mode 100644 src/build/arch/x64/Add_test.go rename src/build/arch/x64/{Math.go => Sub.go} (57%) create mode 100644 src/build/arch/x64/Sub_test.go diff --git a/src/build/arch/x64/Add.go b/src/build/arch/x64/Add.go new file mode 100644 index 0000000..d394968 --- /dev/null +++ b/src/build/arch/x64/Add.go @@ -0,0 +1,10 @@ +package x64 + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// AddRegNum adds a number to the given register. +func AddRegNum(code []byte, destination cpu.Register, number int) []byte { + return numberToRegister(0x83, 0x81, 0b000, code, destination, number) +} diff --git a/src/build/arch/x64/Add_test.go b/src/build/arch/x64/Add_test.go new file mode 100644 index 0000000..221dbbe --- /dev/null +++ b/src/build/arch/x64/Add_test.go @@ -0,0 +1,56 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestAddRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0x83, 0xc0, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0x83, 0xc1, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0x83, 0xc2, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0x83, 0xc3, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0x83, 0xc4, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0x83, 0xc5, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0x83, 0xc6, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0x83, 0xc7, 0x01}}, + {x64.R8, 1, []byte{0x49, 0x83, 0xc0, 0x01}}, + {x64.R9, 1, []byte{0x49, 0x83, 0xc1, 0x01}}, + {x64.R10, 1, []byte{0x49, 0x83, 0xc2, 0x01}}, + {x64.R11, 1, []byte{0x49, 0x83, 0xc3, 0x01}}, + {x64.R12, 1, []byte{0x49, 0x83, 0xc4, 0x01}}, + {x64.R13, 1, []byte{0x49, 0x83, 0xc5, 0x01}}, + {x64.R14, 1, []byte{0x49, 0x83, 0xc6, 0x01}}, + {x64.R15, 1, []byte{0x49, 0x83, 0xc7, 0x01}}, + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("add %s, %x", pattern.Register, pattern.Number) + code := x64.AddRegNum(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go index 7aaa3c8..ac47f67 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/build/arch/x64/Registers.go @@ -3,26 +3,26 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" const ( - rax = iota - rcx - rdx - rbx - rsp - rbp - rsi - rdi - r8 - r9 - r10 - r11 - r12 - r13 - r14 - r15 + RAX = iota + RCX + RDX + RBX + RSP + RBP + RSI + RDI + R8 + R9 + R10 + R11 + R12 + R13 + R14 + R15 ) -const SyscallReturn = rax +const SyscallReturn = RAX -var GeneralRegisters = []cpu.Register{rbx, rbp, r12, r13, r14, r15} -var SyscallRegisters = []cpu.Register{rax, rdi, rsi, rdx, r10, r8, r9} -var ReturnValueRegisters = []cpu.Register{rax, rcx, r11} +var GeneralRegisters = []cpu.Register{RBX, RBP, R12, R13, R14, R15} +var SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} +var ReturnValueRegisters = []cpu.Register{RAX, RCX, R11} diff --git a/src/build/arch/x64/Math.go b/src/build/arch/x64/Sub.go similarity index 57% rename from src/build/arch/x64/Math.go rename to src/build/arch/x64/Sub.go index 92a7137..af9511c 100644 --- a/src/build/arch/x64/Math.go +++ b/src/build/arch/x64/Sub.go @@ -4,11 +4,6 @@ import ( "git.akyoto.dev/cli/q/src/build/cpu" ) -// AddRegNum adds a number to the given register. -func AddRegNum(code []byte, destination cpu.Register, number int) []byte { - return numberToRegister(0x83, 0x81, 0b000, code, destination, number) -} - // SubRegNum subtracts a number from the given register. func SubRegNum(code []byte, destination cpu.Register, number int) []byte { return numberToRegister(0x83, 0x81, 0b101, code, destination, number) diff --git a/src/build/arch/x64/Sub_test.go b/src/build/arch/x64/Sub_test.go new file mode 100644 index 0000000..a0b0b10 --- /dev/null +++ b/src/build/arch/x64/Sub_test.go @@ -0,0 +1,56 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestSubRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0x83, 0xE8, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0x83, 0xE9, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0x83, 0xEA, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0x83, 0xEB, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0x83, 0xEC, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0x83, 0xED, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0x83, 0xEE, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0x83, 0xEF, 0x01}}, + {x64.R8, 1, []byte{0x49, 0x83, 0xE8, 0x01}}, + {x64.R9, 1, []byte{0x49, 0x83, 0xE9, 0x01}}, + {x64.R10, 1, []byte{0x49, 0x83, 0xEA, 0x01}}, + {x64.R11, 1, []byte{0x49, 0x83, 0xEB, 0x01}}, + {x64.R12, 1, []byte{0x49, 0x83, 0xEC, 0x01}}, + {x64.R13, 1, []byte{0x49, 0x83, 0xED, 0x01}}, + {x64.R14, 1, []byte{0x49, 0x83, 0xEE, 0x01}}, + {x64.R15, 1, []byte{0x49, 0x83, 0xEF, 0x01}}, + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("sub %s, %x", pattern.Register, pattern.Number) + code := x64.SubRegNum(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} From 862598cfc18d91743e8e5b833493906abfd6ce68 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 24 Jun 2024 17:51:24 +0200 Subject: [PATCH 0128/1012] Added more tests --- src/build/arch/x64/Add.go | 10 +++++ src/build/arch/x64/Add_test.go | 56 ++++++++++++++++++++++++++ src/build/arch/x64/Registers.go | 40 +++++++++--------- src/build/arch/x64/{Math.go => Sub.go} | 5 --- src/build/arch/x64/Sub_test.go | 56 ++++++++++++++++++++++++++ 5 files changed, 142 insertions(+), 25 deletions(-) create mode 100644 src/build/arch/x64/Add.go create mode 100644 src/build/arch/x64/Add_test.go rename src/build/arch/x64/{Math.go => Sub.go} (57%) create mode 100644 src/build/arch/x64/Sub_test.go diff --git a/src/build/arch/x64/Add.go b/src/build/arch/x64/Add.go new file mode 100644 index 0000000..d394968 --- /dev/null +++ b/src/build/arch/x64/Add.go @@ -0,0 +1,10 @@ +package x64 + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// AddRegNum adds a number to the given register. +func AddRegNum(code []byte, destination cpu.Register, number int) []byte { + return numberToRegister(0x83, 0x81, 0b000, code, destination, number) +} diff --git a/src/build/arch/x64/Add_test.go b/src/build/arch/x64/Add_test.go new file mode 100644 index 0000000..221dbbe --- /dev/null +++ b/src/build/arch/x64/Add_test.go @@ -0,0 +1,56 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestAddRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0x83, 0xc0, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0x83, 0xc1, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0x83, 0xc2, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0x83, 0xc3, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0x83, 0xc4, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0x83, 0xc5, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0x83, 0xc6, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0x83, 0xc7, 0x01}}, + {x64.R8, 1, []byte{0x49, 0x83, 0xc0, 0x01}}, + {x64.R9, 1, []byte{0x49, 0x83, 0xc1, 0x01}}, + {x64.R10, 1, []byte{0x49, 0x83, 0xc2, 0x01}}, + {x64.R11, 1, []byte{0x49, 0x83, 0xc3, 0x01}}, + {x64.R12, 1, []byte{0x49, 0x83, 0xc4, 0x01}}, + {x64.R13, 1, []byte{0x49, 0x83, 0xc5, 0x01}}, + {x64.R14, 1, []byte{0x49, 0x83, 0xc6, 0x01}}, + {x64.R15, 1, []byte{0x49, 0x83, 0xc7, 0x01}}, + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("add %s, %x", pattern.Register, pattern.Number) + code := x64.AddRegNum(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go index 7aaa3c8..ac47f67 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/build/arch/x64/Registers.go @@ -3,26 +3,26 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" const ( - rax = iota - rcx - rdx - rbx - rsp - rbp - rsi - rdi - r8 - r9 - r10 - r11 - r12 - r13 - r14 - r15 + RAX = iota + RCX + RDX + RBX + RSP + RBP + RSI + RDI + R8 + R9 + R10 + R11 + R12 + R13 + R14 + R15 ) -const SyscallReturn = rax +const SyscallReturn = RAX -var GeneralRegisters = []cpu.Register{rbx, rbp, r12, r13, r14, r15} -var SyscallRegisters = []cpu.Register{rax, rdi, rsi, rdx, r10, r8, r9} -var ReturnValueRegisters = []cpu.Register{rax, rcx, r11} +var GeneralRegisters = []cpu.Register{RBX, RBP, R12, R13, R14, R15} +var SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} +var ReturnValueRegisters = []cpu.Register{RAX, RCX, R11} diff --git a/src/build/arch/x64/Math.go b/src/build/arch/x64/Sub.go similarity index 57% rename from src/build/arch/x64/Math.go rename to src/build/arch/x64/Sub.go index 92a7137..af9511c 100644 --- a/src/build/arch/x64/Math.go +++ b/src/build/arch/x64/Sub.go @@ -4,11 +4,6 @@ import ( "git.akyoto.dev/cli/q/src/build/cpu" ) -// AddRegNum adds a number to the given register. -func AddRegNum(code []byte, destination cpu.Register, number int) []byte { - return numberToRegister(0x83, 0x81, 0b000, code, destination, number) -} - // SubRegNum subtracts a number from the given register. func SubRegNum(code []byte, destination cpu.Register, number int) []byte { return numberToRegister(0x83, 0x81, 0b101, code, destination, number) diff --git a/src/build/arch/x64/Sub_test.go b/src/build/arch/x64/Sub_test.go new file mode 100644 index 0000000..a0b0b10 --- /dev/null +++ b/src/build/arch/x64/Sub_test.go @@ -0,0 +1,56 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestSubRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0x83, 0xE8, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0x83, 0xE9, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0x83, 0xEA, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0x83, 0xEB, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0x83, 0xEC, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0x83, 0xED, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0x83, 0xEE, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0x83, 0xEF, 0x01}}, + {x64.R8, 1, []byte{0x49, 0x83, 0xE8, 0x01}}, + {x64.R9, 1, []byte{0x49, 0x83, 0xE9, 0x01}}, + {x64.R10, 1, []byte{0x49, 0x83, 0xEA, 0x01}}, + {x64.R11, 1, []byte{0x49, 0x83, 0xEB, 0x01}}, + {x64.R12, 1, []byte{0x49, 0x83, 0xEC, 0x01}}, + {x64.R13, 1, []byte{0x49, 0x83, 0xED, 0x01}}, + {x64.R14, 1, []byte{0x49, 0x83, 0xEE, 0x01}}, + {x64.R15, 1, []byte{0x49, 0x83, 0xEF, 0x01}}, + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("sub %s, %x", pattern.Register, pattern.Number) + code := x64.SubRegNum(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} From 81fcb50e7737f92f1d43755157fba824312beeaf Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 24 Jun 2024 21:54:03 +0200 Subject: [PATCH 0129/1012] Improved x64 encoder --- src/build/Function.go | 12 +++++----- src/build/arch/x64/Add.go | 2 +- src/build/arch/x64/Sub.go | 2 +- src/build/arch/x64/numberToRegister.go | 33 ++++++++++++++------------ src/build/arch/x64/sizeOf.go | 20 ++++++++++++++++ 5 files changed, 46 insertions(+), 23 deletions(-) create mode 100644 src/build/arch/x64/sizeOf.go diff --git a/src/build/Function.go b/src/build/Function.go index 58e7cee..cc86f47 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -114,17 +114,17 @@ func (f *Function) CompileInstruction(line token.List) error { } if expr.Token.Kind == token.Operator { + name := expr.Children[0].Token.Text() + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) + register := f.Variables[name].Register + switch expr.Token.Text() { case "+=": - name := expr.Children[0].Token.Text() - number, _ := strconv.Atoi(expr.Children[1].Token.Text()) - f.Assembler.AddRegisterNumber(f.Variables[name].Register, number) + f.Assembler.AddRegisterNumber(register, number) return nil case "-=": - name := expr.Children[0].Token.Text() - number, _ := strconv.Atoi(expr.Children[1].Token.Text()) - f.Assembler.SubRegisterNumber(f.Variables[name].Register, number) + f.Assembler.SubRegisterNumber(register, number) return nil } } diff --git a/src/build/arch/x64/Add.go b/src/build/arch/x64/Add.go index d394968..f67094d 100644 --- a/src/build/arch/x64/Add.go +++ b/src/build/arch/x64/Add.go @@ -6,5 +6,5 @@ import ( // AddRegNum adds a number to the given register. func AddRegNum(code []byte, destination cpu.Register, number int) []byte { - return numberToRegister(0x83, 0x81, 0b000, code, destination, number) + return numRegReg(code, 0x83, 0x81, 0, byte(destination), number) } diff --git a/src/build/arch/x64/Sub.go b/src/build/arch/x64/Sub.go index af9511c..f2d5e9b 100644 --- a/src/build/arch/x64/Sub.go +++ b/src/build/arch/x64/Sub.go @@ -6,5 +6,5 @@ import ( // SubRegNum subtracts a number from the given register. func SubRegNum(code []byte, destination cpu.Register, number int) []byte { - return numberToRegister(0x83, 0x81, 0b101, code, destination, number) + return numRegReg(code, 0x83, 0x81, 0b101, byte(destination), number) } diff --git a/src/build/arch/x64/numberToRegister.go b/src/build/arch/x64/numberToRegister.go index 025a2ec..c67889e 100644 --- a/src/build/arch/x64/numberToRegister.go +++ b/src/build/arch/x64/numberToRegister.go @@ -1,24 +1,27 @@ package x64 -import ( - "math" +// numRegReg encodes an instruction with up to two registers and a number parameter. +func numRegReg(code []byte, opCode8 byte, opCode32 byte, reg byte, rm byte, number int) []byte { + w := byte(1) // Indicates a 64-bit register. + r := byte(0) // Extension to the "reg" field in ModRM. + x := byte(0) // Extension to the SIB index field. + b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). + mod := byte(0b11) // Direct addressing mode, no register offsets. - "git.akyoto.dev/cli/q/src/build/cpu" -) - -// numberToRegister encodes an instruction with a register and a number parameter. -func numberToRegister(opCode8 byte, opCode32 byte, reg byte, code []byte, destination cpu.Register, number int) []byte { - b := byte(0) - - if destination >= 8 { - b = 1 - destination -= 8 + if reg > 0b111 { + r = 1 + reg &= 0b111 } - rex := REX(1, 0, 0, b) - modRM := ModRM(0b11, reg, byte(destination)) + if rm > 0b111 { + b = 1 + rm &= 0b111 + } - if number >= math.MinInt8 && number <= math.MaxInt8 { + rex := REX(w, r, x, b) + modRM := ModRM(mod, reg, rm) + + if sizeOf(number) == 1 { return append( code, rex, diff --git a/src/build/arch/x64/sizeOf.go b/src/build/arch/x64/sizeOf.go new file mode 100644 index 0000000..8e6533b --- /dev/null +++ b/src/build/arch/x64/sizeOf.go @@ -0,0 +1,20 @@ +package x64 + +import "math" + +// sizeOf tells you how many bytes are needed to encode this number. +func sizeOf(number int) int { + switch { + case number >= math.MinInt8 && number <= math.MaxInt8: + return 1 + + case number >= math.MinInt16 && number <= math.MaxInt16: + return 2 + + case number >= math.MinInt32 && number <= math.MaxInt32: + return 4 + + default: + return 8 + } +} From 625e8d5e2e79502a5bf2e2e8cd444c53165115ec Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 24 Jun 2024 21:54:03 +0200 Subject: [PATCH 0130/1012] Improved x64 encoder --- src/build/Function.go | 12 +++++----- src/build/arch/x64/Add.go | 2 +- src/build/arch/x64/Sub.go | 2 +- src/build/arch/x64/numberToRegister.go | 33 ++++++++++++++------------ src/build/arch/x64/sizeOf.go | 20 ++++++++++++++++ 5 files changed, 46 insertions(+), 23 deletions(-) create mode 100644 src/build/arch/x64/sizeOf.go diff --git a/src/build/Function.go b/src/build/Function.go index 58e7cee..cc86f47 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -114,17 +114,17 @@ func (f *Function) CompileInstruction(line token.List) error { } if expr.Token.Kind == token.Operator { + name := expr.Children[0].Token.Text() + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) + register := f.Variables[name].Register + switch expr.Token.Text() { case "+=": - name := expr.Children[0].Token.Text() - number, _ := strconv.Atoi(expr.Children[1].Token.Text()) - f.Assembler.AddRegisterNumber(f.Variables[name].Register, number) + f.Assembler.AddRegisterNumber(register, number) return nil case "-=": - name := expr.Children[0].Token.Text() - number, _ := strconv.Atoi(expr.Children[1].Token.Text()) - f.Assembler.SubRegisterNumber(f.Variables[name].Register, number) + f.Assembler.SubRegisterNumber(register, number) return nil } } diff --git a/src/build/arch/x64/Add.go b/src/build/arch/x64/Add.go index d394968..f67094d 100644 --- a/src/build/arch/x64/Add.go +++ b/src/build/arch/x64/Add.go @@ -6,5 +6,5 @@ import ( // AddRegNum adds a number to the given register. func AddRegNum(code []byte, destination cpu.Register, number int) []byte { - return numberToRegister(0x83, 0x81, 0b000, code, destination, number) + return numRegReg(code, 0x83, 0x81, 0, byte(destination), number) } diff --git a/src/build/arch/x64/Sub.go b/src/build/arch/x64/Sub.go index af9511c..f2d5e9b 100644 --- a/src/build/arch/x64/Sub.go +++ b/src/build/arch/x64/Sub.go @@ -6,5 +6,5 @@ import ( // SubRegNum subtracts a number from the given register. func SubRegNum(code []byte, destination cpu.Register, number int) []byte { - return numberToRegister(0x83, 0x81, 0b101, code, destination, number) + return numRegReg(code, 0x83, 0x81, 0b101, byte(destination), number) } diff --git a/src/build/arch/x64/numberToRegister.go b/src/build/arch/x64/numberToRegister.go index 025a2ec..c67889e 100644 --- a/src/build/arch/x64/numberToRegister.go +++ b/src/build/arch/x64/numberToRegister.go @@ -1,24 +1,27 @@ package x64 -import ( - "math" +// numRegReg encodes an instruction with up to two registers and a number parameter. +func numRegReg(code []byte, opCode8 byte, opCode32 byte, reg byte, rm byte, number int) []byte { + w := byte(1) // Indicates a 64-bit register. + r := byte(0) // Extension to the "reg" field in ModRM. + x := byte(0) // Extension to the SIB index field. + b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). + mod := byte(0b11) // Direct addressing mode, no register offsets. - "git.akyoto.dev/cli/q/src/build/cpu" -) - -// numberToRegister encodes an instruction with a register and a number parameter. -func numberToRegister(opCode8 byte, opCode32 byte, reg byte, code []byte, destination cpu.Register, number int) []byte { - b := byte(0) - - if destination >= 8 { - b = 1 - destination -= 8 + if reg > 0b111 { + r = 1 + reg &= 0b111 } - rex := REX(1, 0, 0, b) - modRM := ModRM(0b11, reg, byte(destination)) + if rm > 0b111 { + b = 1 + rm &= 0b111 + } - if number >= math.MinInt8 && number <= math.MaxInt8 { + rex := REX(w, r, x, b) + modRM := ModRM(mod, reg, rm) + + if sizeOf(number) == 1 { return append( code, rex, diff --git a/src/build/arch/x64/sizeOf.go b/src/build/arch/x64/sizeOf.go new file mode 100644 index 0000000..8e6533b --- /dev/null +++ b/src/build/arch/x64/sizeOf.go @@ -0,0 +1,20 @@ +package x64 + +import "math" + +// sizeOf tells you how many bytes are needed to encode this number. +func sizeOf(number int) int { + switch { + case number >= math.MinInt8 && number <= math.MaxInt8: + return 1 + + case number >= math.MinInt16 && number <= math.MaxInt16: + return 2 + + case number >= math.MinInt32 && number <= math.MaxInt32: + return 4 + + default: + return 8 + } +} From 432397043d113b9e3c147bcd9c1ca22052b6a62c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 24 Jun 2024 22:43:01 +0200 Subject: [PATCH 0131/1012] Implemented multiplication --- examples/hello/hello.q | 3 +- src/build/Finalize.go | 4 +- src/build/Function.go | 10 +++- src/build/arch/x64/Add_test.go | 33 +++++------ src/build/arch/x64/Mul.go | 8 +++ src/build/arch/x64/Mul_test.go | 57 +++++++++++++++++++ src/build/arch/x64/Sub_test.go | 1 + .../x64/{numberToRegister.go => numRegReg.go} | 0 src/build/asm/Assembler.go | 6 ++ src/build/asm/Instructions.go | 28 +-------- src/build/asm/Mnemonic.go | 4 ++ 11 files changed, 107 insertions(+), 47 deletions(-) create mode 100644 src/build/arch/x64/Mul.go create mode 100644 src/build/arch/x64/Mul_test.go rename src/build/arch/x64/{numberToRegister.go => numRegReg.go} (100%) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index c5324ab..4f30709 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -11,7 +11,8 @@ hello() { address += 4194304 address += 1 length += 5 - length -= 2 + length *= 2 + length -= 7 loop { syscall(write, stdout, address, length) diff --git a/src/build/Finalize.go b/src/build/Finalize.go index 915b42b..dadf7f6 100644 --- a/src/build/Finalize.go +++ b/src/build/Finalize.go @@ -29,8 +29,8 @@ func Finalize(functions map[string]*Function) ([]byte, []byte) { func entry() *asm.Assembler { entry := asm.New() entry.Call("main") - entry.MoveRegisterNumber(x64.SyscallRegisters[0], linux.Exit) - entry.MoveRegisterNumber(x64.SyscallRegisters[1], 0) + entry.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit) + entry.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0) entry.Syscall() return entry } diff --git a/src/build/Function.go b/src/build/Function.go index cc86f47..f0298e7 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -120,11 +120,15 @@ func (f *Function) CompileInstruction(line token.List) error { switch expr.Token.Text() { case "+=": - f.Assembler.AddRegisterNumber(register, number) + f.Assembler.RegisterNumber(asm.ADD, register, number) return nil case "-=": - f.Assembler.SubRegisterNumber(register, number) + f.Assembler.RegisterNumber(asm.SUB, register, number) + return nil + + case "*=": + f.Assembler.RegisterNumber(asm.MUL, register, number) return nil } } @@ -164,7 +168,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return err } - f.Assembler.MoveRegisterNumber(register, n) + f.Assembler.RegisterNumber(asm.MOVE, register, n) return nil case token.String: diff --git a/src/build/arch/x64/Add_test.go b/src/build/arch/x64/Add_test.go index 221dbbe..80abc27 100644 --- a/src/build/arch/x64/Add_test.go +++ b/src/build/arch/x64/Add_test.go @@ -14,22 +14,23 @@ func TestAddRegisterNumber(t *testing.T) { Number int Code []byte }{ - {x64.RAX, 1, []byte{0x48, 0x83, 0xc0, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0x83, 0xc1, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0x83, 0xc2, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0x83, 0xc3, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0x83, 0xc4, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0x83, 0xc5, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0x83, 0xc6, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0x83, 0xc7, 0x01}}, - {x64.R8, 1, []byte{0x49, 0x83, 0xc0, 0x01}}, - {x64.R9, 1, []byte{0x49, 0x83, 0xc1, 0x01}}, - {x64.R10, 1, []byte{0x49, 0x83, 0xc2, 0x01}}, - {x64.R11, 1, []byte{0x49, 0x83, 0xc3, 0x01}}, - {x64.R12, 1, []byte{0x49, 0x83, 0xc4, 0x01}}, - {x64.R13, 1, []byte{0x49, 0x83, 0xc5, 0x01}}, - {x64.R14, 1, []byte{0x49, 0x83, 0xc6, 0x01}}, - {x64.R15, 1, []byte{0x49, 0x83, 0xc7, 0x01}}, + {x64.RAX, 1, []byte{0x48, 0x83, 0xC0, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0x83, 0xC1, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0x83, 0xC2, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0x83, 0xC3, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0x83, 0xC4, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0x83, 0xC5, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0x83, 0xC6, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0x83, 0xC7, 0x01}}, + {x64.R8, 1, []byte{0x49, 0x83, 0xC0, 0x01}}, + {x64.R9, 1, []byte{0x49, 0x83, 0xC1, 0x01}}, + {x64.R10, 1, []byte{0x49, 0x83, 0xC2, 0x01}}, + {x64.R11, 1, []byte{0x49, 0x83, 0xC3, 0x01}}, + {x64.R12, 1, []byte{0x49, 0x83, 0xC4, 0x01}}, + {x64.R13, 1, []byte{0x49, 0x83, 0xC5, 0x01}}, + {x64.R14, 1, []byte{0x49, 0x83, 0xC6, 0x01}}, + {x64.R15, 1, []byte{0x49, 0x83, 0xC7, 0x01}}, + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, diff --git a/src/build/arch/x64/Mul.go b/src/build/arch/x64/Mul.go new file mode 100644 index 0000000..d952b2b --- /dev/null +++ b/src/build/arch/x64/Mul.go @@ -0,0 +1,8 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// MulRegNum multiplies a register with a number. +func MulRegNum(code []byte, destination cpu.Register, number int) []byte { + return numRegReg(code, 0x6B, 0x69, byte(destination), byte(destination), number) +} diff --git a/src/build/arch/x64/Mul_test.go b/src/build/arch/x64/Mul_test.go new file mode 100644 index 0000000..1a81fd3 --- /dev/null +++ b/src/build/arch/x64/Mul_test.go @@ -0,0 +1,57 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestMulRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0x6B, 0xC0, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0x6B, 0xC9, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0x6B, 0xD2, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0x6B, 0xDB, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0x6B, 0xE4, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0x6B, 0xED, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0x6B, 0xF6, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0x6B, 0xFF, 0x01}}, + {x64.R8, 1, []byte{0x4D, 0x6B, 0xC0, 0x01}}, + {x64.R9, 1, []byte{0x4D, 0x6B, 0xC9, 0x01}}, + {x64.R10, 1, []byte{0x4D, 0x6B, 0xD2, 0x01}}, + {x64.R11, 1, []byte{0x4D, 0x6B, 0xDB, 0x01}}, + {x64.R12, 1, []byte{0x4D, 0x6B, 0xE4, 0x01}}, + {x64.R13, 1, []byte{0x4D, 0x6B, 0xED, 0x01}}, + {x64.R14, 1, []byte{0x4D, 0x6B, 0xF6, 0x01}}, + {x64.R15, 1, []byte{0x4D, 0x6B, 0xFF, 0x01}}, + + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mul %s, %x", pattern.Register, pattern.Number) + code := x64.MulRegNum(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Sub_test.go b/src/build/arch/x64/Sub_test.go index a0b0b10..5dcf691 100644 --- a/src/build/arch/x64/Sub_test.go +++ b/src/build/arch/x64/Sub_test.go @@ -30,6 +30,7 @@ func TestSubRegisterNumber(t *testing.T) { {x64.R13, 1, []byte{0x49, 0x83, 0xED, 0x01}}, {x64.R14, 1, []byte{0x49, 0x83, 0xEE, 0x01}}, {x64.R15, 1, []byte{0x49, 0x83, 0xEF, 0x01}}, + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, diff --git a/src/build/arch/x64/numberToRegister.go b/src/build/arch/x64/numRegReg.go similarity index 100% rename from src/build/arch/x64/numberToRegister.go rename to src/build/arch/x64/numRegReg.go diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 6dbb8bd..959aea2 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -39,6 +39,12 @@ func (a *Assembler) Finalize() ([]byte, []byte) { code = x64.SubRegNum(code, operands.Register, operands.Number) } + case MUL: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.MulRegNum(code, operands.Register, operands.Number) + } + case CALL: code = x64.Call(code, 0x00_00_00_00) size := 4 diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index efd6920..7eb041f 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -2,32 +2,10 @@ package asm import "git.akyoto.dev/cli/q/src/build/cpu" -// AddRegisterNumber adds a number to the given register. -func (a *Assembler) AddRegisterNumber(reg cpu.Register, number int) { +// RegisterNumber adds an instruction with a register and a number. +func (a *Assembler) RegisterNumber(mnemonic Mnemonic, reg cpu.Register, number int) { a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: ADD, - Data: &RegisterNumber{ - Register: reg, - Number: number, - }, - }) -} - -// SubRegisterNumber subtracts a number from the given register. -func (a *Assembler) SubRegisterNumber(reg cpu.Register, number int) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: SUB, - Data: &RegisterNumber{ - Register: reg, - Number: number, - }, - }) -} - -// MoveRegisterNumber moves a number into the given register. -func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number int) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: MOVE, + Mnemonic: mnemonic, Data: &RegisterNumber{ Register: reg, Number: number, diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index c5309eb..6b97585 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -7,6 +7,7 @@ const ( ADD CALL JUMP + MUL LABEL MOVE RETURN @@ -32,6 +33,9 @@ func (m Mnemonic) String() string { case MOVE: return "move" + case MUL: + return "mul" + case RETURN: return "return" From 34af20d7a011f09aea7e394691f2d46a40ceeb9e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 24 Jun 2024 22:43:01 +0200 Subject: [PATCH 0132/1012] Implemented multiplication --- examples/hello/hello.q | 3 +- src/build/Finalize.go | 4 +- src/build/Function.go | 10 +++- src/build/arch/x64/Add_test.go | 33 +++++------ src/build/arch/x64/Mul.go | 8 +++ src/build/arch/x64/Mul_test.go | 57 +++++++++++++++++++ src/build/arch/x64/Sub_test.go | 1 + .../x64/{numberToRegister.go => numRegReg.go} | 0 src/build/asm/Assembler.go | 6 ++ src/build/asm/Instructions.go | 28 +-------- src/build/asm/Mnemonic.go | 4 ++ 11 files changed, 107 insertions(+), 47 deletions(-) create mode 100644 src/build/arch/x64/Mul.go create mode 100644 src/build/arch/x64/Mul_test.go rename src/build/arch/x64/{numberToRegister.go => numRegReg.go} (100%) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index c5324ab..4f30709 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -11,7 +11,8 @@ hello() { address += 4194304 address += 1 length += 5 - length -= 2 + length *= 2 + length -= 7 loop { syscall(write, stdout, address, length) diff --git a/src/build/Finalize.go b/src/build/Finalize.go index 915b42b..dadf7f6 100644 --- a/src/build/Finalize.go +++ b/src/build/Finalize.go @@ -29,8 +29,8 @@ func Finalize(functions map[string]*Function) ([]byte, []byte) { func entry() *asm.Assembler { entry := asm.New() entry.Call("main") - entry.MoveRegisterNumber(x64.SyscallRegisters[0], linux.Exit) - entry.MoveRegisterNumber(x64.SyscallRegisters[1], 0) + entry.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit) + entry.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0) entry.Syscall() return entry } diff --git a/src/build/Function.go b/src/build/Function.go index cc86f47..f0298e7 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -120,11 +120,15 @@ func (f *Function) CompileInstruction(line token.List) error { switch expr.Token.Text() { case "+=": - f.Assembler.AddRegisterNumber(register, number) + f.Assembler.RegisterNumber(asm.ADD, register, number) return nil case "-=": - f.Assembler.SubRegisterNumber(register, number) + f.Assembler.RegisterNumber(asm.SUB, register, number) + return nil + + case "*=": + f.Assembler.RegisterNumber(asm.MUL, register, number) return nil } } @@ -164,7 +168,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return err } - f.Assembler.MoveRegisterNumber(register, n) + f.Assembler.RegisterNumber(asm.MOVE, register, n) return nil case token.String: diff --git a/src/build/arch/x64/Add_test.go b/src/build/arch/x64/Add_test.go index 221dbbe..80abc27 100644 --- a/src/build/arch/x64/Add_test.go +++ b/src/build/arch/x64/Add_test.go @@ -14,22 +14,23 @@ func TestAddRegisterNumber(t *testing.T) { Number int Code []byte }{ - {x64.RAX, 1, []byte{0x48, 0x83, 0xc0, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0x83, 0xc1, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0x83, 0xc2, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0x83, 0xc3, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0x83, 0xc4, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0x83, 0xc5, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0x83, 0xc6, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0x83, 0xc7, 0x01}}, - {x64.R8, 1, []byte{0x49, 0x83, 0xc0, 0x01}}, - {x64.R9, 1, []byte{0x49, 0x83, 0xc1, 0x01}}, - {x64.R10, 1, []byte{0x49, 0x83, 0xc2, 0x01}}, - {x64.R11, 1, []byte{0x49, 0x83, 0xc3, 0x01}}, - {x64.R12, 1, []byte{0x49, 0x83, 0xc4, 0x01}}, - {x64.R13, 1, []byte{0x49, 0x83, 0xc5, 0x01}}, - {x64.R14, 1, []byte{0x49, 0x83, 0xc6, 0x01}}, - {x64.R15, 1, []byte{0x49, 0x83, 0xc7, 0x01}}, + {x64.RAX, 1, []byte{0x48, 0x83, 0xC0, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0x83, 0xC1, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0x83, 0xC2, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0x83, 0xC3, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0x83, 0xC4, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0x83, 0xC5, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0x83, 0xC6, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0x83, 0xC7, 0x01}}, + {x64.R8, 1, []byte{0x49, 0x83, 0xC0, 0x01}}, + {x64.R9, 1, []byte{0x49, 0x83, 0xC1, 0x01}}, + {x64.R10, 1, []byte{0x49, 0x83, 0xC2, 0x01}}, + {x64.R11, 1, []byte{0x49, 0x83, 0xC3, 0x01}}, + {x64.R12, 1, []byte{0x49, 0x83, 0xC4, 0x01}}, + {x64.R13, 1, []byte{0x49, 0x83, 0xC5, 0x01}}, + {x64.R14, 1, []byte{0x49, 0x83, 0xC6, 0x01}}, + {x64.R15, 1, []byte{0x49, 0x83, 0xC7, 0x01}}, + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, diff --git a/src/build/arch/x64/Mul.go b/src/build/arch/x64/Mul.go new file mode 100644 index 0000000..d952b2b --- /dev/null +++ b/src/build/arch/x64/Mul.go @@ -0,0 +1,8 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// MulRegNum multiplies a register with a number. +func MulRegNum(code []byte, destination cpu.Register, number int) []byte { + return numRegReg(code, 0x6B, 0x69, byte(destination), byte(destination), number) +} diff --git a/src/build/arch/x64/Mul_test.go b/src/build/arch/x64/Mul_test.go new file mode 100644 index 0000000..1a81fd3 --- /dev/null +++ b/src/build/arch/x64/Mul_test.go @@ -0,0 +1,57 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestMulRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0x6B, 0xC0, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0x6B, 0xC9, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0x6B, 0xD2, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0x6B, 0xDB, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0x6B, 0xE4, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0x6B, 0xED, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0x6B, 0xF6, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0x6B, 0xFF, 0x01}}, + {x64.R8, 1, []byte{0x4D, 0x6B, 0xC0, 0x01}}, + {x64.R9, 1, []byte{0x4D, 0x6B, 0xC9, 0x01}}, + {x64.R10, 1, []byte{0x4D, 0x6B, 0xD2, 0x01}}, + {x64.R11, 1, []byte{0x4D, 0x6B, 0xDB, 0x01}}, + {x64.R12, 1, []byte{0x4D, 0x6B, 0xE4, 0x01}}, + {x64.R13, 1, []byte{0x4D, 0x6B, 0xED, 0x01}}, + {x64.R14, 1, []byte{0x4D, 0x6B, 0xF6, 0x01}}, + {x64.R15, 1, []byte{0x4D, 0x6B, 0xFF, 0x01}}, + + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mul %s, %x", pattern.Register, pattern.Number) + code := x64.MulRegNum(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Sub_test.go b/src/build/arch/x64/Sub_test.go index a0b0b10..5dcf691 100644 --- a/src/build/arch/x64/Sub_test.go +++ b/src/build/arch/x64/Sub_test.go @@ -30,6 +30,7 @@ func TestSubRegisterNumber(t *testing.T) { {x64.R13, 1, []byte{0x49, 0x83, 0xED, 0x01}}, {x64.R14, 1, []byte{0x49, 0x83, 0xEE, 0x01}}, {x64.R15, 1, []byte{0x49, 0x83, 0xEF, 0x01}}, + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, diff --git a/src/build/arch/x64/numberToRegister.go b/src/build/arch/x64/numRegReg.go similarity index 100% rename from src/build/arch/x64/numberToRegister.go rename to src/build/arch/x64/numRegReg.go diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 6dbb8bd..959aea2 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -39,6 +39,12 @@ func (a *Assembler) Finalize() ([]byte, []byte) { code = x64.SubRegNum(code, operands.Register, operands.Number) } + case MUL: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.MulRegNum(code, operands.Register, operands.Number) + } + case CALL: code = x64.Call(code, 0x00_00_00_00) size := 4 diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index efd6920..7eb041f 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -2,32 +2,10 @@ package asm import "git.akyoto.dev/cli/q/src/build/cpu" -// AddRegisterNumber adds a number to the given register. -func (a *Assembler) AddRegisterNumber(reg cpu.Register, number int) { +// RegisterNumber adds an instruction with a register and a number. +func (a *Assembler) RegisterNumber(mnemonic Mnemonic, reg cpu.Register, number int) { a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: ADD, - Data: &RegisterNumber{ - Register: reg, - Number: number, - }, - }) -} - -// SubRegisterNumber subtracts a number from the given register. -func (a *Assembler) SubRegisterNumber(reg cpu.Register, number int) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: SUB, - Data: &RegisterNumber{ - Register: reg, - Number: number, - }, - }) -} - -// MoveRegisterNumber moves a number into the given register. -func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number int) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: MOVE, + Mnemonic: mnemonic, Data: &RegisterNumber{ Register: reg, Number: number, diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index c5309eb..6b97585 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -7,6 +7,7 @@ const ( ADD CALL JUMP + MUL LABEL MOVE RETURN @@ -32,6 +33,9 @@ func (m Mnemonic) String() string { case MOVE: return "move" + case MUL: + return "mul" + case RETURN: return "return" From 3aedcef9eb9397a77f6b3d8fd5db500f9af940ce Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 25 Jun 2024 00:19:01 +0200 Subject: [PATCH 0133/1012] Implemented division --- examples/hello/hello.q | 8 +++--- src/build/Function.go | 4 +++ src/build/arch/x64/Div.go | 20 ++++++++++++++ src/build/arch/x64/Div_test.go | 39 ++++++++++++++++++++++++++++ src/build/arch/x64/ExtendRAXToRDX.go | 7 +++++ src/build/asm/Assembler.go | 10 +++++++ src/build/asm/Mnemonic.go | 4 +++ 7 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 src/build/arch/x64/Div.go create mode 100644 src/build/arch/x64/Div_test.go create mode 100644 src/build/arch/x64/ExtendRAXToRDX.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 4f30709..1a3745e 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -10,9 +10,11 @@ hello() { address += 4194304 address += 1 - length += 5 - length *= 2 - length -= 7 + + length += 50 + length -= 20 + length *= 10 + length /= 100 loop { syscall(write, stdout, address, length) diff --git a/src/build/Function.go b/src/build/Function.go index f0298e7..141c210 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -130,6 +130,10 @@ func (f *Function) CompileInstruction(line token.List) error { case "*=": f.Assembler.RegisterNumber(asm.MUL, register, number) return nil + + case "/=": + f.Assembler.RegisterNumber(asm.DIV, register, number) + return nil } } diff --git a/src/build/arch/x64/Div.go b/src/build/arch/x64/Div.go new file mode 100644 index 0000000..0ed4a56 --- /dev/null +++ b/src/build/arch/x64/Div.go @@ -0,0 +1,20 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// DivReg divides RDX:RAX by the value in the register. +func DivReg(code []byte, divisor cpu.Register) []byte { + rex := byte(0x48) + + if divisor >= 8 { + rex++ + divisor -= 8 + } + + return append( + code, + rex, + 0xF7, + 0xF8+byte(divisor), + ) +} diff --git a/src/build/arch/x64/Div_test.go b/src/build/arch/x64/Div_test.go new file mode 100644 index 0000000..04ed063 --- /dev/null +++ b/src/build/arch/x64/Div_test.go @@ -0,0 +1,39 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestDivRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x64.RAX, []byte{0x48, 0xF7, 0xF8}}, + {x64.RCX, []byte{0x48, 0xF7, 0xF9}}, + {x64.RDX, []byte{0x48, 0xF7, 0xFA}}, + {x64.RBX, []byte{0x48, 0xF7, 0xFB}}, + {x64.RSP, []byte{0x48, 0xF7, 0xFC}}, + {x64.RBP, []byte{0x48, 0xF7, 0xFD}}, + {x64.RSI, []byte{0x48, 0xF7, 0xFE}}, + {x64.RDI, []byte{0x48, 0xF7, 0xFF}}, + {x64.R8, []byte{0x49, 0xF7, 0xF8}}, + {x64.R9, []byte{0x49, 0xF7, 0xF9}}, + {x64.R10, []byte{0x49, 0xF7, 0xFA}}, + {x64.R11, []byte{0x49, 0xF7, 0xFB}}, + {x64.R12, []byte{0x49, 0xF7, 0xFC}}, + {x64.R13, []byte{0x49, 0xF7, 0xFD}}, + {x64.R14, []byte{0x49, 0xF7, 0xFE}}, + {x64.R15, []byte{0x49, 0xF7, 0xFF}}, + } + + for _, pattern := range usagePatterns { + t.Logf("idiv %s", pattern.Register) + code := x64.DivReg(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/ExtendRAXToRDX.go b/src/build/arch/x64/ExtendRAXToRDX.go new file mode 100644 index 0000000..2f03963 --- /dev/null +++ b/src/build/arch/x64/ExtendRAXToRDX.go @@ -0,0 +1,7 @@ +package x64 + +// ExtendRAXToRDX doubles the size of RAX by sign-extending it to RDX. +// This is also known as CQO. +func ExtendRAXToRDX(code []byte) []byte { + return append(code, 0x48, 0x99) +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 959aea2..fb93ed7 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -45,6 +45,16 @@ func (a *Assembler) Finalize() ([]byte, []byte) { code = x64.MulRegNum(code, operands.Register, operands.Number) } + case DIV: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.MoveRegReg64(code, x64.RAX, operands.Register) + code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number)) + code = x64.ExtendRAXToRDX(code) + code = x64.DivReg(code, operands.Register) + code = x64.MoveRegReg64(code, operands.Register, x64.RAX) + } + case CALL: code = x64.Call(code, 0x00_00_00_00) size := 4 diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 6b97585..95fc2a2 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -6,6 +6,7 @@ const ( NONE Mnemonic = iota ADD CALL + DIV JUMP MUL LABEL @@ -24,6 +25,9 @@ func (m Mnemonic) String() string { case CALL: return "call" + case DIV: + return "div" + case JUMP: return "jump" From 2cbc064f1423bc631269043fc12aeb15485c302d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 25 Jun 2024 00:19:01 +0200 Subject: [PATCH 0134/1012] Implemented division --- examples/hello/hello.q | 8 +++--- src/build/Function.go | 4 +++ src/build/arch/x64/Div.go | 20 ++++++++++++++ src/build/arch/x64/Div_test.go | 39 ++++++++++++++++++++++++++++ src/build/arch/x64/ExtendRAXToRDX.go | 7 +++++ src/build/asm/Assembler.go | 10 +++++++ src/build/asm/Mnemonic.go | 4 +++ 7 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 src/build/arch/x64/Div.go create mode 100644 src/build/arch/x64/Div_test.go create mode 100644 src/build/arch/x64/ExtendRAXToRDX.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 4f30709..1a3745e 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -10,9 +10,11 @@ hello() { address += 4194304 address += 1 - length += 5 - length *= 2 - length -= 7 + + length += 50 + length -= 20 + length *= 10 + length /= 100 loop { syscall(write, stdout, address, length) diff --git a/src/build/Function.go b/src/build/Function.go index f0298e7..141c210 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -130,6 +130,10 @@ func (f *Function) CompileInstruction(line token.List) error { case "*=": f.Assembler.RegisterNumber(asm.MUL, register, number) return nil + + case "/=": + f.Assembler.RegisterNumber(asm.DIV, register, number) + return nil } } diff --git a/src/build/arch/x64/Div.go b/src/build/arch/x64/Div.go new file mode 100644 index 0000000..0ed4a56 --- /dev/null +++ b/src/build/arch/x64/Div.go @@ -0,0 +1,20 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// DivReg divides RDX:RAX by the value in the register. +func DivReg(code []byte, divisor cpu.Register) []byte { + rex := byte(0x48) + + if divisor >= 8 { + rex++ + divisor -= 8 + } + + return append( + code, + rex, + 0xF7, + 0xF8+byte(divisor), + ) +} diff --git a/src/build/arch/x64/Div_test.go b/src/build/arch/x64/Div_test.go new file mode 100644 index 0000000..04ed063 --- /dev/null +++ b/src/build/arch/x64/Div_test.go @@ -0,0 +1,39 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestDivRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x64.RAX, []byte{0x48, 0xF7, 0xF8}}, + {x64.RCX, []byte{0x48, 0xF7, 0xF9}}, + {x64.RDX, []byte{0x48, 0xF7, 0xFA}}, + {x64.RBX, []byte{0x48, 0xF7, 0xFB}}, + {x64.RSP, []byte{0x48, 0xF7, 0xFC}}, + {x64.RBP, []byte{0x48, 0xF7, 0xFD}}, + {x64.RSI, []byte{0x48, 0xF7, 0xFE}}, + {x64.RDI, []byte{0x48, 0xF7, 0xFF}}, + {x64.R8, []byte{0x49, 0xF7, 0xF8}}, + {x64.R9, []byte{0x49, 0xF7, 0xF9}}, + {x64.R10, []byte{0x49, 0xF7, 0xFA}}, + {x64.R11, []byte{0x49, 0xF7, 0xFB}}, + {x64.R12, []byte{0x49, 0xF7, 0xFC}}, + {x64.R13, []byte{0x49, 0xF7, 0xFD}}, + {x64.R14, []byte{0x49, 0xF7, 0xFE}}, + {x64.R15, []byte{0x49, 0xF7, 0xFF}}, + } + + for _, pattern := range usagePatterns { + t.Logf("idiv %s", pattern.Register) + code := x64.DivReg(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/ExtendRAXToRDX.go b/src/build/arch/x64/ExtendRAXToRDX.go new file mode 100644 index 0000000..2f03963 --- /dev/null +++ b/src/build/arch/x64/ExtendRAXToRDX.go @@ -0,0 +1,7 @@ +package x64 + +// ExtendRAXToRDX doubles the size of RAX by sign-extending it to RDX. +// This is also known as CQO. +func ExtendRAXToRDX(code []byte) []byte { + return append(code, 0x48, 0x99) +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 959aea2..fb93ed7 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -45,6 +45,16 @@ func (a *Assembler) Finalize() ([]byte, []byte) { code = x64.MulRegNum(code, operands.Register, operands.Number) } + case DIV: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.MoveRegReg64(code, x64.RAX, operands.Register) + code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number)) + code = x64.ExtendRAXToRDX(code) + code = x64.DivReg(code, operands.Register) + code = x64.MoveRegReg64(code, operands.Register, x64.RAX) + } + case CALL: code = x64.Call(code, 0x00_00_00_00) size := 4 diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 6b97585..95fc2a2 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -6,6 +6,7 @@ const ( NONE Mnemonic = iota ADD CALL + DIV JUMP MUL LABEL @@ -24,6 +25,9 @@ func (m Mnemonic) String() string { case CALL: return "call" + case DIV: + return "div" + case JUMP: return "jump" From e9c46bc3cd831951deac687e27443ede1548a7a4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 25 Jun 2024 16:19:47 +0200 Subject: [PATCH 0135/1012] Implemented register push and pop --- src/build/arch/x64/Pop.go | 16 ++++++++++++++ src/build/arch/x64/Pop_test.go | 39 +++++++++++++++++++++++++++++++++ src/build/arch/x64/Push.go | 16 ++++++++++++++ src/build/arch/x64/Push_test.go | 39 +++++++++++++++++++++++++++++++++ src/build/asm/Assembler.go | 22 ++++++++++++++----- src/build/asm/RegisterNumber.go | 2 +- 6 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 src/build/arch/x64/Pop.go create mode 100644 src/build/arch/x64/Pop_test.go create mode 100644 src/build/arch/x64/Push.go create mode 100644 src/build/arch/x64/Push_test.go diff --git a/src/build/arch/x64/Pop.go b/src/build/arch/x64/Pop.go new file mode 100644 index 0000000..746f3a0 --- /dev/null +++ b/src/build/arch/x64/Pop.go @@ -0,0 +1,16 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// PopReg pops a value from the stack and saves it into the register. +func PopReg(code []byte, register cpu.Register) []byte { + if register >= 8 { + code = append(code, REX(0, 0, 0, 1)) + register -= 8 + } + + return append( + code, + 0x58+byte(register), + ) +} diff --git a/src/build/arch/x64/Pop_test.go b/src/build/arch/x64/Pop_test.go new file mode 100644 index 0000000..64acf13 --- /dev/null +++ b/src/build/arch/x64/Pop_test.go @@ -0,0 +1,39 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestPopRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x64.RAX, []byte{0x58}}, + {x64.RCX, []byte{0x59}}, + {x64.RDX, []byte{0x5A}}, + {x64.RBX, []byte{0x5B}}, + {x64.RSP, []byte{0x5C}}, + {x64.RBP, []byte{0x5D}}, + {x64.RSI, []byte{0x5E}}, + {x64.RDI, []byte{0x5F}}, + {x64.R8, []byte{0x41, 0x58}}, + {x64.R9, []byte{0x41, 0x59}}, + {x64.R10, []byte{0x41, 0x5A}}, + {x64.R11, []byte{0x41, 0x5B}}, + {x64.R12, []byte{0x41, 0x5C}}, + {x64.R13, []byte{0x41, 0x5D}}, + {x64.R14, []byte{0x41, 0x5E}}, + {x64.R15, []byte{0x41, 0x5F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("pop %s", pattern.Register) + code := x64.PopReg(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Push.go b/src/build/arch/x64/Push.go new file mode 100644 index 0000000..48bed9d --- /dev/null +++ b/src/build/arch/x64/Push.go @@ -0,0 +1,16 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// PushReg pushes the value inside the register onto the stack. +func PushReg(code []byte, register cpu.Register) []byte { + if register >= 8 { + code = append(code, REX(0, 0, 0, 1)) + register -= 8 + } + + return append( + code, + 0x50+byte(register), + ) +} diff --git a/src/build/arch/x64/Push_test.go b/src/build/arch/x64/Push_test.go new file mode 100644 index 0000000..22994bb --- /dev/null +++ b/src/build/arch/x64/Push_test.go @@ -0,0 +1,39 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestPushRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x64.RAX, []byte{0x50}}, + {x64.RCX, []byte{0x51}}, + {x64.RDX, []byte{0x52}}, + {x64.RBX, []byte{0x53}}, + {x64.RSP, []byte{0x54}}, + {x64.RBP, []byte{0x55}}, + {x64.RSI, []byte{0x56}}, + {x64.RDI, []byte{0x57}}, + {x64.R8, []byte{0x41, 0x50}}, + {x64.R9, []byte{0x41, 0x51}}, + {x64.R10, []byte{0x41, 0x52}}, + {x64.R11, []byte{0x41, 0x53}}, + {x64.R12, []byte{0x41, 0x54}}, + {x64.R13, []byte{0x41, 0x55}}, + {x64.R14, []byte{0x41, 0x56}}, + {x64.R15, []byte{0x41, 0x57}}, + } + + for _, pattern := range usagePatterns { + t.Logf("push %s", pattern.Register) + code := x64.PushReg(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index fb93ed7..81d5299 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -48,11 +48,23 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case DIV: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.MoveRegReg64(code, x64.RAX, operands.Register) - code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number)) - code = x64.ExtendRAXToRDX(code) - code = x64.DivReg(code, operands.Register) - code = x64.MoveRegReg64(code, operands.Register, x64.RAX) + if operands.Register == x64.RAX { + code = x64.PushReg(code, x64.RCX) + code = x64.MoveRegNum32(code, x64.RCX, uint32(operands.Number)) + code = x64.ExtendRAXToRDX(code) + code = x64.DivReg(code, x64.RCX) + code = x64.PopReg(code, x64.RCX) + } else { + code = x64.PushReg(code, x64.RAX) + code = x64.PushReg(code, x64.RDX) + code = x64.MoveRegReg64(code, x64.RAX, operands.Register) + code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number)) + code = x64.ExtendRAXToRDX(code) + code = x64.DivReg(code, operands.Register) + code = x64.MoveRegReg64(code, operands.Register, x64.RAX) + code = x64.PopReg(code, x64.RDX) + code = x64.PopReg(code, x64.RAX) + } } case CALL: diff --git a/src/build/asm/RegisterNumber.go b/src/build/asm/RegisterNumber.go index 5040b53..7e4ba23 100644 --- a/src/build/asm/RegisterNumber.go +++ b/src/build/asm/RegisterNumber.go @@ -14,5 +14,5 @@ type RegisterNumber struct { // String returns a human readable version. func (data *RegisterNumber) String() string { - return fmt.Sprintf("%s, %x", data.Register, data.Number) + return fmt.Sprintf("%s, %Xₕ", data.Register, data.Number) } From a87775a1dbcde81d5011943130074bd21fc983d8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 25 Jun 2024 16:19:47 +0200 Subject: [PATCH 0136/1012] Implemented register push and pop --- src/build/arch/x64/Pop.go | 16 ++++++++++++++ src/build/arch/x64/Pop_test.go | 39 +++++++++++++++++++++++++++++++++ src/build/arch/x64/Push.go | 16 ++++++++++++++ src/build/arch/x64/Push_test.go | 39 +++++++++++++++++++++++++++++++++ src/build/asm/Assembler.go | 22 ++++++++++++++----- src/build/asm/RegisterNumber.go | 2 +- 6 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 src/build/arch/x64/Pop.go create mode 100644 src/build/arch/x64/Pop_test.go create mode 100644 src/build/arch/x64/Push.go create mode 100644 src/build/arch/x64/Push_test.go diff --git a/src/build/arch/x64/Pop.go b/src/build/arch/x64/Pop.go new file mode 100644 index 0000000..746f3a0 --- /dev/null +++ b/src/build/arch/x64/Pop.go @@ -0,0 +1,16 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// PopReg pops a value from the stack and saves it into the register. +func PopReg(code []byte, register cpu.Register) []byte { + if register >= 8 { + code = append(code, REX(0, 0, 0, 1)) + register -= 8 + } + + return append( + code, + 0x58+byte(register), + ) +} diff --git a/src/build/arch/x64/Pop_test.go b/src/build/arch/x64/Pop_test.go new file mode 100644 index 0000000..64acf13 --- /dev/null +++ b/src/build/arch/x64/Pop_test.go @@ -0,0 +1,39 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestPopRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x64.RAX, []byte{0x58}}, + {x64.RCX, []byte{0x59}}, + {x64.RDX, []byte{0x5A}}, + {x64.RBX, []byte{0x5B}}, + {x64.RSP, []byte{0x5C}}, + {x64.RBP, []byte{0x5D}}, + {x64.RSI, []byte{0x5E}}, + {x64.RDI, []byte{0x5F}}, + {x64.R8, []byte{0x41, 0x58}}, + {x64.R9, []byte{0x41, 0x59}}, + {x64.R10, []byte{0x41, 0x5A}}, + {x64.R11, []byte{0x41, 0x5B}}, + {x64.R12, []byte{0x41, 0x5C}}, + {x64.R13, []byte{0x41, 0x5D}}, + {x64.R14, []byte{0x41, 0x5E}}, + {x64.R15, []byte{0x41, 0x5F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("pop %s", pattern.Register) + code := x64.PopReg(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Push.go b/src/build/arch/x64/Push.go new file mode 100644 index 0000000..48bed9d --- /dev/null +++ b/src/build/arch/x64/Push.go @@ -0,0 +1,16 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// PushReg pushes the value inside the register onto the stack. +func PushReg(code []byte, register cpu.Register) []byte { + if register >= 8 { + code = append(code, REX(0, 0, 0, 1)) + register -= 8 + } + + return append( + code, + 0x50+byte(register), + ) +} diff --git a/src/build/arch/x64/Push_test.go b/src/build/arch/x64/Push_test.go new file mode 100644 index 0000000..22994bb --- /dev/null +++ b/src/build/arch/x64/Push_test.go @@ -0,0 +1,39 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestPushRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x64.RAX, []byte{0x50}}, + {x64.RCX, []byte{0x51}}, + {x64.RDX, []byte{0x52}}, + {x64.RBX, []byte{0x53}}, + {x64.RSP, []byte{0x54}}, + {x64.RBP, []byte{0x55}}, + {x64.RSI, []byte{0x56}}, + {x64.RDI, []byte{0x57}}, + {x64.R8, []byte{0x41, 0x50}}, + {x64.R9, []byte{0x41, 0x51}}, + {x64.R10, []byte{0x41, 0x52}}, + {x64.R11, []byte{0x41, 0x53}}, + {x64.R12, []byte{0x41, 0x54}}, + {x64.R13, []byte{0x41, 0x55}}, + {x64.R14, []byte{0x41, 0x56}}, + {x64.R15, []byte{0x41, 0x57}}, + } + + for _, pattern := range usagePatterns { + t.Logf("push %s", pattern.Register) + code := x64.PushReg(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index fb93ed7..81d5299 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -48,11 +48,23 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case DIV: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.MoveRegReg64(code, x64.RAX, operands.Register) - code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number)) - code = x64.ExtendRAXToRDX(code) - code = x64.DivReg(code, operands.Register) - code = x64.MoveRegReg64(code, operands.Register, x64.RAX) + if operands.Register == x64.RAX { + code = x64.PushReg(code, x64.RCX) + code = x64.MoveRegNum32(code, x64.RCX, uint32(operands.Number)) + code = x64.ExtendRAXToRDX(code) + code = x64.DivReg(code, x64.RCX) + code = x64.PopReg(code, x64.RCX) + } else { + code = x64.PushReg(code, x64.RAX) + code = x64.PushReg(code, x64.RDX) + code = x64.MoveRegReg64(code, x64.RAX, operands.Register) + code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number)) + code = x64.ExtendRAXToRDX(code) + code = x64.DivReg(code, operands.Register) + code = x64.MoveRegReg64(code, operands.Register, x64.RAX) + code = x64.PopReg(code, x64.RDX) + code = x64.PopReg(code, x64.RAX) + } } case CALL: diff --git a/src/build/asm/RegisterNumber.go b/src/build/asm/RegisterNumber.go index 5040b53..7e4ba23 100644 --- a/src/build/asm/RegisterNumber.go +++ b/src/build/asm/RegisterNumber.go @@ -14,5 +14,5 @@ type RegisterNumber struct { // String returns a human readable version. func (data *RegisterNumber) String() string { - return fmt.Sprintf("%s, %x", data.Register, data.Number) + return fmt.Sprintf("%s, %Xₕ", data.Register, data.Number) } From e736a2bde4259418bc3aec68d0d573d30018fdab Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 25 Jun 2024 16:31:59 +0200 Subject: [PATCH 0137/1012] Reversed parameter order --- src/build/FunctionCall.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go index 387fe43..1857a82 100644 --- a/src/build/FunctionCall.go +++ b/src/build/FunctionCall.go @@ -10,8 +10,8 @@ func (f *Function) CompileFunctionCall(expr *expression.Expression) error { funcName := expr.Children[0].Token.Text() parameters := expr.Children[1:] - for i, parameter := range parameters { - err := f.ExpressionToRegister(parameter, f.CPU.Syscall[i]) + for i := len(parameters) - 1; i >= 0; i-- { + err := f.ExpressionToRegister(parameters[i], f.CPU.Syscall[i]) if err != nil { return err From c7d90a2d11cf8ec734047bf0f728ba52ac9e5e7c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 25 Jun 2024 16:31:59 +0200 Subject: [PATCH 0138/1012] Reversed parameter order --- src/build/FunctionCall.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go index 387fe43..1857a82 100644 --- a/src/build/FunctionCall.go +++ b/src/build/FunctionCall.go @@ -10,8 +10,8 @@ func (f *Function) CompileFunctionCall(expr *expression.Expression) error { funcName := expr.Children[0].Token.Text() parameters := expr.Children[1:] - for i, parameter := range parameters { - err := f.ExpressionToRegister(parameter, f.CPU.Syscall[i]) + for i := len(parameters) - 1; i >= 0; i-- { + err := f.ExpressionToRegister(parameters[i], f.CPU.Syscall[i]) if err != nil { return err From c437b1d0f87c321558da8987f87ad7971a5d6d2c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 25 Jun 2024 17:09:25 +0200 Subject: [PATCH 0139/1012] Implemented assignments --- examples/hello/hello.q | 1 + src/build/Assignment.go | 38 +++++++++++++++++++++++++++++ src/build/Function.go | 43 +++++++++++++++------------------ src/build/FunctionCall.go | 6 ----- src/build/VariableDefinition.go | 5 ---- 5 files changed, 58 insertions(+), 35 deletions(-) create mode 100644 src/build/Assignment.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 1a3745e..a49c249 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -11,6 +11,7 @@ hello() { address += 4194304 address += 1 + length = 0 length += 50 length -= 20 length *= 10 diff --git a/src/build/Assignment.go b/src/build/Assignment.go new file mode 100644 index 0000000..a5a91bb --- /dev/null +++ b/src/build/Assignment.go @@ -0,0 +1,38 @@ +package build + +import ( + "strconv" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/errors" +) + +// CompileAssignment compiles an assignment. +func (f *Function) CompileAssignment(expr *expression.Expression) error { + name := expr.Children[0].Token.Text() + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) + register := f.Variables[name].Register + + switch expr.Token.Text() { + case "=": + f.ExpressionToRegister(expr.Children[1], register) + + case "+=": + f.Assembler.RegisterNumber(asm.ADD, register, number) + + case "-=": + f.Assembler.RegisterNumber(asm.SUB, register, number) + + case "*=": + f.Assembler.RegisterNumber(asm.MUL, register, number) + + case "/=": + f.Assembler.RegisterNumber(asm.DIV, register, number) + + default: + return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) + } + + return nil +} diff --git a/src/build/Function.go b/src/build/Function.go index 141c210..5d7666d 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -109,37 +109,32 @@ func (f *Function) CompileInstruction(line token.List) error { return f.CompileVariableDefinition(expr) } + if isAssignment(expr) { + return f.CompileAssignment(expr) + } + if isFunctionCall(expr) { return f.CompileFunctionCall(expr) } - if expr.Token.Kind == token.Operator { - name := expr.Children[0].Token.Text() - number, _ := strconv.Atoi(expr.Children[1].Token.Text()) - register := f.Variables[name].Register - - switch expr.Token.Text() { - case "+=": - f.Assembler.RegisterNumber(asm.ADD, register, number) - return nil - - case "-=": - f.Assembler.RegisterNumber(asm.SUB, register, number) - return nil - - case "*=": - f.Assembler.RegisterNumber(asm.MUL, register, number) - return nil - - case "/=": - f.Assembler.RegisterNumber(asm.DIV, register, number) - return nil - } - } - return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) } +// isAssignment returns true if the expression is an assignment. +func isAssignment(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '=' +} + +// isFunctionCall returns true if the expression is a function call. +func isFunctionCall(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" +} + +// isVariableDefinition returns true if the expression is a variable definition. +func isVariableDefinition(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" +} + // ExpressionToRegister moves the result of an expression into the given register. func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { if root.IsLeaf() { diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go index 1857a82..9c90ee7 100644 --- a/src/build/FunctionCall.go +++ b/src/build/FunctionCall.go @@ -2,7 +2,6 @@ package build import ( "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" ) // CompileFunctionCall compiles a top-level function call. @@ -26,8 +25,3 @@ func (f *Function) CompileFunctionCall(expr *expression.Expression) error { return nil } - -// isFunctionCall returns true if the expression is a function call. -func isFunctionCall(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" -} diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index 9eca8e9..1043143 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -48,8 +48,3 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return nil } - -// isVariableDefinition returns true if the expression is a variable definition. -func isVariableDefinition(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" -} From 2569c1bf633fce00fab478761c9a6b07c93a7f8b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 25 Jun 2024 17:09:25 +0200 Subject: [PATCH 0140/1012] Implemented assignments --- examples/hello/hello.q | 1 + src/build/Assignment.go | 38 +++++++++++++++++++++++++++++ src/build/Function.go | 43 +++++++++++++++------------------ src/build/FunctionCall.go | 6 ----- src/build/VariableDefinition.go | 5 ---- 5 files changed, 58 insertions(+), 35 deletions(-) create mode 100644 src/build/Assignment.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 1a3745e..a49c249 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -11,6 +11,7 @@ hello() { address += 4194304 address += 1 + length = 0 length += 50 length -= 20 length *= 10 diff --git a/src/build/Assignment.go b/src/build/Assignment.go new file mode 100644 index 0000000..a5a91bb --- /dev/null +++ b/src/build/Assignment.go @@ -0,0 +1,38 @@ +package build + +import ( + "strconv" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/errors" +) + +// CompileAssignment compiles an assignment. +func (f *Function) CompileAssignment(expr *expression.Expression) error { + name := expr.Children[0].Token.Text() + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) + register := f.Variables[name].Register + + switch expr.Token.Text() { + case "=": + f.ExpressionToRegister(expr.Children[1], register) + + case "+=": + f.Assembler.RegisterNumber(asm.ADD, register, number) + + case "-=": + f.Assembler.RegisterNumber(asm.SUB, register, number) + + case "*=": + f.Assembler.RegisterNumber(asm.MUL, register, number) + + case "/=": + f.Assembler.RegisterNumber(asm.DIV, register, number) + + default: + return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) + } + + return nil +} diff --git a/src/build/Function.go b/src/build/Function.go index 141c210..5d7666d 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -109,37 +109,32 @@ func (f *Function) CompileInstruction(line token.List) error { return f.CompileVariableDefinition(expr) } + if isAssignment(expr) { + return f.CompileAssignment(expr) + } + if isFunctionCall(expr) { return f.CompileFunctionCall(expr) } - if expr.Token.Kind == token.Operator { - name := expr.Children[0].Token.Text() - number, _ := strconv.Atoi(expr.Children[1].Token.Text()) - register := f.Variables[name].Register - - switch expr.Token.Text() { - case "+=": - f.Assembler.RegisterNumber(asm.ADD, register, number) - return nil - - case "-=": - f.Assembler.RegisterNumber(asm.SUB, register, number) - return nil - - case "*=": - f.Assembler.RegisterNumber(asm.MUL, register, number) - return nil - - case "/=": - f.Assembler.RegisterNumber(asm.DIV, register, number) - return nil - } - } - return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) } +// isAssignment returns true if the expression is an assignment. +func isAssignment(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '=' +} + +// isFunctionCall returns true if the expression is a function call. +func isFunctionCall(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" +} + +// isVariableDefinition returns true if the expression is a variable definition. +func isVariableDefinition(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" +} + // ExpressionToRegister moves the result of an expression into the given register. func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { if root.IsLeaf() { diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go index 1857a82..9c90ee7 100644 --- a/src/build/FunctionCall.go +++ b/src/build/FunctionCall.go @@ -2,7 +2,6 @@ package build import ( "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" ) // CompileFunctionCall compiles a top-level function call. @@ -26,8 +25,3 @@ func (f *Function) CompileFunctionCall(expr *expression.Expression) error { return nil } - -// isFunctionCall returns true if the expression is a function call. -func isFunctionCall(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" -} diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index 9eca8e9..1043143 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -48,8 +48,3 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return nil } - -// isVariableDefinition returns true if the expression is a variable definition. -func isVariableDefinition(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" -} From 37e222e0222f7cf8625ccfb5ea14455bbe7f3055 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 25 Jun 2024 20:50:06 +0200 Subject: [PATCH 0141/1012] Implemented simple expressions --- examples/hello/hello.q | 1 + src/build/Assignment.go | 5 +- src/build/Function.go | 79 ++++++++++++++++++++++++------ src/build/arch/x64/Add.go | 34 ++++++++++++- src/build/arch/x64/Add_test.go | 31 ++++++++++++ src/build/arch/x64/Mul.go | 7 ++- src/build/arch/x64/Mul_test.go | 31 ++++++++++++ src/build/arch/x64/Sub.go | 7 ++- src/build/arch/x64/numRegReg.go | 34 ++----------- src/build/asm/Assembler.go | 16 ++++++ src/build/asm/Instructions.go | 11 +++++ src/build/expression/Expression.go | 28 +++++++++-- 12 files changed, 232 insertions(+), 52 deletions(-) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index a49c249..7eddb30 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -16,6 +16,7 @@ hello() { length -= 20 length *= 10 length /= 100 + length = (0 + 50 - 20) * 10 / 100 loop { syscall(write, stdout, address, length) diff --git a/src/build/Assignment.go b/src/build/Assignment.go index a5a91bb..96ebd79 100644 --- a/src/build/Assignment.go +++ b/src/build/Assignment.go @@ -11,7 +11,6 @@ import ( // CompileAssignment compiles an assignment. func (f *Function) CompileAssignment(expr *expression.Expression) error { name := expr.Children[0].Token.Text() - number, _ := strconv.Atoi(expr.Children[1].Token.Text()) register := f.Variables[name].Register switch expr.Token.Text() { @@ -19,15 +18,19 @@ func (f *Function) CompileAssignment(expr *expression.Expression) error { f.ExpressionToRegister(expr.Children[1], register) case "+=": + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) f.Assembler.RegisterNumber(asm.ADD, register, number) case "-=": + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) f.Assembler.RegisterNumber(asm.SUB, register, number) case "*=": + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) f.Assembler.RegisterNumber(asm.MUL, register, number) case "/=": + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) f.Assembler.RegisterNumber(asm.DIV, register, number) default: diff --git a/src/build/Function.go b/src/build/Function.go index 5d7666d..f99bc2d 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -120,27 +120,61 @@ func (f *Function) CompileInstruction(line token.List) error { return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) } -// isAssignment returns true if the expression is an assignment. -func isAssignment(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '=' -} - -// isFunctionCall returns true if the expression is a function call. -func isFunctionCall(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" -} - -// isVariableDefinition returns true if the expression is a variable definition. -func isVariableDefinition(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" -} - // ExpressionToRegister moves the result of an expression into the given register. func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { if root.IsLeaf() { return f.TokenToRegister(root.Token, register) } + left := root.Children[0] + right := root.Children[1] + + f.ExpressionToRegister(left, register) + + if right.IsLeaf() { + value := right.Token.Text() + n, err := strconv.Atoi(value) + + if err != nil { + return err + } + + switch root.Token.Text() { + case "+": + f.Assembler.RegisterNumber(asm.ADD, register, n) + + case "-": + f.Assembler.RegisterNumber(asm.SUB, register, n) + + case "*": + f.Assembler.RegisterNumber(asm.MUL, register, n) + + case "/": + f.Assembler.RegisterNumber(asm.DIV, register, n) + } + + return nil + } else { + temporary, _ := f.CPU.FindFree() + f.CPU.Use(temporary) + f.ExpressionToRegister(right, temporary) + f.CPU.Free(temporary) + + switch root.Token.Text() { + case "+": + f.Assembler.RegisterRegister(asm.ADD, register, temporary) + + case "-": + f.Assembler.RegisterRegister(asm.SUB, register, temporary) + + case "*": + f.Assembler.RegisterRegister(asm.MUL, register, temporary) + + case "/": + f.Assembler.RegisterRegister(asm.DIV, register, temporary) + } + } + return errors.New(errors.NotImplemented, f.File, root.Token.Position) } @@ -207,3 +241,18 @@ func (f *Function) identifierExists(name string) bool { _, exists := f.Variables[name] return exists } + +// isAssignment returns true if the expression is an assignment. +func isAssignment(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '=' +} + +// isFunctionCall returns true if the expression is a function call. +func isFunctionCall(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" +} + +// isVariableDefinition returns true if the expression is a variable definition. +func isVariableDefinition(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" +} diff --git a/src/build/arch/x64/Add.go b/src/build/arch/x64/Add.go index f67094d..5e28c0b 100644 --- a/src/build/arch/x64/Add.go +++ b/src/build/arch/x64/Add.go @@ -6,5 +6,37 @@ import ( // AddRegNum adds a number to the given register. func AddRegNum(code []byte, destination cpu.Register, number int) []byte { - return numRegReg(code, 0x83, 0x81, 0, byte(destination), number) + return numRegReg(code, 0, byte(destination), number, 0x83, 0x81) +} + +// AddRegReg adds a number to the given register. +func AddRegReg(code []byte, destination cpu.Register, operand cpu.Register) []byte { + return regReg(code, byte(operand), byte(destination), 0x01) +} + +func regReg(code []byte, reg byte, rm byte, opCodes ...byte) []byte { + w := byte(1) // Indicates a 64-bit register. + r := byte(0) // Extension to the "reg" field in ModRM. + x := byte(0) // Extension to the SIB index field. + b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). + mod := byte(0b11) // Direct addressing mode, no register offsets. + + if reg > 0b111 { + r = 1 + reg &= 0b111 + } + + if rm > 0b111 { + b = 1 + rm &= 0b111 + } + + rex := REX(w, r, x, b) + modRM := ModRM(mod, reg, rm) + + code = append(code, rex) + code = append(code, opCodes...) + code = append(code, modRM) + + return code } diff --git a/src/build/arch/x64/Add_test.go b/src/build/arch/x64/Add_test.go index 80abc27..ca21dc7 100644 --- a/src/build/arch/x64/Add_test.go +++ b/src/build/arch/x64/Add_test.go @@ -55,3 +55,34 @@ func TestAddRegisterNumber(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestAddRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x4C, 0x01, 0xF8}}, + {x64.RCX, x64.R14, []byte{0x4C, 0x01, 0xF1}}, + {x64.RDX, x64.R13, []byte{0x4C, 0x01, 0xEA}}, + {x64.RBX, x64.R12, []byte{0x4C, 0x01, 0xE3}}, + {x64.RSP, x64.R11, []byte{0x4C, 0x01, 0xDC}}, + {x64.RBP, x64.R10, []byte{0x4C, 0x01, 0xD5}}, + {x64.RSI, x64.R9, []byte{0x4C, 0x01, 0xCE}}, + {x64.RDI, x64.R8, []byte{0x4C, 0x01, 0xC7}}, + {x64.R8, x64.RDI, []byte{0x49, 0x01, 0xF8}}, + {x64.R9, x64.RSI, []byte{0x49, 0x01, 0xF1}}, + {x64.R10, x64.RBP, []byte{0x49, 0x01, 0xEA}}, + {x64.R11, x64.RSP, []byte{0x49, 0x01, 0xE3}}, + {x64.R12, x64.RBX, []byte{0x49, 0x01, 0xDC}}, + {x64.R13, x64.RDX, []byte{0x49, 0x01, 0xD5}}, + {x64.R14, x64.RCX, []byte{0x49, 0x01, 0xCE}}, + {x64.R15, x64.RAX, []byte{0x49, 0x01, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("add %s, %s", pattern.Left, pattern.Right) + code := x64.AddRegReg(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Mul.go b/src/build/arch/x64/Mul.go index d952b2b..45254b2 100644 --- a/src/build/arch/x64/Mul.go +++ b/src/build/arch/x64/Mul.go @@ -4,5 +4,10 @@ import "git.akyoto.dev/cli/q/src/build/cpu" // MulRegNum multiplies a register with a number. func MulRegNum(code []byte, destination cpu.Register, number int) []byte { - return numRegReg(code, 0x6B, 0x69, byte(destination), byte(destination), number) + return numRegReg(code, byte(destination), byte(destination), number, 0x6B, 0x69) +} + +// MulRegReg multiplies a register with another register. +func MulRegReg(code []byte, destination cpu.Register, operand cpu.Register) []byte { + return regReg(code, byte(destination), byte(operand), 0x0F, 0xAF) } diff --git a/src/build/arch/x64/Mul_test.go b/src/build/arch/x64/Mul_test.go index 1a81fd3..ffea393 100644 --- a/src/build/arch/x64/Mul_test.go +++ b/src/build/arch/x64/Mul_test.go @@ -55,3 +55,34 @@ func TestMulRegisterNumber(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestMulRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x49, 0x0F, 0xAF, 0xC7}}, + {x64.RCX, x64.R14, []byte{0x49, 0x0F, 0xAF, 0xCE}}, + {x64.RDX, x64.R13, []byte{0x49, 0x0F, 0xAF, 0xD5}}, + {x64.RBX, x64.R12, []byte{0x49, 0x0F, 0xAF, 0xDC}}, + {x64.RSP, x64.R11, []byte{0x49, 0x0F, 0xAF, 0xE3}}, + {x64.RBP, x64.R10, []byte{0x49, 0x0F, 0xAF, 0xEA}}, + {x64.RSI, x64.R9, []byte{0x49, 0x0F, 0xAF, 0xF1}}, + {x64.RDI, x64.R8, []byte{0x49, 0x0F, 0xAF, 0xF8}}, + {x64.R8, x64.RDI, []byte{0x4C, 0x0F, 0xAF, 0xC7}}, + {x64.R9, x64.RSI, []byte{0x4C, 0x0F, 0xAF, 0xCE}}, + {x64.R10, x64.RBP, []byte{0x4C, 0x0F, 0xAF, 0xD5}}, + {x64.R11, x64.RSP, []byte{0x4C, 0x0F, 0xAF, 0xDC}}, + {x64.R12, x64.RBX, []byte{0x4C, 0x0F, 0xAF, 0xE3}}, + {x64.R13, x64.RDX, []byte{0x4C, 0x0F, 0xAF, 0xEA}}, + {x64.R14, x64.RCX, []byte{0x4C, 0x0F, 0xAF, 0xF1}}, + {x64.R15, x64.RAX, []byte{0x4C, 0x0F, 0xAF, 0xF8}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mul %s, %s", pattern.Left, pattern.Right) + code := x64.MulRegReg(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Sub.go b/src/build/arch/x64/Sub.go index f2d5e9b..60fc81d 100644 --- a/src/build/arch/x64/Sub.go +++ b/src/build/arch/x64/Sub.go @@ -6,5 +6,10 @@ import ( // SubRegNum subtracts a number from the given register. func SubRegNum(code []byte, destination cpu.Register, number int) []byte { - return numRegReg(code, 0x83, 0x81, 0b101, byte(destination), number) + return numRegReg(code, 0b101, byte(destination), number, 0x83, 0x81) +} + +// SubRegReg subtracts a register value from another register. +func SubRegReg(code []byte, destination cpu.Register, operand cpu.Register) []byte { + return regReg(code, byte(operand), byte(destination), 0x29) } diff --git a/src/build/arch/x64/numRegReg.go b/src/build/arch/x64/numRegReg.go index c67889e..bb7fa96 100644 --- a/src/build/arch/x64/numRegReg.go +++ b/src/build/arch/x64/numRegReg.go @@ -1,41 +1,15 @@ package x64 // numRegReg encodes an instruction with up to two registers and a number parameter. -func numRegReg(code []byte, opCode8 byte, opCode32 byte, reg byte, rm byte, number int) []byte { - w := byte(1) // Indicates a 64-bit register. - r := byte(0) // Extension to the "reg" field in ModRM. - x := byte(0) // Extension to the SIB index field. - b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). - mod := byte(0b11) // Direct addressing mode, no register offsets. - - if reg > 0b111 { - r = 1 - reg &= 0b111 - } - - if rm > 0b111 { - b = 1 - rm &= 0b111 - } - - rex := REX(w, r, x, b) - modRM := ModRM(mod, reg, rm) - +func numRegReg(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { if sizeOf(number) == 1 { - return append( - code, - rex, - opCode8, - modRM, - byte(number), - ) + code = regReg(code, reg, rm, opCode8) + return append(code, byte(number)) } + code = regReg(code, reg, rm, opCode32) return append( code, - rex, - opCode32, - modRM, byte(number), byte(number>>8), byte(number>>16), diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 81d5299..f3529ec 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -31,18 +31,24 @@ func (a *Assembler) Finalize() ([]byte, []byte) { switch operands := x.Data.(type) { case *RegisterNumber: code = x64.AddRegNum(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.AddRegReg(code, operands.Destination, operands.Source) } case SUB: switch operands := x.Data.(type) { case *RegisterNumber: code = x64.SubRegNum(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.SubRegReg(code, operands.Destination, operands.Source) } case MUL: switch operands := x.Data.(type) { case *RegisterNumber: code = x64.MulRegNum(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.MulRegReg(code, operands.Destination, operands.Source) } case DIV: @@ -65,6 +71,16 @@ func (a *Assembler) Finalize() ([]byte, []byte) { code = x64.PopReg(code, x64.RDX) code = x64.PopReg(code, x64.RAX) } + + case *RegisterRegister: + code = x64.PushReg(code, x64.RAX) + code = x64.PushReg(code, x64.RDX) + code = x64.MoveRegReg64(code, x64.RAX, operands.Destination) + code = x64.ExtendRAXToRDX(code) + code = x64.DivReg(code, operands.Source) + code = x64.MoveRegReg64(code, operands.Destination, x64.RAX) + code = x64.PopReg(code, x64.RDX) + code = x64.PopReg(code, x64.RAX) } case CALL: diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 7eb041f..92842e2 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -13,6 +13,17 @@ func (a *Assembler) RegisterNumber(mnemonic Mnemonic, reg cpu.Register, number i }) } +// RegisterRegister adds an instruction using two registers. +func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right cpu.Register) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &RegisterRegister{ + Destination: left, + Source: right, + }, + }) +} + // MoveRegisterRegister moves a register value into another register. func (a *Assembler) MoveRegisterRegister(destination cpu.Register, source cpu.Register) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 37b7719..dc5a206 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -55,13 +55,13 @@ func (expr *Expression) Close() { } // EachLeaf iterates through all leaves in the tree. -func (expr *Expression) EachLeaf(callBack func(*Expression) error) error { +func (expr *Expression) EachLeaf(call func(*Expression) error) error { if expr.IsLeaf() { - return callBack(expr) + return call(expr) } for _, child := range expr.Children { - err := child.EachLeaf(callBack) + err := child.EachLeaf(call) if err != nil { return err @@ -71,6 +71,28 @@ func (expr *Expression) EachLeaf(callBack func(*Expression) error) error { return nil } +// EachOperation iterates through all the operations in the tree. +func (expr *Expression) EachOperation(call func(*Expression) error) error { + if expr.IsLeaf() { + return nil + } + + // Don't descend into the parameters of function calls + if expr.Token.Text() == "λ" { + return call(expr) + } + + for _, child := range expr.Children { + err := child.EachOperation(call) + + if err != nil { + return err + } + } + + return call(expr) +} + // RemoveChild removes a child from the expression. func (expr *Expression) RemoveChild(child *Expression) { for i, c := range expr.Children { From e6462266ef9e75bb8ad14ab94c73cadea0dd84cb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 25 Jun 2024 20:50:06 +0200 Subject: [PATCH 0142/1012] Implemented simple expressions --- examples/hello/hello.q | 1 + src/build/Assignment.go | 5 +- src/build/Function.go | 79 ++++++++++++++++++++++++------ src/build/arch/x64/Add.go | 34 ++++++++++++- src/build/arch/x64/Add_test.go | 31 ++++++++++++ src/build/arch/x64/Mul.go | 7 ++- src/build/arch/x64/Mul_test.go | 31 ++++++++++++ src/build/arch/x64/Sub.go | 7 ++- src/build/arch/x64/numRegReg.go | 34 ++----------- src/build/asm/Assembler.go | 16 ++++++ src/build/asm/Instructions.go | 11 +++++ src/build/expression/Expression.go | 28 +++++++++-- 12 files changed, 232 insertions(+), 52 deletions(-) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index a49c249..7eddb30 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -16,6 +16,7 @@ hello() { length -= 20 length *= 10 length /= 100 + length = (0 + 50 - 20) * 10 / 100 loop { syscall(write, stdout, address, length) diff --git a/src/build/Assignment.go b/src/build/Assignment.go index a5a91bb..96ebd79 100644 --- a/src/build/Assignment.go +++ b/src/build/Assignment.go @@ -11,7 +11,6 @@ import ( // CompileAssignment compiles an assignment. func (f *Function) CompileAssignment(expr *expression.Expression) error { name := expr.Children[0].Token.Text() - number, _ := strconv.Atoi(expr.Children[1].Token.Text()) register := f.Variables[name].Register switch expr.Token.Text() { @@ -19,15 +18,19 @@ func (f *Function) CompileAssignment(expr *expression.Expression) error { f.ExpressionToRegister(expr.Children[1], register) case "+=": + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) f.Assembler.RegisterNumber(asm.ADD, register, number) case "-=": + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) f.Assembler.RegisterNumber(asm.SUB, register, number) case "*=": + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) f.Assembler.RegisterNumber(asm.MUL, register, number) case "/=": + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) f.Assembler.RegisterNumber(asm.DIV, register, number) default: diff --git a/src/build/Function.go b/src/build/Function.go index 5d7666d..f99bc2d 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -120,27 +120,61 @@ func (f *Function) CompileInstruction(line token.List) error { return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) } -// isAssignment returns true if the expression is an assignment. -func isAssignment(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '=' -} - -// isFunctionCall returns true if the expression is a function call. -func isFunctionCall(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" -} - -// isVariableDefinition returns true if the expression is a variable definition. -func isVariableDefinition(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" -} - // ExpressionToRegister moves the result of an expression into the given register. func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { if root.IsLeaf() { return f.TokenToRegister(root.Token, register) } + left := root.Children[0] + right := root.Children[1] + + f.ExpressionToRegister(left, register) + + if right.IsLeaf() { + value := right.Token.Text() + n, err := strconv.Atoi(value) + + if err != nil { + return err + } + + switch root.Token.Text() { + case "+": + f.Assembler.RegisterNumber(asm.ADD, register, n) + + case "-": + f.Assembler.RegisterNumber(asm.SUB, register, n) + + case "*": + f.Assembler.RegisterNumber(asm.MUL, register, n) + + case "/": + f.Assembler.RegisterNumber(asm.DIV, register, n) + } + + return nil + } else { + temporary, _ := f.CPU.FindFree() + f.CPU.Use(temporary) + f.ExpressionToRegister(right, temporary) + f.CPU.Free(temporary) + + switch root.Token.Text() { + case "+": + f.Assembler.RegisterRegister(asm.ADD, register, temporary) + + case "-": + f.Assembler.RegisterRegister(asm.SUB, register, temporary) + + case "*": + f.Assembler.RegisterRegister(asm.MUL, register, temporary) + + case "/": + f.Assembler.RegisterRegister(asm.DIV, register, temporary) + } + } + return errors.New(errors.NotImplemented, f.File, root.Token.Position) } @@ -207,3 +241,18 @@ func (f *Function) identifierExists(name string) bool { _, exists := f.Variables[name] return exists } + +// isAssignment returns true if the expression is an assignment. +func isAssignment(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '=' +} + +// isFunctionCall returns true if the expression is a function call. +func isFunctionCall(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" +} + +// isVariableDefinition returns true if the expression is a variable definition. +func isVariableDefinition(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" +} diff --git a/src/build/arch/x64/Add.go b/src/build/arch/x64/Add.go index f67094d..5e28c0b 100644 --- a/src/build/arch/x64/Add.go +++ b/src/build/arch/x64/Add.go @@ -6,5 +6,37 @@ import ( // AddRegNum adds a number to the given register. func AddRegNum(code []byte, destination cpu.Register, number int) []byte { - return numRegReg(code, 0x83, 0x81, 0, byte(destination), number) + return numRegReg(code, 0, byte(destination), number, 0x83, 0x81) +} + +// AddRegReg adds a number to the given register. +func AddRegReg(code []byte, destination cpu.Register, operand cpu.Register) []byte { + return regReg(code, byte(operand), byte(destination), 0x01) +} + +func regReg(code []byte, reg byte, rm byte, opCodes ...byte) []byte { + w := byte(1) // Indicates a 64-bit register. + r := byte(0) // Extension to the "reg" field in ModRM. + x := byte(0) // Extension to the SIB index field. + b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). + mod := byte(0b11) // Direct addressing mode, no register offsets. + + if reg > 0b111 { + r = 1 + reg &= 0b111 + } + + if rm > 0b111 { + b = 1 + rm &= 0b111 + } + + rex := REX(w, r, x, b) + modRM := ModRM(mod, reg, rm) + + code = append(code, rex) + code = append(code, opCodes...) + code = append(code, modRM) + + return code } diff --git a/src/build/arch/x64/Add_test.go b/src/build/arch/x64/Add_test.go index 80abc27..ca21dc7 100644 --- a/src/build/arch/x64/Add_test.go +++ b/src/build/arch/x64/Add_test.go @@ -55,3 +55,34 @@ func TestAddRegisterNumber(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestAddRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x4C, 0x01, 0xF8}}, + {x64.RCX, x64.R14, []byte{0x4C, 0x01, 0xF1}}, + {x64.RDX, x64.R13, []byte{0x4C, 0x01, 0xEA}}, + {x64.RBX, x64.R12, []byte{0x4C, 0x01, 0xE3}}, + {x64.RSP, x64.R11, []byte{0x4C, 0x01, 0xDC}}, + {x64.RBP, x64.R10, []byte{0x4C, 0x01, 0xD5}}, + {x64.RSI, x64.R9, []byte{0x4C, 0x01, 0xCE}}, + {x64.RDI, x64.R8, []byte{0x4C, 0x01, 0xC7}}, + {x64.R8, x64.RDI, []byte{0x49, 0x01, 0xF8}}, + {x64.R9, x64.RSI, []byte{0x49, 0x01, 0xF1}}, + {x64.R10, x64.RBP, []byte{0x49, 0x01, 0xEA}}, + {x64.R11, x64.RSP, []byte{0x49, 0x01, 0xE3}}, + {x64.R12, x64.RBX, []byte{0x49, 0x01, 0xDC}}, + {x64.R13, x64.RDX, []byte{0x49, 0x01, 0xD5}}, + {x64.R14, x64.RCX, []byte{0x49, 0x01, 0xCE}}, + {x64.R15, x64.RAX, []byte{0x49, 0x01, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("add %s, %s", pattern.Left, pattern.Right) + code := x64.AddRegReg(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Mul.go b/src/build/arch/x64/Mul.go index d952b2b..45254b2 100644 --- a/src/build/arch/x64/Mul.go +++ b/src/build/arch/x64/Mul.go @@ -4,5 +4,10 @@ import "git.akyoto.dev/cli/q/src/build/cpu" // MulRegNum multiplies a register with a number. func MulRegNum(code []byte, destination cpu.Register, number int) []byte { - return numRegReg(code, 0x6B, 0x69, byte(destination), byte(destination), number) + return numRegReg(code, byte(destination), byte(destination), number, 0x6B, 0x69) +} + +// MulRegReg multiplies a register with another register. +func MulRegReg(code []byte, destination cpu.Register, operand cpu.Register) []byte { + return regReg(code, byte(destination), byte(operand), 0x0F, 0xAF) } diff --git a/src/build/arch/x64/Mul_test.go b/src/build/arch/x64/Mul_test.go index 1a81fd3..ffea393 100644 --- a/src/build/arch/x64/Mul_test.go +++ b/src/build/arch/x64/Mul_test.go @@ -55,3 +55,34 @@ func TestMulRegisterNumber(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestMulRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x49, 0x0F, 0xAF, 0xC7}}, + {x64.RCX, x64.R14, []byte{0x49, 0x0F, 0xAF, 0xCE}}, + {x64.RDX, x64.R13, []byte{0x49, 0x0F, 0xAF, 0xD5}}, + {x64.RBX, x64.R12, []byte{0x49, 0x0F, 0xAF, 0xDC}}, + {x64.RSP, x64.R11, []byte{0x49, 0x0F, 0xAF, 0xE3}}, + {x64.RBP, x64.R10, []byte{0x49, 0x0F, 0xAF, 0xEA}}, + {x64.RSI, x64.R9, []byte{0x49, 0x0F, 0xAF, 0xF1}}, + {x64.RDI, x64.R8, []byte{0x49, 0x0F, 0xAF, 0xF8}}, + {x64.R8, x64.RDI, []byte{0x4C, 0x0F, 0xAF, 0xC7}}, + {x64.R9, x64.RSI, []byte{0x4C, 0x0F, 0xAF, 0xCE}}, + {x64.R10, x64.RBP, []byte{0x4C, 0x0F, 0xAF, 0xD5}}, + {x64.R11, x64.RSP, []byte{0x4C, 0x0F, 0xAF, 0xDC}}, + {x64.R12, x64.RBX, []byte{0x4C, 0x0F, 0xAF, 0xE3}}, + {x64.R13, x64.RDX, []byte{0x4C, 0x0F, 0xAF, 0xEA}}, + {x64.R14, x64.RCX, []byte{0x4C, 0x0F, 0xAF, 0xF1}}, + {x64.R15, x64.RAX, []byte{0x4C, 0x0F, 0xAF, 0xF8}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mul %s, %s", pattern.Left, pattern.Right) + code := x64.MulRegReg(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Sub.go b/src/build/arch/x64/Sub.go index f2d5e9b..60fc81d 100644 --- a/src/build/arch/x64/Sub.go +++ b/src/build/arch/x64/Sub.go @@ -6,5 +6,10 @@ import ( // SubRegNum subtracts a number from the given register. func SubRegNum(code []byte, destination cpu.Register, number int) []byte { - return numRegReg(code, 0x83, 0x81, 0b101, byte(destination), number) + return numRegReg(code, 0b101, byte(destination), number, 0x83, 0x81) +} + +// SubRegReg subtracts a register value from another register. +func SubRegReg(code []byte, destination cpu.Register, operand cpu.Register) []byte { + return regReg(code, byte(operand), byte(destination), 0x29) } diff --git a/src/build/arch/x64/numRegReg.go b/src/build/arch/x64/numRegReg.go index c67889e..bb7fa96 100644 --- a/src/build/arch/x64/numRegReg.go +++ b/src/build/arch/x64/numRegReg.go @@ -1,41 +1,15 @@ package x64 // numRegReg encodes an instruction with up to two registers and a number parameter. -func numRegReg(code []byte, opCode8 byte, opCode32 byte, reg byte, rm byte, number int) []byte { - w := byte(1) // Indicates a 64-bit register. - r := byte(0) // Extension to the "reg" field in ModRM. - x := byte(0) // Extension to the SIB index field. - b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). - mod := byte(0b11) // Direct addressing mode, no register offsets. - - if reg > 0b111 { - r = 1 - reg &= 0b111 - } - - if rm > 0b111 { - b = 1 - rm &= 0b111 - } - - rex := REX(w, r, x, b) - modRM := ModRM(mod, reg, rm) - +func numRegReg(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { if sizeOf(number) == 1 { - return append( - code, - rex, - opCode8, - modRM, - byte(number), - ) + code = regReg(code, reg, rm, opCode8) + return append(code, byte(number)) } + code = regReg(code, reg, rm, opCode32) return append( code, - rex, - opCode32, - modRM, byte(number), byte(number>>8), byte(number>>16), diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 81d5299..f3529ec 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -31,18 +31,24 @@ func (a *Assembler) Finalize() ([]byte, []byte) { switch operands := x.Data.(type) { case *RegisterNumber: code = x64.AddRegNum(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.AddRegReg(code, operands.Destination, operands.Source) } case SUB: switch operands := x.Data.(type) { case *RegisterNumber: code = x64.SubRegNum(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.SubRegReg(code, operands.Destination, operands.Source) } case MUL: switch operands := x.Data.(type) { case *RegisterNumber: code = x64.MulRegNum(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.MulRegReg(code, operands.Destination, operands.Source) } case DIV: @@ -65,6 +71,16 @@ func (a *Assembler) Finalize() ([]byte, []byte) { code = x64.PopReg(code, x64.RDX) code = x64.PopReg(code, x64.RAX) } + + case *RegisterRegister: + code = x64.PushReg(code, x64.RAX) + code = x64.PushReg(code, x64.RDX) + code = x64.MoveRegReg64(code, x64.RAX, operands.Destination) + code = x64.ExtendRAXToRDX(code) + code = x64.DivReg(code, operands.Source) + code = x64.MoveRegReg64(code, operands.Destination, x64.RAX) + code = x64.PopReg(code, x64.RDX) + code = x64.PopReg(code, x64.RAX) } case CALL: diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 7eb041f..92842e2 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -13,6 +13,17 @@ func (a *Assembler) RegisterNumber(mnemonic Mnemonic, reg cpu.Register, number i }) } +// RegisterRegister adds an instruction using two registers. +func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right cpu.Register) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &RegisterRegister{ + Destination: left, + Source: right, + }, + }) +} + // MoveRegisterRegister moves a register value into another register. func (a *Assembler) MoveRegisterRegister(destination cpu.Register, source cpu.Register) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 37b7719..dc5a206 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -55,13 +55,13 @@ func (expr *Expression) Close() { } // EachLeaf iterates through all leaves in the tree. -func (expr *Expression) EachLeaf(callBack func(*Expression) error) error { +func (expr *Expression) EachLeaf(call func(*Expression) error) error { if expr.IsLeaf() { - return callBack(expr) + return call(expr) } for _, child := range expr.Children { - err := child.EachLeaf(callBack) + err := child.EachLeaf(call) if err != nil { return err @@ -71,6 +71,28 @@ func (expr *Expression) EachLeaf(callBack func(*Expression) error) error { return nil } +// EachOperation iterates through all the operations in the tree. +func (expr *Expression) EachOperation(call func(*Expression) error) error { + if expr.IsLeaf() { + return nil + } + + // Don't descend into the parameters of function calls + if expr.Token.Text() == "λ" { + return call(expr) + } + + for _, child := range expr.Children { + err := child.EachOperation(call) + + if err != nil { + return err + } + } + + return call(expr) +} + // RemoveChild removes a child from the expression. func (expr *Expression) RemoveChild(child *Expression) { for i, c := range expr.Children { From 871ebc147f4877f30d1f6a6d65ae7412b5cffdae Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 25 Jun 2024 23:37:14 +0200 Subject: [PATCH 0143/1012] Improved function names --- src/build/arch/x64/Add.go | 37 ++----------- src/build/arch/x64/Add_test.go | 4 +- src/build/arch/x64/Div.go | 4 +- src/build/arch/x64/Div_test.go | 2 +- src/build/arch/x64/Move.go | 8 +-- src/build/arch/x64/Mul.go | 10 ++-- src/build/arch/x64/Mul_test.go | 4 +- src/build/arch/x64/Pop.go | 4 +- src/build/arch/x64/Pop_test.go | 2 +- src/build/arch/x64/Push.go | 4 +- src/build/arch/x64/Push_test.go | 2 +- src/build/arch/x64/Sub.go | 10 ++-- src/build/arch/x64/Sub_test.go | 2 +- src/build/arch/x64/Syscall.go | 2 +- src/build/arch/x64/regReg.go | 29 ++++++++++ .../arch/x64/{numRegReg.go => regRegNum.go} | 4 +- src/build/arch/x64/x64_test.go | 4 +- src/build/asm/Assembler.go | 54 +++++++++---------- src/build/expression/Expression.go | 22 -------- 19 files changed, 94 insertions(+), 114 deletions(-) create mode 100644 src/build/arch/x64/regReg.go rename src/build/arch/x64/{numRegReg.go => regRegNum.go} (71%) diff --git a/src/build/arch/x64/Add.go b/src/build/arch/x64/Add.go index 5e28c0b..b92cc60 100644 --- a/src/build/arch/x64/Add.go +++ b/src/build/arch/x64/Add.go @@ -4,39 +4,12 @@ import ( "git.akyoto.dev/cli/q/src/build/cpu" ) -// AddRegNum adds a number to the given register. -func AddRegNum(code []byte, destination cpu.Register, number int) []byte { - return numRegReg(code, 0, byte(destination), number, 0x83, 0x81) +// AddRegisterNumber adds a number to the given register. +func AddRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + return regRegNum(code, 0, byte(destination), number, 0x83, 0x81) } -// AddRegReg adds a number to the given register. -func AddRegReg(code []byte, destination cpu.Register, operand cpu.Register) []byte { +// AddRegisterRegister adds a register value into another register. +func AddRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { return regReg(code, byte(operand), byte(destination), 0x01) } - -func regReg(code []byte, reg byte, rm byte, opCodes ...byte) []byte { - w := byte(1) // Indicates a 64-bit register. - r := byte(0) // Extension to the "reg" field in ModRM. - x := byte(0) // Extension to the SIB index field. - b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). - mod := byte(0b11) // Direct addressing mode, no register offsets. - - if reg > 0b111 { - r = 1 - reg &= 0b111 - } - - if rm > 0b111 { - b = 1 - rm &= 0b111 - } - - rex := REX(w, r, x, b) - modRM := ModRM(mod, reg, rm) - - code = append(code, rex) - code = append(code, opCodes...) - code = append(code, modRM) - - return code -} diff --git a/src/build/arch/x64/Add_test.go b/src/build/arch/x64/Add_test.go index ca21dc7..607b467 100644 --- a/src/build/arch/x64/Add_test.go +++ b/src/build/arch/x64/Add_test.go @@ -51,7 +51,7 @@ func TestAddRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("add %s, %x", pattern.Register, pattern.Number) - code := x64.AddRegNum(nil, pattern.Register, pattern.Number) + code := x64.AddRegisterNumber(nil, pattern.Register, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } @@ -82,7 +82,7 @@ func TestAddRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("add %s, %s", pattern.Left, pattern.Right) - code := x64.AddRegReg(nil, pattern.Left, pattern.Right) + code := x64.AddRegisterRegister(nil, pattern.Left, pattern.Right) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/Div.go b/src/build/arch/x64/Div.go index 0ed4a56..5c567bd 100644 --- a/src/build/arch/x64/Div.go +++ b/src/build/arch/x64/Div.go @@ -2,8 +2,8 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" -// DivReg divides RDX:RAX by the value in the register. -func DivReg(code []byte, divisor cpu.Register) []byte { +// DivRegister divides RDX:RAX by the value in the register. +func DivRegister(code []byte, divisor cpu.Register) []byte { rex := byte(0x48) if divisor >= 8 { diff --git a/src/build/arch/x64/Div_test.go b/src/build/arch/x64/Div_test.go index 04ed063..267250e 100644 --- a/src/build/arch/x64/Div_test.go +++ b/src/build/arch/x64/Div_test.go @@ -33,7 +33,7 @@ func TestDivRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("idiv %s", pattern.Register) - code := x64.DivReg(nil, pattern.Register) + code := x64.DivRegister(nil, pattern.Register) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/Move.go b/src/build/arch/x64/Move.go index 63ffaf0..9f57728 100644 --- a/src/build/arch/x64/Move.go +++ b/src/build/arch/x64/Move.go @@ -2,8 +2,8 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" -// MoveRegNum32 moves a 32 bit integer into the given register. -func MoveRegNum32(code []byte, destination cpu.Register, number uint32) []byte { +// MoveRegisterNumber32 moves a 32 bit integer into the given register. +func MoveRegisterNumber32(code []byte, destination cpu.Register, number uint32) []byte { if destination >= 8 { code = append(code, REX(0, 0, 0, 1)) destination -= 8 @@ -19,8 +19,8 @@ func MoveRegNum32(code []byte, destination cpu.Register, number uint32) []byte { ) } -// MoveRegReg64 moves a register value into another register. -func MoveRegReg64(code []byte, destination cpu.Register, source cpu.Register) []byte { +// MoveRegisterRegister64 moves a register value into another register. +func MoveRegisterRegister64(code []byte, destination cpu.Register, source cpu.Register) []byte { r := byte(0) // Extension to the "reg" field in ModRM. b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). diff --git a/src/build/arch/x64/Mul.go b/src/build/arch/x64/Mul.go index 45254b2..a041799 100644 --- a/src/build/arch/x64/Mul.go +++ b/src/build/arch/x64/Mul.go @@ -2,12 +2,12 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" -// MulRegNum multiplies a register with a number. -func MulRegNum(code []byte, destination cpu.Register, number int) []byte { - return numRegReg(code, byte(destination), byte(destination), number, 0x6B, 0x69) +// MulRegisterNumber multiplies a register with a number. +func MulRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + return regRegNum(code, byte(destination), byte(destination), number, 0x6B, 0x69) } -// MulRegReg multiplies a register with another register. -func MulRegReg(code []byte, destination cpu.Register, operand cpu.Register) []byte { +// MulRegisterRegister multiplies a register with another register. +func MulRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { return regReg(code, byte(destination), byte(operand), 0x0F, 0xAF) } diff --git a/src/build/arch/x64/Mul_test.go b/src/build/arch/x64/Mul_test.go index ffea393..5c029dd 100644 --- a/src/build/arch/x64/Mul_test.go +++ b/src/build/arch/x64/Mul_test.go @@ -51,7 +51,7 @@ func TestMulRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("mul %s, %x", pattern.Register, pattern.Number) - code := x64.MulRegNum(nil, pattern.Register, pattern.Number) + code := x64.MulRegisterNumber(nil, pattern.Register, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } @@ -82,7 +82,7 @@ func TestMulRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("mul %s, %s", pattern.Left, pattern.Right) - code := x64.MulRegReg(nil, pattern.Left, pattern.Right) + code := x64.MulRegisterRegister(nil, pattern.Left, pattern.Right) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/Pop.go b/src/build/arch/x64/Pop.go index 746f3a0..4ce82ec 100644 --- a/src/build/arch/x64/Pop.go +++ b/src/build/arch/x64/Pop.go @@ -2,8 +2,8 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" -// PopReg pops a value from the stack and saves it into the register. -func PopReg(code []byte, register cpu.Register) []byte { +// PopRegister pops a value from the stack and saves it into the register. +func PopRegister(code []byte, register cpu.Register) []byte { if register >= 8 { code = append(code, REX(0, 0, 0, 1)) register -= 8 diff --git a/src/build/arch/x64/Pop_test.go b/src/build/arch/x64/Pop_test.go index 64acf13..5375bde 100644 --- a/src/build/arch/x64/Pop_test.go +++ b/src/build/arch/x64/Pop_test.go @@ -33,7 +33,7 @@ func TestPopRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("pop %s", pattern.Register) - code := x64.PopReg(nil, pattern.Register) + code := x64.PopRegister(nil, pattern.Register) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/Push.go b/src/build/arch/x64/Push.go index 48bed9d..944d1e4 100644 --- a/src/build/arch/x64/Push.go +++ b/src/build/arch/x64/Push.go @@ -2,8 +2,8 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" -// PushReg pushes the value inside the register onto the stack. -func PushReg(code []byte, register cpu.Register) []byte { +// PushRegister pushes the value inside the register onto the stack. +func PushRegister(code []byte, register cpu.Register) []byte { if register >= 8 { code = append(code, REX(0, 0, 0, 1)) register -= 8 diff --git a/src/build/arch/x64/Push_test.go b/src/build/arch/x64/Push_test.go index 22994bb..9177532 100644 --- a/src/build/arch/x64/Push_test.go +++ b/src/build/arch/x64/Push_test.go @@ -33,7 +33,7 @@ func TestPushRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("push %s", pattern.Register) - code := x64.PushReg(nil, pattern.Register) + code := x64.PushRegister(nil, pattern.Register) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/Sub.go b/src/build/arch/x64/Sub.go index 60fc81d..4dd9b23 100644 --- a/src/build/arch/x64/Sub.go +++ b/src/build/arch/x64/Sub.go @@ -4,12 +4,12 @@ import ( "git.akyoto.dev/cli/q/src/build/cpu" ) -// SubRegNum subtracts a number from the given register. -func SubRegNum(code []byte, destination cpu.Register, number int) []byte { - return numRegReg(code, 0b101, byte(destination), number, 0x83, 0x81) +// SubRegisterNumber subtracts a number from the given register. +func SubRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + return regRegNum(code, 0b101, byte(destination), number, 0x83, 0x81) } -// SubRegReg subtracts a register value from another register. -func SubRegReg(code []byte, destination cpu.Register, operand cpu.Register) []byte { +// SubRegisterRegister subtracts a register value from another register. +func SubRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { return regReg(code, byte(operand), byte(destination), 0x29) } diff --git a/src/build/arch/x64/Sub_test.go b/src/build/arch/x64/Sub_test.go index 5dcf691..2380098 100644 --- a/src/build/arch/x64/Sub_test.go +++ b/src/build/arch/x64/Sub_test.go @@ -51,7 +51,7 @@ func TestSubRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("sub %s, %x", pattern.Register, pattern.Number) - code := x64.SubRegNum(nil, pattern.Register, pattern.Number) + code := x64.SubRegisterNumber(nil, pattern.Register, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/Syscall.go b/src/build/arch/x64/Syscall.go index 94b07d2..31e2b96 100644 --- a/src/build/arch/x64/Syscall.go +++ b/src/build/arch/x64/Syscall.go @@ -2,5 +2,5 @@ package x64 // Syscall is the primary way to communicate with the OS kernel. func Syscall(code []byte) []byte { - return append(code, 0x0f, 0x05) + return append(code, 0x0F, 0x05) } diff --git a/src/build/arch/x64/regReg.go b/src/build/arch/x64/regReg.go new file mode 100644 index 0000000..57b0f00 --- /dev/null +++ b/src/build/arch/x64/regReg.go @@ -0,0 +1,29 @@ +package x64 + +// regReg encodes an operation using 2 registers. +func regReg(code []byte, reg byte, rm byte, opCodes ...byte) []byte { + w := byte(1) // Indicates a 64-bit register. + r := byte(0) // Extension to the "reg" field in ModRM. + x := byte(0) // Extension to the SIB index field. + b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). + mod := byte(0b11) // Direct addressing mode, no register offsets. + + if reg > 0b111 { + r = 1 + reg &= 0b111 + } + + if rm > 0b111 { + b = 1 + rm &= 0b111 + } + + rex := REX(w, r, x, b) + modRM := ModRM(mod, reg, rm) + + code = append(code, rex) + code = append(code, opCodes...) + code = append(code, modRM) + + return code +} diff --git a/src/build/arch/x64/numRegReg.go b/src/build/arch/x64/regRegNum.go similarity index 71% rename from src/build/arch/x64/numRegReg.go rename to src/build/arch/x64/regRegNum.go index bb7fa96..8a04be4 100644 --- a/src/build/arch/x64/numRegReg.go +++ b/src/build/arch/x64/regRegNum.go @@ -1,7 +1,7 @@ package x64 -// numRegReg encodes an instruction with up to two registers and a number parameter. -func numRegReg(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { +// regRegNum encodes an instruction with up to two registers and a number parameter. +func regRegNum(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { if sizeOf(number) == 1 { code = regReg(code, reg, rm, opCode8) return append(code, byte(number)) diff --git a/src/build/arch/x64/x64_test.go b/src/build/arch/x64/x64_test.go index b39570d..8369738 100644 --- a/src/build/arch/x64/x64_test.go +++ b/src/build/arch/x64/x64_test.go @@ -9,8 +9,8 @@ import ( func TestX64(t *testing.T) { assert.DeepEqual(t, x64.Call([]byte{}, 1), []byte{0xe8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegNum32([]byte{}, 0, 1), []byte{0xb8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegNum32([]byte{}, 1, 1), []byte{0xb9, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegisterNumber32([]byte{}, 0, 1), []byte{0xb8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegisterNumber32([]byte{}, 1, 1), []byte{0xb9, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x64.Return([]byte{}), []byte{0xc3}) assert.DeepEqual(t, x64.Syscall([]byte{}), []byte{0x0f, 0x05}) } diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index f3529ec..e2e1483 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -30,57 +30,57 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case ADD: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.AddRegNum(code, operands.Register, operands.Number) + code = x64.AddRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.AddRegReg(code, operands.Destination, operands.Source) + code = x64.AddRegisterRegister(code, operands.Destination, operands.Source) } case SUB: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.SubRegNum(code, operands.Register, operands.Number) + code = x64.SubRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.SubRegReg(code, operands.Destination, operands.Source) + code = x64.SubRegisterRegister(code, operands.Destination, operands.Source) } case MUL: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.MulRegNum(code, operands.Register, operands.Number) + code = x64.MulRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.MulRegReg(code, operands.Destination, operands.Source) + code = x64.MulRegisterRegister(code, operands.Destination, operands.Source) } case DIV: switch operands := x.Data.(type) { case *RegisterNumber: if operands.Register == x64.RAX { - code = x64.PushReg(code, x64.RCX) - code = x64.MoveRegNum32(code, x64.RCX, uint32(operands.Number)) + code = x64.PushRegister(code, x64.RCX) + code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number)) code = x64.ExtendRAXToRDX(code) - code = x64.DivReg(code, x64.RCX) - code = x64.PopReg(code, x64.RCX) + code = x64.DivRegister(code, x64.RCX) + code = x64.PopRegister(code, x64.RCX) } else { - code = x64.PushReg(code, x64.RAX) - code = x64.PushReg(code, x64.RDX) - code = x64.MoveRegReg64(code, x64.RAX, operands.Register) - code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number)) + code = x64.PushRegister(code, x64.RAX) + code = x64.PushRegister(code, x64.RDX) + code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) + code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) code = x64.ExtendRAXToRDX(code) - code = x64.DivReg(code, operands.Register) - code = x64.MoveRegReg64(code, operands.Register, x64.RAX) - code = x64.PopReg(code, x64.RDX) - code = x64.PopReg(code, x64.RAX) + code = x64.DivRegister(code, operands.Register) + code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX) + code = x64.PopRegister(code, x64.RDX) + code = x64.PopRegister(code, x64.RAX) } case *RegisterRegister: - code = x64.PushReg(code, x64.RAX) - code = x64.PushReg(code, x64.RDX) - code = x64.MoveRegReg64(code, x64.RAX, operands.Destination) + code = x64.PushRegister(code, x64.RAX) + code = x64.PushRegister(code, x64.RDX) + code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) code = x64.ExtendRAXToRDX(code) - code = x64.DivReg(code, operands.Source) - code = x64.MoveRegReg64(code, operands.Destination, x64.RAX) - code = x64.PopReg(code, x64.RDX) - code = x64.PopReg(code, x64.RAX) + code = x64.DivRegister(code, operands.Source) + code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX) + code = x64.PopRegister(code, x64.RDX) + code = x64.PopRegister(code, x64.RAX) } case CALL: @@ -121,10 +121,10 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case MOVE: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number)) + code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) case *RegisterRegister: - code = x64.MoveRegReg64(code, operands.Destination, operands.Source) + code = x64.MoveRegisterRegister64(code, operands.Destination, operands.Source) } case RETURN: diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index dc5a206..84b9249 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -71,28 +71,6 @@ func (expr *Expression) EachLeaf(call func(*Expression) error) error { return nil } -// EachOperation iterates through all the operations in the tree. -func (expr *Expression) EachOperation(call func(*Expression) error) error { - if expr.IsLeaf() { - return nil - } - - // Don't descend into the parameters of function calls - if expr.Token.Text() == "λ" { - return call(expr) - } - - for _, child := range expr.Children { - err := child.EachOperation(call) - - if err != nil { - return err - } - } - - return call(expr) -} - // RemoveChild removes a child from the expression. func (expr *Expression) RemoveChild(child *Expression) { for i, c := range expr.Children { From e69d695f6bf94d23544821b77f0bfec17e78ec50 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 25 Jun 2024 23:37:14 +0200 Subject: [PATCH 0144/1012] Improved function names --- src/build/arch/x64/Add.go | 37 ++----------- src/build/arch/x64/Add_test.go | 4 +- src/build/arch/x64/Div.go | 4 +- src/build/arch/x64/Div_test.go | 2 +- src/build/arch/x64/Move.go | 8 +-- src/build/arch/x64/Mul.go | 10 ++-- src/build/arch/x64/Mul_test.go | 4 +- src/build/arch/x64/Pop.go | 4 +- src/build/arch/x64/Pop_test.go | 2 +- src/build/arch/x64/Push.go | 4 +- src/build/arch/x64/Push_test.go | 2 +- src/build/arch/x64/Sub.go | 10 ++-- src/build/arch/x64/Sub_test.go | 2 +- src/build/arch/x64/Syscall.go | 2 +- src/build/arch/x64/regReg.go | 29 ++++++++++ .../arch/x64/{numRegReg.go => regRegNum.go} | 4 +- src/build/arch/x64/x64_test.go | 4 +- src/build/asm/Assembler.go | 54 +++++++++---------- src/build/expression/Expression.go | 22 -------- 19 files changed, 94 insertions(+), 114 deletions(-) create mode 100644 src/build/arch/x64/regReg.go rename src/build/arch/x64/{numRegReg.go => regRegNum.go} (71%) diff --git a/src/build/arch/x64/Add.go b/src/build/arch/x64/Add.go index 5e28c0b..b92cc60 100644 --- a/src/build/arch/x64/Add.go +++ b/src/build/arch/x64/Add.go @@ -4,39 +4,12 @@ import ( "git.akyoto.dev/cli/q/src/build/cpu" ) -// AddRegNum adds a number to the given register. -func AddRegNum(code []byte, destination cpu.Register, number int) []byte { - return numRegReg(code, 0, byte(destination), number, 0x83, 0x81) +// AddRegisterNumber adds a number to the given register. +func AddRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + return regRegNum(code, 0, byte(destination), number, 0x83, 0x81) } -// AddRegReg adds a number to the given register. -func AddRegReg(code []byte, destination cpu.Register, operand cpu.Register) []byte { +// AddRegisterRegister adds a register value into another register. +func AddRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { return regReg(code, byte(operand), byte(destination), 0x01) } - -func regReg(code []byte, reg byte, rm byte, opCodes ...byte) []byte { - w := byte(1) // Indicates a 64-bit register. - r := byte(0) // Extension to the "reg" field in ModRM. - x := byte(0) // Extension to the SIB index field. - b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). - mod := byte(0b11) // Direct addressing mode, no register offsets. - - if reg > 0b111 { - r = 1 - reg &= 0b111 - } - - if rm > 0b111 { - b = 1 - rm &= 0b111 - } - - rex := REX(w, r, x, b) - modRM := ModRM(mod, reg, rm) - - code = append(code, rex) - code = append(code, opCodes...) - code = append(code, modRM) - - return code -} diff --git a/src/build/arch/x64/Add_test.go b/src/build/arch/x64/Add_test.go index ca21dc7..607b467 100644 --- a/src/build/arch/x64/Add_test.go +++ b/src/build/arch/x64/Add_test.go @@ -51,7 +51,7 @@ func TestAddRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("add %s, %x", pattern.Register, pattern.Number) - code := x64.AddRegNum(nil, pattern.Register, pattern.Number) + code := x64.AddRegisterNumber(nil, pattern.Register, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } @@ -82,7 +82,7 @@ func TestAddRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("add %s, %s", pattern.Left, pattern.Right) - code := x64.AddRegReg(nil, pattern.Left, pattern.Right) + code := x64.AddRegisterRegister(nil, pattern.Left, pattern.Right) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/Div.go b/src/build/arch/x64/Div.go index 0ed4a56..5c567bd 100644 --- a/src/build/arch/x64/Div.go +++ b/src/build/arch/x64/Div.go @@ -2,8 +2,8 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" -// DivReg divides RDX:RAX by the value in the register. -func DivReg(code []byte, divisor cpu.Register) []byte { +// DivRegister divides RDX:RAX by the value in the register. +func DivRegister(code []byte, divisor cpu.Register) []byte { rex := byte(0x48) if divisor >= 8 { diff --git a/src/build/arch/x64/Div_test.go b/src/build/arch/x64/Div_test.go index 04ed063..267250e 100644 --- a/src/build/arch/x64/Div_test.go +++ b/src/build/arch/x64/Div_test.go @@ -33,7 +33,7 @@ func TestDivRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("idiv %s", pattern.Register) - code := x64.DivReg(nil, pattern.Register) + code := x64.DivRegister(nil, pattern.Register) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/Move.go b/src/build/arch/x64/Move.go index 63ffaf0..9f57728 100644 --- a/src/build/arch/x64/Move.go +++ b/src/build/arch/x64/Move.go @@ -2,8 +2,8 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" -// MoveRegNum32 moves a 32 bit integer into the given register. -func MoveRegNum32(code []byte, destination cpu.Register, number uint32) []byte { +// MoveRegisterNumber32 moves a 32 bit integer into the given register. +func MoveRegisterNumber32(code []byte, destination cpu.Register, number uint32) []byte { if destination >= 8 { code = append(code, REX(0, 0, 0, 1)) destination -= 8 @@ -19,8 +19,8 @@ func MoveRegNum32(code []byte, destination cpu.Register, number uint32) []byte { ) } -// MoveRegReg64 moves a register value into another register. -func MoveRegReg64(code []byte, destination cpu.Register, source cpu.Register) []byte { +// MoveRegisterRegister64 moves a register value into another register. +func MoveRegisterRegister64(code []byte, destination cpu.Register, source cpu.Register) []byte { r := byte(0) // Extension to the "reg" field in ModRM. b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). diff --git a/src/build/arch/x64/Mul.go b/src/build/arch/x64/Mul.go index 45254b2..a041799 100644 --- a/src/build/arch/x64/Mul.go +++ b/src/build/arch/x64/Mul.go @@ -2,12 +2,12 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" -// MulRegNum multiplies a register with a number. -func MulRegNum(code []byte, destination cpu.Register, number int) []byte { - return numRegReg(code, byte(destination), byte(destination), number, 0x6B, 0x69) +// MulRegisterNumber multiplies a register with a number. +func MulRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + return regRegNum(code, byte(destination), byte(destination), number, 0x6B, 0x69) } -// MulRegReg multiplies a register with another register. -func MulRegReg(code []byte, destination cpu.Register, operand cpu.Register) []byte { +// MulRegisterRegister multiplies a register with another register. +func MulRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { return regReg(code, byte(destination), byte(operand), 0x0F, 0xAF) } diff --git a/src/build/arch/x64/Mul_test.go b/src/build/arch/x64/Mul_test.go index ffea393..5c029dd 100644 --- a/src/build/arch/x64/Mul_test.go +++ b/src/build/arch/x64/Mul_test.go @@ -51,7 +51,7 @@ func TestMulRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("mul %s, %x", pattern.Register, pattern.Number) - code := x64.MulRegNum(nil, pattern.Register, pattern.Number) + code := x64.MulRegisterNumber(nil, pattern.Register, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } @@ -82,7 +82,7 @@ func TestMulRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("mul %s, %s", pattern.Left, pattern.Right) - code := x64.MulRegReg(nil, pattern.Left, pattern.Right) + code := x64.MulRegisterRegister(nil, pattern.Left, pattern.Right) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/Pop.go b/src/build/arch/x64/Pop.go index 746f3a0..4ce82ec 100644 --- a/src/build/arch/x64/Pop.go +++ b/src/build/arch/x64/Pop.go @@ -2,8 +2,8 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" -// PopReg pops a value from the stack and saves it into the register. -func PopReg(code []byte, register cpu.Register) []byte { +// PopRegister pops a value from the stack and saves it into the register. +func PopRegister(code []byte, register cpu.Register) []byte { if register >= 8 { code = append(code, REX(0, 0, 0, 1)) register -= 8 diff --git a/src/build/arch/x64/Pop_test.go b/src/build/arch/x64/Pop_test.go index 64acf13..5375bde 100644 --- a/src/build/arch/x64/Pop_test.go +++ b/src/build/arch/x64/Pop_test.go @@ -33,7 +33,7 @@ func TestPopRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("pop %s", pattern.Register) - code := x64.PopReg(nil, pattern.Register) + code := x64.PopRegister(nil, pattern.Register) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/Push.go b/src/build/arch/x64/Push.go index 48bed9d..944d1e4 100644 --- a/src/build/arch/x64/Push.go +++ b/src/build/arch/x64/Push.go @@ -2,8 +2,8 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" -// PushReg pushes the value inside the register onto the stack. -func PushReg(code []byte, register cpu.Register) []byte { +// PushRegister pushes the value inside the register onto the stack. +func PushRegister(code []byte, register cpu.Register) []byte { if register >= 8 { code = append(code, REX(0, 0, 0, 1)) register -= 8 diff --git a/src/build/arch/x64/Push_test.go b/src/build/arch/x64/Push_test.go index 22994bb..9177532 100644 --- a/src/build/arch/x64/Push_test.go +++ b/src/build/arch/x64/Push_test.go @@ -33,7 +33,7 @@ func TestPushRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("push %s", pattern.Register) - code := x64.PushReg(nil, pattern.Register) + code := x64.PushRegister(nil, pattern.Register) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/Sub.go b/src/build/arch/x64/Sub.go index 60fc81d..4dd9b23 100644 --- a/src/build/arch/x64/Sub.go +++ b/src/build/arch/x64/Sub.go @@ -4,12 +4,12 @@ import ( "git.akyoto.dev/cli/q/src/build/cpu" ) -// SubRegNum subtracts a number from the given register. -func SubRegNum(code []byte, destination cpu.Register, number int) []byte { - return numRegReg(code, 0b101, byte(destination), number, 0x83, 0x81) +// SubRegisterNumber subtracts a number from the given register. +func SubRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + return regRegNum(code, 0b101, byte(destination), number, 0x83, 0x81) } -// SubRegReg subtracts a register value from another register. -func SubRegReg(code []byte, destination cpu.Register, operand cpu.Register) []byte { +// SubRegisterRegister subtracts a register value from another register. +func SubRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { return regReg(code, byte(operand), byte(destination), 0x29) } diff --git a/src/build/arch/x64/Sub_test.go b/src/build/arch/x64/Sub_test.go index 5dcf691..2380098 100644 --- a/src/build/arch/x64/Sub_test.go +++ b/src/build/arch/x64/Sub_test.go @@ -51,7 +51,7 @@ func TestSubRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("sub %s, %x", pattern.Register, pattern.Number) - code := x64.SubRegNum(nil, pattern.Register, pattern.Number) + code := x64.SubRegisterNumber(nil, pattern.Register, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/Syscall.go b/src/build/arch/x64/Syscall.go index 94b07d2..31e2b96 100644 --- a/src/build/arch/x64/Syscall.go +++ b/src/build/arch/x64/Syscall.go @@ -2,5 +2,5 @@ package x64 // Syscall is the primary way to communicate with the OS kernel. func Syscall(code []byte) []byte { - return append(code, 0x0f, 0x05) + return append(code, 0x0F, 0x05) } diff --git a/src/build/arch/x64/regReg.go b/src/build/arch/x64/regReg.go new file mode 100644 index 0000000..57b0f00 --- /dev/null +++ b/src/build/arch/x64/regReg.go @@ -0,0 +1,29 @@ +package x64 + +// regReg encodes an operation using 2 registers. +func regReg(code []byte, reg byte, rm byte, opCodes ...byte) []byte { + w := byte(1) // Indicates a 64-bit register. + r := byte(0) // Extension to the "reg" field in ModRM. + x := byte(0) // Extension to the SIB index field. + b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). + mod := byte(0b11) // Direct addressing mode, no register offsets. + + if reg > 0b111 { + r = 1 + reg &= 0b111 + } + + if rm > 0b111 { + b = 1 + rm &= 0b111 + } + + rex := REX(w, r, x, b) + modRM := ModRM(mod, reg, rm) + + code = append(code, rex) + code = append(code, opCodes...) + code = append(code, modRM) + + return code +} diff --git a/src/build/arch/x64/numRegReg.go b/src/build/arch/x64/regRegNum.go similarity index 71% rename from src/build/arch/x64/numRegReg.go rename to src/build/arch/x64/regRegNum.go index bb7fa96..8a04be4 100644 --- a/src/build/arch/x64/numRegReg.go +++ b/src/build/arch/x64/regRegNum.go @@ -1,7 +1,7 @@ package x64 -// numRegReg encodes an instruction with up to two registers and a number parameter. -func numRegReg(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { +// regRegNum encodes an instruction with up to two registers and a number parameter. +func regRegNum(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { if sizeOf(number) == 1 { code = regReg(code, reg, rm, opCode8) return append(code, byte(number)) diff --git a/src/build/arch/x64/x64_test.go b/src/build/arch/x64/x64_test.go index b39570d..8369738 100644 --- a/src/build/arch/x64/x64_test.go +++ b/src/build/arch/x64/x64_test.go @@ -9,8 +9,8 @@ import ( func TestX64(t *testing.T) { assert.DeepEqual(t, x64.Call([]byte{}, 1), []byte{0xe8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegNum32([]byte{}, 0, 1), []byte{0xb8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegNum32([]byte{}, 1, 1), []byte{0xb9, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegisterNumber32([]byte{}, 0, 1), []byte{0xb8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegisterNumber32([]byte{}, 1, 1), []byte{0xb9, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x64.Return([]byte{}), []byte{0xc3}) assert.DeepEqual(t, x64.Syscall([]byte{}), []byte{0x0f, 0x05}) } diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index f3529ec..e2e1483 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -30,57 +30,57 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case ADD: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.AddRegNum(code, operands.Register, operands.Number) + code = x64.AddRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.AddRegReg(code, operands.Destination, operands.Source) + code = x64.AddRegisterRegister(code, operands.Destination, operands.Source) } case SUB: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.SubRegNum(code, operands.Register, operands.Number) + code = x64.SubRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.SubRegReg(code, operands.Destination, operands.Source) + code = x64.SubRegisterRegister(code, operands.Destination, operands.Source) } case MUL: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.MulRegNum(code, operands.Register, operands.Number) + code = x64.MulRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.MulRegReg(code, operands.Destination, operands.Source) + code = x64.MulRegisterRegister(code, operands.Destination, operands.Source) } case DIV: switch operands := x.Data.(type) { case *RegisterNumber: if operands.Register == x64.RAX { - code = x64.PushReg(code, x64.RCX) - code = x64.MoveRegNum32(code, x64.RCX, uint32(operands.Number)) + code = x64.PushRegister(code, x64.RCX) + code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number)) code = x64.ExtendRAXToRDX(code) - code = x64.DivReg(code, x64.RCX) - code = x64.PopReg(code, x64.RCX) + code = x64.DivRegister(code, x64.RCX) + code = x64.PopRegister(code, x64.RCX) } else { - code = x64.PushReg(code, x64.RAX) - code = x64.PushReg(code, x64.RDX) - code = x64.MoveRegReg64(code, x64.RAX, operands.Register) - code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number)) + code = x64.PushRegister(code, x64.RAX) + code = x64.PushRegister(code, x64.RDX) + code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) + code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) code = x64.ExtendRAXToRDX(code) - code = x64.DivReg(code, operands.Register) - code = x64.MoveRegReg64(code, operands.Register, x64.RAX) - code = x64.PopReg(code, x64.RDX) - code = x64.PopReg(code, x64.RAX) + code = x64.DivRegister(code, operands.Register) + code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX) + code = x64.PopRegister(code, x64.RDX) + code = x64.PopRegister(code, x64.RAX) } case *RegisterRegister: - code = x64.PushReg(code, x64.RAX) - code = x64.PushReg(code, x64.RDX) - code = x64.MoveRegReg64(code, x64.RAX, operands.Destination) + code = x64.PushRegister(code, x64.RAX) + code = x64.PushRegister(code, x64.RDX) + code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) code = x64.ExtendRAXToRDX(code) - code = x64.DivReg(code, operands.Source) - code = x64.MoveRegReg64(code, operands.Destination, x64.RAX) - code = x64.PopReg(code, x64.RDX) - code = x64.PopReg(code, x64.RAX) + code = x64.DivRegister(code, operands.Source) + code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX) + code = x64.PopRegister(code, x64.RDX) + code = x64.PopRegister(code, x64.RAX) } case CALL: @@ -121,10 +121,10 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case MOVE: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number)) + code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) case *RegisterRegister: - code = x64.MoveRegReg64(code, operands.Destination, operands.Source) + code = x64.MoveRegisterRegister64(code, operands.Destination, operands.Source) } case RETURN: diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index dc5a206..84b9249 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -71,28 +71,6 @@ func (expr *Expression) EachLeaf(call func(*Expression) error) error { return nil } -// EachOperation iterates through all the operations in the tree. -func (expr *Expression) EachOperation(call func(*Expression) error) error { - if expr.IsLeaf() { - return nil - } - - // Don't descend into the parameters of function calls - if expr.Token.Text() == "λ" { - return call(expr) - } - - for _, child := range expr.Children { - err := child.EachOperation(call) - - if err != nil { - return err - } - } - - return call(expr) -} - // RemoveChild removes a child from the expression. func (expr *Expression) RemoveChild(child *Expression) { for i, c := range expr.Children { From 47d94acd439a137a2fc0ddb2b5a32ff392e0ac01 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Jun 2024 12:55:22 +0200 Subject: [PATCH 0145/1012] Added more tests --- src/build/arch/x64/Jump.go | 2 +- src/build/arch/x64/Move.go | 40 +++++-------------- src/build/arch/x64/Move_test.go | 71 +++++++++++++++++++++++++++++++++ src/build/arch/x64/Sub_test.go | 31 ++++++++++++++ src/build/arch/x64/regRegNum.go | 10 ++--- 5 files changed, 116 insertions(+), 38 deletions(-) create mode 100644 src/build/arch/x64/Move_test.go diff --git a/src/build/arch/x64/Jump.go b/src/build/arch/x64/Jump.go index 32b8b98..5b3f125 100644 --- a/src/build/arch/x64/Jump.go +++ b/src/build/arch/x64/Jump.go @@ -5,7 +5,7 @@ package x64 func Jump8(code []byte, address int8) []byte { return append( code, - 0xeb, + 0xEB, byte(address), ) } diff --git a/src/build/arch/x64/Move.go b/src/build/arch/x64/Move.go index 9f57728..e266d33 100644 --- a/src/build/arch/x64/Move.go +++ b/src/build/arch/x64/Move.go @@ -1,43 +1,23 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import ( + "encoding/binary" + + "git.akyoto.dev/cli/q/src/build/cpu" +) // MoveRegisterNumber32 moves a 32 bit integer into the given register. func MoveRegisterNumber32(code []byte, destination cpu.Register, number uint32) []byte { - if destination >= 8 { + if destination > 0b111 { code = append(code, REX(0, 0, 0, 1)) - destination -= 8 + destination &= 0b111 } - return append( - code, - 0xb8+byte(destination), - byte(number), - byte(number>>8), - byte(number>>16), - byte(number>>24), - ) + code = append(code, 0xb8+byte(destination)) + return binary.LittleEndian.AppendUint32(code, number) } // MoveRegisterRegister64 moves a register value into another register. func MoveRegisterRegister64(code []byte, destination cpu.Register, source cpu.Register) []byte { - r := byte(0) // Extension to the "reg" field in ModRM. - b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). - - if source >= 8 { - r = 1 - source -= 8 - } - - if destination >= 8 { - b = 1 - destination -= 8 - } - - return append( - code, - REX(1, r, 0, b), - 0x89, - ModRM(0b11, byte(source), byte(destination)), - ) + return regReg(code, byte(source), byte(destination), 0x89) } diff --git a/src/build/arch/x64/Move_test.go b/src/build/arch/x64/Move_test.go new file mode 100644 index 0000000..7d5251e --- /dev/null +++ b/src/build/arch/x64/Move_test.go @@ -0,0 +1,71 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestMoveRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 0x7FFFFFFF, []byte{0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFF, []byte{0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFF, []byte{0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFF, []byte{0xBB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFF, []byte{0xBC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFF, []byte{0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFF, []byte{0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFF, []byte{0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFF, []byte{0x41, 0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFF, []byte{0x41, 0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFF, []byte{0x41, 0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFF, []byte{0x41, 0xBB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFF, []byte{0x41, 0xBC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFF, []byte{0x41, 0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFF, []byte{0x41, 0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFF, []byte{0x41, 0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, %x", pattern.Register, pattern.Number) + code := x64.MoveRegisterNumber32(nil, pattern.Register, uint32(pattern.Number)) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestMoveRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x4C, 0x89, 0xF8}}, + {x64.RCX, x64.R14, []byte{0x4C, 0x89, 0xF1}}, + {x64.RDX, x64.R13, []byte{0x4C, 0x89, 0xEA}}, + {x64.RBX, x64.R12, []byte{0x4C, 0x89, 0xE3}}, + {x64.RSP, x64.R11, []byte{0x4C, 0x89, 0xDC}}, + {x64.RBP, x64.R10, []byte{0x4C, 0x89, 0xD5}}, + {x64.RSI, x64.R9, []byte{0x4C, 0x89, 0xCE}}, + {x64.RDI, x64.R8, []byte{0x4C, 0x89, 0xC7}}, + {x64.R8, x64.RDI, []byte{0x49, 0x89, 0xF8}}, + {x64.R9, x64.RSI, []byte{0x49, 0x89, 0xF1}}, + {x64.R10, x64.RBP, []byte{0x49, 0x89, 0xEA}}, + {x64.R11, x64.RSP, []byte{0x49, 0x89, 0xE3}}, + {x64.R12, x64.RBX, []byte{0x49, 0x89, 0xDC}}, + {x64.R13, x64.RDX, []byte{0x49, 0x89, 0xD5}}, + {x64.R14, x64.RCX, []byte{0x49, 0x89, 0xCE}}, + {x64.R15, x64.RAX, []byte{0x49, 0x89, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, %s", pattern.Left, pattern.Right) + code := x64.MoveRegisterRegister64(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Sub_test.go b/src/build/arch/x64/Sub_test.go index 2380098..69199e9 100644 --- a/src/build/arch/x64/Sub_test.go +++ b/src/build/arch/x64/Sub_test.go @@ -55,3 +55,34 @@ func TestSubRegisterNumber(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestSubRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x4C, 0x29, 0xF8}}, + {x64.RCX, x64.R14, []byte{0x4C, 0x29, 0xF1}}, + {x64.RDX, x64.R13, []byte{0x4C, 0x29, 0xEA}}, + {x64.RBX, x64.R12, []byte{0x4C, 0x29, 0xE3}}, + {x64.RSP, x64.R11, []byte{0x4C, 0x29, 0xDC}}, + {x64.RBP, x64.R10, []byte{0x4C, 0x29, 0xD5}}, + {x64.RSI, x64.R9, []byte{0x4C, 0x29, 0xCE}}, + {x64.RDI, x64.R8, []byte{0x4C, 0x29, 0xC7}}, + {x64.R8, x64.RDI, []byte{0x49, 0x29, 0xF8}}, + {x64.R9, x64.RSI, []byte{0x49, 0x29, 0xF1}}, + {x64.R10, x64.RBP, []byte{0x49, 0x29, 0xEA}}, + {x64.R11, x64.RSP, []byte{0x49, 0x29, 0xE3}}, + {x64.R12, x64.RBX, []byte{0x49, 0x29, 0xDC}}, + {x64.R13, x64.RDX, []byte{0x49, 0x29, 0xD5}}, + {x64.R14, x64.RCX, []byte{0x49, 0x29, 0xCE}}, + {x64.R15, x64.RAX, []byte{0x49, 0x29, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("sub %s, %s", pattern.Left, pattern.Right) + code := x64.SubRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/regRegNum.go b/src/build/arch/x64/regRegNum.go index 8a04be4..0fb41d1 100644 --- a/src/build/arch/x64/regRegNum.go +++ b/src/build/arch/x64/regRegNum.go @@ -1,5 +1,7 @@ package x64 +import "encoding/binary" + // regRegNum encodes an instruction with up to two registers and a number parameter. func regRegNum(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { if sizeOf(number) == 1 { @@ -8,11 +10,5 @@ func regRegNum(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode3 } code = regReg(code, reg, rm, opCode32) - return append( - code, - byte(number), - byte(number>>8), - byte(number>>16), - byte(number>>24), - ) + return binary.LittleEndian.AppendUint32(code, uint32(number)) } From e23c1ece809afbc7691c4a2a8350da1d290d5516 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Jun 2024 12:55:22 +0200 Subject: [PATCH 0146/1012] Added more tests --- src/build/arch/x64/Jump.go | 2 +- src/build/arch/x64/Move.go | 40 +++++-------------- src/build/arch/x64/Move_test.go | 71 +++++++++++++++++++++++++++++++++ src/build/arch/x64/Sub_test.go | 31 ++++++++++++++ src/build/arch/x64/regRegNum.go | 10 ++--- 5 files changed, 116 insertions(+), 38 deletions(-) create mode 100644 src/build/arch/x64/Move_test.go diff --git a/src/build/arch/x64/Jump.go b/src/build/arch/x64/Jump.go index 32b8b98..5b3f125 100644 --- a/src/build/arch/x64/Jump.go +++ b/src/build/arch/x64/Jump.go @@ -5,7 +5,7 @@ package x64 func Jump8(code []byte, address int8) []byte { return append( code, - 0xeb, + 0xEB, byte(address), ) } diff --git a/src/build/arch/x64/Move.go b/src/build/arch/x64/Move.go index 9f57728..e266d33 100644 --- a/src/build/arch/x64/Move.go +++ b/src/build/arch/x64/Move.go @@ -1,43 +1,23 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import ( + "encoding/binary" + + "git.akyoto.dev/cli/q/src/build/cpu" +) // MoveRegisterNumber32 moves a 32 bit integer into the given register. func MoveRegisterNumber32(code []byte, destination cpu.Register, number uint32) []byte { - if destination >= 8 { + if destination > 0b111 { code = append(code, REX(0, 0, 0, 1)) - destination -= 8 + destination &= 0b111 } - return append( - code, - 0xb8+byte(destination), - byte(number), - byte(number>>8), - byte(number>>16), - byte(number>>24), - ) + code = append(code, 0xb8+byte(destination)) + return binary.LittleEndian.AppendUint32(code, number) } // MoveRegisterRegister64 moves a register value into another register. func MoveRegisterRegister64(code []byte, destination cpu.Register, source cpu.Register) []byte { - r := byte(0) // Extension to the "reg" field in ModRM. - b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). - - if source >= 8 { - r = 1 - source -= 8 - } - - if destination >= 8 { - b = 1 - destination -= 8 - } - - return append( - code, - REX(1, r, 0, b), - 0x89, - ModRM(0b11, byte(source), byte(destination)), - ) + return regReg(code, byte(source), byte(destination), 0x89) } diff --git a/src/build/arch/x64/Move_test.go b/src/build/arch/x64/Move_test.go new file mode 100644 index 0000000..7d5251e --- /dev/null +++ b/src/build/arch/x64/Move_test.go @@ -0,0 +1,71 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestMoveRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 0x7FFFFFFF, []byte{0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFF, []byte{0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFF, []byte{0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFF, []byte{0xBB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFF, []byte{0xBC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFF, []byte{0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFF, []byte{0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFF, []byte{0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFF, []byte{0x41, 0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFF, []byte{0x41, 0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFF, []byte{0x41, 0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFF, []byte{0x41, 0xBB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFF, []byte{0x41, 0xBC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFF, []byte{0x41, 0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFF, []byte{0x41, 0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFF, []byte{0x41, 0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, %x", pattern.Register, pattern.Number) + code := x64.MoveRegisterNumber32(nil, pattern.Register, uint32(pattern.Number)) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestMoveRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x4C, 0x89, 0xF8}}, + {x64.RCX, x64.R14, []byte{0x4C, 0x89, 0xF1}}, + {x64.RDX, x64.R13, []byte{0x4C, 0x89, 0xEA}}, + {x64.RBX, x64.R12, []byte{0x4C, 0x89, 0xE3}}, + {x64.RSP, x64.R11, []byte{0x4C, 0x89, 0xDC}}, + {x64.RBP, x64.R10, []byte{0x4C, 0x89, 0xD5}}, + {x64.RSI, x64.R9, []byte{0x4C, 0x89, 0xCE}}, + {x64.RDI, x64.R8, []byte{0x4C, 0x89, 0xC7}}, + {x64.R8, x64.RDI, []byte{0x49, 0x89, 0xF8}}, + {x64.R9, x64.RSI, []byte{0x49, 0x89, 0xF1}}, + {x64.R10, x64.RBP, []byte{0x49, 0x89, 0xEA}}, + {x64.R11, x64.RSP, []byte{0x49, 0x89, 0xE3}}, + {x64.R12, x64.RBX, []byte{0x49, 0x89, 0xDC}}, + {x64.R13, x64.RDX, []byte{0x49, 0x89, 0xD5}}, + {x64.R14, x64.RCX, []byte{0x49, 0x89, 0xCE}}, + {x64.R15, x64.RAX, []byte{0x49, 0x89, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, %s", pattern.Left, pattern.Right) + code := x64.MoveRegisterRegister64(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Sub_test.go b/src/build/arch/x64/Sub_test.go index 2380098..69199e9 100644 --- a/src/build/arch/x64/Sub_test.go +++ b/src/build/arch/x64/Sub_test.go @@ -55,3 +55,34 @@ func TestSubRegisterNumber(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestSubRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x4C, 0x29, 0xF8}}, + {x64.RCX, x64.R14, []byte{0x4C, 0x29, 0xF1}}, + {x64.RDX, x64.R13, []byte{0x4C, 0x29, 0xEA}}, + {x64.RBX, x64.R12, []byte{0x4C, 0x29, 0xE3}}, + {x64.RSP, x64.R11, []byte{0x4C, 0x29, 0xDC}}, + {x64.RBP, x64.R10, []byte{0x4C, 0x29, 0xD5}}, + {x64.RSI, x64.R9, []byte{0x4C, 0x29, 0xCE}}, + {x64.RDI, x64.R8, []byte{0x4C, 0x29, 0xC7}}, + {x64.R8, x64.RDI, []byte{0x49, 0x29, 0xF8}}, + {x64.R9, x64.RSI, []byte{0x49, 0x29, 0xF1}}, + {x64.R10, x64.RBP, []byte{0x49, 0x29, 0xEA}}, + {x64.R11, x64.RSP, []byte{0x49, 0x29, 0xE3}}, + {x64.R12, x64.RBX, []byte{0x49, 0x29, 0xDC}}, + {x64.R13, x64.RDX, []byte{0x49, 0x29, 0xD5}}, + {x64.R14, x64.RCX, []byte{0x49, 0x29, 0xCE}}, + {x64.R15, x64.RAX, []byte{0x49, 0x29, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("sub %s, %s", pattern.Left, pattern.Right) + code := x64.SubRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/regRegNum.go b/src/build/arch/x64/regRegNum.go index 8a04be4..0fb41d1 100644 --- a/src/build/arch/x64/regRegNum.go +++ b/src/build/arch/x64/regRegNum.go @@ -1,5 +1,7 @@ package x64 +import "encoding/binary" + // regRegNum encodes an instruction with up to two registers and a number parameter. func regRegNum(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { if sizeOf(number) == 1 { @@ -8,11 +10,5 @@ func regRegNum(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode3 } code = regReg(code, reg, rm, opCode32) - return append( - code, - byte(number), - byte(number>>8), - byte(number>>16), - byte(number>>24), - ) + return binary.LittleEndian.AppendUint32(code, uint32(number)) } From c5b61c1148e5165f5093e574c50b9dbf1b3a2071 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Jun 2024 14:14:11 +0200 Subject: [PATCH 0147/1012] Improved assembler --- src/build/Function.go | 2 +- src/build/VariableDefinition.go | 2 +- src/build/arch/arm64/Registers.go | 39 +++++++++++++++++ src/build/arch/arm64/Syscall.go | 9 ---- src/build/arch/x64/Registers.go | 4 +- src/build/asm/Assembler.go | 71 ++++++++++++++++++------------- src/build/cpu/CPU.go | 4 +- 7 files changed, 85 insertions(+), 46 deletions(-) create mode 100644 src/build/arch/arm64/Registers.go delete mode 100644 src/build/arch/arm64/Syscall.go diff --git a/src/build/Function.go b/src/build/Function.go index f99bc2d..a7b0847 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -155,7 +155,7 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp return nil } else { - temporary, _ := f.CPU.FindFree() + temporary, _ := f.CPU.FindFree(f.CPU.General) f.CPU.Use(temporary) f.ExpressionToRegister(right, temporary) f.CPU.Free(temporary) diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index 1043143..29a7899 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -32,7 +32,7 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return err } - reg, exists := f.CPU.FindFree() + reg, exists := f.CPU.FindFree(f.CPU.General) if !exists { panic("no free registers") diff --git a/src/build/arch/arm64/Registers.go b/src/build/arch/arm64/Registers.go new file mode 100644 index 0000000..39c87b6 --- /dev/null +++ b/src/build/arch/arm64/Registers.go @@ -0,0 +1,39 @@ +package register + +import "git.akyoto.dev/cli/q/src/build/cpu" + +const ( + X0 cpu.Register = iota + X1 + X2 + X3 + X4 + X5 + X6 + X7 + X8 + X9 + X10 + X11 + X12 + X13 + X14 + X15 + X16 + X17 + X18 + X19 + X20 + X21 + X22 + X23 + X24 + X25 + X26 + X27 + X28 + X29 + X30 +) + +var SyscallArgs = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} diff --git a/src/build/arch/arm64/Syscall.go b/src/build/arch/arm64/Syscall.go deleted file mode 100644 index fb6495b..0000000 --- a/src/build/arch/arm64/Syscall.go +++ /dev/null @@ -1,9 +0,0 @@ -package register - -import "git.akyoto.dev/cli/q/src/build/cpu" - -const ( - SyscallReturn = 0 -) - -var SyscallArgs = []cpu.Register{8, 0, 1, 2, 3, 4, 5} diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go index ac47f67..da154af 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/build/arch/x64/Registers.go @@ -3,7 +3,7 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" const ( - RAX = iota + RAX cpu.Register = iota RCX RDX RBX @@ -21,8 +21,6 @@ const ( R15 ) -const SyscallReturn = RAX - var GeneralRegisters = []cpu.Register{RBX, RBP, R12, R13, R14, R15} var SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} var ReturnValueRegisters = []cpu.Register{RAX, RCX, R11} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index e2e1483..7c0ed62 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -52,36 +52,7 @@ func (a *Assembler) Finalize() ([]byte, []byte) { } case DIV: - switch operands := x.Data.(type) { - case *RegisterNumber: - if operands.Register == x64.RAX { - code = x64.PushRegister(code, x64.RCX) - code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number)) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, x64.RCX) - code = x64.PopRegister(code, x64.RCX) - } else { - code = x64.PushRegister(code, x64.RAX) - code = x64.PushRegister(code, x64.RDX) - code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) - code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Register) - code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX) - code = x64.PopRegister(code, x64.RDX) - code = x64.PopRegister(code, x64.RAX) - } - - case *RegisterRegister: - code = x64.PushRegister(code, x64.RAX) - code = x64.PushRegister(code, x64.RDX) - code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Source) - code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX) - code = x64.PopRegister(code, x64.RDX) - code = x64.PopRegister(code, x64.RAX) - } + code = divide(code, x.Data) case CALL: code = x64.Call(code, 0x00_00_00_00) @@ -166,3 +137,43 @@ func (a *Assembler) Finalize() ([]byte, []byte) { func (a *Assembler) Merge(b *Assembler) { a.Instructions = append(a.Instructions, b.Instructions...) } + +// divide implements the division on x64 machines. +func divide(code []byte, data any) []byte { + code = x64.PushRegister(code, x64.RDX) + + switch operands := data.(type) { + case *RegisterNumber: + if operands.Register == x64.RAX { + code = x64.PushRegister(code, x64.RCX) + code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number)) + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, x64.RCX) + code = x64.PopRegister(code, x64.RCX) + } else { + code = x64.PushRegister(code, x64.RAX) + code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) + code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Register) + code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX) + code = x64.PopRegister(code, x64.RAX) + } + + case *RegisterRegister: + if operands.Destination == x64.RAX { + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Source) + } else { + code = x64.PushRegister(code, x64.RAX) + code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Source) + code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX) + code = x64.PopRegister(code, x64.RAX) + } + } + + code = x64.PopRegister(code, x64.RDX) + return code +} diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go index 7e28e22..f6a6f9b 100644 --- a/src/build/cpu/CPU.go +++ b/src/build/cpu/CPU.go @@ -20,8 +20,8 @@ func (c *CPU) IsFree(reg Register) bool { return c.usage&(1< Date: Wed, 26 Jun 2024 14:14:11 +0200 Subject: [PATCH 0148/1012] Improved assembler --- src/build/Function.go | 2 +- src/build/VariableDefinition.go | 2 +- src/build/arch/arm64/Registers.go | 39 +++++++++++++++++ src/build/arch/arm64/Syscall.go | 9 ---- src/build/arch/x64/Registers.go | 4 +- src/build/asm/Assembler.go | 71 ++++++++++++++++++------------- src/build/cpu/CPU.go | 4 +- 7 files changed, 85 insertions(+), 46 deletions(-) create mode 100644 src/build/arch/arm64/Registers.go delete mode 100644 src/build/arch/arm64/Syscall.go diff --git a/src/build/Function.go b/src/build/Function.go index f99bc2d..a7b0847 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -155,7 +155,7 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp return nil } else { - temporary, _ := f.CPU.FindFree() + temporary, _ := f.CPU.FindFree(f.CPU.General) f.CPU.Use(temporary) f.ExpressionToRegister(right, temporary) f.CPU.Free(temporary) diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index 1043143..29a7899 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -32,7 +32,7 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return err } - reg, exists := f.CPU.FindFree() + reg, exists := f.CPU.FindFree(f.CPU.General) if !exists { panic("no free registers") diff --git a/src/build/arch/arm64/Registers.go b/src/build/arch/arm64/Registers.go new file mode 100644 index 0000000..39c87b6 --- /dev/null +++ b/src/build/arch/arm64/Registers.go @@ -0,0 +1,39 @@ +package register + +import "git.akyoto.dev/cli/q/src/build/cpu" + +const ( + X0 cpu.Register = iota + X1 + X2 + X3 + X4 + X5 + X6 + X7 + X8 + X9 + X10 + X11 + X12 + X13 + X14 + X15 + X16 + X17 + X18 + X19 + X20 + X21 + X22 + X23 + X24 + X25 + X26 + X27 + X28 + X29 + X30 +) + +var SyscallArgs = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} diff --git a/src/build/arch/arm64/Syscall.go b/src/build/arch/arm64/Syscall.go deleted file mode 100644 index fb6495b..0000000 --- a/src/build/arch/arm64/Syscall.go +++ /dev/null @@ -1,9 +0,0 @@ -package register - -import "git.akyoto.dev/cli/q/src/build/cpu" - -const ( - SyscallReturn = 0 -) - -var SyscallArgs = []cpu.Register{8, 0, 1, 2, 3, 4, 5} diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go index ac47f67..da154af 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/build/arch/x64/Registers.go @@ -3,7 +3,7 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" const ( - RAX = iota + RAX cpu.Register = iota RCX RDX RBX @@ -21,8 +21,6 @@ const ( R15 ) -const SyscallReturn = RAX - var GeneralRegisters = []cpu.Register{RBX, RBP, R12, R13, R14, R15} var SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} var ReturnValueRegisters = []cpu.Register{RAX, RCX, R11} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index e2e1483..7c0ed62 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -52,36 +52,7 @@ func (a *Assembler) Finalize() ([]byte, []byte) { } case DIV: - switch operands := x.Data.(type) { - case *RegisterNumber: - if operands.Register == x64.RAX { - code = x64.PushRegister(code, x64.RCX) - code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number)) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, x64.RCX) - code = x64.PopRegister(code, x64.RCX) - } else { - code = x64.PushRegister(code, x64.RAX) - code = x64.PushRegister(code, x64.RDX) - code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) - code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Register) - code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX) - code = x64.PopRegister(code, x64.RDX) - code = x64.PopRegister(code, x64.RAX) - } - - case *RegisterRegister: - code = x64.PushRegister(code, x64.RAX) - code = x64.PushRegister(code, x64.RDX) - code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Source) - code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX) - code = x64.PopRegister(code, x64.RDX) - code = x64.PopRegister(code, x64.RAX) - } + code = divide(code, x.Data) case CALL: code = x64.Call(code, 0x00_00_00_00) @@ -166,3 +137,43 @@ func (a *Assembler) Finalize() ([]byte, []byte) { func (a *Assembler) Merge(b *Assembler) { a.Instructions = append(a.Instructions, b.Instructions...) } + +// divide implements the division on x64 machines. +func divide(code []byte, data any) []byte { + code = x64.PushRegister(code, x64.RDX) + + switch operands := data.(type) { + case *RegisterNumber: + if operands.Register == x64.RAX { + code = x64.PushRegister(code, x64.RCX) + code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number)) + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, x64.RCX) + code = x64.PopRegister(code, x64.RCX) + } else { + code = x64.PushRegister(code, x64.RAX) + code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) + code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Register) + code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX) + code = x64.PopRegister(code, x64.RAX) + } + + case *RegisterRegister: + if operands.Destination == x64.RAX { + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Source) + } else { + code = x64.PushRegister(code, x64.RAX) + code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Source) + code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX) + code = x64.PopRegister(code, x64.RAX) + } + } + + code = x64.PopRegister(code, x64.RDX) + return code +} diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go index 7e28e22..f6a6f9b 100644 --- a/src/build/cpu/CPU.go +++ b/src/build/cpu/CPU.go @@ -20,8 +20,8 @@ func (c *CPU) IsFree(reg Register) bool { return c.usage&(1< Date: Wed, 26 Jun 2024 15:24:10 +0200 Subject: [PATCH 0149/1012] Improved test coverage --- src/build/expression/Expression.go | 9 ----- src/build/expression/Expression_test.go | 48 +++++++++++++++++++++---- src/build/expression/bench_test.go | 18 ++++++++++ 3 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 src/build/expression/bench_test.go diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 84b9249..c2be790 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -26,15 +26,6 @@ func NewLeaf(t token.Token) *Expression { return expr } -// NewBinary creates a new binary operator expression. -func NewBinary(left *Expression, operator token.Token, right *Expression) *Expression { - expr := New() - expr.Token = operator - expr.AddChild(left) - expr.AddChild(right) - return expr -} - // AddChild adds a child to the expression. func (expr *Expression) AddChild(child *Expression) { expr.Children = append(expr.Children, child) diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index 62ce2d9..c0fe259 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -1,6 +1,7 @@ package expression_test import ( + "fmt" "testing" "git.akyoto.dev/cli/q/src/build/expression" @@ -95,12 +96,47 @@ func TestExpressionParse(t *testing.T) { } } -func BenchmarkExpression(b *testing.B) { - src := []byte("(1+2-3*4)+(5*6-7+8)\n") +func TestEachLeaf(t *testing.T) { + src := []byte("(1+2-3*4)+(5*6-7+8)") tokens := token.Tokenize(src) + expr := expression.Parse(tokens) + leaves := []string{} - for i := 0; i < b.N; i++ { - expr := expression.Parse(tokens) - expr.Close() - } + expr.EachLeaf(func(leaf *expression.Expression) error { + leaves = append(leaves, leaf.Token.Text()) + return nil + }) + + assert.DeepEqual(t, leaves, []string{"1", "2", "3", "4", "5", "6", "7", "8"}) + + err := expr.EachLeaf(func(leaf *expression.Expression) error { + return fmt.Errorf("error") + }) + + assert.NotNil(t, err) + assert.Equal(t, err.Error(), "error") +} + +func TestRemoveChild(t *testing.T) { + src := []byte("(1+2-3*4)+(5*6-7+8)") + tokens := token.Tokenize(src) + expr := expression.Parse(tokens) + left := expr.Children[0] + right := expr.Children[1] + expr.RemoveChild(left) + assert.Equal(t, expr.Children[0], right) +} + +func TestNilExpression(t *testing.T) { + src := []byte("") + tokens := token.Tokenize(src) + expr := expression.Parse(tokens) + assert.Nil(t, expr) +} + +func TestNilGroup(t *testing.T) { + src := []byte("()") + tokens := token.Tokenize(src) + expr := expression.Parse(tokens) + assert.Nil(t, expr) } diff --git a/src/build/expression/bench_test.go b/src/build/expression/bench_test.go new file mode 100644 index 0000000..8287416 --- /dev/null +++ b/src/build/expression/bench_test.go @@ -0,0 +1,18 @@ +package expression_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +func BenchmarkExpression(b *testing.B) { + src := []byte("(1+2-3*4)+(5*6-7+8)") + tokens := token.Tokenize(src) + + for i := 0; i < b.N; i++ { + expr := expression.Parse(tokens) + expr.Close() + } +} From 49b75dbda4767055852ca4e90ca33c39575f64c3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Jun 2024 15:24:10 +0200 Subject: [PATCH 0150/1012] Improved test coverage --- src/build/expression/Expression.go | 9 ----- src/build/expression/Expression_test.go | 48 +++++++++++++++++++++---- src/build/expression/bench_test.go | 18 ++++++++++ 3 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 src/build/expression/bench_test.go diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 84b9249..c2be790 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -26,15 +26,6 @@ func NewLeaf(t token.Token) *Expression { return expr } -// NewBinary creates a new binary operator expression. -func NewBinary(left *Expression, operator token.Token, right *Expression) *Expression { - expr := New() - expr.Token = operator - expr.AddChild(left) - expr.AddChild(right) - return expr -} - // AddChild adds a child to the expression. func (expr *Expression) AddChild(child *Expression) { expr.Children = append(expr.Children, child) diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index 62ce2d9..c0fe259 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -1,6 +1,7 @@ package expression_test import ( + "fmt" "testing" "git.akyoto.dev/cli/q/src/build/expression" @@ -95,12 +96,47 @@ func TestExpressionParse(t *testing.T) { } } -func BenchmarkExpression(b *testing.B) { - src := []byte("(1+2-3*4)+(5*6-7+8)\n") +func TestEachLeaf(t *testing.T) { + src := []byte("(1+2-3*4)+(5*6-7+8)") tokens := token.Tokenize(src) + expr := expression.Parse(tokens) + leaves := []string{} - for i := 0; i < b.N; i++ { - expr := expression.Parse(tokens) - expr.Close() - } + expr.EachLeaf(func(leaf *expression.Expression) error { + leaves = append(leaves, leaf.Token.Text()) + return nil + }) + + assert.DeepEqual(t, leaves, []string{"1", "2", "3", "4", "5", "6", "7", "8"}) + + err := expr.EachLeaf(func(leaf *expression.Expression) error { + return fmt.Errorf("error") + }) + + assert.NotNil(t, err) + assert.Equal(t, err.Error(), "error") +} + +func TestRemoveChild(t *testing.T) { + src := []byte("(1+2-3*4)+(5*6-7+8)") + tokens := token.Tokenize(src) + expr := expression.Parse(tokens) + left := expr.Children[0] + right := expr.Children[1] + expr.RemoveChild(left) + assert.Equal(t, expr.Children[0], right) +} + +func TestNilExpression(t *testing.T) { + src := []byte("") + tokens := token.Tokenize(src) + expr := expression.Parse(tokens) + assert.Nil(t, expr) +} + +func TestNilGroup(t *testing.T) { + src := []byte("()") + tokens := token.Tokenize(src) + expr := expression.Parse(tokens) + assert.Nil(t, expr) } diff --git a/src/build/expression/bench_test.go b/src/build/expression/bench_test.go new file mode 100644 index 0000000..8287416 --- /dev/null +++ b/src/build/expression/bench_test.go @@ -0,0 +1,18 @@ +package expression_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +func BenchmarkExpression(b *testing.B) { + src := []byte("(1+2-3*4)+(5*6-7+8)") + tokens := token.Tokenize(src) + + for i := 0; i < b.N; i++ { + expr := expression.Parse(tokens) + expr.Close() + } +} From 2676ac1ebbfec6a53921b3d19307ed4eb0a61b77 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Jun 2024 18:49:03 +0200 Subject: [PATCH 0151/1012] Added tokenizer benchmark --- src/build/token/Token_test.go | 36 +++++++++++++++++++++++++++++++++++ src/build/token/Tokenize.go | 7 ++++++- src/build/token/bench_test.go | 15 +++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 src/build/token/bench_test.go diff --git a/src/build/token/Token_test.go b/src/build/token/Token_test.go index b083cfd..6bd1065 100644 --- a/src/build/token/Token_test.go +++ b/src/build/token/Token_test.go @@ -137,6 +137,42 @@ func TestNumber(t *testing.T) { }) } +func TestOperator(t *testing.T) { + tokens := token.Tokenize([]byte(`+ - * / ==`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Operator, + Bytes: []byte("+"), + Position: 0, + }, + { + Kind: token.Operator, + Bytes: []byte("-"), + Position: 2, + }, + { + Kind: token.Operator, + Bytes: []byte("*"), + Position: 4, + }, + { + Kind: token.Operator, + Bytes: []byte("/"), + Position: 6, + }, + { + Kind: token.Operator, + Bytes: []byte("=="), + Position: 8, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 10, + }, + }) +} + func TestSeparator(t *testing.T) { tokens := token.Tokenize([]byte("a,b,c")) assert.DeepEqual(t, tokens, token.List{ diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 0e51758..a8e4acd 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -144,5 +144,10 @@ func isNumber(c byte) bool { } func isOperator(c byte) bool { - return c == '=' || c == ':' || c == '+' || c == '-' || c == '*' || c == '/' || c == '<' || c == '>' || c == '!' || c == '&' || c == '|' || c == '^' || c == '%' || c == '.' + switch c { + case '=', ':', '.', '+', '-', '*', '/', '<', '>', '&', '|', '^', '%', '!': + return true + default: + return false + } } diff --git a/src/build/token/bench_test.go b/src/build/token/bench_test.go new file mode 100644 index 0000000..b6db849 --- /dev/null +++ b/src/build/token/bench_test.go @@ -0,0 +1,15 @@ +package token_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/token" +) + +func BenchmarkTokenize(b *testing.B) { + input := []byte("hello := 123\nworld := 456") + + for i := 0; i < b.N; i++ { + token.Tokenize(input) + } +} From 3268f7a7ee480cd124244805725b9f17d99a1d41 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Jun 2024 18:49:03 +0200 Subject: [PATCH 0152/1012] Added tokenizer benchmark --- src/build/token/Token_test.go | 36 +++++++++++++++++++++++++++++++++++ src/build/token/Tokenize.go | 7 ++++++- src/build/token/bench_test.go | 15 +++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 src/build/token/bench_test.go diff --git a/src/build/token/Token_test.go b/src/build/token/Token_test.go index b083cfd..6bd1065 100644 --- a/src/build/token/Token_test.go +++ b/src/build/token/Token_test.go @@ -137,6 +137,42 @@ func TestNumber(t *testing.T) { }) } +func TestOperator(t *testing.T) { + tokens := token.Tokenize([]byte(`+ - * / ==`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Operator, + Bytes: []byte("+"), + Position: 0, + }, + { + Kind: token.Operator, + Bytes: []byte("-"), + Position: 2, + }, + { + Kind: token.Operator, + Bytes: []byte("*"), + Position: 4, + }, + { + Kind: token.Operator, + Bytes: []byte("/"), + Position: 6, + }, + { + Kind: token.Operator, + Bytes: []byte("=="), + Position: 8, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 10, + }, + }) +} + func TestSeparator(t *testing.T) { tokens := token.Tokenize([]byte("a,b,c")) assert.DeepEqual(t, tokens, token.List{ diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 0e51758..a8e4acd 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -144,5 +144,10 @@ func isNumber(c byte) bool { } func isOperator(c byte) bool { - return c == '=' || c == ':' || c == '+' || c == '-' || c == '*' || c == '/' || c == '<' || c == '>' || c == '!' || c == '&' || c == '|' || c == '^' || c == '%' || c == '.' + switch c { + case '=', ':', '.', '+', '-', '*', '/', '<', '>', '&', '|', '^', '%', '!': + return true + default: + return false + } } diff --git a/src/build/token/bench_test.go b/src/build/token/bench_test.go new file mode 100644 index 0000000..b6db849 --- /dev/null +++ b/src/build/token/bench_test.go @@ -0,0 +1,15 @@ +package token_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/token" +) + +func BenchmarkTokenize(b *testing.B) { + input := []byte("hello := 123\nworld := 456") + + for i := 0; i < b.N; i++ { + token.Tokenize(input) + } +} From 03a7651cad3f92da0641cb1fd4ed66847c0c3a6a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Jun 2024 20:16:18 +0200 Subject: [PATCH 0153/1012] Added build result struct --- src/build/Build.go | 2 +- src/build/Finalize.go | 36 -------------------------------- src/build/Result.go | 42 ++++++++++++++++++++++++++++++++++++++ src/build/asm/Assembler.go | 7 ------- src/build/compile.go | 20 ++++++++++-------- src/build/scan.go | 4 ++++ src/cli/Build.go | 4 ++-- 7 files changed, 61 insertions(+), 54 deletions(-) delete mode 100644 src/build/Finalize.go create mode 100644 src/build/Result.go diff --git a/src/build/Build.go b/src/build/Build.go index 1efb881..2821e2d 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -18,7 +18,7 @@ func New(files ...string) *Build { } // Run parses the input files and generates an executable file. -func (build *Build) Run() (map[string]*Function, error) { +func (build *Build) Run() (Result, error) { functions, errors := scan(build.Files) return compile(functions, errors) } diff --git a/src/build/Finalize.go b/src/build/Finalize.go deleted file mode 100644 index dadf7f6..0000000 --- a/src/build/Finalize.go +++ /dev/null @@ -1,36 +0,0 @@ -package build - -import ( - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/os/linux" -) - -// Finalize generates the final machine code. -func Finalize(functions map[string]*Function) ([]byte, []byte) { - a := entry() - - main := functions["main"] - delete(functions, "main") - a.Merge(&main.Assembler) - - for _, f := range functions { - a.Merge(&f.Assembler) - } - - code, data := a.Finalize() - return code, data -} - -// entry returns the entry point of the executable. -// The only job of the entry function is to call `main` and exit cleanly. -// The reason we call `main` instead of using `main` itself is to place -// a return address on the stack, which allows return statements in `main`. -func entry() *asm.Assembler { - entry := asm.New() - entry.Call("main") - entry.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit) - entry.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0) - entry.Syscall() - return entry -} diff --git a/src/build/Result.go b/src/build/Result.go new file mode 100644 index 0000000..3b13dba --- /dev/null +++ b/src/build/Result.go @@ -0,0 +1,42 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/os/linux" +) + +// Result contains all the compiled functions in a build. +type Result struct { + Functions map[string]*Function + instructionCount int +} + +// Finalize generates the final machine code. +func (r Result) Finalize() ([]byte, []byte) { + // This will be the entry point of the executable. + // The only job of the entry function is to call `main` and exit cleanly. + // The reason we call `main` instead of using `main` itself is to place + // a return address on the stack, which allows return statements in `main`. + final := asm.Assembler{ + Instructions: make([]asm.Instruction, 0, r.instructionCount+4), + } + + final.Call("main") + final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0) + final.Syscall() + + // Place the `main` function immediately after the entry point. + main := r.Functions["main"] + delete(r.Functions, "main") + final.Merge(&main.Assembler) + + // Merge all the remaining functions. + for _, f := range r.Functions { + final.Merge(&f.Assembler) + } + + code, data := final.Finalize() + return code, data +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 7c0ed62..9fe9229 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -11,13 +11,6 @@ type Assembler struct { Instructions []Instruction } -// New creates a new assembler. -func New() *Assembler { - return &Assembler{ - Instructions: make([]Instruction, 0, 8), - } -} - // Finalize generates the final machine code. func (a *Assembler) Finalize() ([]byte, []byte) { code := make([]byte, 0, len(a.Instructions)*8) diff --git a/src/build/compile.go b/src/build/compile.go index 6088e5a..110bd40 100644 --- a/src/build/compile.go +++ b/src/build/compile.go @@ -5,8 +5,10 @@ import ( ) // compile waits for the scan to finish and compiles all functions. -func compile(functions <-chan *Function, errors <-chan error) (map[string]*Function, error) { - allFunctions := map[string]*Function{} +func compile(functions <-chan *Function, errors <-chan error) (Result, error) { + result := Result{ + Functions: map[string]*Function{}, + } for functions != nil || errors != nil { select { @@ -16,7 +18,7 @@ func compile(functions <-chan *Function, errors <-chan error) (map[string]*Funct continue } - return nil, err + return result, err case function, ok := <-functions: if !ok { @@ -24,19 +26,21 @@ func compile(functions <-chan *Function, errors <-chan error) (map[string]*Funct continue } - allFunctions[function.Name] = function + result.Functions[function.Name] = function } } - compileFunctions(allFunctions) + compileFunctions(result.Functions) - for _, function := range allFunctions { + for _, function := range result.Functions { if function.Error != nil { - return nil, function.Error + return result, function.Error } + + result.instructionCount += len(function.Assembler.Instructions) } - return allFunctions, nil + return result, nil } // compileFunctions starts a goroutine for each function compilation and waits for completion. diff --git a/src/build/scan.go b/src/build/scan.go index a78ce93..fea378a 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -7,6 +7,7 @@ import ( "sync" "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" @@ -231,6 +232,9 @@ func scanFile(path string, functions chan<- *Function) error { Syscall: x64.SyscallRegisters, Return: x64.ReturnValueRegisters, }, + Assembler: asm.Assembler{ + Instructions: make([]asm.Instruction, 0, 32), + }, } nameStart = -1 diff --git a/src/cli/Build.go b/src/cli/Build.go index 6120250..f9fe85d 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -43,7 +43,7 @@ func Build(args []string) int { } if config.Verbose { - for _, function := range result { + for _, function := range result.Functions { function.PrintAsm() } } @@ -53,7 +53,7 @@ func Build(args []string) int { } path := b.Executable() - code, data := build.Finalize(result) + code, data := result.Finalize() err = build.Write(path, code, data) if err != nil { From 988a53866174d7fc09fde9a02f8057a946d62797 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Jun 2024 20:16:18 +0200 Subject: [PATCH 0154/1012] Added build result struct --- src/build/Build.go | 2 +- src/build/Finalize.go | 36 -------------------------------- src/build/Result.go | 42 ++++++++++++++++++++++++++++++++++++++ src/build/asm/Assembler.go | 7 ------- src/build/compile.go | 20 ++++++++++-------- src/build/scan.go | 4 ++++ src/cli/Build.go | 4 ++-- 7 files changed, 61 insertions(+), 54 deletions(-) delete mode 100644 src/build/Finalize.go create mode 100644 src/build/Result.go diff --git a/src/build/Build.go b/src/build/Build.go index 1efb881..2821e2d 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -18,7 +18,7 @@ func New(files ...string) *Build { } // Run parses the input files and generates an executable file. -func (build *Build) Run() (map[string]*Function, error) { +func (build *Build) Run() (Result, error) { functions, errors := scan(build.Files) return compile(functions, errors) } diff --git a/src/build/Finalize.go b/src/build/Finalize.go deleted file mode 100644 index dadf7f6..0000000 --- a/src/build/Finalize.go +++ /dev/null @@ -1,36 +0,0 @@ -package build - -import ( - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/os/linux" -) - -// Finalize generates the final machine code. -func Finalize(functions map[string]*Function) ([]byte, []byte) { - a := entry() - - main := functions["main"] - delete(functions, "main") - a.Merge(&main.Assembler) - - for _, f := range functions { - a.Merge(&f.Assembler) - } - - code, data := a.Finalize() - return code, data -} - -// entry returns the entry point of the executable. -// The only job of the entry function is to call `main` and exit cleanly. -// The reason we call `main` instead of using `main` itself is to place -// a return address on the stack, which allows return statements in `main`. -func entry() *asm.Assembler { - entry := asm.New() - entry.Call("main") - entry.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit) - entry.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0) - entry.Syscall() - return entry -} diff --git a/src/build/Result.go b/src/build/Result.go new file mode 100644 index 0000000..3b13dba --- /dev/null +++ b/src/build/Result.go @@ -0,0 +1,42 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/os/linux" +) + +// Result contains all the compiled functions in a build. +type Result struct { + Functions map[string]*Function + instructionCount int +} + +// Finalize generates the final machine code. +func (r Result) Finalize() ([]byte, []byte) { + // This will be the entry point of the executable. + // The only job of the entry function is to call `main` and exit cleanly. + // The reason we call `main` instead of using `main` itself is to place + // a return address on the stack, which allows return statements in `main`. + final := asm.Assembler{ + Instructions: make([]asm.Instruction, 0, r.instructionCount+4), + } + + final.Call("main") + final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0) + final.Syscall() + + // Place the `main` function immediately after the entry point. + main := r.Functions["main"] + delete(r.Functions, "main") + final.Merge(&main.Assembler) + + // Merge all the remaining functions. + for _, f := range r.Functions { + final.Merge(&f.Assembler) + } + + code, data := final.Finalize() + return code, data +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 7c0ed62..9fe9229 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -11,13 +11,6 @@ type Assembler struct { Instructions []Instruction } -// New creates a new assembler. -func New() *Assembler { - return &Assembler{ - Instructions: make([]Instruction, 0, 8), - } -} - // Finalize generates the final machine code. func (a *Assembler) Finalize() ([]byte, []byte) { code := make([]byte, 0, len(a.Instructions)*8) diff --git a/src/build/compile.go b/src/build/compile.go index 6088e5a..110bd40 100644 --- a/src/build/compile.go +++ b/src/build/compile.go @@ -5,8 +5,10 @@ import ( ) // compile waits for the scan to finish and compiles all functions. -func compile(functions <-chan *Function, errors <-chan error) (map[string]*Function, error) { - allFunctions := map[string]*Function{} +func compile(functions <-chan *Function, errors <-chan error) (Result, error) { + result := Result{ + Functions: map[string]*Function{}, + } for functions != nil || errors != nil { select { @@ -16,7 +18,7 @@ func compile(functions <-chan *Function, errors <-chan error) (map[string]*Funct continue } - return nil, err + return result, err case function, ok := <-functions: if !ok { @@ -24,19 +26,21 @@ func compile(functions <-chan *Function, errors <-chan error) (map[string]*Funct continue } - allFunctions[function.Name] = function + result.Functions[function.Name] = function } } - compileFunctions(allFunctions) + compileFunctions(result.Functions) - for _, function := range allFunctions { + for _, function := range result.Functions { if function.Error != nil { - return nil, function.Error + return result, function.Error } + + result.instructionCount += len(function.Assembler.Instructions) } - return allFunctions, nil + return result, nil } // compileFunctions starts a goroutine for each function compilation and waits for completion. diff --git a/src/build/scan.go b/src/build/scan.go index a78ce93..fea378a 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -7,6 +7,7 @@ import ( "sync" "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" @@ -231,6 +232,9 @@ func scanFile(path string, functions chan<- *Function) error { Syscall: x64.SyscallRegisters, Return: x64.ReturnValueRegisters, }, + Assembler: asm.Assembler{ + Instructions: make([]asm.Instruction, 0, 32), + }, } nameStart = -1 diff --git a/src/cli/Build.go b/src/cli/Build.go index 6120250..f9fe85d 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -43,7 +43,7 @@ func Build(args []string) int { } if config.Verbose { - for _, function := range result { + for _, function := range result.Functions { function.PrintAsm() } } @@ -53,7 +53,7 @@ func Build(args []string) int { } path := b.Executable() - code, data := build.Finalize(result) + code, data := result.Finalize() err = build.Write(path, code, data) if err != nil { From 0394d68bdfec9ffe5e51907b195214e41cb53f87 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Jun 2024 22:51:14 +0200 Subject: [PATCH 0155/1012] Implemented complex expressions --- examples/hello/hello.q | 5 +- src/build/Assignment.go | 41 ++++++----------- src/build/Calculate.go | 81 +++++++++++++++++++++++++++++++++ src/build/Function.go | 77 +++++++++++++------------------ src/build/VariableDefinition.go | 22 +++------ src/build/asm/Instructions.go | 11 ----- src/build/asm/RegisterNumber.go | 2 +- 7 files changed, 137 insertions(+), 102 deletions(-) create mode 100644 src/build/Calculate.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 7eddb30..9032441 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -11,7 +11,10 @@ hello() { address += 4194304 address += 1 - length = 0 + length = 0 + 1 + length -= length + length += write + stdout + length -= length length += 50 length -= 20 length *= 10 diff --git a/src/build/Assignment.go b/src/build/Assignment.go index 96ebd79..7ef8d06 100644 --- a/src/build/Assignment.go +++ b/src/build/Assignment.go @@ -1,9 +1,6 @@ package build import ( - "strconv" - - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/errors" ) @@ -11,31 +8,21 @@ import ( // CompileAssignment compiles an assignment. func (f *Function) CompileAssignment(expr *expression.Expression) error { name := expr.Children[0].Token.Text() - register := f.Variables[name].Register + variable, exists := f.Variables[name] - switch expr.Token.Text() { - case "=": - f.ExpressionToRegister(expr.Children[1], register) - - case "+=": - number, _ := strconv.Atoi(expr.Children[1].Token.Text()) - f.Assembler.RegisterNumber(asm.ADD, register, number) - - case "-=": - number, _ := strconv.Atoi(expr.Children[1].Token.Text()) - f.Assembler.RegisterNumber(asm.SUB, register, number) - - case "*=": - number, _ := strconv.Atoi(expr.Children[1].Token.Text()) - f.Assembler.RegisterNumber(asm.MUL, register, number) - - case "/=": - number, _ := strconv.Atoi(expr.Children[1].Token.Text()) - f.Assembler.RegisterNumber(asm.DIV, register, number) - - default: - return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) } - return nil + if expr.Token.Text() == "=" { + return f.ExpressionToRegister(expr.Children[1], variable.Register) + } + + right := expr.Children[1] + + if right.IsLeaf() { + return f.Calculate(variable.Register, expr.Token, right.Token) + } + + return f.Execute(expr.Token, variable.Register, expr.Children[1]) } diff --git a/src/build/Calculate.go b/src/build/Calculate.go new file mode 100644 index 0000000..5daeb72 --- /dev/null +++ b/src/build/Calculate.go @@ -0,0 +1,81 @@ +package build + +import ( + "strconv" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// Calculate performs an operation on a register with the given operand. +func (f *Function) Calculate(register cpu.Register, operation token.Token, operand token.Token) error { + switch operand.Kind { + case token.Number: + value := operand.Text() + number, err := strconv.Atoi(value) + + if err != nil { + return err + } + + return f.CalculateRegisterNumber(operation, register, number) + + case token.Identifier: + name := operand.Text() + variable, exists := f.Variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) + } + + return f.CalculateRegisterRegister(operation, register, variable.Register) + } + + return errors.New(errors.NotImplemented, f.File, operation.Position) +} + +// CalculateRegisterNumber performs an operation on a register and a number. +func (f *Function) CalculateRegisterNumber(operation token.Token, register cpu.Register, number int) error { + switch operation.Text() { + case "+", "+=": + f.Assembler.RegisterNumber(asm.ADD, register, number) + + case "-", "-=": + f.Assembler.RegisterNumber(asm.SUB, register, number) + + case "*", "*=": + f.Assembler.RegisterNumber(asm.MUL, register, number) + + case "/", "/=": + f.Assembler.RegisterNumber(asm.DIV, register, number) + + default: + return errors.New(errors.NotImplemented, f.File, operation.Position) + } + + return nil +} + +// CalculateRegisterRegister performs an operation on two registers. +func (f *Function) CalculateRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { + switch operation.Text() { + case "+", "+=": + f.Assembler.RegisterRegister(asm.ADD, destination, source) + + case "-", "-=": + f.Assembler.RegisterRegister(asm.SUB, destination, source) + + case "*", "*=": + f.Assembler.RegisterRegister(asm.MUL, destination, source) + + case "/", "/=": + f.Assembler.RegisterRegister(asm.DIV, destination, source) + + default: + return errors.New(errors.NotImplemented, f.File, operation.Position) + } + + return nil +} diff --git a/src/build/Function.go b/src/build/Function.go index a7b0847..1a50f0a 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -122,60 +122,45 @@ func (f *Function) CompileInstruction(line token.List) error { // ExpressionToRegister moves the result of an expression into the given register. func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { + operation := root.Token + if root.IsLeaf() { - return f.TokenToRegister(root.Token, register) + return f.TokenToRegister(operation, register) } left := root.Children[0] right := root.Children[1] - f.ExpressionToRegister(left, register) + err := f.ExpressionToRegister(left, register) - if right.IsLeaf() { - value := right.Token.Text() - n, err := strconv.Atoi(value) - - if err != nil { - return err - } - - switch root.Token.Text() { - case "+": - f.Assembler.RegisterNumber(asm.ADD, register, n) - - case "-": - f.Assembler.RegisterNumber(asm.SUB, register, n) - - case "*": - f.Assembler.RegisterNumber(asm.MUL, register, n) - - case "/": - f.Assembler.RegisterNumber(asm.DIV, register, n) - } - - return nil - } else { - temporary, _ := f.CPU.FindFree(f.CPU.General) - f.CPU.Use(temporary) - f.ExpressionToRegister(right, temporary) - f.CPU.Free(temporary) - - switch root.Token.Text() { - case "+": - f.Assembler.RegisterRegister(asm.ADD, register, temporary) - - case "-": - f.Assembler.RegisterRegister(asm.SUB, register, temporary) - - case "*": - f.Assembler.RegisterRegister(asm.MUL, register, temporary) - - case "/": - f.Assembler.RegisterRegister(asm.DIV, register, temporary) - } + if err != nil { + return err } - return errors.New(errors.NotImplemented, f.File, root.Token.Position) + return f.Execute(operation, register, right) +} + +// Execute executes an operation on a register with a value operand. +func (f *Function) Execute(operation token.Token, register cpu.Register, value *expression.Expression) error { + if value.IsLeaf() { + return f.Calculate(register, operation, value.Token) + } + + temporary, found := f.CPU.FindFree(f.CPU.General) + + if !found { + panic("no free registers") + } + + f.CPU.Use(temporary) + defer f.CPU.Free(temporary) + err := f.ExpressionToRegister(value, temporary) + + if err != nil { + return err + } + + return f.CalculateRegisterRegister(operation, register, temporary) } // TokenToRegister moves a token into a register. @@ -190,7 +175,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - f.Assembler.MoveRegisterRegister(register, variable.Register) + f.Assembler.RegisterRegister(asm.MOVE, register, variable.Register) return nil case token.Number: diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index 29a7899..270b8e1 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -2,7 +2,6 @@ package build import ( "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" ) @@ -18,27 +17,18 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) } - value := expr.Children[1] - - err := value.EachLeaf(func(leaf *expression.Expression) error { - if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) { - return errors.New(&errors.UnknownIdentifier{Name: leaf.Token.Text()}, f.File, leaf.Token.Position) - } - - return nil - }) - - if err != nil { - return err - } - reg, exists := f.CPU.FindFree(f.CPU.General) if !exists { panic("no free registers") } - f.ExpressionToRegister(value, reg) + err := f.ExpressionToRegister(expr.Children[1], reg) + + if err != nil { + return err + } + f.CPU.Use(reg) f.Variables[name] = &Variable{ diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 92842e2..04de2ea 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -24,17 +24,6 @@ func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right }) } -// MoveRegisterRegister moves a register value into another register. -func (a *Assembler) MoveRegisterRegister(destination cpu.Register, source cpu.Register) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: MOVE, - Data: &RegisterRegister{ - Destination: destination, - Source: source, - }, - }) -} - // Label adds a label at the current position. func (a *Assembler) Label(name string) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/asm/RegisterNumber.go b/src/build/asm/RegisterNumber.go index 7e4ba23..ff5d616 100644 --- a/src/build/asm/RegisterNumber.go +++ b/src/build/asm/RegisterNumber.go @@ -14,5 +14,5 @@ type RegisterNumber struct { // String returns a human readable version. func (data *RegisterNumber) String() string { - return fmt.Sprintf("%s, %Xₕ", data.Register, data.Number) + return fmt.Sprintf("%s, %d", data.Register, data.Number) } From d1a3ffb1a5607238910b1523e878c9d79ecd37c4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Jun 2024 22:51:14 +0200 Subject: [PATCH 0156/1012] Implemented complex expressions --- examples/hello/hello.q | 5 +- src/build/Assignment.go | 41 ++++++----------- src/build/Calculate.go | 81 +++++++++++++++++++++++++++++++++ src/build/Function.go | 77 +++++++++++++------------------ src/build/VariableDefinition.go | 22 +++------ src/build/asm/Instructions.go | 11 ----- src/build/asm/RegisterNumber.go | 2 +- 7 files changed, 137 insertions(+), 102 deletions(-) create mode 100644 src/build/Calculate.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 7eddb30..9032441 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -11,7 +11,10 @@ hello() { address += 4194304 address += 1 - length = 0 + length = 0 + 1 + length -= length + length += write + stdout + length -= length length += 50 length -= 20 length *= 10 diff --git a/src/build/Assignment.go b/src/build/Assignment.go index 96ebd79..7ef8d06 100644 --- a/src/build/Assignment.go +++ b/src/build/Assignment.go @@ -1,9 +1,6 @@ package build import ( - "strconv" - - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/errors" ) @@ -11,31 +8,21 @@ import ( // CompileAssignment compiles an assignment. func (f *Function) CompileAssignment(expr *expression.Expression) error { name := expr.Children[0].Token.Text() - register := f.Variables[name].Register + variable, exists := f.Variables[name] - switch expr.Token.Text() { - case "=": - f.ExpressionToRegister(expr.Children[1], register) - - case "+=": - number, _ := strconv.Atoi(expr.Children[1].Token.Text()) - f.Assembler.RegisterNumber(asm.ADD, register, number) - - case "-=": - number, _ := strconv.Atoi(expr.Children[1].Token.Text()) - f.Assembler.RegisterNumber(asm.SUB, register, number) - - case "*=": - number, _ := strconv.Atoi(expr.Children[1].Token.Text()) - f.Assembler.RegisterNumber(asm.MUL, register, number) - - case "/=": - number, _ := strconv.Atoi(expr.Children[1].Token.Text()) - f.Assembler.RegisterNumber(asm.DIV, register, number) - - default: - return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) } - return nil + if expr.Token.Text() == "=" { + return f.ExpressionToRegister(expr.Children[1], variable.Register) + } + + right := expr.Children[1] + + if right.IsLeaf() { + return f.Calculate(variable.Register, expr.Token, right.Token) + } + + return f.Execute(expr.Token, variable.Register, expr.Children[1]) } diff --git a/src/build/Calculate.go b/src/build/Calculate.go new file mode 100644 index 0000000..5daeb72 --- /dev/null +++ b/src/build/Calculate.go @@ -0,0 +1,81 @@ +package build + +import ( + "strconv" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// Calculate performs an operation on a register with the given operand. +func (f *Function) Calculate(register cpu.Register, operation token.Token, operand token.Token) error { + switch operand.Kind { + case token.Number: + value := operand.Text() + number, err := strconv.Atoi(value) + + if err != nil { + return err + } + + return f.CalculateRegisterNumber(operation, register, number) + + case token.Identifier: + name := operand.Text() + variable, exists := f.Variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) + } + + return f.CalculateRegisterRegister(operation, register, variable.Register) + } + + return errors.New(errors.NotImplemented, f.File, operation.Position) +} + +// CalculateRegisterNumber performs an operation on a register and a number. +func (f *Function) CalculateRegisterNumber(operation token.Token, register cpu.Register, number int) error { + switch operation.Text() { + case "+", "+=": + f.Assembler.RegisterNumber(asm.ADD, register, number) + + case "-", "-=": + f.Assembler.RegisterNumber(asm.SUB, register, number) + + case "*", "*=": + f.Assembler.RegisterNumber(asm.MUL, register, number) + + case "/", "/=": + f.Assembler.RegisterNumber(asm.DIV, register, number) + + default: + return errors.New(errors.NotImplemented, f.File, operation.Position) + } + + return nil +} + +// CalculateRegisterRegister performs an operation on two registers. +func (f *Function) CalculateRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { + switch operation.Text() { + case "+", "+=": + f.Assembler.RegisterRegister(asm.ADD, destination, source) + + case "-", "-=": + f.Assembler.RegisterRegister(asm.SUB, destination, source) + + case "*", "*=": + f.Assembler.RegisterRegister(asm.MUL, destination, source) + + case "/", "/=": + f.Assembler.RegisterRegister(asm.DIV, destination, source) + + default: + return errors.New(errors.NotImplemented, f.File, operation.Position) + } + + return nil +} diff --git a/src/build/Function.go b/src/build/Function.go index a7b0847..1a50f0a 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -122,60 +122,45 @@ func (f *Function) CompileInstruction(line token.List) error { // ExpressionToRegister moves the result of an expression into the given register. func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { + operation := root.Token + if root.IsLeaf() { - return f.TokenToRegister(root.Token, register) + return f.TokenToRegister(operation, register) } left := root.Children[0] right := root.Children[1] - f.ExpressionToRegister(left, register) + err := f.ExpressionToRegister(left, register) - if right.IsLeaf() { - value := right.Token.Text() - n, err := strconv.Atoi(value) - - if err != nil { - return err - } - - switch root.Token.Text() { - case "+": - f.Assembler.RegisterNumber(asm.ADD, register, n) - - case "-": - f.Assembler.RegisterNumber(asm.SUB, register, n) - - case "*": - f.Assembler.RegisterNumber(asm.MUL, register, n) - - case "/": - f.Assembler.RegisterNumber(asm.DIV, register, n) - } - - return nil - } else { - temporary, _ := f.CPU.FindFree(f.CPU.General) - f.CPU.Use(temporary) - f.ExpressionToRegister(right, temporary) - f.CPU.Free(temporary) - - switch root.Token.Text() { - case "+": - f.Assembler.RegisterRegister(asm.ADD, register, temporary) - - case "-": - f.Assembler.RegisterRegister(asm.SUB, register, temporary) - - case "*": - f.Assembler.RegisterRegister(asm.MUL, register, temporary) - - case "/": - f.Assembler.RegisterRegister(asm.DIV, register, temporary) - } + if err != nil { + return err } - return errors.New(errors.NotImplemented, f.File, root.Token.Position) + return f.Execute(operation, register, right) +} + +// Execute executes an operation on a register with a value operand. +func (f *Function) Execute(operation token.Token, register cpu.Register, value *expression.Expression) error { + if value.IsLeaf() { + return f.Calculate(register, operation, value.Token) + } + + temporary, found := f.CPU.FindFree(f.CPU.General) + + if !found { + panic("no free registers") + } + + f.CPU.Use(temporary) + defer f.CPU.Free(temporary) + err := f.ExpressionToRegister(value, temporary) + + if err != nil { + return err + } + + return f.CalculateRegisterRegister(operation, register, temporary) } // TokenToRegister moves a token into a register. @@ -190,7 +175,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - f.Assembler.MoveRegisterRegister(register, variable.Register) + f.Assembler.RegisterRegister(asm.MOVE, register, variable.Register) return nil case token.Number: diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index 29a7899..270b8e1 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -2,7 +2,6 @@ package build import ( "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" ) @@ -18,27 +17,18 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) } - value := expr.Children[1] - - err := value.EachLeaf(func(leaf *expression.Expression) error { - if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) { - return errors.New(&errors.UnknownIdentifier{Name: leaf.Token.Text()}, f.File, leaf.Token.Position) - } - - return nil - }) - - if err != nil { - return err - } - reg, exists := f.CPU.FindFree(f.CPU.General) if !exists { panic("no free registers") } - f.ExpressionToRegister(value, reg) + err := f.ExpressionToRegister(expr.Children[1], reg) + + if err != nil { + return err + } + f.CPU.Use(reg) f.Variables[name] = &Variable{ diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 92842e2..04de2ea 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -24,17 +24,6 @@ func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right }) } -// MoveRegisterRegister moves a register value into another register. -func (a *Assembler) MoveRegisterRegister(destination cpu.Register, source cpu.Register) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: MOVE, - Data: &RegisterRegister{ - Destination: destination, - Source: source, - }, - }) -} - // Label adds a label at the current position. func (a *Assembler) Label(name string) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/asm/RegisterNumber.go b/src/build/asm/RegisterNumber.go index 7e4ba23..ff5d616 100644 --- a/src/build/asm/RegisterNumber.go +++ b/src/build/asm/RegisterNumber.go @@ -14,5 +14,5 @@ type RegisterNumber struct { // String returns a human readable version. func (data *RegisterNumber) String() string { - return fmt.Sprintf("%s, %Xₕ", data.Register, data.Number) + return fmt.Sprintf("%s, %d", data.Register, data.Number) } From 51a230817909be50e61850111fae552d5c8ceb50 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Jun 2024 23:24:11 +0200 Subject: [PATCH 0157/1012] Enforced consistent naming --- errors_test.go | 3 +- src/build/Assignment.go | 10 --- src/build/Calculate.go | 81 ------------------- src/build/Execute.go | 111 +++++++++++++++++++++++++++ src/build/Function.go | 23 ------ src/build/expression/Operator.go | 80 +++++++++++-------- src/errors/InvalidOperator.go | 13 ++++ tests/errors/InvalidOperator.q | 3 + tests/errors/MissingAssignValue.q | 2 +- tests/errors/VariableAlreadyExists.q | 4 +- 10 files changed, 180 insertions(+), 150 deletions(-) delete mode 100644 src/build/Calculate.go create mode 100644 src/build/Execute.go create mode 100644 src/errors/InvalidOperator.go create mode 100644 tests/errors/InvalidOperator.q diff --git a/errors_test.go b/errors_test.go index efd2ae7..8eecad6 100644 --- a/errors_test.go +++ b/errors_test.go @@ -21,12 +21,13 @@ func TestErrors(t *testing.T) { {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, {"InvalidExpression.q", errors.InvalidExpression}, + {"InvalidOperator.q", &errors.InvalidOperator{Operator: "+++"}}, {"MissingAssignValue.q", errors.MissingAssignValue}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, {"MissingGroupStart.q", errors.MissingGroupStart}, - {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "a"}}, + {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, } diff --git a/src/build/Assignment.go b/src/build/Assignment.go index 7ef8d06..f2618f6 100644 --- a/src/build/Assignment.go +++ b/src/build/Assignment.go @@ -14,15 +14,5 @@ func (f *Function) CompileAssignment(expr *expression.Expression) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) } - if expr.Token.Text() == "=" { - return f.ExpressionToRegister(expr.Children[1], variable.Register) - } - - right := expr.Children[1] - - if right.IsLeaf() { - return f.Calculate(variable.Register, expr.Token, right.Token) - } - return f.Execute(expr.Token, variable.Register, expr.Children[1]) } diff --git a/src/build/Calculate.go b/src/build/Calculate.go deleted file mode 100644 index 5daeb72..0000000 --- a/src/build/Calculate.go +++ /dev/null @@ -1,81 +0,0 @@ -package build - -import ( - "strconv" - - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" -) - -// Calculate performs an operation on a register with the given operand. -func (f *Function) Calculate(register cpu.Register, operation token.Token, operand token.Token) error { - switch operand.Kind { - case token.Number: - value := operand.Text() - number, err := strconv.Atoi(value) - - if err != nil { - return err - } - - return f.CalculateRegisterNumber(operation, register, number) - - case token.Identifier: - name := operand.Text() - variable, exists := f.Variables[name] - - if !exists { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) - } - - return f.CalculateRegisterRegister(operation, register, variable.Register) - } - - return errors.New(errors.NotImplemented, f.File, operation.Position) -} - -// CalculateRegisterNumber performs an operation on a register and a number. -func (f *Function) CalculateRegisterNumber(operation token.Token, register cpu.Register, number int) error { - switch operation.Text() { - case "+", "+=": - f.Assembler.RegisterNumber(asm.ADD, register, number) - - case "-", "-=": - f.Assembler.RegisterNumber(asm.SUB, register, number) - - case "*", "*=": - f.Assembler.RegisterNumber(asm.MUL, register, number) - - case "/", "/=": - f.Assembler.RegisterNumber(asm.DIV, register, number) - - default: - return errors.New(errors.NotImplemented, f.File, operation.Position) - } - - return nil -} - -// CalculateRegisterRegister performs an operation on two registers. -func (f *Function) CalculateRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { - switch operation.Text() { - case "+", "+=": - f.Assembler.RegisterRegister(asm.ADD, destination, source) - - case "-", "-=": - f.Assembler.RegisterRegister(asm.SUB, destination, source) - - case "*", "*=": - f.Assembler.RegisterRegister(asm.MUL, destination, source) - - case "/", "/=": - f.Assembler.RegisterRegister(asm.DIV, destination, source) - - default: - return errors.New(errors.NotImplemented, f.File, operation.Position) - } - - return nil -} diff --git a/src/build/Execute.go b/src/build/Execute.go new file mode 100644 index 0000000..1f78ae0 --- /dev/null +++ b/src/build/Execute.go @@ -0,0 +1,111 @@ +package build + +import ( + "strconv" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// Execute executes an operation on a register with a value operand. +func (f *Function) Execute(operation token.Token, register cpu.Register, value *expression.Expression) error { + if value.IsLeaf() { + return f.ExecuteLeaf(operation, register, value.Token) + } + + temporary, found := f.CPU.FindFree(f.CPU.General) + + if !found { + panic("no free registers") + } + + f.CPU.Use(temporary) + defer f.CPU.Free(temporary) + err := f.ExpressionToRegister(value, temporary) + + if err != nil { + return err + } + + return f.ExecuteRegisterRegister(operation, register, temporary) +} + +// ExecuteLeaf performs an operation on a register with the given leaf operand. +func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error { + switch operand.Kind { + case token.Number: + value := operand.Text() + number, err := strconv.Atoi(value) + + if err != nil { + return err + } + + return f.ExecuteRegisterNumber(operation, register, number) + + case token.Identifier: + name := operand.Text() + variable, exists := f.Variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) + } + + return f.ExecuteRegisterRegister(operation, register, variable.Register) + } + + return errors.New(errors.NotImplemented, f.File, operation.Position) +} + +// ExecuteRegisterNumber performs an operation on a register and a number. +func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { + switch operation.Text() { + case "+", "+=": + f.Assembler.RegisterNumber(asm.ADD, register, number) + + case "-", "-=": + f.Assembler.RegisterNumber(asm.SUB, register, number) + + case "*", "*=": + f.Assembler.RegisterNumber(asm.MUL, register, number) + + case "/", "/=": + f.Assembler.RegisterNumber(asm.DIV, register, number) + + case "=": + f.Assembler.RegisterNumber(asm.MOVE, register, number) + + default: + return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) + } + + return nil +} + +// ExecuteRegisterRegister performs an operation on two registers. +func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { + switch operation.Text() { + case "+", "+=": + f.Assembler.RegisterRegister(asm.ADD, destination, source) + + case "-", "-=": + f.Assembler.RegisterRegister(asm.SUB, destination, source) + + case "*", "*=": + f.Assembler.RegisterRegister(asm.MUL, destination, source) + + case "/", "/=": + f.Assembler.RegisterRegister(asm.DIV, destination, source) + + case "=": + f.Assembler.RegisterRegister(asm.MOVE, destination, source) + + default: + return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) + } + + return nil +} diff --git a/src/build/Function.go b/src/build/Function.go index 1a50f0a..9d90c42 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -140,29 +140,6 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp return f.Execute(operation, register, right) } -// Execute executes an operation on a register with a value operand. -func (f *Function) Execute(operation token.Token, register cpu.Register, value *expression.Expression) error { - if value.IsLeaf() { - return f.Calculate(register, operation, value.Token) - } - - temporary, found := f.CPU.FindFree(f.CPU.General) - - if !found { - panic("no free registers") - } - - f.CPU.Use(temporary) - defer f.CPU.Free(temporary) - err := f.ExpressionToRegister(value, temporary) - - if err != nil { - return err - } - - return f.CalculateRegisterRegister(operation, register, temporary) -} - // TokenToRegister moves a token into a register. // It only works with identifiers, numbers and strings. func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index a1823e1..391ed74 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -1,6 +1,10 @@ package expression -import "git.akyoto.dev/cli/q/src/build/token" +import ( + "math" + + "git.akyoto.dev/cli/q/src/build/token" +) // Operator represents an operator for mathematical expressions. type Operator struct { @@ -12,35 +16,35 @@ type Operator struct { // Operators defines the operators used in the language. // The number corresponds to the operator priority and can not be zero. var Operators = map[string]*Operator{ - ".": {".", 14, 2}, - "λ": {"λ", 13, 1}, - "!": {"!", 12, 1}, - "*": {"*", 11, 2}, - "/": {"/", 11, 2}, - "%": {"%", 11, 2}, - "+": {"+", 10, 2}, - "-": {"-", 10, 2}, - ">>": {">>", 9, 2}, - "<<": {"<<", 9, 2}, - ">": {">", 8, 2}, - "<": {"<", 8, 2}, - ">=": {">=", 8, 2}, - "<=": {"<=", 8, 2}, - "==": {"==", 7, 2}, - "!=": {"!=", 7, 2}, - "&": {"&", 6, 2}, - "^": {"^", 5, 2}, - "|": {"|", 4, 2}, - "&&": {"&&", 3, 2}, - "||": {"||", 2, 2}, - "=": {"=", 1, 2}, - ":=": {":=", 1, 2}, - "+=": {"+=", 1, 2}, - "-=": {"-=", 1, 2}, - "*=": {"*=", 1, 2}, - "/=": {"/=", 1, 2}, - ">>=": {">>=", 1, 2}, - "<<=": {"<<=", 1, 2}, + ".": {".", 13, 2}, + "λ": {"λ", 12, 1}, + "!": {"!", 11, 1}, + "*": {"*", 10, 2}, + "/": {"/", 10, 2}, + "%": {"%", 10, 2}, + "+": {"+", 9, 2}, + "-": {"-", 9, 2}, + ">>": {">>", 8, 2}, + "<<": {"<<", 8, 2}, + ">": {">", 7, 2}, + "<": {"<", 7, 2}, + ">=": {">=", 7, 2}, + "<=": {"<=", 7, 2}, + "==": {"==", 6, 2}, + "!=": {"!=", 6, 2}, + "&": {"&", 5, 2}, + "^": {"^", 4, 2}, + "|": {"|", 3, 2}, + "&&": {"&&", 2, 2}, + "||": {"||", 1, 2}, + "=": {"=", math.MinInt, 2}, + ":=": {":=", math.MinInt, 2}, + "+=": {"+=", math.MinInt, 2}, + "-=": {"-=", math.MinInt, 2}, + "*=": {"*=", math.MinInt, 2}, + "/=": {"/=", math.MinInt, 2}, + ">>=": {">>=", math.MinInt, 2}, + "<<=": {"<<=", math.MinInt, 2}, } func isComplete(expr *Expression) bool { @@ -60,9 +64,21 @@ func isComplete(expr *Expression) bool { } func numOperands(symbol string) int { - return Operators[symbol].Operands + operator, exists := Operators[symbol] + + if !exists { + return -1 + } + + return operator.Operands } func precedence(symbol string) int { - return Operators[symbol].Precedence + operator, exists := Operators[symbol] + + if !exists { + return -1 + } + + return operator.Precedence } diff --git a/src/errors/InvalidOperator.go b/src/errors/InvalidOperator.go new file mode 100644 index 0000000..b3748ce --- /dev/null +++ b/src/errors/InvalidOperator.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// InvalidOperator error is created when an operator is not valid. +type InvalidOperator struct { + Operator string +} + +// Error generates the string representation. +func (err *InvalidOperator) Error() string { + return fmt.Sprintf("Invalid operator '%s'", err.Operator) +} diff --git a/tests/errors/InvalidOperator.q b/tests/errors/InvalidOperator.q new file mode 100644 index 0000000..7b5d4d2 --- /dev/null +++ b/tests/errors/InvalidOperator.q @@ -0,0 +1,3 @@ +main() { + x := 123 +++ 456 +} \ No newline at end of file diff --git a/tests/errors/MissingAssignValue.q b/tests/errors/MissingAssignValue.q index ae9e1eb..4eefb33 100644 --- a/tests/errors/MissingAssignValue.q +++ b/tests/errors/MissingAssignValue.q @@ -1,3 +1,3 @@ main() { - a := + x := } \ No newline at end of file diff --git a/tests/errors/VariableAlreadyExists.q b/tests/errors/VariableAlreadyExists.q index e55bbe6..a179f22 100644 --- a/tests/errors/VariableAlreadyExists.q +++ b/tests/errors/VariableAlreadyExists.q @@ -1,4 +1,4 @@ main() { - a := 1 - a := 2 + x := 1 + x := 2 } \ No newline at end of file From 94151773a56034fbe10428784950229c3e6a3be2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Jun 2024 23:24:11 +0200 Subject: [PATCH 0158/1012] Enforced consistent naming --- errors_test.go | 3 +- src/build/Assignment.go | 10 --- src/build/Calculate.go | 81 ------------------- src/build/Execute.go | 111 +++++++++++++++++++++++++++ src/build/Function.go | 23 ------ src/build/expression/Operator.go | 80 +++++++++++-------- src/errors/InvalidOperator.go | 13 ++++ tests/errors/InvalidOperator.q | 3 + tests/errors/MissingAssignValue.q | 2 +- tests/errors/VariableAlreadyExists.q | 4 +- 10 files changed, 180 insertions(+), 150 deletions(-) delete mode 100644 src/build/Calculate.go create mode 100644 src/build/Execute.go create mode 100644 src/errors/InvalidOperator.go create mode 100644 tests/errors/InvalidOperator.q diff --git a/errors_test.go b/errors_test.go index efd2ae7..8eecad6 100644 --- a/errors_test.go +++ b/errors_test.go @@ -21,12 +21,13 @@ func TestErrors(t *testing.T) { {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, {"InvalidExpression.q", errors.InvalidExpression}, + {"InvalidOperator.q", &errors.InvalidOperator{Operator: "+++"}}, {"MissingAssignValue.q", errors.MissingAssignValue}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, {"MissingGroupStart.q", errors.MissingGroupStart}, - {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "a"}}, + {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, } diff --git a/src/build/Assignment.go b/src/build/Assignment.go index 7ef8d06..f2618f6 100644 --- a/src/build/Assignment.go +++ b/src/build/Assignment.go @@ -14,15 +14,5 @@ func (f *Function) CompileAssignment(expr *expression.Expression) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) } - if expr.Token.Text() == "=" { - return f.ExpressionToRegister(expr.Children[1], variable.Register) - } - - right := expr.Children[1] - - if right.IsLeaf() { - return f.Calculate(variable.Register, expr.Token, right.Token) - } - return f.Execute(expr.Token, variable.Register, expr.Children[1]) } diff --git a/src/build/Calculate.go b/src/build/Calculate.go deleted file mode 100644 index 5daeb72..0000000 --- a/src/build/Calculate.go +++ /dev/null @@ -1,81 +0,0 @@ -package build - -import ( - "strconv" - - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" -) - -// Calculate performs an operation on a register with the given operand. -func (f *Function) Calculate(register cpu.Register, operation token.Token, operand token.Token) error { - switch operand.Kind { - case token.Number: - value := operand.Text() - number, err := strconv.Atoi(value) - - if err != nil { - return err - } - - return f.CalculateRegisterNumber(operation, register, number) - - case token.Identifier: - name := operand.Text() - variable, exists := f.Variables[name] - - if !exists { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) - } - - return f.CalculateRegisterRegister(operation, register, variable.Register) - } - - return errors.New(errors.NotImplemented, f.File, operation.Position) -} - -// CalculateRegisterNumber performs an operation on a register and a number. -func (f *Function) CalculateRegisterNumber(operation token.Token, register cpu.Register, number int) error { - switch operation.Text() { - case "+", "+=": - f.Assembler.RegisterNumber(asm.ADD, register, number) - - case "-", "-=": - f.Assembler.RegisterNumber(asm.SUB, register, number) - - case "*", "*=": - f.Assembler.RegisterNumber(asm.MUL, register, number) - - case "/", "/=": - f.Assembler.RegisterNumber(asm.DIV, register, number) - - default: - return errors.New(errors.NotImplemented, f.File, operation.Position) - } - - return nil -} - -// CalculateRegisterRegister performs an operation on two registers. -func (f *Function) CalculateRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { - switch operation.Text() { - case "+", "+=": - f.Assembler.RegisterRegister(asm.ADD, destination, source) - - case "-", "-=": - f.Assembler.RegisterRegister(asm.SUB, destination, source) - - case "*", "*=": - f.Assembler.RegisterRegister(asm.MUL, destination, source) - - case "/", "/=": - f.Assembler.RegisterRegister(asm.DIV, destination, source) - - default: - return errors.New(errors.NotImplemented, f.File, operation.Position) - } - - return nil -} diff --git a/src/build/Execute.go b/src/build/Execute.go new file mode 100644 index 0000000..1f78ae0 --- /dev/null +++ b/src/build/Execute.go @@ -0,0 +1,111 @@ +package build + +import ( + "strconv" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// Execute executes an operation on a register with a value operand. +func (f *Function) Execute(operation token.Token, register cpu.Register, value *expression.Expression) error { + if value.IsLeaf() { + return f.ExecuteLeaf(operation, register, value.Token) + } + + temporary, found := f.CPU.FindFree(f.CPU.General) + + if !found { + panic("no free registers") + } + + f.CPU.Use(temporary) + defer f.CPU.Free(temporary) + err := f.ExpressionToRegister(value, temporary) + + if err != nil { + return err + } + + return f.ExecuteRegisterRegister(operation, register, temporary) +} + +// ExecuteLeaf performs an operation on a register with the given leaf operand. +func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error { + switch operand.Kind { + case token.Number: + value := operand.Text() + number, err := strconv.Atoi(value) + + if err != nil { + return err + } + + return f.ExecuteRegisterNumber(operation, register, number) + + case token.Identifier: + name := operand.Text() + variable, exists := f.Variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) + } + + return f.ExecuteRegisterRegister(operation, register, variable.Register) + } + + return errors.New(errors.NotImplemented, f.File, operation.Position) +} + +// ExecuteRegisterNumber performs an operation on a register and a number. +func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { + switch operation.Text() { + case "+", "+=": + f.Assembler.RegisterNumber(asm.ADD, register, number) + + case "-", "-=": + f.Assembler.RegisterNumber(asm.SUB, register, number) + + case "*", "*=": + f.Assembler.RegisterNumber(asm.MUL, register, number) + + case "/", "/=": + f.Assembler.RegisterNumber(asm.DIV, register, number) + + case "=": + f.Assembler.RegisterNumber(asm.MOVE, register, number) + + default: + return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) + } + + return nil +} + +// ExecuteRegisterRegister performs an operation on two registers. +func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { + switch operation.Text() { + case "+", "+=": + f.Assembler.RegisterRegister(asm.ADD, destination, source) + + case "-", "-=": + f.Assembler.RegisterRegister(asm.SUB, destination, source) + + case "*", "*=": + f.Assembler.RegisterRegister(asm.MUL, destination, source) + + case "/", "/=": + f.Assembler.RegisterRegister(asm.DIV, destination, source) + + case "=": + f.Assembler.RegisterRegister(asm.MOVE, destination, source) + + default: + return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) + } + + return nil +} diff --git a/src/build/Function.go b/src/build/Function.go index 1a50f0a..9d90c42 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -140,29 +140,6 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp return f.Execute(operation, register, right) } -// Execute executes an operation on a register with a value operand. -func (f *Function) Execute(operation token.Token, register cpu.Register, value *expression.Expression) error { - if value.IsLeaf() { - return f.Calculate(register, operation, value.Token) - } - - temporary, found := f.CPU.FindFree(f.CPU.General) - - if !found { - panic("no free registers") - } - - f.CPU.Use(temporary) - defer f.CPU.Free(temporary) - err := f.ExpressionToRegister(value, temporary) - - if err != nil { - return err - } - - return f.CalculateRegisterRegister(operation, register, temporary) -} - // TokenToRegister moves a token into a register. // It only works with identifiers, numbers and strings. func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index a1823e1..391ed74 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -1,6 +1,10 @@ package expression -import "git.akyoto.dev/cli/q/src/build/token" +import ( + "math" + + "git.akyoto.dev/cli/q/src/build/token" +) // Operator represents an operator for mathematical expressions. type Operator struct { @@ -12,35 +16,35 @@ type Operator struct { // Operators defines the operators used in the language. // The number corresponds to the operator priority and can not be zero. var Operators = map[string]*Operator{ - ".": {".", 14, 2}, - "λ": {"λ", 13, 1}, - "!": {"!", 12, 1}, - "*": {"*", 11, 2}, - "/": {"/", 11, 2}, - "%": {"%", 11, 2}, - "+": {"+", 10, 2}, - "-": {"-", 10, 2}, - ">>": {">>", 9, 2}, - "<<": {"<<", 9, 2}, - ">": {">", 8, 2}, - "<": {"<", 8, 2}, - ">=": {">=", 8, 2}, - "<=": {"<=", 8, 2}, - "==": {"==", 7, 2}, - "!=": {"!=", 7, 2}, - "&": {"&", 6, 2}, - "^": {"^", 5, 2}, - "|": {"|", 4, 2}, - "&&": {"&&", 3, 2}, - "||": {"||", 2, 2}, - "=": {"=", 1, 2}, - ":=": {":=", 1, 2}, - "+=": {"+=", 1, 2}, - "-=": {"-=", 1, 2}, - "*=": {"*=", 1, 2}, - "/=": {"/=", 1, 2}, - ">>=": {">>=", 1, 2}, - "<<=": {"<<=", 1, 2}, + ".": {".", 13, 2}, + "λ": {"λ", 12, 1}, + "!": {"!", 11, 1}, + "*": {"*", 10, 2}, + "/": {"/", 10, 2}, + "%": {"%", 10, 2}, + "+": {"+", 9, 2}, + "-": {"-", 9, 2}, + ">>": {">>", 8, 2}, + "<<": {"<<", 8, 2}, + ">": {">", 7, 2}, + "<": {"<", 7, 2}, + ">=": {">=", 7, 2}, + "<=": {"<=", 7, 2}, + "==": {"==", 6, 2}, + "!=": {"!=", 6, 2}, + "&": {"&", 5, 2}, + "^": {"^", 4, 2}, + "|": {"|", 3, 2}, + "&&": {"&&", 2, 2}, + "||": {"||", 1, 2}, + "=": {"=", math.MinInt, 2}, + ":=": {":=", math.MinInt, 2}, + "+=": {"+=", math.MinInt, 2}, + "-=": {"-=", math.MinInt, 2}, + "*=": {"*=", math.MinInt, 2}, + "/=": {"/=", math.MinInt, 2}, + ">>=": {">>=", math.MinInt, 2}, + "<<=": {"<<=", math.MinInt, 2}, } func isComplete(expr *Expression) bool { @@ -60,9 +64,21 @@ func isComplete(expr *Expression) bool { } func numOperands(symbol string) int { - return Operators[symbol].Operands + operator, exists := Operators[symbol] + + if !exists { + return -1 + } + + return operator.Operands } func precedence(symbol string) int { - return Operators[symbol].Precedence + operator, exists := Operators[symbol] + + if !exists { + return -1 + } + + return operator.Precedence } diff --git a/src/errors/InvalidOperator.go b/src/errors/InvalidOperator.go new file mode 100644 index 0000000..b3748ce --- /dev/null +++ b/src/errors/InvalidOperator.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// InvalidOperator error is created when an operator is not valid. +type InvalidOperator struct { + Operator string +} + +// Error generates the string representation. +func (err *InvalidOperator) Error() string { + return fmt.Sprintf("Invalid operator '%s'", err.Operator) +} diff --git a/tests/errors/InvalidOperator.q b/tests/errors/InvalidOperator.q new file mode 100644 index 0000000..7b5d4d2 --- /dev/null +++ b/tests/errors/InvalidOperator.q @@ -0,0 +1,3 @@ +main() { + x := 123 +++ 456 +} \ No newline at end of file diff --git a/tests/errors/MissingAssignValue.q b/tests/errors/MissingAssignValue.q index ae9e1eb..4eefb33 100644 --- a/tests/errors/MissingAssignValue.q +++ b/tests/errors/MissingAssignValue.q @@ -1,3 +1,3 @@ main() { - a := + x := } \ No newline at end of file diff --git a/tests/errors/VariableAlreadyExists.q b/tests/errors/VariableAlreadyExists.q index e55bbe6..a179f22 100644 --- a/tests/errors/VariableAlreadyExists.q +++ b/tests/errors/VariableAlreadyExists.q @@ -1,4 +1,4 @@ main() { - a := 1 - a := 2 + x := 1 + x := 2 } \ No newline at end of file From 0d8891d8a7ec96c793ac9995dadb382cf3494f4f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 10:12:41 +0200 Subject: [PATCH 0159/1012] Implemented parameters --- examples/hello/hello.q | 17 ++++++++------ src/build/Execute.go | 4 +++- src/build/Function.go | 6 +++-- src/build/FunctionCall.go | 7 +++++- src/build/expression/List.go | 26 +++++++++++++++++----- src/build/scan.go | 43 +++++++++++++++++++++++++++--------- 6 files changed, 76 insertions(+), 27 deletions(-) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 9032441..ef15401 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -3,18 +3,13 @@ main() { } hello() { - write := 1 - stdout := 1 address := 0 length := 0 address += 4194304 address += 1 - length = 0 + 1 - length -= length - length += write + stdout - length -= length + length = address - address length += 50 length -= 20 length *= 10 @@ -22,6 +17,14 @@ hello() { length = (0 + 50 - 20) * 10 / 100 loop { - syscall(write, stdout, address, length) + print(address, length) } +} + +print(address, length) { + write(1, address, length) +} + +write(fd, address, length) { + syscall(1, fd, address, length) } \ No newline at end of file diff --git a/src/build/Execute.go b/src/build/Execute.go index 1f78ae0..84e51d6 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -101,7 +101,9 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cp f.Assembler.RegisterRegister(asm.DIV, destination, source) case "=": - f.Assembler.RegisterRegister(asm.MOVE, destination, source) + if destination != source { + f.Assembler.RegisterRegister(asm.MOVE, destination, source) + } default: return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) diff --git a/src/build/Function.go b/src/build/Function.go index 9d90c42..ba070c2 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -17,7 +17,6 @@ import ( type Function struct { Name string File *fs.File - Head token.List Body token.List Variables map[string]*Variable Assembler asm.Assembler @@ -152,7 +151,10 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - f.Assembler.RegisterRegister(asm.MOVE, register, variable.Register) + if register != variable.Register { + f.Assembler.RegisterRegister(asm.MOVE, register, variable.Register) + } + return nil case token.Number: diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go index 9c90ee7..8f2c2ea 100644 --- a/src/build/FunctionCall.go +++ b/src/build/FunctionCall.go @@ -8,9 +8,14 @@ import ( func (f *Function) CompileFunctionCall(expr *expression.Expression) error { funcName := expr.Children[0].Token.Text() parameters := expr.Children[1:] + registers := f.CPU.Syscall + + if funcName != "syscall" { + registers = registers[1:] + } for i := len(parameters) - 1; i >= 0; i-- { - err := f.ExpressionToRegister(parameters[i], f.CPU.Syscall[i]) + err := f.ExpressionToRegister(parameters[i], registers[i]) if err != nil { return err diff --git a/src/build/expression/List.go b/src/build/expression/List.go index b6fe4a6..2bb4b56 100644 --- a/src/build/expression/List.go +++ b/src/build/expression/List.go @@ -5,9 +5,20 @@ import ( ) // List generates a list of expressions from comma separated parameters. -func List(tokens []token.Token) []*Expression { +func List(tokens token.List) []*Expression { var list []*Expression + EachParameter(tokens, func(parameter token.List) error { + expression := Parse(parameter) + list = append(list, expression) + return nil + }) + + return list +} + +// EachParameter calls the callback function on each parameter in a comma separated list. +func EachParameter(tokens token.List, call func(token.List) error) error { start := 0 groupLevel := 0 @@ -25,17 +36,20 @@ func List(tokens []token.Token) []*Expression { } parameter := tokens[start:i] - expression := Parse(parameter) - list = append(list, expression) + err := call(parameter) + + if err != nil { + return err + } + start = i + 1 } } if start != len(tokens) { parameter := tokens[start:] - expression := Parse(parameter) - list = append(list, expression) + return call(parameter) } - return list + return nil } diff --git a/src/build/scan.go b/src/build/scan.go index fea378a..af81199 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -9,6 +9,7 @@ import ( "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" @@ -100,6 +101,7 @@ func scanFile(path string, functions chan<- *Function) error { blockLevel = 0 nameStart = -1 paramsStart = -1 + paramsEnd = -1 bodyStart = -1 ) @@ -128,12 +130,12 @@ func scanFile(path string, functions chan<- *Function) error { for i < len(tokens) { if tokens[i].Kind == token.GroupStart { groupLevel++ + i++ if groupLevel == 1 { paramsStart = i } - i++ continue } @@ -144,12 +146,13 @@ func scanFile(path string, functions chan<- *Function) error { return errors.New(errors.MissingGroupStart, file, tokens[i].Position) } - i++ - if groupLevel == 0 { + paramsEnd = i + i++ break } + i++ continue } @@ -221,17 +224,37 @@ func scanFile(path string, functions chan<- *Function) error { return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) } + cpu := cpu.CPU{ + General: x64.GeneralRegisters, + Syscall: x64.SyscallRegisters, + Return: x64.ReturnValueRegisters, + } + + parameters := tokens[paramsStart:paramsEnd] + variables := map[string]*Variable{} + + err := expression.EachParameter(parameters, func(parameter token.List) error { + if len(parameter) == 1 { + name := parameter[0].Text() + register := x64.SyscallRegisters[1+len(variables)] + variables[name] = &Variable{Name: name, Register: register} + cpu.Use(register) + return nil + } + + return errors.New(errors.NotImplemented, file, parameter[0].Position) + }) + + if err != nil { + return err + } + functions <- &Function{ Name: tokens[nameStart].Text(), File: file, - Head: tokens[paramsStart:bodyStart], Body: tokens[bodyStart:i], - Variables: map[string]*Variable{}, - CPU: cpu.CPU{ - General: x64.GeneralRegisters, - Syscall: x64.SyscallRegisters, - Return: x64.ReturnValueRegisters, - }, + Variables: variables, + CPU: cpu, Assembler: asm.Assembler{ Instructions: make([]asm.Instruction, 0, 32), }, From 8e64271f74316674c25f17f5783088f666277c95 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 10:12:41 +0200 Subject: [PATCH 0160/1012] Implemented parameters --- examples/hello/hello.q | 17 ++++++++------ src/build/Execute.go | 4 +++- src/build/Function.go | 6 +++-- src/build/FunctionCall.go | 7 +++++- src/build/expression/List.go | 26 +++++++++++++++++----- src/build/scan.go | 43 +++++++++++++++++++++++++++--------- 6 files changed, 76 insertions(+), 27 deletions(-) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 9032441..ef15401 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -3,18 +3,13 @@ main() { } hello() { - write := 1 - stdout := 1 address := 0 length := 0 address += 4194304 address += 1 - length = 0 + 1 - length -= length - length += write + stdout - length -= length + length = address - address length += 50 length -= 20 length *= 10 @@ -22,6 +17,14 @@ hello() { length = (0 + 50 - 20) * 10 / 100 loop { - syscall(write, stdout, address, length) + print(address, length) } +} + +print(address, length) { + write(1, address, length) +} + +write(fd, address, length) { + syscall(1, fd, address, length) } \ No newline at end of file diff --git a/src/build/Execute.go b/src/build/Execute.go index 1f78ae0..84e51d6 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -101,7 +101,9 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cp f.Assembler.RegisterRegister(asm.DIV, destination, source) case "=": - f.Assembler.RegisterRegister(asm.MOVE, destination, source) + if destination != source { + f.Assembler.RegisterRegister(asm.MOVE, destination, source) + } default: return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) diff --git a/src/build/Function.go b/src/build/Function.go index 9d90c42..ba070c2 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -17,7 +17,6 @@ import ( type Function struct { Name string File *fs.File - Head token.List Body token.List Variables map[string]*Variable Assembler asm.Assembler @@ -152,7 +151,10 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - f.Assembler.RegisterRegister(asm.MOVE, register, variable.Register) + if register != variable.Register { + f.Assembler.RegisterRegister(asm.MOVE, register, variable.Register) + } + return nil case token.Number: diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go index 9c90ee7..8f2c2ea 100644 --- a/src/build/FunctionCall.go +++ b/src/build/FunctionCall.go @@ -8,9 +8,14 @@ import ( func (f *Function) CompileFunctionCall(expr *expression.Expression) error { funcName := expr.Children[0].Token.Text() parameters := expr.Children[1:] + registers := f.CPU.Syscall + + if funcName != "syscall" { + registers = registers[1:] + } for i := len(parameters) - 1; i >= 0; i-- { - err := f.ExpressionToRegister(parameters[i], f.CPU.Syscall[i]) + err := f.ExpressionToRegister(parameters[i], registers[i]) if err != nil { return err diff --git a/src/build/expression/List.go b/src/build/expression/List.go index b6fe4a6..2bb4b56 100644 --- a/src/build/expression/List.go +++ b/src/build/expression/List.go @@ -5,9 +5,20 @@ import ( ) // List generates a list of expressions from comma separated parameters. -func List(tokens []token.Token) []*Expression { +func List(tokens token.List) []*Expression { var list []*Expression + EachParameter(tokens, func(parameter token.List) error { + expression := Parse(parameter) + list = append(list, expression) + return nil + }) + + return list +} + +// EachParameter calls the callback function on each parameter in a comma separated list. +func EachParameter(tokens token.List, call func(token.List) error) error { start := 0 groupLevel := 0 @@ -25,17 +36,20 @@ func List(tokens []token.Token) []*Expression { } parameter := tokens[start:i] - expression := Parse(parameter) - list = append(list, expression) + err := call(parameter) + + if err != nil { + return err + } + start = i + 1 } } if start != len(tokens) { parameter := tokens[start:] - expression := Parse(parameter) - list = append(list, expression) + return call(parameter) } - return list + return nil } diff --git a/src/build/scan.go b/src/build/scan.go index fea378a..af81199 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -9,6 +9,7 @@ import ( "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" @@ -100,6 +101,7 @@ func scanFile(path string, functions chan<- *Function) error { blockLevel = 0 nameStart = -1 paramsStart = -1 + paramsEnd = -1 bodyStart = -1 ) @@ -128,12 +130,12 @@ func scanFile(path string, functions chan<- *Function) error { for i < len(tokens) { if tokens[i].Kind == token.GroupStart { groupLevel++ + i++ if groupLevel == 1 { paramsStart = i } - i++ continue } @@ -144,12 +146,13 @@ func scanFile(path string, functions chan<- *Function) error { return errors.New(errors.MissingGroupStart, file, tokens[i].Position) } - i++ - if groupLevel == 0 { + paramsEnd = i + i++ break } + i++ continue } @@ -221,17 +224,37 @@ func scanFile(path string, functions chan<- *Function) error { return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) } + cpu := cpu.CPU{ + General: x64.GeneralRegisters, + Syscall: x64.SyscallRegisters, + Return: x64.ReturnValueRegisters, + } + + parameters := tokens[paramsStart:paramsEnd] + variables := map[string]*Variable{} + + err := expression.EachParameter(parameters, func(parameter token.List) error { + if len(parameter) == 1 { + name := parameter[0].Text() + register := x64.SyscallRegisters[1+len(variables)] + variables[name] = &Variable{Name: name, Register: register} + cpu.Use(register) + return nil + } + + return errors.New(errors.NotImplemented, file, parameter[0].Position) + }) + + if err != nil { + return err + } + functions <- &Function{ Name: tokens[nameStart].Text(), File: file, - Head: tokens[paramsStart:bodyStart], Body: tokens[bodyStart:i], - Variables: map[string]*Variable{}, - CPU: cpu.CPU{ - General: x64.GeneralRegisters, - Syscall: x64.SyscallRegisters, - Return: x64.ReturnValueRegisters, - }, + Variables: variables, + CPU: cpu, Assembler: asm.Assembler{ Instructions: make([]asm.Instruction, 0, 32), }, From 8867845310b94d47bd734cbb74ad008a6b6501d1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 12:59:41 +0200 Subject: [PATCH 0161/1012] Added push and pop instructions --- src/build/asm/Assembler.go | 12 ++++++++++++ src/build/asm/Instructions.go | 10 ++++++++++ src/build/asm/Mnemonic.go | 8 ++++++++ src/build/asm/Register.go | 15 +++++++++++++++ 4 files changed, 45 insertions(+) create mode 100644 src/build/asm/Register.go diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 9fe9229..e3341a3 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -91,6 +91,18 @@ func (a *Assembler) Finalize() ([]byte, []byte) { code = x64.MoveRegisterRegister64(code, operands.Destination, operands.Source) } + case POP: + switch operands := x.Data.(type) { + case *Register: + code = x64.PopRegister(code, operands.Register) + } + + case PUSH: + switch operands := x.Data.(type) { + case *Register: + code = x64.PushRegister(code, operands.Register) + } + case RETURN: code = x64.Return(code) diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 04de2ea..4856d1e 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -24,6 +24,16 @@ func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right }) } +// Register adds an instruction using a single register. +func (a *Assembler) Register(mnemonic Mnemonic, register cpu.Register) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &Register{ + Register: register, + }, + }) +} + // Label adds a label at the current position. func (a *Assembler) Label(name string) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 95fc2a2..77dbf81 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -11,6 +11,8 @@ const ( MUL LABEL MOVE + POP + PUSH RETURN SUB SYSCALL @@ -40,6 +42,12 @@ func (m Mnemonic) String() string { case MUL: return "mul" + case POP: + return "pop" + + case PUSH: + return "push" + case RETURN: return "return" diff --git a/src/build/asm/Register.go b/src/build/asm/Register.go new file mode 100644 index 0000000..a618370 --- /dev/null +++ b/src/build/asm/Register.go @@ -0,0 +1,15 @@ +package asm + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// Register operates with a single register. +type Register struct { + Register cpu.Register +} + +// String returns a human readable version. +func (data *Register) String() string { + return data.Register.String() +} From 735057ac7473fd7bb8b77ab30095475078e2b6d5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 12:59:41 +0200 Subject: [PATCH 0162/1012] Added push and pop instructions --- src/build/asm/Assembler.go | 12 ++++++++++++ src/build/asm/Instructions.go | 10 ++++++++++ src/build/asm/Mnemonic.go | 8 ++++++++ src/build/asm/Register.go | 15 +++++++++++++++ 4 files changed, 45 insertions(+) create mode 100644 src/build/asm/Register.go diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 9fe9229..e3341a3 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -91,6 +91,18 @@ func (a *Assembler) Finalize() ([]byte, []byte) { code = x64.MoveRegisterRegister64(code, operands.Destination, operands.Source) } + case POP: + switch operands := x.Data.(type) { + case *Register: + code = x64.PopRegister(code, operands.Register) + } + + case PUSH: + switch operands := x.Data.(type) { + case *Register: + code = x64.PushRegister(code, operands.Register) + } + case RETURN: code = x64.Return(code) diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 04de2ea..4856d1e 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -24,6 +24,16 @@ func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right }) } +// Register adds an instruction using a single register. +func (a *Assembler) Register(mnemonic Mnemonic, register cpu.Register) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &Register{ + Register: register, + }, + }) +} + // Label adds a label at the current position. func (a *Assembler) Label(name string) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 95fc2a2..77dbf81 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -11,6 +11,8 @@ const ( MUL LABEL MOVE + POP + PUSH RETURN SUB SYSCALL @@ -40,6 +42,12 @@ func (m Mnemonic) String() string { case MUL: return "mul" + case POP: + return "pop" + + case PUSH: + return "push" + case RETURN: return "return" diff --git a/src/build/asm/Register.go b/src/build/asm/Register.go new file mode 100644 index 0000000..a618370 --- /dev/null +++ b/src/build/asm/Register.go @@ -0,0 +1,15 @@ +package asm + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// Register operates with a single register. +type Register struct { + Register cpu.Register +} + +// String returns a human readable version. +func (data *Register) String() string { + return data.Register.String() +} From 9e3112ad52002cb69cc54b8b496477083c4c681f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 14:25:53 +0200 Subject: [PATCH 0163/1012] Added call registers --- examples/hello/hello.q | 7 ++----- src/build/Execute.go | 26 ++++++++++++++++++++++++++ src/build/Function.go | 15 ++++++++++++++- src/build/FunctionCall.go | 32 -------------------------------- src/build/VariableDefinition.go | 6 +++--- src/build/arch/x64/Registers.go | 9 ++++++--- src/build/cpu/CPU.go | 1 + src/build/scan.go | 1 + 8 files changed, 53 insertions(+), 44 deletions(-) delete mode 100644 src/build/FunctionCall.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index ef15401..afd5576 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,8 +1,4 @@ main() { - hello() -} - -hello() { address := 0 length := 0 @@ -15,6 +11,7 @@ hello() { length *= 10 length /= 100 length = (0 + 50 - 20) * 10 / 100 + length = 1 loop { print(address, length) @@ -22,7 +19,7 @@ hello() { } print(address, length) { - write(1, address, length) + write(length, address, length+1) } write(fd, address, length) { diff --git a/src/build/Execute.go b/src/build/Execute.go index 84e51d6..d636bed 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -33,6 +33,32 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteRegisterRegister(operation, register, temporary) } +// ExecuteFunctionCall executes a function call. +func (f *Function) ExecuteFunctionCall(expr *expression.Expression) error { + funcName := expr.Children[0].Token.Text() + parameters := expr.Children[1:] + + if funcName == "syscall" { + err := f.ExpressionsToRegisters(parameters, f.CPU.Syscall) + + if err != nil { + return err + } + + f.Assembler.Syscall() + } else { + err := f.ExpressionsToRegisters(parameters, f.CPU.Call) + + if err != nil { + return err + } + + f.Assembler.Call(funcName) + } + + return nil +} + // ExecuteLeaf performs an operation on a register with the given leaf operand. func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error { switch operand.Kind { diff --git a/src/build/Function.go b/src/build/Function.go index ba070c2..fadb68b 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -113,7 +113,7 @@ func (f *Function) CompileInstruction(line token.List) error { } if isFunctionCall(expr) { - return f.CompileFunctionCall(expr) + return f.ExecuteFunctionCall(expr) } return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) @@ -139,6 +139,19 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp return f.Execute(operation, register, right) } +// ExpressionsToRegisters moves multiple expressions into the specified registers. +func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { + for i := len(expressions) - 1; i >= 0; i-- { + err := f.ExpressionToRegister(expressions[i], registers[i]) + + if err != nil { + return err + } + } + + return nil +} + // TokenToRegister moves a token into a register. // It only works with identifiers, numbers and strings. func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go deleted file mode 100644 index 8f2c2ea..0000000 --- a/src/build/FunctionCall.go +++ /dev/null @@ -1,32 +0,0 @@ -package build - -import ( - "git.akyoto.dev/cli/q/src/build/expression" -) - -// CompileFunctionCall compiles a top-level function call. -func (f *Function) CompileFunctionCall(expr *expression.Expression) error { - funcName := expr.Children[0].Token.Text() - parameters := expr.Children[1:] - registers := f.CPU.Syscall - - if funcName != "syscall" { - registers = registers[1:] - } - - for i := len(parameters) - 1; i >= 0; i-- { - err := f.ExpressionToRegister(parameters[i], registers[i]) - - if err != nil { - return err - } - } - - if funcName == "syscall" { - f.Assembler.Syscall() - } else { - f.Assembler.Call(funcName) - } - - return nil -} diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index 270b8e1..b7ac7ca 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -29,12 +29,12 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return err } - f.CPU.Use(reg) - - f.Variables[name] = &Variable{ + variable := &Variable{ Name: name, Register: reg, } + f.Variables[name] = variable + f.CPU.Use(reg) return nil } diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go index da154af..44807c4 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/build/arch/x64/Registers.go @@ -21,6 +21,9 @@ const ( R15 ) -var GeneralRegisters = []cpu.Register{RBX, RBP, R12, R13, R14, R15} -var SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} -var ReturnValueRegisters = []cpu.Register{RAX, RCX, R11} +var ( + CallRegisters = []cpu.Register{RDI, RSI, RDX, RCX, R8, R9} + GeneralRegisters = []cpu.Register{RBX, RBP, R12, R13, R14, R15} + SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} + ReturnValueRegisters = []cpu.Register{RAX, RCX, R11} +) diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go index f6a6f9b..0092154 100644 --- a/src/build/cpu/CPU.go +++ b/src/build/cpu/CPU.go @@ -2,6 +2,7 @@ package cpu // CPU represents the processor. type CPU struct { + Call []Register General []Register Syscall []Register Return []Register diff --git a/src/build/scan.go b/src/build/scan.go index af81199..f9b7ea0 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -225,6 +225,7 @@ func scanFile(path string, functions chan<- *Function) error { } cpu := cpu.CPU{ + Call: x64.CallRegisters, General: x64.GeneralRegisters, Syscall: x64.SyscallRegisters, Return: x64.ReturnValueRegisters, From dc497ba4fb8f89bd511bae2fca98220d09a1e5a4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 14:25:53 +0200 Subject: [PATCH 0164/1012] Added call registers --- examples/hello/hello.q | 7 ++----- src/build/Execute.go | 26 ++++++++++++++++++++++++++ src/build/Function.go | 15 ++++++++++++++- src/build/FunctionCall.go | 32 -------------------------------- src/build/VariableDefinition.go | 6 +++--- src/build/arch/x64/Registers.go | 9 ++++++--- src/build/cpu/CPU.go | 1 + src/build/scan.go | 1 + 8 files changed, 53 insertions(+), 44 deletions(-) delete mode 100644 src/build/FunctionCall.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index ef15401..afd5576 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,8 +1,4 @@ main() { - hello() -} - -hello() { address := 0 length := 0 @@ -15,6 +11,7 @@ hello() { length *= 10 length /= 100 length = (0 + 50 - 20) * 10 / 100 + length = 1 loop { print(address, length) @@ -22,7 +19,7 @@ hello() { } print(address, length) { - write(1, address, length) + write(length, address, length+1) } write(fd, address, length) { diff --git a/src/build/Execute.go b/src/build/Execute.go index 84e51d6..d636bed 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -33,6 +33,32 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteRegisterRegister(operation, register, temporary) } +// ExecuteFunctionCall executes a function call. +func (f *Function) ExecuteFunctionCall(expr *expression.Expression) error { + funcName := expr.Children[0].Token.Text() + parameters := expr.Children[1:] + + if funcName == "syscall" { + err := f.ExpressionsToRegisters(parameters, f.CPU.Syscall) + + if err != nil { + return err + } + + f.Assembler.Syscall() + } else { + err := f.ExpressionsToRegisters(parameters, f.CPU.Call) + + if err != nil { + return err + } + + f.Assembler.Call(funcName) + } + + return nil +} + // ExecuteLeaf performs an operation on a register with the given leaf operand. func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error { switch operand.Kind { diff --git a/src/build/Function.go b/src/build/Function.go index ba070c2..fadb68b 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -113,7 +113,7 @@ func (f *Function) CompileInstruction(line token.List) error { } if isFunctionCall(expr) { - return f.CompileFunctionCall(expr) + return f.ExecuteFunctionCall(expr) } return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) @@ -139,6 +139,19 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp return f.Execute(operation, register, right) } +// ExpressionsToRegisters moves multiple expressions into the specified registers. +func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { + for i := len(expressions) - 1; i >= 0; i-- { + err := f.ExpressionToRegister(expressions[i], registers[i]) + + if err != nil { + return err + } + } + + return nil +} + // TokenToRegister moves a token into a register. // It only works with identifiers, numbers and strings. func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go deleted file mode 100644 index 8f2c2ea..0000000 --- a/src/build/FunctionCall.go +++ /dev/null @@ -1,32 +0,0 @@ -package build - -import ( - "git.akyoto.dev/cli/q/src/build/expression" -) - -// CompileFunctionCall compiles a top-level function call. -func (f *Function) CompileFunctionCall(expr *expression.Expression) error { - funcName := expr.Children[0].Token.Text() - parameters := expr.Children[1:] - registers := f.CPU.Syscall - - if funcName != "syscall" { - registers = registers[1:] - } - - for i := len(parameters) - 1; i >= 0; i-- { - err := f.ExpressionToRegister(parameters[i], registers[i]) - - if err != nil { - return err - } - } - - if funcName == "syscall" { - f.Assembler.Syscall() - } else { - f.Assembler.Call(funcName) - } - - return nil -} diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index 270b8e1..b7ac7ca 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -29,12 +29,12 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return err } - f.CPU.Use(reg) - - f.Variables[name] = &Variable{ + variable := &Variable{ Name: name, Register: reg, } + f.Variables[name] = variable + f.CPU.Use(reg) return nil } diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go index da154af..44807c4 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/build/arch/x64/Registers.go @@ -21,6 +21,9 @@ const ( R15 ) -var GeneralRegisters = []cpu.Register{RBX, RBP, R12, R13, R14, R15} -var SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} -var ReturnValueRegisters = []cpu.Register{RAX, RCX, R11} +var ( + CallRegisters = []cpu.Register{RDI, RSI, RDX, RCX, R8, R9} + GeneralRegisters = []cpu.Register{RBX, RBP, R12, R13, R14, R15} + SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} + ReturnValueRegisters = []cpu.Register{RAX, RCX, R11} +) diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go index f6a6f9b..0092154 100644 --- a/src/build/cpu/CPU.go +++ b/src/build/cpu/CPU.go @@ -2,6 +2,7 @@ package cpu // CPU represents the processor. type CPU struct { + Call []Register General []Register Syscall []Register Return []Register diff --git a/src/build/scan.go b/src/build/scan.go index af81199..f9b7ea0 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -225,6 +225,7 @@ func scanFile(path string, functions chan<- *Function) error { } cpu := cpu.CPU{ + Call: x64.CallRegisters, General: x64.GeneralRegisters, Syscall: x64.SyscallRegisters, Return: x64.ReturnValueRegisters, From 6b5bc22ec6df51c0fb3e21d465042485faf40aa0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 15:23:02 +0200 Subject: [PATCH 0165/1012] Added simple variable lifetimes --- src/build/Assignment.go | 1 + src/build/Execute.go | 21 ++++++++------- src/build/Function.go | 38 ++++++++++++++++++++++++++- src/build/Variable.go | 5 ++-- src/build/VariableDefinition.go | 35 +++++++++++++++++++++++-- src/build/scan.go | 46 ++++++++++++++++----------------- 6 files changed, 107 insertions(+), 39 deletions(-) diff --git a/src/build/Assignment.go b/src/build/Assignment.go index f2618f6..895078b 100644 --- a/src/build/Assignment.go +++ b/src/build/Assignment.go @@ -14,5 +14,6 @@ func (f *Function) CompileAssignment(expr *expression.Expression) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) } + defer f.useVariable(variable) return f.Execute(expr.Token, variable.Register, expr.Children[1]) } diff --git a/src/build/Execute.go b/src/build/Execute.go index d636bed..1312766 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -62,6 +62,17 @@ func (f *Function) ExecuteFunctionCall(expr *expression.Expression) error { // ExecuteLeaf performs an operation on a register with the given leaf operand. func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error { switch operand.Kind { + case token.Identifier: + name := operand.Text() + variable, exists := f.Variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) + } + + defer f.useVariable(variable) + return f.ExecuteRegisterRegister(operation, register, variable.Register) + case token.Number: value := operand.Text() number, err := strconv.Atoi(value) @@ -71,16 +82,6 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope } return f.ExecuteRegisterNumber(operation, register, number) - - case token.Identifier: - name := operand.Text() - variable, exists := f.Variables[name] - - if !exists { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) - } - - return f.ExecuteRegisterRegister(operation, register, variable.Register) } return errors.New(errors.NotImplemented, f.File, operation.Position) diff --git a/src/build/Function.go b/src/build/Function.go index fadb68b..8815d26 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -4,6 +4,7 @@ import ( "fmt" "strconv" + "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" @@ -141,17 +142,51 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp // ExpressionsToRegisters moves multiple expressions into the specified registers. func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { + var destinations []cpu.Register + for i := len(expressions) - 1; i >= 0; i-- { - err := f.ExpressionToRegister(expressions[i], registers[i]) + original := registers[i] + expression := expressions[i] + + if expression.IsLeaf() { + variable, exists := f.Variables[expression.Token.Text()] + + if exists && variable.Register == original { + continue + } + } + + register := original + save := !f.CPU.IsFree(register) + + if save { + register = x64.RAX + } + + err := f.ExpressionToRegister(expression, register) if err != nil { return err } + + if save { + destinations = append(destinations, original) + f.Assembler.Register(asm.PUSH, x64.RAX) + } + } + + for i := len(destinations) - 1; i >= 0; i-- { + f.Assembler.Register(asm.POP, destinations[i]) } return nil } +func (f *Function) Log(messages ...any) { + fmt.Printf("[%s] ", f.Name) + fmt.Println(messages...) +} + // TokenToRegister moves a token into a register. // It only works with identifiers, numbers and strings. func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { @@ -168,6 +203,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { f.Assembler.RegisterRegister(asm.MOVE, register, variable.Register) } + f.useVariable(variable) return nil case token.Number: diff --git a/src/build/Variable.go b/src/build/Variable.go index c8189f1..cb6ff43 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -6,6 +6,7 @@ import ( // Variable represents a variable in a function. type Variable struct { - Name string - Register cpu.Register + Name string + Register cpu.Register + UsesRemaining int } diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index b7ac7ca..b952bab 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -2,6 +2,7 @@ package build import ( "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" ) @@ -34,7 +35,37 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error Register: reg, } - f.Variables[name] = variable - f.CPU.Use(reg) + f.addVariable(variable) + f.useVariable(variable) return nil } + +func (f *Function) addVariable(variable *Variable) { + variable.UsesRemaining = countIdentifier(f.Body, variable.Name) + f.Variables[variable.Name] = variable + f.CPU.Use(variable.Register) +} + +func (f *Function) useVariable(variable *Variable) { + variable.UsesRemaining-- + + if variable.UsesRemaining < 0 { + panic("incorrect number of variable use calls") + } + + if variable.UsesRemaining == 0 { + f.CPU.Free(variable.Register) + } +} + +func countIdentifier(tokens token.List, name string) int { + count := 0 + + for _, t := range tokens { + if t.Kind == token.Identifier && t.Text() == name { + count++ + } + } + + return count +} diff --git a/src/build/scan.go b/src/build/scan.go index f9b7ea0..fd01a26 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -224,43 +224,41 @@ func scanFile(path string, functions chan<- *Function) error { return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) } - cpu := cpu.CPU{ - Call: x64.CallRegisters, - General: x64.GeneralRegisters, - Syscall: x64.SyscallRegisters, - Return: x64.ReturnValueRegisters, + function := &Function{ + Name: tokens[nameStart].Text(), + File: file, + Body: tokens[bodyStart:i], + Variables: map[string]*Variable{}, + CPU: cpu.CPU{ + Call: x64.CallRegisters, + General: x64.GeneralRegisters, + Syscall: x64.SyscallRegisters, + Return: x64.ReturnValueRegisters, + }, + Assembler: asm.Assembler{ + Instructions: make([]asm.Instruction, 0, 32), + }, } parameters := tokens[paramsStart:paramsEnd] - variables := map[string]*Variable{} - err := expression.EachParameter(parameters, func(parameter token.List) error { - if len(parameter) == 1 { - name := parameter[0].Text() - register := x64.SyscallRegisters[1+len(variables)] - variables[name] = &Variable{Name: name, Register: register} - cpu.Use(register) + err := expression.EachParameter(parameters, func(tokens token.List) error { + if len(tokens) == 1 { + name := tokens[0].Text() + register := x64.CallRegisters[len(function.Variables)] + variable := &Variable{Name: name, Register: register} + function.addVariable(variable) return nil } - return errors.New(errors.NotImplemented, file, parameter[0].Position) + return errors.New(errors.NotImplemented, file, tokens[0].Position) }) if err != nil { return err } - functions <- &Function{ - Name: tokens[nameStart].Text(), - File: file, - Body: tokens[bodyStart:i], - Variables: variables, - CPU: cpu, - Assembler: asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 32), - }, - } - + functions <- function nameStart = -1 paramsStart = -1 bodyStart = -1 From ec5240425bb14d5f398ec497de66d0408c39b008 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 15:23:02 +0200 Subject: [PATCH 0166/1012] Added simple variable lifetimes --- src/build/Assignment.go | 1 + src/build/Execute.go | 21 ++++++++------- src/build/Function.go | 38 ++++++++++++++++++++++++++- src/build/Variable.go | 5 ++-- src/build/VariableDefinition.go | 35 +++++++++++++++++++++++-- src/build/scan.go | 46 ++++++++++++++++----------------- 6 files changed, 107 insertions(+), 39 deletions(-) diff --git a/src/build/Assignment.go b/src/build/Assignment.go index f2618f6..895078b 100644 --- a/src/build/Assignment.go +++ b/src/build/Assignment.go @@ -14,5 +14,6 @@ func (f *Function) CompileAssignment(expr *expression.Expression) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) } + defer f.useVariable(variable) return f.Execute(expr.Token, variable.Register, expr.Children[1]) } diff --git a/src/build/Execute.go b/src/build/Execute.go index d636bed..1312766 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -62,6 +62,17 @@ func (f *Function) ExecuteFunctionCall(expr *expression.Expression) error { // ExecuteLeaf performs an operation on a register with the given leaf operand. func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error { switch operand.Kind { + case token.Identifier: + name := operand.Text() + variable, exists := f.Variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) + } + + defer f.useVariable(variable) + return f.ExecuteRegisterRegister(operation, register, variable.Register) + case token.Number: value := operand.Text() number, err := strconv.Atoi(value) @@ -71,16 +82,6 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope } return f.ExecuteRegisterNumber(operation, register, number) - - case token.Identifier: - name := operand.Text() - variable, exists := f.Variables[name] - - if !exists { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) - } - - return f.ExecuteRegisterRegister(operation, register, variable.Register) } return errors.New(errors.NotImplemented, f.File, operation.Position) diff --git a/src/build/Function.go b/src/build/Function.go index fadb68b..8815d26 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -4,6 +4,7 @@ import ( "fmt" "strconv" + "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" @@ -141,17 +142,51 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp // ExpressionsToRegisters moves multiple expressions into the specified registers. func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { + var destinations []cpu.Register + for i := len(expressions) - 1; i >= 0; i-- { - err := f.ExpressionToRegister(expressions[i], registers[i]) + original := registers[i] + expression := expressions[i] + + if expression.IsLeaf() { + variable, exists := f.Variables[expression.Token.Text()] + + if exists && variable.Register == original { + continue + } + } + + register := original + save := !f.CPU.IsFree(register) + + if save { + register = x64.RAX + } + + err := f.ExpressionToRegister(expression, register) if err != nil { return err } + + if save { + destinations = append(destinations, original) + f.Assembler.Register(asm.PUSH, x64.RAX) + } + } + + for i := len(destinations) - 1; i >= 0; i-- { + f.Assembler.Register(asm.POP, destinations[i]) } return nil } +func (f *Function) Log(messages ...any) { + fmt.Printf("[%s] ", f.Name) + fmt.Println(messages...) +} + // TokenToRegister moves a token into a register. // It only works with identifiers, numbers and strings. func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { @@ -168,6 +203,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { f.Assembler.RegisterRegister(asm.MOVE, register, variable.Register) } + f.useVariable(variable) return nil case token.Number: diff --git a/src/build/Variable.go b/src/build/Variable.go index c8189f1..cb6ff43 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -6,6 +6,7 @@ import ( // Variable represents a variable in a function. type Variable struct { - Name string - Register cpu.Register + Name string + Register cpu.Register + UsesRemaining int } diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index b7ac7ca..b952bab 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -2,6 +2,7 @@ package build import ( "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" ) @@ -34,7 +35,37 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error Register: reg, } - f.Variables[name] = variable - f.CPU.Use(reg) + f.addVariable(variable) + f.useVariable(variable) return nil } + +func (f *Function) addVariable(variable *Variable) { + variable.UsesRemaining = countIdentifier(f.Body, variable.Name) + f.Variables[variable.Name] = variable + f.CPU.Use(variable.Register) +} + +func (f *Function) useVariable(variable *Variable) { + variable.UsesRemaining-- + + if variable.UsesRemaining < 0 { + panic("incorrect number of variable use calls") + } + + if variable.UsesRemaining == 0 { + f.CPU.Free(variable.Register) + } +} + +func countIdentifier(tokens token.List, name string) int { + count := 0 + + for _, t := range tokens { + if t.Kind == token.Identifier && t.Text() == name { + count++ + } + } + + return count +} diff --git a/src/build/scan.go b/src/build/scan.go index f9b7ea0..fd01a26 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -224,43 +224,41 @@ func scanFile(path string, functions chan<- *Function) error { return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) } - cpu := cpu.CPU{ - Call: x64.CallRegisters, - General: x64.GeneralRegisters, - Syscall: x64.SyscallRegisters, - Return: x64.ReturnValueRegisters, + function := &Function{ + Name: tokens[nameStart].Text(), + File: file, + Body: tokens[bodyStart:i], + Variables: map[string]*Variable{}, + CPU: cpu.CPU{ + Call: x64.CallRegisters, + General: x64.GeneralRegisters, + Syscall: x64.SyscallRegisters, + Return: x64.ReturnValueRegisters, + }, + Assembler: asm.Assembler{ + Instructions: make([]asm.Instruction, 0, 32), + }, } parameters := tokens[paramsStart:paramsEnd] - variables := map[string]*Variable{} - err := expression.EachParameter(parameters, func(parameter token.List) error { - if len(parameter) == 1 { - name := parameter[0].Text() - register := x64.SyscallRegisters[1+len(variables)] - variables[name] = &Variable{Name: name, Register: register} - cpu.Use(register) + err := expression.EachParameter(parameters, func(tokens token.List) error { + if len(tokens) == 1 { + name := tokens[0].Text() + register := x64.CallRegisters[len(function.Variables)] + variable := &Variable{Name: name, Register: register} + function.addVariable(variable) return nil } - return errors.New(errors.NotImplemented, file, parameter[0].Position) + return errors.New(errors.NotImplemented, file, tokens[0].Position) }) if err != nil { return err } - functions <- &Function{ - Name: tokens[nameStart].Text(), - File: file, - Body: tokens[bodyStart:i], - Variables: variables, - CPU: cpu, - Assembler: asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 32), - }, - } - + functions <- function nameStart = -1 paramsStart = -1 bodyStart = -1 From e7208b8ba33f5356ae38cc54da499289e6e3e067 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 15:26:18 +0200 Subject: [PATCH 0167/1012] Simplified test program --- examples/hello/hello.q | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index afd5576..c006280 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,17 +1,6 @@ main() { - address := 0 - length := 0 - - address += 4194304 - address += 1 - - length = address - address - length += 50 - length -= 20 - length *= 10 - length /= 100 - length = (0 + 50 - 20) * 10 / 100 - length = 1 + address := 4194304 + 1 + length := (0 + 50 - 20) * 10 / 100 loop { print(address, length) @@ -19,7 +8,7 @@ main() { } print(address, length) { - write(length, address, length+1) + write(length-2, address, length) } write(fd, address, length) { From a62006b4c8658e60f59ac77082719bb3f892dd09 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 15:26:18 +0200 Subject: [PATCH 0168/1012] Simplified test program --- examples/hello/hello.q | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index afd5576..c006280 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,17 +1,6 @@ main() { - address := 0 - length := 0 - - address += 4194304 - address += 1 - - length = address - address - length += 50 - length -= 20 - length *= 10 - length /= 100 - length = (0 + 50 - 20) * 10 / 100 - length = 1 + address := 4194304 + 1 + length := (0 + 50 - 20) * 10 / 100 loop { print(address, length) @@ -19,7 +8,7 @@ main() { } print(address, length) { - write(length, address, length+1) + write(length-2, address, length) } write(fd, address, length) { From be81c5c86bfe5e673da4a390559b022a655ef266 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 16:05:23 +0200 Subject: [PATCH 0169/1012] Added assembler syntax highlighting --- src/build/Function.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/build/Function.go b/src/build/Function.go index 8815d26..819479a 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -227,21 +227,27 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { // PrintAsm shows the assembly instructions. func (f *Function) PrintAsm() { - ansi.Bold.Println(f.Name) - ansi.Dim.Println("╭────────────────────────────────────────────────────────────") + ansi.Dim.Println("╭────────────────────────────────────────────────╮") for _, x := range f.Assembler.Instructions { ansi.Dim.Print("│ ") - fmt.Print(x.Mnemonic.String()) - if x.Data != nil { - fmt.Print(" " + x.Data.String()) + if x.Mnemonic == asm.LABEL { + ansi.Yellow.Printf("%-46s", x.Data.String()+":") + } else { + ansi.Green.Printf("%-8s", x.Mnemonic.String()) + + if x.Data != nil { + fmt.Printf("%-38s", x.Data.String()) + } else { + fmt.Printf("%-38s", "") + } } - fmt.Print("\n") + ansi.Dim.Print(" │\n") } - ansi.Dim.Println("╰────────────────────────────────────────────────────────────") + ansi.Dim.Println("╰────────────────────────────────────────────────╯") } // String returns the function name. From 61024bb13387c734b76486e6c4cd086cfbe09899 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 16:05:23 +0200 Subject: [PATCH 0170/1012] Added assembler syntax highlighting --- src/build/Function.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/build/Function.go b/src/build/Function.go index 8815d26..819479a 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -227,21 +227,27 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { // PrintAsm shows the assembly instructions. func (f *Function) PrintAsm() { - ansi.Bold.Println(f.Name) - ansi.Dim.Println("╭────────────────────────────────────────────────────────────") + ansi.Dim.Println("╭────────────────────────────────────────────────╮") for _, x := range f.Assembler.Instructions { ansi.Dim.Print("│ ") - fmt.Print(x.Mnemonic.String()) - if x.Data != nil { - fmt.Print(" " + x.Data.String()) + if x.Mnemonic == asm.LABEL { + ansi.Yellow.Printf("%-46s", x.Data.String()+":") + } else { + ansi.Green.Printf("%-8s", x.Mnemonic.String()) + + if x.Data != nil { + fmt.Printf("%-38s", x.Data.String()) + } else { + fmt.Printf("%-38s", "") + } } - fmt.Print("\n") + ansi.Dim.Print(" │\n") } - ansi.Dim.Println("╰────────────────────────────────────────────────────────────") + ansi.Dim.Println("╰────────────────────────────────────────────────╯") } // String returns the function name. From 10d195d286c929b686ab70b8ddf3f3da7ca0b728 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 17:13:48 +0200 Subject: [PATCH 0171/1012] Improved verbose output --- src/build/Debug.go | 22 ++++++++++++++++++++++ src/build/Function.go | 33 ++++++++++++++++++++++----------- 2 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 src/build/Debug.go diff --git a/src/build/Debug.go b/src/build/Debug.go new file mode 100644 index 0000000..5f95db4 --- /dev/null +++ b/src/build/Debug.go @@ -0,0 +1,22 @@ +package build + +import "git.akyoto.dev/cli/q/src/build/token" + +type debug struct { + pos int + instruction token.List +} + +func (f *Function) debugLine(instructionIndex int) token.List { + for _, record := range f.debug { + if record.pos == instructionIndex { + return record.instruction + } + + if record.pos > instructionIndex { + return nil + } + } + + return nil +} diff --git a/src/build/Function.go b/src/build/Function.go index 819479a..4bd2a94 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -6,6 +6,7 @@ import ( "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" @@ -24,6 +25,7 @@ type Function struct { CPU cpu.CPU Error error count struct{ loop int } + debug []debug } // Compile turns a function into machine code. @@ -59,6 +61,14 @@ func (f *Function) CompileTokens(body token.List) error { if start != -1 { instruction := body[start:i] + + if config.Verbose { + f.debug = append(f.debug, debug{ + pos: len(f.Assembler.Instructions), + instruction: instruction, + }) + } + err := f.CompileInstruction(instruction) if err != nil { @@ -182,11 +192,6 @@ func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, return nil } -func (f *Function) Log(messages ...any) { - fmt.Printf("[%s] ", f.Name) - fmt.Println(messages...) -} - // TokenToRegister moves a token into a register. // It only works with identifiers, numbers and strings. func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { @@ -227,27 +232,33 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { // PrintAsm shows the assembly instructions. func (f *Function) PrintAsm() { - ansi.Dim.Println("╭────────────────────────────────────────────────╮") + ansi.Dim.Println("╭──────────────────────────────────────╮") + + for i, x := range f.Assembler.Instructions { + instruction := f.debugLine(i) + + if instruction != nil { + ansi.Dim.Println("├──────────────────────────────────────┤") + } - for _, x := range f.Assembler.Instructions { ansi.Dim.Print("│ ") if x.Mnemonic == asm.LABEL { - ansi.Yellow.Printf("%-46s", x.Data.String()+":") + ansi.Yellow.Printf("%-36s", x.Data.String()+":") } else { ansi.Green.Printf("%-8s", x.Mnemonic.String()) if x.Data != nil { - fmt.Printf("%-38s", x.Data.String()) + fmt.Printf("%-28s", x.Data.String()) } else { - fmt.Printf("%-38s", "") + fmt.Printf("%-28s", "") } } ansi.Dim.Print(" │\n") } - ansi.Dim.Println("╰────────────────────────────────────────────────╯") + ansi.Dim.Println("╰──────────────────────────────────────╯") } // String returns the function name. From d86d411959bc2bb74abcad03804f8462276588a6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 17:13:48 +0200 Subject: [PATCH 0172/1012] Improved verbose output --- src/build/Debug.go | 22 ++++++++++++++++++++++ src/build/Function.go | 33 ++++++++++++++++++++++----------- 2 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 src/build/Debug.go diff --git a/src/build/Debug.go b/src/build/Debug.go new file mode 100644 index 0000000..5f95db4 --- /dev/null +++ b/src/build/Debug.go @@ -0,0 +1,22 @@ +package build + +import "git.akyoto.dev/cli/q/src/build/token" + +type debug struct { + pos int + instruction token.List +} + +func (f *Function) debugLine(instructionIndex int) token.List { + for _, record := range f.debug { + if record.pos == instructionIndex { + return record.instruction + } + + if record.pos > instructionIndex { + return nil + } + } + + return nil +} diff --git a/src/build/Function.go b/src/build/Function.go index 819479a..4bd2a94 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -6,6 +6,7 @@ import ( "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" @@ -24,6 +25,7 @@ type Function struct { CPU cpu.CPU Error error count struct{ loop int } + debug []debug } // Compile turns a function into machine code. @@ -59,6 +61,14 @@ func (f *Function) CompileTokens(body token.List) error { if start != -1 { instruction := body[start:i] + + if config.Verbose { + f.debug = append(f.debug, debug{ + pos: len(f.Assembler.Instructions), + instruction: instruction, + }) + } + err := f.CompileInstruction(instruction) if err != nil { @@ -182,11 +192,6 @@ func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, return nil } -func (f *Function) Log(messages ...any) { - fmt.Printf("[%s] ", f.Name) - fmt.Println(messages...) -} - // TokenToRegister moves a token into a register. // It only works with identifiers, numbers and strings. func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { @@ -227,27 +232,33 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { // PrintAsm shows the assembly instructions. func (f *Function) PrintAsm() { - ansi.Dim.Println("╭────────────────────────────────────────────────╮") + ansi.Dim.Println("╭──────────────────────────────────────╮") + + for i, x := range f.Assembler.Instructions { + instruction := f.debugLine(i) + + if instruction != nil { + ansi.Dim.Println("├──────────────────────────────────────┤") + } - for _, x := range f.Assembler.Instructions { ansi.Dim.Print("│ ") if x.Mnemonic == asm.LABEL { - ansi.Yellow.Printf("%-46s", x.Data.String()+":") + ansi.Yellow.Printf("%-36s", x.Data.String()+":") } else { ansi.Green.Printf("%-8s", x.Mnemonic.String()) if x.Data != nil { - fmt.Printf("%-38s", x.Data.String()) + fmt.Printf("%-28s", x.Data.String()) } else { - fmt.Printf("%-38s", "") + fmt.Printf("%-28s", "") } } ansi.Dim.Print(" │\n") } - ansi.Dim.Println("╰────────────────────────────────────────────────╯") + ansi.Dim.Println("╰──────────────────────────────────────╯") } // String returns the function name. From 8beed6dd214a445d6e5c0eb384c856b6e8541238 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 17:36:45 +0200 Subject: [PATCH 0173/1012] Implemented dead code elimination --- errors_test.go | 1 + examples/hello/hello.q | 4 +++ src/build/Result.go | 12 +++------ src/build/compile.go | 40 +++++++++++++++++++++++++++--- src/cli/Build.go | 2 +- src/errors/CompileErrors.go | 9 ++++--- tests/errors/MissingMainFunction.q | 0 7 files changed, 51 insertions(+), 17 deletions(-) create mode 100644 tests/errors/MissingMainFunction.q diff --git a/errors_test.go b/errors_test.go index 8eecad6..f386c66 100644 --- a/errors_test.go +++ b/errors_test.go @@ -27,6 +27,7 @@ func TestErrors(t *testing.T) { {"MissingBlockStart.q", errors.MissingBlockStart}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, {"MissingGroupStart.q", errors.MissingGroupStart}, + {"MissingMainFunction.q", errors.MissingMainFunction}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, diff --git a/examples/hello/hello.q b/examples/hello/hello.q index c006280..07f7c6d 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -13,4 +13,8 @@ print(address, length) { write(fd, address, length) { syscall(1, fd, address, length) +} + +empty() { + } \ No newline at end of file diff --git a/src/build/Result.go b/src/build/Result.go index 3b13dba..615cbb8 100644 --- a/src/build/Result.go +++ b/src/build/Result.go @@ -8,7 +8,8 @@ import ( // Result contains all the compiled functions in a build. type Result struct { - Functions map[string]*Function + Used []*Function + Unused map[string]*Function instructionCount int } @@ -27,13 +28,8 @@ func (r Result) Finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0) final.Syscall() - // Place the `main` function immediately after the entry point. - main := r.Functions["main"] - delete(r.Functions, "main") - final.Merge(&main.Assembler) - - // Merge all the remaining functions. - for _, f := range r.Functions { + // Merge all the called functions. + for _, f := range r.Used { final.Merge(&f.Assembler) } diff --git a/src/build/compile.go b/src/build/compile.go index 110bd40..325bcd5 100644 --- a/src/build/compile.go +++ b/src/build/compile.go @@ -2,12 +2,15 @@ package build import ( "sync" + + "git.akyoto.dev/cli/q/src/build/asm" + fail "git.akyoto.dev/cli/q/src/errors" ) // compile waits for the scan to finish and compiles all functions. func compile(functions <-chan *Function, errors <-chan error) (Result, error) { result := Result{ - Functions: map[string]*Function{}, + Unused: map[string]*Function{}, } for functions != nil || errors != nil { @@ -26,13 +29,13 @@ func compile(functions <-chan *Function, errors <-chan error) (Result, error) { continue } - result.Functions[function.Name] = function + result.Unused[function.Name] = function } } - compileFunctions(result.Functions) + compileFunctions(result.Unused) - for _, function := range result.Functions { + for _, function := range result.Unused { if function.Error != nil { return result, function.Error } @@ -40,9 +43,38 @@ func compile(functions <-chan *Function, errors <-chan error) (Result, error) { result.instructionCount += len(function.Assembler.Instructions) } + main, exists := result.Unused["main"] + + if !exists { + return result, fail.MissingMainFunction + } + + result.Used = append(result.Used, main) + delete(result.Unused, "main") + result.findCalls(main) + return result, nil } +func (result *Result) findCalls(f *Function) { + for _, x := range f.Assembler.Instructions { + if x.Mnemonic != asm.CALL { + continue + } + + name := x.Data.(*asm.Label).Name + called, exists := result.Unused[name] + + if !exists { + continue + } + + result.Used = append(result.Used, called) + delete(result.Unused, name) + result.findCalls(called) + } +} + // compileFunctions starts a goroutine for each function compilation and waits for completion. func compileFunctions(functions map[string]*Function) { wg := sync.WaitGroup{} diff --git a/src/cli/Build.go b/src/cli/Build.go index f9fe85d..2553996 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -43,7 +43,7 @@ func Build(args []string) int { } if config.Verbose { - for _, function := range result.Functions { + for _, function := range result.Used { function.PrintAsm() } } diff --git a/src/errors/CompileErrors.go b/src/errors/CompileErrors.go index 34515d0..13d5948 100644 --- a/src/errors/CompileErrors.go +++ b/src/errors/CompileErrors.go @@ -1,8 +1,9 @@ package errors var ( - InvalidStatement = &Base{"Invalid statement"} - InvalidExpression = &Base{"Invalid expression"} - MissingAssignValue = &Base{"Missing assignment value"} - NotImplemented = &Base{"Not implemented"} + InvalidStatement = &Base{"Invalid statement"} + InvalidExpression = &Base{"Invalid expression"} + MissingAssignValue = &Base{"Missing assignment value"} + MissingMainFunction = &Base{"Missing main function"} + NotImplemented = &Base{"Not implemented"} ) diff --git a/tests/errors/MissingMainFunction.q b/tests/errors/MissingMainFunction.q new file mode 100644 index 0000000..e69de29 From 526b92aa095cd17a29dfec19c78303a17e38c217 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 17:36:45 +0200 Subject: [PATCH 0174/1012] Implemented dead code elimination --- errors_test.go | 1 + examples/hello/hello.q | 4 +++ src/build/Result.go | 12 +++------ src/build/compile.go | 40 +++++++++++++++++++++++++++--- src/cli/Build.go | 2 +- src/errors/CompileErrors.go | 9 ++++--- tests/errors/MissingMainFunction.q | 0 7 files changed, 51 insertions(+), 17 deletions(-) create mode 100644 tests/errors/MissingMainFunction.q diff --git a/errors_test.go b/errors_test.go index 8eecad6..f386c66 100644 --- a/errors_test.go +++ b/errors_test.go @@ -27,6 +27,7 @@ func TestErrors(t *testing.T) { {"MissingBlockStart.q", errors.MissingBlockStart}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, {"MissingGroupStart.q", errors.MissingGroupStart}, + {"MissingMainFunction.q", errors.MissingMainFunction}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, diff --git a/examples/hello/hello.q b/examples/hello/hello.q index c006280..07f7c6d 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -13,4 +13,8 @@ print(address, length) { write(fd, address, length) { syscall(1, fd, address, length) +} + +empty() { + } \ No newline at end of file diff --git a/src/build/Result.go b/src/build/Result.go index 3b13dba..615cbb8 100644 --- a/src/build/Result.go +++ b/src/build/Result.go @@ -8,7 +8,8 @@ import ( // Result contains all the compiled functions in a build. type Result struct { - Functions map[string]*Function + Used []*Function + Unused map[string]*Function instructionCount int } @@ -27,13 +28,8 @@ func (r Result) Finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0) final.Syscall() - // Place the `main` function immediately after the entry point. - main := r.Functions["main"] - delete(r.Functions, "main") - final.Merge(&main.Assembler) - - // Merge all the remaining functions. - for _, f := range r.Functions { + // Merge all the called functions. + for _, f := range r.Used { final.Merge(&f.Assembler) } diff --git a/src/build/compile.go b/src/build/compile.go index 110bd40..325bcd5 100644 --- a/src/build/compile.go +++ b/src/build/compile.go @@ -2,12 +2,15 @@ package build import ( "sync" + + "git.akyoto.dev/cli/q/src/build/asm" + fail "git.akyoto.dev/cli/q/src/errors" ) // compile waits for the scan to finish and compiles all functions. func compile(functions <-chan *Function, errors <-chan error) (Result, error) { result := Result{ - Functions: map[string]*Function{}, + Unused: map[string]*Function{}, } for functions != nil || errors != nil { @@ -26,13 +29,13 @@ func compile(functions <-chan *Function, errors <-chan error) (Result, error) { continue } - result.Functions[function.Name] = function + result.Unused[function.Name] = function } } - compileFunctions(result.Functions) + compileFunctions(result.Unused) - for _, function := range result.Functions { + for _, function := range result.Unused { if function.Error != nil { return result, function.Error } @@ -40,9 +43,38 @@ func compile(functions <-chan *Function, errors <-chan error) (Result, error) { result.instructionCount += len(function.Assembler.Instructions) } + main, exists := result.Unused["main"] + + if !exists { + return result, fail.MissingMainFunction + } + + result.Used = append(result.Used, main) + delete(result.Unused, "main") + result.findCalls(main) + return result, nil } +func (result *Result) findCalls(f *Function) { + for _, x := range f.Assembler.Instructions { + if x.Mnemonic != asm.CALL { + continue + } + + name := x.Data.(*asm.Label).Name + called, exists := result.Unused[name] + + if !exists { + continue + } + + result.Used = append(result.Used, called) + delete(result.Unused, name) + result.findCalls(called) + } +} + // compileFunctions starts a goroutine for each function compilation and waits for completion. func compileFunctions(functions map[string]*Function) { wg := sync.WaitGroup{} diff --git a/src/cli/Build.go b/src/cli/Build.go index f9fe85d..2553996 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -43,7 +43,7 @@ func Build(args []string) int { } if config.Verbose { - for _, function := range result.Functions { + for _, function := range result.Used { function.PrintAsm() } } diff --git a/src/errors/CompileErrors.go b/src/errors/CompileErrors.go index 34515d0..13d5948 100644 --- a/src/errors/CompileErrors.go +++ b/src/errors/CompileErrors.go @@ -1,8 +1,9 @@ package errors var ( - InvalidStatement = &Base{"Invalid statement"} - InvalidExpression = &Base{"Invalid expression"} - MissingAssignValue = &Base{"Missing assignment value"} - NotImplemented = &Base{"Not implemented"} + InvalidStatement = &Base{"Invalid statement"} + InvalidExpression = &Base{"Invalid expression"} + MissingAssignValue = &Base{"Missing assignment value"} + MissingMainFunction = &Base{"Missing main function"} + NotImplemented = &Base{"Not implemented"} ) diff --git a/tests/errors/MissingMainFunction.q b/tests/errors/MissingMainFunction.q new file mode 100644 index 0000000..e69de29 From c67cb81830c40e3aecd2f330fd95e7cf9bcc777d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 20:30:33 +0200 Subject: [PATCH 0175/1012] Improved CLI --- src/cli/Build.go | 11 ++++++----- src/cli/Help.go | 18 +++++++++++------- src/cli/Main.go | 9 +++++++-- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/cli/Build.go b/src/cli/Build.go index 2553996..cd5ac11 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "os" "strings" "git.akyoto.dev/cli/q/src/build" @@ -11,12 +12,12 @@ import ( // Build builds an executable. func Build(args []string) int { b := build.New() - writeExecutable := true + dry := false for i := 0; i < len(args); i++ { switch args[i] { case "--dry": - writeExecutable = false + dry = true case "--verbose", "-v": config.Verbose = true @@ -38,7 +39,7 @@ func Build(args []string) int { result, err := b.Run() if err != nil { - fmt.Println(err) + fmt.Fprintln(os.Stderr, err) return 1 } @@ -48,7 +49,7 @@ func Build(args []string) int { } } - if !writeExecutable { + if dry { return 0 } @@ -57,7 +58,7 @@ func Build(args []string) int { err = build.Write(path, code, data) if err != nil { - fmt.Println(err) + fmt.Fprintln(os.Stderr, err) return 1 } diff --git a/src/cli/Help.go b/src/cli/Help.go index ace2380..cf7c00f 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -1,12 +1,16 @@ package cli -import "fmt" +import ( + "fmt" + "io" +) // Help shows the command line argument usage. -func Help(args []string) int { - fmt.Println("Usage: q [command] [options]") - fmt.Println("") - fmt.Println(" build [directory] [file]") - fmt.Println(" system") - return 2 +func Help(w io.Writer, code int) int { + fmt.Fprintln(w, "Usage: q [command] [options]") + fmt.Fprintln(w, "") + fmt.Fprintln(w, " build [directory] [file]") + fmt.Fprintln(w, " help") + fmt.Fprintln(w, " system") + return code } diff --git a/src/cli/Main.go b/src/cli/Main.go index e681239..2b90bda 100644 --- a/src/cli/Main.go +++ b/src/cli/Main.go @@ -1,11 +1,13 @@ package cli +import "os" + // Main is the entry point for the CLI frontend. // It returns the exit code of the compiler. // We never call os.Exit directly here because it's bad for testing. func Main(args []string) int { if len(args) == 0 { - return Help(nil) + return Help(os.Stderr, 2) } switch args[0] { @@ -15,7 +17,10 @@ func Main(args []string) int { case "system": return System(args[1:]) + case "help": + return Help(os.Stdout, 0) + default: - return Help(args[1:]) + return Help(os.Stderr, 2) } } From a64169d6248441527fd573d3ccd6d43fa55cb49c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 20:30:33 +0200 Subject: [PATCH 0176/1012] Improved CLI --- src/cli/Build.go | 11 ++++++----- src/cli/Help.go | 18 +++++++++++------- src/cli/Main.go | 9 +++++++-- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/cli/Build.go b/src/cli/Build.go index 2553996..cd5ac11 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "os" "strings" "git.akyoto.dev/cli/q/src/build" @@ -11,12 +12,12 @@ import ( // Build builds an executable. func Build(args []string) int { b := build.New() - writeExecutable := true + dry := false for i := 0; i < len(args); i++ { switch args[i] { case "--dry": - writeExecutable = false + dry = true case "--verbose", "-v": config.Verbose = true @@ -38,7 +39,7 @@ func Build(args []string) int { result, err := b.Run() if err != nil { - fmt.Println(err) + fmt.Fprintln(os.Stderr, err) return 1 } @@ -48,7 +49,7 @@ func Build(args []string) int { } } - if !writeExecutable { + if dry { return 0 } @@ -57,7 +58,7 @@ func Build(args []string) int { err = build.Write(path, code, data) if err != nil { - fmt.Println(err) + fmt.Fprintln(os.Stderr, err) return 1 } diff --git a/src/cli/Help.go b/src/cli/Help.go index ace2380..cf7c00f 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -1,12 +1,16 @@ package cli -import "fmt" +import ( + "fmt" + "io" +) // Help shows the command line argument usage. -func Help(args []string) int { - fmt.Println("Usage: q [command] [options]") - fmt.Println("") - fmt.Println(" build [directory] [file]") - fmt.Println(" system") - return 2 +func Help(w io.Writer, code int) int { + fmt.Fprintln(w, "Usage: q [command] [options]") + fmt.Fprintln(w, "") + fmt.Fprintln(w, " build [directory] [file]") + fmt.Fprintln(w, " help") + fmt.Fprintln(w, " system") + return code } diff --git a/src/cli/Main.go b/src/cli/Main.go index e681239..2b90bda 100644 --- a/src/cli/Main.go +++ b/src/cli/Main.go @@ -1,11 +1,13 @@ package cli +import "os" + // Main is the entry point for the CLI frontend. // It returns the exit code of the compiler. // We never call os.Exit directly here because it's bad for testing. func Main(args []string) int { if len(args) == 0 { - return Help(nil) + return Help(os.Stderr, 2) } switch args[0] { @@ -15,7 +17,10 @@ func Main(args []string) int { case "system": return System(args[1:]) + case "help": + return Help(os.Stdout, 0) + default: - return Help(args[1:]) + return Help(os.Stderr, 2) } } From b6947dab182368b253c1f95948b5a5599d9ebf35 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 20:54:07 +0200 Subject: [PATCH 0177/1012] Cleaned up linter warnings --- src/build/Function.go | 2 -- src/build/compile.go | 41 +++++++++++++------------ src/build/expression/Expression_test.go | 5 +-- src/build/expression/List.go | 4 +-- src/build/expression/Parse.go | 2 +- src/build/fs/Walk_test.go | 3 +- 6 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/build/Function.go b/src/build/Function.go index 4bd2a94..7a2b103 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -74,8 +74,6 @@ func (f *Function) CompileTokens(body token.List) error { if err != nil { return err } - - start = -1 } start = i + 1 diff --git a/src/build/compile.go b/src/build/compile.go index 325bcd5..c028a68 100644 --- a/src/build/compile.go +++ b/src/build/compile.go @@ -51,30 +51,11 @@ func compile(functions <-chan *Function, errors <-chan error) (Result, error) { result.Used = append(result.Used, main) delete(result.Unused, "main") - result.findCalls(main) + result.findAliveCode(main) return result, nil } -func (result *Result) findCalls(f *Function) { - for _, x := range f.Assembler.Instructions { - if x.Mnemonic != asm.CALL { - continue - } - - name := x.Data.(*asm.Label).Name - called, exists := result.Unused[name] - - if !exists { - continue - } - - result.Used = append(result.Used, called) - delete(result.Unused, name) - result.findCalls(called) - } -} - // compileFunctions starts a goroutine for each function compilation and waits for completion. func compileFunctions(functions map[string]*Function) { wg := sync.WaitGroup{} @@ -90,3 +71,23 @@ func compileFunctions(functions map[string]*Function) { wg.Wait() } + +// findAliveCode recursively finds all the calls to external functions and marks them as required. +func (result *Result) findAliveCode(f *Function) { + for _, x := range f.Assembler.Instructions { + if x.Mnemonic != asm.CALL { + continue + } + + name := x.Data.(*asm.Label).Name + called, exists := result.Unused[name] + + if !exists { + continue + } + + result.Used = append(result.Used, called) + delete(result.Unused, name) + result.findAliveCode(called) + } +} diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index c0fe259..e267a60 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -102,14 +102,15 @@ func TestEachLeaf(t *testing.T) { expr := expression.Parse(tokens) leaves := []string{} - expr.EachLeaf(func(leaf *expression.Expression) error { + err := expr.EachLeaf(func(leaf *expression.Expression) error { leaves = append(leaves, leaf.Token.Text()) return nil }) + assert.Nil(t, err) assert.DeepEqual(t, leaves, []string{"1", "2", "3", "4", "5", "6", "7", "8"}) - err := expr.EachLeaf(func(leaf *expression.Expression) error { + err = expr.EachLeaf(func(leaf *expression.Expression) error { return fmt.Errorf("error") }) diff --git a/src/build/expression/List.go b/src/build/expression/List.go index 2bb4b56..75c44d0 100644 --- a/src/build/expression/List.go +++ b/src/build/expression/List.go @@ -4,8 +4,8 @@ import ( "git.akyoto.dev/cli/q/src/build/token" ) -// List generates a list of expressions from comma separated parameters. -func List(tokens token.List) []*Expression { +// NewList generates a list of expressions from comma separated parameters. +func NewList(tokens token.List) []*Expression { var list []*Expression EachParameter(tokens, func(parameter token.List) error { diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index 536718b..072dfe4 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -38,7 +38,7 @@ func Parse(tokens token.List) *Expression { isFunctionCall := isComplete(cursor) if isFunctionCall { - parameters := List(tokens[groupPosition:i]) + parameters := NewList(tokens[groupPosition:i]) node := New() node.Token.Kind = token.Operator diff --git a/src/build/fs/Walk_test.go b/src/build/fs/Walk_test.go index ae55b3d..d3f6e17 100644 --- a/src/build/fs/Walk_test.go +++ b/src/build/fs/Walk_test.go @@ -10,10 +10,11 @@ import ( func TestWalk(t *testing.T) { var files []string - fs.Walk(".", func(file string) { + err := fs.Walk(".", func(file string) { files = append(files, file) }) + assert.Nil(t, err) assert.Contains(t, files, "Walk.go") assert.Contains(t, files, "Walk_test.go") } From 77cfe9ff3178ccf73d1f2329a36cb149b31d815f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 20:54:07 +0200 Subject: [PATCH 0178/1012] Cleaned up linter warnings --- src/build/Function.go | 2 -- src/build/compile.go | 41 +++++++++++++------------ src/build/expression/Expression_test.go | 5 +-- src/build/expression/List.go | 4 +-- src/build/expression/Parse.go | 2 +- src/build/fs/Walk_test.go | 3 +- 6 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/build/Function.go b/src/build/Function.go index 4bd2a94..7a2b103 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -74,8 +74,6 @@ func (f *Function) CompileTokens(body token.List) error { if err != nil { return err } - - start = -1 } start = i + 1 diff --git a/src/build/compile.go b/src/build/compile.go index 325bcd5..c028a68 100644 --- a/src/build/compile.go +++ b/src/build/compile.go @@ -51,30 +51,11 @@ func compile(functions <-chan *Function, errors <-chan error) (Result, error) { result.Used = append(result.Used, main) delete(result.Unused, "main") - result.findCalls(main) + result.findAliveCode(main) return result, nil } -func (result *Result) findCalls(f *Function) { - for _, x := range f.Assembler.Instructions { - if x.Mnemonic != asm.CALL { - continue - } - - name := x.Data.(*asm.Label).Name - called, exists := result.Unused[name] - - if !exists { - continue - } - - result.Used = append(result.Used, called) - delete(result.Unused, name) - result.findCalls(called) - } -} - // compileFunctions starts a goroutine for each function compilation and waits for completion. func compileFunctions(functions map[string]*Function) { wg := sync.WaitGroup{} @@ -90,3 +71,23 @@ func compileFunctions(functions map[string]*Function) { wg.Wait() } + +// findAliveCode recursively finds all the calls to external functions and marks them as required. +func (result *Result) findAliveCode(f *Function) { + for _, x := range f.Assembler.Instructions { + if x.Mnemonic != asm.CALL { + continue + } + + name := x.Data.(*asm.Label).Name + called, exists := result.Unused[name] + + if !exists { + continue + } + + result.Used = append(result.Used, called) + delete(result.Unused, name) + result.findAliveCode(called) + } +} diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index c0fe259..e267a60 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -102,14 +102,15 @@ func TestEachLeaf(t *testing.T) { expr := expression.Parse(tokens) leaves := []string{} - expr.EachLeaf(func(leaf *expression.Expression) error { + err := expr.EachLeaf(func(leaf *expression.Expression) error { leaves = append(leaves, leaf.Token.Text()) return nil }) + assert.Nil(t, err) assert.DeepEqual(t, leaves, []string{"1", "2", "3", "4", "5", "6", "7", "8"}) - err := expr.EachLeaf(func(leaf *expression.Expression) error { + err = expr.EachLeaf(func(leaf *expression.Expression) error { return fmt.Errorf("error") }) diff --git a/src/build/expression/List.go b/src/build/expression/List.go index 2bb4b56..75c44d0 100644 --- a/src/build/expression/List.go +++ b/src/build/expression/List.go @@ -4,8 +4,8 @@ import ( "git.akyoto.dev/cli/q/src/build/token" ) -// List generates a list of expressions from comma separated parameters. -func List(tokens token.List) []*Expression { +// NewList generates a list of expressions from comma separated parameters. +func NewList(tokens token.List) []*Expression { var list []*Expression EachParameter(tokens, func(parameter token.List) error { diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index 536718b..072dfe4 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -38,7 +38,7 @@ func Parse(tokens token.List) *Expression { isFunctionCall := isComplete(cursor) if isFunctionCall { - parameters := List(tokens[groupPosition:i]) + parameters := NewList(tokens[groupPosition:i]) node := New() node.Token.Kind = token.Operator diff --git a/src/build/fs/Walk_test.go b/src/build/fs/Walk_test.go index ae55b3d..d3f6e17 100644 --- a/src/build/fs/Walk_test.go +++ b/src/build/fs/Walk_test.go @@ -10,10 +10,11 @@ import ( func TestWalk(t *testing.T) { var files []string - fs.Walk(".", func(file string) { + err := fs.Walk(".", func(file string) { files = append(files, file) }) + assert.Nil(t, err) assert.Contains(t, files, "Walk.go") assert.Contains(t, files, "Walk_test.go") } From d77660cdb8103ef2598dd61f4fb828263bbcbb07 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 21:39:04 +0200 Subject: [PATCH 0179/1012] Added more tests --- src/build/arch/x64/Jump_test.go | 31 +++++++++++++++++++++ src/build/arch/x64/{sizeOf.go => SizeOf.go} | 4 +-- src/build/arch/x64/SizeOf_test.go | 21 ++++++++++++++ src/build/arch/x64/regRegNum.go | 2 +- src/build/arch/x64/x64_test.go | 11 ++++---- 5 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 src/build/arch/x64/Jump_test.go rename src/build/arch/x64/{sizeOf.go => SizeOf.go} (74%) create mode 100644 src/build/arch/x64/SizeOf_test.go diff --git a/src/build/arch/x64/Jump_test.go b/src/build/arch/x64/Jump_test.go new file mode 100644 index 0000000..ba48db0 --- /dev/null +++ b/src/build/arch/x64/Jump_test.go @@ -0,0 +1,31 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/go/assert" +) + +func TestJump8(t *testing.T) { + usagePatterns := []struct { + Offset int8 + Code []byte + }{ + {0, []byte{0xEB, 0x00}}, + {1, []byte{0xEB, 0x01}}, + {2, []byte{0xEB, 0x02}}, + {3, []byte{0xEB, 0x03}}, + {127, []byte{0xEB, 0x7F}}, + {-1, []byte{0xEB, 0xFF}}, + {-2, []byte{0xEB, 0xFE}}, + {-3, []byte{0xEB, 0xFD}}, + {-128, []byte{0xEB, 0x80}}, + } + + for _, pattern := range usagePatterns { + t.Logf("jmp %x", pattern.Offset) + code := x64.Jump8(nil, pattern.Offset) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/sizeOf.go b/src/build/arch/x64/SizeOf.go similarity index 74% rename from src/build/arch/x64/sizeOf.go rename to src/build/arch/x64/SizeOf.go index 8e6533b..c4804ae 100644 --- a/src/build/arch/x64/sizeOf.go +++ b/src/build/arch/x64/SizeOf.go @@ -2,8 +2,8 @@ package x64 import "math" -// sizeOf tells you how many bytes are needed to encode this number. -func sizeOf(number int) int { +// SizeOf tells you how many bytes are needed to encode this number. +func SizeOf(number int) int { switch { case number >= math.MinInt8 && number <= math.MaxInt8: return 1 diff --git a/src/build/arch/x64/SizeOf_test.go b/src/build/arch/x64/SizeOf_test.go new file mode 100644 index 0000000..eb84a59 --- /dev/null +++ b/src/build/arch/x64/SizeOf_test.go @@ -0,0 +1,21 @@ +package x64_test + +import ( + "math" + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/go/assert" +) + +func TestSizeOf(t *testing.T) { + assert.Equal(t, x64.SizeOf(0), 1) + assert.Equal(t, x64.SizeOf(math.MinInt8), 1) + assert.Equal(t, x64.SizeOf(math.MaxInt8), 1) + assert.Equal(t, x64.SizeOf(math.MinInt16), 2) + assert.Equal(t, x64.SizeOf(math.MaxInt16), 2) + assert.Equal(t, x64.SizeOf(math.MinInt32), 4) + assert.Equal(t, x64.SizeOf(math.MaxInt32), 4) + assert.Equal(t, x64.SizeOf(math.MinInt64), 8) + assert.Equal(t, x64.SizeOf(math.MaxInt64), 8) +} diff --git a/src/build/arch/x64/regRegNum.go b/src/build/arch/x64/regRegNum.go index 0fb41d1..3fc6987 100644 --- a/src/build/arch/x64/regRegNum.go +++ b/src/build/arch/x64/regRegNum.go @@ -4,7 +4,7 @@ import "encoding/binary" // regRegNum encodes an instruction with up to two registers and a number parameter. func regRegNum(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { - if sizeOf(number) == 1 { + if SizeOf(number) == 1 { code = regReg(code, reg, rm, opCode8) return append(code, byte(number)) } diff --git a/src/build/arch/x64/x64_test.go b/src/build/arch/x64/x64_test.go index 8369738..bcbf45b 100644 --- a/src/build/arch/x64/x64_test.go +++ b/src/build/arch/x64/x64_test.go @@ -8,9 +8,10 @@ import ( ) func TestX64(t *testing.T) { - assert.DeepEqual(t, x64.Call([]byte{}, 1), []byte{0xe8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegisterNumber32([]byte{}, 0, 1), []byte{0xb8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegisterNumber32([]byte{}, 1, 1), []byte{0xb9, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.Return([]byte{}), []byte{0xc3}) - assert.DeepEqual(t, x64.Syscall([]byte{}), []byte{0x0f, 0x05}) + assert.DeepEqual(t, x64.Call(nil, 1), []byte{0xe8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegisterNumber32(nil, 0, 1), []byte{0xb8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegisterNumber32(nil, 1, 1), []byte{0xb9, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.Return(nil), []byte{0xc3}) + assert.DeepEqual(t, x64.Syscall(nil), []byte{0x0f, 0x05}) + assert.DeepEqual(t, x64.ExtendRAXToRDX(nil), []byte{0x48, 0x99}) } From 668971808b22e2a48d340d5ff110630ec24f96ec Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 21:39:04 +0200 Subject: [PATCH 0180/1012] Added more tests --- src/build/arch/x64/Jump_test.go | 31 +++++++++++++++++++++ src/build/arch/x64/{sizeOf.go => SizeOf.go} | 4 +-- src/build/arch/x64/SizeOf_test.go | 21 ++++++++++++++ src/build/arch/x64/regRegNum.go | 2 +- src/build/arch/x64/x64_test.go | 11 ++++---- 5 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 src/build/arch/x64/Jump_test.go rename src/build/arch/x64/{sizeOf.go => SizeOf.go} (74%) create mode 100644 src/build/arch/x64/SizeOf_test.go diff --git a/src/build/arch/x64/Jump_test.go b/src/build/arch/x64/Jump_test.go new file mode 100644 index 0000000..ba48db0 --- /dev/null +++ b/src/build/arch/x64/Jump_test.go @@ -0,0 +1,31 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/go/assert" +) + +func TestJump8(t *testing.T) { + usagePatterns := []struct { + Offset int8 + Code []byte + }{ + {0, []byte{0xEB, 0x00}}, + {1, []byte{0xEB, 0x01}}, + {2, []byte{0xEB, 0x02}}, + {3, []byte{0xEB, 0x03}}, + {127, []byte{0xEB, 0x7F}}, + {-1, []byte{0xEB, 0xFF}}, + {-2, []byte{0xEB, 0xFE}}, + {-3, []byte{0xEB, 0xFD}}, + {-128, []byte{0xEB, 0x80}}, + } + + for _, pattern := range usagePatterns { + t.Logf("jmp %x", pattern.Offset) + code := x64.Jump8(nil, pattern.Offset) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/sizeOf.go b/src/build/arch/x64/SizeOf.go similarity index 74% rename from src/build/arch/x64/sizeOf.go rename to src/build/arch/x64/SizeOf.go index 8e6533b..c4804ae 100644 --- a/src/build/arch/x64/sizeOf.go +++ b/src/build/arch/x64/SizeOf.go @@ -2,8 +2,8 @@ package x64 import "math" -// sizeOf tells you how many bytes are needed to encode this number. -func sizeOf(number int) int { +// SizeOf tells you how many bytes are needed to encode this number. +func SizeOf(number int) int { switch { case number >= math.MinInt8 && number <= math.MaxInt8: return 1 diff --git a/src/build/arch/x64/SizeOf_test.go b/src/build/arch/x64/SizeOf_test.go new file mode 100644 index 0000000..eb84a59 --- /dev/null +++ b/src/build/arch/x64/SizeOf_test.go @@ -0,0 +1,21 @@ +package x64_test + +import ( + "math" + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/go/assert" +) + +func TestSizeOf(t *testing.T) { + assert.Equal(t, x64.SizeOf(0), 1) + assert.Equal(t, x64.SizeOf(math.MinInt8), 1) + assert.Equal(t, x64.SizeOf(math.MaxInt8), 1) + assert.Equal(t, x64.SizeOf(math.MinInt16), 2) + assert.Equal(t, x64.SizeOf(math.MaxInt16), 2) + assert.Equal(t, x64.SizeOf(math.MinInt32), 4) + assert.Equal(t, x64.SizeOf(math.MaxInt32), 4) + assert.Equal(t, x64.SizeOf(math.MinInt64), 8) + assert.Equal(t, x64.SizeOf(math.MaxInt64), 8) +} diff --git a/src/build/arch/x64/regRegNum.go b/src/build/arch/x64/regRegNum.go index 0fb41d1..3fc6987 100644 --- a/src/build/arch/x64/regRegNum.go +++ b/src/build/arch/x64/regRegNum.go @@ -4,7 +4,7 @@ import "encoding/binary" // regRegNum encodes an instruction with up to two registers and a number parameter. func regRegNum(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { - if sizeOf(number) == 1 { + if SizeOf(number) == 1 { code = regReg(code, reg, rm, opCode8) return append(code, byte(number)) } diff --git a/src/build/arch/x64/x64_test.go b/src/build/arch/x64/x64_test.go index 8369738..bcbf45b 100644 --- a/src/build/arch/x64/x64_test.go +++ b/src/build/arch/x64/x64_test.go @@ -8,9 +8,10 @@ import ( ) func TestX64(t *testing.T) { - assert.DeepEqual(t, x64.Call([]byte{}, 1), []byte{0xe8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegisterNumber32([]byte{}, 0, 1), []byte{0xb8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegisterNumber32([]byte{}, 1, 1), []byte{0xb9, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.Return([]byte{}), []byte{0xc3}) - assert.DeepEqual(t, x64.Syscall([]byte{}), []byte{0x0f, 0x05}) + assert.DeepEqual(t, x64.Call(nil, 1), []byte{0xe8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegisterNumber32(nil, 0, 1), []byte{0xb8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegisterNumber32(nil, 1, 1), []byte{0xb9, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.Return(nil), []byte{0xc3}) + assert.DeepEqual(t, x64.Syscall(nil), []byte{0x0f, 0x05}) + assert.DeepEqual(t, x64.ExtendRAXToRDX(nil), []byte{0x48, 0x99}) } From 9867550c110316718b3c031f5a93aed13293ddc4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Jun 2024 10:28:23 +0200 Subject: [PATCH 0181/1012] Reduced memory usage --- src/build/Debug.go | 22 ---------------------- src/build/Function.go | 4 ++-- src/build/Result.go | 4 ++-- src/build/compile.go | 24 ++++++++++++++---------- src/build/debug.go | 24 ++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 36 deletions(-) delete mode 100644 src/build/Debug.go create mode 100644 src/build/debug.go diff --git a/src/build/Debug.go b/src/build/Debug.go deleted file mode 100644 index 5f95db4..0000000 --- a/src/build/Debug.go +++ /dev/null @@ -1,22 +0,0 @@ -package build - -import "git.akyoto.dev/cli/q/src/build/token" - -type debug struct { - pos int - instruction token.List -} - -func (f *Function) debugLine(instructionIndex int) token.List { - for _, record := range f.debug { - if record.pos == instructionIndex { - return record.instruction - } - - if record.pos > instructionIndex { - return nil - } - } - - return nil -} diff --git a/src/build/Function.go b/src/build/Function.go index 7a2b103..16a8c8c 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -64,7 +64,7 @@ func (f *Function) CompileTokens(body token.List) error { if config.Verbose { f.debug = append(f.debug, debug{ - pos: len(f.Assembler.Instructions), + position: len(f.Assembler.Instructions), instruction: instruction, }) } @@ -233,7 +233,7 @@ func (f *Function) PrintAsm() { ansi.Dim.Println("╭──────────────────────────────────────╮") for i, x := range f.Assembler.Instructions { - instruction := f.debugLine(i) + instruction := f.instructionAt(i) if instruction != nil { ansi.Dim.Println("├──────────────────────────────────────┤") diff --git a/src/build/Result.go b/src/build/Result.go index 615cbb8..bfebb2e 100644 --- a/src/build/Result.go +++ b/src/build/Result.go @@ -10,7 +10,7 @@ import ( type Result struct { Used []*Function Unused map[string]*Function - instructionCount int + InstructionCount int } // Finalize generates the final machine code. @@ -20,7 +20,7 @@ func (r Result) Finalize() ([]byte, []byte) { // The reason we call `main` instead of using `main` itself is to place // a return address on the stack, which allows return statements in `main`. final := asm.Assembler{ - Instructions: make([]asm.Instruction, 0, r.instructionCount+4), + Instructions: make([]asm.Instruction, 0, r.InstructionCount+4), } final.Call("main") diff --git a/src/build/compile.go b/src/build/compile.go index c028a68..6ffade3 100644 --- a/src/build/compile.go +++ b/src/build/compile.go @@ -39,8 +39,6 @@ func compile(functions <-chan *Function, errors <-chan error) (Result, error) { if function.Error != nil { return result, function.Error } - - result.instructionCount += len(function.Assembler.Instructions) } main, exists := result.Unused["main"] @@ -49,8 +47,8 @@ func compile(functions <-chan *Function, errors <-chan error) (Result, error) { return result, fail.MissingMainFunction } - result.Used = append(result.Used, main) - delete(result.Unused, "main") + result.Used = make([]*Function, 0, len(result.Unused)) + result.markAlive(main) result.findAliveCode(main) return result, nil @@ -73,21 +71,27 @@ func compileFunctions(functions map[string]*Function) { } // findAliveCode recursively finds all the calls to external functions and marks them as required. -func (result *Result) findAliveCode(f *Function) { - for _, x := range f.Assembler.Instructions { +func (result *Result) findAliveCode(caller *Function) { + for _, x := range caller.Assembler.Instructions { if x.Mnemonic != asm.CALL { continue } name := x.Data.(*asm.Label).Name - called, exists := result.Unused[name] + callee, exists := result.Unused[name] if !exists { continue } - result.Used = append(result.Used, called) - delete(result.Unused, name) - result.findAliveCode(called) + result.markAlive(callee) + result.findAliveCode(callee) } } + +// markAlive marks a function as being alive. +func (result *Result) markAlive(f *Function) { + result.Used = append(result.Used, f) + result.InstructionCount += len(f.Assembler.Instructions) + delete(result.Unused, f.Name) +} diff --git a/src/build/debug.go b/src/build/debug.go new file mode 100644 index 0000000..0292457 --- /dev/null +++ b/src/build/debug.go @@ -0,0 +1,24 @@ +package build + +import "git.akyoto.dev/cli/q/src/build/token" + +// debug is used to look up instructions at a certain index. +type debug struct { + position int + instruction token.List +} + +// instructionAt retrieves the instruction at the given index. +func (f *Function) instructionAt(index int) token.List { + for _, record := range f.debug { + if record.position == index { + return record.instruction + } + + if record.position > index { + return nil + } + } + + return nil +} From c8b25dd5b7e4c79ee42681b7ed868b2fa6c79ea3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Jun 2024 10:28:23 +0200 Subject: [PATCH 0182/1012] Reduced memory usage --- src/build/Debug.go | 22 ---------------------- src/build/Function.go | 4 ++-- src/build/Result.go | 4 ++-- src/build/compile.go | 24 ++++++++++++++---------- src/build/debug.go | 24 ++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 36 deletions(-) delete mode 100644 src/build/Debug.go create mode 100644 src/build/debug.go diff --git a/src/build/Debug.go b/src/build/Debug.go deleted file mode 100644 index 5f95db4..0000000 --- a/src/build/Debug.go +++ /dev/null @@ -1,22 +0,0 @@ -package build - -import "git.akyoto.dev/cli/q/src/build/token" - -type debug struct { - pos int - instruction token.List -} - -func (f *Function) debugLine(instructionIndex int) token.List { - for _, record := range f.debug { - if record.pos == instructionIndex { - return record.instruction - } - - if record.pos > instructionIndex { - return nil - } - } - - return nil -} diff --git a/src/build/Function.go b/src/build/Function.go index 7a2b103..16a8c8c 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -64,7 +64,7 @@ func (f *Function) CompileTokens(body token.List) error { if config.Verbose { f.debug = append(f.debug, debug{ - pos: len(f.Assembler.Instructions), + position: len(f.Assembler.Instructions), instruction: instruction, }) } @@ -233,7 +233,7 @@ func (f *Function) PrintAsm() { ansi.Dim.Println("╭──────────────────────────────────────╮") for i, x := range f.Assembler.Instructions { - instruction := f.debugLine(i) + instruction := f.instructionAt(i) if instruction != nil { ansi.Dim.Println("├──────────────────────────────────────┤") diff --git a/src/build/Result.go b/src/build/Result.go index 615cbb8..bfebb2e 100644 --- a/src/build/Result.go +++ b/src/build/Result.go @@ -10,7 +10,7 @@ import ( type Result struct { Used []*Function Unused map[string]*Function - instructionCount int + InstructionCount int } // Finalize generates the final machine code. @@ -20,7 +20,7 @@ func (r Result) Finalize() ([]byte, []byte) { // The reason we call `main` instead of using `main` itself is to place // a return address on the stack, which allows return statements in `main`. final := asm.Assembler{ - Instructions: make([]asm.Instruction, 0, r.instructionCount+4), + Instructions: make([]asm.Instruction, 0, r.InstructionCount+4), } final.Call("main") diff --git a/src/build/compile.go b/src/build/compile.go index c028a68..6ffade3 100644 --- a/src/build/compile.go +++ b/src/build/compile.go @@ -39,8 +39,6 @@ func compile(functions <-chan *Function, errors <-chan error) (Result, error) { if function.Error != nil { return result, function.Error } - - result.instructionCount += len(function.Assembler.Instructions) } main, exists := result.Unused["main"] @@ -49,8 +47,8 @@ func compile(functions <-chan *Function, errors <-chan error) (Result, error) { return result, fail.MissingMainFunction } - result.Used = append(result.Used, main) - delete(result.Unused, "main") + result.Used = make([]*Function, 0, len(result.Unused)) + result.markAlive(main) result.findAliveCode(main) return result, nil @@ -73,21 +71,27 @@ func compileFunctions(functions map[string]*Function) { } // findAliveCode recursively finds all the calls to external functions and marks them as required. -func (result *Result) findAliveCode(f *Function) { - for _, x := range f.Assembler.Instructions { +func (result *Result) findAliveCode(caller *Function) { + for _, x := range caller.Assembler.Instructions { if x.Mnemonic != asm.CALL { continue } name := x.Data.(*asm.Label).Name - called, exists := result.Unused[name] + callee, exists := result.Unused[name] if !exists { continue } - result.Used = append(result.Used, called) - delete(result.Unused, name) - result.findAliveCode(called) + result.markAlive(callee) + result.findAliveCode(callee) } } + +// markAlive marks a function as being alive. +func (result *Result) markAlive(f *Function) { + result.Used = append(result.Used, f) + result.InstructionCount += len(f.Assembler.Instructions) + delete(result.Unused, f.Name) +} diff --git a/src/build/debug.go b/src/build/debug.go new file mode 100644 index 0000000..0292457 --- /dev/null +++ b/src/build/debug.go @@ -0,0 +1,24 @@ +package build + +import "git.akyoto.dev/cli/q/src/build/token" + +// debug is used to look up instructions at a certain index. +type debug struct { + position int + instruction token.List +} + +// instructionAt retrieves the instruction at the given index. +func (f *Function) instructionAt(index int) token.List { + for _, record := range f.debug { + if record.position == index { + return record.instruction + } + + if record.position > index { + return nil + } + } + + return nil +} From 81cc7e6a75e3d493a378ccfddfb6fd6ac5cc7baf Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Jun 2024 11:45:54 +0200 Subject: [PATCH 0183/1012] Improved performance --- src/build/Result.go | 41 +++++++++++++++++++++----- src/build/asm/Assembler.go | 4 +-- src/build/compile.go | 60 ++++++++++---------------------------- src/cli/Build.go | 6 ++-- 4 files changed, 55 insertions(+), 56 deletions(-) diff --git a/src/build/Result.go b/src/build/Result.go index bfebb2e..158f56f 100644 --- a/src/build/Result.go +++ b/src/build/Result.go @@ -8,13 +8,13 @@ import ( // Result contains all the compiled functions in a build. type Result struct { - Used []*Function - Unused map[string]*Function + Main *Function + Functions map[string]*Function InstructionCount int } // Finalize generates the final machine code. -func (r Result) Finalize() ([]byte, []byte) { +func (r *Result) Finalize() ([]byte, []byte) { // This will be the entry point of the executable. // The only job of the entry function is to call `main` and exit cleanly. // The reason we call `main` instead of using `main` itself is to place @@ -28,11 +28,38 @@ func (r Result) Finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0) final.Syscall() - // Merge all the called functions. - for _, f := range r.Used { - final.Merge(&f.Assembler) - } + // This will place the main function immediately after the entry point + // and also add everything the main function calls recursively. + r.EachFunction(r.Main, map[*Function]bool{}, func(f *Function) { + final.Merge(f.Assembler) + }) code, data := final.Finalize() return code, data } + +// EachFunction recursively finds all the calls to external functions. +// It avoids calling the same function twice with the help of a hashmap. +func (r *Result) EachFunction(caller *Function, traversed map[*Function]bool, call func(*Function)) { + call(caller) + traversed[caller] = true + + for _, x := range caller.Assembler.Instructions { + if x.Mnemonic != asm.CALL { + continue + } + + name := x.Data.(*asm.Label).Name + callee, exists := r.Functions[name] + + if !exists { + continue + } + + if traversed[callee] { + continue + } + + r.EachFunction(callee, traversed, call) + } +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index e3341a3..9c6f990 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -12,7 +12,7 @@ type Assembler struct { } // Finalize generates the final machine code. -func (a *Assembler) Finalize() ([]byte, []byte) { +func (a Assembler) Finalize() ([]byte, []byte) { code := make([]byte, 0, len(a.Instructions)*8) data := make([]byte, 0, 16) labels := map[string]Address{} @@ -139,7 +139,7 @@ func (a *Assembler) Finalize() ([]byte, []byte) { } // Merge combines the contents of this assembler with another one. -func (a *Assembler) Merge(b *Assembler) { +func (a *Assembler) Merge(b Assembler) { a.Instructions = append(a.Instructions, b.Instructions...) } diff --git a/src/build/compile.go b/src/build/compile.go index 6ffade3..3c1b48a 100644 --- a/src/build/compile.go +++ b/src/build/compile.go @@ -3,21 +3,19 @@ package build import ( "sync" - "git.akyoto.dev/cli/q/src/build/asm" - fail "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/errors" ) // compile waits for the scan to finish and compiles all functions. -func compile(functions <-chan *Function, errors <-chan error) (Result, error) { - result := Result{ - Unused: map[string]*Function{}, - } +func compile(functions <-chan *Function, errs <-chan error) (Result, error) { + result := Result{} + allFunctions := map[string]*Function{} - for functions != nil || errors != nil { + for functions != nil || errs != nil { select { - case err, ok := <-errors: + case err, ok := <-errs: if !ok { - errors = nil + errs = nil continue } @@ -29,28 +27,28 @@ func compile(functions <-chan *Function, errors <-chan error) (Result, error) { continue } - result.Unused[function.Name] = function + allFunctions[function.Name] = function } } - compileFunctions(result.Unused) + compileFunctions(allFunctions) - for _, function := range result.Unused { + for _, function := range allFunctions { if function.Error != nil { return result, function.Error } + + result.InstructionCount += len(function.Assembler.Instructions) } - main, exists := result.Unused["main"] + main, exists := allFunctions["main"] if !exists { - return result, fail.MissingMainFunction + return result, errors.MissingMainFunction } - result.Used = make([]*Function, 0, len(result.Unused)) - result.markAlive(main) - result.findAliveCode(main) - + result.Main = main + result.Functions = allFunctions return result, nil } @@ -69,29 +67,3 @@ func compileFunctions(functions map[string]*Function) { wg.Wait() } - -// findAliveCode recursively finds all the calls to external functions and marks them as required. -func (result *Result) findAliveCode(caller *Function) { - for _, x := range caller.Assembler.Instructions { - if x.Mnemonic != asm.CALL { - continue - } - - name := x.Data.(*asm.Label).Name - callee, exists := result.Unused[name] - - if !exists { - continue - } - - result.markAlive(callee) - result.findAliveCode(callee) - } -} - -// markAlive marks a function as being alive. -func (result *Result) markAlive(f *Function) { - result.Used = append(result.Used, f) - result.InstructionCount += len(f.Assembler.Instructions) - delete(result.Unused, f.Name) -} diff --git a/src/cli/Build.go b/src/cli/Build.go index cd5ac11..c625081 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -44,9 +44,9 @@ func Build(args []string) int { } if config.Verbose { - for _, function := range result.Used { - function.PrintAsm() - } + result.EachFunction(result.Main, map[*build.Function]bool{}, func(f *build.Function) { + f.PrintAsm() + }) } if dry { From 8ae52468079249322ef1d59db5dcd28a16d15074 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Jun 2024 11:45:54 +0200 Subject: [PATCH 0184/1012] Improved performance --- src/build/Result.go | 41 +++++++++++++++++++++----- src/build/asm/Assembler.go | 4 +-- src/build/compile.go | 60 ++++++++++---------------------------- src/cli/Build.go | 6 ++-- 4 files changed, 55 insertions(+), 56 deletions(-) diff --git a/src/build/Result.go b/src/build/Result.go index bfebb2e..158f56f 100644 --- a/src/build/Result.go +++ b/src/build/Result.go @@ -8,13 +8,13 @@ import ( // Result contains all the compiled functions in a build. type Result struct { - Used []*Function - Unused map[string]*Function + Main *Function + Functions map[string]*Function InstructionCount int } // Finalize generates the final machine code. -func (r Result) Finalize() ([]byte, []byte) { +func (r *Result) Finalize() ([]byte, []byte) { // This will be the entry point of the executable. // The only job of the entry function is to call `main` and exit cleanly. // The reason we call `main` instead of using `main` itself is to place @@ -28,11 +28,38 @@ func (r Result) Finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0) final.Syscall() - // Merge all the called functions. - for _, f := range r.Used { - final.Merge(&f.Assembler) - } + // This will place the main function immediately after the entry point + // and also add everything the main function calls recursively. + r.EachFunction(r.Main, map[*Function]bool{}, func(f *Function) { + final.Merge(f.Assembler) + }) code, data := final.Finalize() return code, data } + +// EachFunction recursively finds all the calls to external functions. +// It avoids calling the same function twice with the help of a hashmap. +func (r *Result) EachFunction(caller *Function, traversed map[*Function]bool, call func(*Function)) { + call(caller) + traversed[caller] = true + + for _, x := range caller.Assembler.Instructions { + if x.Mnemonic != asm.CALL { + continue + } + + name := x.Data.(*asm.Label).Name + callee, exists := r.Functions[name] + + if !exists { + continue + } + + if traversed[callee] { + continue + } + + r.EachFunction(callee, traversed, call) + } +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index e3341a3..9c6f990 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -12,7 +12,7 @@ type Assembler struct { } // Finalize generates the final machine code. -func (a *Assembler) Finalize() ([]byte, []byte) { +func (a Assembler) Finalize() ([]byte, []byte) { code := make([]byte, 0, len(a.Instructions)*8) data := make([]byte, 0, 16) labels := map[string]Address{} @@ -139,7 +139,7 @@ func (a *Assembler) Finalize() ([]byte, []byte) { } // Merge combines the contents of this assembler with another one. -func (a *Assembler) Merge(b *Assembler) { +func (a *Assembler) Merge(b Assembler) { a.Instructions = append(a.Instructions, b.Instructions...) } diff --git a/src/build/compile.go b/src/build/compile.go index 6ffade3..3c1b48a 100644 --- a/src/build/compile.go +++ b/src/build/compile.go @@ -3,21 +3,19 @@ package build import ( "sync" - "git.akyoto.dev/cli/q/src/build/asm" - fail "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/errors" ) // compile waits for the scan to finish and compiles all functions. -func compile(functions <-chan *Function, errors <-chan error) (Result, error) { - result := Result{ - Unused: map[string]*Function{}, - } +func compile(functions <-chan *Function, errs <-chan error) (Result, error) { + result := Result{} + allFunctions := map[string]*Function{} - for functions != nil || errors != nil { + for functions != nil || errs != nil { select { - case err, ok := <-errors: + case err, ok := <-errs: if !ok { - errors = nil + errs = nil continue } @@ -29,28 +27,28 @@ func compile(functions <-chan *Function, errors <-chan error) (Result, error) { continue } - result.Unused[function.Name] = function + allFunctions[function.Name] = function } } - compileFunctions(result.Unused) + compileFunctions(allFunctions) - for _, function := range result.Unused { + for _, function := range allFunctions { if function.Error != nil { return result, function.Error } + + result.InstructionCount += len(function.Assembler.Instructions) } - main, exists := result.Unused["main"] + main, exists := allFunctions["main"] if !exists { - return result, fail.MissingMainFunction + return result, errors.MissingMainFunction } - result.Used = make([]*Function, 0, len(result.Unused)) - result.markAlive(main) - result.findAliveCode(main) - + result.Main = main + result.Functions = allFunctions return result, nil } @@ -69,29 +67,3 @@ func compileFunctions(functions map[string]*Function) { wg.Wait() } - -// findAliveCode recursively finds all the calls to external functions and marks them as required. -func (result *Result) findAliveCode(caller *Function) { - for _, x := range caller.Assembler.Instructions { - if x.Mnemonic != asm.CALL { - continue - } - - name := x.Data.(*asm.Label).Name - callee, exists := result.Unused[name] - - if !exists { - continue - } - - result.markAlive(callee) - result.findAliveCode(callee) - } -} - -// markAlive marks a function as being alive. -func (result *Result) markAlive(f *Function) { - result.Used = append(result.Used, f) - result.InstructionCount += len(f.Assembler.Instructions) - delete(result.Unused, f.Name) -} diff --git a/src/cli/Build.go b/src/cli/Build.go index cd5ac11..c625081 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -44,9 +44,9 @@ func Build(args []string) int { } if config.Verbose { - for _, function := range result.Used { - function.PrintAsm() - } + result.EachFunction(result.Main, map[*build.Function]bool{}, func(f *build.Function) { + f.PrintAsm() + }) } if dry { From 7f8579fa110605062e7b34a75d6f98820a064436 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Jun 2024 15:34:25 +0200 Subject: [PATCH 0185/1012] Improved benchmarks --- bench_test.go | 10 +++++++--- examples/hello/hello.q | 4 ---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bench_test.go b/bench_test.go index d091d84..4bb3f8b 100644 --- a/bench_test.go +++ b/bench_test.go @@ -4,13 +4,15 @@ import ( "testing" "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/go/assert" ) func BenchmarkEmpty(b *testing.B) { compiler := build.New("tests/benchmarks/empty.q") for i := 0; i < b.N; i++ { - compiler.Run() + _, err := compiler.Run() + assert.Nil(b, err) } } @@ -18,7 +20,8 @@ func BenchmarkExpressions(b *testing.B) { compiler := build.New("tests/benchmarks/expressions.q") for i := 0; i < b.N; i++ { - compiler.Run() + _, err := compiler.Run() + assert.Nil(b, err) } } @@ -26,6 +29,7 @@ func BenchmarkHello(b *testing.B) { compiler := build.New("examples/hello") for i := 0; i < b.N; i++ { - compiler.Run() + _, err := compiler.Run() + assert.Nil(b, err) } } diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 07f7c6d..c006280 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -13,8 +13,4 @@ print(address, length) { write(fd, address, length) { syscall(1, fd, address, length) -} - -empty() { - } \ No newline at end of file From 98f5f021f02456ac496f62af572012387a9777a3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Jun 2024 15:34:25 +0200 Subject: [PATCH 0186/1012] Improved benchmarks --- bench_test.go | 10 +++++++--- examples/hello/hello.q | 4 ---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bench_test.go b/bench_test.go index d091d84..4bb3f8b 100644 --- a/bench_test.go +++ b/bench_test.go @@ -4,13 +4,15 @@ import ( "testing" "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/go/assert" ) func BenchmarkEmpty(b *testing.B) { compiler := build.New("tests/benchmarks/empty.q") for i := 0; i < b.N; i++ { - compiler.Run() + _, err := compiler.Run() + assert.Nil(b, err) } } @@ -18,7 +20,8 @@ func BenchmarkExpressions(b *testing.B) { compiler := build.New("tests/benchmarks/expressions.q") for i := 0; i < b.N; i++ { - compiler.Run() + _, err := compiler.Run() + assert.Nil(b, err) } } @@ -26,6 +29,7 @@ func BenchmarkHello(b *testing.B) { compiler := build.New("examples/hello") for i := 0; i < b.N; i++ { - compiler.Run() + _, err := compiler.Run() + assert.Nil(b, err) } } diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 07f7c6d..c006280 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -13,8 +13,4 @@ print(address, length) { write(fd, address, length) { syscall(1, fd, address, length) -} - -empty() { - } \ No newline at end of file From 8362d0a91aa503308717b8316fcae5be7cb4a939 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Jun 2024 19:33:02 +0200 Subject: [PATCH 0187/1012] Improved tokenizer benchmarks --- src/build/token/bench_test.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/build/token/bench_test.go b/src/build/token/bench_test.go index b6db849..3ec6007 100644 --- a/src/build/token/bench_test.go +++ b/src/build/token/bench_test.go @@ -1,15 +1,26 @@ package token_test import ( + "bytes" "testing" "git.akyoto.dev/cli/q/src/build/token" ) -func BenchmarkTokenize(b *testing.B) { - input := []byte("hello := 123\nworld := 456") +func BenchmarkLines(b *testing.B) { + b.Run("__1", bench(1)) + b.Run("_10", bench(10)) + b.Run("100", bench(100)) +} - for i := 0; i < b.N; i++ { - token.Tokenize(input) +func bench(n int) func(b *testing.B) { + line := []byte("hello := 123\n") + + return func(b *testing.B) { + input := bytes.Repeat(line, n) + + for i := 0; i < b.N; i++ { + token.Tokenize(input) + } } } From 6852cbb24e866cc382035cb94de844d78e3f00b8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Jun 2024 19:33:02 +0200 Subject: [PATCH 0188/1012] Improved tokenizer benchmarks --- src/build/token/bench_test.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/build/token/bench_test.go b/src/build/token/bench_test.go index b6db849..3ec6007 100644 --- a/src/build/token/bench_test.go +++ b/src/build/token/bench_test.go @@ -1,15 +1,26 @@ package token_test import ( + "bytes" "testing" "git.akyoto.dev/cli/q/src/build/token" ) -func BenchmarkTokenize(b *testing.B) { - input := []byte("hello := 123\nworld := 456") +func BenchmarkLines(b *testing.B) { + b.Run("__1", bench(1)) + b.Run("_10", bench(10)) + b.Run("100", bench(100)) +} - for i := 0; i < b.N; i++ { - token.Tokenize(input) +func bench(n int) func(b *testing.B) { + line := []byte("hello := 123\n") + + return func(b *testing.B) { + input := bytes.Repeat(line, n) + + for i := 0; i < b.N; i++ { + token.Tokenize(input) + } } } From 0553706891d02f020051ad9e7640ef56ab5cbbe9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Jun 2024 19:54:26 +0200 Subject: [PATCH 0189/1012] Implemented invalid character checks --- errors_test.go | 3 +++ src/build/scan.go | 12 ++++++++++++ src/build/token/Tokenize.go | 5 +++++ src/errors/InvalidCharacter.go | 13 +++++++++++++ tests/errors/InvalidCharacter.q | 1 + tests/errors/InvalidCharacter2.q | 1 + tests/errors/InvalidCharacter3.q | 1 + 7 files changed, 36 insertions(+) create mode 100644 src/errors/InvalidCharacter.go create mode 100644 tests/errors/InvalidCharacter.q create mode 100644 tests/errors/InvalidCharacter2.q create mode 100644 tests/errors/InvalidCharacter3.q diff --git a/errors_test.go b/errors_test.go index f386c66..c18344e 100644 --- a/errors_test.go +++ b/errors_test.go @@ -22,6 +22,9 @@ func TestErrors(t *testing.T) { {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, {"InvalidExpression.q", errors.InvalidExpression}, {"InvalidOperator.q", &errors.InvalidOperator{Operator: "+++"}}, + {"InvalidCharacter.q", &errors.InvalidCharacter{Character: "@"}}, + {"InvalidCharacter2.q", &errors.InvalidCharacter{Character: "@"}}, + {"InvalidCharacter3.q", &errors.InvalidCharacter{Character: "@"}}, {"MissingAssignValue.q", errors.MissingAssignValue}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, diff --git a/src/build/scan.go b/src/build/scan.go index fd01a26..15fdfe9 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -119,6 +119,10 @@ func scanFile(path string, functions chan<- *Function) error { continue } + if tokens[i].Kind == token.Invalid { + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + } + if tokens[i].Kind == token.EOF { return nil } @@ -156,6 +160,10 @@ func scanFile(path string, functions chan<- *Function) error { continue } + if tokens[i].Kind == token.Invalid { + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + } + if tokens[i].Kind == token.EOF { if groupLevel > 0 { return errors.New(errors.MissingGroupEnd, file, tokens[i].Position) @@ -204,6 +212,10 @@ func scanFile(path string, functions chan<- *Function) error { continue } + if tokens[i].Kind == token.Invalid { + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + } + if tokens[i].Kind == token.EOF { if blockLevel > 0 { return errors.New(errors.MissingBlockEnd, file, tokens[i].Position) diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index a8e4acd..4c4223f 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -22,6 +22,8 @@ func Tokenize(buffer []byte) List { for i < len(buffer) { switch buffer[i] { + // Whitespace + case ' ', '\t': // Texts case '"': start := i @@ -118,6 +120,9 @@ func Tokenize(buffer []byte) List { tokens = append(tokens, Token{Operator, position, buffer[position:i]}) continue } + + // Invalid characters + tokens = append(tokens, Token{Invalid, i, buffer[i : i+1]}) } i++ diff --git a/src/errors/InvalidCharacter.go b/src/errors/InvalidCharacter.go new file mode 100644 index 0000000..9d03918 --- /dev/null +++ b/src/errors/InvalidCharacter.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// InvalidCharacter error is created when an invalid character appears. +type InvalidCharacter struct { + Character string +} + +// Error generates the string representation. +func (err *InvalidCharacter) Error() string { + return fmt.Sprintf("Invalid character '%s'", err.Character) +} diff --git a/tests/errors/InvalidCharacter.q b/tests/errors/InvalidCharacter.q new file mode 100644 index 0000000..b516b2c --- /dev/null +++ b/tests/errors/InvalidCharacter.q @@ -0,0 +1 @@ +@ \ No newline at end of file diff --git a/tests/errors/InvalidCharacter2.q b/tests/errors/InvalidCharacter2.q new file mode 100644 index 0000000..f870343 --- /dev/null +++ b/tests/errors/InvalidCharacter2.q @@ -0,0 +1 @@ +main@ \ No newline at end of file diff --git a/tests/errors/InvalidCharacter3.q b/tests/errors/InvalidCharacter3.q new file mode 100644 index 0000000..5f777b8 --- /dev/null +++ b/tests/errors/InvalidCharacter3.q @@ -0,0 +1 @@ +main()@ \ No newline at end of file From 3664e740744f9cf6b43499a3409227ec3bc2d514 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Jun 2024 19:54:26 +0200 Subject: [PATCH 0190/1012] Implemented invalid character checks --- errors_test.go | 3 +++ src/build/scan.go | 12 ++++++++++++ src/build/token/Tokenize.go | 5 +++++ src/errors/InvalidCharacter.go | 13 +++++++++++++ tests/errors/InvalidCharacter.q | 1 + tests/errors/InvalidCharacter2.q | 1 + tests/errors/InvalidCharacter3.q | 1 + 7 files changed, 36 insertions(+) create mode 100644 src/errors/InvalidCharacter.go create mode 100644 tests/errors/InvalidCharacter.q create mode 100644 tests/errors/InvalidCharacter2.q create mode 100644 tests/errors/InvalidCharacter3.q diff --git a/errors_test.go b/errors_test.go index f386c66..c18344e 100644 --- a/errors_test.go +++ b/errors_test.go @@ -22,6 +22,9 @@ func TestErrors(t *testing.T) { {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, {"InvalidExpression.q", errors.InvalidExpression}, {"InvalidOperator.q", &errors.InvalidOperator{Operator: "+++"}}, + {"InvalidCharacter.q", &errors.InvalidCharacter{Character: "@"}}, + {"InvalidCharacter2.q", &errors.InvalidCharacter{Character: "@"}}, + {"InvalidCharacter3.q", &errors.InvalidCharacter{Character: "@"}}, {"MissingAssignValue.q", errors.MissingAssignValue}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, diff --git a/src/build/scan.go b/src/build/scan.go index fd01a26..15fdfe9 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -119,6 +119,10 @@ func scanFile(path string, functions chan<- *Function) error { continue } + if tokens[i].Kind == token.Invalid { + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + } + if tokens[i].Kind == token.EOF { return nil } @@ -156,6 +160,10 @@ func scanFile(path string, functions chan<- *Function) error { continue } + if tokens[i].Kind == token.Invalid { + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + } + if tokens[i].Kind == token.EOF { if groupLevel > 0 { return errors.New(errors.MissingGroupEnd, file, tokens[i].Position) @@ -204,6 +212,10 @@ func scanFile(path string, functions chan<- *Function) error { continue } + if tokens[i].Kind == token.Invalid { + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + } + if tokens[i].Kind == token.EOF { if blockLevel > 0 { return errors.New(errors.MissingBlockEnd, file, tokens[i].Position) diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index a8e4acd..4c4223f 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -22,6 +22,8 @@ func Tokenize(buffer []byte) List { for i < len(buffer) { switch buffer[i] { + // Whitespace + case ' ', '\t': // Texts case '"': start := i @@ -118,6 +120,9 @@ func Tokenize(buffer []byte) List { tokens = append(tokens, Token{Operator, position, buffer[position:i]}) continue } + + // Invalid characters + tokens = append(tokens, Token{Invalid, i, buffer[i : i+1]}) } i++ diff --git a/src/errors/InvalidCharacter.go b/src/errors/InvalidCharacter.go new file mode 100644 index 0000000..9d03918 --- /dev/null +++ b/src/errors/InvalidCharacter.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// InvalidCharacter error is created when an invalid character appears. +type InvalidCharacter struct { + Character string +} + +// Error generates the string representation. +func (err *InvalidCharacter) Error() string { + return fmt.Sprintf("Invalid character '%s'", err.Character) +} diff --git a/tests/errors/InvalidCharacter.q b/tests/errors/InvalidCharacter.q new file mode 100644 index 0000000..b516b2c --- /dev/null +++ b/tests/errors/InvalidCharacter.q @@ -0,0 +1 @@ +@ \ No newline at end of file diff --git a/tests/errors/InvalidCharacter2.q b/tests/errors/InvalidCharacter2.q new file mode 100644 index 0000000..f870343 --- /dev/null +++ b/tests/errors/InvalidCharacter2.q @@ -0,0 +1 @@ +main@ \ No newline at end of file diff --git a/tests/errors/InvalidCharacter3.q b/tests/errors/InvalidCharacter3.q new file mode 100644 index 0000000..5f777b8 --- /dev/null +++ b/tests/errors/InvalidCharacter3.q @@ -0,0 +1 @@ +main()@ \ No newline at end of file From b8d7e2444b072eb6e07d81fa627750912bcbfd38 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Jun 2024 20:29:07 +0200 Subject: [PATCH 0191/1012] Added single line comments --- examples/hello/hello.q | 3 ++ src/build/token/Token_test.go | 87 +++++++++++++++++++++++++++++++++++ src/build/token/Tokenize.go | 14 ++++++ 3 files changed, 104 insertions(+) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index c006280..1da41c1 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,3 +1,4 @@ +// Comment main() { address := 4194304 + 1 length := (0 + 50 - 20) * 10 / 100 @@ -7,10 +8,12 @@ main() { } } +// Comment print(address, length) { write(length-2, address, length) } +// Comment write(fd, address, length) { syscall(1, fd, address, length) } \ No newline at end of file diff --git a/src/build/token/Token_test.go b/src/build/token/Token_test.go index 6bd1065..5616634 100644 --- a/src/build/token/Token_test.go +++ b/src/build/token/Token_test.go @@ -209,6 +209,93 @@ func TestSeparator(t *testing.T) { }) } +func TestComment(t *testing.T) { + tokens := token.Tokenize([]byte("// Hello\n// World")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Comment, + Bytes: []byte(`// Hello`), + Position: 0, + }, + { + Kind: token.NewLine, + Bytes: []byte("\n"), + Position: 8, + }, + { + Kind: token.Comment, + Bytes: []byte(`// World`), + Position: 9, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 17, + }, + }) + + tokens = token.Tokenize([]byte("// Hello\n")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Comment, + Bytes: []byte(`// Hello`), + Position: 0, + }, + { + Kind: token.NewLine, + Bytes: []byte("\n"), + Position: 8, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 9, + }, + }) + + tokens = token.Tokenize([]byte(`// Hello`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Comment, + Bytes: []byte(`// Hello`), + Position: 0, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, + }) + + tokens = token.Tokenize([]byte(`//`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Comment, + Bytes: []byte(`//`), + Position: 0, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 2, + }, + }) + + tokens = token.Tokenize([]byte(`/`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Operator, + Bytes: []byte(`/`), + Position: 0, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 1, + }, + }) +} + func TestString(t *testing.T) { tokens := token.Tokenize([]byte(`"Hello" "World"`)) assert.DeepEqual(t, tokens, token.List{ diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 4c4223f..bd2d4c6 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -75,6 +75,20 @@ func Tokenize(buffer []byte) List { case '\n': tokens = append(tokens, Token{NewLine, i, newLineBytes}) + // Comment + case '/': + if i+1 >= len(buffer) || buffer[i+1] != '/' { + tokens = append(tokens, Token{Operator, i, buffer[i : i+1]}) + i++ + continue + } + + for i < len(buffer) && buffer[i] != '\n' { + i++ + } + + continue + default: // Identifiers if isIdentifierStart(buffer[i]) { From 2520681ad3ed38aaa5a5cc1566a7727a5a08ac4e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Jun 2024 20:29:07 +0200 Subject: [PATCH 0192/1012] Added single line comments --- examples/hello/hello.q | 3 ++ src/build/token/Token_test.go | 87 +++++++++++++++++++++++++++++++++++ src/build/token/Tokenize.go | 14 ++++++ 3 files changed, 104 insertions(+) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index c006280..1da41c1 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,3 +1,4 @@ +// Comment main() { address := 4194304 + 1 length := (0 + 50 - 20) * 10 / 100 @@ -7,10 +8,12 @@ main() { } } +// Comment print(address, length) { write(length-2, address, length) } +// Comment write(fd, address, length) { syscall(1, fd, address, length) } \ No newline at end of file diff --git a/src/build/token/Token_test.go b/src/build/token/Token_test.go index 6bd1065..5616634 100644 --- a/src/build/token/Token_test.go +++ b/src/build/token/Token_test.go @@ -209,6 +209,93 @@ func TestSeparator(t *testing.T) { }) } +func TestComment(t *testing.T) { + tokens := token.Tokenize([]byte("// Hello\n// World")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Comment, + Bytes: []byte(`// Hello`), + Position: 0, + }, + { + Kind: token.NewLine, + Bytes: []byte("\n"), + Position: 8, + }, + { + Kind: token.Comment, + Bytes: []byte(`// World`), + Position: 9, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 17, + }, + }) + + tokens = token.Tokenize([]byte("// Hello\n")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Comment, + Bytes: []byte(`// Hello`), + Position: 0, + }, + { + Kind: token.NewLine, + Bytes: []byte("\n"), + Position: 8, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 9, + }, + }) + + tokens = token.Tokenize([]byte(`// Hello`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Comment, + Bytes: []byte(`// Hello`), + Position: 0, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, + }) + + tokens = token.Tokenize([]byte(`//`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Comment, + Bytes: []byte(`//`), + Position: 0, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 2, + }, + }) + + tokens = token.Tokenize([]byte(`/`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Operator, + Bytes: []byte(`/`), + Position: 0, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 1, + }, + }) +} + func TestString(t *testing.T) { tokens := token.Tokenize([]byte(`"Hello" "World"`)) assert.DeepEqual(t, tokens, token.List{ diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 4c4223f..bd2d4c6 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -75,6 +75,20 @@ func Tokenize(buffer []byte) List { case '\n': tokens = append(tokens, Token{NewLine, i, newLineBytes}) + // Comment + case '/': + if i+1 >= len(buffer) || buffer[i+1] != '/' { + tokens = append(tokens, Token{Operator, i, buffer[i : i+1]}) + i++ + continue + } + + for i < len(buffer) && buffer[i] != '\n' { + i++ + } + + continue + default: // Identifiers if isIdentifierStart(buffer[i]) { From b20a79080fa9b847a0d5499a70bf758eda83243f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Jun 2024 21:11:05 +0200 Subject: [PATCH 0193/1012] Reordered tokenizer cases --- src/build/token/Tokenize.go | 52 ++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index bd2d4c6..b2fc798 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -24,24 +24,9 @@ func Tokenize(buffer []byte) List { switch buffer[i] { // Whitespace case ' ', '\t': - // Texts - case '"': - start := i - end := len(buffer) - i++ - - for i < len(buffer) { - if buffer[i] == '"' { - end = i + 1 - i++ - break - } - - i++ - } - - tokens = append(tokens, Token{String, start, buffer[start:end]}) - continue + // Separator + case ',': + tokens = append(tokens, Token{Separator, i, separatorBytes}) // Parentheses start case '(': @@ -67,10 +52,6 @@ func Tokenize(buffer []byte) List { case ']': tokens = append(tokens, Token{ArrayEnd, i, arrayEndBytes}) - // Separator - case ',': - tokens = append(tokens, Token{Separator, i, separatorBytes}) - // New line case '\n': tokens = append(tokens, Token{NewLine, i, newLineBytes}) @@ -89,8 +70,27 @@ func Tokenize(buffer []byte) List { continue + // String + case '"': + start := i + end := len(buffer) + i++ + + for i < len(buffer) { + if buffer[i] == '"' { + end = i + 1 + i++ + break + } + + i++ + } + + tokens = append(tokens, Token{String, start, buffer[start:end]}) + continue + default: - // Identifiers + // Identifier if isIdentifierStart(buffer[i]) { position := i i++ @@ -109,7 +109,7 @@ func Tokenize(buffer []byte) List { continue } - // Numbers + // Number if isNumber(buffer[i]) { position := i i++ @@ -122,7 +122,7 @@ func Tokenize(buffer []byte) List { continue } - // Operators + // Operator if isOperator(buffer[i]) { position := i i++ @@ -135,7 +135,7 @@ func Tokenize(buffer []byte) List { continue } - // Invalid characters + // Invalid character tokens = append(tokens, Token{Invalid, i, buffer[i : i+1]}) } From 445556b64d362632ad6849ed0435ece5d6f68bd9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Jun 2024 21:11:05 +0200 Subject: [PATCH 0194/1012] Reordered tokenizer cases --- src/build/token/Tokenize.go | 52 ++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index bd2d4c6..b2fc798 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -24,24 +24,9 @@ func Tokenize(buffer []byte) List { switch buffer[i] { // Whitespace case ' ', '\t': - // Texts - case '"': - start := i - end := len(buffer) - i++ - - for i < len(buffer) { - if buffer[i] == '"' { - end = i + 1 - i++ - break - } - - i++ - } - - tokens = append(tokens, Token{String, start, buffer[start:end]}) - continue + // Separator + case ',': + tokens = append(tokens, Token{Separator, i, separatorBytes}) // Parentheses start case '(': @@ -67,10 +52,6 @@ func Tokenize(buffer []byte) List { case ']': tokens = append(tokens, Token{ArrayEnd, i, arrayEndBytes}) - // Separator - case ',': - tokens = append(tokens, Token{Separator, i, separatorBytes}) - // New line case '\n': tokens = append(tokens, Token{NewLine, i, newLineBytes}) @@ -89,8 +70,27 @@ func Tokenize(buffer []byte) List { continue + // String + case '"': + start := i + end := len(buffer) + i++ + + for i < len(buffer) { + if buffer[i] == '"' { + end = i + 1 + i++ + break + } + + i++ + } + + tokens = append(tokens, Token{String, start, buffer[start:end]}) + continue + default: - // Identifiers + // Identifier if isIdentifierStart(buffer[i]) { position := i i++ @@ -109,7 +109,7 @@ func Tokenize(buffer []byte) List { continue } - // Numbers + // Number if isNumber(buffer[i]) { position := i i++ @@ -122,7 +122,7 @@ func Tokenize(buffer []byte) List { continue } - // Operators + // Operator if isOperator(buffer[i]) { position := i i++ @@ -135,7 +135,7 @@ func Tokenize(buffer []byte) List { continue } - // Invalid characters + // Invalid character tokens = append(tokens, Token{Invalid, i, buffer[i : i+1]}) } From f870a5f1f84f891e6a48999fab2011c32178f5ea Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Jun 2024 23:13:55 +0200 Subject: [PATCH 0195/1012] Added more tests --- src/build/expression/Expression.go | 8 ++--- src/build/expression/Expression_test.go | 36 +++++++++++++++++++ src/build/expression/Operator.go | 46 +++++++++++++------------ src/build/scan.go | 2 +- src/build/token/Tokenize.go | 11 +++++- 5 files changed, 73 insertions(+), 30 deletions(-) diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index c2be790..7776c35 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -109,14 +109,10 @@ func (expr *Expression) write(builder *strings.Builder) { builder.WriteByte('(') builder.WriteString(expr.Token.Text()) - builder.WriteByte(' ') - for i, child := range expr.Children { + for _, child := range expr.Children { + builder.WriteByte(' ') child.write(builder) - - if i != len(expr.Children)-1 { - builder.WriteByte(' ') - } } builder.WriteByte(')') diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index e267a60..eb992c6 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -118,6 +118,28 @@ func TestEachLeaf(t *testing.T) { assert.Equal(t, err.Error(), "error") } +func TestEachParameter(t *testing.T) { + src := []byte("1+2,3*4,5*6,7+8") + tokens := token.Tokenize(src) + parameters := []string{} + + err := expression.EachParameter(tokens, func(parameter token.List) error { + expr := expression.Parse(parameter) + parameters = append(parameters, expr.String()) + return nil + }) + + assert.Nil(t, err) + assert.DeepEqual(t, parameters, []string{"(+ 1 2)", "(* 3 4)", "(* 5 6)", "(+ 7 8)"}) + + err = expression.EachParameter(tokens, func(parameter token.List) error { + return fmt.Errorf("error") + }) + + assert.NotNil(t, err) + assert.Equal(t, err.Error(), "error") +} + func TestRemoveChild(t *testing.T) { src := []byte("(1+2-3*4)+(5*6-7+8)") tokens := token.Tokenize(src) @@ -141,3 +163,17 @@ func TestNilGroup(t *testing.T) { expr := expression.Parse(tokens) assert.Nil(t, expr) } + +func TestInvalidOperator(t *testing.T) { + src := []byte("a +++ 2") + tokens := token.Tokenize(src) + expr := expression.Parse(tokens) + assert.Equal(t, expr.String(), "(+++ a 2)") +} + +func TestInvalidOperatorCall(t *testing.T) { + src := []byte("+++()") + tokens := token.Tokenize(src) + expr := expression.Parse(tokens) + assert.NotNil(t, expr) +} diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index 391ed74..5e50dc5 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -16,27 +16,29 @@ type Operator struct { // Operators defines the operators used in the language. // The number corresponds to the operator priority and can not be zero. var Operators = map[string]*Operator{ - ".": {".", 13, 2}, - "λ": {"λ", 12, 1}, - "!": {"!", 11, 1}, - "*": {"*", 10, 2}, - "/": {"/", 10, 2}, - "%": {"%", 10, 2}, - "+": {"+", 9, 2}, - "-": {"-", 9, 2}, - ">>": {">>", 8, 2}, - "<<": {"<<", 8, 2}, - ">": {">", 7, 2}, - "<": {"<", 7, 2}, - ">=": {">=", 7, 2}, - "<=": {"<=", 7, 2}, - "==": {"==", 6, 2}, - "!=": {"!=", 6, 2}, - "&": {"&", 5, 2}, - "^": {"^", 4, 2}, - "|": {"|", 3, 2}, - "&&": {"&&", 2, 2}, - "||": {"||", 1, 2}, + ".": {".", 13, 2}, + "λ": {"λ", 12, 1}, + "!": {"!", 11, 1}, + "*": {"*", 10, 2}, + "/": {"/", 10, 2}, + "%": {"%", 10, 2}, + "+": {"+", 9, 2}, + "-": {"-", 9, 2}, + ">>": {">>", 8, 2}, + "<<": {"<<", 8, 2}, + "&": {"&", 7, 2}, + "^": {"^", 6, 2}, + "|": {"|", 5, 2}, + + ">": {">", 4, 2}, + "<": {"<", 4, 2}, + ">=": {">=", 4, 2}, + "<=": {"<=", 4, 2}, + "==": {"==", 3, 2}, + "!=": {"!=", 3, 2}, + "&&": {"&&", 2, 2}, + "||": {"||", 1, 2}, + "=": {"=", math.MinInt, 2}, ":=": {":=", math.MinInt, 2}, "+=": {"+=", math.MinInt, 2}, @@ -56,7 +58,7 @@ func isComplete(expr *Expression) bool { return true } - if expr.Token.Kind == token.Operator && len(expr.Children) >= numOperands(expr.Token.Text()) { + if expr.Token.Kind == token.Operator && len(expr.Children) == numOperands(expr.Token.Text()) { return true } diff --git a/src/build/scan.go b/src/build/scan.go index 15fdfe9..93149b7 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -114,7 +114,7 @@ func scanFile(path string, functions chan<- *Function) error { break } - if tokens[i].Kind == token.NewLine { + if tokens[i].Kind == token.NewLine || tokens[i].Kind == token.Comment { i++ continue } diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index b2fc798..5388b4b 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -59,15 +59,24 @@ func Tokenize(buffer []byte) List { // Comment case '/': if i+1 >= len(buffer) || buffer[i+1] != '/' { - tokens = append(tokens, Token{Operator, i, buffer[i : i+1]}) + position := i i++ + + for i < len(buffer) && isOperator(buffer[i]) { + i++ + } + + tokens = append(tokens, Token{Operator, position, buffer[position:i]}) continue } + position := i + for i < len(buffer) && buffer[i] != '\n' { i++ } + tokens = append(tokens, Token{Comment, position, buffer[position:i]}) continue // String From 5f2ff5e74e39aef0c5d40c50fa8bbba541da499f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Jun 2024 23:13:55 +0200 Subject: [PATCH 0196/1012] Added more tests --- src/build/expression/Expression.go | 8 ++--- src/build/expression/Expression_test.go | 36 +++++++++++++++++++ src/build/expression/Operator.go | 46 +++++++++++++------------ src/build/scan.go | 2 +- src/build/token/Tokenize.go | 11 +++++- 5 files changed, 73 insertions(+), 30 deletions(-) diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index c2be790..7776c35 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -109,14 +109,10 @@ func (expr *Expression) write(builder *strings.Builder) { builder.WriteByte('(') builder.WriteString(expr.Token.Text()) - builder.WriteByte(' ') - for i, child := range expr.Children { + for _, child := range expr.Children { + builder.WriteByte(' ') child.write(builder) - - if i != len(expr.Children)-1 { - builder.WriteByte(' ') - } } builder.WriteByte(')') diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index e267a60..eb992c6 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -118,6 +118,28 @@ func TestEachLeaf(t *testing.T) { assert.Equal(t, err.Error(), "error") } +func TestEachParameter(t *testing.T) { + src := []byte("1+2,3*4,5*6,7+8") + tokens := token.Tokenize(src) + parameters := []string{} + + err := expression.EachParameter(tokens, func(parameter token.List) error { + expr := expression.Parse(parameter) + parameters = append(parameters, expr.String()) + return nil + }) + + assert.Nil(t, err) + assert.DeepEqual(t, parameters, []string{"(+ 1 2)", "(* 3 4)", "(* 5 6)", "(+ 7 8)"}) + + err = expression.EachParameter(tokens, func(parameter token.List) error { + return fmt.Errorf("error") + }) + + assert.NotNil(t, err) + assert.Equal(t, err.Error(), "error") +} + func TestRemoveChild(t *testing.T) { src := []byte("(1+2-3*4)+(5*6-7+8)") tokens := token.Tokenize(src) @@ -141,3 +163,17 @@ func TestNilGroup(t *testing.T) { expr := expression.Parse(tokens) assert.Nil(t, expr) } + +func TestInvalidOperator(t *testing.T) { + src := []byte("a +++ 2") + tokens := token.Tokenize(src) + expr := expression.Parse(tokens) + assert.Equal(t, expr.String(), "(+++ a 2)") +} + +func TestInvalidOperatorCall(t *testing.T) { + src := []byte("+++()") + tokens := token.Tokenize(src) + expr := expression.Parse(tokens) + assert.NotNil(t, expr) +} diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index 391ed74..5e50dc5 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -16,27 +16,29 @@ type Operator struct { // Operators defines the operators used in the language. // The number corresponds to the operator priority and can not be zero. var Operators = map[string]*Operator{ - ".": {".", 13, 2}, - "λ": {"λ", 12, 1}, - "!": {"!", 11, 1}, - "*": {"*", 10, 2}, - "/": {"/", 10, 2}, - "%": {"%", 10, 2}, - "+": {"+", 9, 2}, - "-": {"-", 9, 2}, - ">>": {">>", 8, 2}, - "<<": {"<<", 8, 2}, - ">": {">", 7, 2}, - "<": {"<", 7, 2}, - ">=": {">=", 7, 2}, - "<=": {"<=", 7, 2}, - "==": {"==", 6, 2}, - "!=": {"!=", 6, 2}, - "&": {"&", 5, 2}, - "^": {"^", 4, 2}, - "|": {"|", 3, 2}, - "&&": {"&&", 2, 2}, - "||": {"||", 1, 2}, + ".": {".", 13, 2}, + "λ": {"λ", 12, 1}, + "!": {"!", 11, 1}, + "*": {"*", 10, 2}, + "/": {"/", 10, 2}, + "%": {"%", 10, 2}, + "+": {"+", 9, 2}, + "-": {"-", 9, 2}, + ">>": {">>", 8, 2}, + "<<": {"<<", 8, 2}, + "&": {"&", 7, 2}, + "^": {"^", 6, 2}, + "|": {"|", 5, 2}, + + ">": {">", 4, 2}, + "<": {"<", 4, 2}, + ">=": {">=", 4, 2}, + "<=": {"<=", 4, 2}, + "==": {"==", 3, 2}, + "!=": {"!=", 3, 2}, + "&&": {"&&", 2, 2}, + "||": {"||", 1, 2}, + "=": {"=", math.MinInt, 2}, ":=": {":=", math.MinInt, 2}, "+=": {"+=", math.MinInt, 2}, @@ -56,7 +58,7 @@ func isComplete(expr *Expression) bool { return true } - if expr.Token.Kind == token.Operator && len(expr.Children) >= numOperands(expr.Token.Text()) { + if expr.Token.Kind == token.Operator && len(expr.Children) == numOperands(expr.Token.Text()) { return true } diff --git a/src/build/scan.go b/src/build/scan.go index 15fdfe9..93149b7 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -114,7 +114,7 @@ func scanFile(path string, functions chan<- *Function) error { break } - if tokens[i].Kind == token.NewLine { + if tokens[i].Kind == token.NewLine || tokens[i].Kind == token.Comment { i++ continue } diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index b2fc798..5388b4b 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -59,15 +59,24 @@ func Tokenize(buffer []byte) List { // Comment case '/': if i+1 >= len(buffer) || buffer[i+1] != '/' { - tokens = append(tokens, Token{Operator, i, buffer[i : i+1]}) + position := i i++ + + for i < len(buffer) && isOperator(buffer[i]) { + i++ + } + + tokens = append(tokens, Token{Operator, position, buffer[position:i]}) continue } + position := i + for i < len(buffer) && buffer[i] != '\n' { i++ } + tokens = append(tokens, Token{Comment, position, buffer[position:i]}) continue // String From 67b3a3d8207dceb03220ff6de03171f0aa33d947 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 29 Jun 2024 11:49:32 +0200 Subject: [PATCH 0197/1012] Cleaned up function type --- src/build/Assignment.go | 2 +- src/build/Execute.go | 155 ++++++++++++++++++------- src/build/Function.go | 199 ++++---------------------------- src/build/FunctionCall.go | 17 +++ src/build/Keyword.go | 36 +----- src/build/Loop.go | 28 +++++ src/build/Result.go | 4 +- src/build/Return.go | 18 +++ src/build/VariableDefinition.go | 8 +- src/build/compile.go | 6 +- src/build/compiler.go | 77 ++++++++++++ src/build/debug.go | 24 ---- src/build/scan.go | 28 ++--- src/cli/Build.go | 2 +- 14 files changed, 310 insertions(+), 294 deletions(-) create mode 100644 src/build/FunctionCall.go create mode 100644 src/build/Loop.go create mode 100644 src/build/Return.go create mode 100644 src/build/compiler.go delete mode 100644 src/build/debug.go diff --git a/src/build/Assignment.go b/src/build/Assignment.go index 895078b..66703da 100644 --- a/src/build/Assignment.go +++ b/src/build/Assignment.go @@ -8,7 +8,7 @@ import ( // CompileAssignment compiles an assignment. func (f *Function) CompileAssignment(expr *expression.Expression) error { name := expr.Children[0].Token.Text() - variable, exists := f.Variables[name] + variable, exists := f.variables[name] if !exists { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) diff --git a/src/build/Execute.go b/src/build/Execute.go index 1312766..42ffeb7 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -3,6 +3,7 @@ package build import ( "strconv" + "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" @@ -16,14 +17,14 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteLeaf(operation, register, value.Token) } - temporary, found := f.CPU.FindFree(f.CPU.General) + temporary, found := f.cpu.FindFree(f.cpu.General) if !found { panic("no free registers") } - f.CPU.Use(temporary) - defer f.CPU.Free(temporary) + f.cpu.Use(temporary) + defer f.cpu.Free(temporary) err := f.ExpressionToRegister(value, temporary) if err != nil { @@ -33,38 +34,12 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteRegisterRegister(operation, register, temporary) } -// ExecuteFunctionCall executes a function call. -func (f *Function) ExecuteFunctionCall(expr *expression.Expression) error { - funcName := expr.Children[0].Token.Text() - parameters := expr.Children[1:] - - if funcName == "syscall" { - err := f.ExpressionsToRegisters(parameters, f.CPU.Syscall) - - if err != nil { - return err - } - - f.Assembler.Syscall() - } else { - err := f.ExpressionsToRegisters(parameters, f.CPU.Call) - - if err != nil { - return err - } - - f.Assembler.Call(funcName) - } - - return nil -} - // ExecuteLeaf performs an operation on a register with the given leaf operand. func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error { switch operand.Kind { case token.Identifier: name := operand.Text() - variable, exists := f.Variables[name] + variable, exists := f.variables[name] if !exists { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) @@ -91,19 +66,19 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { switch operation.Text() { case "+", "+=": - f.Assembler.RegisterNumber(asm.ADD, register, number) + f.assembler.RegisterNumber(asm.ADD, register, number) case "-", "-=": - f.Assembler.RegisterNumber(asm.SUB, register, number) + f.assembler.RegisterNumber(asm.SUB, register, number) case "*", "*=": - f.Assembler.RegisterNumber(asm.MUL, register, number) + f.assembler.RegisterNumber(asm.MUL, register, number) case "/", "/=": - f.Assembler.RegisterNumber(asm.DIV, register, number) + f.assembler.RegisterNumber(asm.DIV, register, number) case "=": - f.Assembler.RegisterNumber(asm.MOVE, register, number) + f.assembler.RegisterNumber(asm.MOVE, register, number) default: return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) @@ -116,20 +91,20 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { switch operation.Text() { case "+", "+=": - f.Assembler.RegisterRegister(asm.ADD, destination, source) + f.assembler.RegisterRegister(asm.ADD, destination, source) case "-", "-=": - f.Assembler.RegisterRegister(asm.SUB, destination, source) + f.assembler.RegisterRegister(asm.SUB, destination, source) case "*", "*=": - f.Assembler.RegisterRegister(asm.MUL, destination, source) + f.assembler.RegisterRegister(asm.MUL, destination, source) case "/", "/=": - f.Assembler.RegisterRegister(asm.DIV, destination, source) + f.assembler.RegisterRegister(asm.DIV, destination, source) case "=": if destination != source { - f.Assembler.RegisterRegister(asm.MOVE, destination, source) + f.assembler.RegisterRegister(asm.MOVE, destination, source) } default: @@ -138,3 +113,103 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cp return nil } + +// ExpressionToRegister moves the result of an expression into the given register. +func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { + operation := root.Token + + if root.IsLeaf() { + return f.TokenToRegister(operation, register) + } + + left := root.Children[0] + right := root.Children[1] + + err := f.ExpressionToRegister(left, register) + + if err != nil { + return err + } + + return f.Execute(operation, register, right) +} + +// ExpressionsToRegisters moves multiple expressions into the specified registers. +func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { + var destinations []cpu.Register + + for i := len(expressions) - 1; i >= 0; i-- { + original := registers[i] + expression := expressions[i] + + if expression.IsLeaf() { + variable, exists := f.variables[expression.Token.Text()] + + if exists && variable.Register == original { + continue + } + } + + register := original + save := !f.cpu.IsFree(register) + + if save { + register = x64.RAX + } + + err := f.ExpressionToRegister(expression, register) + + if err != nil { + return err + } + + if save { + destinations = append(destinations, original) + f.assembler.Register(asm.PUSH, x64.RAX) + } + } + + for i := len(destinations) - 1; i >= 0; i-- { + f.assembler.Register(asm.POP, destinations[i]) + } + + return nil +} + +// TokenToRegister moves a token into a register. +// It only works with identifiers, numbers and strings. +func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { + switch t.Kind { + case token.Identifier: + name := t.Text() + variable, exists := f.variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) + } + + if register != variable.Register { + f.assembler.RegisterRegister(asm.MOVE, register, variable.Register) + } + + f.useVariable(variable) + return nil + + case token.Number: + value := t.Text() + n, err := strconv.Atoi(value) + + if err != nil { + return err + } + + f.assembler.RegisterNumber(asm.MOVE, register, n) + return nil + + case token.String: + return errors.New(errors.NotImplemented, f.File, t.Position) + + default: + return errors.New(errors.InvalidExpression, f.File, t.Position) + } +} diff --git a/src/build/Function.go b/src/build/Function.go index 16a8c8c..2370fc8 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -1,44 +1,26 @@ package build import ( - "fmt" - "strconv" - - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/go/color/ansi" ) // Function represents a function. type Function struct { - Name string - File *fs.File - Body token.List - Variables map[string]*Variable - Assembler asm.Assembler - CPU cpu.CPU - Error error - count struct{ loop int } - debug []debug + Name string + File *fs.File + Body token.List + compiler } // Compile turns a function into machine code. func (f *Function) Compile() { - f.Assembler.Label(f.Name) - err := f.CompileTokens(f.Body) - - if err != nil { - f.Error = err - return - } - - f.Assembler.Return() + f.assembler.Label(f.Name) + f.err = f.CompileTokens(f.Body) + f.assembler.Return() } // CompileTokens compiles a token list. @@ -59,21 +41,19 @@ func (f *Function) CompileTokens(body token.List) error { continue } - if start != -1 { - instruction := body[start:i] + instruction := body[start:i] - if config.Verbose { - f.debug = append(f.debug, debug{ - position: len(f.Assembler.Instructions), - instruction: instruction, - }) - } + if config.Verbose { + f.debug = append(f.debug, debug{ + position: len(f.assembler.Instructions), + source: instruction, + }) + } - err := f.CompileInstruction(instruction) + err := f.CompileInstruction(instruction) - if err != nil { - return err - } + if err != nil { + return err } start = i + 1 @@ -113,152 +93,21 @@ func (f *Function) CompileInstruction(line token.List) error { defer expr.Close() - if isVariableDefinition(expr) { + switch true { + case isVariableDefinition(expr): return f.CompileVariableDefinition(expr) - } - if isAssignment(expr) { + case isAssignment(expr): return f.CompileAssignment(expr) - } - if isFunctionCall(expr) { - return f.ExecuteFunctionCall(expr) - } - - return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) -} - -// ExpressionToRegister moves the result of an expression into the given register. -func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { - operation := root.Token - - if root.IsLeaf() { - return f.TokenToRegister(operation, register) - } - - left := root.Children[0] - right := root.Children[1] - - err := f.ExpressionToRegister(left, register) - - if err != nil { - return err - } - - return f.Execute(operation, register, right) -} - -// ExpressionsToRegisters moves multiple expressions into the specified registers. -func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { - var destinations []cpu.Register - - for i := len(expressions) - 1; i >= 0; i-- { - original := registers[i] - expression := expressions[i] - - if expression.IsLeaf() { - variable, exists := f.Variables[expression.Token.Text()] - - if exists && variable.Register == original { - continue - } - } - - register := original - save := !f.CPU.IsFree(register) - - if save { - register = x64.RAX - } - - err := f.ExpressionToRegister(expression, register) - - if err != nil { - return err - } - - if save { - destinations = append(destinations, original) - f.Assembler.Register(asm.PUSH, x64.RAX) - } - } - - for i := len(destinations) - 1; i >= 0; i-- { - f.Assembler.Register(asm.POP, destinations[i]) - } - - return nil -} - -// TokenToRegister moves a token into a register. -// It only works with identifiers, numbers and strings. -func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { - switch t.Kind { - case token.Identifier: - name := t.Text() - variable, exists := f.Variables[name] - - if !exists { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) - } - - if register != variable.Register { - f.Assembler.RegisterRegister(asm.MOVE, register, variable.Register) - } - - f.useVariable(variable) - return nil - - case token.Number: - value := t.Text() - n, err := strconv.Atoi(value) - - if err != nil { - return err - } - - f.Assembler.RegisterNumber(asm.MOVE, register, n) - return nil - - case token.String: - return errors.New(errors.NotImplemented, f.File, t.Position) + case isFunctionCall(expr): + return f.CompileFunctionCall(expr) default: - return errors.New(errors.InvalidExpression, f.File, t.Position) + return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) } } -// PrintAsm shows the assembly instructions. -func (f *Function) PrintAsm() { - ansi.Dim.Println("╭──────────────────────────────────────╮") - - for i, x := range f.Assembler.Instructions { - instruction := f.instructionAt(i) - - if instruction != nil { - ansi.Dim.Println("├──────────────────────────────────────┤") - } - - ansi.Dim.Print("│ ") - - if x.Mnemonic == asm.LABEL { - ansi.Yellow.Printf("%-36s", x.Data.String()+":") - } else { - ansi.Green.Printf("%-8s", x.Mnemonic.String()) - - if x.Data != nil { - fmt.Printf("%-28s", x.Data.String()) - } else { - fmt.Printf("%-28s", "") - } - } - - ansi.Dim.Print(" │\n") - } - - ansi.Dim.Println("╰──────────────────────────────────────╯") -} - // String returns the function name. func (f *Function) String() string { return f.Name @@ -266,7 +115,7 @@ func (f *Function) String() string { // identifierExists returns true if the identifier has been defined. func (f *Function) identifierExists(name string) bool { - _, exists := f.Variables[name] + _, exists := f.variables[name] return exists } diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go new file mode 100644 index 0000000..06fa966 --- /dev/null +++ b/src/build/FunctionCall.go @@ -0,0 +1,17 @@ +package build + +import "git.akyoto.dev/cli/q/src/build/expression" + +// CompileFunctionCall executes a function call. +func (f *Function) CompileFunctionCall(expr *expression.Expression) error { + funcName := expr.Children[0].Token.Text() + parameters := expr.Children[1:] + + if funcName == "syscall" { + defer f.assembler.Syscall() + return f.ExpressionsToRegisters(parameters, f.cpu.Syscall) + } + + defer f.assembler.Call(funcName) + return f.ExpressionsToRegisters(parameters, f.cpu.Call) +} diff --git a/src/build/Keyword.go b/src/build/Keyword.go index 5913124..80e5ee9 100644 --- a/src/build/Keyword.go +++ b/src/build/Keyword.go @@ -1,46 +1,20 @@ package build import ( - "fmt" - - "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" ) // CompileKeyword compiles an instruction that starts with a keyword. -func (f *Function) CompileKeyword(line token.List) error { - switch line[0].Text() { +func (f *Function) CompileKeyword(tokens token.List) error { + switch tokens[0].Text() { case "return": - if len(line) > 1 { - value := expression.Parse(line[1:]) - defer value.Close() - // TODO: Set the return value - } - - f.Assembler.Return() + return f.CompileReturn(tokens) case "loop": - blockStart := line.IndexKind(token.BlockStart) + 1 - blockEnd := line.LastIndexKind(token.BlockEnd) - - if blockStart == -1 { - return errors.New(errors.MissingBlockStart, f.File, line[0].End()) - } - - if blockEnd == -1 { - return errors.New(errors.MissingBlockEnd, f.File, line[len(line)-1].End()) - } - - loop := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) - f.Assembler.Label(loop) - defer f.Assembler.Jump(loop) - f.count.loop++ - return f.CompileTokens(line[blockStart:blockEnd]) + return f.CompileLoop(tokens) default: - return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) + return errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, f.File, tokens[0].Position) } - - return nil } diff --git a/src/build/Loop.go b/src/build/Loop.go new file mode 100644 index 0000000..41a9b4f --- /dev/null +++ b/src/build/Loop.go @@ -0,0 +1,28 @@ +package build + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// CompileLoop compiles a loop instruction. +func (f *Function) CompileLoop(tokens token.List) error { + blockStart := tokens.IndexKind(token.BlockStart) + 1 + blockEnd := tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return errors.New(errors.MissingBlockStart, f.File, tokens[0].End()) + } + + if blockEnd == -1 { + return errors.New(errors.MissingBlockEnd, f.File, tokens[len(tokens)-1].End()) + } + + loop := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) + f.assembler.Label(loop) + defer f.assembler.Jump(loop) + f.count.loop++ + return f.CompileTokens(tokens[blockStart:blockEnd]) +} diff --git a/src/build/Result.go b/src/build/Result.go index 158f56f..d0d3699 100644 --- a/src/build/Result.go +++ b/src/build/Result.go @@ -31,7 +31,7 @@ func (r *Result) Finalize() ([]byte, []byte) { // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. r.EachFunction(r.Main, map[*Function]bool{}, func(f *Function) { - final.Merge(f.Assembler) + final.Merge(f.assembler) }) code, data := final.Finalize() @@ -44,7 +44,7 @@ func (r *Result) EachFunction(caller *Function, traversed map[*Function]bool, ca call(caller) traversed[caller] = true - for _, x := range caller.Assembler.Instructions { + for _, x := range caller.assembler.Instructions { if x.Mnemonic != asm.CALL { continue } diff --git a/src/build/Return.go b/src/build/Return.go new file mode 100644 index 0000000..63ff276 --- /dev/null +++ b/src/build/Return.go @@ -0,0 +1,18 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// CompileReturn compiles a return instruction. +func (f *Function) CompileReturn(tokens token.List) error { + if len(tokens) > 1 { + value := expression.Parse(tokens[1:]) + defer value.Close() + // TODO: Set the return value + } + + f.assembler.Return() + return nil +} diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index b952bab..560aff4 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -18,7 +18,7 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) } - reg, exists := f.CPU.FindFree(f.CPU.General) + reg, exists := f.cpu.FindFree(f.cpu.General) if !exists { panic("no free registers") @@ -42,8 +42,8 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error func (f *Function) addVariable(variable *Variable) { variable.UsesRemaining = countIdentifier(f.Body, variable.Name) - f.Variables[variable.Name] = variable - f.CPU.Use(variable.Register) + f.variables[variable.Name] = variable + f.cpu.Use(variable.Register) } func (f *Function) useVariable(variable *Variable) { @@ -54,7 +54,7 @@ func (f *Function) useVariable(variable *Variable) { } if variable.UsesRemaining == 0 { - f.CPU.Free(variable.Register) + f.cpu.Free(variable.Register) } } diff --git a/src/build/compile.go b/src/build/compile.go index 3c1b48a..2e1f4b3 100644 --- a/src/build/compile.go +++ b/src/build/compile.go @@ -34,11 +34,11 @@ func compile(functions <-chan *Function, errs <-chan error) (Result, error) { compileFunctions(allFunctions) for _, function := range allFunctions { - if function.Error != nil { - return result, function.Error + if function.err != nil { + return result, function.err } - result.InstructionCount += len(function.Assembler.Instructions) + result.InstructionCount += len(function.assembler.Instructions) } main, exists := allFunctions["main"] diff --git a/src/build/compiler.go b/src/build/compiler.go new file mode 100644 index 0000000..3e727be --- /dev/null +++ b/src/build/compiler.go @@ -0,0 +1,77 @@ +package build + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/go/color/ansi" +) + +// compiler is the data structure we embed in each function to preserve compilation state. +type compiler struct { + assembler asm.Assembler + count counter + cpu cpu.CPU + debug []debug + err error + variables map[string]*Variable +} + +// counter stores how often a certain statement appeared so we can generate a unique label from it. +type counter struct { + loop int +} + +// debug is used to look up the source code at the given position. +type debug struct { + position int + source token.List +} + +// PrintInstructions shows the assembly instructions. +func (c *compiler) PrintInstructions() { + ansi.Dim.Println("╭──────────────────────────────────────╮") + + for i, x := range c.assembler.Instructions { + instruction := c.sourceAt(i) + + if instruction != nil { + ansi.Dim.Println("├──────────────────────────────────────┤") + } + + ansi.Dim.Print("│ ") + + if x.Mnemonic == asm.LABEL { + ansi.Yellow.Printf("%-36s", x.Data.String()+":") + } else { + ansi.Green.Printf("%-8s", x.Mnemonic.String()) + + if x.Data != nil { + fmt.Printf("%-28s", x.Data.String()) + } else { + fmt.Printf("%-28s", "") + } + } + + ansi.Dim.Print(" │\n") + } + + ansi.Dim.Println("╰──────────────────────────────────────╯") +} + +// sourceAt retrieves the source code at the given position or `nil`. +func (c *compiler) sourceAt(position int) token.List { + for _, record := range c.debug { + if record.position == position { + return record.source + } + + if record.position > position { + return nil + } + } + + return nil +} diff --git a/src/build/debug.go b/src/build/debug.go deleted file mode 100644 index 0292457..0000000 --- a/src/build/debug.go +++ /dev/null @@ -1,24 +0,0 @@ -package build - -import "git.akyoto.dev/cli/q/src/build/token" - -// debug is used to look up instructions at a certain index. -type debug struct { - position int - instruction token.List -} - -// instructionAt retrieves the instruction at the given index. -func (f *Function) instructionAt(index int) token.List { - for _, record := range f.debug { - if record.position == index { - return record.instruction - } - - if record.position > index { - return nil - } - } - - return nil -} diff --git a/src/build/scan.go b/src/build/scan.go index 93149b7..b9058e8 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -237,18 +237,20 @@ func scanFile(path string, functions chan<- *Function) error { } function := &Function{ - Name: tokens[nameStart].Text(), - File: file, - Body: tokens[bodyStart:i], - Variables: map[string]*Variable{}, - CPU: cpu.CPU{ - Call: x64.CallRegisters, - General: x64.GeneralRegisters, - Syscall: x64.SyscallRegisters, - Return: x64.ReturnValueRegisters, - }, - Assembler: asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 32), + Name: tokens[nameStart].Text(), + File: file, + Body: tokens[bodyStart:i], + compiler: compiler{ + assembler: asm.Assembler{ + Instructions: make([]asm.Instruction, 0, 32), + }, + cpu: cpu.CPU{ + Call: x64.CallRegisters, + General: x64.GeneralRegisters, + Syscall: x64.SyscallRegisters, + Return: x64.ReturnValueRegisters, + }, + variables: map[string]*Variable{}, }, } @@ -257,7 +259,7 @@ func scanFile(path string, functions chan<- *Function) error { err := expression.EachParameter(parameters, func(tokens token.List) error { if len(tokens) == 1 { name := tokens[0].Text() - register := x64.CallRegisters[len(function.Variables)] + register := x64.CallRegisters[len(function.variables)] variable := &Variable{Name: name, Register: register} function.addVariable(variable) return nil diff --git a/src/cli/Build.go b/src/cli/Build.go index c625081..a8dfee9 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -45,7 +45,7 @@ func Build(args []string) int { if config.Verbose { result.EachFunction(result.Main, map[*build.Function]bool{}, func(f *build.Function) { - f.PrintAsm() + f.PrintInstructions() }) } From 3e47a12ecdaeecf47e47af4a83984bcbeb2cf03a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 29 Jun 2024 11:49:32 +0200 Subject: [PATCH 0198/1012] Cleaned up function type --- src/build/Assignment.go | 2 +- src/build/Execute.go | 155 ++++++++++++++++++------- src/build/Function.go | 199 ++++---------------------------- src/build/FunctionCall.go | 17 +++ src/build/Keyword.go | 36 +----- src/build/Loop.go | 28 +++++ src/build/Result.go | 4 +- src/build/Return.go | 18 +++ src/build/VariableDefinition.go | 8 +- src/build/compile.go | 6 +- src/build/compiler.go | 77 ++++++++++++ src/build/debug.go | 24 ---- src/build/scan.go | 28 ++--- src/cli/Build.go | 2 +- 14 files changed, 310 insertions(+), 294 deletions(-) create mode 100644 src/build/FunctionCall.go create mode 100644 src/build/Loop.go create mode 100644 src/build/Return.go create mode 100644 src/build/compiler.go delete mode 100644 src/build/debug.go diff --git a/src/build/Assignment.go b/src/build/Assignment.go index 895078b..66703da 100644 --- a/src/build/Assignment.go +++ b/src/build/Assignment.go @@ -8,7 +8,7 @@ import ( // CompileAssignment compiles an assignment. func (f *Function) CompileAssignment(expr *expression.Expression) error { name := expr.Children[0].Token.Text() - variable, exists := f.Variables[name] + variable, exists := f.variables[name] if !exists { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) diff --git a/src/build/Execute.go b/src/build/Execute.go index 1312766..42ffeb7 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -3,6 +3,7 @@ package build import ( "strconv" + "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" @@ -16,14 +17,14 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteLeaf(operation, register, value.Token) } - temporary, found := f.CPU.FindFree(f.CPU.General) + temporary, found := f.cpu.FindFree(f.cpu.General) if !found { panic("no free registers") } - f.CPU.Use(temporary) - defer f.CPU.Free(temporary) + f.cpu.Use(temporary) + defer f.cpu.Free(temporary) err := f.ExpressionToRegister(value, temporary) if err != nil { @@ -33,38 +34,12 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteRegisterRegister(operation, register, temporary) } -// ExecuteFunctionCall executes a function call. -func (f *Function) ExecuteFunctionCall(expr *expression.Expression) error { - funcName := expr.Children[0].Token.Text() - parameters := expr.Children[1:] - - if funcName == "syscall" { - err := f.ExpressionsToRegisters(parameters, f.CPU.Syscall) - - if err != nil { - return err - } - - f.Assembler.Syscall() - } else { - err := f.ExpressionsToRegisters(parameters, f.CPU.Call) - - if err != nil { - return err - } - - f.Assembler.Call(funcName) - } - - return nil -} - // ExecuteLeaf performs an operation on a register with the given leaf operand. func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error { switch operand.Kind { case token.Identifier: name := operand.Text() - variable, exists := f.Variables[name] + variable, exists := f.variables[name] if !exists { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) @@ -91,19 +66,19 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { switch operation.Text() { case "+", "+=": - f.Assembler.RegisterNumber(asm.ADD, register, number) + f.assembler.RegisterNumber(asm.ADD, register, number) case "-", "-=": - f.Assembler.RegisterNumber(asm.SUB, register, number) + f.assembler.RegisterNumber(asm.SUB, register, number) case "*", "*=": - f.Assembler.RegisterNumber(asm.MUL, register, number) + f.assembler.RegisterNumber(asm.MUL, register, number) case "/", "/=": - f.Assembler.RegisterNumber(asm.DIV, register, number) + f.assembler.RegisterNumber(asm.DIV, register, number) case "=": - f.Assembler.RegisterNumber(asm.MOVE, register, number) + f.assembler.RegisterNumber(asm.MOVE, register, number) default: return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) @@ -116,20 +91,20 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { switch operation.Text() { case "+", "+=": - f.Assembler.RegisterRegister(asm.ADD, destination, source) + f.assembler.RegisterRegister(asm.ADD, destination, source) case "-", "-=": - f.Assembler.RegisterRegister(asm.SUB, destination, source) + f.assembler.RegisterRegister(asm.SUB, destination, source) case "*", "*=": - f.Assembler.RegisterRegister(asm.MUL, destination, source) + f.assembler.RegisterRegister(asm.MUL, destination, source) case "/", "/=": - f.Assembler.RegisterRegister(asm.DIV, destination, source) + f.assembler.RegisterRegister(asm.DIV, destination, source) case "=": if destination != source { - f.Assembler.RegisterRegister(asm.MOVE, destination, source) + f.assembler.RegisterRegister(asm.MOVE, destination, source) } default: @@ -138,3 +113,103 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cp return nil } + +// ExpressionToRegister moves the result of an expression into the given register. +func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { + operation := root.Token + + if root.IsLeaf() { + return f.TokenToRegister(operation, register) + } + + left := root.Children[0] + right := root.Children[1] + + err := f.ExpressionToRegister(left, register) + + if err != nil { + return err + } + + return f.Execute(operation, register, right) +} + +// ExpressionsToRegisters moves multiple expressions into the specified registers. +func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { + var destinations []cpu.Register + + for i := len(expressions) - 1; i >= 0; i-- { + original := registers[i] + expression := expressions[i] + + if expression.IsLeaf() { + variable, exists := f.variables[expression.Token.Text()] + + if exists && variable.Register == original { + continue + } + } + + register := original + save := !f.cpu.IsFree(register) + + if save { + register = x64.RAX + } + + err := f.ExpressionToRegister(expression, register) + + if err != nil { + return err + } + + if save { + destinations = append(destinations, original) + f.assembler.Register(asm.PUSH, x64.RAX) + } + } + + for i := len(destinations) - 1; i >= 0; i-- { + f.assembler.Register(asm.POP, destinations[i]) + } + + return nil +} + +// TokenToRegister moves a token into a register. +// It only works with identifiers, numbers and strings. +func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { + switch t.Kind { + case token.Identifier: + name := t.Text() + variable, exists := f.variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) + } + + if register != variable.Register { + f.assembler.RegisterRegister(asm.MOVE, register, variable.Register) + } + + f.useVariable(variable) + return nil + + case token.Number: + value := t.Text() + n, err := strconv.Atoi(value) + + if err != nil { + return err + } + + f.assembler.RegisterNumber(asm.MOVE, register, n) + return nil + + case token.String: + return errors.New(errors.NotImplemented, f.File, t.Position) + + default: + return errors.New(errors.InvalidExpression, f.File, t.Position) + } +} diff --git a/src/build/Function.go b/src/build/Function.go index 16a8c8c..2370fc8 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -1,44 +1,26 @@ package build import ( - "fmt" - "strconv" - - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/go/color/ansi" ) // Function represents a function. type Function struct { - Name string - File *fs.File - Body token.List - Variables map[string]*Variable - Assembler asm.Assembler - CPU cpu.CPU - Error error - count struct{ loop int } - debug []debug + Name string + File *fs.File + Body token.List + compiler } // Compile turns a function into machine code. func (f *Function) Compile() { - f.Assembler.Label(f.Name) - err := f.CompileTokens(f.Body) - - if err != nil { - f.Error = err - return - } - - f.Assembler.Return() + f.assembler.Label(f.Name) + f.err = f.CompileTokens(f.Body) + f.assembler.Return() } // CompileTokens compiles a token list. @@ -59,21 +41,19 @@ func (f *Function) CompileTokens(body token.List) error { continue } - if start != -1 { - instruction := body[start:i] + instruction := body[start:i] - if config.Verbose { - f.debug = append(f.debug, debug{ - position: len(f.Assembler.Instructions), - instruction: instruction, - }) - } + if config.Verbose { + f.debug = append(f.debug, debug{ + position: len(f.assembler.Instructions), + source: instruction, + }) + } - err := f.CompileInstruction(instruction) + err := f.CompileInstruction(instruction) - if err != nil { - return err - } + if err != nil { + return err } start = i + 1 @@ -113,152 +93,21 @@ func (f *Function) CompileInstruction(line token.List) error { defer expr.Close() - if isVariableDefinition(expr) { + switch true { + case isVariableDefinition(expr): return f.CompileVariableDefinition(expr) - } - if isAssignment(expr) { + case isAssignment(expr): return f.CompileAssignment(expr) - } - if isFunctionCall(expr) { - return f.ExecuteFunctionCall(expr) - } - - return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) -} - -// ExpressionToRegister moves the result of an expression into the given register. -func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { - operation := root.Token - - if root.IsLeaf() { - return f.TokenToRegister(operation, register) - } - - left := root.Children[0] - right := root.Children[1] - - err := f.ExpressionToRegister(left, register) - - if err != nil { - return err - } - - return f.Execute(operation, register, right) -} - -// ExpressionsToRegisters moves multiple expressions into the specified registers. -func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { - var destinations []cpu.Register - - for i := len(expressions) - 1; i >= 0; i-- { - original := registers[i] - expression := expressions[i] - - if expression.IsLeaf() { - variable, exists := f.Variables[expression.Token.Text()] - - if exists && variable.Register == original { - continue - } - } - - register := original - save := !f.CPU.IsFree(register) - - if save { - register = x64.RAX - } - - err := f.ExpressionToRegister(expression, register) - - if err != nil { - return err - } - - if save { - destinations = append(destinations, original) - f.Assembler.Register(asm.PUSH, x64.RAX) - } - } - - for i := len(destinations) - 1; i >= 0; i-- { - f.Assembler.Register(asm.POP, destinations[i]) - } - - return nil -} - -// TokenToRegister moves a token into a register. -// It only works with identifiers, numbers and strings. -func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { - switch t.Kind { - case token.Identifier: - name := t.Text() - variable, exists := f.Variables[name] - - if !exists { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) - } - - if register != variable.Register { - f.Assembler.RegisterRegister(asm.MOVE, register, variable.Register) - } - - f.useVariable(variable) - return nil - - case token.Number: - value := t.Text() - n, err := strconv.Atoi(value) - - if err != nil { - return err - } - - f.Assembler.RegisterNumber(asm.MOVE, register, n) - return nil - - case token.String: - return errors.New(errors.NotImplemented, f.File, t.Position) + case isFunctionCall(expr): + return f.CompileFunctionCall(expr) default: - return errors.New(errors.InvalidExpression, f.File, t.Position) + return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) } } -// PrintAsm shows the assembly instructions. -func (f *Function) PrintAsm() { - ansi.Dim.Println("╭──────────────────────────────────────╮") - - for i, x := range f.Assembler.Instructions { - instruction := f.instructionAt(i) - - if instruction != nil { - ansi.Dim.Println("├──────────────────────────────────────┤") - } - - ansi.Dim.Print("│ ") - - if x.Mnemonic == asm.LABEL { - ansi.Yellow.Printf("%-36s", x.Data.String()+":") - } else { - ansi.Green.Printf("%-8s", x.Mnemonic.String()) - - if x.Data != nil { - fmt.Printf("%-28s", x.Data.String()) - } else { - fmt.Printf("%-28s", "") - } - } - - ansi.Dim.Print(" │\n") - } - - ansi.Dim.Println("╰──────────────────────────────────────╯") -} - // String returns the function name. func (f *Function) String() string { return f.Name @@ -266,7 +115,7 @@ func (f *Function) String() string { // identifierExists returns true if the identifier has been defined. func (f *Function) identifierExists(name string) bool { - _, exists := f.Variables[name] + _, exists := f.variables[name] return exists } diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go new file mode 100644 index 0000000..06fa966 --- /dev/null +++ b/src/build/FunctionCall.go @@ -0,0 +1,17 @@ +package build + +import "git.akyoto.dev/cli/q/src/build/expression" + +// CompileFunctionCall executes a function call. +func (f *Function) CompileFunctionCall(expr *expression.Expression) error { + funcName := expr.Children[0].Token.Text() + parameters := expr.Children[1:] + + if funcName == "syscall" { + defer f.assembler.Syscall() + return f.ExpressionsToRegisters(parameters, f.cpu.Syscall) + } + + defer f.assembler.Call(funcName) + return f.ExpressionsToRegisters(parameters, f.cpu.Call) +} diff --git a/src/build/Keyword.go b/src/build/Keyword.go index 5913124..80e5ee9 100644 --- a/src/build/Keyword.go +++ b/src/build/Keyword.go @@ -1,46 +1,20 @@ package build import ( - "fmt" - - "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" ) // CompileKeyword compiles an instruction that starts with a keyword. -func (f *Function) CompileKeyword(line token.List) error { - switch line[0].Text() { +func (f *Function) CompileKeyword(tokens token.List) error { + switch tokens[0].Text() { case "return": - if len(line) > 1 { - value := expression.Parse(line[1:]) - defer value.Close() - // TODO: Set the return value - } - - f.Assembler.Return() + return f.CompileReturn(tokens) case "loop": - blockStart := line.IndexKind(token.BlockStart) + 1 - blockEnd := line.LastIndexKind(token.BlockEnd) - - if blockStart == -1 { - return errors.New(errors.MissingBlockStart, f.File, line[0].End()) - } - - if blockEnd == -1 { - return errors.New(errors.MissingBlockEnd, f.File, line[len(line)-1].End()) - } - - loop := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) - f.Assembler.Label(loop) - defer f.Assembler.Jump(loop) - f.count.loop++ - return f.CompileTokens(line[blockStart:blockEnd]) + return f.CompileLoop(tokens) default: - return errors.New(&errors.KeywordNotImplemented{Keyword: line[0].Text()}, f.File, line[0].Position) + return errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, f.File, tokens[0].Position) } - - return nil } diff --git a/src/build/Loop.go b/src/build/Loop.go new file mode 100644 index 0000000..41a9b4f --- /dev/null +++ b/src/build/Loop.go @@ -0,0 +1,28 @@ +package build + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// CompileLoop compiles a loop instruction. +func (f *Function) CompileLoop(tokens token.List) error { + blockStart := tokens.IndexKind(token.BlockStart) + 1 + blockEnd := tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return errors.New(errors.MissingBlockStart, f.File, tokens[0].End()) + } + + if blockEnd == -1 { + return errors.New(errors.MissingBlockEnd, f.File, tokens[len(tokens)-1].End()) + } + + loop := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) + f.assembler.Label(loop) + defer f.assembler.Jump(loop) + f.count.loop++ + return f.CompileTokens(tokens[blockStart:blockEnd]) +} diff --git a/src/build/Result.go b/src/build/Result.go index 158f56f..d0d3699 100644 --- a/src/build/Result.go +++ b/src/build/Result.go @@ -31,7 +31,7 @@ func (r *Result) Finalize() ([]byte, []byte) { // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. r.EachFunction(r.Main, map[*Function]bool{}, func(f *Function) { - final.Merge(f.Assembler) + final.Merge(f.assembler) }) code, data := final.Finalize() @@ -44,7 +44,7 @@ func (r *Result) EachFunction(caller *Function, traversed map[*Function]bool, ca call(caller) traversed[caller] = true - for _, x := range caller.Assembler.Instructions { + for _, x := range caller.assembler.Instructions { if x.Mnemonic != asm.CALL { continue } diff --git a/src/build/Return.go b/src/build/Return.go new file mode 100644 index 0000000..63ff276 --- /dev/null +++ b/src/build/Return.go @@ -0,0 +1,18 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// CompileReturn compiles a return instruction. +func (f *Function) CompileReturn(tokens token.List) error { + if len(tokens) > 1 { + value := expression.Parse(tokens[1:]) + defer value.Close() + // TODO: Set the return value + } + + f.assembler.Return() + return nil +} diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index b952bab..560aff4 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -18,7 +18,7 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) } - reg, exists := f.CPU.FindFree(f.CPU.General) + reg, exists := f.cpu.FindFree(f.cpu.General) if !exists { panic("no free registers") @@ -42,8 +42,8 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error func (f *Function) addVariable(variable *Variable) { variable.UsesRemaining = countIdentifier(f.Body, variable.Name) - f.Variables[variable.Name] = variable - f.CPU.Use(variable.Register) + f.variables[variable.Name] = variable + f.cpu.Use(variable.Register) } func (f *Function) useVariable(variable *Variable) { @@ -54,7 +54,7 @@ func (f *Function) useVariable(variable *Variable) { } if variable.UsesRemaining == 0 { - f.CPU.Free(variable.Register) + f.cpu.Free(variable.Register) } } diff --git a/src/build/compile.go b/src/build/compile.go index 3c1b48a..2e1f4b3 100644 --- a/src/build/compile.go +++ b/src/build/compile.go @@ -34,11 +34,11 @@ func compile(functions <-chan *Function, errs <-chan error) (Result, error) { compileFunctions(allFunctions) for _, function := range allFunctions { - if function.Error != nil { - return result, function.Error + if function.err != nil { + return result, function.err } - result.InstructionCount += len(function.Assembler.Instructions) + result.InstructionCount += len(function.assembler.Instructions) } main, exists := allFunctions["main"] diff --git a/src/build/compiler.go b/src/build/compiler.go new file mode 100644 index 0000000..3e727be --- /dev/null +++ b/src/build/compiler.go @@ -0,0 +1,77 @@ +package build + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/go/color/ansi" +) + +// compiler is the data structure we embed in each function to preserve compilation state. +type compiler struct { + assembler asm.Assembler + count counter + cpu cpu.CPU + debug []debug + err error + variables map[string]*Variable +} + +// counter stores how often a certain statement appeared so we can generate a unique label from it. +type counter struct { + loop int +} + +// debug is used to look up the source code at the given position. +type debug struct { + position int + source token.List +} + +// PrintInstructions shows the assembly instructions. +func (c *compiler) PrintInstructions() { + ansi.Dim.Println("╭──────────────────────────────────────╮") + + for i, x := range c.assembler.Instructions { + instruction := c.sourceAt(i) + + if instruction != nil { + ansi.Dim.Println("├──────────────────────────────────────┤") + } + + ansi.Dim.Print("│ ") + + if x.Mnemonic == asm.LABEL { + ansi.Yellow.Printf("%-36s", x.Data.String()+":") + } else { + ansi.Green.Printf("%-8s", x.Mnemonic.String()) + + if x.Data != nil { + fmt.Printf("%-28s", x.Data.String()) + } else { + fmt.Printf("%-28s", "") + } + } + + ansi.Dim.Print(" │\n") + } + + ansi.Dim.Println("╰──────────────────────────────────────╯") +} + +// sourceAt retrieves the source code at the given position or `nil`. +func (c *compiler) sourceAt(position int) token.List { + for _, record := range c.debug { + if record.position == position { + return record.source + } + + if record.position > position { + return nil + } + } + + return nil +} diff --git a/src/build/debug.go b/src/build/debug.go deleted file mode 100644 index 0292457..0000000 --- a/src/build/debug.go +++ /dev/null @@ -1,24 +0,0 @@ -package build - -import "git.akyoto.dev/cli/q/src/build/token" - -// debug is used to look up instructions at a certain index. -type debug struct { - position int - instruction token.List -} - -// instructionAt retrieves the instruction at the given index. -func (f *Function) instructionAt(index int) token.List { - for _, record := range f.debug { - if record.position == index { - return record.instruction - } - - if record.position > index { - return nil - } - } - - return nil -} diff --git a/src/build/scan.go b/src/build/scan.go index 93149b7..b9058e8 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -237,18 +237,20 @@ func scanFile(path string, functions chan<- *Function) error { } function := &Function{ - Name: tokens[nameStart].Text(), - File: file, - Body: tokens[bodyStart:i], - Variables: map[string]*Variable{}, - CPU: cpu.CPU{ - Call: x64.CallRegisters, - General: x64.GeneralRegisters, - Syscall: x64.SyscallRegisters, - Return: x64.ReturnValueRegisters, - }, - Assembler: asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 32), + Name: tokens[nameStart].Text(), + File: file, + Body: tokens[bodyStart:i], + compiler: compiler{ + assembler: asm.Assembler{ + Instructions: make([]asm.Instruction, 0, 32), + }, + cpu: cpu.CPU{ + Call: x64.CallRegisters, + General: x64.GeneralRegisters, + Syscall: x64.SyscallRegisters, + Return: x64.ReturnValueRegisters, + }, + variables: map[string]*Variable{}, }, } @@ -257,7 +259,7 @@ func scanFile(path string, functions chan<- *Function) error { err := expression.EachParameter(parameters, func(tokens token.List) error { if len(tokens) == 1 { name := tokens[0].Text() - register := x64.CallRegisters[len(function.Variables)] + register := x64.CallRegisters[len(function.variables)] variable := &Variable{Name: name, Register: register} function.addVariable(variable) return nil diff --git a/src/cli/Build.go b/src/cli/Build.go index c625081..a8dfee9 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -45,7 +45,7 @@ func Build(args []string) int { if config.Verbose { result.EachFunction(result.Main, map[*build.Function]bool{}, func(f *build.Function) { - f.PrintAsm() + f.PrintInstructions() }) } From a0edc45e194f0523ed13351e5c9f0396fc040154 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 29 Jun 2024 21:06:59 +0200 Subject: [PATCH 0199/1012] Implemented return values --- examples/hello/hello.q | 19 +++-------- src/build/Execute.go | 57 +++++++++++++++------------------ src/build/FunctionCall.go | 37 ++++++++++++++++----- src/build/Return.go | 18 +++++++---- src/build/SaveRegister.go | 37 +++++++++++++++++++++ src/build/arch/x64/Registers.go | 2 +- src/build/asm/Instructions.go | 4 +++ 7 files changed, 113 insertions(+), 61 deletions(-) create mode 100644 src/build/SaveRegister.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 1da41c1..aace245 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,19 +1,8 @@ -// Comment main() { - address := 4194304 + 1 - length := (0 + 50 - 20) * 10 / 100 - - loop { - print(address, length) - } + x := f(2, 3) + syscall(60, x) } -// Comment -print(address, length) { - write(length-2, address, length) -} - -// Comment -write(fd, address, length) { - syscall(1, fd, address, length) +f(x, y) { + return (x + y) * (x + y) } \ No newline at end of file diff --git a/src/build/Execute.go b/src/build/Execute.go index 42ffeb7..9939aaa 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -3,7 +3,6 @@ package build import ( "strconv" - "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" @@ -17,10 +16,17 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteLeaf(operation, register, value.Token) } - temporary, found := f.cpu.FindFree(f.cpu.General) + var temporary cpu.Register - if !found { - panic("no free registers") + if isFunctionCall(value) { + temporary = f.cpu.Return[0] + } else { + found := false + temporary, found = f.cpu.FindFree(f.cpu.General) + + if !found { + panic("no free registers") + } } f.cpu.Use(temporary) @@ -122,6 +128,20 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp return f.TokenToRegister(operation, register) } + if isFunctionCall(root) { + err := f.CompileFunctionCall(root) + + if err != nil { + return err + } + + if register != f.cpu.Return[0] { + f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Return[0]) + } + + return nil + } + left := root.Children[0] right := root.Children[1] @@ -131,46 +151,21 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp return err } + f.SaveRegister(register) return f.Execute(operation, register, right) } // ExpressionsToRegisters moves multiple expressions into the specified registers. func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { - var destinations []cpu.Register - for i := len(expressions) - 1; i >= 0; i-- { - original := registers[i] expression := expressions[i] - - if expression.IsLeaf() { - variable, exists := f.variables[expression.Token.Text()] - - if exists && variable.Register == original { - continue - } - } - - register := original - save := !f.cpu.IsFree(register) - - if save { - register = x64.RAX - } + register := registers[i] err := f.ExpressionToRegister(expression, register) if err != nil { return err } - - if save { - destinations = append(destinations, original) - f.assembler.Register(asm.PUSH, x64.RAX) - } - } - - for i := len(destinations) - 1; i >= 0; i-- { - f.assembler.Register(asm.POP, destinations[i]) } return nil diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go index 06fa966..99b71ba 100644 --- a/src/build/FunctionCall.go +++ b/src/build/FunctionCall.go @@ -1,17 +1,38 @@ package build -import "git.akyoto.dev/cli/q/src/build/expression" +import ( + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" +) // CompileFunctionCall executes a function call. func (f *Function) CompileFunctionCall(expr *expression.Expression) error { - funcName := expr.Children[0].Token.Text() - parameters := expr.Children[1:] + var ( + funcName = expr.Children[0].Token.Text() + parameters = expr.Children[1:] + isSyscall = funcName == "syscall" + registers []cpu.Register + ) - if funcName == "syscall" { - defer f.assembler.Syscall() - return f.ExpressionsToRegisters(parameters, f.cpu.Syscall) + if isSyscall { + registers = f.cpu.Syscall + } else { + registers = f.cpu.Call } - defer f.assembler.Call(funcName) - return f.ExpressionsToRegisters(parameters, f.cpu.Call) + registers = registers[:len(parameters)] + + for _, register := range registers { + f.SaveRegister(register) + } + + err := f.ExpressionsToRegisters(parameters, registers) + + if isSyscall { + f.assembler.Syscall() + } else { + f.assembler.Call(funcName) + } + + return err } diff --git a/src/build/Return.go b/src/build/Return.go index 63ff276..8ffd8fb 100644 --- a/src/build/Return.go +++ b/src/build/Return.go @@ -7,12 +7,18 @@ import ( // CompileReturn compiles a return instruction. func (f *Function) CompileReturn(tokens token.List) error { - if len(tokens) > 1 { - value := expression.Parse(tokens[1:]) - defer value.Close() - // TODO: Set the return value + defer f.assembler.Return() + + if len(tokens) == 1 { + return nil } - f.assembler.Return() - return nil + value := expression.Parse(tokens[1:]) + + if value == nil { + return nil + } + + defer value.Close() + return f.ExpressionToRegister(value, f.cpu.Return[0]) } diff --git a/src/build/SaveRegister.go b/src/build/SaveRegister.go new file mode 100644 index 0000000..08b7d11 --- /dev/null +++ b/src/build/SaveRegister.go @@ -0,0 +1,37 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// SaveRegister attempts to move a variable occupying this register to another register. +func (f *Function) SaveRegister(register cpu.Register) { + if f.cpu.IsFree(register) { + return + } + + var variable *Variable + + for _, v := range f.variables { + if v.Register == register { + variable = v + break + } + } + + if variable == nil || variable.UsesRemaining == 0 { + return + } + + newRegister, exists := f.cpu.FindFree(f.cpu.General) + + if !exists { + panic("no free registers") + } + + f.assembler.RegisterRegister(asm.MOVE, newRegister, register) + f.cpu.Free(register) + f.cpu.Use(newRegister) + variable.Register = newRegister +} diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go index 44807c4..8104f84 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/build/arch/x64/Registers.go @@ -22,7 +22,7 @@ const ( ) var ( - CallRegisters = []cpu.Register{RDI, RSI, RDX, RCX, R8, R9} + CallRegisters = SyscallRegisters GeneralRegisters = []cpu.Register{RBX, RBP, R12, R13, R14, R15} SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} ReturnValueRegisters = []cpu.Register{RAX, RCX, R11} diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 4856d1e..4126bcf 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -66,6 +66,10 @@ func (a *Assembler) Jump(name string) { // Return returns back to the caller. func (a *Assembler) Return() { + if len(a.Instructions) > 0 && a.Instructions[len(a.Instructions)-1].Mnemonic == RETURN { + return + } + a.Instructions = append(a.Instructions, Instruction{Mnemonic: RETURN}) } From 83640ba590ce4d0ff6728b20d20ab3ddae7c8989 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 29 Jun 2024 21:06:59 +0200 Subject: [PATCH 0200/1012] Implemented return values --- examples/hello/hello.q | 19 +++-------- src/build/Execute.go | 57 +++++++++++++++------------------ src/build/FunctionCall.go | 37 ++++++++++++++++----- src/build/Return.go | 18 +++++++---- src/build/SaveRegister.go | 37 +++++++++++++++++++++ src/build/arch/x64/Registers.go | 2 +- src/build/asm/Instructions.go | 4 +++ 7 files changed, 113 insertions(+), 61 deletions(-) create mode 100644 src/build/SaveRegister.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 1da41c1..aace245 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,19 +1,8 @@ -// Comment main() { - address := 4194304 + 1 - length := (0 + 50 - 20) * 10 / 100 - - loop { - print(address, length) - } + x := f(2, 3) + syscall(60, x) } -// Comment -print(address, length) { - write(length-2, address, length) -} - -// Comment -write(fd, address, length) { - syscall(1, fd, address, length) +f(x, y) { + return (x + y) * (x + y) } \ No newline at end of file diff --git a/src/build/Execute.go b/src/build/Execute.go index 42ffeb7..9939aaa 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -3,7 +3,6 @@ package build import ( "strconv" - "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" @@ -17,10 +16,17 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteLeaf(operation, register, value.Token) } - temporary, found := f.cpu.FindFree(f.cpu.General) + var temporary cpu.Register - if !found { - panic("no free registers") + if isFunctionCall(value) { + temporary = f.cpu.Return[0] + } else { + found := false + temporary, found = f.cpu.FindFree(f.cpu.General) + + if !found { + panic("no free registers") + } } f.cpu.Use(temporary) @@ -122,6 +128,20 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp return f.TokenToRegister(operation, register) } + if isFunctionCall(root) { + err := f.CompileFunctionCall(root) + + if err != nil { + return err + } + + if register != f.cpu.Return[0] { + f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Return[0]) + } + + return nil + } + left := root.Children[0] right := root.Children[1] @@ -131,46 +151,21 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp return err } + f.SaveRegister(register) return f.Execute(operation, register, right) } // ExpressionsToRegisters moves multiple expressions into the specified registers. func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { - var destinations []cpu.Register - for i := len(expressions) - 1; i >= 0; i-- { - original := registers[i] expression := expressions[i] - - if expression.IsLeaf() { - variable, exists := f.variables[expression.Token.Text()] - - if exists && variable.Register == original { - continue - } - } - - register := original - save := !f.cpu.IsFree(register) - - if save { - register = x64.RAX - } + register := registers[i] err := f.ExpressionToRegister(expression, register) if err != nil { return err } - - if save { - destinations = append(destinations, original) - f.assembler.Register(asm.PUSH, x64.RAX) - } - } - - for i := len(destinations) - 1; i >= 0; i-- { - f.assembler.Register(asm.POP, destinations[i]) } return nil diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go index 06fa966..99b71ba 100644 --- a/src/build/FunctionCall.go +++ b/src/build/FunctionCall.go @@ -1,17 +1,38 @@ package build -import "git.akyoto.dev/cli/q/src/build/expression" +import ( + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" +) // CompileFunctionCall executes a function call. func (f *Function) CompileFunctionCall(expr *expression.Expression) error { - funcName := expr.Children[0].Token.Text() - parameters := expr.Children[1:] + var ( + funcName = expr.Children[0].Token.Text() + parameters = expr.Children[1:] + isSyscall = funcName == "syscall" + registers []cpu.Register + ) - if funcName == "syscall" { - defer f.assembler.Syscall() - return f.ExpressionsToRegisters(parameters, f.cpu.Syscall) + if isSyscall { + registers = f.cpu.Syscall + } else { + registers = f.cpu.Call } - defer f.assembler.Call(funcName) - return f.ExpressionsToRegisters(parameters, f.cpu.Call) + registers = registers[:len(parameters)] + + for _, register := range registers { + f.SaveRegister(register) + } + + err := f.ExpressionsToRegisters(parameters, registers) + + if isSyscall { + f.assembler.Syscall() + } else { + f.assembler.Call(funcName) + } + + return err } diff --git a/src/build/Return.go b/src/build/Return.go index 63ff276..8ffd8fb 100644 --- a/src/build/Return.go +++ b/src/build/Return.go @@ -7,12 +7,18 @@ import ( // CompileReturn compiles a return instruction. func (f *Function) CompileReturn(tokens token.List) error { - if len(tokens) > 1 { - value := expression.Parse(tokens[1:]) - defer value.Close() - // TODO: Set the return value + defer f.assembler.Return() + + if len(tokens) == 1 { + return nil } - f.assembler.Return() - return nil + value := expression.Parse(tokens[1:]) + + if value == nil { + return nil + } + + defer value.Close() + return f.ExpressionToRegister(value, f.cpu.Return[0]) } diff --git a/src/build/SaveRegister.go b/src/build/SaveRegister.go new file mode 100644 index 0000000..08b7d11 --- /dev/null +++ b/src/build/SaveRegister.go @@ -0,0 +1,37 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// SaveRegister attempts to move a variable occupying this register to another register. +func (f *Function) SaveRegister(register cpu.Register) { + if f.cpu.IsFree(register) { + return + } + + var variable *Variable + + for _, v := range f.variables { + if v.Register == register { + variable = v + break + } + } + + if variable == nil || variable.UsesRemaining == 0 { + return + } + + newRegister, exists := f.cpu.FindFree(f.cpu.General) + + if !exists { + panic("no free registers") + } + + f.assembler.RegisterRegister(asm.MOVE, newRegister, register) + f.cpu.Free(register) + f.cpu.Use(newRegister) + variable.Register = newRegister +} diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go index 44807c4..8104f84 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/build/arch/x64/Registers.go @@ -22,7 +22,7 @@ const ( ) var ( - CallRegisters = []cpu.Register{RDI, RSI, RDX, RCX, R8, R9} + CallRegisters = SyscallRegisters GeneralRegisters = []cpu.Register{RBX, RBP, R12, R13, R14, R15} SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} ReturnValueRegisters = []cpu.Register{RAX, RCX, R11} diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 4856d1e..4126bcf 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -66,6 +66,10 @@ func (a *Assembler) Jump(name string) { // Return returns back to the caller. func (a *Assembler) Return() { + if len(a.Instructions) > 0 && a.Instructions[len(a.Instructions)-1].Mnemonic == RETURN { + return + } + a.Instructions = append(a.Instructions, Instruction{Mnemonic: RETURN}) } From 1510f8816593801bc1f65be09fb0278cfc5c2a0d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 29 Jun 2024 21:40:14 +0200 Subject: [PATCH 0201/1012] Added tests for examples --- examples/write/write.q | 13 ++++++++ examples_test.go | 67 ++++++++++++++++++++++++++++++++++++++++++ src/build/Result.go | 6 ++++ src/cli/Build.go | 4 +-- 4 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 examples/write/write.q create mode 100644 examples_test.go diff --git a/examples/write/write.q b/examples/write/write.q new file mode 100644 index 0000000..0f343c1 --- /dev/null +++ b/examples/write/write.q @@ -0,0 +1,13 @@ +main() { + address := 4194304 + 1 + length := (0 + 50 - 20) * 10 / 100 + print(address, length) +} + +print(address, length) { + write(length-2, address, length) +} + +write(fd, address, length) { + syscall(1, fd, address, length) +} \ No newline at end of file diff --git a/examples_test.go b/examples_test.go new file mode 100644 index 0000000..faf1103 --- /dev/null +++ b/examples_test.go @@ -0,0 +1,67 @@ +package main_test + +import ( + "os" + "os/exec" + "testing" + + "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/go/assert" +) + +func TestExamples(t *testing.T) { + var examples = []struct { + Name string + ExpectedOutput string + ExpectedExitCode int + }{ + {"hello", "", 25}, + {"write", "ELF", 0}, + } + + for _, example := range examples { + example := example + + t.Run(example.Name, func(t *testing.T) { + runExample(t, example.Name, example.ExpectedOutput, example.ExpectedExitCode) + }) + } +} + +// runExample builds and runs the example to check if the output matches the expected output. +func runExample(t *testing.T, name string, expectedOutput string, expectedExitCode int) { + b := build.New("examples/" + name) + assert.True(t, len(b.Executable()) > 0) + defer os.Remove(b.Executable()) + + t.Run("Compile", func(t *testing.T) { + result, err := b.Run() + assert.Nil(t, err) + + err = result.Write(b.Executable()) + assert.Nil(t, err) + + stat, err := os.Stat(b.Executable()) + assert.Nil(t, err) + assert.True(t, stat.Size() > 0) + }) + + t.Run("Output", func(t *testing.T) { + cmd := exec.Command(b.Executable()) + output, err := cmd.Output() + exitCode := 0 + + if err != nil { + exitError, ok := err.(*exec.ExitError) + + if !ok { + t.Fatal(exitError) + } + + exitCode = exitError.ExitCode() + } + + assert.Equal(t, exitCode, expectedExitCode) + assert.DeepEqual(t, string(output), expectedOutput) + }) +} diff --git a/src/build/Result.go b/src/build/Result.go index d0d3699..6a5e94b 100644 --- a/src/build/Result.go +++ b/src/build/Result.go @@ -63,3 +63,9 @@ func (r *Result) EachFunction(caller *Function, traversed map[*Function]bool, ca r.EachFunction(callee, traversed, call) } } + +// Write write the final executable to disk. +func (r *Result) Write(path string) error { + code, data := r.Finalize() + return Write(path, code, data) +} diff --git a/src/cli/Build.go b/src/cli/Build.go index a8dfee9..99a5118 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -53,9 +53,7 @@ func Build(args []string) int { return 0 } - path := b.Executable() - code, data := result.Finalize() - err = build.Write(path, code, data) + err = result.Write(b.Executable()) if err != nil { fmt.Fprintln(os.Stderr, err) From 6e1af81733389dc49e95fbeb4c753ac72adcb6c2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 29 Jun 2024 21:40:14 +0200 Subject: [PATCH 0202/1012] Added tests for examples --- examples/write/write.q | 13 ++++++++ examples_test.go | 67 ++++++++++++++++++++++++++++++++++++++++++ src/build/Result.go | 6 ++++ src/cli/Build.go | 4 +-- 4 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 examples/write/write.q create mode 100644 examples_test.go diff --git a/examples/write/write.q b/examples/write/write.q new file mode 100644 index 0000000..0f343c1 --- /dev/null +++ b/examples/write/write.q @@ -0,0 +1,13 @@ +main() { + address := 4194304 + 1 + length := (0 + 50 - 20) * 10 / 100 + print(address, length) +} + +print(address, length) { + write(length-2, address, length) +} + +write(fd, address, length) { + syscall(1, fd, address, length) +} \ No newline at end of file diff --git a/examples_test.go b/examples_test.go new file mode 100644 index 0000000..faf1103 --- /dev/null +++ b/examples_test.go @@ -0,0 +1,67 @@ +package main_test + +import ( + "os" + "os/exec" + "testing" + + "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/go/assert" +) + +func TestExamples(t *testing.T) { + var examples = []struct { + Name string + ExpectedOutput string + ExpectedExitCode int + }{ + {"hello", "", 25}, + {"write", "ELF", 0}, + } + + for _, example := range examples { + example := example + + t.Run(example.Name, func(t *testing.T) { + runExample(t, example.Name, example.ExpectedOutput, example.ExpectedExitCode) + }) + } +} + +// runExample builds and runs the example to check if the output matches the expected output. +func runExample(t *testing.T, name string, expectedOutput string, expectedExitCode int) { + b := build.New("examples/" + name) + assert.True(t, len(b.Executable()) > 0) + defer os.Remove(b.Executable()) + + t.Run("Compile", func(t *testing.T) { + result, err := b.Run() + assert.Nil(t, err) + + err = result.Write(b.Executable()) + assert.Nil(t, err) + + stat, err := os.Stat(b.Executable()) + assert.Nil(t, err) + assert.True(t, stat.Size() > 0) + }) + + t.Run("Output", func(t *testing.T) { + cmd := exec.Command(b.Executable()) + output, err := cmd.Output() + exitCode := 0 + + if err != nil { + exitError, ok := err.(*exec.ExitError) + + if !ok { + t.Fatal(exitError) + } + + exitCode = exitError.ExitCode() + } + + assert.Equal(t, exitCode, expectedExitCode) + assert.DeepEqual(t, string(output), expectedOutput) + }) +} diff --git a/src/build/Result.go b/src/build/Result.go index d0d3699..6a5e94b 100644 --- a/src/build/Result.go +++ b/src/build/Result.go @@ -63,3 +63,9 @@ func (r *Result) EachFunction(caller *Function, traversed map[*Function]bool, ca r.EachFunction(callee, traversed, call) } } + +// Write write the final executable to disk. +func (r *Result) Write(path string) error { + code, data := r.Finalize() + return Write(path, code, data) +} diff --git a/src/cli/Build.go b/src/cli/Build.go index a8dfee9..99a5118 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -53,9 +53,7 @@ func Build(args []string) int { return 0 } - path := b.Executable() - code, data := result.Finalize() - err = build.Write(path, code, data) + err = result.Write(b.Executable()) if err != nil { fmt.Fprintln(os.Stderr, err) From b8c011d7423ac2916faef5b7c048b0718c61c8dc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 30 Jun 2024 00:17:14 +0200 Subject: [PATCH 0203/1012] Improved variable lifetime tracking --- bench_test.go | 9 --------- errors_test.go | 1 + examples/write/write.q | 2 +- src/build/Execute.go | 9 +++++++++ src/build/Function.go | 11 +++++++++++ src/build/FunctionCall.go | 5 +++++ src/build/SaveRegister.go | 7 ++++++- src/build/Variable.go | 6 +++--- src/build/VariableDefinition.go | 33 +++++++++++++++++++++++++-------- src/build/config/config.go | 3 ++- src/build/scan.go | 13 ++++++++++++- src/cli/Build.go | 5 ++++- src/cli/Help.go | 17 ++++++++++++----- src/errors/UnusedVariable.go | 13 +++++++++++++ tests/benchmarks/expressions.q | 7 ++++++- tests/errors/InvalidOperator.q | 1 + tests/errors/UnusedVariable.q | 3 +++ 17 files changed, 114 insertions(+), 31 deletions(-) create mode 100644 src/errors/UnusedVariable.go create mode 100644 tests/errors/UnusedVariable.q diff --git a/bench_test.go b/bench_test.go index 4bb3f8b..46b62ed 100644 --- a/bench_test.go +++ b/bench_test.go @@ -24,12 +24,3 @@ func BenchmarkExpressions(b *testing.B) { assert.Nil(b, err) } } - -func BenchmarkHello(b *testing.B) { - compiler := build.New("examples/hello") - - for i := 0; i < b.N; i++ { - _, err := compiler.Run() - assert.Nil(b, err) - } -} diff --git a/errors_test.go b/errors_test.go index c18344e..f4cb7ab 100644 --- a/errors_test.go +++ b/errors_test.go @@ -34,6 +34,7 @@ func TestErrors(t *testing.T) { {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, + {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, } for _, test := range tests { diff --git a/examples/write/write.q b/examples/write/write.q index 0f343c1..c06884e 100644 --- a/examples/write/write.q +++ b/examples/write/write.q @@ -1,6 +1,6 @@ main() { address := 4194304 + 1 - length := (0 + 50 - 20) * 10 / 100 + length := 3 print(address, length) } diff --git a/src/build/Execute.go b/src/build/Execute.go index 9939aaa..8947218 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -4,6 +4,7 @@ import ( "strconv" "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" @@ -12,6 +13,10 @@ import ( // Execute executes an operation on a register with a value operand. func (f *Function) Execute(operation token.Token, register cpu.Register, value *expression.Expression) error { + if config.Verbose { + f.Logf("execute: %s on register %s with value %s", operation.Text(), register, value) + } + if value.IsLeaf() { return f.ExecuteLeaf(operation, register, value.Token) } @@ -122,6 +127,10 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cp // ExpressionToRegister moves the result of an expression into the given register. func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { + if config.Verbose { + f.Logf("%s to register %s", root, register) + } + operation := root.Token if root.IsLeaf() { diff --git a/src/build/Function.go b/src/build/Function.go index 2370fc8..8c271e0 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -1,6 +1,8 @@ package build import ( + "fmt" + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" @@ -44,6 +46,10 @@ func (f *Function) CompileTokens(body token.List) error { instruction := body[start:i] if config.Verbose { + f.Logf("compiling: %s", instruction) + } + + if config.Assembler { f.debug = append(f.debug, debug{ position: len(f.assembler.Instructions), source: instruction, @@ -108,6 +114,11 @@ func (f *Function) CompileInstruction(line token.List) error { } } +// Logf formats a message for verbose output. +func (f *Function) Logf(format string, data ...any) { + fmt.Printf("[%s @ %d] %s\n", f, len(f.assembler.Instructions), fmt.Sprintf(format, data...)) +} + // String returns the function name. func (f *Function) String() string { return f.Name diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go index 99b71ba..8b62e25 100644 --- a/src/build/FunctionCall.go +++ b/src/build/FunctionCall.go @@ -1,6 +1,7 @@ package build import ( + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" ) @@ -28,6 +29,10 @@ func (f *Function) CompileFunctionCall(expr *expression.Expression) error { err := f.ExpressionsToRegisters(parameters, registers) + if config.Verbose { + f.Logf("call: %s", funcName) + } + if isSyscall { f.assembler.Syscall() } else { diff --git a/src/build/SaveRegister.go b/src/build/SaveRegister.go index 08b7d11..d74c95d 100644 --- a/src/build/SaveRegister.go +++ b/src/build/SaveRegister.go @@ -2,6 +2,7 @@ package build import ( "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" ) @@ -20,7 +21,7 @@ func (f *Function) SaveRegister(register cpu.Register) { } } - if variable == nil || variable.UsesRemaining == 0 { + if variable == nil || variable.Alive == 0 { return } @@ -30,6 +31,10 @@ func (f *Function) SaveRegister(register cpu.Register) { panic("no free registers") } + if config.Verbose { + f.Logf("moving %s from %s to %s (alive: %d)", variable.Name, variable.Register, newRegister, variable.Alive) + } + f.assembler.RegisterRegister(asm.MOVE, newRegister, register) f.cpu.Free(register) f.cpu.Use(newRegister) diff --git a/src/build/Variable.go b/src/build/Variable.go index cb6ff43..9d18c37 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -6,7 +6,7 @@ import ( // Variable represents a variable in a function. type Variable struct { - Name string - Register cpu.Register - UsesRemaining int + Name string + Register cpu.Register + Alive int } diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index 560aff4..f0624da 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -1,6 +1,7 @@ package build import ( + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" @@ -18,6 +19,12 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) } + uses := countIdentifier(f.Body, name) - 1 + + if uses == 0 { + return errors.New(&errors.UnusedVariable{Name: name}, f.File, expr.Children[0].Token.Position) + } + reg, exists := f.cpu.FindFree(f.cpu.General) if !exists { @@ -30,30 +37,40 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return err } - variable := &Variable{ + f.addVariable(&Variable{ Name: name, Register: reg, - } + Alive: uses, + }) - f.addVariable(variable) - f.useVariable(variable) return nil } func (f *Function) addVariable(variable *Variable) { - variable.UsesRemaining = countIdentifier(f.Body, variable.Name) + if config.Verbose { + f.Logf("%s occupies %s (alive: %d)", variable.Name, variable.Register, variable.Alive) + } + f.variables[variable.Name] = variable f.cpu.Use(variable.Register) } func (f *Function) useVariable(variable *Variable) { - variable.UsesRemaining-- + variable.Alive-- - if variable.UsesRemaining < 0 { + if config.Verbose { + f.Logf("%s occupying %s was used (alive: %d)", variable.Name, variable.Register, variable.Alive) + } + + if variable.Alive < 0 { panic("incorrect number of variable use calls") } - if variable.UsesRemaining == 0 { + if variable.Alive == 0 { + if config.Verbose { + f.Logf("%s is no longer used, free register: %s", variable.Name, variable.Register) + } + f.cpu.Free(variable.Register) } } diff --git a/src/build/config/config.go b/src/build/config/config.go index baaf72b..d87cf95 100644 --- a/src/build/config/config.go +++ b/src/build/config/config.go @@ -8,5 +8,6 @@ const ( ) var ( - Verbose = false + Assembler = false + Verbose = false ) diff --git a/src/build/scan.go b/src/build/scan.go index b9058e8..695a401 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -260,7 +260,18 @@ func scanFile(path string, functions chan<- *Function) error { if len(tokens) == 1 { name := tokens[0].Text() register := x64.CallRegisters[len(function.variables)] - variable := &Variable{Name: name, Register: register} + uses := countIdentifier(function.Body, name) + + if uses == 0 { + return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) + } + + variable := &Variable{ + Name: name, + Register: register, + Alive: uses, + } + function.addVariable(variable) return nil } diff --git a/src/cli/Build.go b/src/cli/Build.go index 99a5118..8db214f 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -19,6 +19,9 @@ func Build(args []string) int { case "--dry": dry = true + case "--assembler", "-a": + config.Assembler = true + case "--verbose", "-v": config.Verbose = true @@ -43,7 +46,7 @@ func Build(args []string) int { return 1 } - if config.Verbose { + if config.Assembler { result.EachFunction(result.Main, map[*build.Function]bool{}, func(f *build.Function) { f.PrintInstructions() }) diff --git a/src/cli/Help.go b/src/cli/Help.go index cf7c00f..7cf99e4 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -7,10 +7,17 @@ import ( // Help shows the command line argument usage. func Help(w io.Writer, code int) int { - fmt.Fprintln(w, "Usage: q [command] [options]") - fmt.Fprintln(w, "") - fmt.Fprintln(w, " build [directory] [file]") - fmt.Fprintln(w, " help") - fmt.Fprintln(w, " system") + fmt.Fprintln(w, `Usage: q [command] [options] + + commands: + + build [directory | file] + help + system + + build options: + + --assembler, -a Show assembler instructions. + --verbose, -v Show verbose output.`) return code } diff --git a/src/errors/UnusedVariable.go b/src/errors/UnusedVariable.go new file mode 100644 index 0000000..00a5a6f --- /dev/null +++ b/src/errors/UnusedVariable.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// UnusedVariable error is created when a defined variable is never used. +type UnusedVariable struct { + Name string +} + +// Error generates the string representation. +func (err *UnusedVariable) Error() string { + return fmt.Sprintf("Unused variable '%s'", err.Name) +} diff --git a/tests/benchmarks/expressions.q b/tests/benchmarks/expressions.q index 194f47d..aace245 100644 --- a/tests/benchmarks/expressions.q +++ b/tests/benchmarks/expressions.q @@ -1,3 +1,8 @@ main() { - return 1+2*3 + x := f(2, 3) + syscall(60, x) +} + +f(x, y) { + return (x + y) * (x + y) } \ No newline at end of file diff --git a/tests/errors/InvalidOperator.q b/tests/errors/InvalidOperator.q index 7b5d4d2..a97c489 100644 --- a/tests/errors/InvalidOperator.q +++ b/tests/errors/InvalidOperator.q @@ -1,3 +1,4 @@ main() { x := 123 +++ 456 + syscall(60, x) } \ No newline at end of file diff --git a/tests/errors/UnusedVariable.q b/tests/errors/UnusedVariable.q new file mode 100644 index 0000000..f57a9e8 --- /dev/null +++ b/tests/errors/UnusedVariable.q @@ -0,0 +1,3 @@ +main() { + x := 1 +} \ No newline at end of file From 3fe2cd1da2ee049c269abf0dda222e75662c1f61 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 30 Jun 2024 00:17:14 +0200 Subject: [PATCH 0204/1012] Improved variable lifetime tracking --- bench_test.go | 9 --------- errors_test.go | 1 + examples/write/write.q | 2 +- src/build/Execute.go | 9 +++++++++ src/build/Function.go | 11 +++++++++++ src/build/FunctionCall.go | 5 +++++ src/build/SaveRegister.go | 7 ++++++- src/build/Variable.go | 6 +++--- src/build/VariableDefinition.go | 33 +++++++++++++++++++++++++-------- src/build/config/config.go | 3 ++- src/build/scan.go | 13 ++++++++++++- src/cli/Build.go | 5 ++++- src/cli/Help.go | 17 ++++++++++++----- src/errors/UnusedVariable.go | 13 +++++++++++++ tests/benchmarks/expressions.q | 7 ++++++- tests/errors/InvalidOperator.q | 1 + tests/errors/UnusedVariable.q | 3 +++ 17 files changed, 114 insertions(+), 31 deletions(-) create mode 100644 src/errors/UnusedVariable.go create mode 100644 tests/errors/UnusedVariable.q diff --git a/bench_test.go b/bench_test.go index 4bb3f8b..46b62ed 100644 --- a/bench_test.go +++ b/bench_test.go @@ -24,12 +24,3 @@ func BenchmarkExpressions(b *testing.B) { assert.Nil(b, err) } } - -func BenchmarkHello(b *testing.B) { - compiler := build.New("examples/hello") - - for i := 0; i < b.N; i++ { - _, err := compiler.Run() - assert.Nil(b, err) - } -} diff --git a/errors_test.go b/errors_test.go index c18344e..f4cb7ab 100644 --- a/errors_test.go +++ b/errors_test.go @@ -34,6 +34,7 @@ func TestErrors(t *testing.T) { {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, + {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, } for _, test := range tests { diff --git a/examples/write/write.q b/examples/write/write.q index 0f343c1..c06884e 100644 --- a/examples/write/write.q +++ b/examples/write/write.q @@ -1,6 +1,6 @@ main() { address := 4194304 + 1 - length := (0 + 50 - 20) * 10 / 100 + length := 3 print(address, length) } diff --git a/src/build/Execute.go b/src/build/Execute.go index 9939aaa..8947218 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -4,6 +4,7 @@ import ( "strconv" "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" @@ -12,6 +13,10 @@ import ( // Execute executes an operation on a register with a value operand. func (f *Function) Execute(operation token.Token, register cpu.Register, value *expression.Expression) error { + if config.Verbose { + f.Logf("execute: %s on register %s with value %s", operation.Text(), register, value) + } + if value.IsLeaf() { return f.ExecuteLeaf(operation, register, value.Token) } @@ -122,6 +127,10 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cp // ExpressionToRegister moves the result of an expression into the given register. func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { + if config.Verbose { + f.Logf("%s to register %s", root, register) + } + operation := root.Token if root.IsLeaf() { diff --git a/src/build/Function.go b/src/build/Function.go index 2370fc8..8c271e0 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -1,6 +1,8 @@ package build import ( + "fmt" + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" @@ -44,6 +46,10 @@ func (f *Function) CompileTokens(body token.List) error { instruction := body[start:i] if config.Verbose { + f.Logf("compiling: %s", instruction) + } + + if config.Assembler { f.debug = append(f.debug, debug{ position: len(f.assembler.Instructions), source: instruction, @@ -108,6 +114,11 @@ func (f *Function) CompileInstruction(line token.List) error { } } +// Logf formats a message for verbose output. +func (f *Function) Logf(format string, data ...any) { + fmt.Printf("[%s @ %d] %s\n", f, len(f.assembler.Instructions), fmt.Sprintf(format, data...)) +} + // String returns the function name. func (f *Function) String() string { return f.Name diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go index 99b71ba..8b62e25 100644 --- a/src/build/FunctionCall.go +++ b/src/build/FunctionCall.go @@ -1,6 +1,7 @@ package build import ( + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" ) @@ -28,6 +29,10 @@ func (f *Function) CompileFunctionCall(expr *expression.Expression) error { err := f.ExpressionsToRegisters(parameters, registers) + if config.Verbose { + f.Logf("call: %s", funcName) + } + if isSyscall { f.assembler.Syscall() } else { diff --git a/src/build/SaveRegister.go b/src/build/SaveRegister.go index 08b7d11..d74c95d 100644 --- a/src/build/SaveRegister.go +++ b/src/build/SaveRegister.go @@ -2,6 +2,7 @@ package build import ( "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" ) @@ -20,7 +21,7 @@ func (f *Function) SaveRegister(register cpu.Register) { } } - if variable == nil || variable.UsesRemaining == 0 { + if variable == nil || variable.Alive == 0 { return } @@ -30,6 +31,10 @@ func (f *Function) SaveRegister(register cpu.Register) { panic("no free registers") } + if config.Verbose { + f.Logf("moving %s from %s to %s (alive: %d)", variable.Name, variable.Register, newRegister, variable.Alive) + } + f.assembler.RegisterRegister(asm.MOVE, newRegister, register) f.cpu.Free(register) f.cpu.Use(newRegister) diff --git a/src/build/Variable.go b/src/build/Variable.go index cb6ff43..9d18c37 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -6,7 +6,7 @@ import ( // Variable represents a variable in a function. type Variable struct { - Name string - Register cpu.Register - UsesRemaining int + Name string + Register cpu.Register + Alive int } diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index 560aff4..f0624da 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -1,6 +1,7 @@ package build import ( + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" @@ -18,6 +19,12 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) } + uses := countIdentifier(f.Body, name) - 1 + + if uses == 0 { + return errors.New(&errors.UnusedVariable{Name: name}, f.File, expr.Children[0].Token.Position) + } + reg, exists := f.cpu.FindFree(f.cpu.General) if !exists { @@ -30,30 +37,40 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return err } - variable := &Variable{ + f.addVariable(&Variable{ Name: name, Register: reg, - } + Alive: uses, + }) - f.addVariable(variable) - f.useVariable(variable) return nil } func (f *Function) addVariable(variable *Variable) { - variable.UsesRemaining = countIdentifier(f.Body, variable.Name) + if config.Verbose { + f.Logf("%s occupies %s (alive: %d)", variable.Name, variable.Register, variable.Alive) + } + f.variables[variable.Name] = variable f.cpu.Use(variable.Register) } func (f *Function) useVariable(variable *Variable) { - variable.UsesRemaining-- + variable.Alive-- - if variable.UsesRemaining < 0 { + if config.Verbose { + f.Logf("%s occupying %s was used (alive: %d)", variable.Name, variable.Register, variable.Alive) + } + + if variable.Alive < 0 { panic("incorrect number of variable use calls") } - if variable.UsesRemaining == 0 { + if variable.Alive == 0 { + if config.Verbose { + f.Logf("%s is no longer used, free register: %s", variable.Name, variable.Register) + } + f.cpu.Free(variable.Register) } } diff --git a/src/build/config/config.go b/src/build/config/config.go index baaf72b..d87cf95 100644 --- a/src/build/config/config.go +++ b/src/build/config/config.go @@ -8,5 +8,6 @@ const ( ) var ( - Verbose = false + Assembler = false + Verbose = false ) diff --git a/src/build/scan.go b/src/build/scan.go index b9058e8..695a401 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -260,7 +260,18 @@ func scanFile(path string, functions chan<- *Function) error { if len(tokens) == 1 { name := tokens[0].Text() register := x64.CallRegisters[len(function.variables)] - variable := &Variable{Name: name, Register: register} + uses := countIdentifier(function.Body, name) + + if uses == 0 { + return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) + } + + variable := &Variable{ + Name: name, + Register: register, + Alive: uses, + } + function.addVariable(variable) return nil } diff --git a/src/cli/Build.go b/src/cli/Build.go index 99a5118..8db214f 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -19,6 +19,9 @@ func Build(args []string) int { case "--dry": dry = true + case "--assembler", "-a": + config.Assembler = true + case "--verbose", "-v": config.Verbose = true @@ -43,7 +46,7 @@ func Build(args []string) int { return 1 } - if config.Verbose { + if config.Assembler { result.EachFunction(result.Main, map[*build.Function]bool{}, func(f *build.Function) { f.PrintInstructions() }) diff --git a/src/cli/Help.go b/src/cli/Help.go index cf7c00f..7cf99e4 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -7,10 +7,17 @@ import ( // Help shows the command line argument usage. func Help(w io.Writer, code int) int { - fmt.Fprintln(w, "Usage: q [command] [options]") - fmt.Fprintln(w, "") - fmt.Fprintln(w, " build [directory] [file]") - fmt.Fprintln(w, " help") - fmt.Fprintln(w, " system") + fmt.Fprintln(w, `Usage: q [command] [options] + + commands: + + build [directory | file] + help + system + + build options: + + --assembler, -a Show assembler instructions. + --verbose, -v Show verbose output.`) return code } diff --git a/src/errors/UnusedVariable.go b/src/errors/UnusedVariable.go new file mode 100644 index 0000000..00a5a6f --- /dev/null +++ b/src/errors/UnusedVariable.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// UnusedVariable error is created when a defined variable is never used. +type UnusedVariable struct { + Name string +} + +// Error generates the string representation. +func (err *UnusedVariable) Error() string { + return fmt.Sprintf("Unused variable '%s'", err.Name) +} diff --git a/tests/benchmarks/expressions.q b/tests/benchmarks/expressions.q index 194f47d..aace245 100644 --- a/tests/benchmarks/expressions.q +++ b/tests/benchmarks/expressions.q @@ -1,3 +1,8 @@ main() { - return 1+2*3 + x := f(2, 3) + syscall(60, x) +} + +f(x, y) { + return (x + y) * (x + y) } \ No newline at end of file diff --git a/tests/errors/InvalidOperator.q b/tests/errors/InvalidOperator.q index 7b5d4d2..a97c489 100644 --- a/tests/errors/InvalidOperator.q +++ b/tests/errors/InvalidOperator.q @@ -1,3 +1,4 @@ main() { x := 123 +++ 456 + syscall(60, x) } \ No newline at end of file diff --git a/tests/errors/UnusedVariable.q b/tests/errors/UnusedVariable.q new file mode 100644 index 0000000..f57a9e8 --- /dev/null +++ b/tests/errors/UnusedVariable.q @@ -0,0 +1,3 @@ +main() { + x := 1 +} \ No newline at end of file From aba9cf2412f02b9df85523b2cfb7fb8c63bb3610 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 30 Jun 2024 11:41:59 +0200 Subject: [PATCH 0205/1012] Implemented compilation finished events --- README.md | 8 ++++++- src/build/Call.go | 44 +++++++++++++++++++++++++++++++++++++++ src/build/Execute.go | 4 ++++ src/build/Function.go | 6 ++++++ src/build/FunctionCall.go | 43 -------------------------------------- src/build/compile.go | 1 + src/build/compiler.go | 15 +++++++------ src/build/scan.go | 1 + 8 files changed, 72 insertions(+), 50 deletions(-) create mode 100644 src/build/Call.go delete mode 100644 src/build/FunctionCall.go diff --git a/README.md b/README.md index 3f36e89..e5e860f 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,13 @@ q build examples/hello q build examples/hello --dry ``` -To produce verbose output, add the `-v` flag which shows the generated assembly instructions: +Adding the `-a` or `--assembler` flag shows the generated assembly instructions: + +```shell +q build examples/hello -a +``` + +Adding the `-v` or `--verbose` flag shows verbose compiler information: ```shell q build examples/hello -v diff --git a/src/build/Call.go b/src/build/Call.go new file mode 100644 index 0000000..2051499 --- /dev/null +++ b/src/build/Call.go @@ -0,0 +1,44 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/expression" +) + +// CompileFunctionCall executes a function call. +func (f *Function) CompileFunctionCall(expr *expression.Expression) error { + funcName := expr.Children[0].Token.Text() + + if funcName == "syscall" { + return f.CompileSyscall(expr) + } + + function := f.functions[funcName] + + if function != f { + function.Wait() + } + + parameters := expr.Children[1:] + registers := f.cpu.Call[:len(parameters)] + + err := f.ExpressionsToRegisters(parameters, registers) + + if config.Verbose { + f.Logf("call: %s", funcName) + } + + f.assembler.Call(funcName) + f.sideEffects += function.sideEffects + return err +} + +// CompileSyscall executes a syscall. +func (f *Function) CompileSyscall(expr *expression.Expression) error { + parameters := expr.Children[1:] + registers := f.cpu.Syscall[:len(parameters)] + err := f.ExpressionsToRegisters(parameters, registers) + f.assembler.Syscall() + f.sideEffects++ + return err +} diff --git a/src/build/Execute.go b/src/build/Execute.go index 8947218..bd50435 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -166,6 +166,10 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp // ExpressionsToRegisters moves multiple expressions into the specified registers. func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { + for _, register := range registers { + f.SaveRegister(register) + } + for i := len(expressions) - 1; i >= 0; i-- { expression := expressions[i] register := registers[i] diff --git a/src/build/Function.go b/src/build/Function.go index 8c271e0..8853605 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -20,6 +20,7 @@ type Function struct { // Compile turns a function into machine code. func (f *Function) Compile() { + defer close(f.finished) f.assembler.Label(f.Name) f.err = f.CompileTokens(f.Body) f.assembler.Return() @@ -124,6 +125,11 @@ func (f *Function) String() string { return f.Name } +// Wait will block until the compilation finishes. +func (f *Function) Wait() { + <-f.finished +} + // identifierExists returns true if the identifier has been defined. func (f *Function) identifierExists(name string) bool { _, exists := f.variables[name] diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go deleted file mode 100644 index 8b62e25..0000000 --- a/src/build/FunctionCall.go +++ /dev/null @@ -1,43 +0,0 @@ -package build - -import ( - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" -) - -// CompileFunctionCall executes a function call. -func (f *Function) CompileFunctionCall(expr *expression.Expression) error { - var ( - funcName = expr.Children[0].Token.Text() - parameters = expr.Children[1:] - isSyscall = funcName == "syscall" - registers []cpu.Register - ) - - if isSyscall { - registers = f.cpu.Syscall - } else { - registers = f.cpu.Call - } - - registers = registers[:len(parameters)] - - for _, register := range registers { - f.SaveRegister(register) - } - - err := f.ExpressionsToRegisters(parameters, registers) - - if config.Verbose { - f.Logf("call: %s", funcName) - } - - if isSyscall { - f.assembler.Syscall() - } else { - f.assembler.Call(funcName) - } - - return err -} diff --git a/src/build/compile.go b/src/build/compile.go index 2e1f4b3..0430873 100644 --- a/src/build/compile.go +++ b/src/build/compile.go @@ -27,6 +27,7 @@ func compile(functions <-chan *Function, errs <-chan error) (Result, error) { continue } + function.functions = allFunctions allFunctions[function.Name] = function } } diff --git a/src/build/compiler.go b/src/build/compiler.go index 3e727be..19b75a2 100644 --- a/src/build/compiler.go +++ b/src/build/compiler.go @@ -11,12 +11,15 @@ import ( // compiler is the data structure we embed in each function to preserve compilation state. type compiler struct { - assembler asm.Assembler - count counter - cpu cpu.CPU - debug []debug - err error - variables map[string]*Variable + assembler asm.Assembler + count counter + cpu cpu.CPU + debug []debug + err error + variables map[string]*Variable + functions map[string]*Function + sideEffects int + finished chan struct{} } // counter stores how often a certain statement appeared so we can generate a unique label from it. diff --git a/src/build/scan.go b/src/build/scan.go index 695a401..701a824 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -251,6 +251,7 @@ func scanFile(path string, functions chan<- *Function) error { Return: x64.ReturnValueRegisters, }, variables: map[string]*Variable{}, + finished: make(chan struct{}), }, } From 247b82b5290a5d38b6f0172f156cbd9dff75b1c1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 30 Jun 2024 11:41:59 +0200 Subject: [PATCH 0206/1012] Implemented compilation finished events --- README.md | 8 ++++++- src/build/Call.go | 44 +++++++++++++++++++++++++++++++++++++++ src/build/Execute.go | 4 ++++ src/build/Function.go | 6 ++++++ src/build/FunctionCall.go | 43 -------------------------------------- src/build/compile.go | 1 + src/build/compiler.go | 15 +++++++------ src/build/scan.go | 1 + 8 files changed, 72 insertions(+), 50 deletions(-) create mode 100644 src/build/Call.go delete mode 100644 src/build/FunctionCall.go diff --git a/README.md b/README.md index 3f36e89..e5e860f 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,13 @@ q build examples/hello q build examples/hello --dry ``` -To produce verbose output, add the `-v` flag which shows the generated assembly instructions: +Adding the `-a` or `--assembler` flag shows the generated assembly instructions: + +```shell +q build examples/hello -a +``` + +Adding the `-v` or `--verbose` flag shows verbose compiler information: ```shell q build examples/hello -v diff --git a/src/build/Call.go b/src/build/Call.go new file mode 100644 index 0000000..2051499 --- /dev/null +++ b/src/build/Call.go @@ -0,0 +1,44 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/expression" +) + +// CompileFunctionCall executes a function call. +func (f *Function) CompileFunctionCall(expr *expression.Expression) error { + funcName := expr.Children[0].Token.Text() + + if funcName == "syscall" { + return f.CompileSyscall(expr) + } + + function := f.functions[funcName] + + if function != f { + function.Wait() + } + + parameters := expr.Children[1:] + registers := f.cpu.Call[:len(parameters)] + + err := f.ExpressionsToRegisters(parameters, registers) + + if config.Verbose { + f.Logf("call: %s", funcName) + } + + f.assembler.Call(funcName) + f.sideEffects += function.sideEffects + return err +} + +// CompileSyscall executes a syscall. +func (f *Function) CompileSyscall(expr *expression.Expression) error { + parameters := expr.Children[1:] + registers := f.cpu.Syscall[:len(parameters)] + err := f.ExpressionsToRegisters(parameters, registers) + f.assembler.Syscall() + f.sideEffects++ + return err +} diff --git a/src/build/Execute.go b/src/build/Execute.go index 8947218..bd50435 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -166,6 +166,10 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp // ExpressionsToRegisters moves multiple expressions into the specified registers. func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { + for _, register := range registers { + f.SaveRegister(register) + } + for i := len(expressions) - 1; i >= 0; i-- { expression := expressions[i] register := registers[i] diff --git a/src/build/Function.go b/src/build/Function.go index 8c271e0..8853605 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -20,6 +20,7 @@ type Function struct { // Compile turns a function into machine code. func (f *Function) Compile() { + defer close(f.finished) f.assembler.Label(f.Name) f.err = f.CompileTokens(f.Body) f.assembler.Return() @@ -124,6 +125,11 @@ func (f *Function) String() string { return f.Name } +// Wait will block until the compilation finishes. +func (f *Function) Wait() { + <-f.finished +} + // identifierExists returns true if the identifier has been defined. func (f *Function) identifierExists(name string) bool { _, exists := f.variables[name] diff --git a/src/build/FunctionCall.go b/src/build/FunctionCall.go deleted file mode 100644 index 8b62e25..0000000 --- a/src/build/FunctionCall.go +++ /dev/null @@ -1,43 +0,0 @@ -package build - -import ( - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" -) - -// CompileFunctionCall executes a function call. -func (f *Function) CompileFunctionCall(expr *expression.Expression) error { - var ( - funcName = expr.Children[0].Token.Text() - parameters = expr.Children[1:] - isSyscall = funcName == "syscall" - registers []cpu.Register - ) - - if isSyscall { - registers = f.cpu.Syscall - } else { - registers = f.cpu.Call - } - - registers = registers[:len(parameters)] - - for _, register := range registers { - f.SaveRegister(register) - } - - err := f.ExpressionsToRegisters(parameters, registers) - - if config.Verbose { - f.Logf("call: %s", funcName) - } - - if isSyscall { - f.assembler.Syscall() - } else { - f.assembler.Call(funcName) - } - - return err -} diff --git a/src/build/compile.go b/src/build/compile.go index 2e1f4b3..0430873 100644 --- a/src/build/compile.go +++ b/src/build/compile.go @@ -27,6 +27,7 @@ func compile(functions <-chan *Function, errs <-chan error) (Result, error) { continue } + function.functions = allFunctions allFunctions[function.Name] = function } } diff --git a/src/build/compiler.go b/src/build/compiler.go index 3e727be..19b75a2 100644 --- a/src/build/compiler.go +++ b/src/build/compiler.go @@ -11,12 +11,15 @@ import ( // compiler is the data structure we embed in each function to preserve compilation state. type compiler struct { - assembler asm.Assembler - count counter - cpu cpu.CPU - debug []debug - err error - variables map[string]*Variable + assembler asm.Assembler + count counter + cpu cpu.CPU + debug []debug + err error + variables map[string]*Variable + functions map[string]*Function + sideEffects int + finished chan struct{} } // counter stores how often a certain statement appeared so we can generate a unique label from it. diff --git a/src/build/scan.go b/src/build/scan.go index 695a401..701a824 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -251,6 +251,7 @@ func scanFile(path string, functions chan<- *Function) error { Return: x64.ReturnValueRegisters, }, variables: map[string]*Variable{}, + finished: make(chan struct{}), }, } From ef7deb30b71c1110d20a401fcbd979b2d24c05ab Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 30 Jun 2024 12:28:43 +0200 Subject: [PATCH 0207/1012] Implemented definitions --- src/build/Execute.go | 10 +++++++ src/build/Function.go | 12 +++++++++ src/build/Variable.go | 9 ++++++- src/build/VariableDefinition.go | 47 +++++++++++++++++++++++++-------- src/build/compiler.go | 1 + src/build/scan.go | 5 ++-- 6 files changed, 70 insertions(+), 14 deletions(-) diff --git a/src/build/Execute.go b/src/build/Execute.go index bd50435..3bdc61e 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -190,6 +190,16 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { switch t.Kind { case token.Identifier: name := t.Text() + constant, exists := f.definitions[name] + + if exists { + if config.Verbose { + f.Logf("constant %s = %s", constant.Name, constant.Value) + } + + return f.ExpressionToRegister(constant.Value, register) + } + variable, exists := f.variables[name] if !exists { diff --git a/src/build/Function.go b/src/build/Function.go index 8853605..d58afc2 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -133,6 +133,18 @@ func (f *Function) Wait() { // identifierExists returns true if the identifier has been defined. func (f *Function) identifierExists(name string) bool { _, exists := f.variables[name] + + if exists { + return true + } + + _, exists = f.definitions[name] + + if exists { + return true + } + + _, exists = f.functions[name] return exists } diff --git a/src/build/Variable.go b/src/build/Variable.go index 9d18c37..291d2d4 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -2,11 +2,18 @@ package build import ( "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" ) -// Variable represents a variable in a function. +// Variable represents a named register. type Variable struct { Name string Register cpu.Register Alive int } + +// Definitions are single use expressions that don't reside in a register yet. +type Definition struct { + Name string + Value *expression.Expression +} diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index f0624da..58a4844 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -25,25 +25,32 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return errors.New(&errors.UnusedVariable{Name: name}, f.File, expr.Children[0].Token.Position) } - reg, exists := f.cpu.FindFree(f.cpu.General) + value := expr.Children[1] - if !exists { - panic("no free registers") - } + err := value.EachLeaf(func(leaf *expression.Expression) error { + if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) { + return errors.New(&errors.UnknownIdentifier{Name: leaf.Token.Text()}, f.File, leaf.Token.Position) + } - err := f.ExpressionToRegister(expr.Children[1], reg) + return nil + }) if err != nil { return err } - f.addVariable(&Variable{ - Name: name, - Register: reg, - Alive: uses, - }) + if uses == 1 { + expr.RemoveChild(value) - return nil + f.definitions[name] = &Definition{ + Name: name, + Value: value, + } + + return nil + } + + return f.storeVariableInRegister(name, value, uses) } func (f *Function) addVariable(variable *Variable) { @@ -75,6 +82,24 @@ func (f *Function) useVariable(variable *Variable) { } } +func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error { + reg, exists := f.cpu.FindFree(f.cpu.General) + + if !exists { + panic("no free registers") + } + + err := f.ExpressionToRegister(value, reg) + + f.addVariable(&Variable{ + Name: name, + Register: reg, + Alive: uses, + }) + + return err +} + func countIdentifier(tokens token.List, name string) int { count := 0 diff --git a/src/build/compiler.go b/src/build/compiler.go index 19b75a2..60cfec0 100644 --- a/src/build/compiler.go +++ b/src/build/compiler.go @@ -16,6 +16,7 @@ type compiler struct { cpu cpu.CPU debug []debug err error + definitions map[string]*Definition variables map[string]*Variable functions map[string]*Function sideEffects int diff --git a/src/build/scan.go b/src/build/scan.go index 701a824..758bbec 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -250,8 +250,9 @@ func scanFile(path string, functions chan<- *Function) error { Syscall: x64.SyscallRegisters, Return: x64.ReturnValueRegisters, }, - variables: map[string]*Variable{}, - finished: make(chan struct{}), + definitions: map[string]*Definition{}, + variables: map[string]*Variable{}, + finished: make(chan struct{}), }, } From 7e5d45f17dfdd5219342784366dcb582ff62ad39 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 30 Jun 2024 12:28:43 +0200 Subject: [PATCH 0208/1012] Implemented definitions --- src/build/Execute.go | 10 +++++++ src/build/Function.go | 12 +++++++++ src/build/Variable.go | 9 ++++++- src/build/VariableDefinition.go | 47 +++++++++++++++++++++++++-------- src/build/compiler.go | 1 + src/build/scan.go | 5 ++-- 6 files changed, 70 insertions(+), 14 deletions(-) diff --git a/src/build/Execute.go b/src/build/Execute.go index bd50435..3bdc61e 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -190,6 +190,16 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { switch t.Kind { case token.Identifier: name := t.Text() + constant, exists := f.definitions[name] + + if exists { + if config.Verbose { + f.Logf("constant %s = %s", constant.Name, constant.Value) + } + + return f.ExpressionToRegister(constant.Value, register) + } + variable, exists := f.variables[name] if !exists { diff --git a/src/build/Function.go b/src/build/Function.go index 8853605..d58afc2 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -133,6 +133,18 @@ func (f *Function) Wait() { // identifierExists returns true if the identifier has been defined. func (f *Function) identifierExists(name string) bool { _, exists := f.variables[name] + + if exists { + return true + } + + _, exists = f.definitions[name] + + if exists { + return true + } + + _, exists = f.functions[name] return exists } diff --git a/src/build/Variable.go b/src/build/Variable.go index 9d18c37..291d2d4 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -2,11 +2,18 @@ package build import ( "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" ) -// Variable represents a variable in a function. +// Variable represents a named register. type Variable struct { Name string Register cpu.Register Alive int } + +// Definitions are single use expressions that don't reside in a register yet. +type Definition struct { + Name string + Value *expression.Expression +} diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index f0624da..58a4844 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -25,25 +25,32 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return errors.New(&errors.UnusedVariable{Name: name}, f.File, expr.Children[0].Token.Position) } - reg, exists := f.cpu.FindFree(f.cpu.General) + value := expr.Children[1] - if !exists { - panic("no free registers") - } + err := value.EachLeaf(func(leaf *expression.Expression) error { + if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) { + return errors.New(&errors.UnknownIdentifier{Name: leaf.Token.Text()}, f.File, leaf.Token.Position) + } - err := f.ExpressionToRegister(expr.Children[1], reg) + return nil + }) if err != nil { return err } - f.addVariable(&Variable{ - Name: name, - Register: reg, - Alive: uses, - }) + if uses == 1 { + expr.RemoveChild(value) - return nil + f.definitions[name] = &Definition{ + Name: name, + Value: value, + } + + return nil + } + + return f.storeVariableInRegister(name, value, uses) } func (f *Function) addVariable(variable *Variable) { @@ -75,6 +82,24 @@ func (f *Function) useVariable(variable *Variable) { } } +func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error { + reg, exists := f.cpu.FindFree(f.cpu.General) + + if !exists { + panic("no free registers") + } + + err := f.ExpressionToRegister(value, reg) + + f.addVariable(&Variable{ + Name: name, + Register: reg, + Alive: uses, + }) + + return err +} + func countIdentifier(tokens token.List, name string) int { count := 0 diff --git a/src/build/compiler.go b/src/build/compiler.go index 19b75a2..60cfec0 100644 --- a/src/build/compiler.go +++ b/src/build/compiler.go @@ -16,6 +16,7 @@ type compiler struct { cpu cpu.CPU debug []debug err error + definitions map[string]*Definition variables map[string]*Variable functions map[string]*Function sideEffects int diff --git a/src/build/scan.go b/src/build/scan.go index 701a824..758bbec 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -250,8 +250,9 @@ func scanFile(path string, functions chan<- *Function) error { Syscall: x64.SyscallRegisters, Return: x64.ReturnValueRegisters, }, - variables: map[string]*Variable{}, - finished: make(chan struct{}), + definitions: map[string]*Definition{}, + variables: map[string]*Variable{}, + finished: make(chan struct{}), }, } From 3b29d4cdee001207b68418125fc6993989dfe960 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 30 Jun 2024 14:14:24 +0200 Subject: [PATCH 0209/1012] Reorganized functions --- src/build/Call.go | 3 ++ src/build/Execute.go | 52 ---------------------------------- src/build/Function.go | 34 ----------------------- src/build/Instruction.go | 36 ++++++++++++++++++++++++ src/build/Math.go | 60 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 86 deletions(-) create mode 100644 src/build/Instruction.go create mode 100644 src/build/Math.go diff --git a/src/build/Call.go b/src/build/Call.go index 2051499..ee813fc 100644 --- a/src/build/Call.go +++ b/src/build/Call.go @@ -6,6 +6,9 @@ import ( ) // CompileFunctionCall executes a function call. +// All call registers must hold the correct parameter values before the function invocation. +// Registers that are in use must be saved if they are modified by the function. +// After the function call, they must be restored in reverse order. func (f *Function) CompileFunctionCall(expr *expression.Expression) error { funcName := expr.Children[0].Token.Text() diff --git a/src/build/Execute.go b/src/build/Execute.go index 3bdc61e..4903ef7 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -73,58 +73,6 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope return errors.New(errors.NotImplemented, f.File, operation.Position) } -// ExecuteRegisterNumber performs an operation on a register and a number. -func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { - switch operation.Text() { - case "+", "+=": - f.assembler.RegisterNumber(asm.ADD, register, number) - - case "-", "-=": - f.assembler.RegisterNumber(asm.SUB, register, number) - - case "*", "*=": - f.assembler.RegisterNumber(asm.MUL, register, number) - - case "/", "/=": - f.assembler.RegisterNumber(asm.DIV, register, number) - - case "=": - f.assembler.RegisterNumber(asm.MOVE, register, number) - - default: - return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) - } - - return nil -} - -// ExecuteRegisterRegister performs an operation on two registers. -func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { - switch operation.Text() { - case "+", "+=": - f.assembler.RegisterRegister(asm.ADD, destination, source) - - case "-", "-=": - f.assembler.RegisterRegister(asm.SUB, destination, source) - - case "*", "*=": - f.assembler.RegisterRegister(asm.MUL, destination, source) - - case "/", "/=": - f.assembler.RegisterRegister(asm.DIV, destination, source) - - case "=": - if destination != source { - f.assembler.RegisterRegister(asm.MOVE, destination, source) - } - - default: - return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) - } - - return nil -} - // ExpressionToRegister moves the result of an expression into the given register. func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { if config.Verbose { diff --git a/src/build/Function.go b/src/build/Function.go index d58afc2..d615a90 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -7,7 +7,6 @@ import ( "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) // Function represents a function. @@ -82,39 +81,6 @@ func (f *Function) CompileTokens(body token.List) error { return nil } -// CompileInstruction compiles a single instruction. -func (f *Function) CompileInstruction(line token.List) error { - if len(line) == 0 { - return nil - } - - if line[0].Kind == token.Keyword { - return f.CompileKeyword(line) - } - - expr := expression.Parse(line) - - if expr == nil { - return nil - } - - defer expr.Close() - - switch true { - case isVariableDefinition(expr): - return f.CompileVariableDefinition(expr) - - case isAssignment(expr): - return f.CompileAssignment(expr) - - case isFunctionCall(expr): - return f.CompileFunctionCall(expr) - - default: - return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) - } -} - // Logf formats a message for verbose output. func (f *Function) Logf(format string, data ...any) { fmt.Printf("[%s @ %d] %s\n", f, len(f.assembler.Instructions), fmt.Sprintf(format, data...)) diff --git a/src/build/Instruction.go b/src/build/Instruction.go new file mode 100644 index 0000000..c668258 --- /dev/null +++ b/src/build/Instruction.go @@ -0,0 +1,36 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// CompileInstruction compiles a single instruction. +func (f *Function) CompileInstruction(instruction token.List) error { + if instruction[0].Kind == token.Keyword { + return f.CompileKeyword(instruction) + } + + expr := expression.Parse(instruction) + + if expr == nil { + return nil + } + + defer expr.Close() + + switch true { + case isVariableDefinition(expr): + return f.CompileVariableDefinition(expr) + + case isAssignment(expr): + return f.CompileAssignment(expr) + + case isFunctionCall(expr): + return f.CompileFunctionCall(expr) + + default: + return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) + } +} diff --git a/src/build/Math.go b/src/build/Math.go new file mode 100644 index 0000000..b02d42d --- /dev/null +++ b/src/build/Math.go @@ -0,0 +1,60 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// ExecuteRegisterNumber performs an operation on a register and a number. +func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { + switch operation.Text() { + case "+", "+=": + f.assembler.RegisterNumber(asm.ADD, register, number) + + case "-", "-=": + f.assembler.RegisterNumber(asm.SUB, register, number) + + case "*", "*=": + f.assembler.RegisterNumber(asm.MUL, register, number) + + case "/", "/=": + f.assembler.RegisterNumber(asm.DIV, register, number) + + case "=": + f.assembler.RegisterNumber(asm.MOVE, register, number) + + default: + return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) + } + + return nil +} + +// ExecuteRegisterRegister performs an operation on two registers. +func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { + switch operation.Text() { + case "+", "+=": + f.assembler.RegisterRegister(asm.ADD, destination, source) + + case "-", "-=": + f.assembler.RegisterRegister(asm.SUB, destination, source) + + case "*", "*=": + f.assembler.RegisterRegister(asm.MUL, destination, source) + + case "/", "/=": + f.assembler.RegisterRegister(asm.DIV, destination, source) + + case "=": + if destination != source { + f.assembler.RegisterRegister(asm.MOVE, destination, source) + } + + default: + return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) + } + + return nil +} From 27c707b6ff6b53d23b2f8c647628e5d4a48b9583 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 30 Jun 2024 14:14:24 +0200 Subject: [PATCH 0210/1012] Reorganized functions --- src/build/Call.go | 3 ++ src/build/Execute.go | 52 ---------------------------------- src/build/Function.go | 34 ----------------------- src/build/Instruction.go | 36 ++++++++++++++++++++++++ src/build/Math.go | 60 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 86 deletions(-) create mode 100644 src/build/Instruction.go create mode 100644 src/build/Math.go diff --git a/src/build/Call.go b/src/build/Call.go index 2051499..ee813fc 100644 --- a/src/build/Call.go +++ b/src/build/Call.go @@ -6,6 +6,9 @@ import ( ) // CompileFunctionCall executes a function call. +// All call registers must hold the correct parameter values before the function invocation. +// Registers that are in use must be saved if they are modified by the function. +// After the function call, they must be restored in reverse order. func (f *Function) CompileFunctionCall(expr *expression.Expression) error { funcName := expr.Children[0].Token.Text() diff --git a/src/build/Execute.go b/src/build/Execute.go index 3bdc61e..4903ef7 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -73,58 +73,6 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope return errors.New(errors.NotImplemented, f.File, operation.Position) } -// ExecuteRegisterNumber performs an operation on a register and a number. -func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { - switch operation.Text() { - case "+", "+=": - f.assembler.RegisterNumber(asm.ADD, register, number) - - case "-", "-=": - f.assembler.RegisterNumber(asm.SUB, register, number) - - case "*", "*=": - f.assembler.RegisterNumber(asm.MUL, register, number) - - case "/", "/=": - f.assembler.RegisterNumber(asm.DIV, register, number) - - case "=": - f.assembler.RegisterNumber(asm.MOVE, register, number) - - default: - return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) - } - - return nil -} - -// ExecuteRegisterRegister performs an operation on two registers. -func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { - switch operation.Text() { - case "+", "+=": - f.assembler.RegisterRegister(asm.ADD, destination, source) - - case "-", "-=": - f.assembler.RegisterRegister(asm.SUB, destination, source) - - case "*", "*=": - f.assembler.RegisterRegister(asm.MUL, destination, source) - - case "/", "/=": - f.assembler.RegisterRegister(asm.DIV, destination, source) - - case "=": - if destination != source { - f.assembler.RegisterRegister(asm.MOVE, destination, source) - } - - default: - return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) - } - - return nil -} - // ExpressionToRegister moves the result of an expression into the given register. func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { if config.Verbose { diff --git a/src/build/Function.go b/src/build/Function.go index d58afc2..d615a90 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -7,7 +7,6 @@ import ( "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) // Function represents a function. @@ -82,39 +81,6 @@ func (f *Function) CompileTokens(body token.List) error { return nil } -// CompileInstruction compiles a single instruction. -func (f *Function) CompileInstruction(line token.List) error { - if len(line) == 0 { - return nil - } - - if line[0].Kind == token.Keyword { - return f.CompileKeyword(line) - } - - expr := expression.Parse(line) - - if expr == nil { - return nil - } - - defer expr.Close() - - switch true { - case isVariableDefinition(expr): - return f.CompileVariableDefinition(expr) - - case isAssignment(expr): - return f.CompileAssignment(expr) - - case isFunctionCall(expr): - return f.CompileFunctionCall(expr) - - default: - return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) - } -} - // Logf formats a message for verbose output. func (f *Function) Logf(format string, data ...any) { fmt.Printf("[%s @ %d] %s\n", f, len(f.assembler.Instructions), fmt.Sprintf(format, data...)) diff --git a/src/build/Instruction.go b/src/build/Instruction.go new file mode 100644 index 0000000..c668258 --- /dev/null +++ b/src/build/Instruction.go @@ -0,0 +1,36 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// CompileInstruction compiles a single instruction. +func (f *Function) CompileInstruction(instruction token.List) error { + if instruction[0].Kind == token.Keyword { + return f.CompileKeyword(instruction) + } + + expr := expression.Parse(instruction) + + if expr == nil { + return nil + } + + defer expr.Close() + + switch true { + case isVariableDefinition(expr): + return f.CompileVariableDefinition(expr) + + case isAssignment(expr): + return f.CompileAssignment(expr) + + case isFunctionCall(expr): + return f.CompileFunctionCall(expr) + + default: + return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) + } +} diff --git a/src/build/Math.go b/src/build/Math.go new file mode 100644 index 0000000..b02d42d --- /dev/null +++ b/src/build/Math.go @@ -0,0 +1,60 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// ExecuteRegisterNumber performs an operation on a register and a number. +func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { + switch operation.Text() { + case "+", "+=": + f.assembler.RegisterNumber(asm.ADD, register, number) + + case "-", "-=": + f.assembler.RegisterNumber(asm.SUB, register, number) + + case "*", "*=": + f.assembler.RegisterNumber(asm.MUL, register, number) + + case "/", "/=": + f.assembler.RegisterNumber(asm.DIV, register, number) + + case "=": + f.assembler.RegisterNumber(asm.MOVE, register, number) + + default: + return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) + } + + return nil +} + +// ExecuteRegisterRegister performs an operation on two registers. +func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { + switch operation.Text() { + case "+", "+=": + f.assembler.RegisterRegister(asm.ADD, destination, source) + + case "-", "-=": + f.assembler.RegisterRegister(asm.SUB, destination, source) + + case "*", "*=": + f.assembler.RegisterRegister(asm.MUL, destination, source) + + case "/", "/=": + f.assembler.RegisterRegister(asm.DIV, destination, source) + + case "=": + if destination != source { + f.assembler.RegisterRegister(asm.MOVE, destination, source) + } + + default: + return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) + } + + return nil +} From 8453273d73f1f799a5c24308c8eb86e863df2db0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 30 Jun 2024 22:54:59 +0200 Subject: [PATCH 0211/1012] Implemented an abstract syntax tree --- src/build/AST.go | 87 +++++++++++++++++++ src/build/Call.go | 21 +++++ src/build/Execute.go | 108 ------------------------ src/build/ExpressionToRegister.go | 47 +++++++++++ src/build/Function.go | 90 ++++++++++---------- src/build/Instruction.go | 66 +++++++++------ src/build/Keyword.go | 20 ----- src/build/Loop.go | 24 ++---- src/build/Return.go | 16 +--- src/build/TokenToRegister.go | 59 +++++++++++++ src/build/Variable.go | 2 +- src/build/VariableDefinition.go | 17 ++-- src/build/asm/Instruction.go | 2 +- src/build/asm/Pointer.go | 2 +- src/build/ast/AST.go | 36 ++++++++ src/build/compiler.go | 16 ++-- src/build/expression/Expression.go | 19 ++--- src/build/expression/Expression_test.go | 2 +- src/build/expression/Operator.go | 20 ++--- src/build/expression/Parse.go | 2 +- src/build/expression/bench_test.go | 3 +- src/build/expression/pool.go | 9 -- src/build/fs/File.go | 2 +- src/build/keyword/Keyword.go | 12 +++ src/build/token/Keywords.go | 7 -- src/build/token/Token.go | 4 +- src/build/token/Tokenize.go | 42 ++++----- src/errors/Error.go | 2 +- 28 files changed, 422 insertions(+), 315 deletions(-) create mode 100644 src/build/AST.go create mode 100644 src/build/ExpressionToRegister.go delete mode 100644 src/build/Keyword.go create mode 100644 src/build/TokenToRegister.go create mode 100644 src/build/ast/AST.go delete mode 100644 src/build/expression/pool.go create mode 100644 src/build/keyword/Keyword.go delete mode 100644 src/build/token/Keywords.go diff --git a/src/build/AST.go b/src/build/AST.go new file mode 100644 index 0000000..b12cc5e --- /dev/null +++ b/src/build/AST.go @@ -0,0 +1,87 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/keyword" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// toAST generates an AST from a list of tokens. +func (f *Function) toAST(tokens token.List) (ast.AST, error) { + tree := make(ast.AST, 0, len(tokens)/64) + + err := EachInstruction(tokens, func(instruction token.List) error { + node, err := f.toASTNode(instruction) + + if err == nil && node != nil { + tree = append(tree, node) + } + + return err + }) + + return tree, err +} + +// toASTNode generates an AST node from an instruction. +func (f *Function) toASTNode(tokens token.List) (ast.Node, error) { + if tokens[0].Kind == token.Keyword { + switch tokens[0].Text() { + case keyword.Return: + value := expression.Parse(tokens[1:]) + return &ast.Return{Value: value}, nil + + case keyword.Loop: + blockStart := tokens.IndexKind(token.BlockStart) + 1 + blockEnd := tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return nil, errors.New(errors.MissingBlockStart, f.File, tokens[0].End()) + } + + if blockEnd == -1 { + return nil, errors.New(errors.MissingBlockEnd, f.File, tokens[len(tokens)-1].End()) + } + + tree, err := f.toAST(tokens[blockStart:blockEnd]) + return &ast.Loop{Body: tree}, err + + default: + return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, f.File, tokens[0].Position) + } + } + + expr := expression.Parse(tokens) + + if expr == nil { + return nil, nil + } + + switch { + case isVariableDefinition(expr): + if len(expr.Children) < 2 { + return nil, errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) + } + + name := expr.Children[0].Token + value := expr.Children[1] + return &ast.Define{Name: name, Value: value}, nil + + case isAssignment(expr): + if len(expr.Children) < 2 { + return nil, errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) + } + + name := expr.Children[0].Token + value := expr.Children[1] + return &ast.Assign{Name: name, Value: value}, nil + + case isFunctionCall(expr): + return &ast.Call{Expression: expr}, nil + + default: + return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) + } +} diff --git a/src/build/Call.go b/src/build/Call.go index ee813fc..44df7c9 100644 --- a/src/build/Call.go +++ b/src/build/Call.go @@ -2,6 +2,7 @@ package build import ( "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" ) @@ -45,3 +46,23 @@ func (f *Function) CompileSyscall(expr *expression.Expression) error { f.sideEffects++ return err } + +// ExpressionsToRegisters moves multiple expressions into the specified registers. +func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { + for _, register := range registers { + f.SaveRegister(register) + } + + for i := len(expressions) - 1; i >= 0; i-- { + expression := expressions[i] + register := registers[i] + + err := f.ExpressionToRegister(expression, register) + + if err != nil { + return err + } + } + + return nil +} diff --git a/src/build/Execute.go b/src/build/Execute.go index 4903ef7..6a96ce8 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -3,7 +3,6 @@ package build import ( "strconv" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" @@ -72,110 +71,3 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope return errors.New(errors.NotImplemented, f.File, operation.Position) } - -// ExpressionToRegister moves the result of an expression into the given register. -func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { - if config.Verbose { - f.Logf("%s to register %s", root, register) - } - - operation := root.Token - - if root.IsLeaf() { - return f.TokenToRegister(operation, register) - } - - if isFunctionCall(root) { - err := f.CompileFunctionCall(root) - - if err != nil { - return err - } - - if register != f.cpu.Return[0] { - f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Return[0]) - } - - return nil - } - - left := root.Children[0] - right := root.Children[1] - - err := f.ExpressionToRegister(left, register) - - if err != nil { - return err - } - - f.SaveRegister(register) - return f.Execute(operation, register, right) -} - -// ExpressionsToRegisters moves multiple expressions into the specified registers. -func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { - for _, register := range registers { - f.SaveRegister(register) - } - - for i := len(expressions) - 1; i >= 0; i-- { - expression := expressions[i] - register := registers[i] - - err := f.ExpressionToRegister(expression, register) - - if err != nil { - return err - } - } - - return nil -} - -// TokenToRegister moves a token into a register. -// It only works with identifiers, numbers and strings. -func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { - switch t.Kind { - case token.Identifier: - name := t.Text() - constant, exists := f.definitions[name] - - if exists { - if config.Verbose { - f.Logf("constant %s = %s", constant.Name, constant.Value) - } - - return f.ExpressionToRegister(constant.Value, register) - } - - variable, exists := f.variables[name] - - if !exists { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) - } - - if register != variable.Register { - f.assembler.RegisterRegister(asm.MOVE, register, variable.Register) - } - - f.useVariable(variable) - return nil - - case token.Number: - value := t.Text() - n, err := strconv.Atoi(value) - - if err != nil { - return err - } - - f.assembler.RegisterNumber(asm.MOVE, register, n) - return nil - - case token.String: - return errors.New(errors.NotImplemented, f.File, t.Position) - - default: - return errors.New(errors.InvalidExpression, f.File, t.Position) - } -} diff --git a/src/build/ExpressionToRegister.go b/src/build/ExpressionToRegister.go new file mode 100644 index 0000000..9a6f09d --- /dev/null +++ b/src/build/ExpressionToRegister.go @@ -0,0 +1,47 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" +) + +// ExpressionToRegister moves the result of an expression into the given register. +func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { + if config.Verbose { + f.Logf("%s to register %s", root, register) + } + + operation := root.Token + + if root.IsLeaf() { + return f.TokenToRegister(operation, register) + } + + if isFunctionCall(root) { + err := f.CompileFunctionCall(root) + + if err != nil { + return err + } + + if register != f.cpu.Return[0] { + f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Return[0]) + } + + return nil + } + + left := root.Children[0] + right := root.Children[1] + + err := f.ExpressionToRegister(left, register) + + if err != nil { + return err + } + + f.SaveRegister(register) + return f.Execute(operation, register, right) +} diff --git a/src/build/Function.go b/src/build/Function.go index d615a90..39bf0c3 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -3,6 +3,7 @@ package build import ( "fmt" + "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" @@ -11,8 +12,8 @@ import ( // Function represents a function. type Function struct { - Name string File *fs.File + Name string Body token.List compiler } @@ -26,61 +27,60 @@ func (f *Function) Compile() { } // CompileTokens compiles a token list. -func (f *Function) CompileTokens(body token.List) error { - start := 0 - groupLevel := 0 - blockLevel := 0 +func (f *Function) CompileTokens(tokens token.List) error { + tree, err := f.toAST(tokens) - for i, t := range body { - if start == i && t.Kind == token.NewLine { - start = i + 1 - continue + if err != nil { + return err + } + + return f.CompileAST(tree) +} + +// CompileAST compiles an abstract syntax tree. +func (f *Function) CompileAST(tree ast.AST) error { + for _, node := range tree { + if config.Verbose { + f.Logf("%T %s", node, node) } - switch t.Kind { - case token.NewLine: - if groupLevel > 0 || blockLevel > 0 { - continue - } + if config.Assembler { + f.debug = append(f.debug, debug{ + position: len(f.assembler.Instructions), + source: node, + }) + } - instruction := body[start:i] + err := f.CompileNode(node) - if config.Verbose { - f.Logf("compiling: %s", instruction) - } - - if config.Assembler { - f.debug = append(f.debug, debug{ - position: len(f.assembler.Instructions), - source: instruction, - }) - } - - err := f.CompileInstruction(instruction) - - if err != nil { - return err - } - - start = i + 1 - - case token.GroupStart: - groupLevel++ - - case token.GroupEnd: - groupLevel-- - - case token.BlockStart: - blockLevel++ - - case token.BlockEnd: - blockLevel-- + if err != nil { + return err } } return nil } +// CompileNode compiles a node in the AST. +func (f *Function) CompileNode(node ast.Node) error { + switch node := node.(type) { + case *ast.Call: + return f.CompileFunctionCall(node.Expression) + + case *ast.Define: + return f.CompileVariableDefinition(node) + + case *ast.Return: + return f.CompileReturn(node) + + case *ast.Loop: + return f.CompileLoop(node) + + default: + panic("Unknown AST type") + } +} + // Logf formats a message for verbose output. func (f *Function) Logf(format string, data ...any) { fmt.Printf("[%s @ %d] %s\n", f, len(f.assembler.Instructions), fmt.Sprintf(format, data...)) diff --git a/src/build/Instruction.go b/src/build/Instruction.go index c668258..944ab92 100644 --- a/src/build/Instruction.go +++ b/src/build/Instruction.go @@ -1,36 +1,48 @@ package build import ( - "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) -// CompileInstruction compiles a single instruction. -func (f *Function) CompileInstruction(instruction token.List) error { - if instruction[0].Kind == token.Keyword { - return f.CompileKeyword(instruction) +// EachInstruction calls the function on each instruction. +func EachInstruction(body token.List, call func(token.List) error) error { + start := 0 + groupLevel := 0 + blockLevel := 0 + + for i, t := range body { + if start == i && t.Kind == token.NewLine { + start = i + 1 + continue + } + + switch t.Kind { + case token.NewLine: + if groupLevel > 0 || blockLevel > 0 { + continue + } + + err := call(body[start:i]) + + if err != nil { + return err + } + + start = i + 1 + + case token.GroupStart: + groupLevel++ + + case token.GroupEnd: + groupLevel-- + + case token.BlockStart: + blockLevel++ + + case token.BlockEnd: + blockLevel-- + } } - expr := expression.Parse(instruction) - - if expr == nil { - return nil - } - - defer expr.Close() - - switch true { - case isVariableDefinition(expr): - return f.CompileVariableDefinition(expr) - - case isAssignment(expr): - return f.CompileAssignment(expr) - - case isFunctionCall(expr): - return f.CompileFunctionCall(expr) - - default: - return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) - } + return nil } diff --git a/src/build/Keyword.go b/src/build/Keyword.go deleted file mode 100644 index 80e5ee9..0000000 --- a/src/build/Keyword.go +++ /dev/null @@ -1,20 +0,0 @@ -package build - -import ( - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" -) - -// CompileKeyword compiles an instruction that starts with a keyword. -func (f *Function) CompileKeyword(tokens token.List) error { - switch tokens[0].Text() { - case "return": - return f.CompileReturn(tokens) - - case "loop": - return f.CompileLoop(tokens) - - default: - return errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, f.File, tokens[0].Position) - } -} diff --git a/src/build/Loop.go b/src/build/Loop.go index 41a9b4f..2165eca 100644 --- a/src/build/Loop.go +++ b/src/build/Loop.go @@ -3,26 +3,14 @@ package build import ( "fmt" - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/build/ast" ) // CompileLoop compiles a loop instruction. -func (f *Function) CompileLoop(tokens token.List) error { - blockStart := tokens.IndexKind(token.BlockStart) + 1 - blockEnd := tokens.LastIndexKind(token.BlockEnd) - - if blockStart == -1 { - return errors.New(errors.MissingBlockStart, f.File, tokens[0].End()) - } - - if blockEnd == -1 { - return errors.New(errors.MissingBlockEnd, f.File, tokens[len(tokens)-1].End()) - } - - loop := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) - f.assembler.Label(loop) - defer f.assembler.Jump(loop) +func (f *Function) CompileLoop(loop *ast.Loop) error { + label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) + f.assembler.Label(label) + defer f.assembler.Jump(label) f.count.loop++ - return f.CompileTokens(tokens[blockStart:blockEnd]) + return f.CompileAST(loop.Body) } diff --git a/src/build/Return.go b/src/build/Return.go index 8ffd8fb..c661fc7 100644 --- a/src/build/Return.go +++ b/src/build/Return.go @@ -1,24 +1,16 @@ package build import ( - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/ast" ) // CompileReturn compiles a return instruction. -func (f *Function) CompileReturn(tokens token.List) error { +func (f *Function) CompileReturn(node *ast.Return) error { defer f.assembler.Return() - if len(tokens) == 1 { + if node.Value == nil { return nil } - value := expression.Parse(tokens[1:]) - - if value == nil { - return nil - } - - defer value.Close() - return f.ExpressionToRegister(value, f.cpu.Return[0]) + return f.ExpressionToRegister(node.Value, f.cpu.Return[0]) } diff --git a/src/build/TokenToRegister.go b/src/build/TokenToRegister.go new file mode 100644 index 0000000..6bef4cb --- /dev/null +++ b/src/build/TokenToRegister.go @@ -0,0 +1,59 @@ +package build + +import ( + "strconv" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// TokenToRegister moves a token into a register. +// It only works with identifiers, numbers and strings. +func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { + switch t.Kind { + case token.Identifier: + name := t.Text() + constant, exists := f.definitions[name] + + if exists { + if config.Verbose { + f.Logf("constant %s = %s", constant.Name, constant.Value) + } + + return f.ExpressionToRegister(constant.Value, register) + } + + variable, exists := f.variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) + } + + if register != variable.Register { + f.assembler.RegisterRegister(asm.MOVE, register, variable.Register) + } + + f.useVariable(variable) + return nil + + case token.Number: + value := t.Text() + n, err := strconv.Atoi(value) + + if err != nil { + return err + } + + f.assembler.RegisterNumber(asm.MOVE, register, n) + return nil + + case token.String: + return errors.New(errors.NotImplemented, f.File, t.Position) + + default: + return errors.New(errors.InvalidExpression, f.File, t.Position) + } +} diff --git a/src/build/Variable.go b/src/build/Variable.go index 291d2d4..c81b850 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -14,6 +14,6 @@ type Variable struct { // Definitions are single use expressions that don't reside in a register yet. type Definition struct { - Name string Value *expression.Expression + Name string } diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index 58a4844..917f12f 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -1,6 +1,7 @@ package build import ( + "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" @@ -8,24 +9,20 @@ import ( ) // CompileVariableDefinition compiles a variable definition. -func (f *Function) CompileVariableDefinition(expr *expression.Expression) error { - if len(expr.Children) < 2 { - return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) - } - - name := expr.Children[0].Token.Text() +func (f *Function) CompileVariableDefinition(node *ast.Define) error { + name := node.Name.Text() if f.identifierExists(name) { - return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) + return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, node.Name.Position) } uses := countIdentifier(f.Body, name) - 1 if uses == 0 { - return errors.New(&errors.UnusedVariable{Name: name}, f.File, expr.Children[0].Token.Position) + return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) } - value := expr.Children[1] + value := node.Value err := value.EachLeaf(func(leaf *expression.Expression) error { if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) { @@ -40,8 +37,6 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error } if uses == 1 { - expr.RemoveChild(value) - f.definitions[name] = &Definition{ Name: name, Value: value, diff --git a/src/build/asm/Instruction.go b/src/build/asm/Instruction.go index 5b2bbce..2447bae 100644 --- a/src/build/asm/Instruction.go +++ b/src/build/asm/Instruction.go @@ -4,6 +4,6 @@ import "fmt" // Instruction represents a single instruction which can be converted to machine code. type Instruction struct { - Mnemonic Mnemonic Data fmt.Stringer + Mnemonic Mnemonic } diff --git a/src/build/asm/Pointer.go b/src/build/asm/Pointer.go index 9be746f..4e33a63 100644 --- a/src/build/asm/Pointer.go +++ b/src/build/asm/Pointer.go @@ -7,7 +7,7 @@ type Address = uint32 // Position: The machine code offset where the address was inserted. // Resolve: The function that will return the final address. type Pointer struct { + Resolve func() Address Position Address Size uint8 - Resolve func() Address } diff --git a/src/build/ast/AST.go b/src/build/ast/AST.go new file mode 100644 index 0000000..2d8314a --- /dev/null +++ b/src/build/ast/AST.go @@ -0,0 +1,36 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +type Node interface{} +type AST []Node + +type Assign struct { + Value *expression.Expression + Name token.Token +} + +type Call struct { + Expression *expression.Expression +} + +type Define struct { + Value *expression.Expression + Name token.Token +} + +type If struct { + Condition *expression.Expression + Body AST +} + +type Loop struct { + Body AST +} + +type Return struct { + Value *expression.Expression +} diff --git a/src/build/compiler.go b/src/build/compiler.go index 60cfec0..ac28b37 100644 --- a/src/build/compiler.go +++ b/src/build/compiler.go @@ -4,23 +4,23 @@ import ( "fmt" "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/go/color/ansi" ) // compiler is the data structure we embed in each function to preserve compilation state. type compiler struct { - assembler asm.Assembler - count counter - cpu cpu.CPU - debug []debug err error definitions map[string]*Definition variables map[string]*Variable functions map[string]*Function - sideEffects int finished chan struct{} + assembler asm.Assembler + debug []debug + cpu cpu.CPU + count counter + sideEffects int } // counter stores how often a certain statement appeared so we can generate a unique label from it. @@ -30,8 +30,8 @@ type counter struct { // debug is used to look up the source code at the given position. type debug struct { + source ast.Node position int - source token.List } // PrintInstructions shows the assembly instructions. @@ -66,7 +66,7 @@ func (c *compiler) PrintInstructions() { } // sourceAt retrieves the source code at the given position or `nil`. -func (c *compiler) sourceAt(position int) token.List { +func (c *compiler) sourceAt(position int) ast.Node { for _, record := range c.debug { if record.position == position { return record.source diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 7776c35..1d2efa8 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -8,22 +8,22 @@ import ( // Expression is a binary tree with an operator on each node. type Expression struct { - Token token.Token Parent *Expression Children []*Expression - Precedence int + Token token.Token + Precedence int8 } // New creates a new expression. func New() *Expression { - return pool.Get().(*Expression) + return &Expression{} } // NewLeaf creates a new leaf node. func NewLeaf(t token.Token) *Expression { - expr := New() - expr.Token = t - return expr + return &Expression{ + Token: t, + } } // AddChild adds a child to the expression. @@ -32,17 +32,16 @@ func (expr *Expression) AddChild(child *Expression) { child.Parent = expr } -// Close puts the expression back into the memory pool. -func (expr *Expression) Close() { +// Reset resets all values to the default. +func (expr *Expression) Reset() { for _, child := range expr.Children { - child.Close() + child.Reset() } expr.Token.Reset() expr.Parent = nil expr.Children = expr.Children[:0] expr.Precedence = 0 - pool.Put(expr) } // EachLeaf iterates through all leaves in the tree. diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index eb992c6..08c4e5d 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -88,7 +88,7 @@ func TestExpressionParse(t *testing.T) { src := []byte(test.Expression) tokens := token.Tokenize(src) expr := expression.Parse(tokens) - defer expr.Close() + defer expr.Reset() assert.NotNil(t, expr) assert.Equal(t, expr.String(), test.Result) diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index 5e50dc5..07fc5a1 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -9,7 +9,7 @@ import ( // Operator represents an operator for mathematical expressions. type Operator struct { Symbol string - Precedence int + Precedence int8 Operands int } @@ -39,14 +39,14 @@ var Operators = map[string]*Operator{ "&&": {"&&", 2, 2}, "||": {"||", 1, 2}, - "=": {"=", math.MinInt, 2}, - ":=": {":=", math.MinInt, 2}, - "+=": {"+=", math.MinInt, 2}, - "-=": {"-=", math.MinInt, 2}, - "*=": {"*=", math.MinInt, 2}, - "/=": {"/=", math.MinInt, 2}, - ">>=": {">>=", math.MinInt, 2}, - "<<=": {"<<=", math.MinInt, 2}, + "=": {"=", math.MinInt8, 2}, + ":=": {":=", math.MinInt8, 2}, + "+=": {"+=", math.MinInt8, 2}, + "-=": {"-=", math.MinInt8, 2}, + "*=": {"*=", math.MinInt8, 2}, + "/=": {"/=", math.MinInt8, 2}, + ">>=": {">>=", math.MinInt8, 2}, + "<<=": {"<<=", math.MinInt8, 2}, } func isComplete(expr *Expression) bool { @@ -75,7 +75,7 @@ func numOperands(symbol string) int { return operator.Operands } -func precedence(symbol string) int { +func precedence(symbol string) int8 { operator, exists := Operators[symbol] if !exists { diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index 072dfe4..ee90a0b 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -70,7 +70,7 @@ func Parse(tokens token.List) *Expression { continue } - group.Precedence = math.MaxInt + group.Precedence = math.MaxInt8 if cursor == nil { cursor = group diff --git a/src/build/expression/bench_test.go b/src/build/expression/bench_test.go index 8287416..c447910 100644 --- a/src/build/expression/bench_test.go +++ b/src/build/expression/bench_test.go @@ -12,7 +12,6 @@ func BenchmarkExpression(b *testing.B) { tokens := token.Tokenize(src) for i := 0; i < b.N; i++ { - expr := expression.Parse(tokens) - expr.Close() + expression.Parse(tokens) } } diff --git a/src/build/expression/pool.go b/src/build/expression/pool.go deleted file mode 100644 index 42b571d..0000000 --- a/src/build/expression/pool.go +++ /dev/null @@ -1,9 +0,0 @@ -package expression - -import "sync" - -var pool = sync.Pool{ - New: func() interface{} { - return &Expression{} - }, -} diff --git a/src/build/fs/File.go b/src/build/fs/File.go index 53d49c2..d4fd1cf 100644 --- a/src/build/fs/File.go +++ b/src/build/fs/File.go @@ -4,6 +4,6 @@ import "git.akyoto.dev/cli/q/src/build/token" // File represents a single source file. type File struct { - Tokens token.List Path string + Tokens token.List } diff --git a/src/build/keyword/Keyword.go b/src/build/keyword/Keyword.go new file mode 100644 index 0000000..d3d9b18 --- /dev/null +++ b/src/build/keyword/Keyword.go @@ -0,0 +1,12 @@ +package keyword + +const ( + Loop = "loop" + Return = "return" +) + +// Map is a map of all keywords used in the language. +var Map = map[string][]byte{ + Loop: []byte(Loop), + Return: []byte(Return), +} diff --git a/src/build/token/Keywords.go b/src/build/token/Keywords.go deleted file mode 100644 index d5f6231..0000000 --- a/src/build/token/Keywords.go +++ /dev/null @@ -1,7 +0,0 @@ -package token - -// Keywords defines the keywords used in the language. -var Keywords = map[string]bool{ - "return": true, - "loop": true, -} diff --git a/src/build/token/Token.go b/src/build/token/Token.go index fadf760..e10f8bc 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -6,9 +6,9 @@ import "fmt" // The characters that make up an identifier are grouped into a single token. // This makes parsing easier and allows us to do better syntax checks. type Token struct { - Kind Kind - Position int Bytes []byte + Position int + Kind Kind } // End returns the position after the token. diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 5388b4b..7347252 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -1,5 +1,7 @@ package token +import "git.akyoto.dev/cli/q/src/build/keyword" + // Pre-allocate these byte buffers so we can re-use them // instead of allocating a new buffer every time. var ( @@ -26,35 +28,35 @@ func Tokenize(buffer []byte) List { case ' ', '\t': // Separator case ',': - tokens = append(tokens, Token{Separator, i, separatorBytes}) + tokens = append(tokens, Token{Kind: Separator, Position: i, Bytes: separatorBytes}) // Parentheses start case '(': - tokens = append(tokens, Token{GroupStart, i, groupStartBytes}) + tokens = append(tokens, Token{Kind: GroupStart, Position: i, Bytes: groupStartBytes}) // Parentheses end case ')': - tokens = append(tokens, Token{GroupEnd, i, groupEndBytes}) + tokens = append(tokens, Token{Kind: GroupEnd, Position: i, Bytes: groupEndBytes}) // Block start case '{': - tokens = append(tokens, Token{BlockStart, i, blockStartBytes}) + tokens = append(tokens, Token{Kind: BlockStart, Position: i, Bytes: blockStartBytes}) // Block end case '}': - tokens = append(tokens, Token{BlockEnd, i, blockEndBytes}) + tokens = append(tokens, Token{Kind: BlockEnd, Position: i, Bytes: blockEndBytes}) // Array start case '[': - tokens = append(tokens, Token{ArrayStart, i, arrayStartBytes}) + tokens = append(tokens, Token{Kind: ArrayStart, Position: i, Bytes: arrayStartBytes}) // Array end case ']': - tokens = append(tokens, Token{ArrayEnd, i, arrayEndBytes}) + tokens = append(tokens, Token{Kind: ArrayEnd, Position: i, Bytes: arrayEndBytes}) // New line case '\n': - tokens = append(tokens, Token{NewLine, i, newLineBytes}) + tokens = append(tokens, Token{Kind: NewLine, Position: i, Bytes: newLineBytes}) // Comment case '/': @@ -66,7 +68,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Operator, position, buffer[position:i]}) + tokens = append(tokens, Token{Kind: Operator, Position: position, Bytes: buffer[position:i]}) continue } @@ -76,7 +78,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Comment, position, buffer[position:i]}) + tokens = append(tokens, Token{Kind: Comment, Position: position, Bytes: buffer[position:i]}) continue // String @@ -95,7 +97,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{String, start, buffer[start:end]}) + tokens = append(tokens, Token{Kind: String, Position: start, Bytes: buffer[start:end]}) continue default: @@ -108,13 +110,15 @@ func Tokenize(buffer []byte) List { i++ } - token := Token{Identifier, position, buffer[position:i]} + identifier := buffer[position:i] + keyword, isKeyword := keyword.Map[string(identifier)] - if Keywords[string(token.Bytes)] { - token.Kind = Keyword + if isKeyword { + tokens = append(tokens, Token{Kind: Keyword, Position: position, Bytes: keyword}) + } else { + tokens = append(tokens, Token{Kind: Identifier, Position: position, Bytes: identifier}) } - tokens = append(tokens, token) continue } @@ -127,7 +131,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Number, position, buffer[position:i]}) + tokens = append(tokens, Token{Kind: Number, Position: position, Bytes: buffer[position:i]}) continue } @@ -140,18 +144,18 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Operator, position, buffer[position:i]}) + tokens = append(tokens, Token{Kind: Operator, Position: position, Bytes: buffer[position:i]}) continue } // Invalid character - tokens = append(tokens, Token{Invalid, i, buffer[i : i+1]}) + tokens = append(tokens, Token{Kind: Invalid, Position: i, Bytes: buffer[i : i+1]}) } i++ } - tokens = append(tokens, Token{EOF, i, nil}) + tokens = append(tokens, Token{Kind: EOF, Position: i, Bytes: nil}) return tokens } diff --git a/src/errors/Error.go b/src/errors/Error.go index ad434d3..0821e25 100644 --- a/src/errors/Error.go +++ b/src/errors/Error.go @@ -13,8 +13,8 @@ import ( type Error struct { Err error File *fs.File - Position int Stack string + Position int } // New generates an error message at the current token position. From f479b5a03ab698c7423cb996b245f154ebe625bd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 30 Jun 2024 22:54:59 +0200 Subject: [PATCH 0212/1012] Implemented an abstract syntax tree --- src/build/AST.go | 87 +++++++++++++++++++ src/build/Call.go | 21 +++++ src/build/Execute.go | 108 ------------------------ src/build/ExpressionToRegister.go | 47 +++++++++++ src/build/Function.go | 90 ++++++++++---------- src/build/Instruction.go | 66 +++++++++------ src/build/Keyword.go | 20 ----- src/build/Loop.go | 24 ++---- src/build/Return.go | 16 +--- src/build/TokenToRegister.go | 59 +++++++++++++ src/build/Variable.go | 2 +- src/build/VariableDefinition.go | 17 ++-- src/build/asm/Instruction.go | 2 +- src/build/asm/Pointer.go | 2 +- src/build/ast/AST.go | 36 ++++++++ src/build/compiler.go | 16 ++-- src/build/expression/Expression.go | 19 ++--- src/build/expression/Expression_test.go | 2 +- src/build/expression/Operator.go | 20 ++--- src/build/expression/Parse.go | 2 +- src/build/expression/bench_test.go | 3 +- src/build/expression/pool.go | 9 -- src/build/fs/File.go | 2 +- src/build/keyword/Keyword.go | 12 +++ src/build/token/Keywords.go | 7 -- src/build/token/Token.go | 4 +- src/build/token/Tokenize.go | 42 ++++----- src/errors/Error.go | 2 +- 28 files changed, 422 insertions(+), 315 deletions(-) create mode 100644 src/build/AST.go create mode 100644 src/build/ExpressionToRegister.go delete mode 100644 src/build/Keyword.go create mode 100644 src/build/TokenToRegister.go create mode 100644 src/build/ast/AST.go delete mode 100644 src/build/expression/pool.go create mode 100644 src/build/keyword/Keyword.go delete mode 100644 src/build/token/Keywords.go diff --git a/src/build/AST.go b/src/build/AST.go new file mode 100644 index 0000000..b12cc5e --- /dev/null +++ b/src/build/AST.go @@ -0,0 +1,87 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/keyword" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// toAST generates an AST from a list of tokens. +func (f *Function) toAST(tokens token.List) (ast.AST, error) { + tree := make(ast.AST, 0, len(tokens)/64) + + err := EachInstruction(tokens, func(instruction token.List) error { + node, err := f.toASTNode(instruction) + + if err == nil && node != nil { + tree = append(tree, node) + } + + return err + }) + + return tree, err +} + +// toASTNode generates an AST node from an instruction. +func (f *Function) toASTNode(tokens token.List) (ast.Node, error) { + if tokens[0].Kind == token.Keyword { + switch tokens[0].Text() { + case keyword.Return: + value := expression.Parse(tokens[1:]) + return &ast.Return{Value: value}, nil + + case keyword.Loop: + blockStart := tokens.IndexKind(token.BlockStart) + 1 + blockEnd := tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return nil, errors.New(errors.MissingBlockStart, f.File, tokens[0].End()) + } + + if blockEnd == -1 { + return nil, errors.New(errors.MissingBlockEnd, f.File, tokens[len(tokens)-1].End()) + } + + tree, err := f.toAST(tokens[blockStart:blockEnd]) + return &ast.Loop{Body: tree}, err + + default: + return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, f.File, tokens[0].Position) + } + } + + expr := expression.Parse(tokens) + + if expr == nil { + return nil, nil + } + + switch { + case isVariableDefinition(expr): + if len(expr.Children) < 2 { + return nil, errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) + } + + name := expr.Children[0].Token + value := expr.Children[1] + return &ast.Define{Name: name, Value: value}, nil + + case isAssignment(expr): + if len(expr.Children) < 2 { + return nil, errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) + } + + name := expr.Children[0].Token + value := expr.Children[1] + return &ast.Assign{Name: name, Value: value}, nil + + case isFunctionCall(expr): + return &ast.Call{Expression: expr}, nil + + default: + return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) + } +} diff --git a/src/build/Call.go b/src/build/Call.go index ee813fc..44df7c9 100644 --- a/src/build/Call.go +++ b/src/build/Call.go @@ -2,6 +2,7 @@ package build import ( "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" ) @@ -45,3 +46,23 @@ func (f *Function) CompileSyscall(expr *expression.Expression) error { f.sideEffects++ return err } + +// ExpressionsToRegisters moves multiple expressions into the specified registers. +func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { + for _, register := range registers { + f.SaveRegister(register) + } + + for i := len(expressions) - 1; i >= 0; i-- { + expression := expressions[i] + register := registers[i] + + err := f.ExpressionToRegister(expression, register) + + if err != nil { + return err + } + } + + return nil +} diff --git a/src/build/Execute.go b/src/build/Execute.go index 4903ef7..6a96ce8 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -3,7 +3,6 @@ package build import ( "strconv" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" @@ -72,110 +71,3 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope return errors.New(errors.NotImplemented, f.File, operation.Position) } - -// ExpressionToRegister moves the result of an expression into the given register. -func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { - if config.Verbose { - f.Logf("%s to register %s", root, register) - } - - operation := root.Token - - if root.IsLeaf() { - return f.TokenToRegister(operation, register) - } - - if isFunctionCall(root) { - err := f.CompileFunctionCall(root) - - if err != nil { - return err - } - - if register != f.cpu.Return[0] { - f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Return[0]) - } - - return nil - } - - left := root.Children[0] - right := root.Children[1] - - err := f.ExpressionToRegister(left, register) - - if err != nil { - return err - } - - f.SaveRegister(register) - return f.Execute(operation, register, right) -} - -// ExpressionsToRegisters moves multiple expressions into the specified registers. -func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { - for _, register := range registers { - f.SaveRegister(register) - } - - for i := len(expressions) - 1; i >= 0; i-- { - expression := expressions[i] - register := registers[i] - - err := f.ExpressionToRegister(expression, register) - - if err != nil { - return err - } - } - - return nil -} - -// TokenToRegister moves a token into a register. -// It only works with identifiers, numbers and strings. -func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { - switch t.Kind { - case token.Identifier: - name := t.Text() - constant, exists := f.definitions[name] - - if exists { - if config.Verbose { - f.Logf("constant %s = %s", constant.Name, constant.Value) - } - - return f.ExpressionToRegister(constant.Value, register) - } - - variable, exists := f.variables[name] - - if !exists { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) - } - - if register != variable.Register { - f.assembler.RegisterRegister(asm.MOVE, register, variable.Register) - } - - f.useVariable(variable) - return nil - - case token.Number: - value := t.Text() - n, err := strconv.Atoi(value) - - if err != nil { - return err - } - - f.assembler.RegisterNumber(asm.MOVE, register, n) - return nil - - case token.String: - return errors.New(errors.NotImplemented, f.File, t.Position) - - default: - return errors.New(errors.InvalidExpression, f.File, t.Position) - } -} diff --git a/src/build/ExpressionToRegister.go b/src/build/ExpressionToRegister.go new file mode 100644 index 0000000..9a6f09d --- /dev/null +++ b/src/build/ExpressionToRegister.go @@ -0,0 +1,47 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" +) + +// ExpressionToRegister moves the result of an expression into the given register. +func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { + if config.Verbose { + f.Logf("%s to register %s", root, register) + } + + operation := root.Token + + if root.IsLeaf() { + return f.TokenToRegister(operation, register) + } + + if isFunctionCall(root) { + err := f.CompileFunctionCall(root) + + if err != nil { + return err + } + + if register != f.cpu.Return[0] { + f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Return[0]) + } + + return nil + } + + left := root.Children[0] + right := root.Children[1] + + err := f.ExpressionToRegister(left, register) + + if err != nil { + return err + } + + f.SaveRegister(register) + return f.Execute(operation, register, right) +} diff --git a/src/build/Function.go b/src/build/Function.go index d615a90..39bf0c3 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -3,6 +3,7 @@ package build import ( "fmt" + "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" @@ -11,8 +12,8 @@ import ( // Function represents a function. type Function struct { - Name string File *fs.File + Name string Body token.List compiler } @@ -26,61 +27,60 @@ func (f *Function) Compile() { } // CompileTokens compiles a token list. -func (f *Function) CompileTokens(body token.List) error { - start := 0 - groupLevel := 0 - blockLevel := 0 +func (f *Function) CompileTokens(tokens token.List) error { + tree, err := f.toAST(tokens) - for i, t := range body { - if start == i && t.Kind == token.NewLine { - start = i + 1 - continue + if err != nil { + return err + } + + return f.CompileAST(tree) +} + +// CompileAST compiles an abstract syntax tree. +func (f *Function) CompileAST(tree ast.AST) error { + for _, node := range tree { + if config.Verbose { + f.Logf("%T %s", node, node) } - switch t.Kind { - case token.NewLine: - if groupLevel > 0 || blockLevel > 0 { - continue - } + if config.Assembler { + f.debug = append(f.debug, debug{ + position: len(f.assembler.Instructions), + source: node, + }) + } - instruction := body[start:i] + err := f.CompileNode(node) - if config.Verbose { - f.Logf("compiling: %s", instruction) - } - - if config.Assembler { - f.debug = append(f.debug, debug{ - position: len(f.assembler.Instructions), - source: instruction, - }) - } - - err := f.CompileInstruction(instruction) - - if err != nil { - return err - } - - start = i + 1 - - case token.GroupStart: - groupLevel++ - - case token.GroupEnd: - groupLevel-- - - case token.BlockStart: - blockLevel++ - - case token.BlockEnd: - blockLevel-- + if err != nil { + return err } } return nil } +// CompileNode compiles a node in the AST. +func (f *Function) CompileNode(node ast.Node) error { + switch node := node.(type) { + case *ast.Call: + return f.CompileFunctionCall(node.Expression) + + case *ast.Define: + return f.CompileVariableDefinition(node) + + case *ast.Return: + return f.CompileReturn(node) + + case *ast.Loop: + return f.CompileLoop(node) + + default: + panic("Unknown AST type") + } +} + // Logf formats a message for verbose output. func (f *Function) Logf(format string, data ...any) { fmt.Printf("[%s @ %d] %s\n", f, len(f.assembler.Instructions), fmt.Sprintf(format, data...)) diff --git a/src/build/Instruction.go b/src/build/Instruction.go index c668258..944ab92 100644 --- a/src/build/Instruction.go +++ b/src/build/Instruction.go @@ -1,36 +1,48 @@ package build import ( - "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) -// CompileInstruction compiles a single instruction. -func (f *Function) CompileInstruction(instruction token.List) error { - if instruction[0].Kind == token.Keyword { - return f.CompileKeyword(instruction) +// EachInstruction calls the function on each instruction. +func EachInstruction(body token.List, call func(token.List) error) error { + start := 0 + groupLevel := 0 + blockLevel := 0 + + for i, t := range body { + if start == i && t.Kind == token.NewLine { + start = i + 1 + continue + } + + switch t.Kind { + case token.NewLine: + if groupLevel > 0 || blockLevel > 0 { + continue + } + + err := call(body[start:i]) + + if err != nil { + return err + } + + start = i + 1 + + case token.GroupStart: + groupLevel++ + + case token.GroupEnd: + groupLevel-- + + case token.BlockStart: + blockLevel++ + + case token.BlockEnd: + blockLevel-- + } } - expr := expression.Parse(instruction) - - if expr == nil { - return nil - } - - defer expr.Close() - - switch true { - case isVariableDefinition(expr): - return f.CompileVariableDefinition(expr) - - case isAssignment(expr): - return f.CompileAssignment(expr) - - case isFunctionCall(expr): - return f.CompileFunctionCall(expr) - - default: - return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) - } + return nil } diff --git a/src/build/Keyword.go b/src/build/Keyword.go deleted file mode 100644 index 80e5ee9..0000000 --- a/src/build/Keyword.go +++ /dev/null @@ -1,20 +0,0 @@ -package build - -import ( - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" -) - -// CompileKeyword compiles an instruction that starts with a keyword. -func (f *Function) CompileKeyword(tokens token.List) error { - switch tokens[0].Text() { - case "return": - return f.CompileReturn(tokens) - - case "loop": - return f.CompileLoop(tokens) - - default: - return errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, f.File, tokens[0].Position) - } -} diff --git a/src/build/Loop.go b/src/build/Loop.go index 41a9b4f..2165eca 100644 --- a/src/build/Loop.go +++ b/src/build/Loop.go @@ -3,26 +3,14 @@ package build import ( "fmt" - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/build/ast" ) // CompileLoop compiles a loop instruction. -func (f *Function) CompileLoop(tokens token.List) error { - blockStart := tokens.IndexKind(token.BlockStart) + 1 - blockEnd := tokens.LastIndexKind(token.BlockEnd) - - if blockStart == -1 { - return errors.New(errors.MissingBlockStart, f.File, tokens[0].End()) - } - - if blockEnd == -1 { - return errors.New(errors.MissingBlockEnd, f.File, tokens[len(tokens)-1].End()) - } - - loop := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) - f.assembler.Label(loop) - defer f.assembler.Jump(loop) +func (f *Function) CompileLoop(loop *ast.Loop) error { + label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) + f.assembler.Label(label) + defer f.assembler.Jump(label) f.count.loop++ - return f.CompileTokens(tokens[blockStart:blockEnd]) + return f.CompileAST(loop.Body) } diff --git a/src/build/Return.go b/src/build/Return.go index 8ffd8fb..c661fc7 100644 --- a/src/build/Return.go +++ b/src/build/Return.go @@ -1,24 +1,16 @@ package build import ( - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/ast" ) // CompileReturn compiles a return instruction. -func (f *Function) CompileReturn(tokens token.List) error { +func (f *Function) CompileReturn(node *ast.Return) error { defer f.assembler.Return() - if len(tokens) == 1 { + if node.Value == nil { return nil } - value := expression.Parse(tokens[1:]) - - if value == nil { - return nil - } - - defer value.Close() - return f.ExpressionToRegister(value, f.cpu.Return[0]) + return f.ExpressionToRegister(node.Value, f.cpu.Return[0]) } diff --git a/src/build/TokenToRegister.go b/src/build/TokenToRegister.go new file mode 100644 index 0000000..6bef4cb --- /dev/null +++ b/src/build/TokenToRegister.go @@ -0,0 +1,59 @@ +package build + +import ( + "strconv" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// TokenToRegister moves a token into a register. +// It only works with identifiers, numbers and strings. +func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { + switch t.Kind { + case token.Identifier: + name := t.Text() + constant, exists := f.definitions[name] + + if exists { + if config.Verbose { + f.Logf("constant %s = %s", constant.Name, constant.Value) + } + + return f.ExpressionToRegister(constant.Value, register) + } + + variable, exists := f.variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) + } + + if register != variable.Register { + f.assembler.RegisterRegister(asm.MOVE, register, variable.Register) + } + + f.useVariable(variable) + return nil + + case token.Number: + value := t.Text() + n, err := strconv.Atoi(value) + + if err != nil { + return err + } + + f.assembler.RegisterNumber(asm.MOVE, register, n) + return nil + + case token.String: + return errors.New(errors.NotImplemented, f.File, t.Position) + + default: + return errors.New(errors.InvalidExpression, f.File, t.Position) + } +} diff --git a/src/build/Variable.go b/src/build/Variable.go index 291d2d4..c81b850 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -14,6 +14,6 @@ type Variable struct { // Definitions are single use expressions that don't reside in a register yet. type Definition struct { - Name string Value *expression.Expression + Name string } diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index 58a4844..917f12f 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -1,6 +1,7 @@ package build import ( + "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" @@ -8,24 +9,20 @@ import ( ) // CompileVariableDefinition compiles a variable definition. -func (f *Function) CompileVariableDefinition(expr *expression.Expression) error { - if len(expr.Children) < 2 { - return errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) - } - - name := expr.Children[0].Token.Text() +func (f *Function) CompileVariableDefinition(node *ast.Define) error { + name := node.Name.Text() if f.identifierExists(name) { - return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, expr.Children[0].Token.Position) + return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, node.Name.Position) } uses := countIdentifier(f.Body, name) - 1 if uses == 0 { - return errors.New(&errors.UnusedVariable{Name: name}, f.File, expr.Children[0].Token.Position) + return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) } - value := expr.Children[1] + value := node.Value err := value.EachLeaf(func(leaf *expression.Expression) error { if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) { @@ -40,8 +37,6 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error } if uses == 1 { - expr.RemoveChild(value) - f.definitions[name] = &Definition{ Name: name, Value: value, diff --git a/src/build/asm/Instruction.go b/src/build/asm/Instruction.go index 5b2bbce..2447bae 100644 --- a/src/build/asm/Instruction.go +++ b/src/build/asm/Instruction.go @@ -4,6 +4,6 @@ import "fmt" // Instruction represents a single instruction which can be converted to machine code. type Instruction struct { - Mnemonic Mnemonic Data fmt.Stringer + Mnemonic Mnemonic } diff --git a/src/build/asm/Pointer.go b/src/build/asm/Pointer.go index 9be746f..4e33a63 100644 --- a/src/build/asm/Pointer.go +++ b/src/build/asm/Pointer.go @@ -7,7 +7,7 @@ type Address = uint32 // Position: The machine code offset where the address was inserted. // Resolve: The function that will return the final address. type Pointer struct { + Resolve func() Address Position Address Size uint8 - Resolve func() Address } diff --git a/src/build/ast/AST.go b/src/build/ast/AST.go new file mode 100644 index 0000000..2d8314a --- /dev/null +++ b/src/build/ast/AST.go @@ -0,0 +1,36 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +type Node interface{} +type AST []Node + +type Assign struct { + Value *expression.Expression + Name token.Token +} + +type Call struct { + Expression *expression.Expression +} + +type Define struct { + Value *expression.Expression + Name token.Token +} + +type If struct { + Condition *expression.Expression + Body AST +} + +type Loop struct { + Body AST +} + +type Return struct { + Value *expression.Expression +} diff --git a/src/build/compiler.go b/src/build/compiler.go index 60cfec0..ac28b37 100644 --- a/src/build/compiler.go +++ b/src/build/compiler.go @@ -4,23 +4,23 @@ import ( "fmt" "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/go/color/ansi" ) // compiler is the data structure we embed in each function to preserve compilation state. type compiler struct { - assembler asm.Assembler - count counter - cpu cpu.CPU - debug []debug err error definitions map[string]*Definition variables map[string]*Variable functions map[string]*Function - sideEffects int finished chan struct{} + assembler asm.Assembler + debug []debug + cpu cpu.CPU + count counter + sideEffects int } // counter stores how often a certain statement appeared so we can generate a unique label from it. @@ -30,8 +30,8 @@ type counter struct { // debug is used to look up the source code at the given position. type debug struct { + source ast.Node position int - source token.List } // PrintInstructions shows the assembly instructions. @@ -66,7 +66,7 @@ func (c *compiler) PrintInstructions() { } // sourceAt retrieves the source code at the given position or `nil`. -func (c *compiler) sourceAt(position int) token.List { +func (c *compiler) sourceAt(position int) ast.Node { for _, record := range c.debug { if record.position == position { return record.source diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 7776c35..1d2efa8 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -8,22 +8,22 @@ import ( // Expression is a binary tree with an operator on each node. type Expression struct { - Token token.Token Parent *Expression Children []*Expression - Precedence int + Token token.Token + Precedence int8 } // New creates a new expression. func New() *Expression { - return pool.Get().(*Expression) + return &Expression{} } // NewLeaf creates a new leaf node. func NewLeaf(t token.Token) *Expression { - expr := New() - expr.Token = t - return expr + return &Expression{ + Token: t, + } } // AddChild adds a child to the expression. @@ -32,17 +32,16 @@ func (expr *Expression) AddChild(child *Expression) { child.Parent = expr } -// Close puts the expression back into the memory pool. -func (expr *Expression) Close() { +// Reset resets all values to the default. +func (expr *Expression) Reset() { for _, child := range expr.Children { - child.Close() + child.Reset() } expr.Token.Reset() expr.Parent = nil expr.Children = expr.Children[:0] expr.Precedence = 0 - pool.Put(expr) } // EachLeaf iterates through all leaves in the tree. diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index eb992c6..08c4e5d 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -88,7 +88,7 @@ func TestExpressionParse(t *testing.T) { src := []byte(test.Expression) tokens := token.Tokenize(src) expr := expression.Parse(tokens) - defer expr.Close() + defer expr.Reset() assert.NotNil(t, expr) assert.Equal(t, expr.String(), test.Result) diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index 5e50dc5..07fc5a1 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -9,7 +9,7 @@ import ( // Operator represents an operator for mathematical expressions. type Operator struct { Symbol string - Precedence int + Precedence int8 Operands int } @@ -39,14 +39,14 @@ var Operators = map[string]*Operator{ "&&": {"&&", 2, 2}, "||": {"||", 1, 2}, - "=": {"=", math.MinInt, 2}, - ":=": {":=", math.MinInt, 2}, - "+=": {"+=", math.MinInt, 2}, - "-=": {"-=", math.MinInt, 2}, - "*=": {"*=", math.MinInt, 2}, - "/=": {"/=", math.MinInt, 2}, - ">>=": {">>=", math.MinInt, 2}, - "<<=": {"<<=", math.MinInt, 2}, + "=": {"=", math.MinInt8, 2}, + ":=": {":=", math.MinInt8, 2}, + "+=": {"+=", math.MinInt8, 2}, + "-=": {"-=", math.MinInt8, 2}, + "*=": {"*=", math.MinInt8, 2}, + "/=": {"/=", math.MinInt8, 2}, + ">>=": {">>=", math.MinInt8, 2}, + "<<=": {"<<=", math.MinInt8, 2}, } func isComplete(expr *Expression) bool { @@ -75,7 +75,7 @@ func numOperands(symbol string) int { return operator.Operands } -func precedence(symbol string) int { +func precedence(symbol string) int8 { operator, exists := Operators[symbol] if !exists { diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index 072dfe4..ee90a0b 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -70,7 +70,7 @@ func Parse(tokens token.List) *Expression { continue } - group.Precedence = math.MaxInt + group.Precedence = math.MaxInt8 if cursor == nil { cursor = group diff --git a/src/build/expression/bench_test.go b/src/build/expression/bench_test.go index 8287416..c447910 100644 --- a/src/build/expression/bench_test.go +++ b/src/build/expression/bench_test.go @@ -12,7 +12,6 @@ func BenchmarkExpression(b *testing.B) { tokens := token.Tokenize(src) for i := 0; i < b.N; i++ { - expr := expression.Parse(tokens) - expr.Close() + expression.Parse(tokens) } } diff --git a/src/build/expression/pool.go b/src/build/expression/pool.go deleted file mode 100644 index 42b571d..0000000 --- a/src/build/expression/pool.go +++ /dev/null @@ -1,9 +0,0 @@ -package expression - -import "sync" - -var pool = sync.Pool{ - New: func() interface{} { - return &Expression{} - }, -} diff --git a/src/build/fs/File.go b/src/build/fs/File.go index 53d49c2..d4fd1cf 100644 --- a/src/build/fs/File.go +++ b/src/build/fs/File.go @@ -4,6 +4,6 @@ import "git.akyoto.dev/cli/q/src/build/token" // File represents a single source file. type File struct { - Tokens token.List Path string + Tokens token.List } diff --git a/src/build/keyword/Keyword.go b/src/build/keyword/Keyword.go new file mode 100644 index 0000000..d3d9b18 --- /dev/null +++ b/src/build/keyword/Keyword.go @@ -0,0 +1,12 @@ +package keyword + +const ( + Loop = "loop" + Return = "return" +) + +// Map is a map of all keywords used in the language. +var Map = map[string][]byte{ + Loop: []byte(Loop), + Return: []byte(Return), +} diff --git a/src/build/token/Keywords.go b/src/build/token/Keywords.go deleted file mode 100644 index d5f6231..0000000 --- a/src/build/token/Keywords.go +++ /dev/null @@ -1,7 +0,0 @@ -package token - -// Keywords defines the keywords used in the language. -var Keywords = map[string]bool{ - "return": true, - "loop": true, -} diff --git a/src/build/token/Token.go b/src/build/token/Token.go index fadf760..e10f8bc 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -6,9 +6,9 @@ import "fmt" // The characters that make up an identifier are grouped into a single token. // This makes parsing easier and allows us to do better syntax checks. type Token struct { - Kind Kind - Position int Bytes []byte + Position int + Kind Kind } // End returns the position after the token. diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 5388b4b..7347252 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -1,5 +1,7 @@ package token +import "git.akyoto.dev/cli/q/src/build/keyword" + // Pre-allocate these byte buffers so we can re-use them // instead of allocating a new buffer every time. var ( @@ -26,35 +28,35 @@ func Tokenize(buffer []byte) List { case ' ', '\t': // Separator case ',': - tokens = append(tokens, Token{Separator, i, separatorBytes}) + tokens = append(tokens, Token{Kind: Separator, Position: i, Bytes: separatorBytes}) // Parentheses start case '(': - tokens = append(tokens, Token{GroupStart, i, groupStartBytes}) + tokens = append(tokens, Token{Kind: GroupStart, Position: i, Bytes: groupStartBytes}) // Parentheses end case ')': - tokens = append(tokens, Token{GroupEnd, i, groupEndBytes}) + tokens = append(tokens, Token{Kind: GroupEnd, Position: i, Bytes: groupEndBytes}) // Block start case '{': - tokens = append(tokens, Token{BlockStart, i, blockStartBytes}) + tokens = append(tokens, Token{Kind: BlockStart, Position: i, Bytes: blockStartBytes}) // Block end case '}': - tokens = append(tokens, Token{BlockEnd, i, blockEndBytes}) + tokens = append(tokens, Token{Kind: BlockEnd, Position: i, Bytes: blockEndBytes}) // Array start case '[': - tokens = append(tokens, Token{ArrayStart, i, arrayStartBytes}) + tokens = append(tokens, Token{Kind: ArrayStart, Position: i, Bytes: arrayStartBytes}) // Array end case ']': - tokens = append(tokens, Token{ArrayEnd, i, arrayEndBytes}) + tokens = append(tokens, Token{Kind: ArrayEnd, Position: i, Bytes: arrayEndBytes}) // New line case '\n': - tokens = append(tokens, Token{NewLine, i, newLineBytes}) + tokens = append(tokens, Token{Kind: NewLine, Position: i, Bytes: newLineBytes}) // Comment case '/': @@ -66,7 +68,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Operator, position, buffer[position:i]}) + tokens = append(tokens, Token{Kind: Operator, Position: position, Bytes: buffer[position:i]}) continue } @@ -76,7 +78,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Comment, position, buffer[position:i]}) + tokens = append(tokens, Token{Kind: Comment, Position: position, Bytes: buffer[position:i]}) continue // String @@ -95,7 +97,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{String, start, buffer[start:end]}) + tokens = append(tokens, Token{Kind: String, Position: start, Bytes: buffer[start:end]}) continue default: @@ -108,13 +110,15 @@ func Tokenize(buffer []byte) List { i++ } - token := Token{Identifier, position, buffer[position:i]} + identifier := buffer[position:i] + keyword, isKeyword := keyword.Map[string(identifier)] - if Keywords[string(token.Bytes)] { - token.Kind = Keyword + if isKeyword { + tokens = append(tokens, Token{Kind: Keyword, Position: position, Bytes: keyword}) + } else { + tokens = append(tokens, Token{Kind: Identifier, Position: position, Bytes: identifier}) } - tokens = append(tokens, token) continue } @@ -127,7 +131,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Number, position, buffer[position:i]}) + tokens = append(tokens, Token{Kind: Number, Position: position, Bytes: buffer[position:i]}) continue } @@ -140,18 +144,18 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Operator, position, buffer[position:i]}) + tokens = append(tokens, Token{Kind: Operator, Position: position, Bytes: buffer[position:i]}) continue } // Invalid character - tokens = append(tokens, Token{Invalid, i, buffer[i : i+1]}) + tokens = append(tokens, Token{Kind: Invalid, Position: i, Bytes: buffer[i : i+1]}) } i++ } - tokens = append(tokens, Token{EOF, i, nil}) + tokens = append(tokens, Token{Kind: EOF, Position: i, Bytes: nil}) return tokens } diff --git a/src/errors/Error.go b/src/errors/Error.go index ad434d3..0821e25 100644 --- a/src/errors/Error.go +++ b/src/errors/Error.go @@ -13,8 +13,8 @@ import ( type Error struct { Err error File *fs.File - Position int Stack string + Position int } // New generates an error message at the current token position. From 2d045759f263e4bd132531fdc052d0abe66246db Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 1 Jul 2024 15:05:15 +0200 Subject: [PATCH 0213/1012] Moved AST generation to its own package --- src/build/AST.go | 87 ------------------------- src/build/Execute.go | 3 +- src/build/ExpressionToRegister.go | 3 +- src/build/Function.go | 38 +---------- src/build/Instruction.go | 47 -------------- src/build/VariableDefinition.go | 18 ++++++ src/build/ast/EachInstruction.go | 46 ++++++++++++++ src/build/ast/Parse.go | 101 ++++++++++++++++++++++++++++++ 8 files changed, 172 insertions(+), 171 deletions(-) delete mode 100644 src/build/AST.go create mode 100644 src/build/ast/EachInstruction.go create mode 100644 src/build/ast/Parse.go diff --git a/src/build/AST.go b/src/build/AST.go deleted file mode 100644 index b12cc5e..0000000 --- a/src/build/AST.go +++ /dev/null @@ -1,87 +0,0 @@ -package build - -import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/keyword" - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" -) - -// toAST generates an AST from a list of tokens. -func (f *Function) toAST(tokens token.List) (ast.AST, error) { - tree := make(ast.AST, 0, len(tokens)/64) - - err := EachInstruction(tokens, func(instruction token.List) error { - node, err := f.toASTNode(instruction) - - if err == nil && node != nil { - tree = append(tree, node) - } - - return err - }) - - return tree, err -} - -// toASTNode generates an AST node from an instruction. -func (f *Function) toASTNode(tokens token.List) (ast.Node, error) { - if tokens[0].Kind == token.Keyword { - switch tokens[0].Text() { - case keyword.Return: - value := expression.Parse(tokens[1:]) - return &ast.Return{Value: value}, nil - - case keyword.Loop: - blockStart := tokens.IndexKind(token.BlockStart) + 1 - blockEnd := tokens.LastIndexKind(token.BlockEnd) - - if blockStart == -1 { - return nil, errors.New(errors.MissingBlockStart, f.File, tokens[0].End()) - } - - if blockEnd == -1 { - return nil, errors.New(errors.MissingBlockEnd, f.File, tokens[len(tokens)-1].End()) - } - - tree, err := f.toAST(tokens[blockStart:blockEnd]) - return &ast.Loop{Body: tree}, err - - default: - return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, f.File, tokens[0].Position) - } - } - - expr := expression.Parse(tokens) - - if expr == nil { - return nil, nil - } - - switch { - case isVariableDefinition(expr): - if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) - } - - name := expr.Children[0].Token - value := expr.Children[1] - return &ast.Define{Name: name, Value: value}, nil - - case isAssignment(expr): - if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) - } - - name := expr.Children[0].Token - value := expr.Children[1] - return &ast.Assign{Name: name, Value: value}, nil - - case isFunctionCall(expr): - return &ast.Call{Expression: expr}, nil - - default: - return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) - } -} diff --git a/src/build/Execute.go b/src/build/Execute.go index 6a96ce8..e85cd1e 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -3,6 +3,7 @@ package build import ( "strconv" + "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" @@ -22,7 +23,7 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * var temporary cpu.Register - if isFunctionCall(value) { + if ast.IsFunctionCall(value) { temporary = f.cpu.Return[0] } else { found := false diff --git a/src/build/ExpressionToRegister.go b/src/build/ExpressionToRegister.go index 9a6f09d..cf0a04c 100644 --- a/src/build/ExpressionToRegister.go +++ b/src/build/ExpressionToRegister.go @@ -2,6 +2,7 @@ package build import ( "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" @@ -19,7 +20,7 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp return f.TokenToRegister(operation, register) } - if isFunctionCall(root) { + if ast.IsFunctionCall(root) { err := f.CompileFunctionCall(root) if err != nil { diff --git a/src/build/Function.go b/src/build/Function.go index 39bf0c3..de87e2f 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -5,9 +5,9 @@ import ( "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" ) // Function represents a function. @@ -28,9 +28,10 @@ func (f *Function) Compile() { // CompileTokens compiles a token list. func (f *Function) CompileTokens(tokens token.List) error { - tree, err := f.toAST(tokens) + tree, err := ast.Parse(tokens) if err != nil { + err.(*errors.Error).File = f.File return err } @@ -95,36 +96,3 @@ func (f *Function) String() string { func (f *Function) Wait() { <-f.finished } - -// identifierExists returns true if the identifier has been defined. -func (f *Function) identifierExists(name string) bool { - _, exists := f.variables[name] - - if exists { - return true - } - - _, exists = f.definitions[name] - - if exists { - return true - } - - _, exists = f.functions[name] - return exists -} - -// isAssignment returns true if the expression is an assignment. -func isAssignment(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '=' -} - -// isFunctionCall returns true if the expression is a function call. -func isFunctionCall(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" -} - -// isVariableDefinition returns true if the expression is a variable definition. -func isVariableDefinition(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" -} diff --git a/src/build/Instruction.go b/src/build/Instruction.go index 944ab92..5c93a93 100644 --- a/src/build/Instruction.go +++ b/src/build/Instruction.go @@ -1,48 +1 @@ package build - -import ( - "git.akyoto.dev/cli/q/src/build/token" -) - -// EachInstruction calls the function on each instruction. -func EachInstruction(body token.List, call func(token.List) error) error { - start := 0 - groupLevel := 0 - blockLevel := 0 - - for i, t := range body { - if start == i && t.Kind == token.NewLine { - start = i + 1 - continue - } - - switch t.Kind { - case token.NewLine: - if groupLevel > 0 || blockLevel > 0 { - continue - } - - err := call(body[start:i]) - - if err != nil { - return err - } - - start = i + 1 - - case token.GroupStart: - groupLevel++ - - case token.GroupEnd: - groupLevel-- - - case token.BlockStart: - blockLevel++ - - case token.BlockEnd: - blockLevel-- - } - } - - return nil -} diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index 917f12f..f385c24 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -77,6 +77,24 @@ func (f *Function) useVariable(variable *Variable) { } } +// identifierExists returns true if the identifier has been defined. +func (f *Function) identifierExists(name string) bool { + _, exists := f.variables[name] + + if exists { + return true + } + + _, exists = f.definitions[name] + + if exists { + return true + } + + _, exists = f.functions[name] + return exists +} + func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error { reg, exists := f.cpu.FindFree(f.cpu.General) diff --git a/src/build/ast/EachInstruction.go b/src/build/ast/EachInstruction.go new file mode 100644 index 0000000..f11bedc --- /dev/null +++ b/src/build/ast/EachInstruction.go @@ -0,0 +1,46 @@ +package ast + +import "git.akyoto.dev/cli/q/src/build/token" + +// EachInstruction calls the function on each instruction. +func EachInstruction(body token.List, call func(token.List) error) error { + start := 0 + groupLevel := 0 + blockLevel := 0 + + for i, t := range body { + if start == i && t.Kind == token.NewLine { + start = i + 1 + continue + } + + switch t.Kind { + case token.NewLine: + if groupLevel > 0 || blockLevel > 0 { + continue + } + + err := call(body[start:i]) + + if err != nil { + return err + } + + start = i + 1 + + case token.GroupStart: + groupLevel++ + + case token.GroupEnd: + groupLevel-- + + case token.BlockStart: + blockLevel++ + + case token.BlockEnd: + blockLevel-- + } + } + + return nil +} diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go new file mode 100644 index 0000000..b977704 --- /dev/null +++ b/src/build/ast/Parse.go @@ -0,0 +1,101 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/keyword" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// Parse generates an AST from a list of tokens. +func Parse(tokens token.List) (AST, error) { + tree := make(AST, 0, len(tokens)/64) + + err := EachInstruction(tokens, func(instruction token.List) error { + node, err := toASTNode(instruction) + + if err == nil && node != nil { + tree = append(tree, node) + } + + return err + }) + + return tree, err +} + +// toASTNode generates an AST node from an instruction. +func toASTNode(tokens token.List) (Node, error) { + if tokens[0].Kind == token.Keyword { + switch tokens[0].Text() { + case keyword.Return: + value := expression.Parse(tokens[1:]) + return &Return{Value: value}, nil + + case keyword.Loop: + blockStart := tokens.IndexKind(token.BlockStart) + 1 + blockEnd := tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) + } + + if blockEnd == -1 { + return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) + } + + tree, err := Parse(tokens[blockStart:blockEnd]) + return &Loop{Body: tree}, err + + default: + return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, nil, tokens[0].Position) + } + } + + expr := expression.Parse(tokens) + + if expr == nil { + return nil, nil + } + + switch { + case IsVariableDefinition(expr): + if len(expr.Children) < 2 { + return nil, errors.New(errors.MissingAssignValue, nil, expr.LastChild().Token.End()) + } + + name := expr.Children[0].Token + value := expr.Children[1] + return &Define{Name: name, Value: value}, nil + + case IsAssignment(expr): + if len(expr.Children) < 2 { + return nil, errors.New(errors.MissingAssignValue, nil, expr.LastChild().Token.End()) + } + + name := expr.Children[0].Token + value := expr.Children[1] + return &Assign{Name: name, Value: value}, nil + + case IsFunctionCall(expr): + return &Call{Expression: expr}, nil + + default: + return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, nil, expr.Token.Position) + } +} + +// IsAssignment returns true if the expression is an assignment. +func IsAssignment(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '=' +} + +// IsFunctionCall returns true if the expression is a function call. +func IsFunctionCall(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" +} + +// IsVariableDefinition returns true if the expression is a variable definition. +func IsVariableDefinition(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" +} From 115f46c12d363ec8ac37bdd9d0a510bb9d3bf972 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 1 Jul 2024 15:05:15 +0200 Subject: [PATCH 0214/1012] Moved AST generation to its own package --- src/build/AST.go | 87 ------------------------- src/build/Execute.go | 3 +- src/build/ExpressionToRegister.go | 3 +- src/build/Function.go | 38 +---------- src/build/Instruction.go | 47 -------------- src/build/VariableDefinition.go | 18 ++++++ src/build/ast/EachInstruction.go | 46 ++++++++++++++ src/build/ast/Parse.go | 101 ++++++++++++++++++++++++++++++ 8 files changed, 172 insertions(+), 171 deletions(-) delete mode 100644 src/build/AST.go create mode 100644 src/build/ast/EachInstruction.go create mode 100644 src/build/ast/Parse.go diff --git a/src/build/AST.go b/src/build/AST.go deleted file mode 100644 index b12cc5e..0000000 --- a/src/build/AST.go +++ /dev/null @@ -1,87 +0,0 @@ -package build - -import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/keyword" - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" -) - -// toAST generates an AST from a list of tokens. -func (f *Function) toAST(tokens token.List) (ast.AST, error) { - tree := make(ast.AST, 0, len(tokens)/64) - - err := EachInstruction(tokens, func(instruction token.List) error { - node, err := f.toASTNode(instruction) - - if err == nil && node != nil { - tree = append(tree, node) - } - - return err - }) - - return tree, err -} - -// toASTNode generates an AST node from an instruction. -func (f *Function) toASTNode(tokens token.List) (ast.Node, error) { - if tokens[0].Kind == token.Keyword { - switch tokens[0].Text() { - case keyword.Return: - value := expression.Parse(tokens[1:]) - return &ast.Return{Value: value}, nil - - case keyword.Loop: - blockStart := tokens.IndexKind(token.BlockStart) + 1 - blockEnd := tokens.LastIndexKind(token.BlockEnd) - - if blockStart == -1 { - return nil, errors.New(errors.MissingBlockStart, f.File, tokens[0].End()) - } - - if blockEnd == -1 { - return nil, errors.New(errors.MissingBlockEnd, f.File, tokens[len(tokens)-1].End()) - } - - tree, err := f.toAST(tokens[blockStart:blockEnd]) - return &ast.Loop{Body: tree}, err - - default: - return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, f.File, tokens[0].Position) - } - } - - expr := expression.Parse(tokens) - - if expr == nil { - return nil, nil - } - - switch { - case isVariableDefinition(expr): - if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) - } - - name := expr.Children[0].Token - value := expr.Children[1] - return &ast.Define{Name: name, Value: value}, nil - - case isAssignment(expr): - if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingAssignValue, f.File, expr.LastChild().Token.End()) - } - - name := expr.Children[0].Token - value := expr.Children[1] - return &ast.Assign{Name: name, Value: value}, nil - - case isFunctionCall(expr): - return &ast.Call{Expression: expr}, nil - - default: - return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) - } -} diff --git a/src/build/Execute.go b/src/build/Execute.go index 6a96ce8..e85cd1e 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -3,6 +3,7 @@ package build import ( "strconv" + "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" @@ -22,7 +23,7 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * var temporary cpu.Register - if isFunctionCall(value) { + if ast.IsFunctionCall(value) { temporary = f.cpu.Return[0] } else { found := false diff --git a/src/build/ExpressionToRegister.go b/src/build/ExpressionToRegister.go index 9a6f09d..cf0a04c 100644 --- a/src/build/ExpressionToRegister.go +++ b/src/build/ExpressionToRegister.go @@ -2,6 +2,7 @@ package build import ( "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" @@ -19,7 +20,7 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp return f.TokenToRegister(operation, register) } - if isFunctionCall(root) { + if ast.IsFunctionCall(root) { err := f.CompileFunctionCall(root) if err != nil { diff --git a/src/build/Function.go b/src/build/Function.go index 39bf0c3..de87e2f 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -5,9 +5,9 @@ import ( "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" ) // Function represents a function. @@ -28,9 +28,10 @@ func (f *Function) Compile() { // CompileTokens compiles a token list. func (f *Function) CompileTokens(tokens token.List) error { - tree, err := f.toAST(tokens) + tree, err := ast.Parse(tokens) if err != nil { + err.(*errors.Error).File = f.File return err } @@ -95,36 +96,3 @@ func (f *Function) String() string { func (f *Function) Wait() { <-f.finished } - -// identifierExists returns true if the identifier has been defined. -func (f *Function) identifierExists(name string) bool { - _, exists := f.variables[name] - - if exists { - return true - } - - _, exists = f.definitions[name] - - if exists { - return true - } - - _, exists = f.functions[name] - return exists -} - -// isAssignment returns true if the expression is an assignment. -func isAssignment(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '=' -} - -// isFunctionCall returns true if the expression is a function call. -func isFunctionCall(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" -} - -// isVariableDefinition returns true if the expression is a variable definition. -func isVariableDefinition(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" -} diff --git a/src/build/Instruction.go b/src/build/Instruction.go index 944ab92..5c93a93 100644 --- a/src/build/Instruction.go +++ b/src/build/Instruction.go @@ -1,48 +1 @@ package build - -import ( - "git.akyoto.dev/cli/q/src/build/token" -) - -// EachInstruction calls the function on each instruction. -func EachInstruction(body token.List, call func(token.List) error) error { - start := 0 - groupLevel := 0 - blockLevel := 0 - - for i, t := range body { - if start == i && t.Kind == token.NewLine { - start = i + 1 - continue - } - - switch t.Kind { - case token.NewLine: - if groupLevel > 0 || blockLevel > 0 { - continue - } - - err := call(body[start:i]) - - if err != nil { - return err - } - - start = i + 1 - - case token.GroupStart: - groupLevel++ - - case token.GroupEnd: - groupLevel-- - - case token.BlockStart: - blockLevel++ - - case token.BlockEnd: - blockLevel-- - } - } - - return nil -} diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index 917f12f..f385c24 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -77,6 +77,24 @@ func (f *Function) useVariable(variable *Variable) { } } +// identifierExists returns true if the identifier has been defined. +func (f *Function) identifierExists(name string) bool { + _, exists := f.variables[name] + + if exists { + return true + } + + _, exists = f.definitions[name] + + if exists { + return true + } + + _, exists = f.functions[name] + return exists +} + func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error { reg, exists := f.cpu.FindFree(f.cpu.General) diff --git a/src/build/ast/EachInstruction.go b/src/build/ast/EachInstruction.go new file mode 100644 index 0000000..f11bedc --- /dev/null +++ b/src/build/ast/EachInstruction.go @@ -0,0 +1,46 @@ +package ast + +import "git.akyoto.dev/cli/q/src/build/token" + +// EachInstruction calls the function on each instruction. +func EachInstruction(body token.List, call func(token.List) error) error { + start := 0 + groupLevel := 0 + blockLevel := 0 + + for i, t := range body { + if start == i && t.Kind == token.NewLine { + start = i + 1 + continue + } + + switch t.Kind { + case token.NewLine: + if groupLevel > 0 || blockLevel > 0 { + continue + } + + err := call(body[start:i]) + + if err != nil { + return err + } + + start = i + 1 + + case token.GroupStart: + groupLevel++ + + case token.GroupEnd: + groupLevel-- + + case token.BlockStart: + blockLevel++ + + case token.BlockEnd: + blockLevel-- + } + } + + return nil +} diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go new file mode 100644 index 0000000..b977704 --- /dev/null +++ b/src/build/ast/Parse.go @@ -0,0 +1,101 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/keyword" + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" +) + +// Parse generates an AST from a list of tokens. +func Parse(tokens token.List) (AST, error) { + tree := make(AST, 0, len(tokens)/64) + + err := EachInstruction(tokens, func(instruction token.List) error { + node, err := toASTNode(instruction) + + if err == nil && node != nil { + tree = append(tree, node) + } + + return err + }) + + return tree, err +} + +// toASTNode generates an AST node from an instruction. +func toASTNode(tokens token.List) (Node, error) { + if tokens[0].Kind == token.Keyword { + switch tokens[0].Text() { + case keyword.Return: + value := expression.Parse(tokens[1:]) + return &Return{Value: value}, nil + + case keyword.Loop: + blockStart := tokens.IndexKind(token.BlockStart) + 1 + blockEnd := tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) + } + + if blockEnd == -1 { + return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) + } + + tree, err := Parse(tokens[blockStart:blockEnd]) + return &Loop{Body: tree}, err + + default: + return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, nil, tokens[0].Position) + } + } + + expr := expression.Parse(tokens) + + if expr == nil { + return nil, nil + } + + switch { + case IsVariableDefinition(expr): + if len(expr.Children) < 2 { + return nil, errors.New(errors.MissingAssignValue, nil, expr.LastChild().Token.End()) + } + + name := expr.Children[0].Token + value := expr.Children[1] + return &Define{Name: name, Value: value}, nil + + case IsAssignment(expr): + if len(expr.Children) < 2 { + return nil, errors.New(errors.MissingAssignValue, nil, expr.LastChild().Token.End()) + } + + name := expr.Children[0].Token + value := expr.Children[1] + return &Assign{Name: name, Value: value}, nil + + case IsFunctionCall(expr): + return &Call{Expression: expr}, nil + + default: + return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, nil, expr.Token.Position) + } +} + +// IsAssignment returns true if the expression is an assignment. +func IsAssignment(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '=' +} + +// IsFunctionCall returns true if the expression is a function call. +func IsFunctionCall(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" +} + +// IsVariableDefinition returns true if the expression is a variable definition. +func IsVariableDefinition(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" +} From 9e52e2dd1c9813ebf6260b2f0a6294c73ea384b1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 1 Jul 2024 21:23:36 +0200 Subject: [PATCH 0215/1012] Cleaned up tokenizer --- src/build/token/Token_test.go | 58 +++++++++++++++++++++++++++++++++-- src/build/token/Tokenize.go | 41 ++++++------------------- 2 files changed, 64 insertions(+), 35 deletions(-) diff --git a/src/build/token/Token_test.go b/src/build/token/Token_test.go index 5616634..ac5b206 100644 --- a/src/build/token/Token_test.go +++ b/src/build/token/Token_test.go @@ -138,7 +138,7 @@ func TestNumber(t *testing.T) { } func TestOperator(t *testing.T) { - tokens := token.Tokenize([]byte(`+ - * / ==`)) + tokens := token.Tokenize([]byte(`+ - * /`)) assert.DeepEqual(t, tokens, token.List{ { Kind: token.Operator, @@ -160,15 +160,46 @@ func TestOperator(t *testing.T) { Bytes: []byte("/"), Position: 6, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 7, + }, + }) +} + +func TestOperatorAssign(t *testing.T) { + tokens := token.Tokenize([]byte(`+= -= *= /= ==`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Operator, + Bytes: []byte("+="), + Position: 0, + }, + { + Kind: token.Operator, + Bytes: []byte("-="), + Position: 3, + }, + { + Kind: token.Operator, + Bytes: []byte("*="), + Position: 6, + }, + { + Kind: token.Operator, + Bytes: []byte("/="), + Position: 9, + }, { Kind: token.Operator, Bytes: []byte("=="), - Position: 8, + Position: 12, }, { Kind: token.EOF, Bytes: nil, - Position: 10, + Position: 14, }, }) } @@ -296,6 +327,27 @@ func TestComment(t *testing.T) { }) } +func TestInvalid(t *testing.T) { + tokens := token.Tokenize([]byte(`@#`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Invalid, + Bytes: []byte(`@`), + Position: 0, + }, + { + Kind: token.Invalid, + Bytes: []byte(`#`), + Position: 1, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 2, + }, + }) +} + func TestString(t *testing.T) { tokens := token.Tokenize([]byte(`"Hello" "World"`)) assert.DeepEqual(t, tokens, token.List{ diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 7347252..343411c 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -19,46 +19,28 @@ var ( func Tokenize(buffer []byte) List { var ( i int - tokens = make(List, 0, len(buffer)/2) + tokens = make(List, 0, 8+len(buffer)/2) ) for i < len(buffer) { switch buffer[i] { - // Whitespace case ' ', '\t': - // Separator case ',': tokens = append(tokens, Token{Kind: Separator, Position: i, Bytes: separatorBytes}) - - // Parentheses start case '(': tokens = append(tokens, Token{Kind: GroupStart, Position: i, Bytes: groupStartBytes}) - - // Parentheses end case ')': tokens = append(tokens, Token{Kind: GroupEnd, Position: i, Bytes: groupEndBytes}) - - // Block start case '{': tokens = append(tokens, Token{Kind: BlockStart, Position: i, Bytes: blockStartBytes}) - - // Block end case '}': tokens = append(tokens, Token{Kind: BlockEnd, Position: i, Bytes: blockEndBytes}) - - // Array start case '[': tokens = append(tokens, Token{Kind: ArrayStart, Position: i, Bytes: arrayStartBytes}) - - // Array end case ']': tokens = append(tokens, Token{Kind: ArrayEnd, Position: i, Bytes: arrayEndBytes}) - - // New line case '\n': tokens = append(tokens, Token{Kind: NewLine, Position: i, Bytes: newLineBytes}) - - // Comment case '/': if i+1 >= len(buffer) || buffer[i+1] != '/' { position := i @@ -69,19 +51,18 @@ func Tokenize(buffer []byte) List { } tokens = append(tokens, Token{Kind: Operator, Position: position, Bytes: buffer[position:i]}) - continue + } else { + position := i + + for i < len(buffer) && buffer[i] != '\n' { + i++ + } + + tokens = append(tokens, Token{Kind: Comment, Position: position, Bytes: buffer[position:i]}) } - position := i - - for i < len(buffer) && buffer[i] != '\n' { - i++ - } - - tokens = append(tokens, Token{Kind: Comment, Position: position, Bytes: buffer[position:i]}) continue - // String case '"': start := i end := len(buffer) @@ -101,7 +82,6 @@ func Tokenize(buffer []byte) List { continue default: - // Identifier if isIdentifierStart(buffer[i]) { position := i i++ @@ -122,7 +102,6 @@ func Tokenize(buffer []byte) List { continue } - // Number if isNumber(buffer[i]) { position := i i++ @@ -135,7 +114,6 @@ func Tokenize(buffer []byte) List { continue } - // Operator if isOperator(buffer[i]) { position := i i++ @@ -148,7 +126,6 @@ func Tokenize(buffer []byte) List { continue } - // Invalid character tokens = append(tokens, Token{Kind: Invalid, Position: i, Bytes: buffer[i : i+1]}) } From ed03f6a802e73d4c0ae0e585586abc9c355650af Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 1 Jul 2024 21:23:36 +0200 Subject: [PATCH 0216/1012] Cleaned up tokenizer --- src/build/token/Token_test.go | 58 +++++++++++++++++++++++++++++++++-- src/build/token/Tokenize.go | 41 ++++++------------------- 2 files changed, 64 insertions(+), 35 deletions(-) diff --git a/src/build/token/Token_test.go b/src/build/token/Token_test.go index 5616634..ac5b206 100644 --- a/src/build/token/Token_test.go +++ b/src/build/token/Token_test.go @@ -138,7 +138,7 @@ func TestNumber(t *testing.T) { } func TestOperator(t *testing.T) { - tokens := token.Tokenize([]byte(`+ - * / ==`)) + tokens := token.Tokenize([]byte(`+ - * /`)) assert.DeepEqual(t, tokens, token.List{ { Kind: token.Operator, @@ -160,15 +160,46 @@ func TestOperator(t *testing.T) { Bytes: []byte("/"), Position: 6, }, + { + Kind: token.EOF, + Bytes: nil, + Position: 7, + }, + }) +} + +func TestOperatorAssign(t *testing.T) { + tokens := token.Tokenize([]byte(`+= -= *= /= ==`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Operator, + Bytes: []byte("+="), + Position: 0, + }, + { + Kind: token.Operator, + Bytes: []byte("-="), + Position: 3, + }, + { + Kind: token.Operator, + Bytes: []byte("*="), + Position: 6, + }, + { + Kind: token.Operator, + Bytes: []byte("/="), + Position: 9, + }, { Kind: token.Operator, Bytes: []byte("=="), - Position: 8, + Position: 12, }, { Kind: token.EOF, Bytes: nil, - Position: 10, + Position: 14, }, }) } @@ -296,6 +327,27 @@ func TestComment(t *testing.T) { }) } +func TestInvalid(t *testing.T) { + tokens := token.Tokenize([]byte(`@#`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Invalid, + Bytes: []byte(`@`), + Position: 0, + }, + { + Kind: token.Invalid, + Bytes: []byte(`#`), + Position: 1, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 2, + }, + }) +} + func TestString(t *testing.T) { tokens := token.Tokenize([]byte(`"Hello" "World"`)) assert.DeepEqual(t, tokens, token.List{ diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 7347252..343411c 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -19,46 +19,28 @@ var ( func Tokenize(buffer []byte) List { var ( i int - tokens = make(List, 0, len(buffer)/2) + tokens = make(List, 0, 8+len(buffer)/2) ) for i < len(buffer) { switch buffer[i] { - // Whitespace case ' ', '\t': - // Separator case ',': tokens = append(tokens, Token{Kind: Separator, Position: i, Bytes: separatorBytes}) - - // Parentheses start case '(': tokens = append(tokens, Token{Kind: GroupStart, Position: i, Bytes: groupStartBytes}) - - // Parentheses end case ')': tokens = append(tokens, Token{Kind: GroupEnd, Position: i, Bytes: groupEndBytes}) - - // Block start case '{': tokens = append(tokens, Token{Kind: BlockStart, Position: i, Bytes: blockStartBytes}) - - // Block end case '}': tokens = append(tokens, Token{Kind: BlockEnd, Position: i, Bytes: blockEndBytes}) - - // Array start case '[': tokens = append(tokens, Token{Kind: ArrayStart, Position: i, Bytes: arrayStartBytes}) - - // Array end case ']': tokens = append(tokens, Token{Kind: ArrayEnd, Position: i, Bytes: arrayEndBytes}) - - // New line case '\n': tokens = append(tokens, Token{Kind: NewLine, Position: i, Bytes: newLineBytes}) - - // Comment case '/': if i+1 >= len(buffer) || buffer[i+1] != '/' { position := i @@ -69,19 +51,18 @@ func Tokenize(buffer []byte) List { } tokens = append(tokens, Token{Kind: Operator, Position: position, Bytes: buffer[position:i]}) - continue + } else { + position := i + + for i < len(buffer) && buffer[i] != '\n' { + i++ + } + + tokens = append(tokens, Token{Kind: Comment, Position: position, Bytes: buffer[position:i]}) } - position := i - - for i < len(buffer) && buffer[i] != '\n' { - i++ - } - - tokens = append(tokens, Token{Kind: Comment, Position: position, Bytes: buffer[position:i]}) continue - // String case '"': start := i end := len(buffer) @@ -101,7 +82,6 @@ func Tokenize(buffer []byte) List { continue default: - // Identifier if isIdentifierStart(buffer[i]) { position := i i++ @@ -122,7 +102,6 @@ func Tokenize(buffer []byte) List { continue } - // Number if isNumber(buffer[i]) { position := i i++ @@ -135,7 +114,6 @@ func Tokenize(buffer []byte) List { continue } - // Operator if isOperator(buffer[i]) { position := i i++ @@ -148,7 +126,6 @@ func Tokenize(buffer []byte) List { continue } - // Invalid character tokens = append(tokens, Token{Kind: Invalid, Position: i, Bytes: buffer[i : i+1]}) } From fd6e874b449706446b71f7dd6c135d4934b7cda6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 3 Jul 2024 11:39:24 +0200 Subject: [PATCH 0217/1012] Refactored code structure --- README.md | 18 ++- examples/hello/hello.q | 7 +- examples/write/write.q | 2 +- src/build/Assignment.go | 19 --- src/build/Build.go | 29 +++-- src/build/ExpressionToRegister.go | 48 ------- src/build/Instruction.go | 1 - src/build/Write.go | 29 ----- src/build/arch/x64/Registers.go | 2 +- src/build/asm/Assembler.go | 3 + src/build/asm/Instructions.go | 10 ++ src/build/asm/Mnemonic.go | 18 +-- src/build/ast/AST.go | 30 ++++- src/build/ast/Parse.go | 4 +- src/build/{Build_test.go => build_test.go} | 15 ++- src/build/compiler.go | 81 ------------ src/build/config/config.go | 3 +- src/build/{compile.go => core/Compile.go} | 31 ++--- src/build/core/CompileAllFunctions.go | 19 +++ src/build/core/CompileAssign.go | 19 +++ src/build/{Call.go => core/CompileCall.go} | 43 +----- .../CompileDefinition.go} | 53 +++++--- src/build/{Loop.go => core/CompileLoop.go} | 2 +- .../{Return.go => core/CompileReturn.go} | 4 +- src/build/core/Evaluate.go | 123 ++++++++++++++++++ src/build/{ => core}/Execute.go | 17 +-- src/build/{ => core}/Function.go | 61 +++++---- src/build/{ => core}/Result.go | 54 ++++++-- src/build/{ => core}/SaveRegister.go | 15 +-- src/build/{ => core}/TokenToRegister.go | 11 +- src/build/{ => core}/Variable.go | 3 +- src/build/{Math.go => core/opRegister.go} | 12 +- src/build/core/state.go | 58 +++++++++ src/build/cpu/CPU.go | 11 ++ src/{ => build}/errors/Base.go | 0 src/{ => build}/errors/CompileErrors.go | 0 src/{ => build}/errors/Error.go | 0 src/{ => build}/errors/InvalidCharacter.go | 0 src/{ => build}/errors/InvalidInstruction.go | 0 src/{ => build}/errors/InvalidOperator.go | 0 .../errors/KeywordNotImplemented.go | 0 src/{ => build}/errors/ScanErrors.go | 0 src/{ => build}/errors/Stack.go | 0 src/{ => build}/errors/UnknownIdentifier.go | 0 src/{ => build}/errors/UnusedVariable.go | 0 .../errors/VariableAlreadyExists.go | 0 src/build/{scan.go => scanner/Scan.go} | 78 +++++------ src/cli/Build.go | 30 +++-- src/cli/Help.go | 3 +- bench_test.go => tests/benchmarks_test.go | 6 +- errors_test.go => tests/errors_test.go | 6 +- tests/examples_test.go | 23 ++++ tests/programs/successive-calls.q | 7 + examples_test.go => tests/programs_test.go | 25 ++-- 54 files changed, 583 insertions(+), 450 deletions(-) delete mode 100644 src/build/Assignment.go delete mode 100644 src/build/ExpressionToRegister.go delete mode 100644 src/build/Instruction.go delete mode 100644 src/build/Write.go rename src/build/{Build_test.go => build_test.go} (53%) delete mode 100644 src/build/compiler.go rename src/build/{compile.go => core/Compile.go} (59%) create mode 100644 src/build/core/CompileAllFunctions.go create mode 100644 src/build/core/CompileAssign.go rename src/build/{Call.go => core/CompileCall.go} (51%) rename src/build/{VariableDefinition.go => core/CompileDefinition.go} (64%) rename src/build/{Loop.go => core/CompileLoop.go} (95%) rename src/build/{Return.go => core/CompileReturn.go} (75%) create mode 100644 src/build/core/Evaluate.go rename src/build/{ => core}/Execute.go (75%) rename src/build/{ => core}/Function.go (57%) rename src/build/{ => core}/Result.go (59%) rename src/build/{ => core}/SaveRegister.go (69%) rename src/build/{ => core}/TokenToRegister.go (81%) rename src/build/{ => core}/Variable.go (88%) rename src/build/{Math.go => core/opRegister.go} (74%) create mode 100644 src/build/core/state.go rename src/{ => build}/errors/Base.go (100%) rename src/{ => build}/errors/CompileErrors.go (100%) rename src/{ => build}/errors/Error.go (100%) rename src/{ => build}/errors/InvalidCharacter.go (100%) rename src/{ => build}/errors/InvalidInstruction.go (100%) rename src/{ => build}/errors/InvalidOperator.go (100%) rename src/{ => build}/errors/KeywordNotImplemented.go (100%) rename src/{ => build}/errors/ScanErrors.go (100%) rename src/{ => build}/errors/Stack.go (100%) rename src/{ => build}/errors/UnknownIdentifier.go (100%) rename src/{ => build}/errors/UnusedVariable.go (100%) rename src/{ => build}/errors/VariableAlreadyExists.go (100%) rename src/build/{scan.go => scanner/Scan.go} (75%) rename bench_test.go => tests/benchmarks_test.go (72%) rename errors_test.go => tests/errors_test.go (93%) create mode 100644 tests/examples_test.go create mode 100644 tests/programs/successive-calls.q rename examples_test.go => tests/programs_test.go (60%) diff --git a/README.md b/README.md index e5e860f..866b982 100644 --- a/README.md +++ b/README.md @@ -83,22 +83,30 @@ Each function will then be translated to generic assembler instructions. All the functions that are required to run the program will be added to the final assembler. The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. -### [src/build/Function.go](src/build/Function.go) +### [src/build/core/Function.go](src/build/core/Function.go) This is the "heart" of the compiler. -Each function runs `f.Compile()` which organizes the source code into instructions that are then compiled via `f.CompileInstruction`. -You can think of instructions as the individual lines in your source code, but instructions can also span over multiple lines. +Each function runs `f.Compile` which organizes the source code into an abstract syntax tree that is then compiled via `f.CompileAST`. +You can think of AST nodes as the individual statements in your source code. + +### [src/build/ast/Parse.go](src/build/ast/Parse.go) + +This is what generates the AST from tokens. + +### [src/build/expression/Parse.go](src/build/expression/Parse.go) + +This is what generates expressions from tokens. ## Tests ```shell -go test -coverpkg=./... +go test ./... -v ``` ## Benchmarks ```shell -go test -bench=. -benchmem +go test ./tests -bench=. -benchmem ``` ## License diff --git a/examples/hello/hello.q b/examples/hello/hello.q index aace245..bffc930 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,8 +1,7 @@ main() { - x := f(2, 3) - syscall(60, x) + syscall(60, f(1) + f(2) + f(3)) } -f(x, y) { - return (x + y) * (x + y) +f(x) { + return x + 1 } \ No newline at end of file diff --git a/examples/write/write.q b/examples/write/write.q index c06884e..31b18d6 100644 --- a/examples/write/write.q +++ b/examples/write/write.q @@ -5,7 +5,7 @@ main() { } print(address, length) { - write(length-2, address, length) + write(1, address, length) } write(fd, address, length) { diff --git a/src/build/Assignment.go b/src/build/Assignment.go deleted file mode 100644 index 66703da..0000000 --- a/src/build/Assignment.go +++ /dev/null @@ -1,19 +0,0 @@ -package build - -import ( - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/errors" -) - -// CompileAssignment compiles an assignment. -func (f *Function) CompileAssignment(expr *expression.Expression) error { - name := expr.Children[0].Token.Text() - variable, exists := f.variables[name] - - if !exists { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) - } - - defer f.useVariable(variable) - return f.Execute(expr.Token, variable.Register, expr.Children[1]) -} diff --git a/src/build/Build.go b/src/build/Build.go index 2821e2d..bf90d2c 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -3,6 +3,9 @@ package build import ( "path/filepath" "strings" + + "git.akyoto.dev/cli/q/src/build/core" + "git.akyoto.dev/cli/q/src/build/scanner" ) // Build describes a compiler build. @@ -18,18 +21,28 @@ func New(files ...string) *Build { } // Run parses the input files and generates an executable file. -func (build *Build) Run() (Result, error) { - functions, errors := scan(build.Files) - return compile(functions, errors) +func (build *Build) Run() (core.Result, error) { + functions, errors := scanner.Scan(build.Files) + return core.Compile(functions, errors) } // Executable returns the path to the executable. func (build *Build) Executable() string { - directory, _ := filepath.Abs(build.Files[0]) + path, _ := filepath.Abs(build.Files[0]) - if strings.HasSuffix(directory, ".q") { - directory = filepath.Dir(directory) + if strings.HasSuffix(path, ".q") { + return fromFileName(path) + } else { + return fromDirectoryName(path) } - - return filepath.Join(directory, filepath.Base(directory)) +} + +// fromDirectoryName returns the executable path based on the directory name. +func fromDirectoryName(path string) string { + return filepath.Join(path, filepath.Base(path)) +} + +// fromFileName returns the executable path based on the file name. +func fromFileName(path string) string { + return filepath.Join(filepath.Dir(path), strings.TrimSuffix(filepath.Base(path), ".q")) } diff --git a/src/build/ExpressionToRegister.go b/src/build/ExpressionToRegister.go deleted file mode 100644 index cf0a04c..0000000 --- a/src/build/ExpressionToRegister.go +++ /dev/null @@ -1,48 +0,0 @@ -package build - -import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" -) - -// ExpressionToRegister moves the result of an expression into the given register. -func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { - if config.Verbose { - f.Logf("%s to register %s", root, register) - } - - operation := root.Token - - if root.IsLeaf() { - return f.TokenToRegister(operation, register) - } - - if ast.IsFunctionCall(root) { - err := f.CompileFunctionCall(root) - - if err != nil { - return err - } - - if register != f.cpu.Return[0] { - f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Return[0]) - } - - return nil - } - - left := root.Children[0] - right := root.Children[1] - - err := f.ExpressionToRegister(left, register) - - if err != nil { - return err - } - - f.SaveRegister(register) - return f.Execute(operation, register, right) -} diff --git a/src/build/Instruction.go b/src/build/Instruction.go deleted file mode 100644 index 5c93a93..0000000 --- a/src/build/Instruction.go +++ /dev/null @@ -1 +0,0 @@ -package build diff --git a/src/build/Write.go b/src/build/Write.go deleted file mode 100644 index f6385bb..0000000 --- a/src/build/Write.go +++ /dev/null @@ -1,29 +0,0 @@ -package build - -import ( - "bufio" - "os" - - "git.akyoto.dev/cli/q/src/build/elf" -) - -// Write writes the executable file to disk. -func Write(filePath string, code []byte, data []byte) error { - file, err := os.Create(filePath) - - if err != nil { - return err - } - - buffer := bufio.NewWriter(file) - executable := elf.New(code, data) - executable.Write(buffer) - buffer.Flush() - err = file.Close() - - if err != nil { - return err - } - - return os.Chmod(filePath, 0755) -} diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go index 8104f84..fafea37 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/build/arch/x64/Registers.go @@ -23,7 +23,7 @@ const ( var ( CallRegisters = SyscallRegisters - GeneralRegisters = []cpu.Register{RBX, RBP, R12, R13, R14, R15} + GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} ReturnValueRegisters = []cpu.Register{RAX, RCX, R11} ) diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 9c6f990..9a9f19e 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -63,6 +63,9 @@ func (a Assembler) Finalize() ([]byte, []byte) { }, }) + case COMMENT: + continue + case JUMP: code = x64.Jump8(code, 0x00) size := 1 diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 4126bcf..6b547a2 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -44,6 +44,16 @@ func (a *Assembler) Label(name string) { }) } +// Comment adds a comment at the current position. +func (a *Assembler) Comment(text string) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: COMMENT, + Data: &Label{ + Name: text, + }, + }) +} + // Call calls a function whose position is identified by a label. func (a *Assembler) Call(name string) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 77dbf81..315af19 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -6,6 +6,7 @@ const ( NONE Mnemonic = iota ADD CALL + COMMENT DIV JUMP MUL @@ -23,40 +24,31 @@ func (m Mnemonic) String() string { switch m { case ADD: return "add" - case CALL: return "call" - + case COMMENT: + return "comment" case DIV: return "div" - case JUMP: return "jump" - case LABEL: return "label" - case MOVE: return "move" - case MUL: return "mul" - case POP: return "pop" - case PUSH: return "push" - case RETURN: return "return" - case SUB: return "sub" - case SYSCALL: return "syscall" + default: + return "" } - - return "NONE" } diff --git a/src/build/ast/AST.go b/src/build/ast/AST.go index 2d8314a..d1b991b 100644 --- a/src/build/ast/AST.go +++ b/src/build/ast/AST.go @@ -1,36 +1,54 @@ package ast import ( + "fmt" + "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" ) -type Node interface{} +type Node fmt.Stringer type AST []Node type Assign struct { - Value *expression.Expression - Name token.Token + Value *expression.Expression + Name token.Token + Operator token.Token +} + +func (node *Assign) String() string { + return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) } type Call struct { Expression *expression.Expression } +func (node *Call) String() string { + return node.Expression.String() +} + type Define struct { Value *expression.Expression Name token.Token } -type If struct { - Condition *expression.Expression - Body AST +func (node *Define) String() string { + return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) } type Loop struct { Body AST } +func (node *Loop) String() string { + return fmt.Sprintf("(loop %s)", node.Body) +} + type Return struct { Value *expression.Expression } + +func (node *Return) String() string { + return fmt.Sprintf("(return %s)", node.Value) +} diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index b977704..69e6756 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -1,10 +1,10 @@ package ast import ( + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/keyword" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) // Parse generates an AST from a list of tokens. @@ -75,7 +75,7 @@ func toASTNode(tokens token.List) (Node, error) { name := expr.Children[0].Token value := expr.Children[1] - return &Assign{Name: name, Value: value}, nil + return &Assign{Name: name, Value: value, Operator: expr.Token}, nil case IsFunctionCall(expr): return &Call{Expression: expr}, nil diff --git a/src/build/Build_test.go b/src/build/build_test.go similarity index 53% rename from src/build/Build_test.go rename to src/build/build_test.go index 88d8e52..36242d8 100644 --- a/src/build/Build_test.go +++ b/src/build/build_test.go @@ -8,17 +8,28 @@ import ( "git.akyoto.dev/go/assert" ) -func TestBuild(t *testing.T) { +func TestBuildDirectory(t *testing.T) { b := build.New("../../examples/hello") _, err := b.Run() assert.Nil(t, err) } -func TestExecutable(t *testing.T) { +func TestBuildFile(t *testing.T) { + b := build.New("../../examples/hello/hello.q") + _, err := b.Run() + assert.Nil(t, err) +} + +func TestExecutableFromDirectory(t *testing.T) { b := build.New("../../examples/hello") assert.Equal(t, filepath.Base(b.Executable()), "hello") } +func TestExecutableFromFile(t *testing.T) { + b := build.New("../../examples/hello/hello.q") + assert.Equal(t, filepath.Base(b.Executable()), "hello") +} + func TestNonExisting(t *testing.T) { b := build.New("does-not-exist") _, err := b.Run() diff --git a/src/build/compiler.go b/src/build/compiler.go deleted file mode 100644 index ac28b37..0000000 --- a/src/build/compiler.go +++ /dev/null @@ -1,81 +0,0 @@ -package build - -import ( - "fmt" - - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/go/color/ansi" -) - -// compiler is the data structure we embed in each function to preserve compilation state. -type compiler struct { - err error - definitions map[string]*Definition - variables map[string]*Variable - functions map[string]*Function - finished chan struct{} - assembler asm.Assembler - debug []debug - cpu cpu.CPU - count counter - sideEffects int -} - -// counter stores how often a certain statement appeared so we can generate a unique label from it. -type counter struct { - loop int -} - -// debug is used to look up the source code at the given position. -type debug struct { - source ast.Node - position int -} - -// PrintInstructions shows the assembly instructions. -func (c *compiler) PrintInstructions() { - ansi.Dim.Println("╭──────────────────────────────────────╮") - - for i, x := range c.assembler.Instructions { - instruction := c.sourceAt(i) - - if instruction != nil { - ansi.Dim.Println("├──────────────────────────────────────┤") - } - - ansi.Dim.Print("│ ") - - if x.Mnemonic == asm.LABEL { - ansi.Yellow.Printf("%-36s", x.Data.String()+":") - } else { - ansi.Green.Printf("%-8s", x.Mnemonic.String()) - - if x.Data != nil { - fmt.Printf("%-28s", x.Data.String()) - } else { - fmt.Printf("%-28s", "") - } - } - - ansi.Dim.Print(" │\n") - } - - ansi.Dim.Println("╰──────────────────────────────────────╯") -} - -// sourceAt retrieves the source code at the given position or `nil`. -func (c *compiler) sourceAt(position int) ast.Node { - for _, record := range c.debug { - if record.position == position { - return record.source - } - - if record.position > position { - return nil - } - } - - return nil -} diff --git a/src/build/config/config.go b/src/build/config/config.go index d87cf95..4f97a22 100644 --- a/src/build/config/config.go +++ b/src/build/config/config.go @@ -9,5 +9,6 @@ const ( var ( Assembler = false - Verbose = false + Comments = false + Dry = false ) diff --git a/src/build/compile.go b/src/build/core/Compile.go similarity index 59% rename from src/build/compile.go rename to src/build/core/Compile.go index 0430873..edd8712 100644 --- a/src/build/compile.go +++ b/src/build/core/Compile.go @@ -1,13 +1,11 @@ -package build +package core import ( - "sync" - - "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/build/errors" ) -// compile waits for the scan to finish and compiles all functions. -func compile(functions <-chan *Function, errs <-chan error) (Result, error) { +// Compile waits for the scan to finish and compiles all functions. +func Compile(functions <-chan *Function, errs <-chan error) (Result, error) { result := Result{} allFunctions := map[string]*Function{} @@ -32,8 +30,10 @@ func compile(functions <-chan *Function, errs <-chan error) (Result, error) { } } - compileFunctions(allFunctions) + // Start parallel compilation + CompileAllFunctions(allFunctions) + // Report errors if any occurred for _, function := range allFunctions { if function.err != nil { return result, function.err @@ -42,6 +42,7 @@ func compile(functions <-chan *Function, errs <-chan error) (Result, error) { result.InstructionCount += len(function.assembler.Instructions) } + // Check for existence of `main` main, exists := allFunctions["main"] if !exists { @@ -52,19 +53,3 @@ func compile(functions <-chan *Function, errs <-chan error) (Result, error) { result.Functions = allFunctions return result, nil } - -// compileFunctions starts a goroutine for each function compilation and waits for completion. -func compileFunctions(functions map[string]*Function) { - wg := sync.WaitGroup{} - - for _, function := range functions { - wg.Add(1) - - go func() { - defer wg.Done() - function.Compile() - }() - } - - wg.Wait() -} diff --git a/src/build/core/CompileAllFunctions.go b/src/build/core/CompileAllFunctions.go new file mode 100644 index 0000000..66d3ef9 --- /dev/null +++ b/src/build/core/CompileAllFunctions.go @@ -0,0 +1,19 @@ +package core + +import "sync" + +// CompileAllFunctions starts a goroutine for each function compilation and waits for completion. +func CompileAllFunctions(functions map[string]*Function) { + wg := sync.WaitGroup{} + + for _, function := range functions { + wg.Add(1) + + go func() { + defer wg.Done() + function.Compile() + }() + } + + wg.Wait() +} diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go new file mode 100644 index 0000000..ce46a34 --- /dev/null +++ b/src/build/core/CompileAssign.go @@ -0,0 +1,19 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/errors" +) + +// CompileAssign compiles an assign statement. +func (f *Function) CompileAssign(node *ast.Assign) error { + name := node.Name.Text() + variable, exists := f.variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Name.Position) + } + + defer f.useVariable(variable) + return f.Execute(node.Operator, variable.Register, node.Value) +} diff --git a/src/build/Call.go b/src/build/core/CompileCall.go similarity index 51% rename from src/build/Call.go rename to src/build/core/CompileCall.go index 44df7c9..283439c 100644 --- a/src/build/Call.go +++ b/src/build/core/CompileCall.go @@ -1,39 +1,16 @@ -package build +package core import ( - "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" ) -// CompileFunctionCall executes a function call. +// CompileCall executes a function call. // All call registers must hold the correct parameter values before the function invocation. // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. -func (f *Function) CompileFunctionCall(expr *expression.Expression) error { - funcName := expr.Children[0].Token.Text() - - if funcName == "syscall" { - return f.CompileSyscall(expr) - } - - function := f.functions[funcName] - - if function != f { - function.Wait() - } - - parameters := expr.Children[1:] - registers := f.cpu.Call[:len(parameters)] - - err := f.ExpressionsToRegisters(parameters, registers) - - if config.Verbose { - f.Logf("call: %s", funcName) - } - - f.assembler.Call(funcName) - f.sideEffects += function.sideEffects +func (f *Function) CompileCall(expr *expression.Expression) error { + _, err := f.EvaluateCall(expr) return err } @@ -49,15 +26,9 @@ func (f *Function) CompileSyscall(expr *expression.Expression) error { // ExpressionsToRegisters moves multiple expressions into the specified registers. func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { - for _, register := range registers { - f.SaveRegister(register) - } - - for i := len(expressions) - 1; i >= 0; i-- { - expression := expressions[i] - register := registers[i] - - err := f.ExpressionToRegister(expression, register) + for i := len(registers) - 1; i >= 0; i-- { + f.SaveRegister(registers[i]) + err := f.EvaluateTo(expressions[i], registers[i]) if err != nil { return err diff --git a/src/build/VariableDefinition.go b/src/build/core/CompileDefinition.go similarity index 64% rename from src/build/VariableDefinition.go rename to src/build/core/CompileDefinition.go index f385c24..7abd309 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -1,22 +1,24 @@ -package build +package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) -// CompileVariableDefinition compiles a variable definition. -func (f *Function) CompileVariableDefinition(node *ast.Define) error { +// CompileDefinition compiles a variable definition. +func (f *Function) CompileDefinition(node *ast.Define) error { name := node.Name.Text() if f.identifierExists(name) { return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, node.Name.Position) } - uses := countIdentifier(f.Body, name) - 1 + uses := CountIdentifier(f.Body, name) - 1 if uses == 0 { return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) @@ -48,29 +50,46 @@ func (f *Function) CompileVariableDefinition(node *ast.Define) error { return f.storeVariableInRegister(name, value, uses) } -func (f *Function) addVariable(variable *Variable) { - if config.Verbose { - f.Logf("%s occupies %s (alive: %d)", variable.Name, variable.Register, variable.Alive) +func (f *Function) AddVariable(variable *Variable) { + if config.Comments { + f.assembler.Comment(fmt.Sprintf("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive)) } f.variables[variable.Name] = variable f.cpu.Use(variable.Register) } +func (f *Function) addTemporary(root *expression.Expression) *Variable { + f.count.tmps++ + name := fmt.Sprintf("t%d", f.count.tmps) + register := f.cpu.MustUseFree(f.cpu.General) + + tmp := &Variable{ + Name: name, + Value: root, + Alive: 1, + Register: register, + } + + f.variables[name] = tmp + + if config.Comments { + f.assembler.Comment(fmt.Sprintf("%s = %s (%s)", name, root, register)) + } + + return tmp +} + func (f *Function) useVariable(variable *Variable) { variable.Alive-- - if config.Verbose { - f.Logf("%s occupying %s was used (alive: %d)", variable.Name, variable.Register, variable.Alive) - } - if variable.Alive < 0 { panic("incorrect number of variable use calls") } if variable.Alive == 0 { - if config.Verbose { - f.Logf("%s is no longer used, free register: %s", variable.Name, variable.Register) + if config.Comments { + f.assembler.Comment(fmt.Sprintf("%s died (%s)", variable.Name, variable.Register)) } f.cpu.Free(variable.Register) @@ -102,9 +121,9 @@ func (f *Function) storeVariableInRegister(name string, value *expression.Expres panic("no free registers") } - err := f.ExpressionToRegister(value, reg) + err := f.EvaluateTo(value, reg) - f.addVariable(&Variable{ + f.AddVariable(&Variable{ Name: name, Register: reg, Alive: uses, @@ -113,7 +132,7 @@ func (f *Function) storeVariableInRegister(name string, value *expression.Expres return err } -func countIdentifier(tokens token.List, name string) int { +func CountIdentifier(tokens token.List, name string) int { count := 0 for _, t := range tokens { diff --git a/src/build/Loop.go b/src/build/core/CompileLoop.go similarity index 95% rename from src/build/Loop.go rename to src/build/core/CompileLoop.go index 2165eca..37a3c94 100644 --- a/src/build/Loop.go +++ b/src/build/core/CompileLoop.go @@ -1,4 +1,4 @@ -package build +package core import ( "fmt" diff --git a/src/build/Return.go b/src/build/core/CompileReturn.go similarity index 75% rename from src/build/Return.go rename to src/build/core/CompileReturn.go index c661fc7..c829a91 100644 --- a/src/build/Return.go +++ b/src/build/core/CompileReturn.go @@ -1,4 +1,4 @@ -package build +package core import ( "git.akyoto.dev/cli/q/src/build/ast" @@ -12,5 +12,5 @@ func (f *Function) CompileReturn(node *ast.Return) error { return nil } - return f.ExpressionToRegister(node.Value, f.cpu.Return[0]) + return f.EvaluateTo(node.Value, f.cpu.Return[0]) } diff --git a/src/build/core/Evaluate.go b/src/build/core/Evaluate.go new file mode 100644 index 0000000..ed65df0 --- /dev/null +++ b/src/build/core/Evaluate.go @@ -0,0 +1,123 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Evaluate evaluates the result of an expression and saves it into a temporary register. +func (f *Function) Evaluate(root *expression.Expression) (*Variable, error) { + if root.IsLeaf() { + return f.EvaluateLeaf(root) + } + + if ast.IsFunctionCall(root) { + return f.EvaluateCall(root) + } + + left := root.Children[0] + right := root.Children[1] + + tmpLeft, err := f.Evaluate(left) + + if err != nil { + return nil, err + } + + tmpLeftExpr := expression.NewLeaf(token.Token{ + Kind: token.Identifier, + Position: left.Token.Position, + Bytes: []byte(tmpLeft.Name), + }) + + tmpLeftExpr.Parent = root + root.Children[0].Parent = nil + root.Children[0] = tmpLeftExpr + + tmpRight, err := f.Evaluate(right) + + if err != nil { + return nil, err + } + + tmpRightExpr := expression.NewLeaf(token.Token{ + Kind: token.Identifier, + Position: left.Token.Position, + Bytes: []byte(tmpRight.Name), + }) + + tmpRightExpr.Parent = root + root.Children[1].Parent = nil + root.Children[1] = tmpRightExpr + + tmp := f.addTemporary(root) + + f.assembler.RegisterRegister(asm.MOVE, tmp.Register, tmpLeft.Register) + f.useVariable(tmpLeft) + + err = f.opRegisterRegister(root.Token, tmp.Register, tmpRight.Register) + f.useVariable(tmpRight) + + return tmp, err +} + +func (f *Function) EvaluateCall(root *expression.Expression) (*Variable, error) { + funcName := root.Children[0].Token.Text() + parameters := root.Children[1:] + registers := f.cpu.Call[:len(parameters)] + isSyscall := funcName == "syscall" + + if isSyscall { + registers = f.cpu.Syscall[:len(parameters)] + } + + err := f.ExpressionsToRegisters(parameters, registers) + + for _, register := range f.cpu.General { + if !f.cpu.IsFree(register) { + f.assembler.Register(asm.PUSH, register) + } + } + + if isSyscall { + f.assembler.Syscall() + } else { + f.assembler.Call(funcName) + } + + for i := len(f.cpu.General) - 1; i >= 0; i-- { + register := f.cpu.General[i] + + if !f.cpu.IsFree(register) { + f.assembler.Register(asm.POP, register) + } + } + + tmp := f.addTemporary(root) + f.assembler.RegisterRegister(asm.MOVE, tmp.Register, f.cpu.Return[0]) + return tmp, err +} + +func (f *Function) EvaluateLeaf(root *expression.Expression) (*Variable, error) { + tmp := f.addTemporary(root) + err := f.TokenToRegister(root.Token, tmp.Register) + return tmp, err +} + +func (f *Function) EvaluateTo(root *expression.Expression, register cpu.Register) error { + tmp, err := f.Evaluate(root) + + if err != nil { + return err + } + + if register != tmp.Register { + f.assembler.RegisterRegister(asm.MOVE, register, tmp.Register) + } + + f.useVariable(tmp) + return nil +} diff --git a/src/build/Execute.go b/src/build/core/Execute.go similarity index 75% rename from src/build/Execute.go rename to src/build/core/Execute.go index e85cd1e..067f854 100644 --- a/src/build/Execute.go +++ b/src/build/core/Execute.go @@ -1,22 +1,17 @@ -package build +package core import ( "strconv" "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) // Execute executes an operation on a register with a value operand. func (f *Function) Execute(operation token.Token, register cpu.Register, value *expression.Expression) error { - if config.Verbose { - f.Logf("execute: %s on register %s with value %s", operation.Text(), register, value) - } - if value.IsLeaf() { return f.ExecuteLeaf(operation, register, value.Token) } @@ -36,13 +31,13 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * f.cpu.Use(temporary) defer f.cpu.Free(temporary) - err := f.ExpressionToRegister(value, temporary) + err := f.EvaluateTo(value, temporary) if err != nil { return err } - return f.ExecuteRegisterRegister(operation, register, temporary) + return f.opRegisterRegister(operation, register, temporary) } // ExecuteLeaf performs an operation on a register with the given leaf operand. @@ -57,7 +52,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope } defer f.useVariable(variable) - return f.ExecuteRegisterRegister(operation, register, variable.Register) + return f.opRegisterRegister(operation, register, variable.Register) case token.Number: value := operand.Text() @@ -67,7 +62,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope return err } - return f.ExecuteRegisterNumber(operation, register, number) + return f.opRegisterNumber(operation, register, number) } return errors.New(errors.NotImplemented, f.File, operation.Position) diff --git a/src/build/Function.go b/src/build/core/Function.go similarity index 57% rename from src/build/Function.go rename to src/build/core/Function.go index de87e2f..518affb 100644 --- a/src/build/Function.go +++ b/src/build/core/Function.go @@ -1,21 +1,46 @@ -package build +package core import ( "fmt" + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) -// Function represents a function. +// Function represents the smallest unit of code. type Function struct { - File *fs.File Name string + File *fs.File Body token.List - compiler + state +} + +// NewFunction creates a new function. +func NewFunction(name string, file *fs.File, body token.List) *Function { + return &Function{ + Name: name, + File: file, + Body: body, + state: state{ + assembler: asm.Assembler{ + Instructions: make([]asm.Instruction, 0, 32), + }, + cpu: cpu.CPU{ + Call: x64.CallRegisters, + General: x64.GeneralRegisters, + Syscall: x64.SyscallRegisters, + Return: x64.ReturnValueRegisters, + }, + definitions: map[string]*Definition{}, + variables: map[string]*Variable{}, + finished: make(chan struct{}), + }, + } } // Compile turns a function into machine code. @@ -41,18 +66,7 @@ func (f *Function) CompileTokens(tokens token.List) error { // CompileAST compiles an abstract syntax tree. func (f *Function) CompileAST(tree ast.AST) error { for _, node := range tree { - if config.Verbose { - f.Logf("%T %s", node, node) - } - - if config.Assembler { - f.debug = append(f.debug, debug{ - position: len(f.assembler.Instructions), - source: node, - }) - } - - err := f.CompileNode(node) + err := f.CompileASTNode(node) if err != nil { return err @@ -62,14 +76,17 @@ func (f *Function) CompileAST(tree ast.AST) error { return nil } -// CompileNode compiles a node in the AST. -func (f *Function) CompileNode(node ast.Node) error { +// CompileASTNode compiles a node in the AST. +func (f *Function) CompileASTNode(node ast.Node) error { switch node := node.(type) { + case *ast.Assign: + return f.CompileAssign(node) + case *ast.Call: - return f.CompileFunctionCall(node.Expression) + return f.CompileCall(node.Expression) case *ast.Define: - return f.CompileVariableDefinition(node) + return f.CompileDefinition(node) case *ast.Return: return f.CompileReturn(node) diff --git a/src/build/Result.go b/src/build/core/Result.go similarity index 59% rename from src/build/Result.go rename to src/build/core/Result.go index 6a5e94b..9430012 100644 --- a/src/build/Result.go +++ b/src/build/core/Result.go @@ -1,8 +1,12 @@ -package build +package core import ( + "bufio" + "os" + "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/elf" "git.akyoto.dev/cli/q/src/build/os/linux" ) @@ -13,8 +17,8 @@ type Result struct { InstructionCount int } -// Finalize generates the final machine code. -func (r *Result) Finalize() ([]byte, []byte) { +// finalize generates the final machine code. +func (r *Result) finalize() ([]byte, []byte) { // This will be the entry point of the executable. // The only job of the entry function is to call `main` and exit cleanly. // The reason we call `main` instead of using `main` itself is to place @@ -30,7 +34,7 @@ func (r *Result) Finalize() ([]byte, []byte) { // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. - r.EachFunction(r.Main, map[*Function]bool{}, func(f *Function) { + r.eachFunction(r.Main, map[*Function]bool{}, func(f *Function) { final.Merge(f.assembler) }) @@ -38,9 +42,9 @@ func (r *Result) Finalize() ([]byte, []byte) { return code, data } -// EachFunction recursively finds all the calls to external functions. +// eachFunction recursively finds all the calls to external functions. // It avoids calling the same function twice with the help of a hashmap. -func (r *Result) EachFunction(caller *Function, traversed map[*Function]bool, call func(*Function)) { +func (r *Result) eachFunction(caller *Function, traversed map[*Function]bool, call func(*Function)) { call(caller) traversed[caller] = true @@ -60,12 +64,40 @@ func (r *Result) EachFunction(caller *Function, traversed map[*Function]bool, ca continue } - r.EachFunction(callee, traversed, call) + r.eachFunction(callee, traversed, call) } } -// Write write the final executable to disk. -func (r *Result) Write(path string) error { - code, data := r.Finalize() - return Write(path, code, data) +// PrintInstructions prints out the generated instructions. +func (r *Result) PrintInstructions() { + r.eachFunction(r.Main, map[*Function]bool{}, func(f *Function) { + f.PrintInstructions() + }) +} + +// Write writes an executable file to disk. +func (r *Result) Write(path string) error { + code, data := r.finalize() + return write(path, code, data) +} + +// write writes an executable file to disk. +func write(path string, code []byte, data []byte) error { + file, err := os.Create(path) + + if err != nil { + return err + } + + buffer := bufio.NewWriter(file) + executable := elf.New(code, data) + executable.Write(buffer) + buffer.Flush() + err = file.Close() + + if err != nil { + return err + } + + return os.Chmod(path, 0755) } diff --git a/src/build/SaveRegister.go b/src/build/core/SaveRegister.go similarity index 69% rename from src/build/SaveRegister.go rename to src/build/core/SaveRegister.go index d74c95d..99d5375 100644 --- a/src/build/SaveRegister.go +++ b/src/build/core/SaveRegister.go @@ -1,6 +1,8 @@ -package build +package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" @@ -25,18 +27,13 @@ func (f *Function) SaveRegister(register cpu.Register) { return } - newRegister, exists := f.cpu.FindFree(f.cpu.General) + newRegister := f.cpu.MustUseFree(f.cpu.General) - if !exists { - panic("no free registers") - } - - if config.Verbose { - f.Logf("moving %s from %s to %s (alive: %d)", variable.Name, variable.Register, newRegister, variable.Alive) + if config.Comments { + f.assembler.Comment(fmt.Sprintf("save %s to %s", register, newRegister)) } f.assembler.RegisterRegister(asm.MOVE, newRegister, register) f.cpu.Free(register) - f.cpu.Use(newRegister) variable.Register = newRegister } diff --git a/src/build/TokenToRegister.go b/src/build/core/TokenToRegister.go similarity index 81% rename from src/build/TokenToRegister.go rename to src/build/core/TokenToRegister.go index 6bef4cb..faf4036 100644 --- a/src/build/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -1,13 +1,12 @@ -package build +package core import ( "strconv" "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) // TokenToRegister moves a token into a register. @@ -19,11 +18,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { constant, exists := f.definitions[name] if exists { - if config.Verbose { - f.Logf("constant %s = %s", constant.Name, constant.Value) - } - - return f.ExpressionToRegister(constant.Value, register) + return f.EvaluateTo(constant.Value, register) } variable, exists := f.variables[name] diff --git a/src/build/Variable.go b/src/build/core/Variable.go similarity index 88% rename from src/build/Variable.go rename to src/build/core/Variable.go index c81b850..8667b35 100644 --- a/src/build/Variable.go +++ b/src/build/core/Variable.go @@ -1,4 +1,4 @@ -package build +package core import ( "git.akyoto.dev/cli/q/src/build/cpu" @@ -7,6 +7,7 @@ import ( // Variable represents a named register. type Variable struct { + Value *expression.Expression Name string Register cpu.Register Alive int diff --git a/src/build/Math.go b/src/build/core/opRegister.go similarity index 74% rename from src/build/Math.go rename to src/build/core/opRegister.go index b02d42d..2cdd81c 100644 --- a/src/build/Math.go +++ b/src/build/core/opRegister.go @@ -1,14 +1,14 @@ -package build +package core import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) -// ExecuteRegisterNumber performs an operation on a register and a number. -func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { +// opRegisterNumber performs an operation on a register and a number. +func (f *Function) opRegisterNumber(operation token.Token, register cpu.Register, number int) error { switch operation.Text() { case "+", "+=": f.assembler.RegisterNumber(asm.ADD, register, number) @@ -32,8 +32,8 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg return nil } -// ExecuteRegisterRegister performs an operation on two registers. -func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { +// opRegisterRegister performs an operation on two registers. +func (f *Function) opRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { switch operation.Text() { case "+", "+=": f.assembler.RegisterRegister(asm.ADD, destination, source) diff --git a/src/build/core/state.go b/src/build/core/state.go new file mode 100644 index 0000000..70078c3 --- /dev/null +++ b/src/build/core/state.go @@ -0,0 +1,58 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/color/ansi" +) + +// state is the data structure we embed in each function to preserve compilation state. +type state struct { + err error + definitions map[string]*Definition + variables map[string]*Variable + functions map[string]*Function + finished chan struct{} + assembler asm.Assembler + cpu cpu.CPU + count counter + sideEffects int +} + +// counter stores how often a certain statement appeared so we can generate a unique label from it. +type counter struct { + loop int + tmps int +} + +// PrintInstructions shows the assembly instructions. +func (s *state) PrintInstructions() { + ansi.Dim.Println("╭────────────────────────────────────────────────╮") + + for _, x := range s.assembler.Instructions { + ansi.Dim.Print("│ ") + + switch x.Mnemonic { + case asm.LABEL: + ansi.Yellow.Printf("%-46s", x.Data.String()+":") + + case asm.COMMENT: + ansi.Dim.Printf("%-46s", x.Data.String()) + + default: + ansi.Green.Printf("%-8s", x.Mnemonic.String()) + + if x.Data != nil { + fmt.Printf("%-38s", x.Data.String()) + } else { + fmt.Printf("%-38s", "") + } + } + + ansi.Dim.Print(" │\n") + } + + ansi.Dim.Println("╰────────────────────────────────────────────────╯") +} diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go index 0092154..8f0f9f3 100644 --- a/src/build/cpu/CPU.go +++ b/src/build/cpu/CPU.go @@ -30,3 +30,14 @@ func (c *CPU) FindFree(registers []Register) (Register, bool) { return 0, false } + +func (c *CPU) MustUseFree(registers []Register) Register { + register, exists := c.FindFree(registers) + + if !exists { + panic("no free registers") + } + + c.Use(register) + return register +} diff --git a/src/errors/Base.go b/src/build/errors/Base.go similarity index 100% rename from src/errors/Base.go rename to src/build/errors/Base.go diff --git a/src/errors/CompileErrors.go b/src/build/errors/CompileErrors.go similarity index 100% rename from src/errors/CompileErrors.go rename to src/build/errors/CompileErrors.go diff --git a/src/errors/Error.go b/src/build/errors/Error.go similarity index 100% rename from src/errors/Error.go rename to src/build/errors/Error.go diff --git a/src/errors/InvalidCharacter.go b/src/build/errors/InvalidCharacter.go similarity index 100% rename from src/errors/InvalidCharacter.go rename to src/build/errors/InvalidCharacter.go diff --git a/src/errors/InvalidInstruction.go b/src/build/errors/InvalidInstruction.go similarity index 100% rename from src/errors/InvalidInstruction.go rename to src/build/errors/InvalidInstruction.go diff --git a/src/errors/InvalidOperator.go b/src/build/errors/InvalidOperator.go similarity index 100% rename from src/errors/InvalidOperator.go rename to src/build/errors/InvalidOperator.go diff --git a/src/errors/KeywordNotImplemented.go b/src/build/errors/KeywordNotImplemented.go similarity index 100% rename from src/errors/KeywordNotImplemented.go rename to src/build/errors/KeywordNotImplemented.go diff --git a/src/errors/ScanErrors.go b/src/build/errors/ScanErrors.go similarity index 100% rename from src/errors/ScanErrors.go rename to src/build/errors/ScanErrors.go diff --git a/src/errors/Stack.go b/src/build/errors/Stack.go similarity index 100% rename from src/errors/Stack.go rename to src/build/errors/Stack.go diff --git a/src/errors/UnknownIdentifier.go b/src/build/errors/UnknownIdentifier.go similarity index 100% rename from src/errors/UnknownIdentifier.go rename to src/build/errors/UnknownIdentifier.go diff --git a/src/errors/UnusedVariable.go b/src/build/errors/UnusedVariable.go similarity index 100% rename from src/errors/UnusedVariable.go rename to src/build/errors/UnusedVariable.go diff --git a/src/errors/VariableAlreadyExists.go b/src/build/errors/VariableAlreadyExists.go similarity index 100% rename from src/errors/VariableAlreadyExists.go rename to src/build/errors/VariableAlreadyExists.go diff --git a/src/build/scan.go b/src/build/scanner/Scan.go similarity index 75% rename from src/build/scan.go rename to src/build/scanner/Scan.go index 758bbec..1a4466b 100644 --- a/src/build/scan.go +++ b/src/build/scanner/Scan.go @@ -1,4 +1,4 @@ -package build +package scanner import ( "os" @@ -7,17 +7,16 @@ import ( "sync" "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/core" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) -// scan scans the directory. -func scan(files []string) (<-chan *Function, <-chan error) { - functions := make(chan *Function) +// Scan scans the directory. +func Scan(files []string) (<-chan *core.Function, <-chan error) { + functions := make(chan *core.Function) errors := make(chan error) go func() { @@ -30,7 +29,7 @@ func scan(files []string) (<-chan *Function, <-chan error) { } // scanFiles scans the list of files without channel allocations. -func scanFiles(files []string, functions chan<- *Function, errors chan<- error) { +func scanFiles(files []string, functions chan<- *core.Function, errors chan<- error) { wg := sync.WaitGroup{} for _, file := range files { @@ -81,7 +80,7 @@ func scanFiles(files []string, functions chan<- *Function, errors chan<- error) } // scanFile scans a single file. -func scanFile(path string, functions chan<- *Function) error { +func scanFile(path string, functions chan<- *core.Function) error { contents, err := os.ReadFile(path) if err != nil { @@ -236,49 +235,34 @@ func scanFile(path string, functions chan<- *Function) error { return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) } - function := &Function{ - Name: tokens[nameStart].Text(), - File: file, - Body: tokens[bodyStart:i], - compiler: compiler{ - assembler: asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 32), - }, - cpu: cpu.CPU{ - Call: x64.CallRegisters, - General: x64.GeneralRegisters, - Syscall: x64.SyscallRegisters, - Return: x64.ReturnValueRegisters, - }, - definitions: map[string]*Definition{}, - variables: map[string]*Variable{}, - finished: make(chan struct{}), - }, - } - + name := tokens[nameStart].Text() + body := tokens[bodyStart:i] + function := core.NewFunction(name, file, body) parameters := tokens[paramsStart:paramsEnd] + count := 0 err := expression.EachParameter(parameters, func(tokens token.List) error { - if len(tokens) == 1 { - name := tokens[0].Text() - register := x64.CallRegisters[len(function.variables)] - uses := countIdentifier(function.Body, name) - - if uses == 0 { - return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) - } - - variable := &Variable{ - Name: name, - Register: register, - Alive: uses, - } - - function.addVariable(variable) - return nil + if len(tokens) != 1 { + return errors.New(errors.NotImplemented, file, tokens[0].Position) } - return errors.New(errors.NotImplemented, file, tokens[0].Position) + name := tokens[0].Text() + register := x64.CallRegisters[count] + uses := core.CountIdentifier(function.Body, name) + + if uses == 0 { + return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) + } + + variable := &core.Variable{ + Name: name, + Register: register, + Alive: uses, + } + + function.AddVariable(variable) + count++ + return nil }) if err != nil { diff --git a/src/cli/Build.go b/src/cli/Build.go index 8db214f..ac4066c 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -9,22 +9,21 @@ import ( "git.akyoto.dev/cli/q/src/build/config" ) -// Build builds an executable. +// Build parses the arguments and creates a build. func Build(args []string) int { b := build.New() - dry := false for i := 0; i < len(args); i++ { switch args[i] { - case "--dry": - dry = true - - case "--assembler", "-a": + case "-a", "--assembler": config.Assembler = true - - case "--verbose", "-v": - config.Verbose = true - + case "-c", "--comments": + config.Comments = true + case "-d", "--dry": + config.Dry = true + case "-v", "--verbose": + config.Assembler = true + config.Comments = true default: if strings.HasPrefix(args[i], "-") { fmt.Printf("Unknown parameter: %s\n", args[i]) @@ -39,6 +38,11 @@ func Build(args []string) int { b.Files = append(b.Files, ".") } + return run(b) +} + +// run starts the build by running the compiler and then writing the result to disk. +func run(b *build.Build) int { result, err := b.Run() if err != nil { @@ -47,12 +51,10 @@ func Build(args []string) int { } if config.Assembler { - result.EachFunction(result.Main, map[*build.Function]bool{}, func(f *build.Function) { - f.PrintInstructions() - }) + result.PrintInstructions() } - if dry { + if config.Dry { return 0 } diff --git a/src/cli/Help.go b/src/cli/Help.go index 7cf99e4..b6c8f58 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -18,6 +18,7 @@ func Help(w io.Writer, code int) int { build options: --assembler, -a Show assembler instructions. - --verbose, -v Show verbose output.`) + --comments, -c Show assembler comments. + --verbose, -v Show everything.`) return code } diff --git a/bench_test.go b/tests/benchmarks_test.go similarity index 72% rename from bench_test.go rename to tests/benchmarks_test.go index 46b62ed..6ff03a7 100644 --- a/bench_test.go +++ b/tests/benchmarks_test.go @@ -1,4 +1,4 @@ -package main_test +package tests_test import ( "testing" @@ -8,7 +8,7 @@ import ( ) func BenchmarkEmpty(b *testing.B) { - compiler := build.New("tests/benchmarks/empty.q") + compiler := build.New("benchmarks/empty.q") for i := 0; i < b.N; i++ { _, err := compiler.Run() @@ -17,7 +17,7 @@ func BenchmarkEmpty(b *testing.B) { } func BenchmarkExpressions(b *testing.B) { - compiler := build.New("tests/benchmarks/expressions.q") + compiler := build.New("benchmarks/expressions.q") for i := 0; i < b.N; i++ { _, err := compiler.Run() diff --git a/errors_test.go b/tests/errors_test.go similarity index 93% rename from errors_test.go rename to tests/errors_test.go index f4cb7ab..9de3370 100644 --- a/errors_test.go +++ b/tests/errors_test.go @@ -1,4 +1,4 @@ -package main_test +package tests_test import ( "path/filepath" @@ -6,7 +6,7 @@ import ( "testing" "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/go/assert" ) @@ -41,7 +41,7 @@ func TestErrors(t *testing.T) { name := strings.TrimSuffix(test.File, ".q") t.Run(name, func(t *testing.T) { - b := build.New(filepath.Join("tests", "errors", test.File)) + b := build.New(filepath.Join("errors", test.File)) _, err := b.Run() assert.NotNil(t, err) assert.Contains(t, err.Error(), test.ExpectedError.Error()) diff --git a/tests/examples_test.go b/tests/examples_test.go new file mode 100644 index 0000000..a8d9954 --- /dev/null +++ b/tests/examples_test.go @@ -0,0 +1,23 @@ +package tests_test + +import ( + "path/filepath" + "testing" +) + +func TestExamples(t *testing.T) { + var tests = []struct { + Name string + ExpectedOutput string + ExpectedExitCode int + }{ + {"hello", "", 9}, + {"write", "ELF", 0}, + } + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + run(t, filepath.Join("..", "examples", test.Name), test.ExpectedOutput, test.ExpectedExitCode) + }) + } +} diff --git a/tests/programs/successive-calls.q b/tests/programs/successive-calls.q new file mode 100644 index 0000000..bffc930 --- /dev/null +++ b/tests/programs/successive-calls.q @@ -0,0 +1,7 @@ +main() { + syscall(60, f(1) + f(2) + f(3)) +} + +f(x) { + return x + 1 +} \ No newline at end of file diff --git a/examples_test.go b/tests/programs_test.go similarity index 60% rename from examples_test.go rename to tests/programs_test.go index faf1103..e671fd0 100644 --- a/examples_test.go +++ b/tests/programs_test.go @@ -1,38 +1,35 @@ -package main_test +package tests_test import ( "os" "os/exec" + "path/filepath" "testing" "git.akyoto.dev/cli/q/src/build" "git.akyoto.dev/go/assert" ) -func TestExamples(t *testing.T) { - var examples = []struct { +func TestPrograms(t *testing.T) { + var tests = []struct { Name string ExpectedOutput string ExpectedExitCode int }{ - {"hello", "", 25}, - {"write", "ELF", 0}, + {"successive-calls.q", "", 9}, } - for _, example := range examples { - example := example - - t.Run(example.Name, func(t *testing.T) { - runExample(t, example.Name, example.ExpectedOutput, example.ExpectedExitCode) + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + run(t, filepath.Join("programs", test.Name), test.ExpectedOutput, test.ExpectedExitCode) }) } } -// runExample builds and runs the example to check if the output matches the expected output. -func runExample(t *testing.T, name string, expectedOutput string, expectedExitCode int) { - b := build.New("examples/" + name) +// run builds and runs the file to check if the output matches the expected output. +func run(t *testing.T, name string, expectedOutput string, expectedExitCode int) { + b := build.New(name) assert.True(t, len(b.Executable()) > 0) - defer os.Remove(b.Executable()) t.Run("Compile", func(t *testing.T) { result, err := b.Run() From feebfe65bb4180ee4aeef81364291df0acd9d13c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 3 Jul 2024 11:39:24 +0200 Subject: [PATCH 0218/1012] Refactored code structure --- README.md | 18 ++- examples/hello/hello.q | 7 +- examples/write/write.q | 2 +- src/build/Assignment.go | 19 --- src/build/Build.go | 29 +++-- src/build/ExpressionToRegister.go | 48 ------- src/build/Instruction.go | 1 - src/build/Write.go | 29 ----- src/build/arch/x64/Registers.go | 2 +- src/build/asm/Assembler.go | 3 + src/build/asm/Instructions.go | 10 ++ src/build/asm/Mnemonic.go | 18 +-- src/build/ast/AST.go | 30 ++++- src/build/ast/Parse.go | 4 +- src/build/{Build_test.go => build_test.go} | 15 ++- src/build/compiler.go | 81 ------------ src/build/config/config.go | 3 +- src/build/{compile.go => core/Compile.go} | 31 ++--- src/build/core/CompileAllFunctions.go | 19 +++ src/build/core/CompileAssign.go | 19 +++ src/build/{Call.go => core/CompileCall.go} | 43 +----- .../CompileDefinition.go} | 53 +++++--- src/build/{Loop.go => core/CompileLoop.go} | 2 +- .../{Return.go => core/CompileReturn.go} | 4 +- src/build/core/Evaluate.go | 123 ++++++++++++++++++ src/build/{ => core}/Execute.go | 17 +-- src/build/{ => core}/Function.go | 61 +++++---- src/build/{ => core}/Result.go | 54 ++++++-- src/build/{ => core}/SaveRegister.go | 15 +-- src/build/{ => core}/TokenToRegister.go | 11 +- src/build/{ => core}/Variable.go | 3 +- src/build/{Math.go => core/opRegister.go} | 12 +- src/build/core/state.go | 58 +++++++++ src/build/cpu/CPU.go | 11 ++ src/{ => build}/errors/Base.go | 0 src/{ => build}/errors/CompileErrors.go | 0 src/{ => build}/errors/Error.go | 0 src/{ => build}/errors/InvalidCharacter.go | 0 src/{ => build}/errors/InvalidInstruction.go | 0 src/{ => build}/errors/InvalidOperator.go | 0 .../errors/KeywordNotImplemented.go | 0 src/{ => build}/errors/ScanErrors.go | 0 src/{ => build}/errors/Stack.go | 0 src/{ => build}/errors/UnknownIdentifier.go | 0 src/{ => build}/errors/UnusedVariable.go | 0 .../errors/VariableAlreadyExists.go | 0 src/build/{scan.go => scanner/Scan.go} | 78 +++++------ src/cli/Build.go | 30 +++-- src/cli/Help.go | 3 +- bench_test.go => tests/benchmarks_test.go | 6 +- errors_test.go => tests/errors_test.go | 6 +- tests/examples_test.go | 23 ++++ tests/programs/successive-calls.q | 7 + examples_test.go => tests/programs_test.go | 25 ++-- 54 files changed, 583 insertions(+), 450 deletions(-) delete mode 100644 src/build/Assignment.go delete mode 100644 src/build/ExpressionToRegister.go delete mode 100644 src/build/Instruction.go delete mode 100644 src/build/Write.go rename src/build/{Build_test.go => build_test.go} (53%) delete mode 100644 src/build/compiler.go rename src/build/{compile.go => core/Compile.go} (59%) create mode 100644 src/build/core/CompileAllFunctions.go create mode 100644 src/build/core/CompileAssign.go rename src/build/{Call.go => core/CompileCall.go} (51%) rename src/build/{VariableDefinition.go => core/CompileDefinition.go} (64%) rename src/build/{Loop.go => core/CompileLoop.go} (95%) rename src/build/{Return.go => core/CompileReturn.go} (75%) create mode 100644 src/build/core/Evaluate.go rename src/build/{ => core}/Execute.go (75%) rename src/build/{ => core}/Function.go (57%) rename src/build/{ => core}/Result.go (59%) rename src/build/{ => core}/SaveRegister.go (69%) rename src/build/{ => core}/TokenToRegister.go (81%) rename src/build/{ => core}/Variable.go (88%) rename src/build/{Math.go => core/opRegister.go} (74%) create mode 100644 src/build/core/state.go rename src/{ => build}/errors/Base.go (100%) rename src/{ => build}/errors/CompileErrors.go (100%) rename src/{ => build}/errors/Error.go (100%) rename src/{ => build}/errors/InvalidCharacter.go (100%) rename src/{ => build}/errors/InvalidInstruction.go (100%) rename src/{ => build}/errors/InvalidOperator.go (100%) rename src/{ => build}/errors/KeywordNotImplemented.go (100%) rename src/{ => build}/errors/ScanErrors.go (100%) rename src/{ => build}/errors/Stack.go (100%) rename src/{ => build}/errors/UnknownIdentifier.go (100%) rename src/{ => build}/errors/UnusedVariable.go (100%) rename src/{ => build}/errors/VariableAlreadyExists.go (100%) rename src/build/{scan.go => scanner/Scan.go} (75%) rename bench_test.go => tests/benchmarks_test.go (72%) rename errors_test.go => tests/errors_test.go (93%) create mode 100644 tests/examples_test.go create mode 100644 tests/programs/successive-calls.q rename examples_test.go => tests/programs_test.go (60%) diff --git a/README.md b/README.md index e5e860f..866b982 100644 --- a/README.md +++ b/README.md @@ -83,22 +83,30 @@ Each function will then be translated to generic assembler instructions. All the functions that are required to run the program will be added to the final assembler. The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. -### [src/build/Function.go](src/build/Function.go) +### [src/build/core/Function.go](src/build/core/Function.go) This is the "heart" of the compiler. -Each function runs `f.Compile()` which organizes the source code into instructions that are then compiled via `f.CompileInstruction`. -You can think of instructions as the individual lines in your source code, but instructions can also span over multiple lines. +Each function runs `f.Compile` which organizes the source code into an abstract syntax tree that is then compiled via `f.CompileAST`. +You can think of AST nodes as the individual statements in your source code. + +### [src/build/ast/Parse.go](src/build/ast/Parse.go) + +This is what generates the AST from tokens. + +### [src/build/expression/Parse.go](src/build/expression/Parse.go) + +This is what generates expressions from tokens. ## Tests ```shell -go test -coverpkg=./... +go test ./... -v ``` ## Benchmarks ```shell -go test -bench=. -benchmem +go test ./tests -bench=. -benchmem ``` ## License diff --git a/examples/hello/hello.q b/examples/hello/hello.q index aace245..bffc930 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,8 +1,7 @@ main() { - x := f(2, 3) - syscall(60, x) + syscall(60, f(1) + f(2) + f(3)) } -f(x, y) { - return (x + y) * (x + y) +f(x) { + return x + 1 } \ No newline at end of file diff --git a/examples/write/write.q b/examples/write/write.q index c06884e..31b18d6 100644 --- a/examples/write/write.q +++ b/examples/write/write.q @@ -5,7 +5,7 @@ main() { } print(address, length) { - write(length-2, address, length) + write(1, address, length) } write(fd, address, length) { diff --git a/src/build/Assignment.go b/src/build/Assignment.go deleted file mode 100644 index 66703da..0000000 --- a/src/build/Assignment.go +++ /dev/null @@ -1,19 +0,0 @@ -package build - -import ( - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/errors" -) - -// CompileAssignment compiles an assignment. -func (f *Function) CompileAssignment(expr *expression.Expression) error { - name := expr.Children[0].Token.Text() - variable, exists := f.variables[name] - - if !exists { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) - } - - defer f.useVariable(variable) - return f.Execute(expr.Token, variable.Register, expr.Children[1]) -} diff --git a/src/build/Build.go b/src/build/Build.go index 2821e2d..bf90d2c 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -3,6 +3,9 @@ package build import ( "path/filepath" "strings" + + "git.akyoto.dev/cli/q/src/build/core" + "git.akyoto.dev/cli/q/src/build/scanner" ) // Build describes a compiler build. @@ -18,18 +21,28 @@ func New(files ...string) *Build { } // Run parses the input files and generates an executable file. -func (build *Build) Run() (Result, error) { - functions, errors := scan(build.Files) - return compile(functions, errors) +func (build *Build) Run() (core.Result, error) { + functions, errors := scanner.Scan(build.Files) + return core.Compile(functions, errors) } // Executable returns the path to the executable. func (build *Build) Executable() string { - directory, _ := filepath.Abs(build.Files[0]) + path, _ := filepath.Abs(build.Files[0]) - if strings.HasSuffix(directory, ".q") { - directory = filepath.Dir(directory) + if strings.HasSuffix(path, ".q") { + return fromFileName(path) + } else { + return fromDirectoryName(path) } - - return filepath.Join(directory, filepath.Base(directory)) +} + +// fromDirectoryName returns the executable path based on the directory name. +func fromDirectoryName(path string) string { + return filepath.Join(path, filepath.Base(path)) +} + +// fromFileName returns the executable path based on the file name. +func fromFileName(path string) string { + return filepath.Join(filepath.Dir(path), strings.TrimSuffix(filepath.Base(path), ".q")) } diff --git a/src/build/ExpressionToRegister.go b/src/build/ExpressionToRegister.go deleted file mode 100644 index cf0a04c..0000000 --- a/src/build/ExpressionToRegister.go +++ /dev/null @@ -1,48 +0,0 @@ -package build - -import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" -) - -// ExpressionToRegister moves the result of an expression into the given register. -func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { - if config.Verbose { - f.Logf("%s to register %s", root, register) - } - - operation := root.Token - - if root.IsLeaf() { - return f.TokenToRegister(operation, register) - } - - if ast.IsFunctionCall(root) { - err := f.CompileFunctionCall(root) - - if err != nil { - return err - } - - if register != f.cpu.Return[0] { - f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Return[0]) - } - - return nil - } - - left := root.Children[0] - right := root.Children[1] - - err := f.ExpressionToRegister(left, register) - - if err != nil { - return err - } - - f.SaveRegister(register) - return f.Execute(operation, register, right) -} diff --git a/src/build/Instruction.go b/src/build/Instruction.go deleted file mode 100644 index 5c93a93..0000000 --- a/src/build/Instruction.go +++ /dev/null @@ -1 +0,0 @@ -package build diff --git a/src/build/Write.go b/src/build/Write.go deleted file mode 100644 index f6385bb..0000000 --- a/src/build/Write.go +++ /dev/null @@ -1,29 +0,0 @@ -package build - -import ( - "bufio" - "os" - - "git.akyoto.dev/cli/q/src/build/elf" -) - -// Write writes the executable file to disk. -func Write(filePath string, code []byte, data []byte) error { - file, err := os.Create(filePath) - - if err != nil { - return err - } - - buffer := bufio.NewWriter(file) - executable := elf.New(code, data) - executable.Write(buffer) - buffer.Flush() - err = file.Close() - - if err != nil { - return err - } - - return os.Chmod(filePath, 0755) -} diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go index 8104f84..fafea37 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/build/arch/x64/Registers.go @@ -23,7 +23,7 @@ const ( var ( CallRegisters = SyscallRegisters - GeneralRegisters = []cpu.Register{RBX, RBP, R12, R13, R14, R15} + GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} ReturnValueRegisters = []cpu.Register{RAX, RCX, R11} ) diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 9c6f990..9a9f19e 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -63,6 +63,9 @@ func (a Assembler) Finalize() ([]byte, []byte) { }, }) + case COMMENT: + continue + case JUMP: code = x64.Jump8(code, 0x00) size := 1 diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 4126bcf..6b547a2 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -44,6 +44,16 @@ func (a *Assembler) Label(name string) { }) } +// Comment adds a comment at the current position. +func (a *Assembler) Comment(text string) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: COMMENT, + Data: &Label{ + Name: text, + }, + }) +} + // Call calls a function whose position is identified by a label. func (a *Assembler) Call(name string) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 77dbf81..315af19 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -6,6 +6,7 @@ const ( NONE Mnemonic = iota ADD CALL + COMMENT DIV JUMP MUL @@ -23,40 +24,31 @@ func (m Mnemonic) String() string { switch m { case ADD: return "add" - case CALL: return "call" - + case COMMENT: + return "comment" case DIV: return "div" - case JUMP: return "jump" - case LABEL: return "label" - case MOVE: return "move" - case MUL: return "mul" - case POP: return "pop" - case PUSH: return "push" - case RETURN: return "return" - case SUB: return "sub" - case SYSCALL: return "syscall" + default: + return "" } - - return "NONE" } diff --git a/src/build/ast/AST.go b/src/build/ast/AST.go index 2d8314a..d1b991b 100644 --- a/src/build/ast/AST.go +++ b/src/build/ast/AST.go @@ -1,36 +1,54 @@ package ast import ( + "fmt" + "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" ) -type Node interface{} +type Node fmt.Stringer type AST []Node type Assign struct { - Value *expression.Expression - Name token.Token + Value *expression.Expression + Name token.Token + Operator token.Token +} + +func (node *Assign) String() string { + return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) } type Call struct { Expression *expression.Expression } +func (node *Call) String() string { + return node.Expression.String() +} + type Define struct { Value *expression.Expression Name token.Token } -type If struct { - Condition *expression.Expression - Body AST +func (node *Define) String() string { + return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) } type Loop struct { Body AST } +func (node *Loop) String() string { + return fmt.Sprintf("(loop %s)", node.Body) +} + type Return struct { Value *expression.Expression } + +func (node *Return) String() string { + return fmt.Sprintf("(return %s)", node.Value) +} diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index b977704..69e6756 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -1,10 +1,10 @@ package ast import ( + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/keyword" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) // Parse generates an AST from a list of tokens. @@ -75,7 +75,7 @@ func toASTNode(tokens token.List) (Node, error) { name := expr.Children[0].Token value := expr.Children[1] - return &Assign{Name: name, Value: value}, nil + return &Assign{Name: name, Value: value, Operator: expr.Token}, nil case IsFunctionCall(expr): return &Call{Expression: expr}, nil diff --git a/src/build/Build_test.go b/src/build/build_test.go similarity index 53% rename from src/build/Build_test.go rename to src/build/build_test.go index 88d8e52..36242d8 100644 --- a/src/build/Build_test.go +++ b/src/build/build_test.go @@ -8,17 +8,28 @@ import ( "git.akyoto.dev/go/assert" ) -func TestBuild(t *testing.T) { +func TestBuildDirectory(t *testing.T) { b := build.New("../../examples/hello") _, err := b.Run() assert.Nil(t, err) } -func TestExecutable(t *testing.T) { +func TestBuildFile(t *testing.T) { + b := build.New("../../examples/hello/hello.q") + _, err := b.Run() + assert.Nil(t, err) +} + +func TestExecutableFromDirectory(t *testing.T) { b := build.New("../../examples/hello") assert.Equal(t, filepath.Base(b.Executable()), "hello") } +func TestExecutableFromFile(t *testing.T) { + b := build.New("../../examples/hello/hello.q") + assert.Equal(t, filepath.Base(b.Executable()), "hello") +} + func TestNonExisting(t *testing.T) { b := build.New("does-not-exist") _, err := b.Run() diff --git a/src/build/compiler.go b/src/build/compiler.go deleted file mode 100644 index ac28b37..0000000 --- a/src/build/compiler.go +++ /dev/null @@ -1,81 +0,0 @@ -package build - -import ( - "fmt" - - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/go/color/ansi" -) - -// compiler is the data structure we embed in each function to preserve compilation state. -type compiler struct { - err error - definitions map[string]*Definition - variables map[string]*Variable - functions map[string]*Function - finished chan struct{} - assembler asm.Assembler - debug []debug - cpu cpu.CPU - count counter - sideEffects int -} - -// counter stores how often a certain statement appeared so we can generate a unique label from it. -type counter struct { - loop int -} - -// debug is used to look up the source code at the given position. -type debug struct { - source ast.Node - position int -} - -// PrintInstructions shows the assembly instructions. -func (c *compiler) PrintInstructions() { - ansi.Dim.Println("╭──────────────────────────────────────╮") - - for i, x := range c.assembler.Instructions { - instruction := c.sourceAt(i) - - if instruction != nil { - ansi.Dim.Println("├──────────────────────────────────────┤") - } - - ansi.Dim.Print("│ ") - - if x.Mnemonic == asm.LABEL { - ansi.Yellow.Printf("%-36s", x.Data.String()+":") - } else { - ansi.Green.Printf("%-8s", x.Mnemonic.String()) - - if x.Data != nil { - fmt.Printf("%-28s", x.Data.String()) - } else { - fmt.Printf("%-28s", "") - } - } - - ansi.Dim.Print(" │\n") - } - - ansi.Dim.Println("╰──────────────────────────────────────╯") -} - -// sourceAt retrieves the source code at the given position or `nil`. -func (c *compiler) sourceAt(position int) ast.Node { - for _, record := range c.debug { - if record.position == position { - return record.source - } - - if record.position > position { - return nil - } - } - - return nil -} diff --git a/src/build/config/config.go b/src/build/config/config.go index d87cf95..4f97a22 100644 --- a/src/build/config/config.go +++ b/src/build/config/config.go @@ -9,5 +9,6 @@ const ( var ( Assembler = false - Verbose = false + Comments = false + Dry = false ) diff --git a/src/build/compile.go b/src/build/core/Compile.go similarity index 59% rename from src/build/compile.go rename to src/build/core/Compile.go index 0430873..edd8712 100644 --- a/src/build/compile.go +++ b/src/build/core/Compile.go @@ -1,13 +1,11 @@ -package build +package core import ( - "sync" - - "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/build/errors" ) -// compile waits for the scan to finish and compiles all functions. -func compile(functions <-chan *Function, errs <-chan error) (Result, error) { +// Compile waits for the scan to finish and compiles all functions. +func Compile(functions <-chan *Function, errs <-chan error) (Result, error) { result := Result{} allFunctions := map[string]*Function{} @@ -32,8 +30,10 @@ func compile(functions <-chan *Function, errs <-chan error) (Result, error) { } } - compileFunctions(allFunctions) + // Start parallel compilation + CompileAllFunctions(allFunctions) + // Report errors if any occurred for _, function := range allFunctions { if function.err != nil { return result, function.err @@ -42,6 +42,7 @@ func compile(functions <-chan *Function, errs <-chan error) (Result, error) { result.InstructionCount += len(function.assembler.Instructions) } + // Check for existence of `main` main, exists := allFunctions["main"] if !exists { @@ -52,19 +53,3 @@ func compile(functions <-chan *Function, errs <-chan error) (Result, error) { result.Functions = allFunctions return result, nil } - -// compileFunctions starts a goroutine for each function compilation and waits for completion. -func compileFunctions(functions map[string]*Function) { - wg := sync.WaitGroup{} - - for _, function := range functions { - wg.Add(1) - - go func() { - defer wg.Done() - function.Compile() - }() - } - - wg.Wait() -} diff --git a/src/build/core/CompileAllFunctions.go b/src/build/core/CompileAllFunctions.go new file mode 100644 index 0000000..66d3ef9 --- /dev/null +++ b/src/build/core/CompileAllFunctions.go @@ -0,0 +1,19 @@ +package core + +import "sync" + +// CompileAllFunctions starts a goroutine for each function compilation and waits for completion. +func CompileAllFunctions(functions map[string]*Function) { + wg := sync.WaitGroup{} + + for _, function := range functions { + wg.Add(1) + + go func() { + defer wg.Done() + function.Compile() + }() + } + + wg.Wait() +} diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go new file mode 100644 index 0000000..ce46a34 --- /dev/null +++ b/src/build/core/CompileAssign.go @@ -0,0 +1,19 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/errors" +) + +// CompileAssign compiles an assign statement. +func (f *Function) CompileAssign(node *ast.Assign) error { + name := node.Name.Text() + variable, exists := f.variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Name.Position) + } + + defer f.useVariable(variable) + return f.Execute(node.Operator, variable.Register, node.Value) +} diff --git a/src/build/Call.go b/src/build/core/CompileCall.go similarity index 51% rename from src/build/Call.go rename to src/build/core/CompileCall.go index 44df7c9..283439c 100644 --- a/src/build/Call.go +++ b/src/build/core/CompileCall.go @@ -1,39 +1,16 @@ -package build +package core import ( - "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" ) -// CompileFunctionCall executes a function call. +// CompileCall executes a function call. // All call registers must hold the correct parameter values before the function invocation. // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. -func (f *Function) CompileFunctionCall(expr *expression.Expression) error { - funcName := expr.Children[0].Token.Text() - - if funcName == "syscall" { - return f.CompileSyscall(expr) - } - - function := f.functions[funcName] - - if function != f { - function.Wait() - } - - parameters := expr.Children[1:] - registers := f.cpu.Call[:len(parameters)] - - err := f.ExpressionsToRegisters(parameters, registers) - - if config.Verbose { - f.Logf("call: %s", funcName) - } - - f.assembler.Call(funcName) - f.sideEffects += function.sideEffects +func (f *Function) CompileCall(expr *expression.Expression) error { + _, err := f.EvaluateCall(expr) return err } @@ -49,15 +26,9 @@ func (f *Function) CompileSyscall(expr *expression.Expression) error { // ExpressionsToRegisters moves multiple expressions into the specified registers. func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { - for _, register := range registers { - f.SaveRegister(register) - } - - for i := len(expressions) - 1; i >= 0; i-- { - expression := expressions[i] - register := registers[i] - - err := f.ExpressionToRegister(expression, register) + for i := len(registers) - 1; i >= 0; i-- { + f.SaveRegister(registers[i]) + err := f.EvaluateTo(expressions[i], registers[i]) if err != nil { return err diff --git a/src/build/VariableDefinition.go b/src/build/core/CompileDefinition.go similarity index 64% rename from src/build/VariableDefinition.go rename to src/build/core/CompileDefinition.go index f385c24..7abd309 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -1,22 +1,24 @@ -package build +package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) -// CompileVariableDefinition compiles a variable definition. -func (f *Function) CompileVariableDefinition(node *ast.Define) error { +// CompileDefinition compiles a variable definition. +func (f *Function) CompileDefinition(node *ast.Define) error { name := node.Name.Text() if f.identifierExists(name) { return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, node.Name.Position) } - uses := countIdentifier(f.Body, name) - 1 + uses := CountIdentifier(f.Body, name) - 1 if uses == 0 { return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) @@ -48,29 +50,46 @@ func (f *Function) CompileVariableDefinition(node *ast.Define) error { return f.storeVariableInRegister(name, value, uses) } -func (f *Function) addVariable(variable *Variable) { - if config.Verbose { - f.Logf("%s occupies %s (alive: %d)", variable.Name, variable.Register, variable.Alive) +func (f *Function) AddVariable(variable *Variable) { + if config.Comments { + f.assembler.Comment(fmt.Sprintf("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive)) } f.variables[variable.Name] = variable f.cpu.Use(variable.Register) } +func (f *Function) addTemporary(root *expression.Expression) *Variable { + f.count.tmps++ + name := fmt.Sprintf("t%d", f.count.tmps) + register := f.cpu.MustUseFree(f.cpu.General) + + tmp := &Variable{ + Name: name, + Value: root, + Alive: 1, + Register: register, + } + + f.variables[name] = tmp + + if config.Comments { + f.assembler.Comment(fmt.Sprintf("%s = %s (%s)", name, root, register)) + } + + return tmp +} + func (f *Function) useVariable(variable *Variable) { variable.Alive-- - if config.Verbose { - f.Logf("%s occupying %s was used (alive: %d)", variable.Name, variable.Register, variable.Alive) - } - if variable.Alive < 0 { panic("incorrect number of variable use calls") } if variable.Alive == 0 { - if config.Verbose { - f.Logf("%s is no longer used, free register: %s", variable.Name, variable.Register) + if config.Comments { + f.assembler.Comment(fmt.Sprintf("%s died (%s)", variable.Name, variable.Register)) } f.cpu.Free(variable.Register) @@ -102,9 +121,9 @@ func (f *Function) storeVariableInRegister(name string, value *expression.Expres panic("no free registers") } - err := f.ExpressionToRegister(value, reg) + err := f.EvaluateTo(value, reg) - f.addVariable(&Variable{ + f.AddVariable(&Variable{ Name: name, Register: reg, Alive: uses, @@ -113,7 +132,7 @@ func (f *Function) storeVariableInRegister(name string, value *expression.Expres return err } -func countIdentifier(tokens token.List, name string) int { +func CountIdentifier(tokens token.List, name string) int { count := 0 for _, t := range tokens { diff --git a/src/build/Loop.go b/src/build/core/CompileLoop.go similarity index 95% rename from src/build/Loop.go rename to src/build/core/CompileLoop.go index 2165eca..37a3c94 100644 --- a/src/build/Loop.go +++ b/src/build/core/CompileLoop.go @@ -1,4 +1,4 @@ -package build +package core import ( "fmt" diff --git a/src/build/Return.go b/src/build/core/CompileReturn.go similarity index 75% rename from src/build/Return.go rename to src/build/core/CompileReturn.go index c661fc7..c829a91 100644 --- a/src/build/Return.go +++ b/src/build/core/CompileReturn.go @@ -1,4 +1,4 @@ -package build +package core import ( "git.akyoto.dev/cli/q/src/build/ast" @@ -12,5 +12,5 @@ func (f *Function) CompileReturn(node *ast.Return) error { return nil } - return f.ExpressionToRegister(node.Value, f.cpu.Return[0]) + return f.EvaluateTo(node.Value, f.cpu.Return[0]) } diff --git a/src/build/core/Evaluate.go b/src/build/core/Evaluate.go new file mode 100644 index 0000000..ed65df0 --- /dev/null +++ b/src/build/core/Evaluate.go @@ -0,0 +1,123 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Evaluate evaluates the result of an expression and saves it into a temporary register. +func (f *Function) Evaluate(root *expression.Expression) (*Variable, error) { + if root.IsLeaf() { + return f.EvaluateLeaf(root) + } + + if ast.IsFunctionCall(root) { + return f.EvaluateCall(root) + } + + left := root.Children[0] + right := root.Children[1] + + tmpLeft, err := f.Evaluate(left) + + if err != nil { + return nil, err + } + + tmpLeftExpr := expression.NewLeaf(token.Token{ + Kind: token.Identifier, + Position: left.Token.Position, + Bytes: []byte(tmpLeft.Name), + }) + + tmpLeftExpr.Parent = root + root.Children[0].Parent = nil + root.Children[0] = tmpLeftExpr + + tmpRight, err := f.Evaluate(right) + + if err != nil { + return nil, err + } + + tmpRightExpr := expression.NewLeaf(token.Token{ + Kind: token.Identifier, + Position: left.Token.Position, + Bytes: []byte(tmpRight.Name), + }) + + tmpRightExpr.Parent = root + root.Children[1].Parent = nil + root.Children[1] = tmpRightExpr + + tmp := f.addTemporary(root) + + f.assembler.RegisterRegister(asm.MOVE, tmp.Register, tmpLeft.Register) + f.useVariable(tmpLeft) + + err = f.opRegisterRegister(root.Token, tmp.Register, tmpRight.Register) + f.useVariable(tmpRight) + + return tmp, err +} + +func (f *Function) EvaluateCall(root *expression.Expression) (*Variable, error) { + funcName := root.Children[0].Token.Text() + parameters := root.Children[1:] + registers := f.cpu.Call[:len(parameters)] + isSyscall := funcName == "syscall" + + if isSyscall { + registers = f.cpu.Syscall[:len(parameters)] + } + + err := f.ExpressionsToRegisters(parameters, registers) + + for _, register := range f.cpu.General { + if !f.cpu.IsFree(register) { + f.assembler.Register(asm.PUSH, register) + } + } + + if isSyscall { + f.assembler.Syscall() + } else { + f.assembler.Call(funcName) + } + + for i := len(f.cpu.General) - 1; i >= 0; i-- { + register := f.cpu.General[i] + + if !f.cpu.IsFree(register) { + f.assembler.Register(asm.POP, register) + } + } + + tmp := f.addTemporary(root) + f.assembler.RegisterRegister(asm.MOVE, tmp.Register, f.cpu.Return[0]) + return tmp, err +} + +func (f *Function) EvaluateLeaf(root *expression.Expression) (*Variable, error) { + tmp := f.addTemporary(root) + err := f.TokenToRegister(root.Token, tmp.Register) + return tmp, err +} + +func (f *Function) EvaluateTo(root *expression.Expression, register cpu.Register) error { + tmp, err := f.Evaluate(root) + + if err != nil { + return err + } + + if register != tmp.Register { + f.assembler.RegisterRegister(asm.MOVE, register, tmp.Register) + } + + f.useVariable(tmp) + return nil +} diff --git a/src/build/Execute.go b/src/build/core/Execute.go similarity index 75% rename from src/build/Execute.go rename to src/build/core/Execute.go index e85cd1e..067f854 100644 --- a/src/build/Execute.go +++ b/src/build/core/Execute.go @@ -1,22 +1,17 @@ -package build +package core import ( "strconv" "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) // Execute executes an operation on a register with a value operand. func (f *Function) Execute(operation token.Token, register cpu.Register, value *expression.Expression) error { - if config.Verbose { - f.Logf("execute: %s on register %s with value %s", operation.Text(), register, value) - } - if value.IsLeaf() { return f.ExecuteLeaf(operation, register, value.Token) } @@ -36,13 +31,13 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * f.cpu.Use(temporary) defer f.cpu.Free(temporary) - err := f.ExpressionToRegister(value, temporary) + err := f.EvaluateTo(value, temporary) if err != nil { return err } - return f.ExecuteRegisterRegister(operation, register, temporary) + return f.opRegisterRegister(operation, register, temporary) } // ExecuteLeaf performs an operation on a register with the given leaf operand. @@ -57,7 +52,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope } defer f.useVariable(variable) - return f.ExecuteRegisterRegister(operation, register, variable.Register) + return f.opRegisterRegister(operation, register, variable.Register) case token.Number: value := operand.Text() @@ -67,7 +62,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope return err } - return f.ExecuteRegisterNumber(operation, register, number) + return f.opRegisterNumber(operation, register, number) } return errors.New(errors.NotImplemented, f.File, operation.Position) diff --git a/src/build/Function.go b/src/build/core/Function.go similarity index 57% rename from src/build/Function.go rename to src/build/core/Function.go index de87e2f..518affb 100644 --- a/src/build/Function.go +++ b/src/build/core/Function.go @@ -1,21 +1,46 @@ -package build +package core import ( "fmt" + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) -// Function represents a function. +// Function represents the smallest unit of code. type Function struct { - File *fs.File Name string + File *fs.File Body token.List - compiler + state +} + +// NewFunction creates a new function. +func NewFunction(name string, file *fs.File, body token.List) *Function { + return &Function{ + Name: name, + File: file, + Body: body, + state: state{ + assembler: asm.Assembler{ + Instructions: make([]asm.Instruction, 0, 32), + }, + cpu: cpu.CPU{ + Call: x64.CallRegisters, + General: x64.GeneralRegisters, + Syscall: x64.SyscallRegisters, + Return: x64.ReturnValueRegisters, + }, + definitions: map[string]*Definition{}, + variables: map[string]*Variable{}, + finished: make(chan struct{}), + }, + } } // Compile turns a function into machine code. @@ -41,18 +66,7 @@ func (f *Function) CompileTokens(tokens token.List) error { // CompileAST compiles an abstract syntax tree. func (f *Function) CompileAST(tree ast.AST) error { for _, node := range tree { - if config.Verbose { - f.Logf("%T %s", node, node) - } - - if config.Assembler { - f.debug = append(f.debug, debug{ - position: len(f.assembler.Instructions), - source: node, - }) - } - - err := f.CompileNode(node) + err := f.CompileASTNode(node) if err != nil { return err @@ -62,14 +76,17 @@ func (f *Function) CompileAST(tree ast.AST) error { return nil } -// CompileNode compiles a node in the AST. -func (f *Function) CompileNode(node ast.Node) error { +// CompileASTNode compiles a node in the AST. +func (f *Function) CompileASTNode(node ast.Node) error { switch node := node.(type) { + case *ast.Assign: + return f.CompileAssign(node) + case *ast.Call: - return f.CompileFunctionCall(node.Expression) + return f.CompileCall(node.Expression) case *ast.Define: - return f.CompileVariableDefinition(node) + return f.CompileDefinition(node) case *ast.Return: return f.CompileReturn(node) diff --git a/src/build/Result.go b/src/build/core/Result.go similarity index 59% rename from src/build/Result.go rename to src/build/core/Result.go index 6a5e94b..9430012 100644 --- a/src/build/Result.go +++ b/src/build/core/Result.go @@ -1,8 +1,12 @@ -package build +package core import ( + "bufio" + "os" + "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/elf" "git.akyoto.dev/cli/q/src/build/os/linux" ) @@ -13,8 +17,8 @@ type Result struct { InstructionCount int } -// Finalize generates the final machine code. -func (r *Result) Finalize() ([]byte, []byte) { +// finalize generates the final machine code. +func (r *Result) finalize() ([]byte, []byte) { // This will be the entry point of the executable. // The only job of the entry function is to call `main` and exit cleanly. // The reason we call `main` instead of using `main` itself is to place @@ -30,7 +34,7 @@ func (r *Result) Finalize() ([]byte, []byte) { // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. - r.EachFunction(r.Main, map[*Function]bool{}, func(f *Function) { + r.eachFunction(r.Main, map[*Function]bool{}, func(f *Function) { final.Merge(f.assembler) }) @@ -38,9 +42,9 @@ func (r *Result) Finalize() ([]byte, []byte) { return code, data } -// EachFunction recursively finds all the calls to external functions. +// eachFunction recursively finds all the calls to external functions. // It avoids calling the same function twice with the help of a hashmap. -func (r *Result) EachFunction(caller *Function, traversed map[*Function]bool, call func(*Function)) { +func (r *Result) eachFunction(caller *Function, traversed map[*Function]bool, call func(*Function)) { call(caller) traversed[caller] = true @@ -60,12 +64,40 @@ func (r *Result) EachFunction(caller *Function, traversed map[*Function]bool, ca continue } - r.EachFunction(callee, traversed, call) + r.eachFunction(callee, traversed, call) } } -// Write write the final executable to disk. -func (r *Result) Write(path string) error { - code, data := r.Finalize() - return Write(path, code, data) +// PrintInstructions prints out the generated instructions. +func (r *Result) PrintInstructions() { + r.eachFunction(r.Main, map[*Function]bool{}, func(f *Function) { + f.PrintInstructions() + }) +} + +// Write writes an executable file to disk. +func (r *Result) Write(path string) error { + code, data := r.finalize() + return write(path, code, data) +} + +// write writes an executable file to disk. +func write(path string, code []byte, data []byte) error { + file, err := os.Create(path) + + if err != nil { + return err + } + + buffer := bufio.NewWriter(file) + executable := elf.New(code, data) + executable.Write(buffer) + buffer.Flush() + err = file.Close() + + if err != nil { + return err + } + + return os.Chmod(path, 0755) } diff --git a/src/build/SaveRegister.go b/src/build/core/SaveRegister.go similarity index 69% rename from src/build/SaveRegister.go rename to src/build/core/SaveRegister.go index d74c95d..99d5375 100644 --- a/src/build/SaveRegister.go +++ b/src/build/core/SaveRegister.go @@ -1,6 +1,8 @@ -package build +package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" @@ -25,18 +27,13 @@ func (f *Function) SaveRegister(register cpu.Register) { return } - newRegister, exists := f.cpu.FindFree(f.cpu.General) + newRegister := f.cpu.MustUseFree(f.cpu.General) - if !exists { - panic("no free registers") - } - - if config.Verbose { - f.Logf("moving %s from %s to %s (alive: %d)", variable.Name, variable.Register, newRegister, variable.Alive) + if config.Comments { + f.assembler.Comment(fmt.Sprintf("save %s to %s", register, newRegister)) } f.assembler.RegisterRegister(asm.MOVE, newRegister, register) f.cpu.Free(register) - f.cpu.Use(newRegister) variable.Register = newRegister } diff --git a/src/build/TokenToRegister.go b/src/build/core/TokenToRegister.go similarity index 81% rename from src/build/TokenToRegister.go rename to src/build/core/TokenToRegister.go index 6bef4cb..faf4036 100644 --- a/src/build/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -1,13 +1,12 @@ -package build +package core import ( "strconv" "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) // TokenToRegister moves a token into a register. @@ -19,11 +18,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { constant, exists := f.definitions[name] if exists { - if config.Verbose { - f.Logf("constant %s = %s", constant.Name, constant.Value) - } - - return f.ExpressionToRegister(constant.Value, register) + return f.EvaluateTo(constant.Value, register) } variable, exists := f.variables[name] diff --git a/src/build/Variable.go b/src/build/core/Variable.go similarity index 88% rename from src/build/Variable.go rename to src/build/core/Variable.go index c81b850..8667b35 100644 --- a/src/build/Variable.go +++ b/src/build/core/Variable.go @@ -1,4 +1,4 @@ -package build +package core import ( "git.akyoto.dev/cli/q/src/build/cpu" @@ -7,6 +7,7 @@ import ( // Variable represents a named register. type Variable struct { + Value *expression.Expression Name string Register cpu.Register Alive int diff --git a/src/build/Math.go b/src/build/core/opRegister.go similarity index 74% rename from src/build/Math.go rename to src/build/core/opRegister.go index b02d42d..2cdd81c 100644 --- a/src/build/Math.go +++ b/src/build/core/opRegister.go @@ -1,14 +1,14 @@ -package build +package core import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) -// ExecuteRegisterNumber performs an operation on a register and a number. -func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { +// opRegisterNumber performs an operation on a register and a number. +func (f *Function) opRegisterNumber(operation token.Token, register cpu.Register, number int) error { switch operation.Text() { case "+", "+=": f.assembler.RegisterNumber(asm.ADD, register, number) @@ -32,8 +32,8 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg return nil } -// ExecuteRegisterRegister performs an operation on two registers. -func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { +// opRegisterRegister performs an operation on two registers. +func (f *Function) opRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { switch operation.Text() { case "+", "+=": f.assembler.RegisterRegister(asm.ADD, destination, source) diff --git a/src/build/core/state.go b/src/build/core/state.go new file mode 100644 index 0000000..70078c3 --- /dev/null +++ b/src/build/core/state.go @@ -0,0 +1,58 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/color/ansi" +) + +// state is the data structure we embed in each function to preserve compilation state. +type state struct { + err error + definitions map[string]*Definition + variables map[string]*Variable + functions map[string]*Function + finished chan struct{} + assembler asm.Assembler + cpu cpu.CPU + count counter + sideEffects int +} + +// counter stores how often a certain statement appeared so we can generate a unique label from it. +type counter struct { + loop int + tmps int +} + +// PrintInstructions shows the assembly instructions. +func (s *state) PrintInstructions() { + ansi.Dim.Println("╭────────────────────────────────────────────────╮") + + for _, x := range s.assembler.Instructions { + ansi.Dim.Print("│ ") + + switch x.Mnemonic { + case asm.LABEL: + ansi.Yellow.Printf("%-46s", x.Data.String()+":") + + case asm.COMMENT: + ansi.Dim.Printf("%-46s", x.Data.String()) + + default: + ansi.Green.Printf("%-8s", x.Mnemonic.String()) + + if x.Data != nil { + fmt.Printf("%-38s", x.Data.String()) + } else { + fmt.Printf("%-38s", "") + } + } + + ansi.Dim.Print(" │\n") + } + + ansi.Dim.Println("╰────────────────────────────────────────────────╯") +} diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go index 0092154..8f0f9f3 100644 --- a/src/build/cpu/CPU.go +++ b/src/build/cpu/CPU.go @@ -30,3 +30,14 @@ func (c *CPU) FindFree(registers []Register) (Register, bool) { return 0, false } + +func (c *CPU) MustUseFree(registers []Register) Register { + register, exists := c.FindFree(registers) + + if !exists { + panic("no free registers") + } + + c.Use(register) + return register +} diff --git a/src/errors/Base.go b/src/build/errors/Base.go similarity index 100% rename from src/errors/Base.go rename to src/build/errors/Base.go diff --git a/src/errors/CompileErrors.go b/src/build/errors/CompileErrors.go similarity index 100% rename from src/errors/CompileErrors.go rename to src/build/errors/CompileErrors.go diff --git a/src/errors/Error.go b/src/build/errors/Error.go similarity index 100% rename from src/errors/Error.go rename to src/build/errors/Error.go diff --git a/src/errors/InvalidCharacter.go b/src/build/errors/InvalidCharacter.go similarity index 100% rename from src/errors/InvalidCharacter.go rename to src/build/errors/InvalidCharacter.go diff --git a/src/errors/InvalidInstruction.go b/src/build/errors/InvalidInstruction.go similarity index 100% rename from src/errors/InvalidInstruction.go rename to src/build/errors/InvalidInstruction.go diff --git a/src/errors/InvalidOperator.go b/src/build/errors/InvalidOperator.go similarity index 100% rename from src/errors/InvalidOperator.go rename to src/build/errors/InvalidOperator.go diff --git a/src/errors/KeywordNotImplemented.go b/src/build/errors/KeywordNotImplemented.go similarity index 100% rename from src/errors/KeywordNotImplemented.go rename to src/build/errors/KeywordNotImplemented.go diff --git a/src/errors/ScanErrors.go b/src/build/errors/ScanErrors.go similarity index 100% rename from src/errors/ScanErrors.go rename to src/build/errors/ScanErrors.go diff --git a/src/errors/Stack.go b/src/build/errors/Stack.go similarity index 100% rename from src/errors/Stack.go rename to src/build/errors/Stack.go diff --git a/src/errors/UnknownIdentifier.go b/src/build/errors/UnknownIdentifier.go similarity index 100% rename from src/errors/UnknownIdentifier.go rename to src/build/errors/UnknownIdentifier.go diff --git a/src/errors/UnusedVariable.go b/src/build/errors/UnusedVariable.go similarity index 100% rename from src/errors/UnusedVariable.go rename to src/build/errors/UnusedVariable.go diff --git a/src/errors/VariableAlreadyExists.go b/src/build/errors/VariableAlreadyExists.go similarity index 100% rename from src/errors/VariableAlreadyExists.go rename to src/build/errors/VariableAlreadyExists.go diff --git a/src/build/scan.go b/src/build/scanner/Scan.go similarity index 75% rename from src/build/scan.go rename to src/build/scanner/Scan.go index 758bbec..1a4466b 100644 --- a/src/build/scan.go +++ b/src/build/scanner/Scan.go @@ -1,4 +1,4 @@ -package build +package scanner import ( "os" @@ -7,17 +7,16 @@ import ( "sync" "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/core" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/errors" ) -// scan scans the directory. -func scan(files []string) (<-chan *Function, <-chan error) { - functions := make(chan *Function) +// Scan scans the directory. +func Scan(files []string) (<-chan *core.Function, <-chan error) { + functions := make(chan *core.Function) errors := make(chan error) go func() { @@ -30,7 +29,7 @@ func scan(files []string) (<-chan *Function, <-chan error) { } // scanFiles scans the list of files without channel allocations. -func scanFiles(files []string, functions chan<- *Function, errors chan<- error) { +func scanFiles(files []string, functions chan<- *core.Function, errors chan<- error) { wg := sync.WaitGroup{} for _, file := range files { @@ -81,7 +80,7 @@ func scanFiles(files []string, functions chan<- *Function, errors chan<- error) } // scanFile scans a single file. -func scanFile(path string, functions chan<- *Function) error { +func scanFile(path string, functions chan<- *core.Function) error { contents, err := os.ReadFile(path) if err != nil { @@ -236,49 +235,34 @@ func scanFile(path string, functions chan<- *Function) error { return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) } - function := &Function{ - Name: tokens[nameStart].Text(), - File: file, - Body: tokens[bodyStart:i], - compiler: compiler{ - assembler: asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 32), - }, - cpu: cpu.CPU{ - Call: x64.CallRegisters, - General: x64.GeneralRegisters, - Syscall: x64.SyscallRegisters, - Return: x64.ReturnValueRegisters, - }, - definitions: map[string]*Definition{}, - variables: map[string]*Variable{}, - finished: make(chan struct{}), - }, - } - + name := tokens[nameStart].Text() + body := tokens[bodyStart:i] + function := core.NewFunction(name, file, body) parameters := tokens[paramsStart:paramsEnd] + count := 0 err := expression.EachParameter(parameters, func(tokens token.List) error { - if len(tokens) == 1 { - name := tokens[0].Text() - register := x64.CallRegisters[len(function.variables)] - uses := countIdentifier(function.Body, name) - - if uses == 0 { - return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) - } - - variable := &Variable{ - Name: name, - Register: register, - Alive: uses, - } - - function.addVariable(variable) - return nil + if len(tokens) != 1 { + return errors.New(errors.NotImplemented, file, tokens[0].Position) } - return errors.New(errors.NotImplemented, file, tokens[0].Position) + name := tokens[0].Text() + register := x64.CallRegisters[count] + uses := core.CountIdentifier(function.Body, name) + + if uses == 0 { + return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) + } + + variable := &core.Variable{ + Name: name, + Register: register, + Alive: uses, + } + + function.AddVariable(variable) + count++ + return nil }) if err != nil { diff --git a/src/cli/Build.go b/src/cli/Build.go index 8db214f..ac4066c 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -9,22 +9,21 @@ import ( "git.akyoto.dev/cli/q/src/build/config" ) -// Build builds an executable. +// Build parses the arguments and creates a build. func Build(args []string) int { b := build.New() - dry := false for i := 0; i < len(args); i++ { switch args[i] { - case "--dry": - dry = true - - case "--assembler", "-a": + case "-a", "--assembler": config.Assembler = true - - case "--verbose", "-v": - config.Verbose = true - + case "-c", "--comments": + config.Comments = true + case "-d", "--dry": + config.Dry = true + case "-v", "--verbose": + config.Assembler = true + config.Comments = true default: if strings.HasPrefix(args[i], "-") { fmt.Printf("Unknown parameter: %s\n", args[i]) @@ -39,6 +38,11 @@ func Build(args []string) int { b.Files = append(b.Files, ".") } + return run(b) +} + +// run starts the build by running the compiler and then writing the result to disk. +func run(b *build.Build) int { result, err := b.Run() if err != nil { @@ -47,12 +51,10 @@ func Build(args []string) int { } if config.Assembler { - result.EachFunction(result.Main, map[*build.Function]bool{}, func(f *build.Function) { - f.PrintInstructions() - }) + result.PrintInstructions() } - if dry { + if config.Dry { return 0 } diff --git a/src/cli/Help.go b/src/cli/Help.go index 7cf99e4..b6c8f58 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -18,6 +18,7 @@ func Help(w io.Writer, code int) int { build options: --assembler, -a Show assembler instructions. - --verbose, -v Show verbose output.`) + --comments, -c Show assembler comments. + --verbose, -v Show everything.`) return code } diff --git a/bench_test.go b/tests/benchmarks_test.go similarity index 72% rename from bench_test.go rename to tests/benchmarks_test.go index 46b62ed..6ff03a7 100644 --- a/bench_test.go +++ b/tests/benchmarks_test.go @@ -1,4 +1,4 @@ -package main_test +package tests_test import ( "testing" @@ -8,7 +8,7 @@ import ( ) func BenchmarkEmpty(b *testing.B) { - compiler := build.New("tests/benchmarks/empty.q") + compiler := build.New("benchmarks/empty.q") for i := 0; i < b.N; i++ { _, err := compiler.Run() @@ -17,7 +17,7 @@ func BenchmarkEmpty(b *testing.B) { } func BenchmarkExpressions(b *testing.B) { - compiler := build.New("tests/benchmarks/expressions.q") + compiler := build.New("benchmarks/expressions.q") for i := 0; i < b.N; i++ { _, err := compiler.Run() diff --git a/errors_test.go b/tests/errors_test.go similarity index 93% rename from errors_test.go rename to tests/errors_test.go index f4cb7ab..9de3370 100644 --- a/errors_test.go +++ b/tests/errors_test.go @@ -1,4 +1,4 @@ -package main_test +package tests_test import ( "path/filepath" @@ -6,7 +6,7 @@ import ( "testing" "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/go/assert" ) @@ -41,7 +41,7 @@ func TestErrors(t *testing.T) { name := strings.TrimSuffix(test.File, ".q") t.Run(name, func(t *testing.T) { - b := build.New(filepath.Join("tests", "errors", test.File)) + b := build.New(filepath.Join("errors", test.File)) _, err := b.Run() assert.NotNil(t, err) assert.Contains(t, err.Error(), test.ExpectedError.Error()) diff --git a/tests/examples_test.go b/tests/examples_test.go new file mode 100644 index 0000000..a8d9954 --- /dev/null +++ b/tests/examples_test.go @@ -0,0 +1,23 @@ +package tests_test + +import ( + "path/filepath" + "testing" +) + +func TestExamples(t *testing.T) { + var tests = []struct { + Name string + ExpectedOutput string + ExpectedExitCode int + }{ + {"hello", "", 9}, + {"write", "ELF", 0}, + } + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + run(t, filepath.Join("..", "examples", test.Name), test.ExpectedOutput, test.ExpectedExitCode) + }) + } +} diff --git a/tests/programs/successive-calls.q b/tests/programs/successive-calls.q new file mode 100644 index 0000000..bffc930 --- /dev/null +++ b/tests/programs/successive-calls.q @@ -0,0 +1,7 @@ +main() { + syscall(60, f(1) + f(2) + f(3)) +} + +f(x) { + return x + 1 +} \ No newline at end of file diff --git a/examples_test.go b/tests/programs_test.go similarity index 60% rename from examples_test.go rename to tests/programs_test.go index faf1103..e671fd0 100644 --- a/examples_test.go +++ b/tests/programs_test.go @@ -1,38 +1,35 @@ -package main_test +package tests_test import ( "os" "os/exec" + "path/filepath" "testing" "git.akyoto.dev/cli/q/src/build" "git.akyoto.dev/go/assert" ) -func TestExamples(t *testing.T) { - var examples = []struct { +func TestPrograms(t *testing.T) { + var tests = []struct { Name string ExpectedOutput string ExpectedExitCode int }{ - {"hello", "", 25}, - {"write", "ELF", 0}, + {"successive-calls.q", "", 9}, } - for _, example := range examples { - example := example - - t.Run(example.Name, func(t *testing.T) { - runExample(t, example.Name, example.ExpectedOutput, example.ExpectedExitCode) + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + run(t, filepath.Join("programs", test.Name), test.ExpectedOutput, test.ExpectedExitCode) }) } } -// runExample builds and runs the example to check if the output matches the expected output. -func runExample(t *testing.T, name string, expectedOutput string, expectedExitCode int) { - b := build.New("examples/" + name) +// run builds and runs the file to check if the output matches the expected output. +func run(t *testing.T, name string, expectedOutput string, expectedExitCode int) { + b := build.New(name) assert.True(t, len(b.Executable()) > 0) - defer os.Remove(b.Executable()) t.Run("Compile", func(t *testing.T) { result, err := b.Run() From 5ca086f177481b2ba509b47ce511e065437241f0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 3 Jul 2024 11:59:36 +0200 Subject: [PATCH 0219/1012] Added more tests --- tests/benchmarks_test.go | 26 --------- tests/errors_test.go | 54 +++++++++---------- tests/examples_test.go | 20 +++---- tests/{benchmarks => programs}/empty.q | 0 .../{successive-calls.q => multi-calls.q} | 0 .../expressions.q => programs/square-sum.q} | 0 tests/programs_test.go | 33 ++++++++---- 7 files changed, 61 insertions(+), 72 deletions(-) delete mode 100644 tests/benchmarks_test.go rename tests/{benchmarks => programs}/empty.q (100%) rename tests/programs/{successive-calls.q => multi-calls.q} (100%) rename tests/{benchmarks/expressions.q => programs/square-sum.q} (100%) diff --git a/tests/benchmarks_test.go b/tests/benchmarks_test.go deleted file mode 100644 index 6ff03a7..0000000 --- a/tests/benchmarks_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package tests_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/go/assert" -) - -func BenchmarkEmpty(b *testing.B) { - compiler := build.New("benchmarks/empty.q") - - for i := 0; i < b.N; i++ { - _, err := compiler.Run() - assert.Nil(b, err) - } -} - -func BenchmarkExpressions(b *testing.B) { - compiler := build.New("benchmarks/expressions.q") - - for i := 0; i < b.N; i++ { - _, err := compiler.Run() - assert.Nil(b, err) - } -} diff --git a/tests/errors_test.go b/tests/errors_test.go index 9de3370..6299be8 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -10,34 +10,34 @@ import ( "git.akyoto.dev/go/assert" ) -func TestErrors(t *testing.T) { - tests := []struct { - File string - ExpectedError error - }{ - {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, - {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, - {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, - {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, - {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, - {"InvalidExpression.q", errors.InvalidExpression}, - {"InvalidOperator.q", &errors.InvalidOperator{Operator: "+++"}}, - {"InvalidCharacter.q", &errors.InvalidCharacter{Character: "@"}}, - {"InvalidCharacter2.q", &errors.InvalidCharacter{Character: "@"}}, - {"InvalidCharacter3.q", &errors.InvalidCharacter{Character: "@"}}, - {"MissingAssignValue.q", errors.MissingAssignValue}, - {"MissingBlockEnd.q", errors.MissingBlockEnd}, - {"MissingBlockStart.q", errors.MissingBlockStart}, - {"MissingGroupEnd.q", errors.MissingGroupEnd}, - {"MissingGroupStart.q", errors.MissingGroupStart}, - {"MissingMainFunction.q", errors.MissingMainFunction}, - {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, - {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, - {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, - {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, - } +var errs = []struct { + File string + ExpectedError error +}{ + {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, + {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, + {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, + {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, + {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, + {"InvalidExpression.q", errors.InvalidExpression}, + {"InvalidOperator.q", &errors.InvalidOperator{Operator: "+++"}}, + {"InvalidCharacter.q", &errors.InvalidCharacter{Character: "@"}}, + {"InvalidCharacter2.q", &errors.InvalidCharacter{Character: "@"}}, + {"InvalidCharacter3.q", &errors.InvalidCharacter{Character: "@"}}, + {"MissingAssignValue.q", errors.MissingAssignValue}, + {"MissingBlockEnd.q", errors.MissingBlockEnd}, + {"MissingBlockStart.q", errors.MissingBlockStart}, + {"MissingGroupEnd.q", errors.MissingGroupEnd}, + {"MissingGroupStart.q", errors.MissingGroupStart}, + {"MissingMainFunction.q", errors.MissingMainFunction}, + {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, + {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, + {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, + {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, +} - for _, test := range tests { +func TestErrors(t *testing.T) { + for _, test := range errs { name := strings.TrimSuffix(test.File, ".q") t.Run(name, func(t *testing.T) { diff --git a/tests/examples_test.go b/tests/examples_test.go index a8d9954..ab00191 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -5,17 +5,17 @@ import ( "testing" ) -func TestExamples(t *testing.T) { - var tests = []struct { - Name string - ExpectedOutput string - ExpectedExitCode int - }{ - {"hello", "", 9}, - {"write", "ELF", 0}, - } +var examples = []struct { + Name string + ExpectedOutput string + ExpectedExitCode int +}{ + {"hello", "", 9}, + {"write", "ELF", 0}, +} - for _, test := range tests { +func TestExamples(t *testing.T) { + for _, test := range examples { t.Run(test.Name, func(t *testing.T) { run(t, filepath.Join("..", "examples", test.Name), test.ExpectedOutput, test.ExpectedExitCode) }) diff --git a/tests/benchmarks/empty.q b/tests/programs/empty.q similarity index 100% rename from tests/benchmarks/empty.q rename to tests/programs/empty.q diff --git a/tests/programs/successive-calls.q b/tests/programs/multi-calls.q similarity index 100% rename from tests/programs/successive-calls.q rename to tests/programs/multi-calls.q diff --git a/tests/benchmarks/expressions.q b/tests/programs/square-sum.q similarity index 100% rename from tests/benchmarks/expressions.q rename to tests/programs/square-sum.q diff --git a/tests/programs_test.go b/tests/programs_test.go index e671fd0..a5ee862 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -10,16 +10,31 @@ import ( "git.akyoto.dev/go/assert" ) -func TestPrograms(t *testing.T) { - var tests = []struct { - Name string - ExpectedOutput string - ExpectedExitCode int - }{ - {"successive-calls.q", "", 9}, - } +var programs = []struct { + Name string + ExpectedOutput string + ExpectedExitCode int +}{ + {"empty.q", "", 0}, + {"square-sum.q", "", 25}, + {"multi-calls.q", "", 9}, +} - for _, test := range tests { +func BenchmarkPrograms(b *testing.B) { + for _, test := range programs { + b.Run(test.Name, func(b *testing.B) { + compiler := build.New(filepath.Join("programs", test.Name)) + + for i := 0; i < b.N; i++ { + _, err := compiler.Run() + assert.Nil(b, err) + } + }) + } +} + +func TestPrograms(t *testing.T) { + for _, test := range programs { t.Run(test.Name, func(t *testing.T) { run(t, filepath.Join("programs", test.Name), test.ExpectedOutput, test.ExpectedExitCode) }) From 795935ddfbd9f38a90a60db8d03c2ef3430f1414 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 3 Jul 2024 11:59:36 +0200 Subject: [PATCH 0220/1012] Added more tests --- tests/benchmarks_test.go | 26 --------- tests/errors_test.go | 54 +++++++++---------- tests/examples_test.go | 20 +++---- tests/{benchmarks => programs}/empty.q | 0 .../{successive-calls.q => multi-calls.q} | 0 .../expressions.q => programs/square-sum.q} | 0 tests/programs_test.go | 33 ++++++++---- 7 files changed, 61 insertions(+), 72 deletions(-) delete mode 100644 tests/benchmarks_test.go rename tests/{benchmarks => programs}/empty.q (100%) rename tests/programs/{successive-calls.q => multi-calls.q} (100%) rename tests/{benchmarks/expressions.q => programs/square-sum.q} (100%) diff --git a/tests/benchmarks_test.go b/tests/benchmarks_test.go deleted file mode 100644 index 6ff03a7..0000000 --- a/tests/benchmarks_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package tests_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/go/assert" -) - -func BenchmarkEmpty(b *testing.B) { - compiler := build.New("benchmarks/empty.q") - - for i := 0; i < b.N; i++ { - _, err := compiler.Run() - assert.Nil(b, err) - } -} - -func BenchmarkExpressions(b *testing.B) { - compiler := build.New("benchmarks/expressions.q") - - for i := 0; i < b.N; i++ { - _, err := compiler.Run() - assert.Nil(b, err) - } -} diff --git a/tests/errors_test.go b/tests/errors_test.go index 9de3370..6299be8 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -10,34 +10,34 @@ import ( "git.akyoto.dev/go/assert" ) -func TestErrors(t *testing.T) { - tests := []struct { - File string - ExpectedError error - }{ - {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, - {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, - {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, - {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, - {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, - {"InvalidExpression.q", errors.InvalidExpression}, - {"InvalidOperator.q", &errors.InvalidOperator{Operator: "+++"}}, - {"InvalidCharacter.q", &errors.InvalidCharacter{Character: "@"}}, - {"InvalidCharacter2.q", &errors.InvalidCharacter{Character: "@"}}, - {"InvalidCharacter3.q", &errors.InvalidCharacter{Character: "@"}}, - {"MissingAssignValue.q", errors.MissingAssignValue}, - {"MissingBlockEnd.q", errors.MissingBlockEnd}, - {"MissingBlockStart.q", errors.MissingBlockStart}, - {"MissingGroupEnd.q", errors.MissingGroupEnd}, - {"MissingGroupStart.q", errors.MissingGroupStart}, - {"MissingMainFunction.q", errors.MissingMainFunction}, - {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, - {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, - {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, - {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, - } +var errs = []struct { + File string + ExpectedError error +}{ + {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, + {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, + {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, + {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, + {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, + {"InvalidExpression.q", errors.InvalidExpression}, + {"InvalidOperator.q", &errors.InvalidOperator{Operator: "+++"}}, + {"InvalidCharacter.q", &errors.InvalidCharacter{Character: "@"}}, + {"InvalidCharacter2.q", &errors.InvalidCharacter{Character: "@"}}, + {"InvalidCharacter3.q", &errors.InvalidCharacter{Character: "@"}}, + {"MissingAssignValue.q", errors.MissingAssignValue}, + {"MissingBlockEnd.q", errors.MissingBlockEnd}, + {"MissingBlockStart.q", errors.MissingBlockStart}, + {"MissingGroupEnd.q", errors.MissingGroupEnd}, + {"MissingGroupStart.q", errors.MissingGroupStart}, + {"MissingMainFunction.q", errors.MissingMainFunction}, + {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, + {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, + {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, + {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, +} - for _, test := range tests { +func TestErrors(t *testing.T) { + for _, test := range errs { name := strings.TrimSuffix(test.File, ".q") t.Run(name, func(t *testing.T) { diff --git a/tests/examples_test.go b/tests/examples_test.go index a8d9954..ab00191 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -5,17 +5,17 @@ import ( "testing" ) -func TestExamples(t *testing.T) { - var tests = []struct { - Name string - ExpectedOutput string - ExpectedExitCode int - }{ - {"hello", "", 9}, - {"write", "ELF", 0}, - } +var examples = []struct { + Name string + ExpectedOutput string + ExpectedExitCode int +}{ + {"hello", "", 9}, + {"write", "ELF", 0}, +} - for _, test := range tests { +func TestExamples(t *testing.T) { + for _, test := range examples { t.Run(test.Name, func(t *testing.T) { run(t, filepath.Join("..", "examples", test.Name), test.ExpectedOutput, test.ExpectedExitCode) }) diff --git a/tests/benchmarks/empty.q b/tests/programs/empty.q similarity index 100% rename from tests/benchmarks/empty.q rename to tests/programs/empty.q diff --git a/tests/programs/successive-calls.q b/tests/programs/multi-calls.q similarity index 100% rename from tests/programs/successive-calls.q rename to tests/programs/multi-calls.q diff --git a/tests/benchmarks/expressions.q b/tests/programs/square-sum.q similarity index 100% rename from tests/benchmarks/expressions.q rename to tests/programs/square-sum.q diff --git a/tests/programs_test.go b/tests/programs_test.go index e671fd0..a5ee862 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -10,16 +10,31 @@ import ( "git.akyoto.dev/go/assert" ) -func TestPrograms(t *testing.T) { - var tests = []struct { - Name string - ExpectedOutput string - ExpectedExitCode int - }{ - {"successive-calls.q", "", 9}, - } +var programs = []struct { + Name string + ExpectedOutput string + ExpectedExitCode int +}{ + {"empty.q", "", 0}, + {"square-sum.q", "", 25}, + {"multi-calls.q", "", 9}, +} - for _, test := range tests { +func BenchmarkPrograms(b *testing.B) { + for _, test := range programs { + b.Run(test.Name, func(b *testing.B) { + compiler := build.New(filepath.Join("programs", test.Name)) + + for i := 0; i < b.N; i++ { + _, err := compiler.Run() + assert.Nil(b, err) + } + }) + } +} + +func TestPrograms(t *testing.T) { + for _, test := range programs { t.Run(test.Name, func(t *testing.T) { run(t, filepath.Join("programs", test.Name), test.ExpectedOutput, test.ExpectedExitCode) }) From 75672c1e161a1c1d76ebdd3d9724f1e0ec4e8cda Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 3 Jul 2024 16:37:59 +0200 Subject: [PATCH 0221/1012] Improved code generation --- src/build/core/CompileCall.go | 56 ++++++---- src/build/core/CompileDefinition.go | 23 +--- src/build/core/CompileReturn.go | 2 +- src/build/core/Evaluate.go | 123 ---------------------- src/build/core/Execute.go | 57 +++------- src/build/core/ExecuteLeaf.go | 37 +++++++ src/build/core/ExecuteRegisterNumber.go | 33 ++++++ src/build/core/ExecuteRegisterRegister.go | 35 ++++++ src/build/core/ExpressionToRegister.go | 33 ++++++ src/build/core/ExpressionsToRegisters.go | 20 ++++ src/build/core/Function.go | 4 +- src/build/core/TokenToRegister.go | 2 +- src/build/core/opRegister.go | 60 ----------- src/build/core/state.go | 1 - src/build/cpu/CPU.go | 4 +- 15 files changed, 215 insertions(+), 275 deletions(-) delete mode 100644 src/build/core/Evaluate.go create mode 100644 src/build/core/ExecuteLeaf.go create mode 100644 src/build/core/ExecuteRegisterNumber.go create mode 100644 src/build/core/ExecuteRegisterRegister.go create mode 100644 src/build/core/ExpressionToRegister.go create mode 100644 src/build/core/ExpressionsToRegisters.go delete mode 100644 src/build/core/opRegister.go diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 283439c..02943fa 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -1,7 +1,7 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/expression" ) @@ -9,9 +9,43 @@ import ( // All call registers must hold the correct parameter values before the function invocation. // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. -func (f *Function) CompileCall(expr *expression.Expression) error { - _, err := f.EvaluateCall(expr) - return err +func (f *Function) CompileCall(root *expression.Expression) error { + funcName := root.Children[0].Token.Text() + parameters := root.Children[1:] + registers := f.cpu.Input[:len(parameters)] + isSyscall := funcName == "syscall" + + if isSyscall { + registers = f.cpu.Syscall[:len(parameters)] + } + + err := f.ExpressionsToRegisters(parameters, registers) + + if err != nil { + return err + } + + for _, register := range f.cpu.General { + if !f.cpu.IsFree(register) { + f.assembler.Register(asm.PUSH, register) + } + } + + if isSyscall { + f.assembler.Syscall() + } else { + f.assembler.Call(funcName) + } + + for i := len(f.cpu.General) - 1; i >= 0; i-- { + register := f.cpu.General[i] + + if !f.cpu.IsFree(register) { + f.assembler.Register(asm.POP, register) + } + } + + return nil } // CompileSyscall executes a syscall. @@ -23,17 +57,3 @@ func (f *Function) CompileSyscall(expr *expression.Expression) error { f.sideEffects++ return err } - -// ExpressionsToRegisters moves multiple expressions into the specified registers. -func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { - for i := len(registers) - 1; i >= 0; i-- { - f.SaveRegister(registers[i]) - err := f.EvaluateTo(expressions[i], registers[i]) - - if err != nil { - return err - } - } - - return nil -} diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 7abd309..13b361f 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -59,27 +59,6 @@ func (f *Function) AddVariable(variable *Variable) { f.cpu.Use(variable.Register) } -func (f *Function) addTemporary(root *expression.Expression) *Variable { - f.count.tmps++ - name := fmt.Sprintf("t%d", f.count.tmps) - register := f.cpu.MustUseFree(f.cpu.General) - - tmp := &Variable{ - Name: name, - Value: root, - Alive: 1, - Register: register, - } - - f.variables[name] = tmp - - if config.Comments { - f.assembler.Comment(fmt.Sprintf("%s = %s (%s)", name, root, register)) - } - - return tmp -} - func (f *Function) useVariable(variable *Variable) { variable.Alive-- @@ -121,7 +100,7 @@ func (f *Function) storeVariableInRegister(name string, value *expression.Expres panic("no free registers") } - err := f.EvaluateTo(value, reg) + err := f.ExpressionToRegister(value, reg) f.AddVariable(&Variable{ Name: name, diff --git a/src/build/core/CompileReturn.go b/src/build/core/CompileReturn.go index c829a91..4effe9a 100644 --- a/src/build/core/CompileReturn.go +++ b/src/build/core/CompileReturn.go @@ -12,5 +12,5 @@ func (f *Function) CompileReturn(node *ast.Return) error { return nil } - return f.EvaluateTo(node.Value, f.cpu.Return[0]) + return f.ExpressionToRegister(node.Value, f.cpu.Output[0]) } diff --git a/src/build/core/Evaluate.go b/src/build/core/Evaluate.go deleted file mode 100644 index ed65df0..0000000 --- a/src/build/core/Evaluate.go +++ /dev/null @@ -1,123 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" -) - -// Evaluate evaluates the result of an expression and saves it into a temporary register. -func (f *Function) Evaluate(root *expression.Expression) (*Variable, error) { - if root.IsLeaf() { - return f.EvaluateLeaf(root) - } - - if ast.IsFunctionCall(root) { - return f.EvaluateCall(root) - } - - left := root.Children[0] - right := root.Children[1] - - tmpLeft, err := f.Evaluate(left) - - if err != nil { - return nil, err - } - - tmpLeftExpr := expression.NewLeaf(token.Token{ - Kind: token.Identifier, - Position: left.Token.Position, - Bytes: []byte(tmpLeft.Name), - }) - - tmpLeftExpr.Parent = root - root.Children[0].Parent = nil - root.Children[0] = tmpLeftExpr - - tmpRight, err := f.Evaluate(right) - - if err != nil { - return nil, err - } - - tmpRightExpr := expression.NewLeaf(token.Token{ - Kind: token.Identifier, - Position: left.Token.Position, - Bytes: []byte(tmpRight.Name), - }) - - tmpRightExpr.Parent = root - root.Children[1].Parent = nil - root.Children[1] = tmpRightExpr - - tmp := f.addTemporary(root) - - f.assembler.RegisterRegister(asm.MOVE, tmp.Register, tmpLeft.Register) - f.useVariable(tmpLeft) - - err = f.opRegisterRegister(root.Token, tmp.Register, tmpRight.Register) - f.useVariable(tmpRight) - - return tmp, err -} - -func (f *Function) EvaluateCall(root *expression.Expression) (*Variable, error) { - funcName := root.Children[0].Token.Text() - parameters := root.Children[1:] - registers := f.cpu.Call[:len(parameters)] - isSyscall := funcName == "syscall" - - if isSyscall { - registers = f.cpu.Syscall[:len(parameters)] - } - - err := f.ExpressionsToRegisters(parameters, registers) - - for _, register := range f.cpu.General { - if !f.cpu.IsFree(register) { - f.assembler.Register(asm.PUSH, register) - } - } - - if isSyscall { - f.assembler.Syscall() - } else { - f.assembler.Call(funcName) - } - - for i := len(f.cpu.General) - 1; i >= 0; i-- { - register := f.cpu.General[i] - - if !f.cpu.IsFree(register) { - f.assembler.Register(asm.POP, register) - } - } - - tmp := f.addTemporary(root) - f.assembler.RegisterRegister(asm.MOVE, tmp.Register, f.cpu.Return[0]) - return tmp, err -} - -func (f *Function) EvaluateLeaf(root *expression.Expression) (*Variable, error) { - tmp := f.addTemporary(root) - err := f.TokenToRegister(root.Token, tmp.Register) - return tmp, err -} - -func (f *Function) EvaluateTo(root *expression.Expression, register cpu.Register) error { - tmp, err := f.Evaluate(root) - - if err != nil { - return err - } - - if register != tmp.Register { - f.assembler.RegisterRegister(asm.MOVE, register, tmp.Register) - } - - f.useVariable(tmp) - return nil -} diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index 067f854..c42c4d1 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -1,11 +1,8 @@ package core import ( - "strconv" - "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" ) @@ -16,54 +13,24 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteLeaf(operation, register, value.Token) } - var temporary cpu.Register - if ast.IsFunctionCall(value) { - temporary = f.cpu.Return[0] - } else { - found := false - temporary, found = f.cpu.FindFree(f.cpu.General) - - if !found { - panic("no free registers") - } - } - - f.cpu.Use(temporary) - defer f.cpu.Free(temporary) - err := f.EvaluateTo(value, temporary) - - if err != nil { - return err - } - - return f.opRegisterRegister(operation, register, temporary) -} - -// ExecuteLeaf performs an operation on a register with the given leaf operand. -func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error { - switch operand.Kind { - case token.Identifier: - name := operand.Text() - variable, exists := f.variables[name] - - if !exists { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) - } - - defer f.useVariable(variable) - return f.opRegisterRegister(operation, register, variable.Register) - - case token.Number: - value := operand.Text() - number, err := strconv.Atoi(value) + err := f.CompileCall(value) if err != nil { return err } - return f.opRegisterNumber(operation, register, number) + return f.ExecuteRegisterRegister(operation, register, f.cpu.Output[0]) } - return errors.New(errors.NotImplemented, f.File, operation.Position) + tmp := f.cpu.MustUseFree(f.cpu.General) + defer f.cpu.Free(tmp) + + err := f.ExpressionToRegister(value, tmp) + + if err != nil { + return err + } + + return f.ExecuteRegisterRegister(operation, register, tmp) } diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go new file mode 100644 index 0000000..1349870 --- /dev/null +++ b/src/build/core/ExecuteLeaf.go @@ -0,0 +1,37 @@ +package core + +import ( + "strconv" + + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" +) + +// ExecuteLeaf performs an operation on a register with the given leaf operand. +func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error { + switch operand.Kind { + case token.Identifier: + name := operand.Text() + variable, exists := f.variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) + } + + defer f.useVariable(variable) + return f.ExecuteRegisterRegister(operation, register, variable.Register) + + case token.Number: + value := operand.Text() + number, err := strconv.Atoi(value) + + if err != nil { + return err + } + + return f.ExecuteRegisterNumber(operation, register, number) + } + + return errors.New(errors.NotImplemented, f.File, operation.Position) +} diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go new file mode 100644 index 0000000..01039ed --- /dev/null +++ b/src/build/core/ExecuteRegisterNumber.go @@ -0,0 +1,33 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" +) + +// ExecuteRegisterNumber performs an operation on a register and a number. +func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { + switch operation.Text() { + case "+", "+=": + f.assembler.RegisterNumber(asm.ADD, register, number) + + case "-", "-=": + f.assembler.RegisterNumber(asm.SUB, register, number) + + case "*", "*=": + f.assembler.RegisterNumber(asm.MUL, register, number) + + case "/", "/=": + f.assembler.RegisterNumber(asm.DIV, register, number) + + case "=": + f.assembler.RegisterNumber(asm.MOVE, register, number) + + default: + return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) + } + + return nil +} diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go new file mode 100644 index 0000000..b271f9a --- /dev/null +++ b/src/build/core/ExecuteRegisterRegister.go @@ -0,0 +1,35 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" +) + +// ExecuteRegisterRegister performs an operation on two registers. +func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { + switch operation.Text() { + case "+", "+=": + f.assembler.RegisterRegister(asm.ADD, destination, source) + + case "-", "-=": + f.assembler.RegisterRegister(asm.SUB, destination, source) + + case "*", "*=": + f.assembler.RegisterRegister(asm.MUL, destination, source) + + case "/", "/=": + f.assembler.RegisterRegister(asm.DIV, destination, source) + + case "=": + if destination != source { + f.assembler.RegisterRegister(asm.MOVE, destination, source) + } + + default: + return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) + } + + return nil +} diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go new file mode 100644 index 0000000..1528e26 --- /dev/null +++ b/src/build/core/ExpressionToRegister.go @@ -0,0 +1,33 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" +) + +// ExpressionToRegister puts the result of an expression into the specified register. +func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { + if root.IsLeaf() { + return f.TokenToRegister(root.Token, register) + } + + if ast.IsFunctionCall(root) { + err := f.CompileCall(root) + f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Output[0]) + return err + } + + left := root.Children[0] + right := root.Children[1] + + err := f.ExpressionToRegister(left, register) + + if err != nil { + return err + } + + f.SaveRegister(register) + return f.Execute(root.Token, register, right) +} diff --git a/src/build/core/ExpressionsToRegisters.go b/src/build/core/ExpressionsToRegisters.go new file mode 100644 index 0000000..6e293e2 --- /dev/null +++ b/src/build/core/ExpressionsToRegisters.go @@ -0,0 +1,20 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" +) + +// ExpressionsToRegisters moves multiple expressions into the specified registers. +func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { + for i := len(registers) - 1; i >= 0; i-- { + f.SaveRegister(registers[i]) + err := f.ExpressionToRegister(expressions[i], registers[i]) + + if err != nil { + return err + } + } + + return nil +} diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 518affb..a80e7d1 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -31,10 +31,10 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { Instructions: make([]asm.Instruction, 0, 32), }, cpu: cpu.CPU{ - Call: x64.CallRegisters, + Input: x64.CallRegisters, General: x64.GeneralRegisters, Syscall: x64.SyscallRegisters, - Return: x64.ReturnValueRegisters, + Output: x64.ReturnValueRegisters, }, definitions: map[string]*Definition{}, variables: map[string]*Variable{}, diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index faf4036..bdce4d6 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -18,7 +18,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { constant, exists := f.definitions[name] if exists { - return f.EvaluateTo(constant.Value, register) + return f.ExpressionToRegister(constant.Value, register) } variable, exists := f.variables[name] diff --git a/src/build/core/opRegister.go b/src/build/core/opRegister.go deleted file mode 100644 index 2cdd81c..0000000 --- a/src/build/core/opRegister.go +++ /dev/null @@ -1,60 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" -) - -// opRegisterNumber performs an operation on a register and a number. -func (f *Function) opRegisterNumber(operation token.Token, register cpu.Register, number int) error { - switch operation.Text() { - case "+", "+=": - f.assembler.RegisterNumber(asm.ADD, register, number) - - case "-", "-=": - f.assembler.RegisterNumber(asm.SUB, register, number) - - case "*", "*=": - f.assembler.RegisterNumber(asm.MUL, register, number) - - case "/", "/=": - f.assembler.RegisterNumber(asm.DIV, register, number) - - case "=": - f.assembler.RegisterNumber(asm.MOVE, register, number) - - default: - return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) - } - - return nil -} - -// opRegisterRegister performs an operation on two registers. -func (f *Function) opRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { - switch operation.Text() { - case "+", "+=": - f.assembler.RegisterRegister(asm.ADD, destination, source) - - case "-", "-=": - f.assembler.RegisterRegister(asm.SUB, destination, source) - - case "*", "*=": - f.assembler.RegisterRegister(asm.MUL, destination, source) - - case "/", "/=": - f.assembler.RegisterRegister(asm.DIV, destination, source) - - case "=": - if destination != source { - f.assembler.RegisterRegister(asm.MOVE, destination, source) - } - - default: - return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) - } - - return nil -} diff --git a/src/build/core/state.go b/src/build/core/state.go index 70078c3..6a0d5e8 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -24,7 +24,6 @@ type state struct { // counter stores how often a certain statement appeared so we can generate a unique label from it. type counter struct { loop int - tmps int } // PrintInstructions shows the assembly instructions. diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go index 8f0f9f3..2015a38 100644 --- a/src/build/cpu/CPU.go +++ b/src/build/cpu/CPU.go @@ -2,10 +2,10 @@ package cpu // CPU represents the processor. type CPU struct { - Call []Register General []Register Syscall []Register - Return []Register + Input []Register + Output []Register usage uint64 } From 4d88260333cd154d78e77aafb2b3459cde2683b4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 3 Jul 2024 16:37:59 +0200 Subject: [PATCH 0222/1012] Improved code generation --- src/build/core/CompileCall.go | 56 ++++++---- src/build/core/CompileDefinition.go | 23 +--- src/build/core/CompileReturn.go | 2 +- src/build/core/Evaluate.go | 123 ---------------------- src/build/core/Execute.go | 57 +++------- src/build/core/ExecuteLeaf.go | 37 +++++++ src/build/core/ExecuteRegisterNumber.go | 33 ++++++ src/build/core/ExecuteRegisterRegister.go | 35 ++++++ src/build/core/ExpressionToRegister.go | 33 ++++++ src/build/core/ExpressionsToRegisters.go | 20 ++++ src/build/core/Function.go | 4 +- src/build/core/TokenToRegister.go | 2 +- src/build/core/opRegister.go | 60 ----------- src/build/core/state.go | 1 - src/build/cpu/CPU.go | 4 +- 15 files changed, 215 insertions(+), 275 deletions(-) delete mode 100644 src/build/core/Evaluate.go create mode 100644 src/build/core/ExecuteLeaf.go create mode 100644 src/build/core/ExecuteRegisterNumber.go create mode 100644 src/build/core/ExecuteRegisterRegister.go create mode 100644 src/build/core/ExpressionToRegister.go create mode 100644 src/build/core/ExpressionsToRegisters.go delete mode 100644 src/build/core/opRegister.go diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 283439c..02943fa 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -1,7 +1,7 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/expression" ) @@ -9,9 +9,43 @@ import ( // All call registers must hold the correct parameter values before the function invocation. // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. -func (f *Function) CompileCall(expr *expression.Expression) error { - _, err := f.EvaluateCall(expr) - return err +func (f *Function) CompileCall(root *expression.Expression) error { + funcName := root.Children[0].Token.Text() + parameters := root.Children[1:] + registers := f.cpu.Input[:len(parameters)] + isSyscall := funcName == "syscall" + + if isSyscall { + registers = f.cpu.Syscall[:len(parameters)] + } + + err := f.ExpressionsToRegisters(parameters, registers) + + if err != nil { + return err + } + + for _, register := range f.cpu.General { + if !f.cpu.IsFree(register) { + f.assembler.Register(asm.PUSH, register) + } + } + + if isSyscall { + f.assembler.Syscall() + } else { + f.assembler.Call(funcName) + } + + for i := len(f.cpu.General) - 1; i >= 0; i-- { + register := f.cpu.General[i] + + if !f.cpu.IsFree(register) { + f.assembler.Register(asm.POP, register) + } + } + + return nil } // CompileSyscall executes a syscall. @@ -23,17 +57,3 @@ func (f *Function) CompileSyscall(expr *expression.Expression) error { f.sideEffects++ return err } - -// ExpressionsToRegisters moves multiple expressions into the specified registers. -func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { - for i := len(registers) - 1; i >= 0; i-- { - f.SaveRegister(registers[i]) - err := f.EvaluateTo(expressions[i], registers[i]) - - if err != nil { - return err - } - } - - return nil -} diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 7abd309..13b361f 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -59,27 +59,6 @@ func (f *Function) AddVariable(variable *Variable) { f.cpu.Use(variable.Register) } -func (f *Function) addTemporary(root *expression.Expression) *Variable { - f.count.tmps++ - name := fmt.Sprintf("t%d", f.count.tmps) - register := f.cpu.MustUseFree(f.cpu.General) - - tmp := &Variable{ - Name: name, - Value: root, - Alive: 1, - Register: register, - } - - f.variables[name] = tmp - - if config.Comments { - f.assembler.Comment(fmt.Sprintf("%s = %s (%s)", name, root, register)) - } - - return tmp -} - func (f *Function) useVariable(variable *Variable) { variable.Alive-- @@ -121,7 +100,7 @@ func (f *Function) storeVariableInRegister(name string, value *expression.Expres panic("no free registers") } - err := f.EvaluateTo(value, reg) + err := f.ExpressionToRegister(value, reg) f.AddVariable(&Variable{ Name: name, diff --git a/src/build/core/CompileReturn.go b/src/build/core/CompileReturn.go index c829a91..4effe9a 100644 --- a/src/build/core/CompileReturn.go +++ b/src/build/core/CompileReturn.go @@ -12,5 +12,5 @@ func (f *Function) CompileReturn(node *ast.Return) error { return nil } - return f.EvaluateTo(node.Value, f.cpu.Return[0]) + return f.ExpressionToRegister(node.Value, f.cpu.Output[0]) } diff --git a/src/build/core/Evaluate.go b/src/build/core/Evaluate.go deleted file mode 100644 index ed65df0..0000000 --- a/src/build/core/Evaluate.go +++ /dev/null @@ -1,123 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" -) - -// Evaluate evaluates the result of an expression and saves it into a temporary register. -func (f *Function) Evaluate(root *expression.Expression) (*Variable, error) { - if root.IsLeaf() { - return f.EvaluateLeaf(root) - } - - if ast.IsFunctionCall(root) { - return f.EvaluateCall(root) - } - - left := root.Children[0] - right := root.Children[1] - - tmpLeft, err := f.Evaluate(left) - - if err != nil { - return nil, err - } - - tmpLeftExpr := expression.NewLeaf(token.Token{ - Kind: token.Identifier, - Position: left.Token.Position, - Bytes: []byte(tmpLeft.Name), - }) - - tmpLeftExpr.Parent = root - root.Children[0].Parent = nil - root.Children[0] = tmpLeftExpr - - tmpRight, err := f.Evaluate(right) - - if err != nil { - return nil, err - } - - tmpRightExpr := expression.NewLeaf(token.Token{ - Kind: token.Identifier, - Position: left.Token.Position, - Bytes: []byte(tmpRight.Name), - }) - - tmpRightExpr.Parent = root - root.Children[1].Parent = nil - root.Children[1] = tmpRightExpr - - tmp := f.addTemporary(root) - - f.assembler.RegisterRegister(asm.MOVE, tmp.Register, tmpLeft.Register) - f.useVariable(tmpLeft) - - err = f.opRegisterRegister(root.Token, tmp.Register, tmpRight.Register) - f.useVariable(tmpRight) - - return tmp, err -} - -func (f *Function) EvaluateCall(root *expression.Expression) (*Variable, error) { - funcName := root.Children[0].Token.Text() - parameters := root.Children[1:] - registers := f.cpu.Call[:len(parameters)] - isSyscall := funcName == "syscall" - - if isSyscall { - registers = f.cpu.Syscall[:len(parameters)] - } - - err := f.ExpressionsToRegisters(parameters, registers) - - for _, register := range f.cpu.General { - if !f.cpu.IsFree(register) { - f.assembler.Register(asm.PUSH, register) - } - } - - if isSyscall { - f.assembler.Syscall() - } else { - f.assembler.Call(funcName) - } - - for i := len(f.cpu.General) - 1; i >= 0; i-- { - register := f.cpu.General[i] - - if !f.cpu.IsFree(register) { - f.assembler.Register(asm.POP, register) - } - } - - tmp := f.addTemporary(root) - f.assembler.RegisterRegister(asm.MOVE, tmp.Register, f.cpu.Return[0]) - return tmp, err -} - -func (f *Function) EvaluateLeaf(root *expression.Expression) (*Variable, error) { - tmp := f.addTemporary(root) - err := f.TokenToRegister(root.Token, tmp.Register) - return tmp, err -} - -func (f *Function) EvaluateTo(root *expression.Expression, register cpu.Register) error { - tmp, err := f.Evaluate(root) - - if err != nil { - return err - } - - if register != tmp.Register { - f.assembler.RegisterRegister(asm.MOVE, register, tmp.Register) - } - - f.useVariable(tmp) - return nil -} diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index 067f854..c42c4d1 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -1,11 +1,8 @@ package core import ( - "strconv" - "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" ) @@ -16,54 +13,24 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteLeaf(operation, register, value.Token) } - var temporary cpu.Register - if ast.IsFunctionCall(value) { - temporary = f.cpu.Return[0] - } else { - found := false - temporary, found = f.cpu.FindFree(f.cpu.General) - - if !found { - panic("no free registers") - } - } - - f.cpu.Use(temporary) - defer f.cpu.Free(temporary) - err := f.EvaluateTo(value, temporary) - - if err != nil { - return err - } - - return f.opRegisterRegister(operation, register, temporary) -} - -// ExecuteLeaf performs an operation on a register with the given leaf operand. -func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error { - switch operand.Kind { - case token.Identifier: - name := operand.Text() - variable, exists := f.variables[name] - - if !exists { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) - } - - defer f.useVariable(variable) - return f.opRegisterRegister(operation, register, variable.Register) - - case token.Number: - value := operand.Text() - number, err := strconv.Atoi(value) + err := f.CompileCall(value) if err != nil { return err } - return f.opRegisterNumber(operation, register, number) + return f.ExecuteRegisterRegister(operation, register, f.cpu.Output[0]) } - return errors.New(errors.NotImplemented, f.File, operation.Position) + tmp := f.cpu.MustUseFree(f.cpu.General) + defer f.cpu.Free(tmp) + + err := f.ExpressionToRegister(value, tmp) + + if err != nil { + return err + } + + return f.ExecuteRegisterRegister(operation, register, tmp) } diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go new file mode 100644 index 0000000..1349870 --- /dev/null +++ b/src/build/core/ExecuteLeaf.go @@ -0,0 +1,37 @@ +package core + +import ( + "strconv" + + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" +) + +// ExecuteLeaf performs an operation on a register with the given leaf operand. +func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error { + switch operand.Kind { + case token.Identifier: + name := operand.Text() + variable, exists := f.variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) + } + + defer f.useVariable(variable) + return f.ExecuteRegisterRegister(operation, register, variable.Register) + + case token.Number: + value := operand.Text() + number, err := strconv.Atoi(value) + + if err != nil { + return err + } + + return f.ExecuteRegisterNumber(operation, register, number) + } + + return errors.New(errors.NotImplemented, f.File, operation.Position) +} diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go new file mode 100644 index 0000000..01039ed --- /dev/null +++ b/src/build/core/ExecuteRegisterNumber.go @@ -0,0 +1,33 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" +) + +// ExecuteRegisterNumber performs an operation on a register and a number. +func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { + switch operation.Text() { + case "+", "+=": + f.assembler.RegisterNumber(asm.ADD, register, number) + + case "-", "-=": + f.assembler.RegisterNumber(asm.SUB, register, number) + + case "*", "*=": + f.assembler.RegisterNumber(asm.MUL, register, number) + + case "/", "/=": + f.assembler.RegisterNumber(asm.DIV, register, number) + + case "=": + f.assembler.RegisterNumber(asm.MOVE, register, number) + + default: + return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) + } + + return nil +} diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go new file mode 100644 index 0000000..b271f9a --- /dev/null +++ b/src/build/core/ExecuteRegisterRegister.go @@ -0,0 +1,35 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" +) + +// ExecuteRegisterRegister performs an operation on two registers. +func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { + switch operation.Text() { + case "+", "+=": + f.assembler.RegisterRegister(asm.ADD, destination, source) + + case "-", "-=": + f.assembler.RegisterRegister(asm.SUB, destination, source) + + case "*", "*=": + f.assembler.RegisterRegister(asm.MUL, destination, source) + + case "/", "/=": + f.assembler.RegisterRegister(asm.DIV, destination, source) + + case "=": + if destination != source { + f.assembler.RegisterRegister(asm.MOVE, destination, source) + } + + default: + return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) + } + + return nil +} diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go new file mode 100644 index 0000000..1528e26 --- /dev/null +++ b/src/build/core/ExpressionToRegister.go @@ -0,0 +1,33 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" +) + +// ExpressionToRegister puts the result of an expression into the specified register. +func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { + if root.IsLeaf() { + return f.TokenToRegister(root.Token, register) + } + + if ast.IsFunctionCall(root) { + err := f.CompileCall(root) + f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Output[0]) + return err + } + + left := root.Children[0] + right := root.Children[1] + + err := f.ExpressionToRegister(left, register) + + if err != nil { + return err + } + + f.SaveRegister(register) + return f.Execute(root.Token, register, right) +} diff --git a/src/build/core/ExpressionsToRegisters.go b/src/build/core/ExpressionsToRegisters.go new file mode 100644 index 0000000..6e293e2 --- /dev/null +++ b/src/build/core/ExpressionsToRegisters.go @@ -0,0 +1,20 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" +) + +// ExpressionsToRegisters moves multiple expressions into the specified registers. +func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { + for i := len(registers) - 1; i >= 0; i-- { + f.SaveRegister(registers[i]) + err := f.ExpressionToRegister(expressions[i], registers[i]) + + if err != nil { + return err + } + } + + return nil +} diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 518affb..a80e7d1 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -31,10 +31,10 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { Instructions: make([]asm.Instruction, 0, 32), }, cpu: cpu.CPU{ - Call: x64.CallRegisters, + Input: x64.CallRegisters, General: x64.GeneralRegisters, Syscall: x64.SyscallRegisters, - Return: x64.ReturnValueRegisters, + Output: x64.ReturnValueRegisters, }, definitions: map[string]*Definition{}, variables: map[string]*Variable{}, diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index faf4036..bdce4d6 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -18,7 +18,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { constant, exists := f.definitions[name] if exists { - return f.EvaluateTo(constant.Value, register) + return f.ExpressionToRegister(constant.Value, register) } variable, exists := f.variables[name] diff --git a/src/build/core/opRegister.go b/src/build/core/opRegister.go deleted file mode 100644 index 2cdd81c..0000000 --- a/src/build/core/opRegister.go +++ /dev/null @@ -1,60 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" -) - -// opRegisterNumber performs an operation on a register and a number. -func (f *Function) opRegisterNumber(operation token.Token, register cpu.Register, number int) error { - switch operation.Text() { - case "+", "+=": - f.assembler.RegisterNumber(asm.ADD, register, number) - - case "-", "-=": - f.assembler.RegisterNumber(asm.SUB, register, number) - - case "*", "*=": - f.assembler.RegisterNumber(asm.MUL, register, number) - - case "/", "/=": - f.assembler.RegisterNumber(asm.DIV, register, number) - - case "=": - f.assembler.RegisterNumber(asm.MOVE, register, number) - - default: - return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) - } - - return nil -} - -// opRegisterRegister performs an operation on two registers. -func (f *Function) opRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { - switch operation.Text() { - case "+", "+=": - f.assembler.RegisterRegister(asm.ADD, destination, source) - - case "-", "-=": - f.assembler.RegisterRegister(asm.SUB, destination, source) - - case "*", "*=": - f.assembler.RegisterRegister(asm.MUL, destination, source) - - case "/", "/=": - f.assembler.RegisterRegister(asm.DIV, destination, source) - - case "=": - if destination != source { - f.assembler.RegisterRegister(asm.MOVE, destination, source) - } - - default: - return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) - } - - return nil -} diff --git a/src/build/core/state.go b/src/build/core/state.go index 70078c3..6a0d5e8 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -24,7 +24,6 @@ type state struct { // counter stores how often a certain statement appeared so we can generate a unique label from it. type counter struct { loop int - tmps int } // PrintInstructions shows the assembly instructions. diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go index 8f0f9f3..2015a38 100644 --- a/src/build/cpu/CPU.go +++ b/src/build/cpu/CPU.go @@ -2,10 +2,10 @@ package cpu // CPU represents the processor. type CPU struct { - Call []Register General []Register Syscall []Register - Return []Register + Input []Register + Output []Register usage uint64 } From 0a661b43451ce35ebeaac3035e957bb72e95b34c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 4 Jul 2024 12:32:56 +0200 Subject: [PATCH 0223/1012] Improved code generation --- examples/hello/hello.q | 6 ++++- src/build/core/CompileCall.go | 3 +++ src/build/core/Execute.go | 3 ++- src/build/core/ExpressionToRegister.go | 36 +++++++++++++++++++------- src/build/core/OverwritesRegister.go | 11 ++++++++ src/build/core/SaveRegister.go | 3 ++- src/build/cpu/CPU.go | 3 +-- tests/programs/exit.q | 11 ++++++++ tests/programs_test.go | 17 ++++++------ 9 files changed, 70 insertions(+), 23 deletions(-) create mode 100644 src/build/core/OverwritesRegister.go create mode 100644 tests/programs/exit.q diff --git a/examples/hello/hello.q b/examples/hello/hello.q index bffc930..95cb219 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,5 +1,9 @@ main() { - syscall(60, f(1) + f(2) + f(3)) + exit(f(1) + f(2) + f(3)) +} + +exit(code) { + syscall(60, code) } f(x) { diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 02943fa..e9fa102 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -25,18 +25,21 @@ func (f *Function) CompileCall(root *expression.Expression) error { return err } + // Push for _, register := range f.cpu.General { if !f.cpu.IsFree(register) { f.assembler.Register(asm.PUSH, register) } } + // Call if isSyscall { f.assembler.Syscall() } else { f.assembler.Call(funcName) } + // Pop for i := len(f.cpu.General) - 1; i >= 0; i-- { register := f.cpu.General[i] diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index c42c4d1..36d7271 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -23,7 +23,8 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteRegisterRegister(operation, register, f.cpu.Output[0]) } - tmp := f.cpu.MustUseFree(f.cpu.General) + tmp := f.cpu.MustFindFree(f.cpu.General) + f.cpu.Use(tmp) defer f.cpu.Free(tmp) err := f.ExpressionToRegister(value, tmp) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 1528e26..ef69a6f 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -8,19 +8,28 @@ import ( ) // ExpressionToRegister puts the result of an expression into the specified register. -func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { - if root.IsLeaf() { - return f.TokenToRegister(root.Token, register) +func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) error { + if node.IsLeaf() { + return f.TokenToRegister(node.Token, register) } - if ast.IsFunctionCall(root) { - err := f.CompileCall(root) - f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Output[0]) + if ast.IsFunctionCall(node) { + err := f.CompileCall(node) + + if register != f.cpu.Output[0] { + f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Output[0]) + } + return err } - left := root.Children[0] - right := root.Children[1] + left := node.Children[0] + right := node.Children[1] + final := register + + if OverwritesRegister(right, register) { + register = f.cpu.MustFindFree(f.cpu.General) + } err := f.ExpressionToRegister(left, register) @@ -28,6 +37,13 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp return err } - f.SaveRegister(register) - return f.Execute(root.Token, register, right) + f.cpu.Use(register) + err = f.Execute(node.Token, register, right) + + if register != final { + f.assembler.RegisterRegister(asm.MOVE, final, register) + } + + f.cpu.Free(register) + return err } diff --git a/src/build/core/OverwritesRegister.go b/src/build/core/OverwritesRegister.go new file mode 100644 index 0000000..ce950f8 --- /dev/null +++ b/src/build/core/OverwritesRegister.go @@ -0,0 +1,11 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" +) + +// OverwritesRegister returns true if evaluating the expression would overwrite the given register. +func OverwritesRegister(expr *expression.Expression, register cpu.Register) bool { + return !expr.IsLeaf() +} diff --git a/src/build/core/SaveRegister.go b/src/build/core/SaveRegister.go index 99d5375..7a23eb8 100644 --- a/src/build/core/SaveRegister.go +++ b/src/build/core/SaveRegister.go @@ -27,7 +27,8 @@ func (f *Function) SaveRegister(register cpu.Register) { return } - newRegister := f.cpu.MustUseFree(f.cpu.General) + newRegister := f.cpu.MustFindFree(f.cpu.General) + f.cpu.Use(newRegister) if config.Comments { f.assembler.Comment(fmt.Sprintf("save %s to %s", register, newRegister)) diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go index 2015a38..ee028db 100644 --- a/src/build/cpu/CPU.go +++ b/src/build/cpu/CPU.go @@ -31,13 +31,12 @@ func (c *CPU) FindFree(registers []Register) (Register, bool) { return 0, false } -func (c *CPU) MustUseFree(registers []Register) Register { +func (c *CPU) MustFindFree(registers []Register) Register { register, exists := c.FindFree(registers) if !exists { panic("no free registers") } - c.Use(register) return register } diff --git a/tests/programs/exit.q b/tests/programs/exit.q new file mode 100644 index 0000000..95cb219 --- /dev/null +++ b/tests/programs/exit.q @@ -0,0 +1,11 @@ +main() { + exit(f(1) + f(2) + f(3)) +} + +exit(code) { + syscall(60, code) +} + +f(x) { + return x + 1 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index a5ee862..9505870 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -18,6 +18,15 @@ var programs = []struct { {"empty.q", "", 0}, {"square-sum.q", "", 25}, {"multi-calls.q", "", 9}, + {"exit.q", "", 9}, +} + +func TestPrograms(t *testing.T) { + for _, test := range programs { + t.Run(test.Name, func(t *testing.T) { + run(t, filepath.Join("programs", test.Name), test.ExpectedOutput, test.ExpectedExitCode) + }) + } } func BenchmarkPrograms(b *testing.B) { @@ -33,14 +42,6 @@ func BenchmarkPrograms(b *testing.B) { } } -func TestPrograms(t *testing.T) { - for _, test := range programs { - t.Run(test.Name, func(t *testing.T) { - run(t, filepath.Join("programs", test.Name), test.ExpectedOutput, test.ExpectedExitCode) - }) - } -} - // run builds and runs the file to check if the output matches the expected output. func run(t *testing.T, name string, expectedOutput string, expectedExitCode int) { b := build.New(name) From 3340a5824f2be950a56f6afac0e7724facd89c37 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 4 Jul 2024 12:32:56 +0200 Subject: [PATCH 0224/1012] Improved code generation --- examples/hello/hello.q | 6 ++++- src/build/core/CompileCall.go | 3 +++ src/build/core/Execute.go | 3 ++- src/build/core/ExpressionToRegister.go | 36 +++++++++++++++++++------- src/build/core/OverwritesRegister.go | 11 ++++++++ src/build/core/SaveRegister.go | 3 ++- src/build/cpu/CPU.go | 3 +-- tests/programs/exit.q | 11 ++++++++ tests/programs_test.go | 17 ++++++------ 9 files changed, 70 insertions(+), 23 deletions(-) create mode 100644 src/build/core/OverwritesRegister.go create mode 100644 tests/programs/exit.q diff --git a/examples/hello/hello.q b/examples/hello/hello.q index bffc930..95cb219 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,5 +1,9 @@ main() { - syscall(60, f(1) + f(2) + f(3)) + exit(f(1) + f(2) + f(3)) +} + +exit(code) { + syscall(60, code) } f(x) { diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 02943fa..e9fa102 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -25,18 +25,21 @@ func (f *Function) CompileCall(root *expression.Expression) error { return err } + // Push for _, register := range f.cpu.General { if !f.cpu.IsFree(register) { f.assembler.Register(asm.PUSH, register) } } + // Call if isSyscall { f.assembler.Syscall() } else { f.assembler.Call(funcName) } + // Pop for i := len(f.cpu.General) - 1; i >= 0; i-- { register := f.cpu.General[i] diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index c42c4d1..36d7271 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -23,7 +23,8 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteRegisterRegister(operation, register, f.cpu.Output[0]) } - tmp := f.cpu.MustUseFree(f.cpu.General) + tmp := f.cpu.MustFindFree(f.cpu.General) + f.cpu.Use(tmp) defer f.cpu.Free(tmp) err := f.ExpressionToRegister(value, tmp) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 1528e26..ef69a6f 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -8,19 +8,28 @@ import ( ) // ExpressionToRegister puts the result of an expression into the specified register. -func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { - if root.IsLeaf() { - return f.TokenToRegister(root.Token, register) +func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) error { + if node.IsLeaf() { + return f.TokenToRegister(node.Token, register) } - if ast.IsFunctionCall(root) { - err := f.CompileCall(root) - f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Output[0]) + if ast.IsFunctionCall(node) { + err := f.CompileCall(node) + + if register != f.cpu.Output[0] { + f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Output[0]) + } + return err } - left := root.Children[0] - right := root.Children[1] + left := node.Children[0] + right := node.Children[1] + final := register + + if OverwritesRegister(right, register) { + register = f.cpu.MustFindFree(f.cpu.General) + } err := f.ExpressionToRegister(left, register) @@ -28,6 +37,13 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp return err } - f.SaveRegister(register) - return f.Execute(root.Token, register, right) + f.cpu.Use(register) + err = f.Execute(node.Token, register, right) + + if register != final { + f.assembler.RegisterRegister(asm.MOVE, final, register) + } + + f.cpu.Free(register) + return err } diff --git a/src/build/core/OverwritesRegister.go b/src/build/core/OverwritesRegister.go new file mode 100644 index 0000000..ce950f8 --- /dev/null +++ b/src/build/core/OverwritesRegister.go @@ -0,0 +1,11 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" +) + +// OverwritesRegister returns true if evaluating the expression would overwrite the given register. +func OverwritesRegister(expr *expression.Expression, register cpu.Register) bool { + return !expr.IsLeaf() +} diff --git a/src/build/core/SaveRegister.go b/src/build/core/SaveRegister.go index 99d5375..7a23eb8 100644 --- a/src/build/core/SaveRegister.go +++ b/src/build/core/SaveRegister.go @@ -27,7 +27,8 @@ func (f *Function) SaveRegister(register cpu.Register) { return } - newRegister := f.cpu.MustUseFree(f.cpu.General) + newRegister := f.cpu.MustFindFree(f.cpu.General) + f.cpu.Use(newRegister) if config.Comments { f.assembler.Comment(fmt.Sprintf("save %s to %s", register, newRegister)) diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go index 2015a38..ee028db 100644 --- a/src/build/cpu/CPU.go +++ b/src/build/cpu/CPU.go @@ -31,13 +31,12 @@ func (c *CPU) FindFree(registers []Register) (Register, bool) { return 0, false } -func (c *CPU) MustUseFree(registers []Register) Register { +func (c *CPU) MustFindFree(registers []Register) Register { register, exists := c.FindFree(registers) if !exists { panic("no free registers") } - c.Use(register) return register } diff --git a/tests/programs/exit.q b/tests/programs/exit.q new file mode 100644 index 0000000..95cb219 --- /dev/null +++ b/tests/programs/exit.q @@ -0,0 +1,11 @@ +main() { + exit(f(1) + f(2) + f(3)) +} + +exit(code) { + syscall(60, code) +} + +f(x) { + return x + 1 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index a5ee862..9505870 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -18,6 +18,15 @@ var programs = []struct { {"empty.q", "", 0}, {"square-sum.q", "", 25}, {"multi-calls.q", "", 9}, + {"exit.q", "", 9}, +} + +func TestPrograms(t *testing.T) { + for _, test := range programs { + t.Run(test.Name, func(t *testing.T) { + run(t, filepath.Join("programs", test.Name), test.ExpectedOutput, test.ExpectedExitCode) + }) + } } func BenchmarkPrograms(b *testing.B) { @@ -33,14 +42,6 @@ func BenchmarkPrograms(b *testing.B) { } } -func TestPrograms(t *testing.T) { - for _, test := range programs { - t.Run(test.Name, func(t *testing.T) { - run(t, filepath.Join("programs", test.Name), test.ExpectedOutput, test.ExpectedExitCode) - }) - } -} - // run builds and runs the file to check if the output matches the expected output. func run(t *testing.T, name string, expectedOutput string, expectedExitCode int) { b := build.New(name) From ec2001fe1ee16e449846fe8953129c922f89331a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 5 Jul 2024 09:50:46 +0200 Subject: [PATCH 0225/1012] Improved assignment error message --- src/build/ast/Parse.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index 69e6756..674f121 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -61,7 +61,7 @@ func toASTNode(tokens token.List) (Node, error) { switch { case IsVariableDefinition(expr): if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingAssignValue, nil, expr.LastChild().Token.End()) + return nil, errors.New(errors.MissingAssignValue, nil, expr.Token.End()) } name := expr.Children[0].Token @@ -70,7 +70,7 @@ func toASTNode(tokens token.List) (Node, error) { case IsAssignment(expr): if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingAssignValue, nil, expr.LastChild().Token.End()) + return nil, errors.New(errors.MissingAssignValue, nil, expr.Token.End()) } name := expr.Children[0].Token From 62057704f3a48d3399c023b28d51bdf972153e2c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 5 Jul 2024 09:50:46 +0200 Subject: [PATCH 0226/1012] Improved assignment error message --- src/build/ast/Parse.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index 69e6756..674f121 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -61,7 +61,7 @@ func toASTNode(tokens token.List) (Node, error) { switch { case IsVariableDefinition(expr): if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingAssignValue, nil, expr.LastChild().Token.End()) + return nil, errors.New(errors.MissingAssignValue, nil, expr.Token.End()) } name := expr.Children[0].Token @@ -70,7 +70,7 @@ func toASTNode(tokens token.List) (Node, error) { case IsAssignment(expr): if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingAssignValue, nil, expr.LastChild().Token.End()) + return nil, errors.New(errors.MissingAssignValue, nil, expr.Token.End()) } name := expr.Children[0].Token From 78ddfa6dbcfc4e5b64d7a5f5ca6c2bcc3ed27a05 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 5 Jul 2024 10:10:09 +0200 Subject: [PATCH 0227/1012] Added operator precedence test --- tests/programs/precedence.q | 5 +++++ tests/programs/{exit.q => register-overlap.q} | 0 tests/programs_test.go | 5 +++-- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 tests/programs/precedence.q rename tests/programs/{exit.q => register-overlap.q} (100%) diff --git a/tests/programs/precedence.q b/tests/programs/precedence.q new file mode 100644 index 0000000..bfb4fd2 --- /dev/null +++ b/tests/programs/precedence.q @@ -0,0 +1,5 @@ +main() { + x := 2 + y := 3 + syscall(60, (x + y + (x + y) * (x + y)) / x / y + x + y) +} \ No newline at end of file diff --git a/tests/programs/exit.q b/tests/programs/register-overlap.q similarity index 100% rename from tests/programs/exit.q rename to tests/programs/register-overlap.q diff --git a/tests/programs_test.go b/tests/programs_test.go index 9505870..2a785e8 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -16,9 +16,10 @@ var programs = []struct { ExpectedExitCode int }{ {"empty.q", "", 0}, - {"square-sum.q", "", 25}, {"multi-calls.q", "", 9}, - {"exit.q", "", 9}, + {"precedence.q", "", 10}, + {"register-overlap.q", "", 9}, + {"square-sum.q", "", 25}, } func TestPrograms(t *testing.T) { From c13a70c95828c60d78eff7ae711ec7304515a1f2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 5 Jul 2024 10:10:09 +0200 Subject: [PATCH 0228/1012] Added operator precedence test --- tests/programs/precedence.q | 5 +++++ tests/programs/{exit.q => register-overlap.q} | 0 tests/programs_test.go | 5 +++-- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 tests/programs/precedence.q rename tests/programs/{exit.q => register-overlap.q} (100%) diff --git a/tests/programs/precedence.q b/tests/programs/precedence.q new file mode 100644 index 0000000..bfb4fd2 --- /dev/null +++ b/tests/programs/precedence.q @@ -0,0 +1,5 @@ +main() { + x := 2 + y := 3 + syscall(60, (x + y + (x + y) * (x + y)) / x / y + x + y) +} \ No newline at end of file diff --git a/tests/programs/exit.q b/tests/programs/register-overlap.q similarity index 100% rename from tests/programs/exit.q rename to tests/programs/register-overlap.q diff --git a/tests/programs_test.go b/tests/programs_test.go index 9505870..2a785e8 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -16,9 +16,10 @@ var programs = []struct { ExpectedExitCode int }{ {"empty.q", "", 0}, - {"square-sum.q", "", 25}, {"multi-calls.q", "", 9}, - {"exit.q", "", 9}, + {"precedence.q", "", 10}, + {"register-overlap.q", "", 9}, + {"square-sum.q", "", 25}, } func TestPrograms(t *testing.T) { From 881cb0a81c2206cab267e23621c10ea36e8bf149 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 5 Jul 2024 11:56:57 +0200 Subject: [PATCH 0229/1012] Added nested calls test --- .../programs/{register-overlap.q => chained-calls.q} | 0 tests/programs/multi-calls.q | 7 ------- tests/programs/nested-calls.q | 11 +++++++++++ tests/programs_test.go | 4 ++-- 4 files changed, 13 insertions(+), 9 deletions(-) rename tests/programs/{register-overlap.q => chained-calls.q} (100%) delete mode 100644 tests/programs/multi-calls.q create mode 100644 tests/programs/nested-calls.q diff --git a/tests/programs/register-overlap.q b/tests/programs/chained-calls.q similarity index 100% rename from tests/programs/register-overlap.q rename to tests/programs/chained-calls.q diff --git a/tests/programs/multi-calls.q b/tests/programs/multi-calls.q deleted file mode 100644 index bffc930..0000000 --- a/tests/programs/multi-calls.q +++ /dev/null @@ -1,7 +0,0 @@ -main() { - syscall(60, f(1) + f(2) + f(3)) -} - -f(x) { - return x + 1 -} \ No newline at end of file diff --git a/tests/programs/nested-calls.q b/tests/programs/nested-calls.q new file mode 100644 index 0000000..8844729 --- /dev/null +++ b/tests/programs/nested-calls.q @@ -0,0 +1,11 @@ +main() { + exit(f(f(f(1)))) +} + +exit(code) { + syscall(60, code) +} + +f(x) { + return x + 1 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 2a785e8..cd70f45 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -16,10 +16,10 @@ var programs = []struct { ExpectedExitCode int }{ {"empty.q", "", 0}, - {"multi-calls.q", "", 9}, {"precedence.q", "", 10}, - {"register-overlap.q", "", 9}, {"square-sum.q", "", 25}, + {"chained-calls.q", "", 9}, + {"nested-calls.q", "", 4}, } func TestPrograms(t *testing.T) { From 099ee729d440389da69fcc823b0244267d5cd4ef Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 5 Jul 2024 11:56:57 +0200 Subject: [PATCH 0230/1012] Added nested calls test --- .../programs/{register-overlap.q => chained-calls.q} | 0 tests/programs/multi-calls.q | 7 ------- tests/programs/nested-calls.q | 11 +++++++++++ tests/programs_test.go | 4 ++-- 4 files changed, 13 insertions(+), 9 deletions(-) rename tests/programs/{register-overlap.q => chained-calls.q} (100%) delete mode 100644 tests/programs/multi-calls.q create mode 100644 tests/programs/nested-calls.q diff --git a/tests/programs/register-overlap.q b/tests/programs/chained-calls.q similarity index 100% rename from tests/programs/register-overlap.q rename to tests/programs/chained-calls.q diff --git a/tests/programs/multi-calls.q b/tests/programs/multi-calls.q deleted file mode 100644 index bffc930..0000000 --- a/tests/programs/multi-calls.q +++ /dev/null @@ -1,7 +0,0 @@ -main() { - syscall(60, f(1) + f(2) + f(3)) -} - -f(x) { - return x + 1 -} \ No newline at end of file diff --git a/tests/programs/nested-calls.q b/tests/programs/nested-calls.q new file mode 100644 index 0000000..8844729 --- /dev/null +++ b/tests/programs/nested-calls.q @@ -0,0 +1,11 @@ +main() { + exit(f(f(f(1)))) +} + +exit(code) { + syscall(60, code) +} + +f(x) { + return x + 1 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 2a785e8..cd70f45 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -16,10 +16,10 @@ var programs = []struct { ExpectedExitCode int }{ {"empty.q", "", 0}, - {"multi-calls.q", "", 9}, {"precedence.q", "", 10}, - {"register-overlap.q", "", 9}, {"square-sum.q", "", 25}, + {"chained-calls.q", "", 9}, + {"nested-calls.q", "", 4}, } func TestPrograms(t *testing.T) { From a80f971e8a6407c0e9692bd4da23ce6b7cf22a34 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 5 Jul 2024 15:51:19 +0200 Subject: [PATCH 0231/1012] Improved code generation --- src/build/core/ExpressionToRegister.go | 2 +- src/build/core/OverwritesRegister.go | 11 ------ src/build/core/UsesRegister.go | 47 ++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 12 deletions(-) delete mode 100644 src/build/core/OverwritesRegister.go create mode 100644 src/build/core/UsesRegister.go diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index ef69a6f..fade8b8 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -27,7 +27,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp right := node.Children[1] final := register - if OverwritesRegister(right, register) { + if f.UsesRegister(right, register) { register = f.cpu.MustFindFree(f.cpu.General) } diff --git a/src/build/core/OverwritesRegister.go b/src/build/core/OverwritesRegister.go deleted file mode 100644 index ce950f8..0000000 --- a/src/build/core/OverwritesRegister.go +++ /dev/null @@ -1,11 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" -) - -// OverwritesRegister returns true if evaluating the expression would overwrite the given register. -func OverwritesRegister(expr *expression.Expression, register cpu.Register) bool { - return !expr.IsLeaf() -} diff --git a/src/build/core/UsesRegister.go b/src/build/core/UsesRegister.go new file mode 100644 index 0000000..5090f63 --- /dev/null +++ b/src/build/core/UsesRegister.go @@ -0,0 +1,47 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// UsesRegister returns true if evaluating the expression would write or read the given register. +func (f *Function) UsesRegister(expr *expression.Expression, register cpu.Register) bool { + if expr.IsLeaf() { + if expr.Token.Kind == token.Number { + return false + } + + name := expr.Token.Text() + variable := f.variables[name] + return register == variable.Register + } + + if ast.IsFunctionCall(expr) { + if register == f.cpu.Output[0] { + return true + } + + for i, parameter := range expr.Children[1:] { + if register == f.cpu.Input[i] { + return true + } + + if f.UsesRegister(parameter, register) { + return true + } + } + + return false + } + + for _, child := range expr.Children { + if f.UsesRegister(child, register) { + return true + } + } + + return false +} From 3e92c83c0d76b0c0a2d5805c6adf701521a5df37 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 5 Jul 2024 15:51:19 +0200 Subject: [PATCH 0232/1012] Improved code generation --- src/build/core/ExpressionToRegister.go | 2 +- src/build/core/OverwritesRegister.go | 11 ------ src/build/core/UsesRegister.go | 47 ++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 12 deletions(-) delete mode 100644 src/build/core/OverwritesRegister.go create mode 100644 src/build/core/UsesRegister.go diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index ef69a6f..fade8b8 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -27,7 +27,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp right := node.Children[1] final := register - if OverwritesRegister(right, register) { + if f.UsesRegister(right, register) { register = f.cpu.MustFindFree(f.cpu.General) } diff --git a/src/build/core/OverwritesRegister.go b/src/build/core/OverwritesRegister.go deleted file mode 100644 index ce950f8..0000000 --- a/src/build/core/OverwritesRegister.go +++ /dev/null @@ -1,11 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" -) - -// OverwritesRegister returns true if evaluating the expression would overwrite the given register. -func OverwritesRegister(expr *expression.Expression, register cpu.Register) bool { - return !expr.IsLeaf() -} diff --git a/src/build/core/UsesRegister.go b/src/build/core/UsesRegister.go new file mode 100644 index 0000000..5090f63 --- /dev/null +++ b/src/build/core/UsesRegister.go @@ -0,0 +1,47 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// UsesRegister returns true if evaluating the expression would write or read the given register. +func (f *Function) UsesRegister(expr *expression.Expression, register cpu.Register) bool { + if expr.IsLeaf() { + if expr.Token.Kind == token.Number { + return false + } + + name := expr.Token.Text() + variable := f.variables[name] + return register == variable.Register + } + + if ast.IsFunctionCall(expr) { + if register == f.cpu.Output[0] { + return true + } + + for i, parameter := range expr.Children[1:] { + if register == f.cpu.Input[i] { + return true + } + + if f.UsesRegister(parameter, register) { + return true + } + } + + return false + } + + for _, child := range expr.Children { + if f.UsesRegister(child, register) { + return true + } + } + + return false +} From 28fc6d2af6c22446d3ebc7b62577c53a883af7d2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 5 Jul 2024 16:31:33 +0200 Subject: [PATCH 0233/1012] Improved code generation --- src/build/core/ExpressionsToRegisters.go | 1 - src/build/core/SaveRegister.go | 40 ------------------------ tests/programs/return.q | 11 +++++++ tests/programs_test.go | 1 + 4 files changed, 12 insertions(+), 41 deletions(-) delete mode 100644 src/build/core/SaveRegister.go create mode 100644 tests/programs/return.q diff --git a/src/build/core/ExpressionsToRegisters.go b/src/build/core/ExpressionsToRegisters.go index 6e293e2..64d6816 100644 --- a/src/build/core/ExpressionsToRegisters.go +++ b/src/build/core/ExpressionsToRegisters.go @@ -8,7 +8,6 @@ import ( // ExpressionsToRegisters moves multiple expressions into the specified registers. func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { for i := len(registers) - 1; i >= 0; i-- { - f.SaveRegister(registers[i]) err := f.ExpressionToRegister(expressions[i], registers[i]) if err != nil { diff --git a/src/build/core/SaveRegister.go b/src/build/core/SaveRegister.go deleted file mode 100644 index 7a23eb8..0000000 --- a/src/build/core/SaveRegister.go +++ /dev/null @@ -1,40 +0,0 @@ -package core - -import ( - "fmt" - - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/cpu" -) - -// SaveRegister attempts to move a variable occupying this register to another register. -func (f *Function) SaveRegister(register cpu.Register) { - if f.cpu.IsFree(register) { - return - } - - var variable *Variable - - for _, v := range f.variables { - if v.Register == register { - variable = v - break - } - } - - if variable == nil || variable.Alive == 0 { - return - } - - newRegister := f.cpu.MustFindFree(f.cpu.General) - f.cpu.Use(newRegister) - - if config.Comments { - f.assembler.Comment(fmt.Sprintf("save %s to %s", register, newRegister)) - } - - f.assembler.RegisterRegister(asm.MOVE, newRegister, register) - f.cpu.Free(register) - variable.Register = newRegister -} diff --git a/tests/programs/return.q b/tests/programs/return.q new file mode 100644 index 0000000..13f1e6a --- /dev/null +++ b/tests/programs/return.q @@ -0,0 +1,11 @@ +main() { + syscall(60, f(2)) +} + +f(x) { + return x + 1 + g(x) +} + +g(x) { + return x + 1 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index cd70f45..91c483d 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -20,6 +20,7 @@ var programs = []struct { {"square-sum.q", "", 25}, {"chained-calls.q", "", 9}, {"nested-calls.q", "", 4}, + {"return.q", "", 6}, } func TestPrograms(t *testing.T) { From d771708693b6c94017ed57ad40e7d5318978eea4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 5 Jul 2024 16:31:33 +0200 Subject: [PATCH 0234/1012] Improved code generation --- src/build/core/ExpressionsToRegisters.go | 1 - src/build/core/SaveRegister.go | 40 ------------------------ tests/programs/return.q | 11 +++++++ tests/programs_test.go | 1 + 4 files changed, 12 insertions(+), 41 deletions(-) delete mode 100644 src/build/core/SaveRegister.go create mode 100644 tests/programs/return.q diff --git a/src/build/core/ExpressionsToRegisters.go b/src/build/core/ExpressionsToRegisters.go index 6e293e2..64d6816 100644 --- a/src/build/core/ExpressionsToRegisters.go +++ b/src/build/core/ExpressionsToRegisters.go @@ -8,7 +8,6 @@ import ( // ExpressionsToRegisters moves multiple expressions into the specified registers. func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { for i := len(registers) - 1; i >= 0; i-- { - f.SaveRegister(registers[i]) err := f.ExpressionToRegister(expressions[i], registers[i]) if err != nil { diff --git a/src/build/core/SaveRegister.go b/src/build/core/SaveRegister.go deleted file mode 100644 index 7a23eb8..0000000 --- a/src/build/core/SaveRegister.go +++ /dev/null @@ -1,40 +0,0 @@ -package core - -import ( - "fmt" - - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/cpu" -) - -// SaveRegister attempts to move a variable occupying this register to another register. -func (f *Function) SaveRegister(register cpu.Register) { - if f.cpu.IsFree(register) { - return - } - - var variable *Variable - - for _, v := range f.variables { - if v.Register == register { - variable = v - break - } - } - - if variable == nil || variable.Alive == 0 { - return - } - - newRegister := f.cpu.MustFindFree(f.cpu.General) - f.cpu.Use(newRegister) - - if config.Comments { - f.assembler.Comment(fmt.Sprintf("save %s to %s", register, newRegister)) - } - - f.assembler.RegisterRegister(asm.MOVE, newRegister, register) - f.cpu.Free(register) - variable.Register = newRegister -} diff --git a/tests/programs/return.q b/tests/programs/return.q new file mode 100644 index 0000000..13f1e6a --- /dev/null +++ b/tests/programs/return.q @@ -0,0 +1,11 @@ +main() { + syscall(60, f(2)) +} + +f(x) { + return x + 1 + g(x) +} + +g(x) { + return x + 1 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index cd70f45..91c483d 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -20,6 +20,7 @@ var programs = []struct { {"square-sum.q", "", 25}, {"chained-calls.q", "", 9}, {"nested-calls.q", "", 4}, + {"return.q", "", 6}, } func TestPrograms(t *testing.T) { From 6db1f1af69d145b4638a6f8bd2fa026a426aff9c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 5 Jul 2024 17:11:30 +0200 Subject: [PATCH 0235/1012] Added more tests --- src/build/ast/Parse.go | 4 ++-- src/build/core/ExpressionToRegister.go | 5 +++++ src/build/errors/CompileErrors.go | 2 +- tests/errors/InvalidInstructionExpression.q | 3 +++ tests/errors/{MissingAssignValue.q => MissingOperand.q} | 0 tests/errors/MissingOperand2.q | 3 +++ tests/errors_test.go | 4 +++- 7 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 tests/errors/InvalidInstructionExpression.q rename tests/errors/{MissingAssignValue.q => MissingOperand.q} (100%) create mode 100644 tests/errors/MissingOperand2.q diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index 674f121..760302e 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -61,7 +61,7 @@ func toASTNode(tokens token.List) (Node, error) { switch { case IsVariableDefinition(expr): if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingAssignValue, nil, expr.Token.End()) + return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) } name := expr.Children[0].Token @@ -70,7 +70,7 @@ func toASTNode(tokens token.List) (Node, error) { case IsAssignment(expr): if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingAssignValue, nil, expr.Token.End()) + return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) } name := expr.Children[0].Token diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index fade8b8..2f6cf6d 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -4,6 +4,7 @@ import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" ) @@ -23,6 +24,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return err } + if len(node.Children) < 2 { + return errors.New(errors.MissingOperand, f.File, node.Token.End()) + } + left := node.Children[0] right := node.Children[1] final := register diff --git a/src/build/errors/CompileErrors.go b/src/build/errors/CompileErrors.go index 13d5948..886d1f8 100644 --- a/src/build/errors/CompileErrors.go +++ b/src/build/errors/CompileErrors.go @@ -3,7 +3,7 @@ package errors var ( InvalidStatement = &Base{"Invalid statement"} InvalidExpression = &Base{"Invalid expression"} - MissingAssignValue = &Base{"Missing assignment value"} + MissingOperand = &Base{"Missing operand"} MissingMainFunction = &Base{"Missing main function"} NotImplemented = &Base{"Not implemented"} ) diff --git a/tests/errors/InvalidInstructionExpression.q b/tests/errors/InvalidInstructionExpression.q new file mode 100644 index 0000000..c90293f --- /dev/null +++ b/tests/errors/InvalidInstructionExpression.q @@ -0,0 +1,3 @@ +main() { + 2+3 +} \ No newline at end of file diff --git a/tests/errors/MissingAssignValue.q b/tests/errors/MissingOperand.q similarity index 100% rename from tests/errors/MissingAssignValue.q rename to tests/errors/MissingOperand.q diff --git a/tests/errors/MissingOperand2.q b/tests/errors/MissingOperand2.q new file mode 100644 index 0000000..9a030f4 --- /dev/null +++ b/tests/errors/MissingOperand2.q @@ -0,0 +1,3 @@ +main() { + syscall(2+) +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 6299be8..c3d2a11 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -19,17 +19,19 @@ var errs = []struct { {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, + {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}}, {"InvalidExpression.q", errors.InvalidExpression}, {"InvalidOperator.q", &errors.InvalidOperator{Operator: "+++"}}, {"InvalidCharacter.q", &errors.InvalidCharacter{Character: "@"}}, {"InvalidCharacter2.q", &errors.InvalidCharacter{Character: "@"}}, {"InvalidCharacter3.q", &errors.InvalidCharacter{Character: "@"}}, - {"MissingAssignValue.q", errors.MissingAssignValue}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, {"MissingGroupStart.q", errors.MissingGroupStart}, {"MissingMainFunction.q", errors.MissingMainFunction}, + {"MissingOperand.q", errors.MissingOperand}, + {"MissingOperand2.q", errors.MissingOperand}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, From cc66b02bf8cd1eb28a912c96065d65a9a57ee34a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 5 Jul 2024 17:11:30 +0200 Subject: [PATCH 0236/1012] Added more tests --- src/build/ast/Parse.go | 4 ++-- src/build/core/ExpressionToRegister.go | 5 +++++ src/build/errors/CompileErrors.go | 2 +- tests/errors/InvalidInstructionExpression.q | 3 +++ tests/errors/{MissingAssignValue.q => MissingOperand.q} | 0 tests/errors/MissingOperand2.q | 3 +++ tests/errors_test.go | 4 +++- 7 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 tests/errors/InvalidInstructionExpression.q rename tests/errors/{MissingAssignValue.q => MissingOperand.q} (100%) create mode 100644 tests/errors/MissingOperand2.q diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index 674f121..760302e 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -61,7 +61,7 @@ func toASTNode(tokens token.List) (Node, error) { switch { case IsVariableDefinition(expr): if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingAssignValue, nil, expr.Token.End()) + return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) } name := expr.Children[0].Token @@ -70,7 +70,7 @@ func toASTNode(tokens token.List) (Node, error) { case IsAssignment(expr): if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingAssignValue, nil, expr.Token.End()) + return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) } name := expr.Children[0].Token diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index fade8b8..2f6cf6d 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -4,6 +4,7 @@ import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" ) @@ -23,6 +24,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return err } + if len(node.Children) < 2 { + return errors.New(errors.MissingOperand, f.File, node.Token.End()) + } + left := node.Children[0] right := node.Children[1] final := register diff --git a/src/build/errors/CompileErrors.go b/src/build/errors/CompileErrors.go index 13d5948..886d1f8 100644 --- a/src/build/errors/CompileErrors.go +++ b/src/build/errors/CompileErrors.go @@ -3,7 +3,7 @@ package errors var ( InvalidStatement = &Base{"Invalid statement"} InvalidExpression = &Base{"Invalid expression"} - MissingAssignValue = &Base{"Missing assignment value"} + MissingOperand = &Base{"Missing operand"} MissingMainFunction = &Base{"Missing main function"} NotImplemented = &Base{"Not implemented"} ) diff --git a/tests/errors/InvalidInstructionExpression.q b/tests/errors/InvalidInstructionExpression.q new file mode 100644 index 0000000..c90293f --- /dev/null +++ b/tests/errors/InvalidInstructionExpression.q @@ -0,0 +1,3 @@ +main() { + 2+3 +} \ No newline at end of file diff --git a/tests/errors/MissingAssignValue.q b/tests/errors/MissingOperand.q similarity index 100% rename from tests/errors/MissingAssignValue.q rename to tests/errors/MissingOperand.q diff --git a/tests/errors/MissingOperand2.q b/tests/errors/MissingOperand2.q new file mode 100644 index 0000000..9a030f4 --- /dev/null +++ b/tests/errors/MissingOperand2.q @@ -0,0 +1,3 @@ +main() { + syscall(2+) +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 6299be8..c3d2a11 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -19,17 +19,19 @@ var errs = []struct { {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, + {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}}, {"InvalidExpression.q", errors.InvalidExpression}, {"InvalidOperator.q", &errors.InvalidOperator{Operator: "+++"}}, {"InvalidCharacter.q", &errors.InvalidCharacter{Character: "@"}}, {"InvalidCharacter2.q", &errors.InvalidCharacter{Character: "@"}}, {"InvalidCharacter3.q", &errors.InvalidCharacter{Character: "@"}}, - {"MissingAssignValue.q", errors.MissingAssignValue}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, {"MissingGroupStart.q", errors.MissingGroupStart}, {"MissingMainFunction.q", errors.MissingMainFunction}, + {"MissingOperand.q", errors.MissingOperand}, + {"MissingOperand2.q", errors.MissingOperand}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, From fca944d26c099ef8a45288f7b924b06418ae4ff8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 6 Jul 2024 13:06:39 +0200 Subject: [PATCH 0237/1012] Added more tests --- src/build/core/CompileCall.go | 10 ---------- src/build/core/state.go | 1 - src/cli/Main_test.go | 2 ++ tests/programs/math.q | 12 ++++++++++++ tests/programs_test.go | 1 + 5 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 tests/programs/math.q diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index e9fa102..079cf97 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -50,13 +50,3 @@ func (f *Function) CompileCall(root *expression.Expression) error { return nil } - -// CompileSyscall executes a syscall. -func (f *Function) CompileSyscall(expr *expression.Expression) error { - parameters := expr.Children[1:] - registers := f.cpu.Syscall[:len(parameters)] - err := f.ExpressionsToRegisters(parameters, registers) - f.assembler.Syscall() - f.sideEffects++ - return err -} diff --git a/src/build/core/state.go b/src/build/core/state.go index 6a0d5e8..2bca805 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -18,7 +18,6 @@ type state struct { assembler asm.Assembler cpu cpu.CPU count counter - sideEffects int } // counter stores how often a certain statement appeared so we can generate a unique label from it. diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index 63b5366..6471939 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -16,6 +16,7 @@ func TestCLI(t *testing.T) { tests := []cliTest{ {[]string{}, 2}, {[]string{"invalid"}, 2}, + {[]string{"help"}, 0}, {[]string{"system"}, 0}, {[]string{"build", "invalid-directory"}, 1}, {[]string{"build", "--invalid-parameter"}, 2}, @@ -23,6 +24,7 @@ func TestCLI(t *testing.T) { {[]string{"build", "../../examples/hello", "--dry"}, 0}, {[]string{"build", "../../examples/hello", "--dry", "--verbose"}, 0}, {[]string{"build", "../../examples/hello/hello.q", "--dry"}, 0}, + {[]string{"build", "../../examples/hello"}, 0}, } for _, test := range tests { diff --git a/tests/programs/math.q b/tests/programs/math.q new file mode 100644 index 0000000..b3bb8ee --- /dev/null +++ b/tests/programs/math.q @@ -0,0 +1,12 @@ +main() { + x := 1000 + syscall(60, div10(x) / 10 + div(x, 100) * 4 - 40 - x + x) +} + +div(x, y) { + return x / y +} + +div10(x) { + return x / 10 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 91c483d..bcd947a 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -16,6 +16,7 @@ var programs = []struct { ExpectedExitCode int }{ {"empty.q", "", 0}, + {"math.q", "", 10}, {"precedence.q", "", 10}, {"square-sum.q", "", 25}, {"chained-calls.q", "", 9}, From 8f9481c5488c801a7f7ae646259a1f2aafbf9294 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 6 Jul 2024 13:06:39 +0200 Subject: [PATCH 0238/1012] Added more tests --- src/build/core/CompileCall.go | 10 ---------- src/build/core/state.go | 1 - src/cli/Main_test.go | 2 ++ tests/programs/math.q | 12 ++++++++++++ tests/programs_test.go | 1 + 5 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 tests/programs/math.q diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index e9fa102..079cf97 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -50,13 +50,3 @@ func (f *Function) CompileCall(root *expression.Expression) error { return nil } - -// CompileSyscall executes a syscall. -func (f *Function) CompileSyscall(expr *expression.Expression) error { - parameters := expr.Children[1:] - registers := f.cpu.Syscall[:len(parameters)] - err := f.ExpressionsToRegisters(parameters, registers) - f.assembler.Syscall() - f.sideEffects++ - return err -} diff --git a/src/build/core/state.go b/src/build/core/state.go index 6a0d5e8..2bca805 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -18,7 +18,6 @@ type state struct { assembler asm.Assembler cpu cpu.CPU count counter - sideEffects int } // counter stores how often a certain statement appeared so we can generate a unique label from it. diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index 63b5366..6471939 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -16,6 +16,7 @@ func TestCLI(t *testing.T) { tests := []cliTest{ {[]string{}, 2}, {[]string{"invalid"}, 2}, + {[]string{"help"}, 0}, {[]string{"system"}, 0}, {[]string{"build", "invalid-directory"}, 1}, {[]string{"build", "--invalid-parameter"}, 2}, @@ -23,6 +24,7 @@ func TestCLI(t *testing.T) { {[]string{"build", "../../examples/hello", "--dry"}, 0}, {[]string{"build", "../../examples/hello", "--dry", "--verbose"}, 0}, {[]string{"build", "../../examples/hello/hello.q", "--dry"}, 0}, + {[]string{"build", "../../examples/hello"}, 0}, } for _, test := range tests { diff --git a/tests/programs/math.q b/tests/programs/math.q new file mode 100644 index 0000000..b3bb8ee --- /dev/null +++ b/tests/programs/math.q @@ -0,0 +1,12 @@ +main() { + x := 1000 + syscall(60, div10(x) / 10 + div(x, 100) * 4 - 40 - x + x) +} + +div(x, y) { + return x / y +} + +div10(x) { + return x / 10 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 91c483d..bcd947a 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -16,6 +16,7 @@ var programs = []struct { ExpectedExitCode int }{ {"empty.q", "", 0}, + {"math.q", "", 10}, {"precedence.q", "", 10}, {"square-sum.q", "", 25}, {"chained-calls.q", "", 9}, From b886637dc37945dc38e5b1d18abae5e69f58c7fd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 6 Jul 2024 15:20:52 +0200 Subject: [PATCH 0239/1012] Removed incorrect optimization --- src/build/core/CompileDefinition.go | 15 --------------- src/build/core/Function.go | 5 ++--- src/build/core/TokenToRegister.go | 6 ------ src/build/core/Variable.go | 6 ------ src/build/core/state.go | 15 +++++++-------- tests/programs/reassign.q | 6 ++++++ tests/programs_test.go | 1 + 7 files changed, 16 insertions(+), 38 deletions(-) create mode 100644 tests/programs/reassign.q diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 13b361f..d9daf58 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -38,15 +38,6 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return err } - if uses == 1 { - f.definitions[name] = &Definition{ - Name: name, - Value: value, - } - - return nil - } - return f.storeVariableInRegister(name, value, uses) } @@ -83,12 +74,6 @@ func (f *Function) identifierExists(name string) bool { return true } - _, exists = f.definitions[name] - - if exists { - return true - } - _, exists = f.functions[name] return exists } diff --git a/src/build/core/Function.go b/src/build/core/Function.go index a80e7d1..be45db3 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -36,9 +36,8 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { Syscall: x64.SyscallRegisters, Output: x64.ReturnValueRegisters, }, - definitions: map[string]*Definition{}, - variables: map[string]*Variable{}, - finished: make(chan struct{}), + variables: map[string]*Variable{}, + finished: make(chan struct{}), }, } } diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index bdce4d6..7a9c428 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -15,12 +15,6 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { switch t.Kind { case token.Identifier: name := t.Text() - constant, exists := f.definitions[name] - - if exists { - return f.ExpressionToRegister(constant.Value, register) - } - variable, exists := f.variables[name] if !exists { diff --git a/src/build/core/Variable.go b/src/build/core/Variable.go index 8667b35..4557945 100644 --- a/src/build/core/Variable.go +++ b/src/build/core/Variable.go @@ -12,9 +12,3 @@ type Variable struct { Register cpu.Register Alive int } - -// Definitions are single use expressions that don't reside in a register yet. -type Definition struct { - Value *expression.Expression - Name string -} diff --git a/src/build/core/state.go b/src/build/core/state.go index 2bca805..31313d0 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -10,14 +10,13 @@ import ( // state is the data structure we embed in each function to preserve compilation state. type state struct { - err error - definitions map[string]*Definition - variables map[string]*Variable - functions map[string]*Function - finished chan struct{} - assembler asm.Assembler - cpu cpu.CPU - count counter + err error + variables map[string]*Variable + functions map[string]*Function + finished chan struct{} + assembler asm.Assembler + cpu cpu.CPU + count counter } // counter stores how often a certain statement appeared so we can generate a unique label from it. diff --git a/tests/programs/reassign.q b/tests/programs/reassign.q new file mode 100644 index 0000000..df940d8 --- /dev/null +++ b/tests/programs/reassign.q @@ -0,0 +1,6 @@ +main() { + x := 1 + y := x + 1 + x = 2 + syscall(60, y) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index bcd947a..feba797 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -22,6 +22,7 @@ var programs = []struct { {"chained-calls.q", "", 9}, {"nested-calls.q", "", 4}, {"return.q", "", 6}, + {"reassign.q", "", 2}, } func TestPrograms(t *testing.T) { From a9f305dec21ae4df745c22e79f2f9e7543762311 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 6 Jul 2024 15:20:52 +0200 Subject: [PATCH 0240/1012] Removed incorrect optimization --- src/build/core/CompileDefinition.go | 15 --------------- src/build/core/Function.go | 5 ++--- src/build/core/TokenToRegister.go | 6 ------ src/build/core/Variable.go | 6 ------ src/build/core/state.go | 15 +++++++-------- tests/programs/reassign.q | 6 ++++++ tests/programs_test.go | 1 + 7 files changed, 16 insertions(+), 38 deletions(-) create mode 100644 tests/programs/reassign.q diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 13b361f..d9daf58 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -38,15 +38,6 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return err } - if uses == 1 { - f.definitions[name] = &Definition{ - Name: name, - Value: value, - } - - return nil - } - return f.storeVariableInRegister(name, value, uses) } @@ -83,12 +74,6 @@ func (f *Function) identifierExists(name string) bool { return true } - _, exists = f.definitions[name] - - if exists { - return true - } - _, exists = f.functions[name] return exists } diff --git a/src/build/core/Function.go b/src/build/core/Function.go index a80e7d1..be45db3 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -36,9 +36,8 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { Syscall: x64.SyscallRegisters, Output: x64.ReturnValueRegisters, }, - definitions: map[string]*Definition{}, - variables: map[string]*Variable{}, - finished: make(chan struct{}), + variables: map[string]*Variable{}, + finished: make(chan struct{}), }, } } diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index bdce4d6..7a9c428 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -15,12 +15,6 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { switch t.Kind { case token.Identifier: name := t.Text() - constant, exists := f.definitions[name] - - if exists { - return f.ExpressionToRegister(constant.Value, register) - } - variable, exists := f.variables[name] if !exists { diff --git a/src/build/core/Variable.go b/src/build/core/Variable.go index 8667b35..4557945 100644 --- a/src/build/core/Variable.go +++ b/src/build/core/Variable.go @@ -12,9 +12,3 @@ type Variable struct { Register cpu.Register Alive int } - -// Definitions are single use expressions that don't reside in a register yet. -type Definition struct { - Value *expression.Expression - Name string -} diff --git a/src/build/core/state.go b/src/build/core/state.go index 2bca805..31313d0 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -10,14 +10,13 @@ import ( // state is the data structure we embed in each function to preserve compilation state. type state struct { - err error - definitions map[string]*Definition - variables map[string]*Variable - functions map[string]*Function - finished chan struct{} - assembler asm.Assembler - cpu cpu.CPU - count counter + err error + variables map[string]*Variable + functions map[string]*Function + finished chan struct{} + assembler asm.Assembler + cpu cpu.CPU + count counter } // counter stores how often a certain statement appeared so we can generate a unique label from it. diff --git a/tests/programs/reassign.q b/tests/programs/reassign.q new file mode 100644 index 0000000..df940d8 --- /dev/null +++ b/tests/programs/reassign.q @@ -0,0 +1,6 @@ +main() { + x := 1 + y := x + 1 + x = 2 + syscall(60, y) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index bcd947a..feba797 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -22,6 +22,7 @@ var programs = []struct { {"chained-calls.q", "", 9}, {"nested-calls.q", "", 4}, {"return.q", "", 6}, + {"reassign.q", "", 2}, } func TestPrograms(t *testing.T) { From 0b4b3343c460260d5a57a40652894e43abd0be0c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 6 Jul 2024 17:01:52 +0200 Subject: [PATCH 0241/1012] Added more tests --- tests/errors/UnknownIdentifier3.q | 3 +++ tests/errors/UnknownIdentifier4.q | 7 +++++++ tests/errors_test.go | 2 ++ 3 files changed, 12 insertions(+) create mode 100644 tests/errors/UnknownIdentifier3.q create mode 100644 tests/errors/UnknownIdentifier4.q diff --git a/tests/errors/UnknownIdentifier3.q b/tests/errors/UnknownIdentifier3.q new file mode 100644 index 0000000..c31b354 --- /dev/null +++ b/tests/errors/UnknownIdentifier3.q @@ -0,0 +1,3 @@ +main() { + x := 1 + f(x) +} \ No newline at end of file diff --git a/tests/errors/UnknownIdentifier4.q b/tests/errors/UnknownIdentifier4.q new file mode 100644 index 0000000..a7387e2 --- /dev/null +++ b/tests/errors/UnknownIdentifier4.q @@ -0,0 +1,7 @@ +main() { + x := 1 + f(x) +} + +f(x) { + return x +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index c3d2a11..d1123bb 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -35,6 +35,8 @@ var errs = []struct { {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, + {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "f"}}, + {"UnknownIdentifier4.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, } From 230bb3670962b97b8c4719d8d030825a5460c0b6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 6 Jul 2024 17:01:52 +0200 Subject: [PATCH 0242/1012] Added more tests --- tests/errors/UnknownIdentifier3.q | 3 +++ tests/errors/UnknownIdentifier4.q | 7 +++++++ tests/errors_test.go | 2 ++ 3 files changed, 12 insertions(+) create mode 100644 tests/errors/UnknownIdentifier3.q create mode 100644 tests/errors/UnknownIdentifier4.q diff --git a/tests/errors/UnknownIdentifier3.q b/tests/errors/UnknownIdentifier3.q new file mode 100644 index 0000000..c31b354 --- /dev/null +++ b/tests/errors/UnknownIdentifier3.q @@ -0,0 +1,3 @@ +main() { + x := 1 + f(x) +} \ No newline at end of file diff --git a/tests/errors/UnknownIdentifier4.q b/tests/errors/UnknownIdentifier4.q new file mode 100644 index 0000000..a7387e2 --- /dev/null +++ b/tests/errors/UnknownIdentifier4.q @@ -0,0 +1,7 @@ +main() { + x := 1 + f(x) +} + +f(x) { + return x +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index c3d2a11..d1123bb 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -35,6 +35,8 @@ var errs = []struct { {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, + {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "f"}}, + {"UnknownIdentifier4.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, } From c139dced9e40ae4a7a29670f8764f202fbe5c1af Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 6 Jul 2024 19:40:20 +0200 Subject: [PATCH 0243/1012] Implemented run command --- README.md | 5 ++- src/build/errors/UnknownCLIParameter.go | 13 +++++++ src/cli/Build.go | 44 ++++++++++++++--------- src/cli/Help.go | 1 + src/cli/Main.go | 3 ++ src/cli/Main_test.go | 2 ++ src/cli/Run.go | 46 +++++++++++++++++++++++++ 7 files changed, 95 insertions(+), 19 deletions(-) create mode 100644 src/build/errors/UnknownCLIParameter.go create mode 100644 src/cli/Run.go diff --git a/README.md b/README.md index 866b982..e4b8a45 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,10 @@ go build ## Usage -Build a Linux x86-64 ELF executable from `examples/hello`: +Build a Linux x86-64 ELF executable from `examples/hello` and run it: ```shell -./q build examples/hello -./examples/hello/hello +./q run examples/hello ``` ## Documentation diff --git a/src/build/errors/UnknownCLIParameter.go b/src/build/errors/UnknownCLIParameter.go new file mode 100644 index 0000000..136864e --- /dev/null +++ b/src/build/errors/UnknownCLIParameter.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// UnknownCLIParameter error is created when a command line parameter is not recognized. +type UnknownCLIParameter struct { + Parameter string +} + +// Error generates the string representation. +func (err *UnknownCLIParameter) Error() string { + return fmt.Sprintf("Unknown parameter '%s'", err.Parameter) +} diff --git a/src/cli/Build.go b/src/cli/Build.go index ac4066c..6b74ce6 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -7,10 +7,30 @@ import ( "git.akyoto.dev/cli/q/src/build" "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/errors" ) // Build parses the arguments and creates a build. func Build(args []string) int { + _, err := buildWithArgs(args) + + if err != nil { + fmt.Fprintln(os.Stderr, err) + + switch err.(type) { + case *errors.UnknownCLIParameter: + return 2 + + default: + return 1 + } + } + + return 0 +} + +// buildWithArgs creates a new build with the given arguments. +func buildWithArgs(args []string) (*build.Build, error) { b := build.New() for i := 0; i < len(args); i++ { @@ -26,8 +46,7 @@ func Build(args []string) int { config.Comments = true default: if strings.HasPrefix(args[i], "-") { - fmt.Printf("Unknown parameter: %s\n", args[i]) - return 2 + return b, &errors.UnknownCLIParameter{Parameter: args[i]} } b.Files = append(b.Files, args[i]) @@ -38,16 +57,16 @@ func Build(args []string) int { b.Files = append(b.Files, ".") } - return run(b) + err := makeExecutable(b) + return b, err } -// run starts the build by running the compiler and then writing the result to disk. -func run(b *build.Build) int { +// makeExecutable starts the build by running the compiler and then writing the result to disk. +func makeExecutable(b *build.Build) error { result, err := b.Run() if err != nil { - fmt.Fprintln(os.Stderr, err) - return 1 + return err } if config.Assembler { @@ -55,15 +74,8 @@ func run(b *build.Build) int { } if config.Dry { - return 0 + return nil } - err = result.Write(b.Executable()) - - if err != nil { - fmt.Fprintln(os.Stderr, err) - return 1 - } - - return 0 + return result.Write(b.Executable()) } diff --git a/src/cli/Help.go b/src/cli/Help.go index b6c8f58..8d4f0ef 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -12,6 +12,7 @@ func Help(w io.Writer, code int) int { commands: build [directory | file] + run [directory | file] help system diff --git a/src/cli/Main.go b/src/cli/Main.go index 2b90bda..31b387b 100644 --- a/src/cli/Main.go +++ b/src/cli/Main.go @@ -14,6 +14,9 @@ func Main(args []string) int { case "build": return Build(args[1:]) + case "run": + return Run(args[1:]) + case "system": return System(args[1:]) diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index 6471939..4d40adb 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -25,6 +25,8 @@ func TestCLI(t *testing.T) { {[]string{"build", "../../examples/hello", "--dry", "--verbose"}, 0}, {[]string{"build", "../../examples/hello/hello.q", "--dry"}, 0}, {[]string{"build", "../../examples/hello"}, 0}, + {[]string{"run", "../../examples/hello", "--invalid"}, 2}, + {[]string{"run", "../../examples/hello"}, 0}, } for _, test := range tests { diff --git a/src/cli/Run.go b/src/cli/Run.go new file mode 100644 index 0000000..9133f36 --- /dev/null +++ b/src/cli/Run.go @@ -0,0 +1,46 @@ +package cli + +import ( + "fmt" + "os" + "os/exec" + + "git.akyoto.dev/cli/q/src/build/errors" +) + +// Run builds and runs the executable. +func Run(args []string) int { + b, err := buildWithArgs(args) + + if err != nil { + fmt.Fprintln(os.Stderr, err) + + switch err.(type) { + case *errors.UnknownCLIParameter: + return 2 + + default: + return 1 + } + } + + cmd := exec.Command(b.Executable()) + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + cmd.Stderr = os.Stderr + err = cmd.Run() + + if err != nil { + fmt.Fprintln(os.Stderr, err) + + switch err.(type) { + case *exec.ExitError: + return 0 + + default: + return 1 + } + } + + return 0 +} From 6fc234c700531a565effc072793b94b3fa3e0a2a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 6 Jul 2024 19:40:20 +0200 Subject: [PATCH 0244/1012] Implemented run command --- README.md | 5 ++- src/build/errors/UnknownCLIParameter.go | 13 +++++++ src/cli/Build.go | 44 ++++++++++++++--------- src/cli/Help.go | 1 + src/cli/Main.go | 3 ++ src/cli/Main_test.go | 2 ++ src/cli/Run.go | 46 +++++++++++++++++++++++++ 7 files changed, 95 insertions(+), 19 deletions(-) create mode 100644 src/build/errors/UnknownCLIParameter.go create mode 100644 src/cli/Run.go diff --git a/README.md b/README.md index 866b982..e4b8a45 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,10 @@ go build ## Usage -Build a Linux x86-64 ELF executable from `examples/hello`: +Build a Linux x86-64 ELF executable from `examples/hello` and run it: ```shell -./q build examples/hello -./examples/hello/hello +./q run examples/hello ``` ## Documentation diff --git a/src/build/errors/UnknownCLIParameter.go b/src/build/errors/UnknownCLIParameter.go new file mode 100644 index 0000000..136864e --- /dev/null +++ b/src/build/errors/UnknownCLIParameter.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// UnknownCLIParameter error is created when a command line parameter is not recognized. +type UnknownCLIParameter struct { + Parameter string +} + +// Error generates the string representation. +func (err *UnknownCLIParameter) Error() string { + return fmt.Sprintf("Unknown parameter '%s'", err.Parameter) +} diff --git a/src/cli/Build.go b/src/cli/Build.go index ac4066c..6b74ce6 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -7,10 +7,30 @@ import ( "git.akyoto.dev/cli/q/src/build" "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/errors" ) // Build parses the arguments and creates a build. func Build(args []string) int { + _, err := buildWithArgs(args) + + if err != nil { + fmt.Fprintln(os.Stderr, err) + + switch err.(type) { + case *errors.UnknownCLIParameter: + return 2 + + default: + return 1 + } + } + + return 0 +} + +// buildWithArgs creates a new build with the given arguments. +func buildWithArgs(args []string) (*build.Build, error) { b := build.New() for i := 0; i < len(args); i++ { @@ -26,8 +46,7 @@ func Build(args []string) int { config.Comments = true default: if strings.HasPrefix(args[i], "-") { - fmt.Printf("Unknown parameter: %s\n", args[i]) - return 2 + return b, &errors.UnknownCLIParameter{Parameter: args[i]} } b.Files = append(b.Files, args[i]) @@ -38,16 +57,16 @@ func Build(args []string) int { b.Files = append(b.Files, ".") } - return run(b) + err := makeExecutable(b) + return b, err } -// run starts the build by running the compiler and then writing the result to disk. -func run(b *build.Build) int { +// makeExecutable starts the build by running the compiler and then writing the result to disk. +func makeExecutable(b *build.Build) error { result, err := b.Run() if err != nil { - fmt.Fprintln(os.Stderr, err) - return 1 + return err } if config.Assembler { @@ -55,15 +74,8 @@ func run(b *build.Build) int { } if config.Dry { - return 0 + return nil } - err = result.Write(b.Executable()) - - if err != nil { - fmt.Fprintln(os.Stderr, err) - return 1 - } - - return 0 + return result.Write(b.Executable()) } diff --git a/src/cli/Help.go b/src/cli/Help.go index b6c8f58..8d4f0ef 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -12,6 +12,7 @@ func Help(w io.Writer, code int) int { commands: build [directory | file] + run [directory | file] help system diff --git a/src/cli/Main.go b/src/cli/Main.go index 2b90bda..31b387b 100644 --- a/src/cli/Main.go +++ b/src/cli/Main.go @@ -14,6 +14,9 @@ func Main(args []string) int { case "build": return Build(args[1:]) + case "run": + return Run(args[1:]) + case "system": return System(args[1:]) diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index 6471939..4d40adb 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -25,6 +25,8 @@ func TestCLI(t *testing.T) { {[]string{"build", "../../examples/hello", "--dry", "--verbose"}, 0}, {[]string{"build", "../../examples/hello/hello.q", "--dry"}, 0}, {[]string{"build", "../../examples/hello"}, 0}, + {[]string{"run", "../../examples/hello", "--invalid"}, 2}, + {[]string{"run", "../../examples/hello"}, 0}, } for _, test := range tests { diff --git a/src/cli/Run.go b/src/cli/Run.go new file mode 100644 index 0000000..9133f36 --- /dev/null +++ b/src/cli/Run.go @@ -0,0 +1,46 @@ +package cli + +import ( + "fmt" + "os" + "os/exec" + + "git.akyoto.dev/cli/q/src/build/errors" +) + +// Run builds and runs the executable. +func Run(args []string) int { + b, err := buildWithArgs(args) + + if err != nil { + fmt.Fprintln(os.Stderr, err) + + switch err.(type) { + case *errors.UnknownCLIParameter: + return 2 + + default: + return 1 + } + } + + cmd := exec.Command(b.Executable()) + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + cmd.Stderr = os.Stderr + err = cmd.Run() + + if err != nil { + fmt.Fprintln(os.Stderr, err) + + switch err.(type) { + case *exec.ExitError: + return 0 + + default: + return 1 + } + } + + return 0 +} From 962a362578718609f393696fe91039f1679b4a4b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 7 Jul 2024 12:30:57 +0200 Subject: [PATCH 0245/1012] Implemented if statements --- examples/hello/hello.q | 8 ++- src/build/arch/x64/Compare.go | 13 +++++ src/build/arch/x64/Compare_test.go | 88 ++++++++++++++++++++++++++++++ src/build/arch/x64/Jump.go | 36 +++++++++++- src/build/arch/x64/Registers.go | 6 +- src/build/asm/Assembler.go | 28 +++++++++- src/build/asm/Instructions.go | 6 +- src/build/asm/Mnemonic.go | 21 +++++++ src/build/ast/AST.go | 9 +++ src/build/ast/Parse.go | 16 ++++++ src/build/core/CompileIf.go | 48 ++++++++++++++++ src/build/core/CompileLoop.go | 3 +- src/build/core/Function.go | 5 +- src/build/core/state.go | 3 +- src/build/keyword/Keyword.go | 2 + tests/examples_test.go | 2 +- 16 files changed, 280 insertions(+), 14 deletions(-) create mode 100644 src/build/arch/x64/Compare.go create mode 100644 src/build/arch/x64/Compare_test.go create mode 100644 src/build/core/CompileIf.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 95cb219..53d906b 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,5 +1,11 @@ main() { - exit(f(1) + f(2) + f(3)) + x := f(1) + f(2) + f(3) + + if x != 9 { + exit(42) + } + + exit(0) } exit(code) { diff --git a/src/build/arch/x64/Compare.go b/src/build/arch/x64/Compare.go new file mode 100644 index 0000000..5e49f7b --- /dev/null +++ b/src/build/arch/x64/Compare.go @@ -0,0 +1,13 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// Compares the register with the number and sets the status flags in the EFLAGS register. +func CompareRegisterNumber(code []byte, register cpu.Register, number int) []byte { + return regRegNum(code, 0b111, byte(register), number, 0x83, 0x81) +} + +// CompareRegisterRegister compares a register with a register and sets the status flags in the EFLAGS register. +func CompareRegisterRegister(code []byte, registerA cpu.Register, registerB cpu.Register) []byte { + return regReg(code, byte(registerB), byte(registerA), 0x39) +} diff --git a/src/build/arch/x64/Compare_test.go b/src/build/arch/x64/Compare_test.go new file mode 100644 index 0000000..cef49b3 --- /dev/null +++ b/src/build/arch/x64/Compare_test.go @@ -0,0 +1,88 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestCompareRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0x83, 0xF8, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0x83, 0xF9, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0x83, 0xFA, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0x83, 0xFB, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0x83, 0xFC, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0x83, 0xFD, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0x83, 0xFE, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0x83, 0xFF, 0x01}}, + {x64.R8, 1, []byte{0x49, 0x83, 0xF8, 0x01}}, + {x64.R9, 1, []byte{0x49, 0x83, 0xF9, 0x01}}, + {x64.R10, 1, []byte{0x49, 0x83, 0xFA, 0x01}}, + {x64.R11, 1, []byte{0x49, 0x83, 0xFB, 0x01}}, + {x64.R12, 1, []byte{0x49, 0x83, 0xFC, 0x01}}, + {x64.R13, 1, []byte{0x49, 0x83, 0xFD, 0x01}}, + {x64.R14, 1, []byte{0x49, 0x83, 0xFE, 0x01}}, + {x64.R15, 1, []byte{0x49, 0x83, 0xFF, 0x01}}, + + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("cmp %s, %x", pattern.Register, pattern.Number) + code := x64.CompareRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestCompareRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x4C, 0x39, 0xF8}}, + {x64.RCX, x64.R14, []byte{0x4C, 0x39, 0xF1}}, + {x64.RDX, x64.R13, []byte{0x4C, 0x39, 0xEA}}, + {x64.RBX, x64.R12, []byte{0x4C, 0x39, 0xE3}}, + {x64.RSP, x64.R11, []byte{0x4C, 0x39, 0xDC}}, + {x64.RBP, x64.R10, []byte{0x4C, 0x39, 0xD5}}, + {x64.RSI, x64.R9, []byte{0x4C, 0x39, 0xCE}}, + {x64.RDI, x64.R8, []byte{0x4C, 0x39, 0xC7}}, + {x64.R8, x64.RDI, []byte{0x49, 0x39, 0xF8}}, + {x64.R9, x64.RSI, []byte{0x49, 0x39, 0xF1}}, + {x64.R10, x64.RBP, []byte{0x49, 0x39, 0xEA}}, + {x64.R11, x64.RSP, []byte{0x49, 0x39, 0xE3}}, + {x64.R12, x64.RBX, []byte{0x49, 0x39, 0xDC}}, + {x64.R13, x64.RDX, []byte{0x49, 0x39, 0xD5}}, + {x64.R14, x64.RCX, []byte{0x49, 0x39, 0xCE}}, + {x64.R15, x64.RAX, []byte{0x49, 0x39, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("cmp %s, %s", pattern.Left, pattern.Right) + code := x64.CompareRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Jump.go b/src/build/arch/x64/Jump.go index 5b3f125..86d2c48 100644 --- a/src/build/arch/x64/Jump.go +++ b/src/build/arch/x64/Jump.go @@ -3,9 +3,43 @@ package x64 // Jump continues program flow at the new address. // The address is relative to the next instruction. func Jump8(code []byte, address int8) []byte { + return jump8(code, 0xEB, address) +} + +// JumpIfLess jumps if the result was less. +func Jump8IfLess(code []byte, address int8) []byte { + return jump8(code, 0x7C, address) +} + +// JumpIfLessOrEqual jumps if the result was less or equal. +func Jump8IfLessOrEqual(code []byte, address int8) []byte { + return jump8(code, 0x7E, address) +} + +// JumpIfGreater jumps if the result was greater. +func Jump8IfGreater(code []byte, address int8) []byte { + return jump8(code, 0x7F, address) +} + +// JumpIfGreaterOrEqual jumps if the result was greater or equal. +func Jump8IfGreaterOrEqual(code []byte, address int8) []byte { + return jump8(code, 0x7D, address) +} + +// JumpIfEqual jumps if the result was equal. +func Jump8IfEqual(code []byte, address int8) []byte { + return jump8(code, 0x74, address) +} + +// JumpIfNotEqual jumps if the result was not equal. +func Jump8IfNotEqual(code []byte, address int8) []byte { + return jump8(code, 0x75, address) +} + +func jump8(code []byte, opCode byte, address int8) []byte { return append( code, - 0xEB, + opCode, byte(address), ) } diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go index fafea37..6968338 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/build/arch/x64/Registers.go @@ -22,8 +22,8 @@ const ( ) var ( - CallRegisters = SyscallRegisters - GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} - ReturnValueRegisters = []cpu.Register{RAX, RCX, R11} + GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} + CallRegisters = SyscallRegisters + ReturnValueRegisters = SyscallRegisters ) diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 9a9f19e..caf600a 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -66,8 +66,32 @@ func (a Assembler) Finalize() ([]byte, []byte) { case COMMENT: continue - case JUMP: - code = x64.Jump8(code, 0x00) + case COMPARE: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.CompareRegisterNumber(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source) + } + + case JUMP, JE, JNE, JG, JL, JGE, JLE: + switch x.Mnemonic { + case JUMP: + code = x64.Jump8(code, 0x00) + case JE: + code = x64.Jump8IfEqual(code, 0x00) + case JNE: + code = x64.Jump8IfNotEqual(code, 0x00) + case JG: + code = x64.Jump8IfGreater(code, 0x00) + case JL: + code = x64.Jump8IfLess(code, 0x00) + case JGE: + code = x64.Jump8IfGreaterOrEqual(code, 0x00) + case JLE: + code = x64.Jump8IfLessOrEqual(code, 0x00) + } + size := 1 label := x.Data.(*Label) nextInstructionAddress := Address(len(code)) diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 6b547a2..1adcd9d 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -34,10 +34,10 @@ func (a *Assembler) Register(mnemonic Mnemonic, register cpu.Register) { }) } -// Label adds a label at the current position. -func (a *Assembler) Label(name string) { +// Label adds an instruction using a label. +func (a *Assembler) Label(mnemonic Mnemonic, name string) { a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: LABEL, + Mnemonic: mnemonic, Data: &Label{ Name: name, }, diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 315af19..06bd87a 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -7,7 +7,14 @@ const ( ADD CALL COMMENT + COMPARE DIV + JE + JNE + JG + JGE + JL + JLE JUMP MUL LABEL @@ -28,10 +35,24 @@ func (m Mnemonic) String() string { return "call" case COMMENT: return "comment" + case COMPARE: + return "compare" case DIV: return "div" case JUMP: return "jump" + case JE: + return "jump ==" + case JNE: + return "jump !=" + case JL: + return "jump <" + case JG: + return "jump >" + case JLE: + return "jump <=" + case JGE: + return "jump >=" case LABEL: return "label" case MOVE: diff --git a/src/build/ast/AST.go b/src/build/ast/AST.go index d1b991b..7def683 100644 --- a/src/build/ast/AST.go +++ b/src/build/ast/AST.go @@ -37,6 +37,15 @@ func (node *Define) String() string { return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) } +type If struct { + Condition *expression.Expression + Body AST +} + +func (node *If) String() string { + return fmt.Sprintf("(if %s %s)", node.Condition, node.Body) +} + type Loop struct { Body AST } diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index 760302e..4f00224 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -47,6 +47,22 @@ func toASTNode(tokens token.List) (Node, error) { tree, err := Parse(tokens[blockStart:blockEnd]) return &Loop{Body: tree}, err + case keyword.If: + blockStart := tokens.IndexKind(token.BlockStart) + 1 + blockEnd := tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) + } + + if blockEnd == -1 { + return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) + } + + condition := expression.Parse(tokens[1:token.BlockStart]) + tree, err := Parse(tokens[blockStart:blockEnd]) + return &If{Condition: condition, Body: tree}, err + default: return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, nil, tokens[0].Position) } diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go new file mode 100644 index 0000000..2f058e3 --- /dev/null +++ b/src/build/core/CompileIf.go @@ -0,0 +1,48 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" +) + +// CompileIf compiles a branch instruction. +func (f *Function) CompileIf(branch *ast.If) error { + condition := branch.Condition + tmpRight := f.cpu.Input[1] + err := f.ExpressionToRegister(condition.Children[1], tmpRight) + + if err != nil { + return err + } + + tmpLeft := f.cpu.Input[0] + err = f.ExpressionToRegister(condition.Children[0], tmpLeft) + + if err != nil { + return err + } + + f.assembler.RegisterRegister(asm.COMPARE, tmpLeft, tmpRight) + elseLabel := fmt.Sprintf("%s_if_%d_else", f.Name, f.count.branch) + + switch condition.Token.Text() { + case "==": + f.assembler.Label(asm.JNE, elseLabel) + case "!=": + f.assembler.Label(asm.JE, elseLabel) + case ">": + f.assembler.Label(asm.JLE, elseLabel) + case "<": + f.assembler.Label(asm.JGE, elseLabel) + case ">=": + f.assembler.Label(asm.JL, elseLabel) + case "<=": + f.assembler.Label(asm.JG, elseLabel) + } + + defer f.assembler.Label(asm.LABEL, elseLabel) + f.count.branch++ + return f.CompileAST(branch.Body) +} diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index 37a3c94..b8fd1d8 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -3,13 +3,14 @@ package core import ( "fmt" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" ) // CompileLoop compiles a loop instruction. func (f *Function) CompileLoop(loop *ast.Loop) error { label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) - f.assembler.Label(label) + f.assembler.Label(asm.LABEL, label) defer f.assembler.Jump(label) f.count.loop++ return f.CompileAST(loop.Body) diff --git a/src/build/core/Function.go b/src/build/core/Function.go index be45db3..e19cf0f 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -45,7 +45,7 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { // Compile turns a function into machine code. func (f *Function) Compile() { defer close(f.finished) - f.assembler.Label(f.Name) + f.assembler.Label(asm.LABEL, f.Name) f.err = f.CompileTokens(f.Body) f.assembler.Return() } @@ -90,6 +90,9 @@ func (f *Function) CompileASTNode(node ast.Node) error { case *ast.Return: return f.CompileReturn(node) + case *ast.If: + return f.CompileIf(node) + case *ast.Loop: return f.CompileLoop(node) diff --git a/src/build/core/state.go b/src/build/core/state.go index 31313d0..8ae6838 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -21,7 +21,8 @@ type state struct { // counter stores how often a certain statement appeared so we can generate a unique label from it. type counter struct { - loop int + loop int + branch int } // PrintInstructions shows the assembly instructions. diff --git a/src/build/keyword/Keyword.go b/src/build/keyword/Keyword.go index d3d9b18..d744384 100644 --- a/src/build/keyword/Keyword.go +++ b/src/build/keyword/Keyword.go @@ -1,12 +1,14 @@ package keyword const ( + If = "if" Loop = "loop" Return = "return" ) // Map is a map of all keywords used in the language. var Map = map[string][]byte{ + If: []byte(If), Loop: []byte(Loop), Return: []byte(Return), } diff --git a/tests/examples_test.go b/tests/examples_test.go index ab00191..be7590d 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -10,7 +10,7 @@ var examples = []struct { ExpectedOutput string ExpectedExitCode int }{ - {"hello", "", 9}, + {"hello", "", 0}, {"write", "ELF", 0}, } From 91e300e49a384a921712b73a597f88c1eb084068 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 7 Jul 2024 12:30:57 +0200 Subject: [PATCH 0246/1012] Implemented if statements --- examples/hello/hello.q | 8 ++- src/build/arch/x64/Compare.go | 13 +++++ src/build/arch/x64/Compare_test.go | 88 ++++++++++++++++++++++++++++++ src/build/arch/x64/Jump.go | 36 +++++++++++- src/build/arch/x64/Registers.go | 6 +- src/build/asm/Assembler.go | 28 +++++++++- src/build/asm/Instructions.go | 6 +- src/build/asm/Mnemonic.go | 21 +++++++ src/build/ast/AST.go | 9 +++ src/build/ast/Parse.go | 16 ++++++ src/build/core/CompileIf.go | 48 ++++++++++++++++ src/build/core/CompileLoop.go | 3 +- src/build/core/Function.go | 5 +- src/build/core/state.go | 3 +- src/build/keyword/Keyword.go | 2 + tests/examples_test.go | 2 +- 16 files changed, 280 insertions(+), 14 deletions(-) create mode 100644 src/build/arch/x64/Compare.go create mode 100644 src/build/arch/x64/Compare_test.go create mode 100644 src/build/core/CompileIf.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 95cb219..53d906b 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,5 +1,11 @@ main() { - exit(f(1) + f(2) + f(3)) + x := f(1) + f(2) + f(3) + + if x != 9 { + exit(42) + } + + exit(0) } exit(code) { diff --git a/src/build/arch/x64/Compare.go b/src/build/arch/x64/Compare.go new file mode 100644 index 0000000..5e49f7b --- /dev/null +++ b/src/build/arch/x64/Compare.go @@ -0,0 +1,13 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// Compares the register with the number and sets the status flags in the EFLAGS register. +func CompareRegisterNumber(code []byte, register cpu.Register, number int) []byte { + return regRegNum(code, 0b111, byte(register), number, 0x83, 0x81) +} + +// CompareRegisterRegister compares a register with a register and sets the status flags in the EFLAGS register. +func CompareRegisterRegister(code []byte, registerA cpu.Register, registerB cpu.Register) []byte { + return regReg(code, byte(registerB), byte(registerA), 0x39) +} diff --git a/src/build/arch/x64/Compare_test.go b/src/build/arch/x64/Compare_test.go new file mode 100644 index 0000000..cef49b3 --- /dev/null +++ b/src/build/arch/x64/Compare_test.go @@ -0,0 +1,88 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestCompareRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0x83, 0xF8, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0x83, 0xF9, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0x83, 0xFA, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0x83, 0xFB, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0x83, 0xFC, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0x83, 0xFD, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0x83, 0xFE, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0x83, 0xFF, 0x01}}, + {x64.R8, 1, []byte{0x49, 0x83, 0xF8, 0x01}}, + {x64.R9, 1, []byte{0x49, 0x83, 0xF9, 0x01}}, + {x64.R10, 1, []byte{0x49, 0x83, 0xFA, 0x01}}, + {x64.R11, 1, []byte{0x49, 0x83, 0xFB, 0x01}}, + {x64.R12, 1, []byte{0x49, 0x83, 0xFC, 0x01}}, + {x64.R13, 1, []byte{0x49, 0x83, 0xFD, 0x01}}, + {x64.R14, 1, []byte{0x49, 0x83, 0xFE, 0x01}}, + {x64.R15, 1, []byte{0x49, 0x83, 0xFF, 0x01}}, + + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("cmp %s, %x", pattern.Register, pattern.Number) + code := x64.CompareRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestCompareRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x4C, 0x39, 0xF8}}, + {x64.RCX, x64.R14, []byte{0x4C, 0x39, 0xF1}}, + {x64.RDX, x64.R13, []byte{0x4C, 0x39, 0xEA}}, + {x64.RBX, x64.R12, []byte{0x4C, 0x39, 0xE3}}, + {x64.RSP, x64.R11, []byte{0x4C, 0x39, 0xDC}}, + {x64.RBP, x64.R10, []byte{0x4C, 0x39, 0xD5}}, + {x64.RSI, x64.R9, []byte{0x4C, 0x39, 0xCE}}, + {x64.RDI, x64.R8, []byte{0x4C, 0x39, 0xC7}}, + {x64.R8, x64.RDI, []byte{0x49, 0x39, 0xF8}}, + {x64.R9, x64.RSI, []byte{0x49, 0x39, 0xF1}}, + {x64.R10, x64.RBP, []byte{0x49, 0x39, 0xEA}}, + {x64.R11, x64.RSP, []byte{0x49, 0x39, 0xE3}}, + {x64.R12, x64.RBX, []byte{0x49, 0x39, 0xDC}}, + {x64.R13, x64.RDX, []byte{0x49, 0x39, 0xD5}}, + {x64.R14, x64.RCX, []byte{0x49, 0x39, 0xCE}}, + {x64.R15, x64.RAX, []byte{0x49, 0x39, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("cmp %s, %s", pattern.Left, pattern.Right) + code := x64.CompareRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Jump.go b/src/build/arch/x64/Jump.go index 5b3f125..86d2c48 100644 --- a/src/build/arch/x64/Jump.go +++ b/src/build/arch/x64/Jump.go @@ -3,9 +3,43 @@ package x64 // Jump continues program flow at the new address. // The address is relative to the next instruction. func Jump8(code []byte, address int8) []byte { + return jump8(code, 0xEB, address) +} + +// JumpIfLess jumps if the result was less. +func Jump8IfLess(code []byte, address int8) []byte { + return jump8(code, 0x7C, address) +} + +// JumpIfLessOrEqual jumps if the result was less or equal. +func Jump8IfLessOrEqual(code []byte, address int8) []byte { + return jump8(code, 0x7E, address) +} + +// JumpIfGreater jumps if the result was greater. +func Jump8IfGreater(code []byte, address int8) []byte { + return jump8(code, 0x7F, address) +} + +// JumpIfGreaterOrEqual jumps if the result was greater or equal. +func Jump8IfGreaterOrEqual(code []byte, address int8) []byte { + return jump8(code, 0x7D, address) +} + +// JumpIfEqual jumps if the result was equal. +func Jump8IfEqual(code []byte, address int8) []byte { + return jump8(code, 0x74, address) +} + +// JumpIfNotEqual jumps if the result was not equal. +func Jump8IfNotEqual(code []byte, address int8) []byte { + return jump8(code, 0x75, address) +} + +func jump8(code []byte, opCode byte, address int8) []byte { return append( code, - 0xEB, + opCode, byte(address), ) } diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go index fafea37..6968338 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/build/arch/x64/Registers.go @@ -22,8 +22,8 @@ const ( ) var ( - CallRegisters = SyscallRegisters - GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} - ReturnValueRegisters = []cpu.Register{RAX, RCX, R11} + GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} + CallRegisters = SyscallRegisters + ReturnValueRegisters = SyscallRegisters ) diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 9a9f19e..caf600a 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -66,8 +66,32 @@ func (a Assembler) Finalize() ([]byte, []byte) { case COMMENT: continue - case JUMP: - code = x64.Jump8(code, 0x00) + case COMPARE: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.CompareRegisterNumber(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source) + } + + case JUMP, JE, JNE, JG, JL, JGE, JLE: + switch x.Mnemonic { + case JUMP: + code = x64.Jump8(code, 0x00) + case JE: + code = x64.Jump8IfEqual(code, 0x00) + case JNE: + code = x64.Jump8IfNotEqual(code, 0x00) + case JG: + code = x64.Jump8IfGreater(code, 0x00) + case JL: + code = x64.Jump8IfLess(code, 0x00) + case JGE: + code = x64.Jump8IfGreaterOrEqual(code, 0x00) + case JLE: + code = x64.Jump8IfLessOrEqual(code, 0x00) + } + size := 1 label := x.Data.(*Label) nextInstructionAddress := Address(len(code)) diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 6b547a2..1adcd9d 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -34,10 +34,10 @@ func (a *Assembler) Register(mnemonic Mnemonic, register cpu.Register) { }) } -// Label adds a label at the current position. -func (a *Assembler) Label(name string) { +// Label adds an instruction using a label. +func (a *Assembler) Label(mnemonic Mnemonic, name string) { a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: LABEL, + Mnemonic: mnemonic, Data: &Label{ Name: name, }, diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 315af19..06bd87a 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -7,7 +7,14 @@ const ( ADD CALL COMMENT + COMPARE DIV + JE + JNE + JG + JGE + JL + JLE JUMP MUL LABEL @@ -28,10 +35,24 @@ func (m Mnemonic) String() string { return "call" case COMMENT: return "comment" + case COMPARE: + return "compare" case DIV: return "div" case JUMP: return "jump" + case JE: + return "jump ==" + case JNE: + return "jump !=" + case JL: + return "jump <" + case JG: + return "jump >" + case JLE: + return "jump <=" + case JGE: + return "jump >=" case LABEL: return "label" case MOVE: diff --git a/src/build/ast/AST.go b/src/build/ast/AST.go index d1b991b..7def683 100644 --- a/src/build/ast/AST.go +++ b/src/build/ast/AST.go @@ -37,6 +37,15 @@ func (node *Define) String() string { return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) } +type If struct { + Condition *expression.Expression + Body AST +} + +func (node *If) String() string { + return fmt.Sprintf("(if %s %s)", node.Condition, node.Body) +} + type Loop struct { Body AST } diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index 760302e..4f00224 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -47,6 +47,22 @@ func toASTNode(tokens token.List) (Node, error) { tree, err := Parse(tokens[blockStart:blockEnd]) return &Loop{Body: tree}, err + case keyword.If: + blockStart := tokens.IndexKind(token.BlockStart) + 1 + blockEnd := tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) + } + + if blockEnd == -1 { + return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) + } + + condition := expression.Parse(tokens[1:token.BlockStart]) + tree, err := Parse(tokens[blockStart:blockEnd]) + return &If{Condition: condition, Body: tree}, err + default: return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, nil, tokens[0].Position) } diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go new file mode 100644 index 0000000..2f058e3 --- /dev/null +++ b/src/build/core/CompileIf.go @@ -0,0 +1,48 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" +) + +// CompileIf compiles a branch instruction. +func (f *Function) CompileIf(branch *ast.If) error { + condition := branch.Condition + tmpRight := f.cpu.Input[1] + err := f.ExpressionToRegister(condition.Children[1], tmpRight) + + if err != nil { + return err + } + + tmpLeft := f.cpu.Input[0] + err = f.ExpressionToRegister(condition.Children[0], tmpLeft) + + if err != nil { + return err + } + + f.assembler.RegisterRegister(asm.COMPARE, tmpLeft, tmpRight) + elseLabel := fmt.Sprintf("%s_if_%d_else", f.Name, f.count.branch) + + switch condition.Token.Text() { + case "==": + f.assembler.Label(asm.JNE, elseLabel) + case "!=": + f.assembler.Label(asm.JE, elseLabel) + case ">": + f.assembler.Label(asm.JLE, elseLabel) + case "<": + f.assembler.Label(asm.JGE, elseLabel) + case ">=": + f.assembler.Label(asm.JL, elseLabel) + case "<=": + f.assembler.Label(asm.JG, elseLabel) + } + + defer f.assembler.Label(asm.LABEL, elseLabel) + f.count.branch++ + return f.CompileAST(branch.Body) +} diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index 37a3c94..b8fd1d8 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -3,13 +3,14 @@ package core import ( "fmt" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" ) // CompileLoop compiles a loop instruction. func (f *Function) CompileLoop(loop *ast.Loop) error { label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) - f.assembler.Label(label) + f.assembler.Label(asm.LABEL, label) defer f.assembler.Jump(label) f.count.loop++ return f.CompileAST(loop.Body) diff --git a/src/build/core/Function.go b/src/build/core/Function.go index be45db3..e19cf0f 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -45,7 +45,7 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { // Compile turns a function into machine code. func (f *Function) Compile() { defer close(f.finished) - f.assembler.Label(f.Name) + f.assembler.Label(asm.LABEL, f.Name) f.err = f.CompileTokens(f.Body) f.assembler.Return() } @@ -90,6 +90,9 @@ func (f *Function) CompileASTNode(node ast.Node) error { case *ast.Return: return f.CompileReturn(node) + case *ast.If: + return f.CompileIf(node) + case *ast.Loop: return f.CompileLoop(node) diff --git a/src/build/core/state.go b/src/build/core/state.go index 31313d0..8ae6838 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -21,7 +21,8 @@ type state struct { // counter stores how often a certain statement appeared so we can generate a unique label from it. type counter struct { - loop int + loop int + branch int } // PrintInstructions shows the assembly instructions. diff --git a/src/build/keyword/Keyword.go b/src/build/keyword/Keyword.go index d3d9b18..d744384 100644 --- a/src/build/keyword/Keyword.go +++ b/src/build/keyword/Keyword.go @@ -1,12 +1,14 @@ package keyword const ( + If = "if" Loop = "loop" Return = "return" ) // Map is a map of all keywords used in the language. var Map = map[string][]byte{ + If: []byte(If), Loop: []byte(Loop), Return: []byte(Return), } diff --git a/tests/examples_test.go b/tests/examples_test.go index ab00191..be7590d 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -10,7 +10,7 @@ var examples = []struct { ExpectedOutput string ExpectedExitCode int }{ - {"hello", "", 9}, + {"hello", "", 0}, {"write", "ELF", 0}, } From d5d29ee9cc1bfd56d5714345ce4c854183741038 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 7 Jul 2024 14:07:34 +0200 Subject: [PATCH 0247/1012] Improved branch code generation --- examples/hello/hello.q | 2 +- src/build/ast/Parse.go | 14 +++++++------- src/build/core/CompileIf.go | 4 +++- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 53d906b..f6e8e26 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,7 +1,7 @@ main() { x := f(1) + f(2) + f(3) - if x != 9 { + if x != f(8) { exit(42) } diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index 4f00224..aa4eeef 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -33,7 +33,7 @@ func toASTNode(tokens token.List) (Node, error) { return &Return{Value: value}, nil case keyword.Loop: - blockStart := tokens.IndexKind(token.BlockStart) + 1 + blockStart := tokens.IndexKind(token.BlockStart) blockEnd := tokens.LastIndexKind(token.BlockEnd) if blockStart == -1 { @@ -44,11 +44,11 @@ func toASTNode(tokens token.List) (Node, error) { return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) } - tree, err := Parse(tokens[blockStart:blockEnd]) - return &Loop{Body: tree}, err + body, err := Parse(tokens[blockStart+1 : blockEnd]) + return &Loop{Body: body}, err case keyword.If: - blockStart := tokens.IndexKind(token.BlockStart) + 1 + blockStart := tokens.IndexKind(token.BlockStart) blockEnd := tokens.LastIndexKind(token.BlockEnd) if blockStart == -1 { @@ -59,9 +59,9 @@ func toASTNode(tokens token.List) (Node, error) { return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) } - condition := expression.Parse(tokens[1:token.BlockStart]) - tree, err := Parse(tokens[blockStart:blockEnd]) - return &If{Condition: condition, Body: tree}, err + condition := expression.Parse(tokens[1:blockStart]) + body, err := Parse(tokens[blockStart+1 : blockEnd]) + return &If{Condition: condition, Body: body}, err default: return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, nil, tokens[0].Position) diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 2f058e3..a38c76f 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -10,13 +10,14 @@ import ( // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { condition := branch.Condition - tmpRight := f.cpu.Input[1] + tmpRight := f.cpu.MustFindFree(f.cpu.General) err := f.ExpressionToRegister(condition.Children[1], tmpRight) if err != nil { return err } + f.cpu.Use(tmpRight) tmpLeft := f.cpu.Input[0] err = f.ExpressionToRegister(condition.Children[0], tmpLeft) @@ -25,6 +26,7 @@ func (f *Function) CompileIf(branch *ast.If) error { } f.assembler.RegisterRegister(asm.COMPARE, tmpLeft, tmpRight) + f.cpu.Free(tmpRight) elseLabel := fmt.Sprintf("%s_if_%d_else", f.Name, f.count.branch) switch condition.Token.Text() { From 403e78f655d2a75502a99f3ccc933abc8781b3c7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 7 Jul 2024 14:07:34 +0200 Subject: [PATCH 0248/1012] Improved branch code generation --- examples/hello/hello.q | 2 +- src/build/ast/Parse.go | 14 +++++++------- src/build/core/CompileIf.go | 4 +++- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 53d906b..f6e8e26 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,7 +1,7 @@ main() { x := f(1) + f(2) + f(3) - if x != 9 { + if x != f(8) { exit(42) } diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index 4f00224..aa4eeef 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -33,7 +33,7 @@ func toASTNode(tokens token.List) (Node, error) { return &Return{Value: value}, nil case keyword.Loop: - blockStart := tokens.IndexKind(token.BlockStart) + 1 + blockStart := tokens.IndexKind(token.BlockStart) blockEnd := tokens.LastIndexKind(token.BlockEnd) if blockStart == -1 { @@ -44,11 +44,11 @@ func toASTNode(tokens token.List) (Node, error) { return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) } - tree, err := Parse(tokens[blockStart:blockEnd]) - return &Loop{Body: tree}, err + body, err := Parse(tokens[blockStart+1 : blockEnd]) + return &Loop{Body: body}, err case keyword.If: - blockStart := tokens.IndexKind(token.BlockStart) + 1 + blockStart := tokens.IndexKind(token.BlockStart) blockEnd := tokens.LastIndexKind(token.BlockEnd) if blockStart == -1 { @@ -59,9 +59,9 @@ func toASTNode(tokens token.List) (Node, error) { return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) } - condition := expression.Parse(tokens[1:token.BlockStart]) - tree, err := Parse(tokens[blockStart:blockEnd]) - return &If{Condition: condition, Body: tree}, err + condition := expression.Parse(tokens[1:blockStart]) + body, err := Parse(tokens[blockStart+1 : blockEnd]) + return &If{Condition: condition, Body: body}, err default: return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, nil, tokens[0].Position) diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 2f058e3..a38c76f 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -10,13 +10,14 @@ import ( // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { condition := branch.Condition - tmpRight := f.cpu.Input[1] + tmpRight := f.cpu.MustFindFree(f.cpu.General) err := f.ExpressionToRegister(condition.Children[1], tmpRight) if err != nil { return err } + f.cpu.Use(tmpRight) tmpLeft := f.cpu.Input[0] err = f.ExpressionToRegister(condition.Children[0], tmpLeft) @@ -25,6 +26,7 @@ func (f *Function) CompileIf(branch *ast.If) error { } f.assembler.RegisterRegister(asm.COMPARE, tmpLeft, tmpRight) + f.cpu.Free(tmpRight) elseLabel := fmt.Sprintf("%s_if_%d_else", f.Name, f.count.branch) switch condition.Token.Text() { From b5efc533ea04973328c186ec1c99205bb29bed4c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 7 Jul 2024 15:20:21 +0200 Subject: [PATCH 0249/1012] Improved branch code generation --- src/build/core/CompileIf.go | 28 +++++++++++------------ src/build/core/ExecuteRegisterNumber.go | 3 +++ src/build/core/ExecuteRegisterRegister.go | 3 +++ 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index a38c76f..bebfc10 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -10,41 +10,39 @@ import ( // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { condition := branch.Condition - tmpRight := f.cpu.MustFindFree(f.cpu.General) - err := f.ExpressionToRegister(condition.Children[1], tmpRight) + tmp := f.cpu.MustFindFree(f.cpu.General) + err := f.ExpressionToRegister(condition.Children[0], tmp) if err != nil { return err } - f.cpu.Use(tmpRight) - tmpLeft := f.cpu.Input[0] - err = f.ExpressionToRegister(condition.Children[0], tmpLeft) + f.cpu.Use(tmp) + err = f.Execute(condition.Token, tmp, condition.Children[1]) if err != nil { return err } - f.assembler.RegisterRegister(asm.COMPARE, tmpLeft, tmpRight) - f.cpu.Free(tmpRight) - elseLabel := fmt.Sprintf("%s_if_%d_else", f.Name, f.count.branch) + f.cpu.Free(tmp) + endLabel := fmt.Sprintf("%s_end_if_%d", f.Name, f.count.branch) switch condition.Token.Text() { case "==": - f.assembler.Label(asm.JNE, elseLabel) + f.assembler.Label(asm.JNE, endLabel) case "!=": - f.assembler.Label(asm.JE, elseLabel) + f.assembler.Label(asm.JE, endLabel) case ">": - f.assembler.Label(asm.JLE, elseLabel) + f.assembler.Label(asm.JLE, endLabel) case "<": - f.assembler.Label(asm.JGE, elseLabel) + f.assembler.Label(asm.JGE, endLabel) case ">=": - f.assembler.Label(asm.JL, elseLabel) + f.assembler.Label(asm.JL, endLabel) case "<=": - f.assembler.Label(asm.JG, elseLabel) + f.assembler.Label(asm.JG, endLabel) } - defer f.assembler.Label(asm.LABEL, elseLabel) + defer f.assembler.Label(asm.LABEL, endLabel) f.count.branch++ return f.CompileAST(branch.Body) } diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index 01039ed..c85ac70 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -22,6 +22,9 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg case "/", "/=": f.assembler.RegisterNumber(asm.DIV, register, number) + case "==", "!=", "<", "<=", ">", ">=": + f.assembler.RegisterNumber(asm.COMPARE, register, number) + case "=": f.assembler.RegisterNumber(asm.MOVE, register, number) diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index b271f9a..d10205d 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -22,6 +22,9 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cp case "/", "/=": f.assembler.RegisterRegister(asm.DIV, destination, source) + case "==", "!=", "<", "<=", ">", ">=": + f.assembler.RegisterRegister(asm.COMPARE, destination, source) + case "=": if destination != source { f.assembler.RegisterRegister(asm.MOVE, destination, source) From 23b4e6442d30fb9e8e4285ab555a272977375189 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 7 Jul 2024 15:20:21 +0200 Subject: [PATCH 0250/1012] Improved branch code generation --- src/build/core/CompileIf.go | 28 +++++++++++------------ src/build/core/ExecuteRegisterNumber.go | 3 +++ src/build/core/ExecuteRegisterRegister.go | 3 +++ 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index a38c76f..bebfc10 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -10,41 +10,39 @@ import ( // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { condition := branch.Condition - tmpRight := f.cpu.MustFindFree(f.cpu.General) - err := f.ExpressionToRegister(condition.Children[1], tmpRight) + tmp := f.cpu.MustFindFree(f.cpu.General) + err := f.ExpressionToRegister(condition.Children[0], tmp) if err != nil { return err } - f.cpu.Use(tmpRight) - tmpLeft := f.cpu.Input[0] - err = f.ExpressionToRegister(condition.Children[0], tmpLeft) + f.cpu.Use(tmp) + err = f.Execute(condition.Token, tmp, condition.Children[1]) if err != nil { return err } - f.assembler.RegisterRegister(asm.COMPARE, tmpLeft, tmpRight) - f.cpu.Free(tmpRight) - elseLabel := fmt.Sprintf("%s_if_%d_else", f.Name, f.count.branch) + f.cpu.Free(tmp) + endLabel := fmt.Sprintf("%s_end_if_%d", f.Name, f.count.branch) switch condition.Token.Text() { case "==": - f.assembler.Label(asm.JNE, elseLabel) + f.assembler.Label(asm.JNE, endLabel) case "!=": - f.assembler.Label(asm.JE, elseLabel) + f.assembler.Label(asm.JE, endLabel) case ">": - f.assembler.Label(asm.JLE, elseLabel) + f.assembler.Label(asm.JLE, endLabel) case "<": - f.assembler.Label(asm.JGE, elseLabel) + f.assembler.Label(asm.JGE, endLabel) case ">=": - f.assembler.Label(asm.JL, elseLabel) + f.assembler.Label(asm.JL, endLabel) case "<=": - f.assembler.Label(asm.JG, elseLabel) + f.assembler.Label(asm.JG, endLabel) } - defer f.assembler.Label(asm.LABEL, elseLabel) + defer f.assembler.Label(asm.LABEL, endLabel) f.count.branch++ return f.CompileAST(branch.Body) } diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index 01039ed..c85ac70 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -22,6 +22,9 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg case "/", "/=": f.assembler.RegisterNumber(asm.DIV, register, number) + case "==", "!=", "<", "<=", ">", ">=": + f.assembler.RegisterNumber(asm.COMPARE, register, number) + case "=": f.assembler.RegisterNumber(asm.MOVE, register, number) diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index b271f9a..d10205d 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -22,6 +22,9 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cp case "/", "/=": f.assembler.RegisterRegister(asm.DIV, destination, source) + case "==", "!=", "<", "<=", ">", ">=": + f.assembler.RegisterRegister(asm.COMPARE, destination, source) + case "=": if destination != source { f.assembler.RegisterRegister(asm.MOVE, destination, source) From 78ae050d1821cad5e25c1b25470c00dd4c5f5406 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 7 Jul 2024 17:13:22 +0200 Subject: [PATCH 0251/1012] Added more tests --- src/build/core/CompileCall.go | 12 ++++- src/build/core/CompileIf.go | 14 +---- src/build/core/Evaluate.go | 41 +++++++++++++++ src/build/errors/UnknownFunction.go | 18 +++++++ tests/errors/UnknownFunction.q | 3 ++ tests/errors_test.go | 1 + tests/programs/branch.q | 81 +++++++++++++++++++++++++++++ 7 files changed, 157 insertions(+), 13 deletions(-) create mode 100644 src/build/core/Evaluate.go create mode 100644 src/build/errors/UnknownFunction.go create mode 100644 tests/errors/UnknownFunction.q create mode 100644 tests/programs/branch.q diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 079cf97..9c92950 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -2,6 +2,7 @@ package core import ( "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" ) @@ -11,9 +12,18 @@ import ( // After the function call, they must be restored in reverse order. func (f *Function) CompileCall(root *expression.Expression) error { funcName := root.Children[0].Token.Text() + isSyscall := funcName == "syscall" + + if !isSyscall { + _, exists := f.functions[funcName] + + if !exists { + return errors.New(&errors.UnknownFunction{Name: funcName}, f.File, root.Children[0].Token.Position) + } + } + parameters := root.Children[1:] registers := f.cpu.Input[:len(parameters)] - isSyscall := funcName == "syscall" if isSyscall { registers = f.cpu.Syscall[:len(parameters)] diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index bebfc10..de43bec 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -9,25 +9,15 @@ import ( // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { - condition := branch.Condition - tmp := f.cpu.MustFindFree(f.cpu.General) - err := f.ExpressionToRegister(condition.Children[0], tmp) + err := f.Evaluate(branch.Condition) if err != nil { return err } - f.cpu.Use(tmp) - err = f.Execute(condition.Token, tmp, condition.Children[1]) - - if err != nil { - return err - } - - f.cpu.Free(tmp) endLabel := fmt.Sprintf("%s_end_if_%d", f.Name, f.count.branch) - switch condition.Token.Text() { + switch branch.Condition.Token.Text() { case "==": f.assembler.Label(asm.JNE, endLabel) case "!=": diff --git a/src/build/core/Evaluate.go b/src/build/core/Evaluate.go new file mode 100644 index 0000000..5c3a60c --- /dev/null +++ b/src/build/core/Evaluate.go @@ -0,0 +1,41 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Evaluate evaluates an expression. +func (f *Function) Evaluate(value *expression.Expression) error { + left := value.Children[0] + right := value.Children[1] + + if left.IsLeaf() && left.Token.Kind == token.Identifier { + variable := f.variables[left.Token.Text()] + register := variable.Register + defer f.useVariable(variable) + return f.Execute(value.Token, register, right) + } + + if ast.IsFunctionCall(left) && right.IsLeaf() { + err := f.CompileCall(left) + + if err != nil { + return err + } + + return f.Execute(value.Token, f.cpu.Output[0], right) + } + + tmp := f.cpu.MustFindFree(f.cpu.General) + err := f.ExpressionToRegister(left, tmp) + + if err != nil { + return err + } + + f.cpu.Use(tmp) + defer f.cpu.Free(tmp) + return f.Execute(value.Token, tmp, right) +} diff --git a/src/build/errors/UnknownFunction.go b/src/build/errors/UnknownFunction.go new file mode 100644 index 0000000..a2b7e4e --- /dev/null +++ b/src/build/errors/UnknownFunction.go @@ -0,0 +1,18 @@ +package errors + +import "fmt" + +// UnknownFunction represents unknown function errors. +type UnknownFunction struct { + Name string + CorrectName string +} + +// Error generates the string representation. +func (err *UnknownFunction) Error() string { + if err.CorrectName != "" { + return fmt.Sprintf("Unknown function '%s', did you mean '%s'?", err.Name, err.CorrectName) + } + + return fmt.Sprintf("Unknown function '%s'", err.Name) +} diff --git a/tests/errors/UnknownFunction.q b/tests/errors/UnknownFunction.q new file mode 100644 index 0000000..6b917e1 --- /dev/null +++ b/tests/errors/UnknownFunction.q @@ -0,0 +1,3 @@ +main() { + unknown() +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index d1123bb..7782a67 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -33,6 +33,7 @@ var errs = []struct { {"MissingOperand.q", errors.MissingOperand}, {"MissingOperand2.q", errors.MissingOperand}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, + {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "f"}}, diff --git a/tests/programs/branch.q b/tests/programs/branch.q new file mode 100644 index 0000000..547fcfc --- /dev/null +++ b/tests/programs/branch.q @@ -0,0 +1,81 @@ +main() { + x := 0 + + if x != 0 { + exit(1) + } + + if x > 0 { + exit(1) + } + + if x < 0 { + exit(1) + } + + if 0 != x { + exit(1) + } + + if 0 > x { + exit(1) + } + + if 0 < x { + exit(1) + } + + if x >= 1 { + exit(1) + } + + if 1 <= x { + exit(1) + } + + if x == inc(x) { + exit(1) + } + + if x == dec(x) { + exit(1) + } + + if inc(0) == x { + exit(1) + } + + if dec(0) == x { + exit(1) + } + + if inc(x) == dec(x) { + exit(1) + } + + if x + 1 != inc(x) { + exit(1) + } + + if x + 1 != x + 1 { + exit(1) + } + + if x == 0 { + exit(0) + } + + exit(1) +} + +exit(x) { + syscall(60, x) +} + +inc(x) { + return x + 1 +} + +dec(x) { + return x - 1 +} \ No newline at end of file From 9f341a61466b680a774a9ebcf5f263841c62fd4f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 7 Jul 2024 17:13:22 +0200 Subject: [PATCH 0252/1012] Added more tests --- src/build/core/CompileCall.go | 12 ++++- src/build/core/CompileIf.go | 14 +---- src/build/core/Evaluate.go | 41 +++++++++++++++ src/build/errors/UnknownFunction.go | 18 +++++++ tests/errors/UnknownFunction.q | 3 ++ tests/errors_test.go | 1 + tests/programs/branch.q | 81 +++++++++++++++++++++++++++++ 7 files changed, 157 insertions(+), 13 deletions(-) create mode 100644 src/build/core/Evaluate.go create mode 100644 src/build/errors/UnknownFunction.go create mode 100644 tests/errors/UnknownFunction.q create mode 100644 tests/programs/branch.q diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 079cf97..9c92950 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -2,6 +2,7 @@ package core import ( "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" ) @@ -11,9 +12,18 @@ import ( // After the function call, they must be restored in reverse order. func (f *Function) CompileCall(root *expression.Expression) error { funcName := root.Children[0].Token.Text() + isSyscall := funcName == "syscall" + + if !isSyscall { + _, exists := f.functions[funcName] + + if !exists { + return errors.New(&errors.UnknownFunction{Name: funcName}, f.File, root.Children[0].Token.Position) + } + } + parameters := root.Children[1:] registers := f.cpu.Input[:len(parameters)] - isSyscall := funcName == "syscall" if isSyscall { registers = f.cpu.Syscall[:len(parameters)] diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index bebfc10..de43bec 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -9,25 +9,15 @@ import ( // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { - condition := branch.Condition - tmp := f.cpu.MustFindFree(f.cpu.General) - err := f.ExpressionToRegister(condition.Children[0], tmp) + err := f.Evaluate(branch.Condition) if err != nil { return err } - f.cpu.Use(tmp) - err = f.Execute(condition.Token, tmp, condition.Children[1]) - - if err != nil { - return err - } - - f.cpu.Free(tmp) endLabel := fmt.Sprintf("%s_end_if_%d", f.Name, f.count.branch) - switch condition.Token.Text() { + switch branch.Condition.Token.Text() { case "==": f.assembler.Label(asm.JNE, endLabel) case "!=": diff --git a/src/build/core/Evaluate.go b/src/build/core/Evaluate.go new file mode 100644 index 0000000..5c3a60c --- /dev/null +++ b/src/build/core/Evaluate.go @@ -0,0 +1,41 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Evaluate evaluates an expression. +func (f *Function) Evaluate(value *expression.Expression) error { + left := value.Children[0] + right := value.Children[1] + + if left.IsLeaf() && left.Token.Kind == token.Identifier { + variable := f.variables[left.Token.Text()] + register := variable.Register + defer f.useVariable(variable) + return f.Execute(value.Token, register, right) + } + + if ast.IsFunctionCall(left) && right.IsLeaf() { + err := f.CompileCall(left) + + if err != nil { + return err + } + + return f.Execute(value.Token, f.cpu.Output[0], right) + } + + tmp := f.cpu.MustFindFree(f.cpu.General) + err := f.ExpressionToRegister(left, tmp) + + if err != nil { + return err + } + + f.cpu.Use(tmp) + defer f.cpu.Free(tmp) + return f.Execute(value.Token, tmp, right) +} diff --git a/src/build/errors/UnknownFunction.go b/src/build/errors/UnknownFunction.go new file mode 100644 index 0000000..a2b7e4e --- /dev/null +++ b/src/build/errors/UnknownFunction.go @@ -0,0 +1,18 @@ +package errors + +import "fmt" + +// UnknownFunction represents unknown function errors. +type UnknownFunction struct { + Name string + CorrectName string +} + +// Error generates the string representation. +func (err *UnknownFunction) Error() string { + if err.CorrectName != "" { + return fmt.Sprintf("Unknown function '%s', did you mean '%s'?", err.Name, err.CorrectName) + } + + return fmt.Sprintf("Unknown function '%s'", err.Name) +} diff --git a/tests/errors/UnknownFunction.q b/tests/errors/UnknownFunction.q new file mode 100644 index 0000000..6b917e1 --- /dev/null +++ b/tests/errors/UnknownFunction.q @@ -0,0 +1,3 @@ +main() { + unknown() +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index d1123bb..7782a67 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -33,6 +33,7 @@ var errs = []struct { {"MissingOperand.q", errors.MissingOperand}, {"MissingOperand2.q", errors.MissingOperand}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, + {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "f"}}, diff --git a/tests/programs/branch.q b/tests/programs/branch.q new file mode 100644 index 0000000..547fcfc --- /dev/null +++ b/tests/programs/branch.q @@ -0,0 +1,81 @@ +main() { + x := 0 + + if x != 0 { + exit(1) + } + + if x > 0 { + exit(1) + } + + if x < 0 { + exit(1) + } + + if 0 != x { + exit(1) + } + + if 0 > x { + exit(1) + } + + if 0 < x { + exit(1) + } + + if x >= 1 { + exit(1) + } + + if 1 <= x { + exit(1) + } + + if x == inc(x) { + exit(1) + } + + if x == dec(x) { + exit(1) + } + + if inc(0) == x { + exit(1) + } + + if dec(0) == x { + exit(1) + } + + if inc(x) == dec(x) { + exit(1) + } + + if x + 1 != inc(x) { + exit(1) + } + + if x + 1 != x + 1 { + exit(1) + } + + if x == 0 { + exit(0) + } + + exit(1) +} + +exit(x) { + syscall(60, x) +} + +inc(x) { + return x + 1 +} + +dec(x) { + return x - 1 +} \ No newline at end of file From 871749101ed75a497dc97836cfd2f0f770e308a7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 7 Jul 2024 19:44:54 +0200 Subject: [PATCH 0253/1012] Simplified branch jumps --- src/build/core/CompileIf.go | 53 ++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index de43bec..0b9b584 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -16,23 +16,44 @@ func (f *Function) CompileIf(branch *ast.If) error { } endLabel := fmt.Sprintf("%s_end_if_%d", f.Name, f.count.branch) - - switch branch.Condition.Token.Text() { - case "==": - f.assembler.Label(asm.JNE, endLabel) - case "!=": - f.assembler.Label(asm.JE, endLabel) - case ">": - f.assembler.Label(asm.JLE, endLabel) - case "<": - f.assembler.Label(asm.JGE, endLabel) - case ">=": - f.assembler.Label(asm.JL, endLabel) - case "<=": - f.assembler.Label(asm.JG, endLabel) - } - + f.JumpIfFalse(branch.Condition.Token.Text(), endLabel) defer f.assembler.Label(asm.LABEL, endLabel) f.count.branch++ return f.CompileAST(branch.Body) } + +// JumpIfFalse jumps to the label if the previous comparison was false. +func (f *Function) JumpIfFalse(operator string, label string) { + switch operator { + case "==": + f.assembler.Label(asm.JNE, label) + case "!=": + f.assembler.Label(asm.JE, label) + case ">": + f.assembler.Label(asm.JLE, label) + case "<": + f.assembler.Label(asm.JGE, label) + case ">=": + f.assembler.Label(asm.JL, label) + case "<=": + f.assembler.Label(asm.JG, label) + } +} + +// JumpIfTrue jumps to the label if the previous comparison was true. +func (f *Function) JumpIfTrue(operator string, label string) { + switch operator { + case "==": + f.assembler.Label(asm.JE, label) + case "!=": + f.assembler.Label(asm.JNE, label) + case ">": + f.assembler.Label(asm.JG, label) + case "<": + f.assembler.Label(asm.JL, label) + case ">=": + f.assembler.Label(asm.JGE, label) + case "<=": + f.assembler.Label(asm.JLE, label) + } +} From 6aad74d6dd91c56282f0d96c769e9bc4d127d4cc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 7 Jul 2024 19:44:54 +0200 Subject: [PATCH 0254/1012] Simplified branch jumps --- src/build/core/CompileIf.go | 53 ++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index de43bec..0b9b584 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -16,23 +16,44 @@ func (f *Function) CompileIf(branch *ast.If) error { } endLabel := fmt.Sprintf("%s_end_if_%d", f.Name, f.count.branch) - - switch branch.Condition.Token.Text() { - case "==": - f.assembler.Label(asm.JNE, endLabel) - case "!=": - f.assembler.Label(asm.JE, endLabel) - case ">": - f.assembler.Label(asm.JLE, endLabel) - case "<": - f.assembler.Label(asm.JGE, endLabel) - case ">=": - f.assembler.Label(asm.JL, endLabel) - case "<=": - f.assembler.Label(asm.JG, endLabel) - } - + f.JumpIfFalse(branch.Condition.Token.Text(), endLabel) defer f.assembler.Label(asm.LABEL, endLabel) f.count.branch++ return f.CompileAST(branch.Body) } + +// JumpIfFalse jumps to the label if the previous comparison was false. +func (f *Function) JumpIfFalse(operator string, label string) { + switch operator { + case "==": + f.assembler.Label(asm.JNE, label) + case "!=": + f.assembler.Label(asm.JE, label) + case ">": + f.assembler.Label(asm.JLE, label) + case "<": + f.assembler.Label(asm.JGE, label) + case ">=": + f.assembler.Label(asm.JL, label) + case "<=": + f.assembler.Label(asm.JG, label) + } +} + +// JumpIfTrue jumps to the label if the previous comparison was true. +func (f *Function) JumpIfTrue(operator string, label string) { + switch operator { + case "==": + f.assembler.Label(asm.JE, label) + case "!=": + f.assembler.Label(asm.JNE, label) + case ">": + f.assembler.Label(asm.JG, label) + case "<": + f.assembler.Label(asm.JL, label) + case ">=": + f.assembler.Label(asm.JGE, label) + case "<=": + f.assembler.Label(asm.JLE, label) + } +} From 13161f5021940a093909e1c36735a790c7d2e04b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 7 Jul 2024 21:55:32 +0200 Subject: [PATCH 0255/1012] Improved branch compilation --- examples/hello/hello.q | 2 +- src/build/asm/Assembler.go | 14 ++++- src/build/asm/Mnemonic.go | 12 ++--- src/build/core/Compare.go | 47 +++++++++++++++++ src/build/core/CompileCondition.go | 82 ++++++++++++++++++++++++++++++ src/build/core/CompileIf.go | 43 ++-------------- src/build/core/Evaluate.go | 41 --------------- src/build/core/state.go | 10 ++-- tests/programs/branch.q | 22 ++------ 9 files changed, 160 insertions(+), 113 deletions(-) create mode 100644 src/build/core/Compare.go create mode 100644 src/build/core/CompileCondition.go delete mode 100644 src/build/core/Evaluate.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index f6e8e26..7cdf32f 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,7 +1,7 @@ main() { x := f(1) + f(2) + f(3) - if x != f(8) { + if x != f(8) || x != 9 || x == 6 { exit(42) } diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index caf600a..18c536d 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -57,7 +57,12 @@ func (a Assembler) Finalize() ([]byte, []byte) { Position: Address(len(code) - size), Size: uint8(size), Resolve: func() Address { - destination := labels[label.Name] + destination, exists := labels[label.Name] + + if !exists { + panic("unknown call label") + } + distance := destination - nextInstructionAddress return Address(distance) }, @@ -100,7 +105,12 @@ func (a Assembler) Finalize() ([]byte, []byte) { Position: Address(len(code) - size), Size: uint8(size), Resolve: func() Address { - destination := labels[label.Name] + destination, exists := labels[label.Name] + + if !exists { + panic("unknown jump label") + } + distance := destination - nextInstructionAddress return Address(distance) }, diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 06bd87a..d25bc99 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -42,17 +42,17 @@ func (m Mnemonic) String() string { case JUMP: return "jump" case JE: - return "jump ==" + return "jump if ==" case JNE: - return "jump !=" + return "jump if !=" case JL: - return "jump <" + return "jump if <" case JG: - return "jump >" + return "jump if >" case JLE: - return "jump <=" + return "jump if <=" case JGE: - return "jump >=" + return "jump if >=" case LABEL: return "label" case MOVE: diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go new file mode 100644 index 0000000..955f67e --- /dev/null +++ b/src/build/core/Compare.go @@ -0,0 +1,47 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Compare evaluates a boolean expression. +func (f *Function) Compare(comparison *expression.Expression) error { + left := comparison.Children[0] + right := comparison.Children[1] + + if left.IsLeaf() && left.Token.Kind == token.Identifier { + name := left.Token.Text() + variable, exists := f.variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) + } + + defer f.useVariable(variable) + return f.Execute(comparison.Token, variable.Register, right) + } + + if ast.IsFunctionCall(left) && right.IsLeaf() { + err := f.CompileCall(left) + + if err != nil { + return err + } + + return f.Execute(comparison.Token, f.cpu.Output[0], right) + } + + tmp := f.cpu.MustFindFree(f.cpu.General) + err := f.ExpressionToRegister(left, tmp) + + if err != nil { + return err + } + + f.cpu.Use(tmp) + defer f.cpu.Free(tmp) + return f.Execute(comparison.Token, tmp, right) +} diff --git a/src/build/core/CompileCondition.go b/src/build/core/CompileCondition.go new file mode 100644 index 0000000..3a50df7 --- /dev/null +++ b/src/build/core/CompileCondition.go @@ -0,0 +1,82 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/expression" +) + +// CompileCondition inserts code to jump to the start label or end label depending on the truth of the condition. +func (f *Function) CompileCondition(condition *expression.Expression, startLabel string, endLabel string) error { + switch condition.Token.Text() { + case "||": + left := condition.Children[0] + err := f.CompileCondition(left, startLabel, endLabel) + + if err != nil { + return err + } + + f.JumpIfTrue(left.Token.Text(), startLabel) + + right := condition.Children[1] + err = f.CompileCondition(right, startLabel, endLabel) + + if err != nil { + return err + } + + if condition.Parent == nil { + f.JumpIfFalse(right.Token.Text(), endLabel) + } else { + f.JumpIfTrue(right.Token.Text(), startLabel) + } + + return nil + case "&&": + return nil + default: + err := f.Compare(condition) + + if condition.Parent == nil { + f.JumpIfFalse(condition.Token.Text(), endLabel) + } + + return err + } +} + +// JumpIfFalse jumps to the label if the previous comparison was false. +func (f *Function) JumpIfFalse(operator string, label string) { + switch operator { + case "==": + f.assembler.Label(asm.JNE, label) + case "!=": + f.assembler.Label(asm.JE, label) + case ">": + f.assembler.Label(asm.JLE, label) + case "<": + f.assembler.Label(asm.JGE, label) + case ">=": + f.assembler.Label(asm.JL, label) + case "<=": + f.assembler.Label(asm.JG, label) + } +} + +// JumpIfTrue jumps to the label if the previous comparison was true. +func (f *Function) JumpIfTrue(operator string, label string) { + switch operator { + case "==": + f.assembler.Label(asm.JE, label) + case "!=": + f.assembler.Label(asm.JNE, label) + case ">": + f.assembler.Label(asm.JG, label) + case "<": + f.assembler.Label(asm.JL, label) + case ">=": + f.assembler.Label(asm.JGE, label) + case "<=": + f.assembler.Label(asm.JLE, label) + } +} diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 0b9b584..c73faba 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -9,51 +9,16 @@ import ( // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { - err := f.Evaluate(branch.Condition) + startLabel := fmt.Sprintf("%s_if_start_%d", f.Name, f.count.branch) + endLabel := fmt.Sprintf("%s_if_end_%d", f.Name, f.count.branch) + err := f.CompileCondition(branch.Condition, startLabel, endLabel) if err != nil { return err } - endLabel := fmt.Sprintf("%s_end_if_%d", f.Name, f.count.branch) - f.JumpIfFalse(branch.Condition.Token.Text(), endLabel) + f.assembler.Label(asm.LABEL, startLabel) defer f.assembler.Label(asm.LABEL, endLabel) f.count.branch++ return f.CompileAST(branch.Body) } - -// JumpIfFalse jumps to the label if the previous comparison was false. -func (f *Function) JumpIfFalse(operator string, label string) { - switch operator { - case "==": - f.assembler.Label(asm.JNE, label) - case "!=": - f.assembler.Label(asm.JE, label) - case ">": - f.assembler.Label(asm.JLE, label) - case "<": - f.assembler.Label(asm.JGE, label) - case ">=": - f.assembler.Label(asm.JL, label) - case "<=": - f.assembler.Label(asm.JG, label) - } -} - -// JumpIfTrue jumps to the label if the previous comparison was true. -func (f *Function) JumpIfTrue(operator string, label string) { - switch operator { - case "==": - f.assembler.Label(asm.JE, label) - case "!=": - f.assembler.Label(asm.JNE, label) - case ">": - f.assembler.Label(asm.JG, label) - case "<": - f.assembler.Label(asm.JL, label) - case ">=": - f.assembler.Label(asm.JGE, label) - case "<=": - f.assembler.Label(asm.JLE, label) - } -} diff --git a/src/build/core/Evaluate.go b/src/build/core/Evaluate.go deleted file mode 100644 index 5c3a60c..0000000 --- a/src/build/core/Evaluate.go +++ /dev/null @@ -1,41 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" -) - -// Evaluate evaluates an expression. -func (f *Function) Evaluate(value *expression.Expression) error { - left := value.Children[0] - right := value.Children[1] - - if left.IsLeaf() && left.Token.Kind == token.Identifier { - variable := f.variables[left.Token.Text()] - register := variable.Register - defer f.useVariable(variable) - return f.Execute(value.Token, register, right) - } - - if ast.IsFunctionCall(left) && right.IsLeaf() { - err := f.CompileCall(left) - - if err != nil { - return err - } - - return f.Execute(value.Token, f.cpu.Output[0], right) - } - - tmp := f.cpu.MustFindFree(f.cpu.General) - err := f.ExpressionToRegister(left, tmp) - - if err != nil { - return err - } - - f.cpu.Use(tmp) - defer f.cpu.Free(tmp) - return f.Execute(value.Token, tmp, right) -} diff --git a/src/build/core/state.go b/src/build/core/state.go index 8ae6838..3127eda 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -27,20 +27,20 @@ type counter struct { // PrintInstructions shows the assembly instructions. func (s *state) PrintInstructions() { - ansi.Dim.Println("╭────────────────────────────────────────────────╮") + ansi.Dim.Println("╭────────────────────────────────────────────────────╮") for _, x := range s.assembler.Instructions { ansi.Dim.Print("│ ") switch x.Mnemonic { case asm.LABEL: - ansi.Yellow.Printf("%-46s", x.Data.String()+":") + ansi.Yellow.Printf("%-50s", x.Data.String()+":") case asm.COMMENT: - ansi.Dim.Printf("%-46s", x.Data.String()) + ansi.Dim.Printf("%-50s", x.Data.String()) default: - ansi.Green.Printf("%-8s", x.Mnemonic.String()) + ansi.Green.Printf("%-12s", x.Mnemonic.String()) if x.Data != nil { fmt.Printf("%-38s", x.Data.String()) @@ -52,5 +52,5 @@ func (s *state) PrintInstructions() { ansi.Dim.Print(" │\n") } - ansi.Dim.Println("╰────────────────────────────────────────────────╯") + ansi.Dim.Println("╰────────────────────────────────────────────────────╯") } diff --git a/tests/programs/branch.q b/tests/programs/branch.q index 547fcfc..5a2830e 100644 --- a/tests/programs/branch.q +++ b/tests/programs/branch.q @@ -25,31 +25,15 @@ main() { exit(1) } - if x >= 1 { + if x >= 1 || 1 <= x { exit(1) } - if 1 <= x { + if x == inc(x) || x == dec(x) { exit(1) } - if x == inc(x) { - exit(1) - } - - if x == dec(x) { - exit(1) - } - - if inc(0) == x { - exit(1) - } - - if dec(0) == x { - exit(1) - } - - if inc(x) == dec(x) { + if inc(0) == x || dec(0) == x || inc(x) == dec(x) { exit(1) } From ee16774fe7cf72e5eba62f90bdb99ccbb77eba7e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 7 Jul 2024 21:55:32 +0200 Subject: [PATCH 0256/1012] Improved branch compilation --- examples/hello/hello.q | 2 +- src/build/asm/Assembler.go | 14 ++++- src/build/asm/Mnemonic.go | 12 ++--- src/build/core/Compare.go | 47 +++++++++++++++++ src/build/core/CompileCondition.go | 82 ++++++++++++++++++++++++++++++ src/build/core/CompileIf.go | 43 ++-------------- src/build/core/Evaluate.go | 41 --------------- src/build/core/state.go | 10 ++-- tests/programs/branch.q | 22 ++------ 9 files changed, 160 insertions(+), 113 deletions(-) create mode 100644 src/build/core/Compare.go create mode 100644 src/build/core/CompileCondition.go delete mode 100644 src/build/core/Evaluate.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index f6e8e26..7cdf32f 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,7 +1,7 @@ main() { x := f(1) + f(2) + f(3) - if x != f(8) { + if x != f(8) || x != 9 || x == 6 { exit(42) } diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index caf600a..18c536d 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -57,7 +57,12 @@ func (a Assembler) Finalize() ([]byte, []byte) { Position: Address(len(code) - size), Size: uint8(size), Resolve: func() Address { - destination := labels[label.Name] + destination, exists := labels[label.Name] + + if !exists { + panic("unknown call label") + } + distance := destination - nextInstructionAddress return Address(distance) }, @@ -100,7 +105,12 @@ func (a Assembler) Finalize() ([]byte, []byte) { Position: Address(len(code) - size), Size: uint8(size), Resolve: func() Address { - destination := labels[label.Name] + destination, exists := labels[label.Name] + + if !exists { + panic("unknown jump label") + } + distance := destination - nextInstructionAddress return Address(distance) }, diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 06bd87a..d25bc99 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -42,17 +42,17 @@ func (m Mnemonic) String() string { case JUMP: return "jump" case JE: - return "jump ==" + return "jump if ==" case JNE: - return "jump !=" + return "jump if !=" case JL: - return "jump <" + return "jump if <" case JG: - return "jump >" + return "jump if >" case JLE: - return "jump <=" + return "jump if <=" case JGE: - return "jump >=" + return "jump if >=" case LABEL: return "label" case MOVE: diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go new file mode 100644 index 0000000..955f67e --- /dev/null +++ b/src/build/core/Compare.go @@ -0,0 +1,47 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Compare evaluates a boolean expression. +func (f *Function) Compare(comparison *expression.Expression) error { + left := comparison.Children[0] + right := comparison.Children[1] + + if left.IsLeaf() && left.Token.Kind == token.Identifier { + name := left.Token.Text() + variable, exists := f.variables[name] + + if !exists { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) + } + + defer f.useVariable(variable) + return f.Execute(comparison.Token, variable.Register, right) + } + + if ast.IsFunctionCall(left) && right.IsLeaf() { + err := f.CompileCall(left) + + if err != nil { + return err + } + + return f.Execute(comparison.Token, f.cpu.Output[0], right) + } + + tmp := f.cpu.MustFindFree(f.cpu.General) + err := f.ExpressionToRegister(left, tmp) + + if err != nil { + return err + } + + f.cpu.Use(tmp) + defer f.cpu.Free(tmp) + return f.Execute(comparison.Token, tmp, right) +} diff --git a/src/build/core/CompileCondition.go b/src/build/core/CompileCondition.go new file mode 100644 index 0000000..3a50df7 --- /dev/null +++ b/src/build/core/CompileCondition.go @@ -0,0 +1,82 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/expression" +) + +// CompileCondition inserts code to jump to the start label or end label depending on the truth of the condition. +func (f *Function) CompileCondition(condition *expression.Expression, startLabel string, endLabel string) error { + switch condition.Token.Text() { + case "||": + left := condition.Children[0] + err := f.CompileCondition(left, startLabel, endLabel) + + if err != nil { + return err + } + + f.JumpIfTrue(left.Token.Text(), startLabel) + + right := condition.Children[1] + err = f.CompileCondition(right, startLabel, endLabel) + + if err != nil { + return err + } + + if condition.Parent == nil { + f.JumpIfFalse(right.Token.Text(), endLabel) + } else { + f.JumpIfTrue(right.Token.Text(), startLabel) + } + + return nil + case "&&": + return nil + default: + err := f.Compare(condition) + + if condition.Parent == nil { + f.JumpIfFalse(condition.Token.Text(), endLabel) + } + + return err + } +} + +// JumpIfFalse jumps to the label if the previous comparison was false. +func (f *Function) JumpIfFalse(operator string, label string) { + switch operator { + case "==": + f.assembler.Label(asm.JNE, label) + case "!=": + f.assembler.Label(asm.JE, label) + case ">": + f.assembler.Label(asm.JLE, label) + case "<": + f.assembler.Label(asm.JGE, label) + case ">=": + f.assembler.Label(asm.JL, label) + case "<=": + f.assembler.Label(asm.JG, label) + } +} + +// JumpIfTrue jumps to the label if the previous comparison was true. +func (f *Function) JumpIfTrue(operator string, label string) { + switch operator { + case "==": + f.assembler.Label(asm.JE, label) + case "!=": + f.assembler.Label(asm.JNE, label) + case ">": + f.assembler.Label(asm.JG, label) + case "<": + f.assembler.Label(asm.JL, label) + case ">=": + f.assembler.Label(asm.JGE, label) + case "<=": + f.assembler.Label(asm.JLE, label) + } +} diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 0b9b584..c73faba 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -9,51 +9,16 @@ import ( // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { - err := f.Evaluate(branch.Condition) + startLabel := fmt.Sprintf("%s_if_start_%d", f.Name, f.count.branch) + endLabel := fmt.Sprintf("%s_if_end_%d", f.Name, f.count.branch) + err := f.CompileCondition(branch.Condition, startLabel, endLabel) if err != nil { return err } - endLabel := fmt.Sprintf("%s_end_if_%d", f.Name, f.count.branch) - f.JumpIfFalse(branch.Condition.Token.Text(), endLabel) + f.assembler.Label(asm.LABEL, startLabel) defer f.assembler.Label(asm.LABEL, endLabel) f.count.branch++ return f.CompileAST(branch.Body) } - -// JumpIfFalse jumps to the label if the previous comparison was false. -func (f *Function) JumpIfFalse(operator string, label string) { - switch operator { - case "==": - f.assembler.Label(asm.JNE, label) - case "!=": - f.assembler.Label(asm.JE, label) - case ">": - f.assembler.Label(asm.JLE, label) - case "<": - f.assembler.Label(asm.JGE, label) - case ">=": - f.assembler.Label(asm.JL, label) - case "<=": - f.assembler.Label(asm.JG, label) - } -} - -// JumpIfTrue jumps to the label if the previous comparison was true. -func (f *Function) JumpIfTrue(operator string, label string) { - switch operator { - case "==": - f.assembler.Label(asm.JE, label) - case "!=": - f.assembler.Label(asm.JNE, label) - case ">": - f.assembler.Label(asm.JG, label) - case "<": - f.assembler.Label(asm.JL, label) - case ">=": - f.assembler.Label(asm.JGE, label) - case "<=": - f.assembler.Label(asm.JLE, label) - } -} diff --git a/src/build/core/Evaluate.go b/src/build/core/Evaluate.go deleted file mode 100644 index 5c3a60c..0000000 --- a/src/build/core/Evaluate.go +++ /dev/null @@ -1,41 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" -) - -// Evaluate evaluates an expression. -func (f *Function) Evaluate(value *expression.Expression) error { - left := value.Children[0] - right := value.Children[1] - - if left.IsLeaf() && left.Token.Kind == token.Identifier { - variable := f.variables[left.Token.Text()] - register := variable.Register - defer f.useVariable(variable) - return f.Execute(value.Token, register, right) - } - - if ast.IsFunctionCall(left) && right.IsLeaf() { - err := f.CompileCall(left) - - if err != nil { - return err - } - - return f.Execute(value.Token, f.cpu.Output[0], right) - } - - tmp := f.cpu.MustFindFree(f.cpu.General) - err := f.ExpressionToRegister(left, tmp) - - if err != nil { - return err - } - - f.cpu.Use(tmp) - defer f.cpu.Free(tmp) - return f.Execute(value.Token, tmp, right) -} diff --git a/src/build/core/state.go b/src/build/core/state.go index 8ae6838..3127eda 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -27,20 +27,20 @@ type counter struct { // PrintInstructions shows the assembly instructions. func (s *state) PrintInstructions() { - ansi.Dim.Println("╭────────────────────────────────────────────────╮") + ansi.Dim.Println("╭────────────────────────────────────────────────────╮") for _, x := range s.assembler.Instructions { ansi.Dim.Print("│ ") switch x.Mnemonic { case asm.LABEL: - ansi.Yellow.Printf("%-46s", x.Data.String()+":") + ansi.Yellow.Printf("%-50s", x.Data.String()+":") case asm.COMMENT: - ansi.Dim.Printf("%-46s", x.Data.String()) + ansi.Dim.Printf("%-50s", x.Data.String()) default: - ansi.Green.Printf("%-8s", x.Mnemonic.String()) + ansi.Green.Printf("%-12s", x.Mnemonic.String()) if x.Data != nil { fmt.Printf("%-38s", x.Data.String()) @@ -52,5 +52,5 @@ func (s *state) PrintInstructions() { ansi.Dim.Print(" │\n") } - ansi.Dim.Println("╰────────────────────────────────────────────────╯") + ansi.Dim.Println("╰────────────────────────────────────────────────────╯") } diff --git a/tests/programs/branch.q b/tests/programs/branch.q index 547fcfc..5a2830e 100644 --- a/tests/programs/branch.q +++ b/tests/programs/branch.q @@ -25,31 +25,15 @@ main() { exit(1) } - if x >= 1 { + if x >= 1 || 1 <= x { exit(1) } - if 1 <= x { + if x == inc(x) || x == dec(x) { exit(1) } - if x == inc(x) { - exit(1) - } - - if x == dec(x) { - exit(1) - } - - if inc(0) == x { - exit(1) - } - - if dec(0) == x { - exit(1) - } - - if inc(x) == dec(x) { + if inc(0) == x || dec(0) == x || inc(x) == dec(x) { exit(1) } From 1c4d2d4f88191d46f63ed6227838b7eaac34c4d4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 8 Jul 2024 13:38:44 +0200 Subject: [PATCH 0257/1012] Implemented boolean operators --- examples/hello/hello.q | 4 +-- src/build/core/CompileCondition.go | 52 +++++++++++++++++++++------ src/build/core/CompileIf.go | 10 +++--- src/build/core/state.go | 5 +-- tests/programs/branch-and.q | 37 +++++++++++++++++++ tests/programs/branch-combined.q | 57 ++++++++++++++++++++++++++++++ tests/programs/branch-or.q | 29 +++++++++++++++ tests/programs/branch.q | 24 ++++++++++--- tests/programs_test.go | 4 +++ 9 files changed, 198 insertions(+), 24 deletions(-) create mode 100644 tests/programs/branch-and.q create mode 100644 tests/programs/branch-combined.q create mode 100644 tests/programs/branch-or.q diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 7cdf32f..43be3f4 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,8 +1,8 @@ main() { x := f(1) + f(2) + f(3) - if x != f(8) || x != 9 || x == 6 { - exit(42) + if x != 9 { + exit(1) } exit(0) diff --git a/src/build/core/CompileCondition.go b/src/build/core/CompileCondition.go index 3a50df7..0365bc4 100644 --- a/src/build/core/CompileCondition.go +++ b/src/build/core/CompileCondition.go @@ -1,44 +1,74 @@ package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/expression" ) // CompileCondition inserts code to jump to the start label or end label depending on the truth of the condition. -func (f *Function) CompileCondition(condition *expression.Expression, startLabel string, endLabel string) error { +func (f *Function) CompileCondition(condition *expression.Expression, successLabel string, failLabel string) error { switch condition.Token.Text() { case "||": + leftFailLabel := fmt.Sprintf("%s_false_%d", f.Name, f.count.subBranch) + f.count.subBranch++ + + // Left left := condition.Children[0] - err := f.CompileCondition(left, startLabel, endLabel) + err := f.CompileCondition(left, successLabel, leftFailLabel) if err != nil { return err } - f.JumpIfTrue(left.Token.Text(), startLabel) + f.JumpIfTrue(left.Token.Text(), successLabel) + // Right + f.assembler.Label(asm.LABEL, leftFailLabel) right := condition.Children[1] - err = f.CompileCondition(right, startLabel, endLabel) + err = f.CompileCondition(right, successLabel, failLabel) + + if condition.Parent != nil && condition.Parent.Token.Text() == "||" && condition != condition.Parent.LastChild() { + f.JumpIfTrue(right.Token.Text(), successLabel) + } else { + f.JumpIfFalse(right.Token.Text(), failLabel) + } + + return err + + case "&&": + leftSuccessLabel := fmt.Sprintf("%s_true_%d", f.Name, f.count.subBranch) + f.count.subBranch++ + + // Left + left := condition.Children[0] + err := f.CompileCondition(left, leftSuccessLabel, failLabel) if err != nil { return err } - if condition.Parent == nil { - f.JumpIfFalse(right.Token.Text(), endLabel) + f.JumpIfFalse(left.Token.Text(), failLabel) + + // Right + f.assembler.Label(asm.LABEL, leftSuccessLabel) + right := condition.Children[1] + err = f.CompileCondition(right, successLabel, failLabel) + + if condition.Parent != nil && condition.Parent.Token.Text() == "||" && condition != condition.Parent.LastChild() { + f.JumpIfTrue(right.Token.Text(), successLabel) } else { - f.JumpIfTrue(right.Token.Text(), startLabel) + f.JumpIfFalse(right.Token.Text(), failLabel) } - return nil - case "&&": - return nil + return err + default: err := f.Compare(condition) if condition.Parent == nil { - f.JumpIfFalse(condition.Token.Text(), endLabel) + f.JumpIfFalse(condition.Token.Text(), failLabel) } return err diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index c73faba..7f0117f 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -9,16 +9,16 @@ import ( // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { - startLabel := fmt.Sprintf("%s_if_start_%d", f.Name, f.count.branch) - endLabel := fmt.Sprintf("%s_if_end_%d", f.Name, f.count.branch) - err := f.CompileCondition(branch.Condition, startLabel, endLabel) + success := fmt.Sprintf("%s_if_%d_true", f.Name, f.count.branch) + fail := fmt.Sprintf("%s_if_%d_false", f.Name, f.count.branch) + err := f.CompileCondition(branch.Condition, success, fail) if err != nil { return err } - f.assembler.Label(asm.LABEL, startLabel) - defer f.assembler.Label(asm.LABEL, endLabel) + f.assembler.Label(asm.LABEL, success) + defer f.assembler.Label(asm.LABEL, fail) f.count.branch++ return f.CompileAST(branch.Body) } diff --git a/src/build/core/state.go b/src/build/core/state.go index 3127eda..c10dc7e 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -21,8 +21,9 @@ type state struct { // counter stores how often a certain statement appeared so we can generate a unique label from it. type counter struct { - loop int - branch int + loop int + branch int + subBranch int } // PrintInstructions shows the assembly instructions. diff --git a/tests/programs/branch-and.q b/tests/programs/branch-and.q new file mode 100644 index 0000000..6761f1e --- /dev/null +++ b/tests/programs/branch-and.q @@ -0,0 +1,37 @@ +main() { + x := 0 + + if x != x && x != x { + exit(1) + } + + if x == x && x != x { + exit(1) + } + + if x != x && x == x { + exit(1) + } + + if x == x && x != x && x != x { + exit(1) + } + + if x != x && x == x && x != x { + exit(1) + } + + if x != x && x != x && x == x { + exit(1) + } + + if x == x && x == x && x == x { + exit(0) + } + + exit(1) +} + +exit(x) { + syscall(60, x) +} \ No newline at end of file diff --git a/tests/programs/branch-combined.q b/tests/programs/branch-combined.q new file mode 100644 index 0000000..3c1f86e --- /dev/null +++ b/tests/programs/branch-combined.q @@ -0,0 +1,57 @@ +main() { + x := 0 + + if x == x && x != x || x != x && x != x { + exit(1) + } + + if x != x && x == x || x != x && x != x { + exit(1) + } + + if x != x && x != x || x == x && x != x { + exit(1) + } + + if x != x && x != x || x != x && x == x { + exit(1) + } + + if (x == x || x != x) && (x != x || x != x) { + exit(1) + } + + if (x != x || x == x) && (x != x || x != x) { + exit(1) + } + + if (x != x || x != x) && (x == x || x != x) { + exit(1) + } + + if (x != x || x != x) && (x != x || x == x) { + exit(1) + } + + if x != x || x != x || x != x || x == x || x != x { + if x + 1 == inc(x) && x - 1 == dec(x) && x == dec(inc(x)) { + if (x == x && x != x || x == x && x == x) && (x == x && x == x || x == x && x != x) { + exit(0) + } + } + } + + exit(1) +} + +exit(x) { + syscall(60, x) +} + +inc(x) { + return x + 1 +} + +dec(x) { + return x - 1 +} \ No newline at end of file diff --git a/tests/programs/branch-or.q b/tests/programs/branch-or.q new file mode 100644 index 0000000..da340a8 --- /dev/null +++ b/tests/programs/branch-or.q @@ -0,0 +1,29 @@ +main() { + x := 0 + + if x != x || x != x { + exit(1) + } + + if x != x || x != x || x != x { + exit(1) + } + + if x == x || x != x { + if x != x || x == x { + if x == x || x != x || x != x { + if x != x || x == x || x != x { + if x != x || x != x || x == x { + exit(0) + } + } + } + } + } + + exit(1) +} + +exit(x) { + syscall(60, x) +} \ No newline at end of file diff --git a/tests/programs/branch.q b/tests/programs/branch.q index 5a2830e..8887013 100644 --- a/tests/programs/branch.q +++ b/tests/programs/branch.q @@ -25,15 +25,15 @@ main() { exit(1) } - if x >= 1 || 1 <= x { + if x >= 1 { exit(1) } - if x == inc(x) || x == dec(x) { + if 1 <= x { exit(1) } - if inc(0) == x || dec(0) == x || inc(x) == dec(x) { + if x + 1 != x + 1 { exit(1) } @@ -41,7 +41,23 @@ main() { exit(1) } - if x + 1 != x + 1 { + if x - 1 != dec(x) { + exit(1) + } + + if inc(x) != x + 1 { + exit(1) + } + + if dec(x) != x - 1 { + exit(1) + } + + if x != inc(dec(x)) { + exit(1) + } + + if inc(dec(x)) != x { exit(1) } diff --git a/tests/programs_test.go b/tests/programs_test.go index feba797..dfd64e8 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -23,6 +23,10 @@ var programs = []struct { {"nested-calls.q", "", 4}, {"return.q", "", 6}, {"reassign.q", "", 2}, + {"branch.q", "", 0}, + {"branch-and.q", "", 0}, + {"branch-or.q", "", 0}, + {"branch-combined.q", "", 0}, } func TestPrograms(t *testing.T) { From b5bb291e47af9ad397e8b1d1f5f3879515c4176a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 8 Jul 2024 13:38:44 +0200 Subject: [PATCH 0258/1012] Implemented boolean operators --- examples/hello/hello.q | 4 +-- src/build/core/CompileCondition.go | 52 +++++++++++++++++++++------ src/build/core/CompileIf.go | 10 +++--- src/build/core/state.go | 5 +-- tests/programs/branch-and.q | 37 +++++++++++++++++++ tests/programs/branch-combined.q | 57 ++++++++++++++++++++++++++++++ tests/programs/branch-or.q | 29 +++++++++++++++ tests/programs/branch.q | 24 ++++++++++--- tests/programs_test.go | 4 +++ 9 files changed, 198 insertions(+), 24 deletions(-) create mode 100644 tests/programs/branch-and.q create mode 100644 tests/programs/branch-combined.q create mode 100644 tests/programs/branch-or.q diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 7cdf32f..43be3f4 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,8 +1,8 @@ main() { x := f(1) + f(2) + f(3) - if x != f(8) || x != 9 || x == 6 { - exit(42) + if x != 9 { + exit(1) } exit(0) diff --git a/src/build/core/CompileCondition.go b/src/build/core/CompileCondition.go index 3a50df7..0365bc4 100644 --- a/src/build/core/CompileCondition.go +++ b/src/build/core/CompileCondition.go @@ -1,44 +1,74 @@ package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/expression" ) // CompileCondition inserts code to jump to the start label or end label depending on the truth of the condition. -func (f *Function) CompileCondition(condition *expression.Expression, startLabel string, endLabel string) error { +func (f *Function) CompileCondition(condition *expression.Expression, successLabel string, failLabel string) error { switch condition.Token.Text() { case "||": + leftFailLabel := fmt.Sprintf("%s_false_%d", f.Name, f.count.subBranch) + f.count.subBranch++ + + // Left left := condition.Children[0] - err := f.CompileCondition(left, startLabel, endLabel) + err := f.CompileCondition(left, successLabel, leftFailLabel) if err != nil { return err } - f.JumpIfTrue(left.Token.Text(), startLabel) + f.JumpIfTrue(left.Token.Text(), successLabel) + // Right + f.assembler.Label(asm.LABEL, leftFailLabel) right := condition.Children[1] - err = f.CompileCondition(right, startLabel, endLabel) + err = f.CompileCondition(right, successLabel, failLabel) + + if condition.Parent != nil && condition.Parent.Token.Text() == "||" && condition != condition.Parent.LastChild() { + f.JumpIfTrue(right.Token.Text(), successLabel) + } else { + f.JumpIfFalse(right.Token.Text(), failLabel) + } + + return err + + case "&&": + leftSuccessLabel := fmt.Sprintf("%s_true_%d", f.Name, f.count.subBranch) + f.count.subBranch++ + + // Left + left := condition.Children[0] + err := f.CompileCondition(left, leftSuccessLabel, failLabel) if err != nil { return err } - if condition.Parent == nil { - f.JumpIfFalse(right.Token.Text(), endLabel) + f.JumpIfFalse(left.Token.Text(), failLabel) + + // Right + f.assembler.Label(asm.LABEL, leftSuccessLabel) + right := condition.Children[1] + err = f.CompileCondition(right, successLabel, failLabel) + + if condition.Parent != nil && condition.Parent.Token.Text() == "||" && condition != condition.Parent.LastChild() { + f.JumpIfTrue(right.Token.Text(), successLabel) } else { - f.JumpIfTrue(right.Token.Text(), startLabel) + f.JumpIfFalse(right.Token.Text(), failLabel) } - return nil - case "&&": - return nil + return err + default: err := f.Compare(condition) if condition.Parent == nil { - f.JumpIfFalse(condition.Token.Text(), endLabel) + f.JumpIfFalse(condition.Token.Text(), failLabel) } return err diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index c73faba..7f0117f 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -9,16 +9,16 @@ import ( // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { - startLabel := fmt.Sprintf("%s_if_start_%d", f.Name, f.count.branch) - endLabel := fmt.Sprintf("%s_if_end_%d", f.Name, f.count.branch) - err := f.CompileCondition(branch.Condition, startLabel, endLabel) + success := fmt.Sprintf("%s_if_%d_true", f.Name, f.count.branch) + fail := fmt.Sprintf("%s_if_%d_false", f.Name, f.count.branch) + err := f.CompileCondition(branch.Condition, success, fail) if err != nil { return err } - f.assembler.Label(asm.LABEL, startLabel) - defer f.assembler.Label(asm.LABEL, endLabel) + f.assembler.Label(asm.LABEL, success) + defer f.assembler.Label(asm.LABEL, fail) f.count.branch++ return f.CompileAST(branch.Body) } diff --git a/src/build/core/state.go b/src/build/core/state.go index 3127eda..c10dc7e 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -21,8 +21,9 @@ type state struct { // counter stores how often a certain statement appeared so we can generate a unique label from it. type counter struct { - loop int - branch int + loop int + branch int + subBranch int } // PrintInstructions shows the assembly instructions. diff --git a/tests/programs/branch-and.q b/tests/programs/branch-and.q new file mode 100644 index 0000000..6761f1e --- /dev/null +++ b/tests/programs/branch-and.q @@ -0,0 +1,37 @@ +main() { + x := 0 + + if x != x && x != x { + exit(1) + } + + if x == x && x != x { + exit(1) + } + + if x != x && x == x { + exit(1) + } + + if x == x && x != x && x != x { + exit(1) + } + + if x != x && x == x && x != x { + exit(1) + } + + if x != x && x != x && x == x { + exit(1) + } + + if x == x && x == x && x == x { + exit(0) + } + + exit(1) +} + +exit(x) { + syscall(60, x) +} \ No newline at end of file diff --git a/tests/programs/branch-combined.q b/tests/programs/branch-combined.q new file mode 100644 index 0000000..3c1f86e --- /dev/null +++ b/tests/programs/branch-combined.q @@ -0,0 +1,57 @@ +main() { + x := 0 + + if x == x && x != x || x != x && x != x { + exit(1) + } + + if x != x && x == x || x != x && x != x { + exit(1) + } + + if x != x && x != x || x == x && x != x { + exit(1) + } + + if x != x && x != x || x != x && x == x { + exit(1) + } + + if (x == x || x != x) && (x != x || x != x) { + exit(1) + } + + if (x != x || x == x) && (x != x || x != x) { + exit(1) + } + + if (x != x || x != x) && (x == x || x != x) { + exit(1) + } + + if (x != x || x != x) && (x != x || x == x) { + exit(1) + } + + if x != x || x != x || x != x || x == x || x != x { + if x + 1 == inc(x) && x - 1 == dec(x) && x == dec(inc(x)) { + if (x == x && x != x || x == x && x == x) && (x == x && x == x || x == x && x != x) { + exit(0) + } + } + } + + exit(1) +} + +exit(x) { + syscall(60, x) +} + +inc(x) { + return x + 1 +} + +dec(x) { + return x - 1 +} \ No newline at end of file diff --git a/tests/programs/branch-or.q b/tests/programs/branch-or.q new file mode 100644 index 0000000..da340a8 --- /dev/null +++ b/tests/programs/branch-or.q @@ -0,0 +1,29 @@ +main() { + x := 0 + + if x != x || x != x { + exit(1) + } + + if x != x || x != x || x != x { + exit(1) + } + + if x == x || x != x { + if x != x || x == x { + if x == x || x != x || x != x { + if x != x || x == x || x != x { + if x != x || x != x || x == x { + exit(0) + } + } + } + } + } + + exit(1) +} + +exit(x) { + syscall(60, x) +} \ No newline at end of file diff --git a/tests/programs/branch.q b/tests/programs/branch.q index 5a2830e..8887013 100644 --- a/tests/programs/branch.q +++ b/tests/programs/branch.q @@ -25,15 +25,15 @@ main() { exit(1) } - if x >= 1 || 1 <= x { + if x >= 1 { exit(1) } - if x == inc(x) || x == dec(x) { + if 1 <= x { exit(1) } - if inc(0) == x || dec(0) == x || inc(x) == dec(x) { + if x + 1 != x + 1 { exit(1) } @@ -41,7 +41,23 @@ main() { exit(1) } - if x + 1 != x + 1 { + if x - 1 != dec(x) { + exit(1) + } + + if inc(x) != x + 1 { + exit(1) + } + + if dec(x) != x - 1 { + exit(1) + } + + if x != inc(dec(x)) { + exit(1) + } + + if inc(dec(x)) != x { exit(1) } diff --git a/tests/programs_test.go b/tests/programs_test.go index feba797..dfd64e8 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -23,6 +23,10 @@ var programs = []struct { {"nested-calls.q", "", 4}, {"return.q", "", 6}, {"reassign.q", "", 2}, + {"branch.q", "", 0}, + {"branch-and.q", "", 0}, + {"branch-or.q", "", 0}, + {"branch-combined.q", "", 0}, } func TestPrograms(t *testing.T) { From 69f70b5b2ff06db35dd528ad4122b4832244b9f5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 8 Jul 2024 13:48:46 +0200 Subject: [PATCH 0259/1012] Simplified branch tests --- tests/programs/branch-combined.q | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/programs/branch-combined.q b/tests/programs/branch-combined.q index 3c1f86e..bb06970 100644 --- a/tests/programs/branch-combined.q +++ b/tests/programs/branch-combined.q @@ -33,11 +33,9 @@ main() { exit(1) } - if x != x || x != x || x != x || x == x || x != x { - if x + 1 == inc(x) && x - 1 == dec(x) && x == dec(inc(x)) { - if (x == x && x != x || x == x && x == x) && (x == x && x == x || x == x && x != x) { - exit(0) - } + if (x == x && x != x || x == x && x == x) && (x == x && x == x || x == x && x != x) { + if (x != x || x == x) && (x != x || x != x) || (x == x || x != x) && (x != x || x == x) { + exit(0) } } From 03a07b646008799c7c4aa014f91c5f4b8c0e918f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 8 Jul 2024 13:48:46 +0200 Subject: [PATCH 0260/1012] Simplified branch tests --- tests/programs/branch-combined.q | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/programs/branch-combined.q b/tests/programs/branch-combined.q index 3c1f86e..bb06970 100644 --- a/tests/programs/branch-combined.q +++ b/tests/programs/branch-combined.q @@ -33,11 +33,9 @@ main() { exit(1) } - if x != x || x != x || x != x || x == x || x != x { - if x + 1 == inc(x) && x - 1 == dec(x) && x == dec(inc(x)) { - if (x == x && x != x || x == x && x == x) && (x == x && x == x || x == x && x != x) { - exit(0) - } + if (x == x && x != x || x == x && x == x) && (x == x && x == x || x == x && x != x) { + if (x != x || x == x) && (x != x || x != x) || (x == x || x != x) && (x != x || x == x) { + exit(0) } } From cbbceedc4df4af1a894f0813cb3f5371eaaba9af Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 8 Jul 2024 14:13:26 +0200 Subject: [PATCH 0261/1012] Removed unused test functions --- tests/programs/branch-combined.q | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/programs/branch-combined.q b/tests/programs/branch-combined.q index bb06970..b8e7029 100644 --- a/tests/programs/branch-combined.q +++ b/tests/programs/branch-combined.q @@ -44,12 +44,4 @@ main() { exit(x) { syscall(60, x) -} - -inc(x) { - return x + 1 -} - -dec(x) { - return x - 1 } \ No newline at end of file From 1c019297d293988430cc6aacd898146c03d9b757 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 8 Jul 2024 14:13:26 +0200 Subject: [PATCH 0262/1012] Removed unused test functions --- tests/programs/branch-combined.q | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/programs/branch-combined.q b/tests/programs/branch-combined.q index bb06970..b8e7029 100644 --- a/tests/programs/branch-combined.q +++ b/tests/programs/branch-combined.q @@ -44,12 +44,4 @@ main() { exit(x) { syscall(60, x) -} - -inc(x) { - return x + 1 -} - -dec(x) { - return x - 1 } \ No newline at end of file From c2b763ab674b4c1bf8a3af9784794d1905373b23 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 9 Jul 2024 10:28:14 +0200 Subject: [PATCH 0263/1012] Added fibonacci example --- examples/fibonacci/fibonacci.q | 11 +++++++ src/build/arch/x64/Registers.go | 1 + src/build/core/CompileCall.go | 10 ++++-- src/build/core/CompileDefinition.go | 2 ++ src/build/core/ExpressionToRegister.go | 3 +- src/build/core/Function.go | 1 + src/build/core/SaveRegister.go | 37 ++++++++++++++++++++++ src/build/cpu/CPU.go | 44 ++++++++++++++++++-------- tests/examples_test.go | 1 + tests/programs/parameters.q | 12 +++++++ tests/programs_test.go | 1 + 11 files changed, 107 insertions(+), 16 deletions(-) create mode 100644 examples/fibonacci/fibonacci.q create mode 100644 src/build/core/SaveRegister.go create mode 100644 tests/programs/parameters.q diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q new file mode 100644 index 0000000..2bedfdb --- /dev/null +++ b/examples/fibonacci/fibonacci.q @@ -0,0 +1,11 @@ +main() { + syscall(60, fibonacci(10)) +} + +fibonacci(x) { + if x == 1 || x == 0 { + return x + } + + return fibonacci(x - 1) + fibonacci(x - 2) +} \ No newline at end of file diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go index 6968338..2d9bd1c 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/build/arch/x64/Registers.go @@ -22,6 +22,7 @@ const ( ) var ( + AllRegisters = []cpu.Register{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15} SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} CallRegisters = SyscallRegisters diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 9c92950..4f063c9 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -29,6 +29,12 @@ func (f *Function) CompileCall(root *expression.Expression) error { registers = f.cpu.Syscall[:len(parameters)] } + for _, register := range f.cpu.Input { + if f.cpu.IsUsed(register) { + f.SaveRegister(register) + } + } + err := f.ExpressionsToRegisters(parameters, registers) if err != nil { @@ -37,7 +43,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { // Push for _, register := range f.cpu.General { - if !f.cpu.IsFree(register) { + if f.cpu.IsUsed(register) { f.assembler.Register(asm.PUSH, register) } } @@ -53,7 +59,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { for i := len(f.cpu.General) - 1; i >= 0; i-- { register := f.cpu.General[i] - if !f.cpu.IsFree(register) { + if f.cpu.IsUsed(register) { f.assembler.Register(asm.POP, register) } } diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index d9daf58..7243d17 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -47,6 +47,7 @@ func (f *Function) AddVariable(variable *Variable) { } f.variables[variable.Name] = variable + f.cpu.Reserve(variable.Register) f.cpu.Use(variable.Register) } @@ -85,6 +86,7 @@ func (f *Function) storeVariableInRegister(name string, value *expression.Expres panic("no free registers") } + f.cpu.Reserve(reg) err := f.ExpressionToRegister(value, reg) f.AddVariable(&Variable{ diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 2f6cf6d..b532757 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -36,6 +36,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp register = f.cpu.MustFindFree(f.cpu.General) } + f.cpu.Reserve(register) err := f.ExpressionToRegister(left, register) if err != nil { @@ -47,8 +48,8 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if register != final { f.assembler.RegisterRegister(asm.MOVE, final, register) + f.cpu.Free(register) } - f.cpu.Free(register) return err } diff --git a/src/build/core/Function.go b/src/build/core/Function.go index e19cf0f..a9d90b8 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -31,6 +31,7 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { Instructions: make([]asm.Instruction, 0, 32), }, cpu: cpu.CPU{ + All: x64.AllRegisters, Input: x64.CallRegisters, General: x64.GeneralRegisters, Syscall: x64.SyscallRegisters, diff --git a/src/build/core/SaveRegister.go b/src/build/core/SaveRegister.go new file mode 100644 index 0000000..95ec385 --- /dev/null +++ b/src/build/core/SaveRegister.go @@ -0,0 +1,37 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// SaveRegister attempts to move a variable occupying this register to another register. +func (f *Function) SaveRegister(register cpu.Register) { + var variable *Variable + + for _, v := range f.variables { + if v.Register == register { + variable = v + break + } + } + + if variable == nil || variable.Alive == 0 { + return + } + + newRegister := f.cpu.MustFindFree(f.cpu.General) + f.cpu.Reserve(newRegister) + f.cpu.Use(newRegister) + + if config.Comments { + f.assembler.Comment(fmt.Sprintf("save %s to %s", register, newRegister)) + } + + f.assembler.RegisterRegister(asm.MOVE, newRegister, register) + f.cpu.Free(register) + variable.Register = newRegister +} diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go index ee028db..c3c5951 100644 --- a/src/build/cpu/CPU.go +++ b/src/build/cpu/CPU.go @@ -2,28 +2,45 @@ package cpu // CPU represents the processor. type CPU struct { - General []Register - Syscall []Register - Input []Register - Output []Register - usage uint64 -} - -func (c *CPU) Use(reg Register) { - c.usage |= (1 << reg) + All []Register + General []Register + Syscall []Register + Input []Register + Output []Register + reserved uint64 + used uint64 } +// Free will reset the reserved and used status which means the register can be allocated again. func (c *CPU) Free(reg Register) { - c.usage &= ^(1 << reg) + c.used &= ^(1 << reg) + c.reserved &= ^(1 << reg) } -func (c *CPU) IsFree(reg Register) bool { - return c.usage&(1< Date: Tue, 9 Jul 2024 10:28:14 +0200 Subject: [PATCH 0264/1012] Added fibonacci example --- examples/fibonacci/fibonacci.q | 11 +++++++ src/build/arch/x64/Registers.go | 1 + src/build/core/CompileCall.go | 10 ++++-- src/build/core/CompileDefinition.go | 2 ++ src/build/core/ExpressionToRegister.go | 3 +- src/build/core/Function.go | 1 + src/build/core/SaveRegister.go | 37 ++++++++++++++++++++++ src/build/cpu/CPU.go | 44 ++++++++++++++++++-------- tests/examples_test.go | 1 + tests/programs/parameters.q | 12 +++++++ tests/programs_test.go | 1 + 11 files changed, 107 insertions(+), 16 deletions(-) create mode 100644 examples/fibonacci/fibonacci.q create mode 100644 src/build/core/SaveRegister.go create mode 100644 tests/programs/parameters.q diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q new file mode 100644 index 0000000..2bedfdb --- /dev/null +++ b/examples/fibonacci/fibonacci.q @@ -0,0 +1,11 @@ +main() { + syscall(60, fibonacci(10)) +} + +fibonacci(x) { + if x == 1 || x == 0 { + return x + } + + return fibonacci(x - 1) + fibonacci(x - 2) +} \ No newline at end of file diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go index 6968338..2d9bd1c 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/build/arch/x64/Registers.go @@ -22,6 +22,7 @@ const ( ) var ( + AllRegisters = []cpu.Register{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15} SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} CallRegisters = SyscallRegisters diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 9c92950..4f063c9 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -29,6 +29,12 @@ func (f *Function) CompileCall(root *expression.Expression) error { registers = f.cpu.Syscall[:len(parameters)] } + for _, register := range f.cpu.Input { + if f.cpu.IsUsed(register) { + f.SaveRegister(register) + } + } + err := f.ExpressionsToRegisters(parameters, registers) if err != nil { @@ -37,7 +43,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { // Push for _, register := range f.cpu.General { - if !f.cpu.IsFree(register) { + if f.cpu.IsUsed(register) { f.assembler.Register(asm.PUSH, register) } } @@ -53,7 +59,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { for i := len(f.cpu.General) - 1; i >= 0; i-- { register := f.cpu.General[i] - if !f.cpu.IsFree(register) { + if f.cpu.IsUsed(register) { f.assembler.Register(asm.POP, register) } } diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index d9daf58..7243d17 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -47,6 +47,7 @@ func (f *Function) AddVariable(variable *Variable) { } f.variables[variable.Name] = variable + f.cpu.Reserve(variable.Register) f.cpu.Use(variable.Register) } @@ -85,6 +86,7 @@ func (f *Function) storeVariableInRegister(name string, value *expression.Expres panic("no free registers") } + f.cpu.Reserve(reg) err := f.ExpressionToRegister(value, reg) f.AddVariable(&Variable{ diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 2f6cf6d..b532757 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -36,6 +36,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp register = f.cpu.MustFindFree(f.cpu.General) } + f.cpu.Reserve(register) err := f.ExpressionToRegister(left, register) if err != nil { @@ -47,8 +48,8 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if register != final { f.assembler.RegisterRegister(asm.MOVE, final, register) + f.cpu.Free(register) } - f.cpu.Free(register) return err } diff --git a/src/build/core/Function.go b/src/build/core/Function.go index e19cf0f..a9d90b8 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -31,6 +31,7 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { Instructions: make([]asm.Instruction, 0, 32), }, cpu: cpu.CPU{ + All: x64.AllRegisters, Input: x64.CallRegisters, General: x64.GeneralRegisters, Syscall: x64.SyscallRegisters, diff --git a/src/build/core/SaveRegister.go b/src/build/core/SaveRegister.go new file mode 100644 index 0000000..95ec385 --- /dev/null +++ b/src/build/core/SaveRegister.go @@ -0,0 +1,37 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// SaveRegister attempts to move a variable occupying this register to another register. +func (f *Function) SaveRegister(register cpu.Register) { + var variable *Variable + + for _, v := range f.variables { + if v.Register == register { + variable = v + break + } + } + + if variable == nil || variable.Alive == 0 { + return + } + + newRegister := f.cpu.MustFindFree(f.cpu.General) + f.cpu.Reserve(newRegister) + f.cpu.Use(newRegister) + + if config.Comments { + f.assembler.Comment(fmt.Sprintf("save %s to %s", register, newRegister)) + } + + f.assembler.RegisterRegister(asm.MOVE, newRegister, register) + f.cpu.Free(register) + variable.Register = newRegister +} diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go index ee028db..c3c5951 100644 --- a/src/build/cpu/CPU.go +++ b/src/build/cpu/CPU.go @@ -2,28 +2,45 @@ package cpu // CPU represents the processor. type CPU struct { - General []Register - Syscall []Register - Input []Register - Output []Register - usage uint64 -} - -func (c *CPU) Use(reg Register) { - c.usage |= (1 << reg) + All []Register + General []Register + Syscall []Register + Input []Register + Output []Register + reserved uint64 + used uint64 } +// Free will reset the reserved and used status which means the register can be allocated again. func (c *CPU) Free(reg Register) { - c.usage &= ^(1 << reg) + c.used &= ^(1 << reg) + c.reserved &= ^(1 << reg) } -func (c *CPU) IsFree(reg Register) bool { - return c.usage&(1< Date: Tue, 9 Jul 2024 10:39:29 +0200 Subject: [PATCH 0265/1012] Added more tests --- tests/programs/reuse.q | 7 +++++++ tests/programs_test.go | 1 + 2 files changed, 8 insertions(+) create mode 100644 tests/programs/reuse.q diff --git a/tests/programs/reuse.q b/tests/programs/reuse.q new file mode 100644 index 0000000..073fd49 --- /dev/null +++ b/tests/programs/reuse.q @@ -0,0 +1,7 @@ +main() { + syscall(60, f(1)) +} + +f(x) { + return x + 1 + x +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index c7d7f1a..c6724ea 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -22,6 +22,7 @@ var programs = []struct { {"chained-calls.q", "", 9}, {"nested-calls.q", "", 4}, {"parameters.q", "", 3}, + {"reuse.q", "", 3}, {"return.q", "", 6}, {"reassign.q", "", 2}, {"branch.q", "", 0}, From 4a7bb9500ac8024ede989c43452c0e9cdfdea9ef Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 9 Jul 2024 10:39:29 +0200 Subject: [PATCH 0266/1012] Added more tests --- tests/programs/reuse.q | 7 +++++++ tests/programs_test.go | 1 + 2 files changed, 8 insertions(+) create mode 100644 tests/programs/reuse.q diff --git a/tests/programs/reuse.q b/tests/programs/reuse.q new file mode 100644 index 0000000..073fd49 --- /dev/null +++ b/tests/programs/reuse.q @@ -0,0 +1,7 @@ +main() { + syscall(60, f(1)) +} + +f(x) { + return x + 1 + x +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index c7d7f1a..c6724ea 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -22,6 +22,7 @@ var programs = []struct { {"chained-calls.q", "", 9}, {"nested-calls.q", "", 4}, {"parameters.q", "", 3}, + {"reuse.q", "", 3}, {"return.q", "", 6}, {"reassign.q", "", 2}, {"branch.q", "", 0}, From 6102ab466a0479390e06a5b10ae824cc29f11133 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 9 Jul 2024 11:43:33 +0200 Subject: [PATCH 0267/1012] Implemented a basic optimization --- src/build/asm/Instructions.go | 4 +++ src/build/asm/Optimizer.go | 34 ++++++++++++++++++++ src/build/core/CompileCall.go | 6 ---- src/build/core/ExecuteRegisterRegister.go | 4 +-- src/build/core/Function.go | 7 ++++ src/build/core/TokenToRegister.go | 5 +-- tests/programs/{parameters.q => overwrite.q} | 0 tests/programs_test.go | 2 +- 8 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 src/build/asm/Optimizer.go rename tests/programs/{parameters.q => overwrite.q} (100%) diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 1adcd9d..57bab73 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -15,6 +15,10 @@ func (a *Assembler) RegisterNumber(mnemonic Mnemonic, reg cpu.Register, number i // RegisterRegister adds an instruction using two registers. func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right cpu.Register) { + if a.unnecessary(mnemonic, left, right) { + return + } + a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, Data: &RegisterRegister{ diff --git a/src/build/asm/Optimizer.go b/src/build/asm/Optimizer.go new file mode 100644 index 0000000..d91daa1 --- /dev/null +++ b/src/build/asm/Optimizer.go @@ -0,0 +1,34 @@ +package asm + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// unnecessary returns true if the register/register operation can be skipped. +func (a *Assembler) unnecessary(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool { + if mnemonic == MOVE && left == right { + return true + } + + if len(a.Instructions) == 0 { + return false + } + + last := a.Instructions[len(a.Instructions)-1] + + if mnemonic == MOVE && last.Mnemonic == MOVE { + data, isRegReg := last.Data.(*RegisterRegister) + + if !isRegReg { + return false + } + + if data.Destination == left && data.Source == right { + return true + } + + if data.Destination == right && data.Source == left { + return true + } + } + + return false +} diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 4f063c9..a70c4b6 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -29,12 +29,6 @@ func (f *Function) CompileCall(root *expression.Expression) error { registers = f.cpu.Syscall[:len(parameters)] } - for _, register := range f.cpu.Input { - if f.cpu.IsUsed(register) { - f.SaveRegister(register) - } - } - err := f.ExpressionsToRegisters(parameters, registers) if err != nil { diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index d10205d..22a6be0 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -26,9 +26,7 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cp f.assembler.RegisterRegister(asm.COMPARE, destination, source) case "=": - if destination != source { - f.assembler.RegisterRegister(asm.MOVE, destination, source) - } + f.assembler.RegisterRegister(asm.MOVE, destination, source) default: return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) diff --git a/src/build/core/Function.go b/src/build/core/Function.go index a9d90b8..4eff587 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -47,6 +47,13 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { func (f *Function) Compile() { defer close(f.finished) f.assembler.Label(asm.LABEL, f.Name) + + for _, register := range f.cpu.Input { + if f.cpu.IsUsed(register) { + f.SaveRegister(register) + } + } + f.err = f.CompileTokens(f.Body) f.assembler.Return() } diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 7a9c428..3e25685 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -21,10 +21,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - if register != variable.Register { - f.assembler.RegisterRegister(asm.MOVE, register, variable.Register) - } - + f.assembler.RegisterRegister(asm.MOVE, register, variable.Register) f.useVariable(variable) return nil diff --git a/tests/programs/parameters.q b/tests/programs/overwrite.q similarity index 100% rename from tests/programs/parameters.q rename to tests/programs/overwrite.q diff --git a/tests/programs_test.go b/tests/programs_test.go index c6724ea..3b494c5 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -21,7 +21,7 @@ var programs = []struct { {"square-sum.q", "", 25}, {"chained-calls.q", "", 9}, {"nested-calls.q", "", 4}, - {"parameters.q", "", 3}, + {"overwrite.q", "", 3}, {"reuse.q", "", 3}, {"return.q", "", 6}, {"reassign.q", "", 2}, From 59d6653eba187165903b926321c13a85612e3448 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 9 Jul 2024 11:43:33 +0200 Subject: [PATCH 0268/1012] Implemented a basic optimization --- src/build/asm/Instructions.go | 4 +++ src/build/asm/Optimizer.go | 34 ++++++++++++++++++++ src/build/core/CompileCall.go | 6 ---- src/build/core/ExecuteRegisterRegister.go | 4 +-- src/build/core/Function.go | 7 ++++ src/build/core/TokenToRegister.go | 5 +-- tests/programs/{parameters.q => overwrite.q} | 0 tests/programs_test.go | 2 +- 8 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 src/build/asm/Optimizer.go rename tests/programs/{parameters.q => overwrite.q} (100%) diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 1adcd9d..57bab73 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -15,6 +15,10 @@ func (a *Assembler) RegisterNumber(mnemonic Mnemonic, reg cpu.Register, number i // RegisterRegister adds an instruction using two registers. func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right cpu.Register) { + if a.unnecessary(mnemonic, left, right) { + return + } + a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, Data: &RegisterRegister{ diff --git a/src/build/asm/Optimizer.go b/src/build/asm/Optimizer.go new file mode 100644 index 0000000..d91daa1 --- /dev/null +++ b/src/build/asm/Optimizer.go @@ -0,0 +1,34 @@ +package asm + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// unnecessary returns true if the register/register operation can be skipped. +func (a *Assembler) unnecessary(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool { + if mnemonic == MOVE && left == right { + return true + } + + if len(a.Instructions) == 0 { + return false + } + + last := a.Instructions[len(a.Instructions)-1] + + if mnemonic == MOVE && last.Mnemonic == MOVE { + data, isRegReg := last.Data.(*RegisterRegister) + + if !isRegReg { + return false + } + + if data.Destination == left && data.Source == right { + return true + } + + if data.Destination == right && data.Source == left { + return true + } + } + + return false +} diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 4f063c9..a70c4b6 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -29,12 +29,6 @@ func (f *Function) CompileCall(root *expression.Expression) error { registers = f.cpu.Syscall[:len(parameters)] } - for _, register := range f.cpu.Input { - if f.cpu.IsUsed(register) { - f.SaveRegister(register) - } - } - err := f.ExpressionsToRegisters(parameters, registers) if err != nil { diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index d10205d..22a6be0 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -26,9 +26,7 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cp f.assembler.RegisterRegister(asm.COMPARE, destination, source) case "=": - if destination != source { - f.assembler.RegisterRegister(asm.MOVE, destination, source) - } + f.assembler.RegisterRegister(asm.MOVE, destination, source) default: return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) diff --git a/src/build/core/Function.go b/src/build/core/Function.go index a9d90b8..4eff587 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -47,6 +47,13 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { func (f *Function) Compile() { defer close(f.finished) f.assembler.Label(asm.LABEL, f.Name) + + for _, register := range f.cpu.Input { + if f.cpu.IsUsed(register) { + f.SaveRegister(register) + } + } + f.err = f.CompileTokens(f.Body) f.assembler.Return() } diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 7a9c428..3e25685 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -21,10 +21,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - if register != variable.Register { - f.assembler.RegisterRegister(asm.MOVE, register, variable.Register) - } - + f.assembler.RegisterRegister(asm.MOVE, register, variable.Register) f.useVariable(variable) return nil diff --git a/tests/programs/parameters.q b/tests/programs/overwrite.q similarity index 100% rename from tests/programs/parameters.q rename to tests/programs/overwrite.q diff --git a/tests/programs_test.go b/tests/programs_test.go index c6724ea..3b494c5 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -21,7 +21,7 @@ var programs = []struct { {"square-sum.q", "", 25}, {"chained-calls.q", "", 9}, {"nested-calls.q", "", 4}, - {"parameters.q", "", 3}, + {"overwrite.q", "", 3}, {"reuse.q", "", 3}, {"return.q", "", 6}, {"reassign.q", "", 2}, From 82f040f7d160280dccf6da57b6f8b51a64056567 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 9 Jul 2024 12:13:59 +0200 Subject: [PATCH 0269/1012] Simplified tests --- tests/programs_test.go | 72 ++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/tests/programs_test.go b/tests/programs_test.go index 3b494c5..5dcc84c 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -15,26 +15,26 @@ var programs = []struct { ExpectedOutput string ExpectedExitCode int }{ - {"empty.q", "", 0}, - {"math.q", "", 10}, - {"precedence.q", "", 10}, - {"square-sum.q", "", 25}, - {"chained-calls.q", "", 9}, - {"nested-calls.q", "", 4}, - {"overwrite.q", "", 3}, - {"reuse.q", "", 3}, - {"return.q", "", 6}, - {"reassign.q", "", 2}, - {"branch.q", "", 0}, - {"branch-and.q", "", 0}, - {"branch-or.q", "", 0}, - {"branch-combined.q", "", 0}, + {"empty", "", 0}, + {"math", "", 10}, + {"precedence", "", 10}, + {"square-sum", "", 25}, + {"chained-calls", "", 9}, + {"nested-calls", "", 4}, + {"overwrite", "", 3}, + {"reuse", "", 3}, + {"return", "", 6}, + {"reassign", "", 2}, + {"branch", "", 0}, + {"branch-and", "", 0}, + {"branch-or", "", 0}, + {"branch-combined", "", 0}, } func TestPrograms(t *testing.T) { for _, test := range programs { t.Run(test.Name, func(t *testing.T) { - run(t, filepath.Join("programs", test.Name), test.ExpectedOutput, test.ExpectedExitCode) + run(t, filepath.Join("programs", test.Name+".q"), test.ExpectedOutput, test.ExpectedExitCode) }) } } @@ -57,34 +57,30 @@ func run(t *testing.T, name string, expectedOutput string, expectedExitCode int) b := build.New(name) assert.True(t, len(b.Executable()) > 0) - t.Run("Compile", func(t *testing.T) { - result, err := b.Run() - assert.Nil(t, err) + result, err := b.Run() + assert.Nil(t, err) - err = result.Write(b.Executable()) - assert.Nil(t, err) + err = result.Write(b.Executable()) + assert.Nil(t, err) - stat, err := os.Stat(b.Executable()) - assert.Nil(t, err) - assert.True(t, stat.Size() > 0) - }) + stat, err := os.Stat(b.Executable()) + assert.Nil(t, err) + assert.True(t, stat.Size() > 0) - t.Run("Output", func(t *testing.T) { - cmd := exec.Command(b.Executable()) - output, err := cmd.Output() - exitCode := 0 + cmd := exec.Command(b.Executable()) + output, err := cmd.Output() + exitCode := 0 - if err != nil { - exitError, ok := err.(*exec.ExitError) + if err != nil { + exitError, ok := err.(*exec.ExitError) - if !ok { - t.Fatal(exitError) - } - - exitCode = exitError.ExitCode() + if !ok { + t.Fatal(exitError) } - assert.Equal(t, exitCode, expectedExitCode) - assert.DeepEqual(t, string(output), expectedOutput) - }) + exitCode = exitError.ExitCode() + } + + assert.Equal(t, exitCode, expectedExitCode) + assert.DeepEqual(t, string(output), expectedOutput) } From 8103faa8b6708716fd6dfc55b9f53f6918977a9a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 9 Jul 2024 12:13:59 +0200 Subject: [PATCH 0270/1012] Simplified tests --- tests/programs_test.go | 72 ++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/tests/programs_test.go b/tests/programs_test.go index 3b494c5..5dcc84c 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -15,26 +15,26 @@ var programs = []struct { ExpectedOutput string ExpectedExitCode int }{ - {"empty.q", "", 0}, - {"math.q", "", 10}, - {"precedence.q", "", 10}, - {"square-sum.q", "", 25}, - {"chained-calls.q", "", 9}, - {"nested-calls.q", "", 4}, - {"overwrite.q", "", 3}, - {"reuse.q", "", 3}, - {"return.q", "", 6}, - {"reassign.q", "", 2}, - {"branch.q", "", 0}, - {"branch-and.q", "", 0}, - {"branch-or.q", "", 0}, - {"branch-combined.q", "", 0}, + {"empty", "", 0}, + {"math", "", 10}, + {"precedence", "", 10}, + {"square-sum", "", 25}, + {"chained-calls", "", 9}, + {"nested-calls", "", 4}, + {"overwrite", "", 3}, + {"reuse", "", 3}, + {"return", "", 6}, + {"reassign", "", 2}, + {"branch", "", 0}, + {"branch-and", "", 0}, + {"branch-or", "", 0}, + {"branch-combined", "", 0}, } func TestPrograms(t *testing.T) { for _, test := range programs { t.Run(test.Name, func(t *testing.T) { - run(t, filepath.Join("programs", test.Name), test.ExpectedOutput, test.ExpectedExitCode) + run(t, filepath.Join("programs", test.Name+".q"), test.ExpectedOutput, test.ExpectedExitCode) }) } } @@ -57,34 +57,30 @@ func run(t *testing.T, name string, expectedOutput string, expectedExitCode int) b := build.New(name) assert.True(t, len(b.Executable()) > 0) - t.Run("Compile", func(t *testing.T) { - result, err := b.Run() - assert.Nil(t, err) + result, err := b.Run() + assert.Nil(t, err) - err = result.Write(b.Executable()) - assert.Nil(t, err) + err = result.Write(b.Executable()) + assert.Nil(t, err) - stat, err := os.Stat(b.Executable()) - assert.Nil(t, err) - assert.True(t, stat.Size() > 0) - }) + stat, err := os.Stat(b.Executable()) + assert.Nil(t, err) + assert.True(t, stat.Size() > 0) - t.Run("Output", func(t *testing.T) { - cmd := exec.Command(b.Executable()) - output, err := cmd.Output() - exitCode := 0 + cmd := exec.Command(b.Executable()) + output, err := cmd.Output() + exitCode := 0 - if err != nil { - exitError, ok := err.(*exec.ExitError) + if err != nil { + exitError, ok := err.(*exec.ExitError) - if !ok { - t.Fatal(exitError) - } - - exitCode = exitError.ExitCode() + if !ok { + t.Fatal(exitError) } - assert.Equal(t, exitCode, expectedExitCode) - assert.DeepEqual(t, string(output), expectedOutput) - }) + exitCode = exitError.ExitCode() + } + + assert.Equal(t, exitCode, expectedExitCode) + assert.DeepEqual(t, string(output), expectedOutput) } From 6e22febc01953d52b94935cd585a3cdb83b53b1c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 9 Jul 2024 12:43:29 +0200 Subject: [PATCH 0271/1012] Improved code generation --- src/build/core/CompileCall.go | 6 ++++++ src/build/core/ExpressionToRegister.go | 2 ++ src/build/core/Function.go | 7 ------- src/build/core/SaveRegister.go | 4 ++++ 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index a70c4b6..ade18b5 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -35,6 +35,12 @@ func (f *Function) CompileCall(root *expression.Expression) error { return err } + f.SaveRegister(f.cpu.Output[0]) + + for _, register := range registers { + f.SaveRegister(register) + } + // Push for _, register := range f.cpu.General { if f.cpu.IsUsed(register) { diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index b532757..580e96f 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -34,6 +34,8 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if f.UsesRegister(right, register) { register = f.cpu.MustFindFree(f.cpu.General) + } else { + f.SaveRegister(register) } f.cpu.Reserve(register) diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 4eff587..a9d90b8 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -47,13 +47,6 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { func (f *Function) Compile() { defer close(f.finished) f.assembler.Label(asm.LABEL, f.Name) - - for _, register := range f.cpu.Input { - if f.cpu.IsUsed(register) { - f.SaveRegister(register) - } - } - f.err = f.CompileTokens(f.Body) f.assembler.Return() } diff --git a/src/build/core/SaveRegister.go b/src/build/core/SaveRegister.go index 95ec385..44c5129 100644 --- a/src/build/core/SaveRegister.go +++ b/src/build/core/SaveRegister.go @@ -10,6 +10,10 @@ import ( // SaveRegister attempts to move a variable occupying this register to another register. func (f *Function) SaveRegister(register cpu.Register) { + if !f.cpu.IsUsed(register) { + return + } + var variable *Variable for _, v := range f.variables { From 1204591cdca2fc43cf27b53239f3eef1c349abc2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 9 Jul 2024 12:43:29 +0200 Subject: [PATCH 0272/1012] Improved code generation --- src/build/core/CompileCall.go | 6 ++++++ src/build/core/ExpressionToRegister.go | 2 ++ src/build/core/Function.go | 7 ------- src/build/core/SaveRegister.go | 4 ++++ 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index a70c4b6..ade18b5 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -35,6 +35,12 @@ func (f *Function) CompileCall(root *expression.Expression) error { return err } + f.SaveRegister(f.cpu.Output[0]) + + for _, register := range registers { + f.SaveRegister(register) + } + // Push for _, register := range f.cpu.General { if f.cpu.IsUsed(register) { diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index b532757..580e96f 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -34,6 +34,8 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if f.UsesRegister(right, register) { register = f.cpu.MustFindFree(f.cpu.General) + } else { + f.SaveRegister(register) } f.cpu.Reserve(register) diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 4eff587..a9d90b8 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -47,13 +47,6 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { func (f *Function) Compile() { defer close(f.finished) f.assembler.Label(asm.LABEL, f.Name) - - for _, register := range f.cpu.Input { - if f.cpu.IsUsed(register) { - f.SaveRegister(register) - } - } - f.err = f.CompileTokens(f.Body) f.assembler.Return() } diff --git a/src/build/core/SaveRegister.go b/src/build/core/SaveRegister.go index 95ec385..44c5129 100644 --- a/src/build/core/SaveRegister.go +++ b/src/build/core/SaveRegister.go @@ -10,6 +10,10 @@ import ( // SaveRegister attempts to move a variable occupying this register to another register. func (f *Function) SaveRegister(register cpu.Register) { + if !f.cpu.IsUsed(register) { + return + } + var variable *Variable for _, v := range f.variables { From 3c189a025fcde21d7a5de4c3891c84f24df94452 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 9 Jul 2024 17:00:04 +0200 Subject: [PATCH 0273/1012] Improved code generation --- src/build/asm/Instructions.go | 10 -- src/build/asm/Optimizer.go | 4 - src/build/core/Compare.go | 1 - src/build/core/CompileCall.go | 12 +-- src/build/core/CompileCondition.go | 28 +++--- src/build/core/CompileDefinition.go | 4 +- src/build/core/CompileIf.go | 5 +- src/build/core/CompileLoop.go | 4 +- src/build/core/CompileReturn.go | 2 +- src/build/core/Execute.go | 1 - src/build/core/ExecuteRegisterNumber.go | 12 +-- src/build/core/ExecuteRegisterRegister.go | 12 +-- src/build/core/ExpressionToRegister.go | 7 +- src/build/core/Function.go | 4 +- src/build/core/Instructions.go | 98 +++++++++++++++++++ src/build/core/SaveRegister.go | 11 ++- src/build/core/TokenToRegister.go | 4 +- src/build/core/state.go | 42 +++++--- src/build/cpu/CPU.go | 16 +-- .../{branch-combined.q => branch-both.q} | 0 tests/programs/param-multi.q | 12 +++ tests/programs/{overwrite.q => param.q} | 0 tests/programs_test.go | 7 +- 23 files changed, 201 insertions(+), 95 deletions(-) create mode 100644 src/build/core/Instructions.go rename tests/programs/{branch-combined.q => branch-both.q} (100%) create mode 100644 tests/programs/param-multi.q rename tests/programs/{overwrite.q => param.q} (100%) diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 57bab73..805a0ea 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -68,16 +68,6 @@ func (a *Assembler) Call(name string) { }) } -// Jump jumps to a position that is identified by a label. -func (a *Assembler) Jump(name string) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: JUMP, - Data: &Label{ - Name: name, - }, - }) -} - // Return returns back to the caller. func (a *Assembler) Return() { if len(a.Instructions) > 0 && a.Instructions[len(a.Instructions)-1].Mnemonic == RETURN { diff --git a/src/build/asm/Optimizer.go b/src/build/asm/Optimizer.go index d91daa1..2ef72ae 100644 --- a/src/build/asm/Optimizer.go +++ b/src/build/asm/Optimizer.go @@ -4,10 +4,6 @@ import "git.akyoto.dev/cli/q/src/build/cpu" // unnecessary returns true if the register/register operation can be skipped. func (a *Assembler) unnecessary(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool { - if mnemonic == MOVE && left == right { - return true - } - if len(a.Instructions) == 0 { return false } diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 955f67e..2e2f29b 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -41,7 +41,6 @@ func (f *Function) Compare(comparison *expression.Expression) error { return err } - f.cpu.Use(tmp) defer f.cpu.Free(tmp) return f.Execute(comparison.Token, tmp, right) } diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index ade18b5..6ed36de 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -37,22 +37,18 @@ func (f *Function) CompileCall(root *expression.Expression) error { f.SaveRegister(f.cpu.Output[0]) - for _, register := range registers { - f.SaveRegister(register) - } - // Push for _, register := range f.cpu.General { if f.cpu.IsUsed(register) { - f.assembler.Register(asm.PUSH, register) + f.Register(asm.PUSH, register) } } // Call if isSyscall { - f.assembler.Syscall() + f.Syscall() } else { - f.assembler.Call(funcName) + f.Call(funcName) } // Pop @@ -60,7 +56,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { register := f.cpu.General[i] if f.cpu.IsUsed(register) { - f.assembler.Register(asm.POP, register) + f.Register(asm.POP, register) } } diff --git a/src/build/core/CompileCondition.go b/src/build/core/CompileCondition.go index 0365bc4..84c4a38 100644 --- a/src/build/core/CompileCondition.go +++ b/src/build/core/CompileCondition.go @@ -25,7 +25,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab f.JumpIfTrue(left.Token.Text(), successLabel) // Right - f.assembler.Label(asm.LABEL, leftFailLabel) + f.AddLabel(leftFailLabel) right := condition.Children[1] err = f.CompileCondition(right, successLabel, failLabel) @@ -52,7 +52,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab f.JumpIfFalse(left.Token.Text(), failLabel) // Right - f.assembler.Label(asm.LABEL, leftSuccessLabel) + f.AddLabel(leftSuccessLabel) right := condition.Children[1] err = f.CompileCondition(right, successLabel, failLabel) @@ -79,17 +79,17 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab func (f *Function) JumpIfFalse(operator string, label string) { switch operator { case "==": - f.assembler.Label(asm.JNE, label) + f.Jump(asm.JNE, label) case "!=": - f.assembler.Label(asm.JE, label) + f.Jump(asm.JE, label) case ">": - f.assembler.Label(asm.JLE, label) + f.Jump(asm.JLE, label) case "<": - f.assembler.Label(asm.JGE, label) + f.Jump(asm.JGE, label) case ">=": - f.assembler.Label(asm.JL, label) + f.Jump(asm.JL, label) case "<=": - f.assembler.Label(asm.JG, label) + f.Jump(asm.JG, label) } } @@ -97,16 +97,16 @@ func (f *Function) JumpIfFalse(operator string, label string) { func (f *Function) JumpIfTrue(operator string, label string) { switch operator { case "==": - f.assembler.Label(asm.JE, label) + f.Jump(asm.JE, label) case "!=": - f.assembler.Label(asm.JNE, label) + f.Jump(asm.JNE, label) case ">": - f.assembler.Label(asm.JG, label) + f.Jump(asm.JG, label) case "<": - f.assembler.Label(asm.JL, label) + f.Jump(asm.JL, label) case ">=": - f.assembler.Label(asm.JGE, label) + f.Jump(asm.JGE, label) case "<=": - f.assembler.Label(asm.JLE, label) + f.Jump(asm.JLE, label) } } diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 7243d17..f2fd234 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -43,7 +43,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { func (f *Function) AddVariable(variable *Variable) { if config.Comments { - f.assembler.Comment(fmt.Sprintf("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive)) + f.Comment(fmt.Sprintf("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive)) } f.variables[variable.Name] = variable @@ -60,7 +60,7 @@ func (f *Function) useVariable(variable *Variable) { if variable.Alive == 0 { if config.Comments { - f.assembler.Comment(fmt.Sprintf("%s died (%s)", variable.Name, variable.Register)) + f.Comment(fmt.Sprintf("%s died (%s)", variable.Name, variable.Register)) } f.cpu.Free(variable.Register) diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 7f0117f..7721bf6 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -3,7 +3,6 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" ) @@ -17,8 +16,8 @@ func (f *Function) CompileIf(branch *ast.If) error { return err } - f.assembler.Label(asm.LABEL, success) - defer f.assembler.Label(asm.LABEL, fail) + f.AddLabel(success) + defer f.AddLabel(fail) f.count.branch++ return f.CompileAST(branch.Body) } diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index b8fd1d8..7bf00c4 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -10,8 +10,8 @@ import ( // CompileLoop compiles a loop instruction. func (f *Function) CompileLoop(loop *ast.Loop) error { label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) - f.assembler.Label(asm.LABEL, label) - defer f.assembler.Jump(label) + f.AddLabel(label) + defer f.Jump(asm.JUMP, label) f.count.loop++ return f.CompileAST(loop.Body) } diff --git a/src/build/core/CompileReturn.go b/src/build/core/CompileReturn.go index 4effe9a..57219c4 100644 --- a/src/build/core/CompileReturn.go +++ b/src/build/core/CompileReturn.go @@ -6,7 +6,7 @@ import ( // CompileReturn compiles a return instruction. func (f *Function) CompileReturn(node *ast.Return) error { - defer f.assembler.Return() + defer f.Return() if node.Value == nil { return nil diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index 36d7271..b401dab 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -24,7 +24,6 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * } tmp := f.cpu.MustFindFree(f.cpu.General) - f.cpu.Use(tmp) defer f.cpu.Free(tmp) err := f.ExpressionToRegister(value, tmp) diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index c85ac70..19b07ea 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -11,22 +11,22 @@ import ( func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { switch operation.Text() { case "+", "+=": - f.assembler.RegisterNumber(asm.ADD, register, number) + f.RegisterNumber(asm.ADD, register, number) case "-", "-=": - f.assembler.RegisterNumber(asm.SUB, register, number) + f.RegisterNumber(asm.SUB, register, number) case "*", "*=": - f.assembler.RegisterNumber(asm.MUL, register, number) + f.RegisterNumber(asm.MUL, register, number) case "/", "/=": - f.assembler.RegisterNumber(asm.DIV, register, number) + f.RegisterNumber(asm.DIV, register, number) case "==", "!=", "<", "<=", ">", ">=": - f.assembler.RegisterNumber(asm.COMPARE, register, number) + f.RegisterNumber(asm.COMPARE, register, number) case "=": - f.assembler.RegisterNumber(asm.MOVE, register, number) + f.RegisterNumber(asm.MOVE, register, number) default: return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index 22a6be0..c163e23 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -11,22 +11,22 @@ import ( func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { switch operation.Text() { case "+", "+=": - f.assembler.RegisterRegister(asm.ADD, destination, source) + f.RegisterRegister(asm.ADD, destination, source) case "-", "-=": - f.assembler.RegisterRegister(asm.SUB, destination, source) + f.RegisterRegister(asm.SUB, destination, source) case "*", "*=": - f.assembler.RegisterRegister(asm.MUL, destination, source) + f.RegisterRegister(asm.MUL, destination, source) case "/", "/=": - f.assembler.RegisterRegister(asm.DIV, destination, source) + f.RegisterRegister(asm.DIV, destination, source) case "==", "!=", "<", "<=", ">", ">=": - f.assembler.RegisterRegister(asm.COMPARE, destination, source) + f.RegisterRegister(asm.COMPARE, destination, source) case "=": - f.assembler.RegisterRegister(asm.MOVE, destination, source) + f.RegisterRegister(asm.MOVE, destination, source) default: return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 580e96f..793403c 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -18,7 +18,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp err := f.CompileCall(node) if register != f.cpu.Output[0] { - f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Output[0]) + f.RegisterRegister(asm.MOVE, register, f.cpu.Output[0]) } return err @@ -34,8 +34,6 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if f.UsesRegister(right, register) { register = f.cpu.MustFindFree(f.cpu.General) - } else { - f.SaveRegister(register) } f.cpu.Reserve(register) @@ -45,11 +43,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return err } - f.cpu.Use(register) err = f.Execute(node.Token, register, right) if register != final { - f.assembler.RegisterRegister(asm.MOVE, final, register) + f.RegisterRegister(asm.MOVE, final, register) f.cpu.Free(register) } diff --git a/src/build/core/Function.go b/src/build/core/Function.go index a9d90b8..0af8d06 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -46,9 +46,9 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { // Compile turns a function into machine code. func (f *Function) Compile() { defer close(f.finished) - f.assembler.Label(asm.LABEL, f.Name) + f.AddLabel(f.Name) f.err = f.CompileTokens(f.Body) - f.assembler.Return() + f.Return() } // CompileTokens compiles a token list. diff --git a/src/build/core/Instructions.go b/src/build/core/Instructions.go new file mode 100644 index 0000000..d9bae49 --- /dev/null +++ b/src/build/core/Instructions.go @@ -0,0 +1,98 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/cpu" +) + +func (f *Function) AddLabel(label string) { + f.assembler.Label(asm.LABEL, label) + f.postInstruction() +} + +func (f *Function) Call(label string) { + f.assembler.Call(label) + f.cpu.Use(f.cpu.Output[0]) + f.postInstruction() +} + +func (f *Function) Comment(comment string) { + f.assembler.Comment(comment) + f.postInstruction() +} + +func (f *Function) Jump(mnemonic asm.Mnemonic, label string) { + f.assembler.Label(mnemonic, label) + f.postInstruction() +} + +func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) { + f.assembler.Register(mnemonic, a) + + if mnemonic == asm.POP { + f.cpu.Use(a) + } + + f.postInstruction() +} + +func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { + if f.cpu.IsUsed(a) && isDestructive(mnemonic) { + f.SaveRegister(a) + } + + f.assembler.RegisterNumber(mnemonic, a, b) + + if mnemonic == asm.MOVE { + f.cpu.Use(a) + } + + f.postInstruction() +} + +func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) { + if mnemonic == asm.MOVE && a == b { + return + } + + if f.cpu.IsUsed(a) && isDestructive(mnemonic) { + f.SaveRegister(a) + } + + f.assembler.RegisterRegister(mnemonic, a, b) + + if mnemonic == asm.MOVE { + f.cpu.Use(a) + } + + f.postInstruction() +} + +func (f *Function) Return() { + f.assembler.Return() + f.postInstruction() +} + +func (f *Function) Syscall() { + f.assembler.Syscall() + f.cpu.Use(f.cpu.Output[0]) + f.postInstruction() +} + +func (f *Function) postInstruction() { + if !config.Assembler { + return + } + + f.registerHistory = append(f.registerHistory, f.cpu.Used) +} + +func isDestructive(mnemonic asm.Mnemonic) bool { + switch mnemonic { + case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV: + return true + default: + return false + } +} diff --git a/src/build/core/SaveRegister.go b/src/build/core/SaveRegister.go index 44c5129..17f16cb 100644 --- a/src/build/core/SaveRegister.go +++ b/src/build/core/SaveRegister.go @@ -14,6 +14,12 @@ func (f *Function) SaveRegister(register cpu.Register) { return } + for _, general := range f.cpu.General { + if register == general { + return + } + } + var variable *Variable for _, v := range f.variables { @@ -29,13 +35,12 @@ func (f *Function) SaveRegister(register cpu.Register) { newRegister := f.cpu.MustFindFree(f.cpu.General) f.cpu.Reserve(newRegister) - f.cpu.Use(newRegister) if config.Comments { - f.assembler.Comment(fmt.Sprintf("save %s to %s", register, newRegister)) + f.Comment(fmt.Sprintf("save %s to %s", register, newRegister)) } - f.assembler.RegisterRegister(asm.MOVE, newRegister, register) + f.RegisterRegister(asm.MOVE, newRegister, register) f.cpu.Free(register) variable.Register = newRegister } diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 3e25685..bbc3808 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -21,8 +21,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - f.assembler.RegisterRegister(asm.MOVE, register, variable.Register) f.useVariable(variable) + f.RegisterRegister(asm.MOVE, register, variable.Register) return nil case token.Number: @@ -33,7 +33,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return err } - f.assembler.RegisterNumber(asm.MOVE, register, n) + f.RegisterNumber(asm.MOVE, register, n) return nil case token.String: diff --git a/src/build/core/state.go b/src/build/core/state.go index c10dc7e..e185533 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -1,6 +1,7 @@ package core import ( + "bytes" "fmt" "git.akyoto.dev/cli/q/src/build/asm" @@ -10,13 +11,14 @@ import ( // state is the data structure we embed in each function to preserve compilation state. type state struct { - err error - variables map[string]*Variable - functions map[string]*Function - finished chan struct{} - assembler asm.Assembler - cpu cpu.CPU - count counter + err error + variables map[string]*Variable + functions map[string]*Function + registerHistory []uint64 + finished chan struct{} + assembler asm.Assembler + cpu cpu.CPU + count counter } // counter stores how often a certain statement appeared so we can generate a unique label from it. @@ -28,30 +30,42 @@ type counter struct { // PrintInstructions shows the assembly instructions. func (s *state) PrintInstructions() { - ansi.Dim.Println("╭────────────────────────────────────────────────────╮") + ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮") - for _, x := range s.assembler.Instructions { + for i, x := range s.assembler.Instructions { ansi.Dim.Print("│ ") switch x.Mnemonic { case asm.LABEL: - ansi.Yellow.Printf("%-50s", x.Data.String()+":") + ansi.Yellow.Printf("%-44s", x.Data.String()+":") case asm.COMMENT: - ansi.Dim.Printf("%-50s", x.Data.String()) + ansi.Dim.Printf("%-44s", x.Data.String()) default: ansi.Green.Printf("%-12s", x.Mnemonic.String()) if x.Data != nil { - fmt.Printf("%-38s", x.Data.String()) + fmt.Printf("%-32s", x.Data.String()) } else { - fmt.Printf("%-38s", "") + fmt.Printf("%-32s", "") } } + registers := bytes.Buffer{} + used := s.registerHistory[i] + + for _, reg := range s.cpu.All { + if used&(1< Date: Tue, 9 Jul 2024 17:00:04 +0200 Subject: [PATCH 0274/1012] Improved code generation --- src/build/asm/Instructions.go | 10 -- src/build/asm/Optimizer.go | 4 - src/build/core/Compare.go | 1 - src/build/core/CompileCall.go | 12 +-- src/build/core/CompileCondition.go | 28 +++--- src/build/core/CompileDefinition.go | 4 +- src/build/core/CompileIf.go | 5 +- src/build/core/CompileLoop.go | 4 +- src/build/core/CompileReturn.go | 2 +- src/build/core/Execute.go | 1 - src/build/core/ExecuteRegisterNumber.go | 12 +-- src/build/core/ExecuteRegisterRegister.go | 12 +-- src/build/core/ExpressionToRegister.go | 7 +- src/build/core/Function.go | 4 +- src/build/core/Instructions.go | 98 +++++++++++++++++++ src/build/core/SaveRegister.go | 11 ++- src/build/core/TokenToRegister.go | 4 +- src/build/core/state.go | 42 +++++--- src/build/cpu/CPU.go | 16 +-- .../{branch-combined.q => branch-both.q} | 0 tests/programs/param-multi.q | 12 +++ tests/programs/{overwrite.q => param.q} | 0 tests/programs_test.go | 7 +- 23 files changed, 201 insertions(+), 95 deletions(-) create mode 100644 src/build/core/Instructions.go rename tests/programs/{branch-combined.q => branch-both.q} (100%) create mode 100644 tests/programs/param-multi.q rename tests/programs/{overwrite.q => param.q} (100%) diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 57bab73..805a0ea 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -68,16 +68,6 @@ func (a *Assembler) Call(name string) { }) } -// Jump jumps to a position that is identified by a label. -func (a *Assembler) Jump(name string) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: JUMP, - Data: &Label{ - Name: name, - }, - }) -} - // Return returns back to the caller. func (a *Assembler) Return() { if len(a.Instructions) > 0 && a.Instructions[len(a.Instructions)-1].Mnemonic == RETURN { diff --git a/src/build/asm/Optimizer.go b/src/build/asm/Optimizer.go index d91daa1..2ef72ae 100644 --- a/src/build/asm/Optimizer.go +++ b/src/build/asm/Optimizer.go @@ -4,10 +4,6 @@ import "git.akyoto.dev/cli/q/src/build/cpu" // unnecessary returns true if the register/register operation can be skipped. func (a *Assembler) unnecessary(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool { - if mnemonic == MOVE && left == right { - return true - } - if len(a.Instructions) == 0 { return false } diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 955f67e..2e2f29b 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -41,7 +41,6 @@ func (f *Function) Compare(comparison *expression.Expression) error { return err } - f.cpu.Use(tmp) defer f.cpu.Free(tmp) return f.Execute(comparison.Token, tmp, right) } diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index ade18b5..6ed36de 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -37,22 +37,18 @@ func (f *Function) CompileCall(root *expression.Expression) error { f.SaveRegister(f.cpu.Output[0]) - for _, register := range registers { - f.SaveRegister(register) - } - // Push for _, register := range f.cpu.General { if f.cpu.IsUsed(register) { - f.assembler.Register(asm.PUSH, register) + f.Register(asm.PUSH, register) } } // Call if isSyscall { - f.assembler.Syscall() + f.Syscall() } else { - f.assembler.Call(funcName) + f.Call(funcName) } // Pop @@ -60,7 +56,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { register := f.cpu.General[i] if f.cpu.IsUsed(register) { - f.assembler.Register(asm.POP, register) + f.Register(asm.POP, register) } } diff --git a/src/build/core/CompileCondition.go b/src/build/core/CompileCondition.go index 0365bc4..84c4a38 100644 --- a/src/build/core/CompileCondition.go +++ b/src/build/core/CompileCondition.go @@ -25,7 +25,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab f.JumpIfTrue(left.Token.Text(), successLabel) // Right - f.assembler.Label(asm.LABEL, leftFailLabel) + f.AddLabel(leftFailLabel) right := condition.Children[1] err = f.CompileCondition(right, successLabel, failLabel) @@ -52,7 +52,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab f.JumpIfFalse(left.Token.Text(), failLabel) // Right - f.assembler.Label(asm.LABEL, leftSuccessLabel) + f.AddLabel(leftSuccessLabel) right := condition.Children[1] err = f.CompileCondition(right, successLabel, failLabel) @@ -79,17 +79,17 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab func (f *Function) JumpIfFalse(operator string, label string) { switch operator { case "==": - f.assembler.Label(asm.JNE, label) + f.Jump(asm.JNE, label) case "!=": - f.assembler.Label(asm.JE, label) + f.Jump(asm.JE, label) case ">": - f.assembler.Label(asm.JLE, label) + f.Jump(asm.JLE, label) case "<": - f.assembler.Label(asm.JGE, label) + f.Jump(asm.JGE, label) case ">=": - f.assembler.Label(asm.JL, label) + f.Jump(asm.JL, label) case "<=": - f.assembler.Label(asm.JG, label) + f.Jump(asm.JG, label) } } @@ -97,16 +97,16 @@ func (f *Function) JumpIfFalse(operator string, label string) { func (f *Function) JumpIfTrue(operator string, label string) { switch operator { case "==": - f.assembler.Label(asm.JE, label) + f.Jump(asm.JE, label) case "!=": - f.assembler.Label(asm.JNE, label) + f.Jump(asm.JNE, label) case ">": - f.assembler.Label(asm.JG, label) + f.Jump(asm.JG, label) case "<": - f.assembler.Label(asm.JL, label) + f.Jump(asm.JL, label) case ">=": - f.assembler.Label(asm.JGE, label) + f.Jump(asm.JGE, label) case "<=": - f.assembler.Label(asm.JLE, label) + f.Jump(asm.JLE, label) } } diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 7243d17..f2fd234 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -43,7 +43,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { func (f *Function) AddVariable(variable *Variable) { if config.Comments { - f.assembler.Comment(fmt.Sprintf("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive)) + f.Comment(fmt.Sprintf("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive)) } f.variables[variable.Name] = variable @@ -60,7 +60,7 @@ func (f *Function) useVariable(variable *Variable) { if variable.Alive == 0 { if config.Comments { - f.assembler.Comment(fmt.Sprintf("%s died (%s)", variable.Name, variable.Register)) + f.Comment(fmt.Sprintf("%s died (%s)", variable.Name, variable.Register)) } f.cpu.Free(variable.Register) diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 7f0117f..7721bf6 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -3,7 +3,6 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" ) @@ -17,8 +16,8 @@ func (f *Function) CompileIf(branch *ast.If) error { return err } - f.assembler.Label(asm.LABEL, success) - defer f.assembler.Label(asm.LABEL, fail) + f.AddLabel(success) + defer f.AddLabel(fail) f.count.branch++ return f.CompileAST(branch.Body) } diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index b8fd1d8..7bf00c4 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -10,8 +10,8 @@ import ( // CompileLoop compiles a loop instruction. func (f *Function) CompileLoop(loop *ast.Loop) error { label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) - f.assembler.Label(asm.LABEL, label) - defer f.assembler.Jump(label) + f.AddLabel(label) + defer f.Jump(asm.JUMP, label) f.count.loop++ return f.CompileAST(loop.Body) } diff --git a/src/build/core/CompileReturn.go b/src/build/core/CompileReturn.go index 4effe9a..57219c4 100644 --- a/src/build/core/CompileReturn.go +++ b/src/build/core/CompileReturn.go @@ -6,7 +6,7 @@ import ( // CompileReturn compiles a return instruction. func (f *Function) CompileReturn(node *ast.Return) error { - defer f.assembler.Return() + defer f.Return() if node.Value == nil { return nil diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index 36d7271..b401dab 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -24,7 +24,6 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * } tmp := f.cpu.MustFindFree(f.cpu.General) - f.cpu.Use(tmp) defer f.cpu.Free(tmp) err := f.ExpressionToRegister(value, tmp) diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index c85ac70..19b07ea 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -11,22 +11,22 @@ import ( func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { switch operation.Text() { case "+", "+=": - f.assembler.RegisterNumber(asm.ADD, register, number) + f.RegisterNumber(asm.ADD, register, number) case "-", "-=": - f.assembler.RegisterNumber(asm.SUB, register, number) + f.RegisterNumber(asm.SUB, register, number) case "*", "*=": - f.assembler.RegisterNumber(asm.MUL, register, number) + f.RegisterNumber(asm.MUL, register, number) case "/", "/=": - f.assembler.RegisterNumber(asm.DIV, register, number) + f.RegisterNumber(asm.DIV, register, number) case "==", "!=", "<", "<=", ">", ">=": - f.assembler.RegisterNumber(asm.COMPARE, register, number) + f.RegisterNumber(asm.COMPARE, register, number) case "=": - f.assembler.RegisterNumber(asm.MOVE, register, number) + f.RegisterNumber(asm.MOVE, register, number) default: return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index 22a6be0..c163e23 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -11,22 +11,22 @@ import ( func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { switch operation.Text() { case "+", "+=": - f.assembler.RegisterRegister(asm.ADD, destination, source) + f.RegisterRegister(asm.ADD, destination, source) case "-", "-=": - f.assembler.RegisterRegister(asm.SUB, destination, source) + f.RegisterRegister(asm.SUB, destination, source) case "*", "*=": - f.assembler.RegisterRegister(asm.MUL, destination, source) + f.RegisterRegister(asm.MUL, destination, source) case "/", "/=": - f.assembler.RegisterRegister(asm.DIV, destination, source) + f.RegisterRegister(asm.DIV, destination, source) case "==", "!=", "<", "<=", ">", ">=": - f.assembler.RegisterRegister(asm.COMPARE, destination, source) + f.RegisterRegister(asm.COMPARE, destination, source) case "=": - f.assembler.RegisterRegister(asm.MOVE, destination, source) + f.RegisterRegister(asm.MOVE, destination, source) default: return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 580e96f..793403c 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -18,7 +18,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp err := f.CompileCall(node) if register != f.cpu.Output[0] { - f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Output[0]) + f.RegisterRegister(asm.MOVE, register, f.cpu.Output[0]) } return err @@ -34,8 +34,6 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if f.UsesRegister(right, register) { register = f.cpu.MustFindFree(f.cpu.General) - } else { - f.SaveRegister(register) } f.cpu.Reserve(register) @@ -45,11 +43,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return err } - f.cpu.Use(register) err = f.Execute(node.Token, register, right) if register != final { - f.assembler.RegisterRegister(asm.MOVE, final, register) + f.RegisterRegister(asm.MOVE, final, register) f.cpu.Free(register) } diff --git a/src/build/core/Function.go b/src/build/core/Function.go index a9d90b8..0af8d06 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -46,9 +46,9 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { // Compile turns a function into machine code. func (f *Function) Compile() { defer close(f.finished) - f.assembler.Label(asm.LABEL, f.Name) + f.AddLabel(f.Name) f.err = f.CompileTokens(f.Body) - f.assembler.Return() + f.Return() } // CompileTokens compiles a token list. diff --git a/src/build/core/Instructions.go b/src/build/core/Instructions.go new file mode 100644 index 0000000..d9bae49 --- /dev/null +++ b/src/build/core/Instructions.go @@ -0,0 +1,98 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/cpu" +) + +func (f *Function) AddLabel(label string) { + f.assembler.Label(asm.LABEL, label) + f.postInstruction() +} + +func (f *Function) Call(label string) { + f.assembler.Call(label) + f.cpu.Use(f.cpu.Output[0]) + f.postInstruction() +} + +func (f *Function) Comment(comment string) { + f.assembler.Comment(comment) + f.postInstruction() +} + +func (f *Function) Jump(mnemonic asm.Mnemonic, label string) { + f.assembler.Label(mnemonic, label) + f.postInstruction() +} + +func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) { + f.assembler.Register(mnemonic, a) + + if mnemonic == asm.POP { + f.cpu.Use(a) + } + + f.postInstruction() +} + +func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { + if f.cpu.IsUsed(a) && isDestructive(mnemonic) { + f.SaveRegister(a) + } + + f.assembler.RegisterNumber(mnemonic, a, b) + + if mnemonic == asm.MOVE { + f.cpu.Use(a) + } + + f.postInstruction() +} + +func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) { + if mnemonic == asm.MOVE && a == b { + return + } + + if f.cpu.IsUsed(a) && isDestructive(mnemonic) { + f.SaveRegister(a) + } + + f.assembler.RegisterRegister(mnemonic, a, b) + + if mnemonic == asm.MOVE { + f.cpu.Use(a) + } + + f.postInstruction() +} + +func (f *Function) Return() { + f.assembler.Return() + f.postInstruction() +} + +func (f *Function) Syscall() { + f.assembler.Syscall() + f.cpu.Use(f.cpu.Output[0]) + f.postInstruction() +} + +func (f *Function) postInstruction() { + if !config.Assembler { + return + } + + f.registerHistory = append(f.registerHistory, f.cpu.Used) +} + +func isDestructive(mnemonic asm.Mnemonic) bool { + switch mnemonic { + case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV: + return true + default: + return false + } +} diff --git a/src/build/core/SaveRegister.go b/src/build/core/SaveRegister.go index 44c5129..17f16cb 100644 --- a/src/build/core/SaveRegister.go +++ b/src/build/core/SaveRegister.go @@ -14,6 +14,12 @@ func (f *Function) SaveRegister(register cpu.Register) { return } + for _, general := range f.cpu.General { + if register == general { + return + } + } + var variable *Variable for _, v := range f.variables { @@ -29,13 +35,12 @@ func (f *Function) SaveRegister(register cpu.Register) { newRegister := f.cpu.MustFindFree(f.cpu.General) f.cpu.Reserve(newRegister) - f.cpu.Use(newRegister) if config.Comments { - f.assembler.Comment(fmt.Sprintf("save %s to %s", register, newRegister)) + f.Comment(fmt.Sprintf("save %s to %s", register, newRegister)) } - f.assembler.RegisterRegister(asm.MOVE, newRegister, register) + f.RegisterRegister(asm.MOVE, newRegister, register) f.cpu.Free(register) variable.Register = newRegister } diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 3e25685..bbc3808 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -21,8 +21,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - f.assembler.RegisterRegister(asm.MOVE, register, variable.Register) f.useVariable(variable) + f.RegisterRegister(asm.MOVE, register, variable.Register) return nil case token.Number: @@ -33,7 +33,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return err } - f.assembler.RegisterNumber(asm.MOVE, register, n) + f.RegisterNumber(asm.MOVE, register, n) return nil case token.String: diff --git a/src/build/core/state.go b/src/build/core/state.go index c10dc7e..e185533 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -1,6 +1,7 @@ package core import ( + "bytes" "fmt" "git.akyoto.dev/cli/q/src/build/asm" @@ -10,13 +11,14 @@ import ( // state is the data structure we embed in each function to preserve compilation state. type state struct { - err error - variables map[string]*Variable - functions map[string]*Function - finished chan struct{} - assembler asm.Assembler - cpu cpu.CPU - count counter + err error + variables map[string]*Variable + functions map[string]*Function + registerHistory []uint64 + finished chan struct{} + assembler asm.Assembler + cpu cpu.CPU + count counter } // counter stores how often a certain statement appeared so we can generate a unique label from it. @@ -28,30 +30,42 @@ type counter struct { // PrintInstructions shows the assembly instructions. func (s *state) PrintInstructions() { - ansi.Dim.Println("╭────────────────────────────────────────────────────╮") + ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮") - for _, x := range s.assembler.Instructions { + for i, x := range s.assembler.Instructions { ansi.Dim.Print("│ ") switch x.Mnemonic { case asm.LABEL: - ansi.Yellow.Printf("%-50s", x.Data.String()+":") + ansi.Yellow.Printf("%-44s", x.Data.String()+":") case asm.COMMENT: - ansi.Dim.Printf("%-50s", x.Data.String()) + ansi.Dim.Printf("%-44s", x.Data.String()) default: ansi.Green.Printf("%-12s", x.Mnemonic.String()) if x.Data != nil { - fmt.Printf("%-38s", x.Data.String()) + fmt.Printf("%-32s", x.Data.String()) } else { - fmt.Printf("%-38s", "") + fmt.Printf("%-32s", "") } } + registers := bytes.Buffer{} + used := s.registerHistory[i] + + for _, reg := range s.cpu.All { + if used&(1< Date: Wed, 10 Jul 2024 10:48:15 +0200 Subject: [PATCH 0275/1012] Reduced usage of temporary registers --- src/build/core/CompileDefinition.go | 6 ++---- src/build/core/ExpressionToRegister.go | 5 +++++ src/build/core/Function.go | 7 ------- src/build/core/Instructions.go | 6 ++++-- src/build/core/SaveRegister.go | 5 +---- src/build/core/UsesRegister.go | 9 +-------- 6 files changed, 13 insertions(+), 25 deletions(-) diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index f2fd234..c23ac56 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/errors" @@ -43,7 +41,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { func (f *Function) AddVariable(variable *Variable) { if config.Comments { - f.Comment(fmt.Sprintf("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive)) + f.Comment("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive) } f.variables[variable.Name] = variable @@ -60,7 +58,7 @@ func (f *Function) useVariable(variable *Variable) { if variable.Alive == 0 { if config.Comments { - f.Comment(fmt.Sprintf("%s died (%s)", variable.Name, variable.Register)) + f.Comment("%s died (%s)", variable.Name, variable.Register) } f.cpu.Free(variable.Register) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 793403c..acc211d 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -3,6 +3,7 @@ package core import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" @@ -34,6 +35,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if f.UsesRegister(right, register) { register = f.cpu.MustFindFree(f.cpu.General) + + if config.Comments { + f.Comment("temporary register %s", register) + } } f.cpu.Reserve(register) diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 0af8d06..7e57da2 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" @@ -102,11 +100,6 @@ func (f *Function) CompileASTNode(node ast.Node) error { } } -// Logf formats a message for verbose output. -func (f *Function) Logf(format string, data ...any) { - fmt.Printf("[%s @ %d] %s\n", f, len(f.assembler.Instructions), fmt.Sprintf(format, data...)) -} - // String returns the function name. func (f *Function) String() string { return f.Name diff --git a/src/build/core/Instructions.go b/src/build/core/Instructions.go index d9bae49..e6764ca 100644 --- a/src/build/core/Instructions.go +++ b/src/build/core/Instructions.go @@ -1,6 +1,8 @@ package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" @@ -17,8 +19,8 @@ func (f *Function) Call(label string) { f.postInstruction() } -func (f *Function) Comment(comment string) { - f.assembler.Comment(comment) +func (f *Function) Comment(format string, args ...any) { + f.assembler.Comment(fmt.Sprintf(format, args...)) f.postInstruction() } diff --git a/src/build/core/SaveRegister.go b/src/build/core/SaveRegister.go index 17f16cb..30e3c33 100644 --- a/src/build/core/SaveRegister.go +++ b/src/build/core/SaveRegister.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" @@ -37,10 +35,9 @@ func (f *Function) SaveRegister(register cpu.Register) { f.cpu.Reserve(newRegister) if config.Comments { - f.Comment(fmt.Sprintf("save %s to %s", register, newRegister)) + f.Comment("save %s to %s", register, newRegister) } f.RegisterRegister(asm.MOVE, newRegister, register) - f.cpu.Free(register) variable.Register = newRegister } diff --git a/src/build/core/UsesRegister.go b/src/build/core/UsesRegister.go index 5090f63..9a10a85 100644 --- a/src/build/core/UsesRegister.go +++ b/src/build/core/UsesRegister.go @@ -4,19 +4,12 @@ import ( "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" ) // UsesRegister returns true if evaluating the expression would write or read the given register. func (f *Function) UsesRegister(expr *expression.Expression, register cpu.Register) bool { if expr.IsLeaf() { - if expr.Token.Kind == token.Number { - return false - } - - name := expr.Token.Text() - variable := f.variables[name] - return register == variable.Register + return false } if ast.IsFunctionCall(expr) { From d3436b13a572722e1493fec2c527d6e4fea16b17 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 10 Jul 2024 10:48:15 +0200 Subject: [PATCH 0276/1012] Reduced usage of temporary registers --- src/build/core/CompileDefinition.go | 6 ++---- src/build/core/ExpressionToRegister.go | 5 +++++ src/build/core/Function.go | 7 ------- src/build/core/Instructions.go | 6 ++++-- src/build/core/SaveRegister.go | 5 +---- src/build/core/UsesRegister.go | 9 +-------- 6 files changed, 13 insertions(+), 25 deletions(-) diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index f2fd234..c23ac56 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/errors" @@ -43,7 +41,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { func (f *Function) AddVariable(variable *Variable) { if config.Comments { - f.Comment(fmt.Sprintf("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive)) + f.Comment("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive) } f.variables[variable.Name] = variable @@ -60,7 +58,7 @@ func (f *Function) useVariable(variable *Variable) { if variable.Alive == 0 { if config.Comments { - f.Comment(fmt.Sprintf("%s died (%s)", variable.Name, variable.Register)) + f.Comment("%s died (%s)", variable.Name, variable.Register) } f.cpu.Free(variable.Register) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 793403c..acc211d 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -3,6 +3,7 @@ package core import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" @@ -34,6 +35,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if f.UsesRegister(right, register) { register = f.cpu.MustFindFree(f.cpu.General) + + if config.Comments { + f.Comment("temporary register %s", register) + } } f.cpu.Reserve(register) diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 0af8d06..7e57da2 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" @@ -102,11 +100,6 @@ func (f *Function) CompileASTNode(node ast.Node) error { } } -// Logf formats a message for verbose output. -func (f *Function) Logf(format string, data ...any) { - fmt.Printf("[%s @ %d] %s\n", f, len(f.assembler.Instructions), fmt.Sprintf(format, data...)) -} - // String returns the function name. func (f *Function) String() string { return f.Name diff --git a/src/build/core/Instructions.go b/src/build/core/Instructions.go index d9bae49..e6764ca 100644 --- a/src/build/core/Instructions.go +++ b/src/build/core/Instructions.go @@ -1,6 +1,8 @@ package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" @@ -17,8 +19,8 @@ func (f *Function) Call(label string) { f.postInstruction() } -func (f *Function) Comment(comment string) { - f.assembler.Comment(comment) +func (f *Function) Comment(format string, args ...any) { + f.assembler.Comment(fmt.Sprintf(format, args...)) f.postInstruction() } diff --git a/src/build/core/SaveRegister.go b/src/build/core/SaveRegister.go index 17f16cb..30e3c33 100644 --- a/src/build/core/SaveRegister.go +++ b/src/build/core/SaveRegister.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" @@ -37,10 +35,9 @@ func (f *Function) SaveRegister(register cpu.Register) { f.cpu.Reserve(newRegister) if config.Comments { - f.Comment(fmt.Sprintf("save %s to %s", register, newRegister)) + f.Comment("save %s to %s", register, newRegister) } f.RegisterRegister(asm.MOVE, newRegister, register) - f.cpu.Free(register) variable.Register = newRegister } diff --git a/src/build/core/UsesRegister.go b/src/build/core/UsesRegister.go index 5090f63..9a10a85 100644 --- a/src/build/core/UsesRegister.go +++ b/src/build/core/UsesRegister.go @@ -4,19 +4,12 @@ import ( "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" ) // UsesRegister returns true if evaluating the expression would write or read the given register. func (f *Function) UsesRegister(expr *expression.Expression, register cpu.Register) bool { if expr.IsLeaf() { - if expr.Token.Kind == token.Number { - return false - } - - name := expr.Token.Text() - variable := f.variables[name] - return register == variable.Register + return false } if ast.IsFunctionCall(expr) { From 3b2cbbe3b7928b9fd7fd5c727beedd0954a6891d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 10 Jul 2024 15:01:46 +0200 Subject: [PATCH 0277/1012] Implemented 32-bit jumps --- examples/write/write.q | 13 --- src/build/arch/x64/Call.go | 2 +- src/build/arch/x64/Jump.go | 22 ++-- src/build/arch/x64/SizeOf.go | 2 +- src/build/arch/x64/regRegNum.go | 2 +- src/build/asm/Assembler.go | 58 +++++++++- src/build/asm/Pointer.go | 3 +- tests/examples_test.go | 1 - tests/programs/jump-near.q | 187 ++++++++++++++++++++++++++++++++ tests/programs_test.go | 1 + 10 files changed, 253 insertions(+), 38 deletions(-) delete mode 100644 examples/write/write.q create mode 100644 tests/programs/jump-near.q diff --git a/examples/write/write.q b/examples/write/write.q deleted file mode 100644 index 31b18d6..0000000 --- a/examples/write/write.q +++ /dev/null @@ -1,13 +0,0 @@ -main() { - address := 4194304 + 1 - length := 3 - print(address, length) -} - -print(address, length) { - write(1, address, length) -} - -write(fd, address, length) { - syscall(1, fd, address, length) -} \ No newline at end of file diff --git a/src/build/arch/x64/Call.go b/src/build/arch/x64/Call.go index 1ada6fd..1bbfc46 100644 --- a/src/build/arch/x64/Call.go +++ b/src/build/arch/x64/Call.go @@ -5,7 +5,7 @@ package x64 func Call(code []byte, address uint32) []byte { return append( code, - 0xe8, + 0xE8, byte(address), byte(address>>8), byte(address>>16), diff --git a/src/build/arch/x64/Jump.go b/src/build/arch/x64/Jump.go index 86d2c48..1174f58 100644 --- a/src/build/arch/x64/Jump.go +++ b/src/build/arch/x64/Jump.go @@ -3,43 +3,35 @@ package x64 // Jump continues program flow at the new address. // The address is relative to the next instruction. func Jump8(code []byte, address int8) []byte { - return jump8(code, 0xEB, address) + return append(code, 0xEB, byte(address)) } // JumpIfLess jumps if the result was less. func Jump8IfLess(code []byte, address int8) []byte { - return jump8(code, 0x7C, address) + return append(code, 0x7C, byte(address)) } // JumpIfLessOrEqual jumps if the result was less or equal. func Jump8IfLessOrEqual(code []byte, address int8) []byte { - return jump8(code, 0x7E, address) + return append(code, 0x7E, byte(address)) } // JumpIfGreater jumps if the result was greater. func Jump8IfGreater(code []byte, address int8) []byte { - return jump8(code, 0x7F, address) + return append(code, 0x7F, byte(address)) } // JumpIfGreaterOrEqual jumps if the result was greater or equal. func Jump8IfGreaterOrEqual(code []byte, address int8) []byte { - return jump8(code, 0x7D, address) + return append(code, 0x7D, byte(address)) } // JumpIfEqual jumps if the result was equal. func Jump8IfEqual(code []byte, address int8) []byte { - return jump8(code, 0x74, address) + return append(code, 0x74, byte(address)) } // JumpIfNotEqual jumps if the result was not equal. func Jump8IfNotEqual(code []byte, address int8) []byte { - return jump8(code, 0x75, address) -} - -func jump8(code []byte, opCode byte, address int8) []byte { - return append( - code, - opCode, - byte(address), - ) + return append(code, 0x75, byte(address)) } diff --git a/src/build/arch/x64/SizeOf.go b/src/build/arch/x64/SizeOf.go index c4804ae..6c7293a 100644 --- a/src/build/arch/x64/SizeOf.go +++ b/src/build/arch/x64/SizeOf.go @@ -3,7 +3,7 @@ package x64 import "math" // SizeOf tells you how many bytes are needed to encode this number. -func SizeOf(number int) int { +func SizeOf(number int64) int { switch { case number >= math.MinInt8 && number <= math.MaxInt8: return 1 diff --git a/src/build/arch/x64/regRegNum.go b/src/build/arch/x64/regRegNum.go index 3fc6987..61ffefb 100644 --- a/src/build/arch/x64/regRegNum.go +++ b/src/build/arch/x64/regRegNum.go @@ -4,7 +4,7 @@ import "encoding/binary" // regRegNum encodes an instruction with up to two registers and a number parameter. func regRegNum(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { - if SizeOf(number) == 1 { + if SizeOf(int64(number)) == 1 { code = regReg(code, reg, rm, opCode8) return append(code, byte(number)) } diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 18c536d..0854b8b 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -2,6 +2,7 @@ package asm import ( "encoding/binary" + "fmt" "git.akyoto.dev/cli/q/src/build/arch/x64" ) @@ -16,7 +17,7 @@ func (a Assembler) Finalize() ([]byte, []byte) { code := make([]byte, 0, len(a.Instructions)*8) data := make([]byte, 0, 16) labels := map[string]Address{} - pointers := []Pointer{} + pointers := []*Pointer{} for _, x := range a.Instructions { switch x.Mnemonic { @@ -53,8 +54,9 @@ func (a Assembler) Finalize() ([]byte, []byte) { label := x.Data.(*Label) nextInstructionAddress := Address(len(code)) - pointers = append(pointers, Pointer{ + pointers = append(pointers, &Pointer{ Position: Address(len(code) - size), + OpSize: 1, Size: uint8(size), Resolve: func() Address { destination, exists := labels[label.Name] @@ -101,8 +103,9 @@ func (a Assembler) Finalize() ([]byte, []byte) { label := x.Data.(*Label) nextInstructionAddress := Address(len(code)) - pointers = append(pointers, Pointer{ + pointers = append(pointers, &Pointer{ Position: Address(len(code) - size), + OpSize: 1, Size: uint8(size), Resolve: func() Address { destination, exists := labels[label.Name] @@ -153,10 +156,55 @@ func (a Assembler) Finalize() ([]byte, []byte) { // dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) - for _, pointer := range pointers { - slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] +restart: + for i, pointer := range pointers { address := pointer.Resolve() + if x64.SizeOf(int64(address)) > int(pointer.Size) { + left := code[:pointer.Position-Address(pointer.OpSize)] + right := code[pointer.Position+Address(pointer.Size):] + size := pointer.Size + pointer.OpSize + opCode := code[pointer.Position-Address(pointer.OpSize)] + + var jump []byte + + switch opCode { + case 0x74: // JE + jump = []byte{0x0F, 0x84} + case 0x75: // JNE + jump = []byte{0x0F, 0x85} + case 0x7C: // JL + jump = []byte{0x0F, 0x8C} + case 0x7D: // JGE + jump = []byte{0x0F, 0x8D} + case 0x7E: // JLE + jump = []byte{0x0F, 0x8E} + case 0x7F: // JG + jump = []byte{0x0F, 0x8F} + case 0xEB: // JMP + jump = []byte{0xE9} + + default: + panic(fmt.Errorf("failed to increase pointer size for instruction 0x%x", opCode)) + } + + pointer.Position += Address(len(jump) - int(pointer.OpSize)) + pointer.OpSize = uint8(len(jump)) + pointer.Size = 4 + jump = binary.LittleEndian.AppendUint32(jump, uint32(address)) + offset := Address(len(jump)) - Address(size) + + for _, following := range pointers[i+1:] { + following.Position += offset + } + + code = append(left, jump...) + code = append(code, right...) + goto restart + } + + slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] + switch pointer.Size { case 1: slice[0] = uint8(address) diff --git a/src/build/asm/Pointer.go b/src/build/asm/Pointer.go index 4e33a63..bb4f9d1 100644 --- a/src/build/asm/Pointer.go +++ b/src/build/asm/Pointer.go @@ -1,7 +1,7 @@ package asm // Address represents a memory address. -type Address = uint32 +type Address = int32 // Pointer stores a relative memory address that we can later turn into an absolute one. // Position: The machine code offset where the address was inserted. @@ -9,5 +9,6 @@ type Address = uint32 type Pointer struct { Resolve func() Address Position Address + OpSize uint8 Size uint8 } diff --git a/tests/examples_test.go b/tests/examples_test.go index 217050f..109734e 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -11,7 +11,6 @@ var examples = []struct { ExpectedExitCode int }{ {"hello", "", 0}, - {"write", "ELF", 0}, {"fibonacci", "", 55}, } diff --git a/tests/programs/jump-near.q b/tests/programs/jump-near.q new file mode 100644 index 0000000..7af145d --- /dev/null +++ b/tests/programs/jump-near.q @@ -0,0 +1,187 @@ +main() { + x := 10 + + if x == 0 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + if x != 10 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + if x > 10 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + if x < 10 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + if x >= 11 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + if x <= 9 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + exit(0) +} + +fail() { + exit(1) +} + +exit(code) { + syscall(60, code) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 0449ec9..93dbd50 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -30,6 +30,7 @@ var programs = []struct { {"branch-and", "", 0}, {"branch-or", "", 0}, {"branch-both", "", 0}, + {"jump-near", "", 0}, } func TestPrograms(t *testing.T) { From e24d9ebb501e4dbae83c3cc13c7a88ea517ddec4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 10 Jul 2024 15:01:46 +0200 Subject: [PATCH 0278/1012] Implemented 32-bit jumps --- examples/write/write.q | 13 --- src/build/arch/x64/Call.go | 2 +- src/build/arch/x64/Jump.go | 22 ++-- src/build/arch/x64/SizeOf.go | 2 +- src/build/arch/x64/regRegNum.go | 2 +- src/build/asm/Assembler.go | 58 +++++++++- src/build/asm/Pointer.go | 3 +- tests/examples_test.go | 1 - tests/programs/jump-near.q | 187 ++++++++++++++++++++++++++++++++ tests/programs_test.go | 1 + 10 files changed, 253 insertions(+), 38 deletions(-) delete mode 100644 examples/write/write.q create mode 100644 tests/programs/jump-near.q diff --git a/examples/write/write.q b/examples/write/write.q deleted file mode 100644 index 31b18d6..0000000 --- a/examples/write/write.q +++ /dev/null @@ -1,13 +0,0 @@ -main() { - address := 4194304 + 1 - length := 3 - print(address, length) -} - -print(address, length) { - write(1, address, length) -} - -write(fd, address, length) { - syscall(1, fd, address, length) -} \ No newline at end of file diff --git a/src/build/arch/x64/Call.go b/src/build/arch/x64/Call.go index 1ada6fd..1bbfc46 100644 --- a/src/build/arch/x64/Call.go +++ b/src/build/arch/x64/Call.go @@ -5,7 +5,7 @@ package x64 func Call(code []byte, address uint32) []byte { return append( code, - 0xe8, + 0xE8, byte(address), byte(address>>8), byte(address>>16), diff --git a/src/build/arch/x64/Jump.go b/src/build/arch/x64/Jump.go index 86d2c48..1174f58 100644 --- a/src/build/arch/x64/Jump.go +++ b/src/build/arch/x64/Jump.go @@ -3,43 +3,35 @@ package x64 // Jump continues program flow at the new address. // The address is relative to the next instruction. func Jump8(code []byte, address int8) []byte { - return jump8(code, 0xEB, address) + return append(code, 0xEB, byte(address)) } // JumpIfLess jumps if the result was less. func Jump8IfLess(code []byte, address int8) []byte { - return jump8(code, 0x7C, address) + return append(code, 0x7C, byte(address)) } // JumpIfLessOrEqual jumps if the result was less or equal. func Jump8IfLessOrEqual(code []byte, address int8) []byte { - return jump8(code, 0x7E, address) + return append(code, 0x7E, byte(address)) } // JumpIfGreater jumps if the result was greater. func Jump8IfGreater(code []byte, address int8) []byte { - return jump8(code, 0x7F, address) + return append(code, 0x7F, byte(address)) } // JumpIfGreaterOrEqual jumps if the result was greater or equal. func Jump8IfGreaterOrEqual(code []byte, address int8) []byte { - return jump8(code, 0x7D, address) + return append(code, 0x7D, byte(address)) } // JumpIfEqual jumps if the result was equal. func Jump8IfEqual(code []byte, address int8) []byte { - return jump8(code, 0x74, address) + return append(code, 0x74, byte(address)) } // JumpIfNotEqual jumps if the result was not equal. func Jump8IfNotEqual(code []byte, address int8) []byte { - return jump8(code, 0x75, address) -} - -func jump8(code []byte, opCode byte, address int8) []byte { - return append( - code, - opCode, - byte(address), - ) + return append(code, 0x75, byte(address)) } diff --git a/src/build/arch/x64/SizeOf.go b/src/build/arch/x64/SizeOf.go index c4804ae..6c7293a 100644 --- a/src/build/arch/x64/SizeOf.go +++ b/src/build/arch/x64/SizeOf.go @@ -3,7 +3,7 @@ package x64 import "math" // SizeOf tells you how many bytes are needed to encode this number. -func SizeOf(number int) int { +func SizeOf(number int64) int { switch { case number >= math.MinInt8 && number <= math.MaxInt8: return 1 diff --git a/src/build/arch/x64/regRegNum.go b/src/build/arch/x64/regRegNum.go index 3fc6987..61ffefb 100644 --- a/src/build/arch/x64/regRegNum.go +++ b/src/build/arch/x64/regRegNum.go @@ -4,7 +4,7 @@ import "encoding/binary" // regRegNum encodes an instruction with up to two registers and a number parameter. func regRegNum(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { - if SizeOf(number) == 1 { + if SizeOf(int64(number)) == 1 { code = regReg(code, reg, rm, opCode8) return append(code, byte(number)) } diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 18c536d..0854b8b 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -2,6 +2,7 @@ package asm import ( "encoding/binary" + "fmt" "git.akyoto.dev/cli/q/src/build/arch/x64" ) @@ -16,7 +17,7 @@ func (a Assembler) Finalize() ([]byte, []byte) { code := make([]byte, 0, len(a.Instructions)*8) data := make([]byte, 0, 16) labels := map[string]Address{} - pointers := []Pointer{} + pointers := []*Pointer{} for _, x := range a.Instructions { switch x.Mnemonic { @@ -53,8 +54,9 @@ func (a Assembler) Finalize() ([]byte, []byte) { label := x.Data.(*Label) nextInstructionAddress := Address(len(code)) - pointers = append(pointers, Pointer{ + pointers = append(pointers, &Pointer{ Position: Address(len(code) - size), + OpSize: 1, Size: uint8(size), Resolve: func() Address { destination, exists := labels[label.Name] @@ -101,8 +103,9 @@ func (a Assembler) Finalize() ([]byte, []byte) { label := x.Data.(*Label) nextInstructionAddress := Address(len(code)) - pointers = append(pointers, Pointer{ + pointers = append(pointers, &Pointer{ Position: Address(len(code) - size), + OpSize: 1, Size: uint8(size), Resolve: func() Address { destination, exists := labels[label.Name] @@ -153,10 +156,55 @@ func (a Assembler) Finalize() ([]byte, []byte) { // dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) - for _, pointer := range pointers { - slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] +restart: + for i, pointer := range pointers { address := pointer.Resolve() + if x64.SizeOf(int64(address)) > int(pointer.Size) { + left := code[:pointer.Position-Address(pointer.OpSize)] + right := code[pointer.Position+Address(pointer.Size):] + size := pointer.Size + pointer.OpSize + opCode := code[pointer.Position-Address(pointer.OpSize)] + + var jump []byte + + switch opCode { + case 0x74: // JE + jump = []byte{0x0F, 0x84} + case 0x75: // JNE + jump = []byte{0x0F, 0x85} + case 0x7C: // JL + jump = []byte{0x0F, 0x8C} + case 0x7D: // JGE + jump = []byte{0x0F, 0x8D} + case 0x7E: // JLE + jump = []byte{0x0F, 0x8E} + case 0x7F: // JG + jump = []byte{0x0F, 0x8F} + case 0xEB: // JMP + jump = []byte{0xE9} + + default: + panic(fmt.Errorf("failed to increase pointer size for instruction 0x%x", opCode)) + } + + pointer.Position += Address(len(jump) - int(pointer.OpSize)) + pointer.OpSize = uint8(len(jump)) + pointer.Size = 4 + jump = binary.LittleEndian.AppendUint32(jump, uint32(address)) + offset := Address(len(jump)) - Address(size) + + for _, following := range pointers[i+1:] { + following.Position += offset + } + + code = append(left, jump...) + code = append(code, right...) + goto restart + } + + slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] + switch pointer.Size { case 1: slice[0] = uint8(address) diff --git a/src/build/asm/Pointer.go b/src/build/asm/Pointer.go index 4e33a63..bb4f9d1 100644 --- a/src/build/asm/Pointer.go +++ b/src/build/asm/Pointer.go @@ -1,7 +1,7 @@ package asm // Address represents a memory address. -type Address = uint32 +type Address = int32 // Pointer stores a relative memory address that we can later turn into an absolute one. // Position: The machine code offset where the address was inserted. @@ -9,5 +9,6 @@ type Address = uint32 type Pointer struct { Resolve func() Address Position Address + OpSize uint8 Size uint8 } diff --git a/tests/examples_test.go b/tests/examples_test.go index 217050f..109734e 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -11,7 +11,6 @@ var examples = []struct { ExpectedExitCode int }{ {"hello", "", 0}, - {"write", "ELF", 0}, {"fibonacci", "", 55}, } diff --git a/tests/programs/jump-near.q b/tests/programs/jump-near.q new file mode 100644 index 0000000..7af145d --- /dev/null +++ b/tests/programs/jump-near.q @@ -0,0 +1,187 @@ +main() { + x := 10 + + if x == 0 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + if x != 10 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + if x > 10 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + if x < 10 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + if x >= 11 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + if x <= 9 { + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + fail() + } + + exit(0) +} + +fail() { + exit(1) +} + +exit(code) { + syscall(60, code) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 0449ec9..93dbd50 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -30,6 +30,7 @@ var programs = []struct { {"branch-and", "", 0}, {"branch-or", "", 0}, {"branch-both", "", 0}, + {"jump-near", "", 0}, } func TestPrograms(t *testing.T) { From dbe0a67dc5d8a4ead8be8fc394be93dd0ea73db8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 11 Jul 2024 11:53:49 +0200 Subject: [PATCH 0279/1012] Reordered jump cases --- examples/fibonacci/fibonacci.q | 2 +- src/build/asm/Assembler.go | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q index 2bedfdb..e02a15c 100644 --- a/examples/fibonacci/fibonacci.q +++ b/examples/fibonacci/fibonacci.q @@ -3,7 +3,7 @@ main() { } fibonacci(x) { - if x == 1 || x == 0 { + if x <= 1 { return x } diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 0854b8b..3f0ad34 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -81,22 +81,22 @@ func (a Assembler) Finalize() ([]byte, []byte) { code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source) } - case JUMP, JE, JNE, JG, JL, JGE, JLE: + case JE, JNE, JG, JGE, JL, JLE, JUMP: switch x.Mnemonic { - case JUMP: - code = x64.Jump8(code, 0x00) case JE: code = x64.Jump8IfEqual(code, 0x00) case JNE: code = x64.Jump8IfNotEqual(code, 0x00) case JG: code = x64.Jump8IfGreater(code, 0x00) - case JL: - code = x64.Jump8IfLess(code, 0x00) case JGE: code = x64.Jump8IfGreaterOrEqual(code, 0x00) + case JL: + code = x64.Jump8IfLess(code, 0x00) case JLE: code = x64.Jump8IfLessOrEqual(code, 0x00) + case JUMP: + code = x64.Jump8(code, 0x00) } size := 1 @@ -183,7 +183,6 @@ restart: jump = []byte{0x0F, 0x8F} case 0xEB: // JMP jump = []byte{0xE9} - default: panic(fmt.Errorf("failed to increase pointer size for instruction 0x%x", opCode)) } From c2401bf8268572a59b80e9a6c5f485179e859df4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 11 Jul 2024 11:53:49 +0200 Subject: [PATCH 0280/1012] Reordered jump cases --- examples/fibonacci/fibonacci.q | 2 +- src/build/asm/Assembler.go | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q index 2bedfdb..e02a15c 100644 --- a/examples/fibonacci/fibonacci.q +++ b/examples/fibonacci/fibonacci.q @@ -3,7 +3,7 @@ main() { } fibonacci(x) { - if x == 1 || x == 0 { + if x <= 1 { return x } diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 0854b8b..3f0ad34 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -81,22 +81,22 @@ func (a Assembler) Finalize() ([]byte, []byte) { code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source) } - case JUMP, JE, JNE, JG, JL, JGE, JLE: + case JE, JNE, JG, JGE, JL, JLE, JUMP: switch x.Mnemonic { - case JUMP: - code = x64.Jump8(code, 0x00) case JE: code = x64.Jump8IfEqual(code, 0x00) case JNE: code = x64.Jump8IfNotEqual(code, 0x00) case JG: code = x64.Jump8IfGreater(code, 0x00) - case JL: - code = x64.Jump8IfLess(code, 0x00) case JGE: code = x64.Jump8IfGreaterOrEqual(code, 0x00) + case JL: + code = x64.Jump8IfLess(code, 0x00) case JLE: code = x64.Jump8IfLessOrEqual(code, 0x00) + case JUMP: + code = x64.Jump8(code, 0x00) } size := 1 @@ -183,7 +183,6 @@ restart: jump = []byte{0x0F, 0x8F} case 0xEB: // JMP jump = []byte{0xE9} - default: panic(fmt.Errorf("failed to increase pointer size for instruction 0x%x", opCode)) } From ff0b8ecb104b85d5fc2c975a63d92ceb46554fb9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 11 Jul 2024 12:04:51 +0200 Subject: [PATCH 0281/1012] Reorganized file structure --- src/build/ast/AST.go | 59 +---------------------------------------- src/build/ast/Assign.go | 19 +++++++++++++ src/build/ast/Call.go | 12 +++++++++ src/build/ast/Define.go | 18 +++++++++++++ src/build/ast/If.go | 17 ++++++++++++ src/build/ast/Loop.go | 12 +++++++++ src/build/ast/Return.go | 16 +++++++++++ 7 files changed, 95 insertions(+), 58 deletions(-) create mode 100644 src/build/ast/Assign.go create mode 100644 src/build/ast/Call.go create mode 100644 src/build/ast/Define.go create mode 100644 src/build/ast/If.go create mode 100644 src/build/ast/Loop.go create mode 100644 src/build/ast/Return.go diff --git a/src/build/ast/AST.go b/src/build/ast/AST.go index 7def683..1c8f6dd 100644 --- a/src/build/ast/AST.go +++ b/src/build/ast/AST.go @@ -1,63 +1,6 @@ package ast -import ( - "fmt" - - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" -) +import "fmt" type Node fmt.Stringer type AST []Node - -type Assign struct { - Value *expression.Expression - Name token.Token - Operator token.Token -} - -func (node *Assign) String() string { - return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) -} - -type Call struct { - Expression *expression.Expression -} - -func (node *Call) String() string { - return node.Expression.String() -} - -type Define struct { - Value *expression.Expression - Name token.Token -} - -func (node *Define) String() string { - return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) -} - -type If struct { - Condition *expression.Expression - Body AST -} - -func (node *If) String() string { - return fmt.Sprintf("(if %s %s)", node.Condition, node.Body) -} - -type Loop struct { - Body AST -} - -func (node *Loop) String() string { - return fmt.Sprintf("(loop %s)", node.Body) -} - -type Return struct { - Value *expression.Expression -} - -func (node *Return) String() string { - return fmt.Sprintf("(return %s)", node.Value) -} diff --git a/src/build/ast/Assign.go b/src/build/ast/Assign.go new file mode 100644 index 0000000..daee95d --- /dev/null +++ b/src/build/ast/Assign.go @@ -0,0 +1,19 @@ +package ast + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Assign represents an assignment to an existing variable or memory location. +type Assign struct { + Value *expression.Expression + Name token.Token + Operator token.Token +} + +func (node *Assign) String() string { + return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) +} diff --git a/src/build/ast/Call.go b/src/build/ast/Call.go new file mode 100644 index 0000000..d7825b3 --- /dev/null +++ b/src/build/ast/Call.go @@ -0,0 +1,12 @@ +package ast + +import "git.akyoto.dev/cli/q/src/build/expression" + +// Call represents a function call. +type Call struct { + Expression *expression.Expression +} + +func (node *Call) String() string { + return node.Expression.String() +} diff --git a/src/build/ast/Define.go b/src/build/ast/Define.go new file mode 100644 index 0000000..6751491 --- /dev/null +++ b/src/build/ast/Define.go @@ -0,0 +1,18 @@ +package ast + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Define represents a variable definition. +type Define struct { + Value *expression.Expression + Name token.Token +} + +func (node *Define) String() string { + return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) +} diff --git a/src/build/ast/If.go b/src/build/ast/If.go new file mode 100644 index 0000000..47d98d4 --- /dev/null +++ b/src/build/ast/If.go @@ -0,0 +1,17 @@ +package ast + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/expression" +) + +// If represents an if statement. +type If struct { + Condition *expression.Expression + Body AST +} + +func (node *If) String() string { + return fmt.Sprintf("(if %s %s)", node.Condition, node.Body) +} diff --git a/src/build/ast/Loop.go b/src/build/ast/Loop.go new file mode 100644 index 0000000..5b0dc33 --- /dev/null +++ b/src/build/ast/Loop.go @@ -0,0 +1,12 @@ +package ast + +import "fmt" + +// Loop represents a block of repeatable statements. +type Loop struct { + Body AST +} + +func (node *Loop) String() string { + return fmt.Sprintf("(loop %s)", node.Body) +} diff --git a/src/build/ast/Return.go b/src/build/ast/Return.go new file mode 100644 index 0000000..a56520f --- /dev/null +++ b/src/build/ast/Return.go @@ -0,0 +1,16 @@ +package ast + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/expression" +) + +// Return represents a return statement. +type Return struct { + Value *expression.Expression +} + +func (node *Return) String() string { + return fmt.Sprintf("(return %s)", node.Value) +} From 7b18056006a81d5bc5bd2361d438a230cbb8012d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 11 Jul 2024 12:04:51 +0200 Subject: [PATCH 0282/1012] Reorganized file structure --- src/build/ast/AST.go | 59 +---------------------------------------- src/build/ast/Assign.go | 19 +++++++++++++ src/build/ast/Call.go | 12 +++++++++ src/build/ast/Define.go | 18 +++++++++++++ src/build/ast/If.go | 17 ++++++++++++ src/build/ast/Loop.go | 12 +++++++++ src/build/ast/Return.go | 16 +++++++++++ 7 files changed, 95 insertions(+), 58 deletions(-) create mode 100644 src/build/ast/Assign.go create mode 100644 src/build/ast/Call.go create mode 100644 src/build/ast/Define.go create mode 100644 src/build/ast/If.go create mode 100644 src/build/ast/Loop.go create mode 100644 src/build/ast/Return.go diff --git a/src/build/ast/AST.go b/src/build/ast/AST.go index 7def683..1c8f6dd 100644 --- a/src/build/ast/AST.go +++ b/src/build/ast/AST.go @@ -1,63 +1,6 @@ package ast -import ( - "fmt" - - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" -) +import "fmt" type Node fmt.Stringer type AST []Node - -type Assign struct { - Value *expression.Expression - Name token.Token - Operator token.Token -} - -func (node *Assign) String() string { - return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) -} - -type Call struct { - Expression *expression.Expression -} - -func (node *Call) String() string { - return node.Expression.String() -} - -type Define struct { - Value *expression.Expression - Name token.Token -} - -func (node *Define) String() string { - return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) -} - -type If struct { - Condition *expression.Expression - Body AST -} - -func (node *If) String() string { - return fmt.Sprintf("(if %s %s)", node.Condition, node.Body) -} - -type Loop struct { - Body AST -} - -func (node *Loop) String() string { - return fmt.Sprintf("(loop %s)", node.Body) -} - -type Return struct { - Value *expression.Expression -} - -func (node *Return) String() string { - return fmt.Sprintf("(return %s)", node.Value) -} diff --git a/src/build/ast/Assign.go b/src/build/ast/Assign.go new file mode 100644 index 0000000..daee95d --- /dev/null +++ b/src/build/ast/Assign.go @@ -0,0 +1,19 @@ +package ast + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Assign represents an assignment to an existing variable or memory location. +type Assign struct { + Value *expression.Expression + Name token.Token + Operator token.Token +} + +func (node *Assign) String() string { + return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) +} diff --git a/src/build/ast/Call.go b/src/build/ast/Call.go new file mode 100644 index 0000000..d7825b3 --- /dev/null +++ b/src/build/ast/Call.go @@ -0,0 +1,12 @@ +package ast + +import "git.akyoto.dev/cli/q/src/build/expression" + +// Call represents a function call. +type Call struct { + Expression *expression.Expression +} + +func (node *Call) String() string { + return node.Expression.String() +} diff --git a/src/build/ast/Define.go b/src/build/ast/Define.go new file mode 100644 index 0000000..6751491 --- /dev/null +++ b/src/build/ast/Define.go @@ -0,0 +1,18 @@ +package ast + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Define represents a variable definition. +type Define struct { + Value *expression.Expression + Name token.Token +} + +func (node *Define) String() string { + return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) +} diff --git a/src/build/ast/If.go b/src/build/ast/If.go new file mode 100644 index 0000000..47d98d4 --- /dev/null +++ b/src/build/ast/If.go @@ -0,0 +1,17 @@ +package ast + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/expression" +) + +// If represents an if statement. +type If struct { + Condition *expression.Expression + Body AST +} + +func (node *If) String() string { + return fmt.Sprintf("(if %s %s)", node.Condition, node.Body) +} diff --git a/src/build/ast/Loop.go b/src/build/ast/Loop.go new file mode 100644 index 0000000..5b0dc33 --- /dev/null +++ b/src/build/ast/Loop.go @@ -0,0 +1,12 @@ +package ast + +import "fmt" + +// Loop represents a block of repeatable statements. +type Loop struct { + Body AST +} + +func (node *Loop) String() string { + return fmt.Sprintf("(loop %s)", node.Body) +} diff --git a/src/build/ast/Return.go b/src/build/ast/Return.go new file mode 100644 index 0000000..a56520f --- /dev/null +++ b/src/build/ast/Return.go @@ -0,0 +1,16 @@ +package ast + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/expression" +) + +// Return represents a return statement. +type Return struct { + Value *expression.Expression +} + +func (node *Return) String() string { + return fmt.Sprintf("(return %s)", node.Value) +} From 1b75529bb3289b3fbe06ab07a1f2498380a043d5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 13 Jul 2024 00:13:13 +0200 Subject: [PATCH 0283/1012] Implemented data labels --- examples/hello/hello.q | 16 +- src/build/asm/Assembler.go | 259 +----------------------------- src/build/asm/Finalize.go | 248 ++++++++++++++++++++++++++++ src/build/asm/Instructions.go | 48 ------ src/build/asm/Label.go | 10 ++ src/build/asm/Register.go | 10 ++ src/build/asm/RegisterLabel.go | 29 ++++ src/build/asm/RegisterNumber.go | 11 ++ src/build/asm/RegisterRegister.go | 15 ++ src/build/asm/divide.go | 43 +++++ src/build/core/ExecuteLeaf.go | 5 + src/build/core/Function.go | 1 + src/build/core/Instructions.go | 14 ++ src/build/core/Result.go | 1 + src/build/core/TokenToRegister.go | 8 +- src/build/core/state.go | 3 +- src/build/elf/ELF.go | 32 ++-- tests/examples_test.go | 2 +- 18 files changed, 428 insertions(+), 327 deletions(-) create mode 100644 src/build/asm/Finalize.go create mode 100644 src/build/asm/RegisterLabel.go create mode 100644 src/build/asm/divide.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 43be3f4..85aa05c 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,17 +1,11 @@ main() { - x := f(1) + f(2) + f(3) - - if x != 9 { - exit(1) - } - - exit(0) + print("Hello", 5) } -exit(code) { - syscall(60, code) +print(address, length) { + write(1, address, length) } -f(x) { - return x + 1 +write(fd, address, length) { + syscall(1, fd, address, length) } \ No newline at end of file diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 3f0ad34..08efb10 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -1,268 +1,15 @@ package asm -import ( - "encoding/binary" - "fmt" - - "git.akyoto.dev/cli/q/src/build/arch/x64" -) +import "maps" // Assembler contains a list of instructions. type Assembler struct { Instructions []Instruction -} - -// Finalize generates the final machine code. -func (a Assembler) Finalize() ([]byte, []byte) { - code := make([]byte, 0, len(a.Instructions)*8) - data := make([]byte, 0, 16) - labels := map[string]Address{} - pointers := []*Pointer{} - - for _, x := range a.Instructions { - switch x.Mnemonic { - case ADD: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x64.AddRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x64.AddRegisterRegister(code, operands.Destination, operands.Source) - } - - case SUB: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x64.SubRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x64.SubRegisterRegister(code, operands.Destination, operands.Source) - } - - case MUL: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x64.MulRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x64.MulRegisterRegister(code, operands.Destination, operands.Source) - } - - case DIV: - code = divide(code, x.Data) - - case CALL: - code = x64.Call(code, 0x00_00_00_00) - size := 4 - label := x.Data.(*Label) - nextInstructionAddress := Address(len(code)) - - pointers = append(pointers, &Pointer{ - Position: Address(len(code) - size), - OpSize: 1, - Size: uint8(size), - Resolve: func() Address { - destination, exists := labels[label.Name] - - if !exists { - panic("unknown call label") - } - - distance := destination - nextInstructionAddress - return Address(distance) - }, - }) - - case COMMENT: - continue - - case COMPARE: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x64.CompareRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source) - } - - case JE, JNE, JG, JGE, JL, JLE, JUMP: - switch x.Mnemonic { - case JE: - code = x64.Jump8IfEqual(code, 0x00) - case JNE: - code = x64.Jump8IfNotEqual(code, 0x00) - case JG: - code = x64.Jump8IfGreater(code, 0x00) - case JGE: - code = x64.Jump8IfGreaterOrEqual(code, 0x00) - case JL: - code = x64.Jump8IfLess(code, 0x00) - case JLE: - code = x64.Jump8IfLessOrEqual(code, 0x00) - case JUMP: - code = x64.Jump8(code, 0x00) - } - - size := 1 - label := x.Data.(*Label) - nextInstructionAddress := Address(len(code)) - - pointers = append(pointers, &Pointer{ - Position: Address(len(code) - size), - OpSize: 1, - Size: uint8(size), - Resolve: func() Address { - destination, exists := labels[label.Name] - - if !exists { - panic("unknown jump label") - } - - distance := destination - nextInstructionAddress - return Address(distance) - }, - }) - - case LABEL: - labels[x.Data.(*Label).Name] = Address(len(code)) - - case MOVE: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) - - case *RegisterRegister: - code = x64.MoveRegisterRegister64(code, operands.Destination, operands.Source) - } - - case POP: - switch operands := x.Data.(type) { - case *Register: - code = x64.PopRegister(code, operands.Register) - } - - case PUSH: - switch operands := x.Data.(type) { - case *Register: - code = x64.PushRegister(code, operands.Register) - } - - case RETURN: - code = x64.Return(code) - - case SYSCALL: - code = x64.Syscall(code) - - default: - panic("Unknown mnemonic: " + x.Mnemonic.String()) - } - } - - // dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) - -restart: - for i, pointer := range pointers { - address := pointer.Resolve() - - if x64.SizeOf(int64(address)) > int(pointer.Size) { - left := code[:pointer.Position-Address(pointer.OpSize)] - right := code[pointer.Position+Address(pointer.Size):] - size := pointer.Size + pointer.OpSize - opCode := code[pointer.Position-Address(pointer.OpSize)] - - var jump []byte - - switch opCode { - case 0x74: // JE - jump = []byte{0x0F, 0x84} - case 0x75: // JNE - jump = []byte{0x0F, 0x85} - case 0x7C: // JL - jump = []byte{0x0F, 0x8C} - case 0x7D: // JGE - jump = []byte{0x0F, 0x8D} - case 0x7E: // JLE - jump = []byte{0x0F, 0x8E} - case 0x7F: // JG - jump = []byte{0x0F, 0x8F} - case 0xEB: // JMP - jump = []byte{0xE9} - default: - panic(fmt.Errorf("failed to increase pointer size for instruction 0x%x", opCode)) - } - - pointer.Position += Address(len(jump) - int(pointer.OpSize)) - pointer.OpSize = uint8(len(jump)) - pointer.Size = 4 - jump = binary.LittleEndian.AppendUint32(jump, uint32(address)) - offset := Address(len(jump)) - Address(size) - - for _, following := range pointers[i+1:] { - following.Position += offset - } - - code = append(left, jump...) - code = append(code, right...) - goto restart - } - - slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] - - switch pointer.Size { - case 1: - slice[0] = uint8(address) - - case 2: - binary.LittleEndian.PutUint16(slice, uint16(address)) - - case 4: - binary.LittleEndian.PutUint32(slice, uint32(address)) - - case 8: - binary.LittleEndian.PutUint64(slice, uint64(address)) - } - } - - return code, data + Data map[string][]byte } // Merge combines the contents of this assembler with another one. func (a *Assembler) Merge(b Assembler) { a.Instructions = append(a.Instructions, b.Instructions...) -} - -// divide implements the division on x64 machines. -func divide(code []byte, data any) []byte { - code = x64.PushRegister(code, x64.RDX) - - switch operands := data.(type) { - case *RegisterNumber: - if operands.Register == x64.RAX { - code = x64.PushRegister(code, x64.RCX) - code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number)) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, x64.RCX) - code = x64.PopRegister(code, x64.RCX) - } else { - code = x64.PushRegister(code, x64.RAX) - code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) - code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Register) - code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX) - code = x64.PopRegister(code, x64.RAX) - } - - case *RegisterRegister: - if operands.Destination == x64.RAX { - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Source) - } else { - code = x64.PushRegister(code, x64.RAX) - code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Source) - code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX) - code = x64.PopRegister(code, x64.RAX) - } - } - - code = x64.PopRegister(code, x64.RDX) - return code + maps.Copy(a.Data, b.Data) } diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go new file mode 100644 index 0000000..b669153 --- /dev/null +++ b/src/build/asm/Finalize.go @@ -0,0 +1,248 @@ +package asm + +import ( + "encoding/binary" + "fmt" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/elf" +) + +// Finalize generates the final machine code. +func (a Assembler) Finalize() ([]byte, []byte) { + code := make([]byte, 0, len(a.Instructions)*8) + data := make([]byte, 0, 16) + labels := map[string]Address{} + pointers := []*Pointer{} + + for _, x := range a.Instructions { + switch x.Mnemonic { + case ADD: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.AddRegisterNumber(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.AddRegisterRegister(code, operands.Destination, operands.Source) + } + + case SUB: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.SubRegisterNumber(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.SubRegisterRegister(code, operands.Destination, operands.Source) + } + + case MUL: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.MulRegisterNumber(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.MulRegisterRegister(code, operands.Destination, operands.Source) + } + + case DIV: + code = divide(code, x.Data) + + case CALL: + code = x64.Call(code, 0x00_00_00_00) + size := 4 + label := x.Data.(*Label) + nextInstructionAddress := Address(len(code)) + + pointers = append(pointers, &Pointer{ + Position: Address(len(code) - size), + OpSize: 1, + Size: uint8(size), + Resolve: func() Address { + destination, exists := labels[label.Name] + + if !exists { + panic("unknown call label") + } + + distance := destination - nextInstructionAddress + return Address(distance) + }, + }) + + case COMMENT: + continue + + case COMPARE: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.CompareRegisterNumber(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source) + } + + case JE, JNE, JG, JGE, JL, JLE, JUMP: + switch x.Mnemonic { + case JE: + code = x64.Jump8IfEqual(code, 0x00) + case JNE: + code = x64.Jump8IfNotEqual(code, 0x00) + case JG: + code = x64.Jump8IfGreater(code, 0x00) + case JGE: + code = x64.Jump8IfGreaterOrEqual(code, 0x00) + case JL: + code = x64.Jump8IfLess(code, 0x00) + case JLE: + code = x64.Jump8IfLessOrEqual(code, 0x00) + case JUMP: + code = x64.Jump8(code, 0x00) + } + + size := 1 + label := x.Data.(*Label) + nextInstructionAddress := Address(len(code)) + + pointers = append(pointers, &Pointer{ + Position: Address(len(code) - size), + OpSize: 1, + Size: uint8(size), + Resolve: func() Address { + destination, exists := labels[label.Name] + + if !exists { + panic("unknown jump label") + } + + distance := destination - nextInstructionAddress + return Address(distance) + }, + }) + + case LABEL: + labels[x.Data.(*Label).Name] = Address(len(code)) + + case MOVE: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) + + case *RegisterRegister: + code = x64.MoveRegisterRegister64(code, operands.Destination, operands.Source) + + case *RegisterLabel: + start := len(code) + code = x64.MoveRegisterNumber32(code, operands.Register, 0x00_00_00_00) + size := 4 + opSize := len(code) - size - start + regLabel := x.Data.(*RegisterLabel) + + pointers = append(pointers, &Pointer{ + Position: Address(len(code) - size), + OpSize: uint8(opSize), + Size: uint8(size), + Resolve: func() Address { + destination, exists := labels[regLabel.Label] + + if !exists { + panic("unknown label") + } + + return Address(destination) + }, + }) + } + + case POP: + switch operands := x.Data.(type) { + case *Register: + code = x64.PopRegister(code, operands.Register) + } + + case PUSH: + switch operands := x.Data.(type) { + case *Register: + code = x64.PushRegister(code, operands.Register) + } + + case RETURN: + code = x64.Return(code) + + case SYSCALL: + code = x64.Syscall(code) + + default: + panic("Unknown mnemonic: " + x.Mnemonic.String()) + } + } + + dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) + dataStart += int32(elf.Padding(int64(dataStart), config.Align)) + + for label, slice := range a.Data { + labels[label] = dataStart + Address(len(data)) + data = append(data, slice...) + } + +restart: + for i, pointer := range pointers { + address := pointer.Resolve() + + if x64.SizeOf(int64(address)) > int(pointer.Size) { + left := code[:pointer.Position-Address(pointer.OpSize)] + right := code[pointer.Position+Address(pointer.Size):] + size := pointer.Size + pointer.OpSize + opCode := code[pointer.Position-Address(pointer.OpSize)] + + var jump []byte + + switch opCode { + case 0x74: // JE + jump = []byte{0x0F, 0x84} + case 0x75: // JNE + jump = []byte{0x0F, 0x85} + case 0x7C: // JL + jump = []byte{0x0F, 0x8C} + case 0x7D: // JGE + jump = []byte{0x0F, 0x8D} + case 0x7E: // JLE + jump = []byte{0x0F, 0x8E} + case 0x7F: // JG + jump = []byte{0x0F, 0x8F} + case 0xEB: // JMP + jump = []byte{0xE9} + default: + panic(fmt.Errorf("failed to increase pointer size for instruction 0x%x", opCode)) + } + + pointer.Position += Address(len(jump) - int(pointer.OpSize)) + pointer.OpSize = uint8(len(jump)) + pointer.Size = 4 + jump = binary.LittleEndian.AppendUint32(jump, uint32(address)) + offset := Address(len(jump)) - Address(size) + + for _, following := range pointers[i+1:] { + following.Position += offset + } + + code = append(left, jump...) + code = append(code, right...) + goto restart + } + + slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] + + switch pointer.Size { + case 1: + slice[0] = uint8(address) + + case 2: + binary.LittleEndian.PutUint16(slice, uint16(address)) + + case 4: + binary.LittleEndian.PutUint32(slice, uint32(address)) + + case 8: + binary.LittleEndian.PutUint64(slice, uint64(address)) + } + } + + return code, data +} diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 805a0ea..4c51e87 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -1,53 +1,5 @@ package asm -import "git.akyoto.dev/cli/q/src/build/cpu" - -// RegisterNumber adds an instruction with a register and a number. -func (a *Assembler) RegisterNumber(mnemonic Mnemonic, reg cpu.Register, number int) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: mnemonic, - Data: &RegisterNumber{ - Register: reg, - Number: number, - }, - }) -} - -// RegisterRegister adds an instruction using two registers. -func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right cpu.Register) { - if a.unnecessary(mnemonic, left, right) { - return - } - - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: mnemonic, - Data: &RegisterRegister{ - Destination: left, - Source: right, - }, - }) -} - -// Register adds an instruction using a single register. -func (a *Assembler) Register(mnemonic Mnemonic, register cpu.Register) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: mnemonic, - Data: &Register{ - Register: register, - }, - }) -} - -// Label adds an instruction using a label. -func (a *Assembler) Label(mnemonic Mnemonic, name string) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: mnemonic, - Data: &Label{ - Name: name, - }, - }) -} - // Comment adds a comment at the current position. func (a *Assembler) Comment(text string) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/asm/Label.go b/src/build/asm/Label.go index 1636fb8..1b463bc 100644 --- a/src/build/asm/Label.go +++ b/src/build/asm/Label.go @@ -9,3 +9,13 @@ type Label struct { func (data *Label) String() string { return data.Name } + +// Label adds an instruction using a label. +func (a *Assembler) Label(mnemonic Mnemonic, name string) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &Label{ + Name: name, + }, + }) +} diff --git a/src/build/asm/Register.go b/src/build/asm/Register.go index a618370..0f737c9 100644 --- a/src/build/asm/Register.go +++ b/src/build/asm/Register.go @@ -13,3 +13,13 @@ type Register struct { func (data *Register) String() string { return data.Register.String() } + +// Register adds an instruction using a single register. +func (a *Assembler) Register(mnemonic Mnemonic, register cpu.Register) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &Register{ + Register: register, + }, + }) +} diff --git a/src/build/asm/RegisterLabel.go b/src/build/asm/RegisterLabel.go new file mode 100644 index 0000000..4eb23a3 --- /dev/null +++ b/src/build/asm/RegisterLabel.go @@ -0,0 +1,29 @@ +package asm + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// RegisterLabel operates with a register and a label. +type RegisterLabel struct { + Register cpu.Register + Label string +} + +// String returns a human readable version. +func (data *RegisterLabel) String() string { + return fmt.Sprintf("%s, %s", data.Register, data.Label) +} + +// RegisterLabel adds an instruction with a register and a label. +func (a *Assembler) RegisterLabel(mnemonic Mnemonic, reg cpu.Register, label string) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &RegisterLabel{ + Register: reg, + Label: label, + }, + }) +} diff --git a/src/build/asm/RegisterNumber.go b/src/build/asm/RegisterNumber.go index ff5d616..b3bcb9e 100644 --- a/src/build/asm/RegisterNumber.go +++ b/src/build/asm/RegisterNumber.go @@ -16,3 +16,14 @@ type RegisterNumber struct { func (data *RegisterNumber) String() string { return fmt.Sprintf("%s, %d", data.Register, data.Number) } + +// RegisterNumber adds an instruction with a register and a number. +func (a *Assembler) RegisterNumber(mnemonic Mnemonic, reg cpu.Register, number int) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &RegisterNumber{ + Register: reg, + Number: number, + }, + }) +} diff --git a/src/build/asm/RegisterRegister.go b/src/build/asm/RegisterRegister.go index 5acb084..584058d 100644 --- a/src/build/asm/RegisterRegister.go +++ b/src/build/asm/RegisterRegister.go @@ -16,3 +16,18 @@ type RegisterRegister struct { func (data *RegisterRegister) String() string { return fmt.Sprintf("%s, %s", data.Destination, data.Source) } + +// RegisterRegister adds an instruction using two registers. +func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right cpu.Register) { + if a.unnecessary(mnemonic, left, right) { + return + } + + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &RegisterRegister{ + Destination: left, + Source: right, + }, + }) +} diff --git a/src/build/asm/divide.go b/src/build/asm/divide.go new file mode 100644 index 0000000..09a879b --- /dev/null +++ b/src/build/asm/divide.go @@ -0,0 +1,43 @@ +package asm + +import "git.akyoto.dev/cli/q/src/build/arch/x64" + +// divide implements the division on x64 machines. +func divide(code []byte, data any) []byte { + code = x64.PushRegister(code, x64.RDX) + + switch operands := data.(type) { + case *RegisterNumber: + if operands.Register == x64.RAX { + code = x64.PushRegister(code, x64.RCX) + code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number)) + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, x64.RCX) + code = x64.PopRegister(code, x64.RCX) + } else { + code = x64.PushRegister(code, x64.RAX) + code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) + code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Register) + code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX) + code = x64.PopRegister(code, x64.RAX) + } + + case *RegisterRegister: + if operands.Destination == x64.RAX { + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Source) + } else { + code = x64.PushRegister(code, x64.RAX) + code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Source) + code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX) + code = x64.PopRegister(code, x64.RAX) + } + } + + code = x64.PopRegister(code, x64.RDX) + return code +} diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index 1349870..c08866d 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -31,6 +31,11 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope } return f.ExecuteRegisterNumber(operation, register, number) + + case token.String: + if operation.Text() == "=" { + return f.TokenToRegister(operand, register) + } } return errors.New(errors.NotImplemented, f.File, operation.Position) diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 7e57da2..961f5d7 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -27,6 +27,7 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { state: state{ assembler: asm.Assembler{ Instructions: make([]asm.Instruction, 0, 32), + Data: map[string][]byte{}, }, cpu: cpu.CPU{ All: x64.AllRegisters, diff --git a/src/build/core/Instructions.go b/src/build/core/Instructions.go index e6764ca..6f4da46 100644 --- a/src/build/core/Instructions.go +++ b/src/build/core/Instructions.go @@ -53,6 +53,20 @@ func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) f.postInstruction() } +func (f *Function) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { + if f.cpu.IsUsed(register) && isDestructive(mnemonic) { + f.SaveRegister(register) + } + + f.assembler.RegisterLabel(mnemonic, register, label) + + if mnemonic == asm.MOVE { + f.cpu.Use(register) + } + + f.postInstruction() +} + func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) { if mnemonic == asm.MOVE && a == b { return diff --git a/src/build/core/Result.go b/src/build/core/Result.go index 9430012..671a90a 100644 --- a/src/build/core/Result.go +++ b/src/build/core/Result.go @@ -25,6 +25,7 @@ func (r *Result) finalize() ([]byte, []byte) { // a return address on the stack, which allows return statements in `main`. final := asm.Assembler{ Instructions: make([]asm.Instruction, 0, r.InstructionCount+4), + Data: map[string][]byte{}, } final.Call("main") diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index bbc3808..d437cec 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "strconv" "git.akyoto.dev/cli/q/src/build/asm" @@ -37,7 +38,12 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return nil case token.String: - return errors.New(errors.NotImplemented, f.File, t.Position) + value := t.Text()[1 : len(t.Bytes)-1] + label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data) + f.assembler.Data[label] = []byte(value) + f.RegisterLabel(asm.MOVE, register, label) + f.count.data++ + return nil default: return errors.New(errors.InvalidExpression, f.File, t.Position) diff --git a/src/build/core/state.go b/src/build/core/state.go index e185533..9269971 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -23,8 +23,9 @@ type state struct { // counter stores how often a certain statement appeared so we can generate a unique label from it. type counter struct { - loop int branch int + data int + loop int subBranch int } diff --git a/src/build/elf/ELF.go b/src/build/elf/ELF.go index f284dbc..b6c3e34 100644 --- a/src/build/elf/ELF.go +++ b/src/build/elf/ELF.go @@ -1,6 +1,7 @@ package elf import ( + "bytes" "encoding/binary" "io" @@ -10,13 +11,19 @@ import ( // ELF represents an ELF file. type ELF struct { Header - ProgramHeader - Code []byte - Data []byte + CodeHeader ProgramHeader + PadCode []byte + Code []byte + PadData []byte + Data []byte } // New creates a new ELF binary. func New(code []byte, data []byte) *ELF { + dataOffset := config.CodeOffset + int64(len(code)) + dataPadding := Padding(dataOffset, config.Align) + dataOffset += dataPadding + elf := &ELF{ Header: Header{ Magic: [4]byte{0x7F, 'E', 'L', 'F'}, @@ -39,9 +46,9 @@ func New(code []byte, data []byte) *ELF { SectionHeaderEntryCount: 0, SectionNameStringTableIndex: 0, }, - ProgramHeader: ProgramHeader{ + CodeHeader: ProgramHeader{ Type: ProgramTypeLOAD, - Flags: ProgramFlagsExecutable, + Flags: ProgramFlagsExecutable | ProgramFlagsReadable, Offset: config.CodeOffset, VirtualAddress: config.BaseAddress + config.CodeOffset, PhysicalAddress: config.BaseAddress + config.CodeOffset, @@ -49,18 +56,25 @@ func New(code []byte, data []byte) *ELF { SizeInMemory: int64(len(code)), Align: config.Align, }, - Code: code, - Data: data, + PadCode: bytes.Repeat([]byte{0}, 8), + Code: code, + PadData: bytes.Repeat([]byte{0}, int(dataPadding)), + Data: data, } return elf } +func Padding(n int64, align int64) int64 { + return align - (n % align) +} + // Write writes the ELF64 format to the given writer. func (elf *ELF) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &elf.Header) - binary.Write(writer, binary.LittleEndian, &elf.ProgramHeader) - writer.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0}) + binary.Write(writer, binary.LittleEndian, &elf.CodeHeader) + writer.Write(elf.PadCode) writer.Write(elf.Code) + writer.Write(elf.PadData) writer.Write(elf.Data) } diff --git a/tests/examples_test.go b/tests/examples_test.go index 109734e..f390eb2 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -10,7 +10,7 @@ var examples = []struct { ExpectedOutput string ExpectedExitCode int }{ - {"hello", "", 0}, + {"hello", "Hello", 0}, {"fibonacci", "", 55}, } From 9df899cb520092dada0cc201bccb3cdf11afdab3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 13 Jul 2024 00:13:13 +0200 Subject: [PATCH 0284/1012] Implemented data labels --- examples/hello/hello.q | 16 +- src/build/asm/Assembler.go | 259 +----------------------------- src/build/asm/Finalize.go | 248 ++++++++++++++++++++++++++++ src/build/asm/Instructions.go | 48 ------ src/build/asm/Label.go | 10 ++ src/build/asm/Register.go | 10 ++ src/build/asm/RegisterLabel.go | 29 ++++ src/build/asm/RegisterNumber.go | 11 ++ src/build/asm/RegisterRegister.go | 15 ++ src/build/asm/divide.go | 43 +++++ src/build/core/ExecuteLeaf.go | 5 + src/build/core/Function.go | 1 + src/build/core/Instructions.go | 14 ++ src/build/core/Result.go | 1 + src/build/core/TokenToRegister.go | 8 +- src/build/core/state.go | 3 +- src/build/elf/ELF.go | 32 ++-- tests/examples_test.go | 2 +- 18 files changed, 428 insertions(+), 327 deletions(-) create mode 100644 src/build/asm/Finalize.go create mode 100644 src/build/asm/RegisterLabel.go create mode 100644 src/build/asm/divide.go diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 43be3f4..85aa05c 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,17 +1,11 @@ main() { - x := f(1) + f(2) + f(3) - - if x != 9 { - exit(1) - } - - exit(0) + print("Hello", 5) } -exit(code) { - syscall(60, code) +print(address, length) { + write(1, address, length) } -f(x) { - return x + 1 +write(fd, address, length) { + syscall(1, fd, address, length) } \ No newline at end of file diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 3f0ad34..08efb10 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -1,268 +1,15 @@ package asm -import ( - "encoding/binary" - "fmt" - - "git.akyoto.dev/cli/q/src/build/arch/x64" -) +import "maps" // Assembler contains a list of instructions. type Assembler struct { Instructions []Instruction -} - -// Finalize generates the final machine code. -func (a Assembler) Finalize() ([]byte, []byte) { - code := make([]byte, 0, len(a.Instructions)*8) - data := make([]byte, 0, 16) - labels := map[string]Address{} - pointers := []*Pointer{} - - for _, x := range a.Instructions { - switch x.Mnemonic { - case ADD: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x64.AddRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x64.AddRegisterRegister(code, operands.Destination, operands.Source) - } - - case SUB: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x64.SubRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x64.SubRegisterRegister(code, operands.Destination, operands.Source) - } - - case MUL: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x64.MulRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x64.MulRegisterRegister(code, operands.Destination, operands.Source) - } - - case DIV: - code = divide(code, x.Data) - - case CALL: - code = x64.Call(code, 0x00_00_00_00) - size := 4 - label := x.Data.(*Label) - nextInstructionAddress := Address(len(code)) - - pointers = append(pointers, &Pointer{ - Position: Address(len(code) - size), - OpSize: 1, - Size: uint8(size), - Resolve: func() Address { - destination, exists := labels[label.Name] - - if !exists { - panic("unknown call label") - } - - distance := destination - nextInstructionAddress - return Address(distance) - }, - }) - - case COMMENT: - continue - - case COMPARE: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x64.CompareRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source) - } - - case JE, JNE, JG, JGE, JL, JLE, JUMP: - switch x.Mnemonic { - case JE: - code = x64.Jump8IfEqual(code, 0x00) - case JNE: - code = x64.Jump8IfNotEqual(code, 0x00) - case JG: - code = x64.Jump8IfGreater(code, 0x00) - case JGE: - code = x64.Jump8IfGreaterOrEqual(code, 0x00) - case JL: - code = x64.Jump8IfLess(code, 0x00) - case JLE: - code = x64.Jump8IfLessOrEqual(code, 0x00) - case JUMP: - code = x64.Jump8(code, 0x00) - } - - size := 1 - label := x.Data.(*Label) - nextInstructionAddress := Address(len(code)) - - pointers = append(pointers, &Pointer{ - Position: Address(len(code) - size), - OpSize: 1, - Size: uint8(size), - Resolve: func() Address { - destination, exists := labels[label.Name] - - if !exists { - panic("unknown jump label") - } - - distance := destination - nextInstructionAddress - return Address(distance) - }, - }) - - case LABEL: - labels[x.Data.(*Label).Name] = Address(len(code)) - - case MOVE: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) - - case *RegisterRegister: - code = x64.MoveRegisterRegister64(code, operands.Destination, operands.Source) - } - - case POP: - switch operands := x.Data.(type) { - case *Register: - code = x64.PopRegister(code, operands.Register) - } - - case PUSH: - switch operands := x.Data.(type) { - case *Register: - code = x64.PushRegister(code, operands.Register) - } - - case RETURN: - code = x64.Return(code) - - case SYSCALL: - code = x64.Syscall(code) - - default: - panic("Unknown mnemonic: " + x.Mnemonic.String()) - } - } - - // dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) - -restart: - for i, pointer := range pointers { - address := pointer.Resolve() - - if x64.SizeOf(int64(address)) > int(pointer.Size) { - left := code[:pointer.Position-Address(pointer.OpSize)] - right := code[pointer.Position+Address(pointer.Size):] - size := pointer.Size + pointer.OpSize - opCode := code[pointer.Position-Address(pointer.OpSize)] - - var jump []byte - - switch opCode { - case 0x74: // JE - jump = []byte{0x0F, 0x84} - case 0x75: // JNE - jump = []byte{0x0F, 0x85} - case 0x7C: // JL - jump = []byte{0x0F, 0x8C} - case 0x7D: // JGE - jump = []byte{0x0F, 0x8D} - case 0x7E: // JLE - jump = []byte{0x0F, 0x8E} - case 0x7F: // JG - jump = []byte{0x0F, 0x8F} - case 0xEB: // JMP - jump = []byte{0xE9} - default: - panic(fmt.Errorf("failed to increase pointer size for instruction 0x%x", opCode)) - } - - pointer.Position += Address(len(jump) - int(pointer.OpSize)) - pointer.OpSize = uint8(len(jump)) - pointer.Size = 4 - jump = binary.LittleEndian.AppendUint32(jump, uint32(address)) - offset := Address(len(jump)) - Address(size) - - for _, following := range pointers[i+1:] { - following.Position += offset - } - - code = append(left, jump...) - code = append(code, right...) - goto restart - } - - slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] - - switch pointer.Size { - case 1: - slice[0] = uint8(address) - - case 2: - binary.LittleEndian.PutUint16(slice, uint16(address)) - - case 4: - binary.LittleEndian.PutUint32(slice, uint32(address)) - - case 8: - binary.LittleEndian.PutUint64(slice, uint64(address)) - } - } - - return code, data + Data map[string][]byte } // Merge combines the contents of this assembler with another one. func (a *Assembler) Merge(b Assembler) { a.Instructions = append(a.Instructions, b.Instructions...) -} - -// divide implements the division on x64 machines. -func divide(code []byte, data any) []byte { - code = x64.PushRegister(code, x64.RDX) - - switch operands := data.(type) { - case *RegisterNumber: - if operands.Register == x64.RAX { - code = x64.PushRegister(code, x64.RCX) - code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number)) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, x64.RCX) - code = x64.PopRegister(code, x64.RCX) - } else { - code = x64.PushRegister(code, x64.RAX) - code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) - code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Register) - code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX) - code = x64.PopRegister(code, x64.RAX) - } - - case *RegisterRegister: - if operands.Destination == x64.RAX { - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Source) - } else { - code = x64.PushRegister(code, x64.RAX) - code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Source) - code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX) - code = x64.PopRegister(code, x64.RAX) - } - } - - code = x64.PopRegister(code, x64.RDX) - return code + maps.Copy(a.Data, b.Data) } diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go new file mode 100644 index 0000000..b669153 --- /dev/null +++ b/src/build/asm/Finalize.go @@ -0,0 +1,248 @@ +package asm + +import ( + "encoding/binary" + "fmt" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/elf" +) + +// Finalize generates the final machine code. +func (a Assembler) Finalize() ([]byte, []byte) { + code := make([]byte, 0, len(a.Instructions)*8) + data := make([]byte, 0, 16) + labels := map[string]Address{} + pointers := []*Pointer{} + + for _, x := range a.Instructions { + switch x.Mnemonic { + case ADD: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.AddRegisterNumber(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.AddRegisterRegister(code, operands.Destination, operands.Source) + } + + case SUB: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.SubRegisterNumber(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.SubRegisterRegister(code, operands.Destination, operands.Source) + } + + case MUL: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.MulRegisterNumber(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.MulRegisterRegister(code, operands.Destination, operands.Source) + } + + case DIV: + code = divide(code, x.Data) + + case CALL: + code = x64.Call(code, 0x00_00_00_00) + size := 4 + label := x.Data.(*Label) + nextInstructionAddress := Address(len(code)) + + pointers = append(pointers, &Pointer{ + Position: Address(len(code) - size), + OpSize: 1, + Size: uint8(size), + Resolve: func() Address { + destination, exists := labels[label.Name] + + if !exists { + panic("unknown call label") + } + + distance := destination - nextInstructionAddress + return Address(distance) + }, + }) + + case COMMENT: + continue + + case COMPARE: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.CompareRegisterNumber(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source) + } + + case JE, JNE, JG, JGE, JL, JLE, JUMP: + switch x.Mnemonic { + case JE: + code = x64.Jump8IfEqual(code, 0x00) + case JNE: + code = x64.Jump8IfNotEqual(code, 0x00) + case JG: + code = x64.Jump8IfGreater(code, 0x00) + case JGE: + code = x64.Jump8IfGreaterOrEqual(code, 0x00) + case JL: + code = x64.Jump8IfLess(code, 0x00) + case JLE: + code = x64.Jump8IfLessOrEqual(code, 0x00) + case JUMP: + code = x64.Jump8(code, 0x00) + } + + size := 1 + label := x.Data.(*Label) + nextInstructionAddress := Address(len(code)) + + pointers = append(pointers, &Pointer{ + Position: Address(len(code) - size), + OpSize: 1, + Size: uint8(size), + Resolve: func() Address { + destination, exists := labels[label.Name] + + if !exists { + panic("unknown jump label") + } + + distance := destination - nextInstructionAddress + return Address(distance) + }, + }) + + case LABEL: + labels[x.Data.(*Label).Name] = Address(len(code)) + + case MOVE: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) + + case *RegisterRegister: + code = x64.MoveRegisterRegister64(code, operands.Destination, operands.Source) + + case *RegisterLabel: + start := len(code) + code = x64.MoveRegisterNumber32(code, operands.Register, 0x00_00_00_00) + size := 4 + opSize := len(code) - size - start + regLabel := x.Data.(*RegisterLabel) + + pointers = append(pointers, &Pointer{ + Position: Address(len(code) - size), + OpSize: uint8(opSize), + Size: uint8(size), + Resolve: func() Address { + destination, exists := labels[regLabel.Label] + + if !exists { + panic("unknown label") + } + + return Address(destination) + }, + }) + } + + case POP: + switch operands := x.Data.(type) { + case *Register: + code = x64.PopRegister(code, operands.Register) + } + + case PUSH: + switch operands := x.Data.(type) { + case *Register: + code = x64.PushRegister(code, operands.Register) + } + + case RETURN: + code = x64.Return(code) + + case SYSCALL: + code = x64.Syscall(code) + + default: + panic("Unknown mnemonic: " + x.Mnemonic.String()) + } + } + + dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) + dataStart += int32(elf.Padding(int64(dataStart), config.Align)) + + for label, slice := range a.Data { + labels[label] = dataStart + Address(len(data)) + data = append(data, slice...) + } + +restart: + for i, pointer := range pointers { + address := pointer.Resolve() + + if x64.SizeOf(int64(address)) > int(pointer.Size) { + left := code[:pointer.Position-Address(pointer.OpSize)] + right := code[pointer.Position+Address(pointer.Size):] + size := pointer.Size + pointer.OpSize + opCode := code[pointer.Position-Address(pointer.OpSize)] + + var jump []byte + + switch opCode { + case 0x74: // JE + jump = []byte{0x0F, 0x84} + case 0x75: // JNE + jump = []byte{0x0F, 0x85} + case 0x7C: // JL + jump = []byte{0x0F, 0x8C} + case 0x7D: // JGE + jump = []byte{0x0F, 0x8D} + case 0x7E: // JLE + jump = []byte{0x0F, 0x8E} + case 0x7F: // JG + jump = []byte{0x0F, 0x8F} + case 0xEB: // JMP + jump = []byte{0xE9} + default: + panic(fmt.Errorf("failed to increase pointer size for instruction 0x%x", opCode)) + } + + pointer.Position += Address(len(jump) - int(pointer.OpSize)) + pointer.OpSize = uint8(len(jump)) + pointer.Size = 4 + jump = binary.LittleEndian.AppendUint32(jump, uint32(address)) + offset := Address(len(jump)) - Address(size) + + for _, following := range pointers[i+1:] { + following.Position += offset + } + + code = append(left, jump...) + code = append(code, right...) + goto restart + } + + slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] + + switch pointer.Size { + case 1: + slice[0] = uint8(address) + + case 2: + binary.LittleEndian.PutUint16(slice, uint16(address)) + + case 4: + binary.LittleEndian.PutUint32(slice, uint32(address)) + + case 8: + binary.LittleEndian.PutUint64(slice, uint64(address)) + } + } + + return code, data +} diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 805a0ea..4c51e87 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -1,53 +1,5 @@ package asm -import "git.akyoto.dev/cli/q/src/build/cpu" - -// RegisterNumber adds an instruction with a register and a number. -func (a *Assembler) RegisterNumber(mnemonic Mnemonic, reg cpu.Register, number int) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: mnemonic, - Data: &RegisterNumber{ - Register: reg, - Number: number, - }, - }) -} - -// RegisterRegister adds an instruction using two registers. -func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right cpu.Register) { - if a.unnecessary(mnemonic, left, right) { - return - } - - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: mnemonic, - Data: &RegisterRegister{ - Destination: left, - Source: right, - }, - }) -} - -// Register adds an instruction using a single register. -func (a *Assembler) Register(mnemonic Mnemonic, register cpu.Register) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: mnemonic, - Data: &Register{ - Register: register, - }, - }) -} - -// Label adds an instruction using a label. -func (a *Assembler) Label(mnemonic Mnemonic, name string) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: mnemonic, - Data: &Label{ - Name: name, - }, - }) -} - // Comment adds a comment at the current position. func (a *Assembler) Comment(text string) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/asm/Label.go b/src/build/asm/Label.go index 1636fb8..1b463bc 100644 --- a/src/build/asm/Label.go +++ b/src/build/asm/Label.go @@ -9,3 +9,13 @@ type Label struct { func (data *Label) String() string { return data.Name } + +// Label adds an instruction using a label. +func (a *Assembler) Label(mnemonic Mnemonic, name string) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &Label{ + Name: name, + }, + }) +} diff --git a/src/build/asm/Register.go b/src/build/asm/Register.go index a618370..0f737c9 100644 --- a/src/build/asm/Register.go +++ b/src/build/asm/Register.go @@ -13,3 +13,13 @@ type Register struct { func (data *Register) String() string { return data.Register.String() } + +// Register adds an instruction using a single register. +func (a *Assembler) Register(mnemonic Mnemonic, register cpu.Register) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &Register{ + Register: register, + }, + }) +} diff --git a/src/build/asm/RegisterLabel.go b/src/build/asm/RegisterLabel.go new file mode 100644 index 0000000..4eb23a3 --- /dev/null +++ b/src/build/asm/RegisterLabel.go @@ -0,0 +1,29 @@ +package asm + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// RegisterLabel operates with a register and a label. +type RegisterLabel struct { + Register cpu.Register + Label string +} + +// String returns a human readable version. +func (data *RegisterLabel) String() string { + return fmt.Sprintf("%s, %s", data.Register, data.Label) +} + +// RegisterLabel adds an instruction with a register and a label. +func (a *Assembler) RegisterLabel(mnemonic Mnemonic, reg cpu.Register, label string) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &RegisterLabel{ + Register: reg, + Label: label, + }, + }) +} diff --git a/src/build/asm/RegisterNumber.go b/src/build/asm/RegisterNumber.go index ff5d616..b3bcb9e 100644 --- a/src/build/asm/RegisterNumber.go +++ b/src/build/asm/RegisterNumber.go @@ -16,3 +16,14 @@ type RegisterNumber struct { func (data *RegisterNumber) String() string { return fmt.Sprintf("%s, %d", data.Register, data.Number) } + +// RegisterNumber adds an instruction with a register and a number. +func (a *Assembler) RegisterNumber(mnemonic Mnemonic, reg cpu.Register, number int) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &RegisterNumber{ + Register: reg, + Number: number, + }, + }) +} diff --git a/src/build/asm/RegisterRegister.go b/src/build/asm/RegisterRegister.go index 5acb084..584058d 100644 --- a/src/build/asm/RegisterRegister.go +++ b/src/build/asm/RegisterRegister.go @@ -16,3 +16,18 @@ type RegisterRegister struct { func (data *RegisterRegister) String() string { return fmt.Sprintf("%s, %s", data.Destination, data.Source) } + +// RegisterRegister adds an instruction using two registers. +func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right cpu.Register) { + if a.unnecessary(mnemonic, left, right) { + return + } + + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &RegisterRegister{ + Destination: left, + Source: right, + }, + }) +} diff --git a/src/build/asm/divide.go b/src/build/asm/divide.go new file mode 100644 index 0000000..09a879b --- /dev/null +++ b/src/build/asm/divide.go @@ -0,0 +1,43 @@ +package asm + +import "git.akyoto.dev/cli/q/src/build/arch/x64" + +// divide implements the division on x64 machines. +func divide(code []byte, data any) []byte { + code = x64.PushRegister(code, x64.RDX) + + switch operands := data.(type) { + case *RegisterNumber: + if operands.Register == x64.RAX { + code = x64.PushRegister(code, x64.RCX) + code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number)) + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, x64.RCX) + code = x64.PopRegister(code, x64.RCX) + } else { + code = x64.PushRegister(code, x64.RAX) + code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) + code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Register) + code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX) + code = x64.PopRegister(code, x64.RAX) + } + + case *RegisterRegister: + if operands.Destination == x64.RAX { + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Source) + } else { + code = x64.PushRegister(code, x64.RAX) + code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Source) + code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX) + code = x64.PopRegister(code, x64.RAX) + } + } + + code = x64.PopRegister(code, x64.RDX) + return code +} diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index 1349870..c08866d 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -31,6 +31,11 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope } return f.ExecuteRegisterNumber(operation, register, number) + + case token.String: + if operation.Text() == "=" { + return f.TokenToRegister(operand, register) + } } return errors.New(errors.NotImplemented, f.File, operation.Position) diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 7e57da2..961f5d7 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -27,6 +27,7 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { state: state{ assembler: asm.Assembler{ Instructions: make([]asm.Instruction, 0, 32), + Data: map[string][]byte{}, }, cpu: cpu.CPU{ All: x64.AllRegisters, diff --git a/src/build/core/Instructions.go b/src/build/core/Instructions.go index e6764ca..6f4da46 100644 --- a/src/build/core/Instructions.go +++ b/src/build/core/Instructions.go @@ -53,6 +53,20 @@ func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) f.postInstruction() } +func (f *Function) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { + if f.cpu.IsUsed(register) && isDestructive(mnemonic) { + f.SaveRegister(register) + } + + f.assembler.RegisterLabel(mnemonic, register, label) + + if mnemonic == asm.MOVE { + f.cpu.Use(register) + } + + f.postInstruction() +} + func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) { if mnemonic == asm.MOVE && a == b { return diff --git a/src/build/core/Result.go b/src/build/core/Result.go index 9430012..671a90a 100644 --- a/src/build/core/Result.go +++ b/src/build/core/Result.go @@ -25,6 +25,7 @@ func (r *Result) finalize() ([]byte, []byte) { // a return address on the stack, which allows return statements in `main`. final := asm.Assembler{ Instructions: make([]asm.Instruction, 0, r.InstructionCount+4), + Data: map[string][]byte{}, } final.Call("main") diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index bbc3808..d437cec 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "strconv" "git.akyoto.dev/cli/q/src/build/asm" @@ -37,7 +38,12 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return nil case token.String: - return errors.New(errors.NotImplemented, f.File, t.Position) + value := t.Text()[1 : len(t.Bytes)-1] + label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data) + f.assembler.Data[label] = []byte(value) + f.RegisterLabel(asm.MOVE, register, label) + f.count.data++ + return nil default: return errors.New(errors.InvalidExpression, f.File, t.Position) diff --git a/src/build/core/state.go b/src/build/core/state.go index e185533..9269971 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -23,8 +23,9 @@ type state struct { // counter stores how often a certain statement appeared so we can generate a unique label from it. type counter struct { - loop int branch int + data int + loop int subBranch int } diff --git a/src/build/elf/ELF.go b/src/build/elf/ELF.go index f284dbc..b6c3e34 100644 --- a/src/build/elf/ELF.go +++ b/src/build/elf/ELF.go @@ -1,6 +1,7 @@ package elf import ( + "bytes" "encoding/binary" "io" @@ -10,13 +11,19 @@ import ( // ELF represents an ELF file. type ELF struct { Header - ProgramHeader - Code []byte - Data []byte + CodeHeader ProgramHeader + PadCode []byte + Code []byte + PadData []byte + Data []byte } // New creates a new ELF binary. func New(code []byte, data []byte) *ELF { + dataOffset := config.CodeOffset + int64(len(code)) + dataPadding := Padding(dataOffset, config.Align) + dataOffset += dataPadding + elf := &ELF{ Header: Header{ Magic: [4]byte{0x7F, 'E', 'L', 'F'}, @@ -39,9 +46,9 @@ func New(code []byte, data []byte) *ELF { SectionHeaderEntryCount: 0, SectionNameStringTableIndex: 0, }, - ProgramHeader: ProgramHeader{ + CodeHeader: ProgramHeader{ Type: ProgramTypeLOAD, - Flags: ProgramFlagsExecutable, + Flags: ProgramFlagsExecutable | ProgramFlagsReadable, Offset: config.CodeOffset, VirtualAddress: config.BaseAddress + config.CodeOffset, PhysicalAddress: config.BaseAddress + config.CodeOffset, @@ -49,18 +56,25 @@ func New(code []byte, data []byte) *ELF { SizeInMemory: int64(len(code)), Align: config.Align, }, - Code: code, - Data: data, + PadCode: bytes.Repeat([]byte{0}, 8), + Code: code, + PadData: bytes.Repeat([]byte{0}, int(dataPadding)), + Data: data, } return elf } +func Padding(n int64, align int64) int64 { + return align - (n % align) +} + // Write writes the ELF64 format to the given writer. func (elf *ELF) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &elf.Header) - binary.Write(writer, binary.LittleEndian, &elf.ProgramHeader) - writer.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0}) + binary.Write(writer, binary.LittleEndian, &elf.CodeHeader) + writer.Write(elf.PadCode) writer.Write(elf.Code) + writer.Write(elf.PadData) writer.Write(elf.Data) } diff --git a/tests/examples_test.go b/tests/examples_test.go index 109734e..f390eb2 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -10,7 +10,7 @@ var examples = []struct { ExpectedOutput string ExpectedExitCode int }{ - {"hello", "", 0}, + {"hello", "Hello", 0}, {"fibonacci", "", 55}, } From 96078f40d88935f4ce0109e09bfd08a1c31c3e61 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 13 Jul 2024 14:18:55 +0200 Subject: [PATCH 0285/1012] Improved documentation --- src/build/asm/Assembler.go | 4 ++-- src/build/asm/RegisterLabel.go | 2 +- src/build/config/config.go | 13 +++++++--- src/build/elf/ELF.go | 44 +++++++++++++++++++--------------- src/build/elf/Padding.go | 6 +++++ src/build/elf/elf.md | 6 ++++- 6 files changed, 49 insertions(+), 26 deletions(-) create mode 100644 src/build/elf/Padding.go diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 08efb10..6ad35c8 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -4,12 +4,12 @@ import "maps" // Assembler contains a list of instructions. type Assembler struct { - Instructions []Instruction Data map[string][]byte + Instructions []Instruction } // Merge combines the contents of this assembler with another one. func (a *Assembler) Merge(b Assembler) { - a.Instructions = append(a.Instructions, b.Instructions...) maps.Copy(a.Data, b.Data) + a.Instructions = append(a.Instructions, b.Instructions...) } diff --git a/src/build/asm/RegisterLabel.go b/src/build/asm/RegisterLabel.go index 4eb23a3..f51bf77 100644 --- a/src/build/asm/RegisterLabel.go +++ b/src/build/asm/RegisterLabel.go @@ -8,8 +8,8 @@ import ( // RegisterLabel operates with a register and a label. type RegisterLabel struct { - Register cpu.Register Label string + Register cpu.Register } // String returns a human readable version. diff --git a/src/build/config/config.go b/src/build/config/config.go index 4f97a22..0697fa1 100644 --- a/src/build/config/config.go +++ b/src/build/config/config.go @@ -1,10 +1,17 @@ package config const ( - MinAddress = 0x10000 + // This is the absolute virtual minimum address we can load a program at, see `sysctl vm.mmap_min_addr`. + MinAddress = 0x10000 + + // The base address is the virtual address for our ELF file. BaseAddress = 0x40 * MinAddress - CodeOffset = 0x80 - Align = 0x10 + + // The code offset is the offset of the executable machine code within the file. + CodeOffset = 64 + 56 + 56 + + // Align decides the alignment of the sections and it must be a multiple of the page size. + Align = 0x1000 ) var ( diff --git a/src/build/elf/ELF.go b/src/build/elf/ELF.go index b6c3e34..a2b1e4a 100644 --- a/src/build/elf/ELF.go +++ b/src/build/elf/ELF.go @@ -11,11 +11,12 @@ import ( // ELF represents an ELF file. type ELF struct { Header - CodeHeader ProgramHeader - PadCode []byte - Code []byte - PadData []byte - Data []byte + CodeHeader ProgramHeader + DataHeader ProgramHeader + CodePadding []byte + Code []byte + DataPadding []byte + Data []byte } // New creates a new ELF binary. @@ -24,7 +25,7 @@ func New(code []byte, data []byte) *ELF { dataPadding := Padding(dataOffset, config.Align) dataOffset += dataPadding - elf := &ELF{ + return &ELF{ Header: Header{ Magic: [4]byte{0x7F, 'E', 'L', 'F'}, Class: 2, @@ -41,7 +42,7 @@ func New(code []byte, data []byte) *ELF { Flags: 0, Size: HeaderSize, ProgramHeaderEntrySize: ProgramHeaderSize, - ProgramHeaderEntryCount: 1, + ProgramHeaderEntryCount: 2, SectionHeaderEntrySize: SectionHeaderSize, SectionHeaderEntryCount: 0, SectionNameStringTableIndex: 0, @@ -56,25 +57,30 @@ func New(code []byte, data []byte) *ELF { SizeInMemory: int64(len(code)), Align: config.Align, }, - PadCode: bytes.Repeat([]byte{0}, 8), - Code: code, - PadData: bytes.Repeat([]byte{0}, int(dataPadding)), - Data: data, + DataHeader: ProgramHeader{ + Type: ProgramTypeLOAD, + Flags: ProgramFlagsReadable, + Offset: dataOffset, + VirtualAddress: config.BaseAddress + dataOffset, + PhysicalAddress: config.BaseAddress + dataOffset, + SizeInFile: int64(len(data)), + SizeInMemory: int64(len(data)), + Align: config.Align, + }, + CodePadding: nil, + Code: code, + DataPadding: bytes.Repeat([]byte{0}, int(dataPadding)), + Data: data, } - - return elf -} - -func Padding(n int64, align int64) int64 { - return align - (n % align) } // Write writes the ELF64 format to the given writer. func (elf *ELF) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.CodeHeader) - writer.Write(elf.PadCode) + binary.Write(writer, binary.LittleEndian, &elf.DataHeader) + writer.Write(elf.CodePadding) writer.Write(elf.Code) - writer.Write(elf.PadData) + writer.Write(elf.DataPadding) writer.Write(elf.Data) } diff --git a/src/build/elf/Padding.go b/src/build/elf/Padding.go new file mode 100644 index 0000000..5cca58e --- /dev/null +++ b/src/build/elf/Padding.go @@ -0,0 +1,6 @@ +package elf + +// Padding calculates the padding needed to align `n` bytes with the specified alignment. +func Padding(n int64, align int64) int64 { + return align - (n % align) +} diff --git a/src/build/elf/elf.md b/src/build/elf/elf.md index 83df35c..c40f005 100644 --- a/src/build/elf/elf.md +++ b/src/build/elf/elf.md @@ -23,4 +23,8 @@ Usually, this value is 65536 (0x1000). ## Initialization in Linux -See `/lib/modules/$(uname -r)/build/arch/x86/include/asm/elf.h`. +ELF loader: +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/binfmt_elf.c + +ELF register definitions: +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/include/asm/elf.h From 948d499231f5633c182d3a7f3987b39625043ad0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 13 Jul 2024 14:18:55 +0200 Subject: [PATCH 0286/1012] Improved documentation --- src/build/asm/Assembler.go | 4 ++-- src/build/asm/RegisterLabel.go | 2 +- src/build/config/config.go | 13 +++++++--- src/build/elf/ELF.go | 44 +++++++++++++++++++--------------- src/build/elf/Padding.go | 6 +++++ src/build/elf/elf.md | 6 ++++- 6 files changed, 49 insertions(+), 26 deletions(-) create mode 100644 src/build/elf/Padding.go diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 08efb10..6ad35c8 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -4,12 +4,12 @@ import "maps" // Assembler contains a list of instructions. type Assembler struct { - Instructions []Instruction Data map[string][]byte + Instructions []Instruction } // Merge combines the contents of this assembler with another one. func (a *Assembler) Merge(b Assembler) { - a.Instructions = append(a.Instructions, b.Instructions...) maps.Copy(a.Data, b.Data) + a.Instructions = append(a.Instructions, b.Instructions...) } diff --git a/src/build/asm/RegisterLabel.go b/src/build/asm/RegisterLabel.go index 4eb23a3..f51bf77 100644 --- a/src/build/asm/RegisterLabel.go +++ b/src/build/asm/RegisterLabel.go @@ -8,8 +8,8 @@ import ( // RegisterLabel operates with a register and a label. type RegisterLabel struct { - Register cpu.Register Label string + Register cpu.Register } // String returns a human readable version. diff --git a/src/build/config/config.go b/src/build/config/config.go index 4f97a22..0697fa1 100644 --- a/src/build/config/config.go +++ b/src/build/config/config.go @@ -1,10 +1,17 @@ package config const ( - MinAddress = 0x10000 + // This is the absolute virtual minimum address we can load a program at, see `sysctl vm.mmap_min_addr`. + MinAddress = 0x10000 + + // The base address is the virtual address for our ELF file. BaseAddress = 0x40 * MinAddress - CodeOffset = 0x80 - Align = 0x10 + + // The code offset is the offset of the executable machine code within the file. + CodeOffset = 64 + 56 + 56 + + // Align decides the alignment of the sections and it must be a multiple of the page size. + Align = 0x1000 ) var ( diff --git a/src/build/elf/ELF.go b/src/build/elf/ELF.go index b6c3e34..a2b1e4a 100644 --- a/src/build/elf/ELF.go +++ b/src/build/elf/ELF.go @@ -11,11 +11,12 @@ import ( // ELF represents an ELF file. type ELF struct { Header - CodeHeader ProgramHeader - PadCode []byte - Code []byte - PadData []byte - Data []byte + CodeHeader ProgramHeader + DataHeader ProgramHeader + CodePadding []byte + Code []byte + DataPadding []byte + Data []byte } // New creates a new ELF binary. @@ -24,7 +25,7 @@ func New(code []byte, data []byte) *ELF { dataPadding := Padding(dataOffset, config.Align) dataOffset += dataPadding - elf := &ELF{ + return &ELF{ Header: Header{ Magic: [4]byte{0x7F, 'E', 'L', 'F'}, Class: 2, @@ -41,7 +42,7 @@ func New(code []byte, data []byte) *ELF { Flags: 0, Size: HeaderSize, ProgramHeaderEntrySize: ProgramHeaderSize, - ProgramHeaderEntryCount: 1, + ProgramHeaderEntryCount: 2, SectionHeaderEntrySize: SectionHeaderSize, SectionHeaderEntryCount: 0, SectionNameStringTableIndex: 0, @@ -56,25 +57,30 @@ func New(code []byte, data []byte) *ELF { SizeInMemory: int64(len(code)), Align: config.Align, }, - PadCode: bytes.Repeat([]byte{0}, 8), - Code: code, - PadData: bytes.Repeat([]byte{0}, int(dataPadding)), - Data: data, + DataHeader: ProgramHeader{ + Type: ProgramTypeLOAD, + Flags: ProgramFlagsReadable, + Offset: dataOffset, + VirtualAddress: config.BaseAddress + dataOffset, + PhysicalAddress: config.BaseAddress + dataOffset, + SizeInFile: int64(len(data)), + SizeInMemory: int64(len(data)), + Align: config.Align, + }, + CodePadding: nil, + Code: code, + DataPadding: bytes.Repeat([]byte{0}, int(dataPadding)), + Data: data, } - - return elf -} - -func Padding(n int64, align int64) int64 { - return align - (n % align) } // Write writes the ELF64 format to the given writer. func (elf *ELF) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.CodeHeader) - writer.Write(elf.PadCode) + binary.Write(writer, binary.LittleEndian, &elf.DataHeader) + writer.Write(elf.CodePadding) writer.Write(elf.Code) - writer.Write(elf.PadData) + writer.Write(elf.DataPadding) writer.Write(elf.Data) } diff --git a/src/build/elf/Padding.go b/src/build/elf/Padding.go new file mode 100644 index 0000000..5cca58e --- /dev/null +++ b/src/build/elf/Padding.go @@ -0,0 +1,6 @@ +package elf + +// Padding calculates the padding needed to align `n` bytes with the specified alignment. +func Padding(n int64, align int64) int64 { + return align - (n % align) +} diff --git a/src/build/elf/elf.md b/src/build/elf/elf.md index 83df35c..c40f005 100644 --- a/src/build/elf/elf.md +++ b/src/build/elf/elf.md @@ -23,4 +23,8 @@ Usually, this value is 65536 (0x1000). ## Initialization in Linux -See `/lib/modules/$(uname -r)/build/arch/x86/include/asm/elf.h`. +ELF loader: +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/binfmt_elf.c + +ELF register definitions: +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/include/asm/elf.h From 1bf288b8fd4644179af02f85342e2f450536f049 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 15 Jul 2024 16:51:36 +0200 Subject: [PATCH 0287/1012] Implemented variable scopes --- examples/factorial/factorial.q | 11 ++++++++++ src/build/core/Compare.go | 4 ++-- src/build/core/CompileAssign.go | 4 ++-- src/build/core/CompileDefinition.go | 8 ++++---- src/build/core/CompileIf.go | 9 ++++++--- src/build/core/ExecuteLeaf.go | 4 ++-- src/build/core/Function.go | 4 ++-- src/build/core/SaveRegister.go | 9 +-------- src/build/core/Scope.go | 31 +++++++++++++++++++++++++++++ src/build/core/TokenToRegister.go | 4 ++-- src/build/core/Variable.go | 16 +++++++++++++++ src/build/core/state.go | 2 +- tests/examples_test.go | 1 + 13 files changed, 81 insertions(+), 26 deletions(-) create mode 100644 examples/factorial/factorial.q create mode 100644 src/build/core/Scope.go diff --git a/examples/factorial/factorial.q b/examples/factorial/factorial.q new file mode 100644 index 0000000..f198ec1 --- /dev/null +++ b/examples/factorial/factorial.q @@ -0,0 +1,11 @@ +main() { + syscall(60, factorial(5)) +} + +factorial(x) { + if x <= 1 { + return 1 + } + + return x * factorial(x - 1) +} \ No newline at end of file diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 2e2f29b..4aab1b6 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -14,9 +14,9 @@ func (f *Function) Compare(comparison *expression.Expression) error { if left.IsLeaf() && left.Token.Kind == token.Identifier { name := left.Token.Text() - variable, exists := f.variables[name] + variable := f.Variable(name) - if !exists { + if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) } diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index ce46a34..460ef05 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -8,9 +8,9 @@ import ( // CompileAssign compiles an assign statement. func (f *Function) CompileAssign(node *ast.Assign) error { name := node.Name.Text() - variable, exists := f.variables[name] + variable := f.Variable(name) - if !exists { + if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Name.Position) } diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index c23ac56..894dd64 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -44,7 +44,7 @@ func (f *Function) AddVariable(variable *Variable) { f.Comment("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive) } - f.variables[variable.Name] = variable + f.Scope()[variable.Name] = variable f.cpu.Reserve(variable.Register) f.cpu.Use(variable.Register) } @@ -67,13 +67,13 @@ func (f *Function) useVariable(variable *Variable) { // identifierExists returns true if the identifier has been defined. func (f *Function) identifierExists(name string) bool { - _, exists := f.variables[name] + variable := f.Variable(name) - if exists { + if variable != nil { return true } - _, exists = f.functions[name] + _, exists := f.functions[name] return exists } diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 7721bf6..3146cc4 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -16,8 +16,11 @@ func (f *Function) CompileIf(branch *ast.If) error { return err } - f.AddLabel(success) - defer f.AddLabel(fail) f.count.branch++ - return f.CompileAST(branch.Body) + f.AddLabel(success) + f.pushScope() + err = f.CompileAST(branch.Body) + f.popScope() + f.AddLabel(fail) + return err } diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index c08866d..3b0c5f6 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -13,9 +13,9 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope switch operand.Kind { case token.Identifier: name := operand.Text() - variable, exists := f.variables[name] + variable := f.Variable(name) - if !exists { + if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) } diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 961f5d7..157d6ea 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -36,8 +36,8 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { Syscall: x64.SyscallRegisters, Output: x64.ReturnValueRegisters, }, - variables: map[string]*Variable{}, - finished: make(chan struct{}), + scopes: []Scope{{}}, + finished: make(chan struct{}), }, } } diff --git a/src/build/core/SaveRegister.go b/src/build/core/SaveRegister.go index 30e3c33..2b0b914 100644 --- a/src/build/core/SaveRegister.go +++ b/src/build/core/SaveRegister.go @@ -18,14 +18,7 @@ func (f *Function) SaveRegister(register cpu.Register) { } } - var variable *Variable - - for _, v := range f.variables { - if v.Register == register { - variable = v - break - } - } + variable := f.VariableInRegister(register) if variable == nil || variable.Alive == 0 { return diff --git a/src/build/core/Scope.go b/src/build/core/Scope.go new file mode 100644 index 0000000..dcf3b3c --- /dev/null +++ b/src/build/core/Scope.go @@ -0,0 +1,31 @@ +package core + +// Scope represents a map of variables. +type Scope map[string]*Variable + +// Scope returns the current scope. +func (s *state) Scope() Scope { + return s.scopes[len(s.scopes)-1] +} + +// pushScope pushes a new scope to the top of the stack. +func (s *state) pushScope() { + lastScope := s.scopes[len(s.scopes)-1] + newScope := make(Scope, len(lastScope)) + + for k, v := range lastScope { + newScope[k] = &Variable{ + Value: v.Value, + Name: v.Name, + Register: v.Register, + Alive: v.Alive, + } + } + + s.scopes = append(s.scopes, newScope) +} + +// popScope removes the scope at the top of the stack. +func (s *state) popScope() { + s.scopes = s.scopes[:len(s.scopes)-1] +} diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index d437cec..1d3640c 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -16,9 +16,9 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { switch t.Kind { case token.Identifier: name := t.Text() - variable, exists := f.variables[name] + variable := f.Variable(name) - if !exists { + if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } diff --git a/src/build/core/Variable.go b/src/build/core/Variable.go index 4557945..88e0109 100644 --- a/src/build/core/Variable.go +++ b/src/build/core/Variable.go @@ -12,3 +12,19 @@ type Variable struct { Register cpu.Register Alive int } + +// Variable returns the variable with the given name or `nil` if it doesn't exist. +func (s *state) Variable(name string) *Variable { + return s.Scope()[name] +} + +// VariableInRegister returns the variable that occupies the given register or `nil` if none occupy the register. +func (s *state) VariableInRegister(register cpu.Register) *Variable { + for _, v := range s.Scope() { + if v.Register == register { + return v + } + } + + return nil +} diff --git a/src/build/core/state.go b/src/build/core/state.go index 9269971..b155573 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -12,7 +12,7 @@ import ( // state is the data structure we embed in each function to preserve compilation state. type state struct { err error - variables map[string]*Variable + scopes []Scope functions map[string]*Function registerHistory []uint64 finished chan struct{} diff --git a/tests/examples_test.go b/tests/examples_test.go index f390eb2..d9f19f4 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -11,6 +11,7 @@ var examples = []struct { ExpectedExitCode int }{ {"hello", "Hello", 0}, + {"factorial", "", 120}, {"fibonacci", "", 55}, } From 24d3e8f2be49050293cd8bfbf2c98e96e17dae5c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 15 Jul 2024 16:51:36 +0200 Subject: [PATCH 0288/1012] Implemented variable scopes --- examples/factorial/factorial.q | 11 ++++++++++ src/build/core/Compare.go | 4 ++-- src/build/core/CompileAssign.go | 4 ++-- src/build/core/CompileDefinition.go | 8 ++++---- src/build/core/CompileIf.go | 9 ++++++--- src/build/core/ExecuteLeaf.go | 4 ++-- src/build/core/Function.go | 4 ++-- src/build/core/SaveRegister.go | 9 +-------- src/build/core/Scope.go | 31 +++++++++++++++++++++++++++++ src/build/core/TokenToRegister.go | 4 ++-- src/build/core/Variable.go | 16 +++++++++++++++ src/build/core/state.go | 2 +- tests/examples_test.go | 1 + 13 files changed, 81 insertions(+), 26 deletions(-) create mode 100644 examples/factorial/factorial.q create mode 100644 src/build/core/Scope.go diff --git a/examples/factorial/factorial.q b/examples/factorial/factorial.q new file mode 100644 index 0000000..f198ec1 --- /dev/null +++ b/examples/factorial/factorial.q @@ -0,0 +1,11 @@ +main() { + syscall(60, factorial(5)) +} + +factorial(x) { + if x <= 1 { + return 1 + } + + return x * factorial(x - 1) +} \ No newline at end of file diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 2e2f29b..4aab1b6 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -14,9 +14,9 @@ func (f *Function) Compare(comparison *expression.Expression) error { if left.IsLeaf() && left.Token.Kind == token.Identifier { name := left.Token.Text() - variable, exists := f.variables[name] + variable := f.Variable(name) - if !exists { + if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) } diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index ce46a34..460ef05 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -8,9 +8,9 @@ import ( // CompileAssign compiles an assign statement. func (f *Function) CompileAssign(node *ast.Assign) error { name := node.Name.Text() - variable, exists := f.variables[name] + variable := f.Variable(name) - if !exists { + if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Name.Position) } diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index c23ac56..894dd64 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -44,7 +44,7 @@ func (f *Function) AddVariable(variable *Variable) { f.Comment("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive) } - f.variables[variable.Name] = variable + f.Scope()[variable.Name] = variable f.cpu.Reserve(variable.Register) f.cpu.Use(variable.Register) } @@ -67,13 +67,13 @@ func (f *Function) useVariable(variable *Variable) { // identifierExists returns true if the identifier has been defined. func (f *Function) identifierExists(name string) bool { - _, exists := f.variables[name] + variable := f.Variable(name) - if exists { + if variable != nil { return true } - _, exists = f.functions[name] + _, exists := f.functions[name] return exists } diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 7721bf6..3146cc4 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -16,8 +16,11 @@ func (f *Function) CompileIf(branch *ast.If) error { return err } - f.AddLabel(success) - defer f.AddLabel(fail) f.count.branch++ - return f.CompileAST(branch.Body) + f.AddLabel(success) + f.pushScope() + err = f.CompileAST(branch.Body) + f.popScope() + f.AddLabel(fail) + return err } diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index c08866d..3b0c5f6 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -13,9 +13,9 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope switch operand.Kind { case token.Identifier: name := operand.Text() - variable, exists := f.variables[name] + variable := f.Variable(name) - if !exists { + if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) } diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 961f5d7..157d6ea 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -36,8 +36,8 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { Syscall: x64.SyscallRegisters, Output: x64.ReturnValueRegisters, }, - variables: map[string]*Variable{}, - finished: make(chan struct{}), + scopes: []Scope{{}}, + finished: make(chan struct{}), }, } } diff --git a/src/build/core/SaveRegister.go b/src/build/core/SaveRegister.go index 30e3c33..2b0b914 100644 --- a/src/build/core/SaveRegister.go +++ b/src/build/core/SaveRegister.go @@ -18,14 +18,7 @@ func (f *Function) SaveRegister(register cpu.Register) { } } - var variable *Variable - - for _, v := range f.variables { - if v.Register == register { - variable = v - break - } - } + variable := f.VariableInRegister(register) if variable == nil || variable.Alive == 0 { return diff --git a/src/build/core/Scope.go b/src/build/core/Scope.go new file mode 100644 index 0000000..dcf3b3c --- /dev/null +++ b/src/build/core/Scope.go @@ -0,0 +1,31 @@ +package core + +// Scope represents a map of variables. +type Scope map[string]*Variable + +// Scope returns the current scope. +func (s *state) Scope() Scope { + return s.scopes[len(s.scopes)-1] +} + +// pushScope pushes a new scope to the top of the stack. +func (s *state) pushScope() { + lastScope := s.scopes[len(s.scopes)-1] + newScope := make(Scope, len(lastScope)) + + for k, v := range lastScope { + newScope[k] = &Variable{ + Value: v.Value, + Name: v.Name, + Register: v.Register, + Alive: v.Alive, + } + } + + s.scopes = append(s.scopes, newScope) +} + +// popScope removes the scope at the top of the stack. +func (s *state) popScope() { + s.scopes = s.scopes[:len(s.scopes)-1] +} diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index d437cec..1d3640c 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -16,9 +16,9 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { switch t.Kind { case token.Identifier: name := t.Text() - variable, exists := f.variables[name] + variable := f.Variable(name) - if !exists { + if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } diff --git a/src/build/core/Variable.go b/src/build/core/Variable.go index 4557945..88e0109 100644 --- a/src/build/core/Variable.go +++ b/src/build/core/Variable.go @@ -12,3 +12,19 @@ type Variable struct { Register cpu.Register Alive int } + +// Variable returns the variable with the given name or `nil` if it doesn't exist. +func (s *state) Variable(name string) *Variable { + return s.Scope()[name] +} + +// VariableInRegister returns the variable that occupies the given register or `nil` if none occupy the register. +func (s *state) VariableInRegister(register cpu.Register) *Variable { + for _, v := range s.Scope() { + if v.Register == register { + return v + } + } + + return nil +} diff --git a/src/build/core/state.go b/src/build/core/state.go index 9269971..b155573 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -12,7 +12,7 @@ import ( // state is the data structure we embed in each function to preserve compilation state. type state struct { err error - variables map[string]*Variable + scopes []Scope functions map[string]*Function registerHistory []uint64 finished chan struct{} diff --git a/tests/examples_test.go b/tests/examples_test.go index f390eb2..d9f19f4 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -11,6 +11,7 @@ var examples = []struct { ExpectedExitCode int }{ {"hello", "Hello", 0}, + {"factorial", "", 120}, {"fibonacci", "", 55}, } From c925fe69b352a1c468fc2fdf3ceeb921983931ad Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 16 Jul 2024 11:44:10 +0200 Subject: [PATCH 0289/1012] Reordered counters --- src/build/core/CompileCondition.go | 4 ++-- src/build/core/CompileIf.go | 2 +- src/build/core/CompileLoop.go | 9 ++++++--- src/build/core/TokenToRegister.go | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/build/core/CompileCondition.go b/src/build/core/CompileCondition.go index 84c4a38..b257cee 100644 --- a/src/build/core/CompileCondition.go +++ b/src/build/core/CompileCondition.go @@ -11,8 +11,8 @@ import ( func (f *Function) CompileCondition(condition *expression.Expression, successLabel string, failLabel string) error { switch condition.Token.Text() { case "||": - leftFailLabel := fmt.Sprintf("%s_false_%d", f.Name, f.count.subBranch) f.count.subBranch++ + leftFailLabel := fmt.Sprintf("%s_false_%d", f.Name, f.count.subBranch) // Left left := condition.Children[0] @@ -38,8 +38,8 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab return err case "&&": - leftSuccessLabel := fmt.Sprintf("%s_true_%d", f.Name, f.count.subBranch) f.count.subBranch++ + leftSuccessLabel := fmt.Sprintf("%s_true_%d", f.Name, f.count.subBranch) // Left left := condition.Children[0] diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 3146cc4..14422cc 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -8,6 +8,7 @@ import ( // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { + f.count.branch++ success := fmt.Sprintf("%s_if_%d_true", f.Name, f.count.branch) fail := fmt.Sprintf("%s_if_%d_false", f.Name, f.count.branch) err := f.CompileCondition(branch.Condition, success, fail) @@ -16,7 +17,6 @@ func (f *Function) CompileIf(branch *ast.If) error { return err } - f.count.branch++ f.AddLabel(success) f.pushScope() err = f.CompileAST(branch.Body) diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index 7bf00c4..7553394 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -9,9 +9,12 @@ import ( // CompileLoop compiles a loop instruction. func (f *Function) CompileLoop(loop *ast.Loop) error { + f.count.loop++ label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) f.AddLabel(label) - defer f.Jump(asm.JUMP, label) - f.count.loop++ - return f.CompileAST(loop.Body) + f.pushScope() + err := f.CompileAST(loop.Body) + f.popScope() + f.Jump(asm.JUMP, label) + return err } diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 1d3640c..afedcb5 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -38,11 +38,11 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return nil case token.String: - value := t.Text()[1 : len(t.Bytes)-1] + f.count.data++ label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data) + value := t.Text()[1 : len(t.Bytes)-1] f.assembler.Data[label] = []byte(value) f.RegisterLabel(asm.MOVE, register, label) - f.count.data++ return nil default: From 448af0707afab634a9e73a97e4485e8688e3f9a5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 16 Jul 2024 11:44:10 +0200 Subject: [PATCH 0290/1012] Reordered counters --- src/build/core/CompileCondition.go | 4 ++-- src/build/core/CompileIf.go | 2 +- src/build/core/CompileLoop.go | 9 ++++++--- src/build/core/TokenToRegister.go | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/build/core/CompileCondition.go b/src/build/core/CompileCondition.go index 84c4a38..b257cee 100644 --- a/src/build/core/CompileCondition.go +++ b/src/build/core/CompileCondition.go @@ -11,8 +11,8 @@ import ( func (f *Function) CompileCondition(condition *expression.Expression, successLabel string, failLabel string) error { switch condition.Token.Text() { case "||": - leftFailLabel := fmt.Sprintf("%s_false_%d", f.Name, f.count.subBranch) f.count.subBranch++ + leftFailLabel := fmt.Sprintf("%s_false_%d", f.Name, f.count.subBranch) // Left left := condition.Children[0] @@ -38,8 +38,8 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab return err case "&&": - leftSuccessLabel := fmt.Sprintf("%s_true_%d", f.Name, f.count.subBranch) f.count.subBranch++ + leftSuccessLabel := fmt.Sprintf("%s_true_%d", f.Name, f.count.subBranch) // Left left := condition.Children[0] diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 3146cc4..14422cc 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -8,6 +8,7 @@ import ( // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { + f.count.branch++ success := fmt.Sprintf("%s_if_%d_true", f.Name, f.count.branch) fail := fmt.Sprintf("%s_if_%d_false", f.Name, f.count.branch) err := f.CompileCondition(branch.Condition, success, fail) @@ -16,7 +17,6 @@ func (f *Function) CompileIf(branch *ast.If) error { return err } - f.count.branch++ f.AddLabel(success) f.pushScope() err = f.CompileAST(branch.Body) diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index 7bf00c4..7553394 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -9,9 +9,12 @@ import ( // CompileLoop compiles a loop instruction. func (f *Function) CompileLoop(loop *ast.Loop) error { + f.count.loop++ label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) f.AddLabel(label) - defer f.Jump(asm.JUMP, label) - f.count.loop++ - return f.CompileAST(loop.Body) + f.pushScope() + err := f.CompileAST(loop.Body) + f.popScope() + f.Jump(asm.JUMP, label) + return err } diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 1d3640c..afedcb5 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -38,11 +38,11 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return nil case token.String: - value := t.Text()[1 : len(t.Bytes)-1] + f.count.data++ label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data) + value := t.Text()[1 : len(t.Bytes)-1] f.assembler.Data[label] = []byte(value) f.RegisterLabel(asm.MOVE, register, label) - f.count.data++ return nil default: From 3bd5b20af3867dbda92f2ef8a2eb793432096ecb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 16 Jul 2024 12:01:38 +0200 Subject: [PATCH 0291/1012] Simplified block parsing --- src/build/ast/Parse.go | 70 ++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index aa4eeef..eaaad01 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -27,45 +27,38 @@ func Parse(tokens token.List) (AST, error) { // toASTNode generates an AST node from an instruction. func toASTNode(tokens token.List) (Node, error) { if tokens[0].Kind == token.Keyword { - switch tokens[0].Text() { - case keyword.Return: + word := tokens[0].Text() + + if word == keyword.Return { value := expression.Parse(tokens[1:]) return &Return{Value: value}, nil - - case keyword.Loop: - blockStart := tokens.IndexKind(token.BlockStart) - blockEnd := tokens.LastIndexKind(token.BlockEnd) - - if blockStart == -1 { - return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) - } - - if blockEnd == -1 { - return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) - } - - body, err := Parse(tokens[blockStart+1 : blockEnd]) - return &Loop{Body: body}, err - - case keyword.If: - blockStart := tokens.IndexKind(token.BlockStart) - blockEnd := tokens.LastIndexKind(token.BlockEnd) - - if blockStart == -1 { - return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) - } - - if blockEnd == -1 { - return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) - } - - condition := expression.Parse(tokens[1:blockStart]) - body, err := Parse(tokens[blockStart+1 : blockEnd]) - return &If{Condition: condition, Body: body}, err - - default: - return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, nil, tokens[0].Position) } + + if keywordHasBlock(word) { + blockStart := tokens.IndexKind(token.BlockStart) + blockEnd := tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) + } + + if blockEnd == -1 { + return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) + } + + body, err := Parse(tokens[blockStart+1 : blockEnd]) + + switch word { + case keyword.If: + condition := expression.Parse(tokens[1:blockStart]) + return &If{Condition: condition, Body: body}, err + + case keyword.Loop: + return &Loop{Body: body}, err + } + } + + return nil, errors.New(&errors.KeywordNotImplemented{Keyword: word}, nil, tokens[0].Position) } expr := expression.Parse(tokens) @@ -115,3 +108,8 @@ func IsFunctionCall(expr *expression.Expression) bool { func IsVariableDefinition(expr *expression.Expression) bool { return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" } + +// keywordHasBlock returns true if the keyword requires a block. +func keywordHasBlock(word string) bool { + return word == keyword.If || word == keyword.Loop +} From d1ccd6013980171d464f80d9ffd6f419528bea5f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 16 Jul 2024 12:01:38 +0200 Subject: [PATCH 0292/1012] Simplified block parsing --- src/build/ast/Parse.go | 70 ++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index aa4eeef..eaaad01 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -27,45 +27,38 @@ func Parse(tokens token.List) (AST, error) { // toASTNode generates an AST node from an instruction. func toASTNode(tokens token.List) (Node, error) { if tokens[0].Kind == token.Keyword { - switch tokens[0].Text() { - case keyword.Return: + word := tokens[0].Text() + + if word == keyword.Return { value := expression.Parse(tokens[1:]) return &Return{Value: value}, nil - - case keyword.Loop: - blockStart := tokens.IndexKind(token.BlockStart) - blockEnd := tokens.LastIndexKind(token.BlockEnd) - - if blockStart == -1 { - return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) - } - - if blockEnd == -1 { - return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) - } - - body, err := Parse(tokens[blockStart+1 : blockEnd]) - return &Loop{Body: body}, err - - case keyword.If: - blockStart := tokens.IndexKind(token.BlockStart) - blockEnd := tokens.LastIndexKind(token.BlockEnd) - - if blockStart == -1 { - return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) - } - - if blockEnd == -1 { - return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) - } - - condition := expression.Parse(tokens[1:blockStart]) - body, err := Parse(tokens[blockStart+1 : blockEnd]) - return &If{Condition: condition, Body: body}, err - - default: - return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, nil, tokens[0].Position) } + + if keywordHasBlock(word) { + blockStart := tokens.IndexKind(token.BlockStart) + blockEnd := tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) + } + + if blockEnd == -1 { + return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) + } + + body, err := Parse(tokens[blockStart+1 : blockEnd]) + + switch word { + case keyword.If: + condition := expression.Parse(tokens[1:blockStart]) + return &If{Condition: condition, Body: body}, err + + case keyword.Loop: + return &Loop{Body: body}, err + } + } + + return nil, errors.New(&errors.KeywordNotImplemented{Keyword: word}, nil, tokens[0].Position) } expr := expression.Parse(tokens) @@ -115,3 +108,8 @@ func IsFunctionCall(expr *expression.Expression) bool { func IsVariableDefinition(expr *expression.Expression) bool { return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" } + +// keywordHasBlock returns true if the keyword requires a block. +func keywordHasBlock(word string) bool { + return word == keyword.If || word == keyword.Loop +} From 545c8dd4f6368af0c1c4b5d56cb946e42bb1998b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 16 Jul 2024 15:30:28 +0200 Subject: [PATCH 0293/1012] Moved register state to scopes --- src/build/asm/Finalize.go | 2 +- src/build/ast/Count.go | 36 +++++++++++++++ src/build/core/Compare.go | 4 +- src/build/core/CompileCall.go | 4 +- src/build/core/CompileDefinition.go | 48 +++++++++----------- src/build/core/CompileIf.go | 2 +- src/build/core/CompileLoop.go | 2 +- src/build/core/Execute.go | 4 +- src/build/core/ExpressionToRegister.go | 6 +-- src/build/core/Function.go | 10 +++-- src/build/core/Instructions.go | 20 ++++----- src/build/core/SaveRegister.go | 6 +-- src/build/core/Scope.go | 42 +++++++++++------ src/build/core/Variable.go | 4 +- src/build/core/state.go | 2 +- src/build/cpu/CPU.go | 60 +++---------------------- src/build/cpu/State.go | 55 +++++++++++++++++++++++ src/build/expression/Expression.go | 15 +++++++ src/build/expression/Expression_test.go | 13 +++++- src/build/scanner/Scan.go | 2 +- src/build/token/Count.go | 14 ++++++ src/build/token/Token_test.go | 8 ++++ 22 files changed, 230 insertions(+), 129 deletions(-) create mode 100644 src/build/ast/Count.go create mode 100644 src/build/cpu/State.go create mode 100644 src/build/token/Count.go diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index b669153..415d594 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -169,7 +169,7 @@ func (a Assembler) Finalize() ([]byte, []byte) { code = x64.Syscall(code) default: - panic("Unknown mnemonic: " + x.Mnemonic.String()) + panic("unknown mnemonic: " + x.Mnemonic.String()) } } diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go new file mode 100644 index 0000000..b14482d --- /dev/null +++ b/src/build/ast/Count.go @@ -0,0 +1,36 @@ +package ast + +import "git.akyoto.dev/cli/q/src/build/token" + +// Count counts how often the given token appears in the AST. +func Count(body AST, kind token.Kind, name string) int { + count := 0 + + for _, node := range body { + switch node := node.(type) { + case *Assign: + count += node.Value.Count(kind, name) + + case *Call: + count += node.Expression.Count(kind, name) + + case *Define: + count += node.Value.Count(kind, name) + + case *Return: + count += node.Value.Count(kind, name) + + case *If: + count += node.Condition.Count(kind, name) + count += Count(node.Body, kind, name) + + case *Loop: + count += Count(node.Body, kind, name) + + default: + panic("unknown AST type") + } + } + + return count +} diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 4aab1b6..ca19ada 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -34,13 +34,13 @@ func (f *Function) Compare(comparison *expression.Expression) error { return f.Execute(comparison.Token, f.cpu.Output[0], right) } - tmp := f.cpu.MustFindFree(f.cpu.General) + tmp := f.Scope().MustFindFree(f.cpu.General) err := f.ExpressionToRegister(left, tmp) if err != nil { return err } - defer f.cpu.Free(tmp) + defer f.Scope().Free(tmp) return f.Execute(comparison.Token, tmp, right) } diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 6ed36de..f7b9dfb 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -39,7 +39,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { // Push for _, register := range f.cpu.General { - if f.cpu.IsUsed(register) { + if f.Scope().IsUsed(register) { f.Register(asm.PUSH, register) } } @@ -55,7 +55,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { for i := len(f.cpu.General) - 1; i >= 0; i-- { register := f.cpu.General[i] - if f.cpu.IsUsed(register) { + if f.Scope().IsUsed(register) { f.Register(asm.POP, register) } } diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 894dd64..6a481a9 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -16,7 +16,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, node.Name.Position) } - uses := CountIdentifier(f.Body, name) - 1 + uses := token.Count(f.Body, token.Identifier, name) - 1 if uses == 0 { return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) @@ -44,24 +44,30 @@ func (f *Function) AddVariable(variable *Variable) { f.Comment("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive) } - f.Scope()[variable.Name] = variable - f.cpu.Reserve(variable.Register) - f.cpu.Use(variable.Register) + f.Scope().variables[variable.Name] = variable + f.Scope().Reserve(variable.Register) + f.Scope().Use(variable.Register) } func (f *Function) useVariable(variable *Variable) { - variable.Alive-- + for _, scope := range f.scopes { + local := scope.variables[variable.Name] - if variable.Alive < 0 { - panic("incorrect number of variable use calls") - } - - if variable.Alive == 0 { - if config.Comments { - f.Comment("%s died (%s)", variable.Name, variable.Register) + if local != nil { + local.Alive-- } - f.cpu.Free(variable.Register) + if local.Alive < 0 { + panic("incorrect number of variable use calls") + } + + if local.Alive == 0 { + if config.Comments { + f.Comment("%s died (%s)", local.Name, local.Register) + } + + scope.Free(local.Register) + } } } @@ -78,13 +84,13 @@ func (f *Function) identifierExists(name string) bool { } func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error { - reg, exists := f.cpu.FindFree(f.cpu.General) + reg, exists := f.Scope().FindFree(f.cpu.General) if !exists { panic("no free registers") } - f.cpu.Reserve(reg) + f.Scope().Reserve(reg) err := f.ExpressionToRegister(value, reg) f.AddVariable(&Variable{ @@ -95,15 +101,3 @@ func (f *Function) storeVariableInRegister(name string, value *expression.Expres return err } - -func CountIdentifier(tokens token.List, name string) int { - count := 0 - - for _, t := range tokens { - if t.Kind == token.Identifier && t.Text() == name { - count++ - } - } - - return count -} diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 14422cc..4d63a44 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -18,7 +18,7 @@ func (f *Function) CompileIf(branch *ast.If) error { } f.AddLabel(success) - f.pushScope() + f.pushScope(branch.Body) err = f.CompileAST(branch.Body) f.popScope() f.AddLabel(fail) diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index 7553394..6a798e5 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -12,7 +12,7 @@ func (f *Function) CompileLoop(loop *ast.Loop) error { f.count.loop++ label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) f.AddLabel(label) - f.pushScope() + f.pushScope(loop.Body) err := f.CompileAST(loop.Body) f.popScope() f.Jump(asm.JUMP, label) diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index b401dab..22d687a 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -23,8 +23,8 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteRegisterRegister(operation, register, f.cpu.Output[0]) } - tmp := f.cpu.MustFindFree(f.cpu.General) - defer f.cpu.Free(tmp) + tmp := f.Scope().MustFindFree(f.cpu.General) + defer f.Scope().Free(tmp) err := f.ExpressionToRegister(value, tmp) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index acc211d..9de8671 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -34,14 +34,14 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp final := register if f.UsesRegister(right, register) { - register = f.cpu.MustFindFree(f.cpu.General) + register = f.Scope().MustFindFree(f.cpu.General) if config.Comments { f.Comment("temporary register %s", register) } } - f.cpu.Reserve(register) + f.Scope().Reserve(register) err := f.ExpressionToRegister(left, register) if err != nil { @@ -52,7 +52,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if register != final { f.RegisterRegister(asm.MOVE, final, register) - f.cpu.Free(register) + f.Scope().Free(register) } return err diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 157d6ea..b5c3c67 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -36,7 +36,9 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { Syscall: x64.SyscallRegisters, Output: x64.ReturnValueRegisters, }, - scopes: []Scope{{}}, + scopes: []*Scope{ + {variables: map[string]*Variable{}}, + }, finished: make(chan struct{}), }, } @@ -52,14 +54,14 @@ func (f *Function) Compile() { // CompileTokens compiles a token list. func (f *Function) CompileTokens(tokens token.List) error { - tree, err := ast.Parse(tokens) + body, err := ast.Parse(tokens) if err != nil { err.(*errors.Error).File = f.File return err } - return f.CompileAST(tree) + return f.CompileAST(body) } // CompileAST compiles an abstract syntax tree. @@ -97,7 +99,7 @@ func (f *Function) CompileASTNode(node ast.Node) error { return f.CompileLoop(node) default: - panic("Unknown AST type") + panic("unknown AST type") } } diff --git a/src/build/core/Instructions.go b/src/build/core/Instructions.go index 6f4da46..7a35222 100644 --- a/src/build/core/Instructions.go +++ b/src/build/core/Instructions.go @@ -15,7 +15,7 @@ func (f *Function) AddLabel(label string) { func (f *Function) Call(label string) { f.assembler.Call(label) - f.cpu.Use(f.cpu.Output[0]) + f.Scope().Use(f.cpu.Output[0]) f.postInstruction() } @@ -33,35 +33,35 @@ func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) { f.assembler.Register(mnemonic, a) if mnemonic == asm.POP { - f.cpu.Use(a) + f.Scope().Use(a) } f.postInstruction() } func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { - if f.cpu.IsUsed(a) && isDestructive(mnemonic) { + if f.Scope().IsUsed(a) && isDestructive(mnemonic) { f.SaveRegister(a) } f.assembler.RegisterNumber(mnemonic, a, b) if mnemonic == asm.MOVE { - f.cpu.Use(a) + f.Scope().Use(a) } f.postInstruction() } func (f *Function) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { - if f.cpu.IsUsed(register) && isDestructive(mnemonic) { + if f.Scope().IsUsed(register) && isDestructive(mnemonic) { f.SaveRegister(register) } f.assembler.RegisterLabel(mnemonic, register, label) if mnemonic == asm.MOVE { - f.cpu.Use(register) + f.Scope().Use(register) } f.postInstruction() @@ -72,14 +72,14 @@ func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu return } - if f.cpu.IsUsed(a) && isDestructive(mnemonic) { + if f.Scope().IsUsed(a) && isDestructive(mnemonic) { f.SaveRegister(a) } f.assembler.RegisterRegister(mnemonic, a, b) if mnemonic == asm.MOVE { - f.cpu.Use(a) + f.Scope().Use(a) } f.postInstruction() @@ -92,7 +92,7 @@ func (f *Function) Return() { func (f *Function) Syscall() { f.assembler.Syscall() - f.cpu.Use(f.cpu.Output[0]) + f.Scope().Use(f.cpu.Output[0]) f.postInstruction() } @@ -101,7 +101,7 @@ func (f *Function) postInstruction() { return } - f.registerHistory = append(f.registerHistory, f.cpu.Used) + f.registerHistory = append(f.registerHistory, f.Scope().Used) } func isDestructive(mnemonic asm.Mnemonic) bool { diff --git a/src/build/core/SaveRegister.go b/src/build/core/SaveRegister.go index 2b0b914..107d344 100644 --- a/src/build/core/SaveRegister.go +++ b/src/build/core/SaveRegister.go @@ -8,7 +8,7 @@ import ( // SaveRegister attempts to move a variable occupying this register to another register. func (f *Function) SaveRegister(register cpu.Register) { - if !f.cpu.IsUsed(register) { + if !f.Scope().IsUsed(register) { return } @@ -24,8 +24,8 @@ func (f *Function) SaveRegister(register cpu.Register) { return } - newRegister := f.cpu.MustFindFree(f.cpu.General) - f.cpu.Reserve(newRegister) + newRegister := f.Scope().MustFindFree(f.cpu.General) + f.Scope().Reserve(newRegister) if config.Comments { f.Comment("save %s to %s", register, newRegister) diff --git a/src/build/core/Scope.go b/src/build/core/Scope.go index dcf3b3c..db9f6bd 100644 --- a/src/build/core/Scope.go +++ b/src/build/core/Scope.go @@ -1,28 +1,44 @@ package core -// Scope represents a map of variables. -type Scope map[string]*Variable +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Scope represents an independent code block. +type Scope struct { + cpu.State + variables map[string]*Variable +} // Scope returns the current scope. -func (s *state) Scope() Scope { +func (s *state) Scope() *Scope { return s.scopes[len(s.scopes)-1] } // pushScope pushes a new scope to the top of the stack. -func (s *state) pushScope() { - lastScope := s.scopes[len(s.scopes)-1] - newScope := make(Scope, len(lastScope)) +func (s *state) pushScope(body ast.AST) { + scope := &Scope{} - for k, v := range lastScope { - newScope[k] = &Variable{ - Value: v.Value, - Name: v.Name, - Register: v.Register, - Alive: v.Alive, + if len(s.scopes) > 0 { + lastScope := s.scopes[len(s.scopes)-1] + scope.State = lastScope.State + scope.variables = make(map[string]*Variable, len(lastScope.variables)) + + for k, v := range lastScope.variables { + scope.variables[k] = &Variable{ + Value: v.Value, + Name: v.Name, + Register: v.Register, + Alive: ast.Count(body, token.Identifier, v.Name), + } } + } else { + scope.variables = map[string]*Variable{} } - s.scopes = append(s.scopes, newScope) + s.scopes = append(s.scopes, scope) } // popScope removes the scope at the top of the stack. diff --git a/src/build/core/Variable.go b/src/build/core/Variable.go index 88e0109..b7dd6ad 100644 --- a/src/build/core/Variable.go +++ b/src/build/core/Variable.go @@ -15,12 +15,12 @@ type Variable struct { // Variable returns the variable with the given name or `nil` if it doesn't exist. func (s *state) Variable(name string) *Variable { - return s.Scope()[name] + return s.Scope().variables[name] } // VariableInRegister returns the variable that occupies the given register or `nil` if none occupy the register. func (s *state) VariableInRegister(register cpu.Register) *Variable { - for _, v := range s.Scope() { + for _, v := range s.Scope().variables { if v.Register == register { return v } diff --git a/src/build/core/state.go b/src/build/core/state.go index b155573..b345c69 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -12,7 +12,7 @@ import ( // state is the data structure we embed in each function to preserve compilation state. type state struct { err error - scopes []Scope + scopes []*Scope functions map[string]*Function registerHistory []uint64 finished chan struct{} diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go index da53e10..9edbc06 100644 --- a/src/build/cpu/CPU.go +++ b/src/build/cpu/CPU.go @@ -2,59 +2,9 @@ package cpu // CPU represents the processor. type CPU struct { - All []Register - General []Register - Syscall []Register - Input []Register - Output []Register - Reserved uint64 - Used uint64 -} - -// Free will reset the reserved and used status which means the register can be allocated again. -func (c *CPU) Free(reg Register) { - c.Used &= ^(1 << reg) - c.Reserved &= ^(1 << reg) -} - -// IsReserved returns true if the register was marked for future use. -func (c *CPU) IsReserved(reg Register) bool { - return c.Reserved&(1< Date: Tue, 16 Jul 2024 15:30:28 +0200 Subject: [PATCH 0294/1012] Moved register state to scopes --- src/build/asm/Finalize.go | 2 +- src/build/ast/Count.go | 36 +++++++++++++++ src/build/core/Compare.go | 4 +- src/build/core/CompileCall.go | 4 +- src/build/core/CompileDefinition.go | 48 +++++++++----------- src/build/core/CompileIf.go | 2 +- src/build/core/CompileLoop.go | 2 +- src/build/core/Execute.go | 4 +- src/build/core/ExpressionToRegister.go | 6 +-- src/build/core/Function.go | 10 +++-- src/build/core/Instructions.go | 20 ++++----- src/build/core/SaveRegister.go | 6 +-- src/build/core/Scope.go | 42 +++++++++++------ src/build/core/Variable.go | 4 +- src/build/core/state.go | 2 +- src/build/cpu/CPU.go | 60 +++---------------------- src/build/cpu/State.go | 55 +++++++++++++++++++++++ src/build/expression/Expression.go | 15 +++++++ src/build/expression/Expression_test.go | 13 +++++- src/build/scanner/Scan.go | 2 +- src/build/token/Count.go | 14 ++++++ src/build/token/Token_test.go | 8 ++++ 22 files changed, 230 insertions(+), 129 deletions(-) create mode 100644 src/build/ast/Count.go create mode 100644 src/build/cpu/State.go create mode 100644 src/build/token/Count.go diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index b669153..415d594 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -169,7 +169,7 @@ func (a Assembler) Finalize() ([]byte, []byte) { code = x64.Syscall(code) default: - panic("Unknown mnemonic: " + x.Mnemonic.String()) + panic("unknown mnemonic: " + x.Mnemonic.String()) } } diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go new file mode 100644 index 0000000..b14482d --- /dev/null +++ b/src/build/ast/Count.go @@ -0,0 +1,36 @@ +package ast + +import "git.akyoto.dev/cli/q/src/build/token" + +// Count counts how often the given token appears in the AST. +func Count(body AST, kind token.Kind, name string) int { + count := 0 + + for _, node := range body { + switch node := node.(type) { + case *Assign: + count += node.Value.Count(kind, name) + + case *Call: + count += node.Expression.Count(kind, name) + + case *Define: + count += node.Value.Count(kind, name) + + case *Return: + count += node.Value.Count(kind, name) + + case *If: + count += node.Condition.Count(kind, name) + count += Count(node.Body, kind, name) + + case *Loop: + count += Count(node.Body, kind, name) + + default: + panic("unknown AST type") + } + } + + return count +} diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 4aab1b6..ca19ada 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -34,13 +34,13 @@ func (f *Function) Compare(comparison *expression.Expression) error { return f.Execute(comparison.Token, f.cpu.Output[0], right) } - tmp := f.cpu.MustFindFree(f.cpu.General) + tmp := f.Scope().MustFindFree(f.cpu.General) err := f.ExpressionToRegister(left, tmp) if err != nil { return err } - defer f.cpu.Free(tmp) + defer f.Scope().Free(tmp) return f.Execute(comparison.Token, tmp, right) } diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 6ed36de..f7b9dfb 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -39,7 +39,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { // Push for _, register := range f.cpu.General { - if f.cpu.IsUsed(register) { + if f.Scope().IsUsed(register) { f.Register(asm.PUSH, register) } } @@ -55,7 +55,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { for i := len(f.cpu.General) - 1; i >= 0; i-- { register := f.cpu.General[i] - if f.cpu.IsUsed(register) { + if f.Scope().IsUsed(register) { f.Register(asm.POP, register) } } diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 894dd64..6a481a9 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -16,7 +16,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, node.Name.Position) } - uses := CountIdentifier(f.Body, name) - 1 + uses := token.Count(f.Body, token.Identifier, name) - 1 if uses == 0 { return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) @@ -44,24 +44,30 @@ func (f *Function) AddVariable(variable *Variable) { f.Comment("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive) } - f.Scope()[variable.Name] = variable - f.cpu.Reserve(variable.Register) - f.cpu.Use(variable.Register) + f.Scope().variables[variable.Name] = variable + f.Scope().Reserve(variable.Register) + f.Scope().Use(variable.Register) } func (f *Function) useVariable(variable *Variable) { - variable.Alive-- + for _, scope := range f.scopes { + local := scope.variables[variable.Name] - if variable.Alive < 0 { - panic("incorrect number of variable use calls") - } - - if variable.Alive == 0 { - if config.Comments { - f.Comment("%s died (%s)", variable.Name, variable.Register) + if local != nil { + local.Alive-- } - f.cpu.Free(variable.Register) + if local.Alive < 0 { + panic("incorrect number of variable use calls") + } + + if local.Alive == 0 { + if config.Comments { + f.Comment("%s died (%s)", local.Name, local.Register) + } + + scope.Free(local.Register) + } } } @@ -78,13 +84,13 @@ func (f *Function) identifierExists(name string) bool { } func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error { - reg, exists := f.cpu.FindFree(f.cpu.General) + reg, exists := f.Scope().FindFree(f.cpu.General) if !exists { panic("no free registers") } - f.cpu.Reserve(reg) + f.Scope().Reserve(reg) err := f.ExpressionToRegister(value, reg) f.AddVariable(&Variable{ @@ -95,15 +101,3 @@ func (f *Function) storeVariableInRegister(name string, value *expression.Expres return err } - -func CountIdentifier(tokens token.List, name string) int { - count := 0 - - for _, t := range tokens { - if t.Kind == token.Identifier && t.Text() == name { - count++ - } - } - - return count -} diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 14422cc..4d63a44 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -18,7 +18,7 @@ func (f *Function) CompileIf(branch *ast.If) error { } f.AddLabel(success) - f.pushScope() + f.pushScope(branch.Body) err = f.CompileAST(branch.Body) f.popScope() f.AddLabel(fail) diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index 7553394..6a798e5 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -12,7 +12,7 @@ func (f *Function) CompileLoop(loop *ast.Loop) error { f.count.loop++ label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) f.AddLabel(label) - f.pushScope() + f.pushScope(loop.Body) err := f.CompileAST(loop.Body) f.popScope() f.Jump(asm.JUMP, label) diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index b401dab..22d687a 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -23,8 +23,8 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteRegisterRegister(operation, register, f.cpu.Output[0]) } - tmp := f.cpu.MustFindFree(f.cpu.General) - defer f.cpu.Free(tmp) + tmp := f.Scope().MustFindFree(f.cpu.General) + defer f.Scope().Free(tmp) err := f.ExpressionToRegister(value, tmp) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index acc211d..9de8671 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -34,14 +34,14 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp final := register if f.UsesRegister(right, register) { - register = f.cpu.MustFindFree(f.cpu.General) + register = f.Scope().MustFindFree(f.cpu.General) if config.Comments { f.Comment("temporary register %s", register) } } - f.cpu.Reserve(register) + f.Scope().Reserve(register) err := f.ExpressionToRegister(left, register) if err != nil { @@ -52,7 +52,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if register != final { f.RegisterRegister(asm.MOVE, final, register) - f.cpu.Free(register) + f.Scope().Free(register) } return err diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 157d6ea..b5c3c67 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -36,7 +36,9 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { Syscall: x64.SyscallRegisters, Output: x64.ReturnValueRegisters, }, - scopes: []Scope{{}}, + scopes: []*Scope{ + {variables: map[string]*Variable{}}, + }, finished: make(chan struct{}), }, } @@ -52,14 +54,14 @@ func (f *Function) Compile() { // CompileTokens compiles a token list. func (f *Function) CompileTokens(tokens token.List) error { - tree, err := ast.Parse(tokens) + body, err := ast.Parse(tokens) if err != nil { err.(*errors.Error).File = f.File return err } - return f.CompileAST(tree) + return f.CompileAST(body) } // CompileAST compiles an abstract syntax tree. @@ -97,7 +99,7 @@ func (f *Function) CompileASTNode(node ast.Node) error { return f.CompileLoop(node) default: - panic("Unknown AST type") + panic("unknown AST type") } } diff --git a/src/build/core/Instructions.go b/src/build/core/Instructions.go index 6f4da46..7a35222 100644 --- a/src/build/core/Instructions.go +++ b/src/build/core/Instructions.go @@ -15,7 +15,7 @@ func (f *Function) AddLabel(label string) { func (f *Function) Call(label string) { f.assembler.Call(label) - f.cpu.Use(f.cpu.Output[0]) + f.Scope().Use(f.cpu.Output[0]) f.postInstruction() } @@ -33,35 +33,35 @@ func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) { f.assembler.Register(mnemonic, a) if mnemonic == asm.POP { - f.cpu.Use(a) + f.Scope().Use(a) } f.postInstruction() } func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { - if f.cpu.IsUsed(a) && isDestructive(mnemonic) { + if f.Scope().IsUsed(a) && isDestructive(mnemonic) { f.SaveRegister(a) } f.assembler.RegisterNumber(mnemonic, a, b) if mnemonic == asm.MOVE { - f.cpu.Use(a) + f.Scope().Use(a) } f.postInstruction() } func (f *Function) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { - if f.cpu.IsUsed(register) && isDestructive(mnemonic) { + if f.Scope().IsUsed(register) && isDestructive(mnemonic) { f.SaveRegister(register) } f.assembler.RegisterLabel(mnemonic, register, label) if mnemonic == asm.MOVE { - f.cpu.Use(register) + f.Scope().Use(register) } f.postInstruction() @@ -72,14 +72,14 @@ func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu return } - if f.cpu.IsUsed(a) && isDestructive(mnemonic) { + if f.Scope().IsUsed(a) && isDestructive(mnemonic) { f.SaveRegister(a) } f.assembler.RegisterRegister(mnemonic, a, b) if mnemonic == asm.MOVE { - f.cpu.Use(a) + f.Scope().Use(a) } f.postInstruction() @@ -92,7 +92,7 @@ func (f *Function) Return() { func (f *Function) Syscall() { f.assembler.Syscall() - f.cpu.Use(f.cpu.Output[0]) + f.Scope().Use(f.cpu.Output[0]) f.postInstruction() } @@ -101,7 +101,7 @@ func (f *Function) postInstruction() { return } - f.registerHistory = append(f.registerHistory, f.cpu.Used) + f.registerHistory = append(f.registerHistory, f.Scope().Used) } func isDestructive(mnemonic asm.Mnemonic) bool { diff --git a/src/build/core/SaveRegister.go b/src/build/core/SaveRegister.go index 2b0b914..107d344 100644 --- a/src/build/core/SaveRegister.go +++ b/src/build/core/SaveRegister.go @@ -8,7 +8,7 @@ import ( // SaveRegister attempts to move a variable occupying this register to another register. func (f *Function) SaveRegister(register cpu.Register) { - if !f.cpu.IsUsed(register) { + if !f.Scope().IsUsed(register) { return } @@ -24,8 +24,8 @@ func (f *Function) SaveRegister(register cpu.Register) { return } - newRegister := f.cpu.MustFindFree(f.cpu.General) - f.cpu.Reserve(newRegister) + newRegister := f.Scope().MustFindFree(f.cpu.General) + f.Scope().Reserve(newRegister) if config.Comments { f.Comment("save %s to %s", register, newRegister) diff --git a/src/build/core/Scope.go b/src/build/core/Scope.go index dcf3b3c..db9f6bd 100644 --- a/src/build/core/Scope.go +++ b/src/build/core/Scope.go @@ -1,28 +1,44 @@ package core -// Scope represents a map of variables. -type Scope map[string]*Variable +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Scope represents an independent code block. +type Scope struct { + cpu.State + variables map[string]*Variable +} // Scope returns the current scope. -func (s *state) Scope() Scope { +func (s *state) Scope() *Scope { return s.scopes[len(s.scopes)-1] } // pushScope pushes a new scope to the top of the stack. -func (s *state) pushScope() { - lastScope := s.scopes[len(s.scopes)-1] - newScope := make(Scope, len(lastScope)) +func (s *state) pushScope(body ast.AST) { + scope := &Scope{} - for k, v := range lastScope { - newScope[k] = &Variable{ - Value: v.Value, - Name: v.Name, - Register: v.Register, - Alive: v.Alive, + if len(s.scopes) > 0 { + lastScope := s.scopes[len(s.scopes)-1] + scope.State = lastScope.State + scope.variables = make(map[string]*Variable, len(lastScope.variables)) + + for k, v := range lastScope.variables { + scope.variables[k] = &Variable{ + Value: v.Value, + Name: v.Name, + Register: v.Register, + Alive: ast.Count(body, token.Identifier, v.Name), + } } + } else { + scope.variables = map[string]*Variable{} } - s.scopes = append(s.scopes, newScope) + s.scopes = append(s.scopes, scope) } // popScope removes the scope at the top of the stack. diff --git a/src/build/core/Variable.go b/src/build/core/Variable.go index 88e0109..b7dd6ad 100644 --- a/src/build/core/Variable.go +++ b/src/build/core/Variable.go @@ -15,12 +15,12 @@ type Variable struct { // Variable returns the variable with the given name or `nil` if it doesn't exist. func (s *state) Variable(name string) *Variable { - return s.Scope()[name] + return s.Scope().variables[name] } // VariableInRegister returns the variable that occupies the given register or `nil` if none occupy the register. func (s *state) VariableInRegister(register cpu.Register) *Variable { - for _, v := range s.Scope() { + for _, v := range s.Scope().variables { if v.Register == register { return v } diff --git a/src/build/core/state.go b/src/build/core/state.go index b155573..b345c69 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -12,7 +12,7 @@ import ( // state is the data structure we embed in each function to preserve compilation state. type state struct { err error - scopes []Scope + scopes []*Scope functions map[string]*Function registerHistory []uint64 finished chan struct{} diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go index da53e10..9edbc06 100644 --- a/src/build/cpu/CPU.go +++ b/src/build/cpu/CPU.go @@ -2,59 +2,9 @@ package cpu // CPU represents the processor. type CPU struct { - All []Register - General []Register - Syscall []Register - Input []Register - Output []Register - Reserved uint64 - Used uint64 -} - -// Free will reset the reserved and used status which means the register can be allocated again. -func (c *CPU) Free(reg Register) { - c.Used &= ^(1 << reg) - c.Reserved &= ^(1 << reg) -} - -// IsReserved returns true if the register was marked for future use. -func (c *CPU) IsReserved(reg Register) bool { - return c.Reserved&(1< Date: Tue, 16 Jul 2024 15:39:32 +0200 Subject: [PATCH 0295/1012] Removed unused variables from scopes --- src/build/core/Scope.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/build/core/Scope.go b/src/build/core/Scope.go index db9f6bd..a88187b 100644 --- a/src/build/core/Scope.go +++ b/src/build/core/Scope.go @@ -27,11 +27,17 @@ func (s *state) pushScope(body ast.AST) { scope.variables = make(map[string]*Variable, len(lastScope.variables)) for k, v := range lastScope.variables { + count := ast.Count(body, token.Identifier, v.Name) + + if count == 0 { + continue + } + scope.variables[k] = &Variable{ Value: v.Value, Name: v.Name, Register: v.Register, - Alive: ast.Count(body, token.Identifier, v.Name), + Alive: count, } } } else { From 1825a72f8c2bbf5f212be8956f549051603f5099 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 16 Jul 2024 15:39:32 +0200 Subject: [PATCH 0296/1012] Removed unused variables from scopes --- src/build/core/Scope.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/build/core/Scope.go b/src/build/core/Scope.go index db9f6bd..a88187b 100644 --- a/src/build/core/Scope.go +++ b/src/build/core/Scope.go @@ -27,11 +27,17 @@ func (s *state) pushScope(body ast.AST) { scope.variables = make(map[string]*Variable, len(lastScope.variables)) for k, v := range lastScope.variables { + count := ast.Count(body, token.Identifier, v.Name) + + if count == 0 { + continue + } + scope.variables[k] = &Variable{ Value: v.Value, Name: v.Name, Register: v.Register, - Alive: ast.Count(body, token.Identifier, v.Name), + Alive: count, } } } else { From c2b42fe2ea75bff99f668807e7e135fb78744fc6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 16 Jul 2024 20:22:28 +0200 Subject: [PATCH 0297/1012] Fixed variable lifetime in loops --- src/build/asm/Instructions.go | 8 ++++++-- src/build/ast/Count.go | 8 +++++++- src/build/ast/Parse.go | 4 ++++ src/build/core/CompileCall.go | 4 ++++ src/build/core/CompileDefinition.go | 23 ++++++++++++++++------- src/build/core/CompileLoop.go | 5 +++-- src/build/core/Scope.go | 25 +++++++++++++++++++------ src/build/core/Variable.go | 1 + tests/programs/loop-lifetime.q | 17 +++++++++++++++++ tests/programs/loop.q | 11 +++++++++++ tests/programs_test.go | 2 ++ 11 files changed, 90 insertions(+), 18 deletions(-) create mode 100644 tests/programs/loop-lifetime.q create mode 100644 tests/programs/loop.q diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 4c51e87..ced8cf5 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -22,8 +22,12 @@ func (a *Assembler) Call(name string) { // Return returns back to the caller. func (a *Assembler) Return() { - if len(a.Instructions) > 0 && a.Instructions[len(a.Instructions)-1].Mnemonic == RETURN { - return + if len(a.Instructions) > 0 { + lastMnemonic := a.Instructions[len(a.Instructions)-1].Mnemonic + + if lastMnemonic == RETURN || lastMnemonic == JUMP { + return + } } a.Instructions = append(a.Instructions, Instruction{Mnemonic: RETURN}) diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index b14482d..d15a29e 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -9,6 +9,10 @@ func Count(body AST, kind token.Kind, name string) int { for _, node := range body { switch node := node.(type) { case *Assign: + if node.Name.Kind == kind && node.Name.Text() == name { + count++ + } + count += node.Value.Count(kind, name) case *Call: @@ -18,7 +22,9 @@ func Count(body AST, kind token.Kind, name string) int { count += node.Value.Count(kind, name) case *Return: - count += node.Value.Count(kind, name) + if node.Value != nil { + count += node.Value.Count(kind, name) + } case *If: count += node.Condition.Count(kind, name) diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index eaaad01..eb55aec 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -30,6 +30,10 @@ func toASTNode(tokens token.List) (Node, error) { word := tokens[0].Text() if word == keyword.Return { + if len(tokens) == 1 { + return &Return{}, nil + } + value := expression.Parse(tokens[1:]) return &Return{Value: value}, nil } diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index f7b9dfb..9bb4f1f 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -51,6 +51,10 @@ func (f *Function) CompileCall(root *expression.Expression) error { f.Call(funcName) } + for _, register := range registers { + f.Scope().Free(register) + } + // Pop for i := len(f.cpu.General) - 1; i >= 0; i-- { register := f.cpu.General[i] diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 6a481a9..e591efa 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -44,26 +44,35 @@ func (f *Function) AddVariable(variable *Variable) { f.Comment("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive) } - f.Scope().variables[variable.Name] = variable - f.Scope().Reserve(variable.Register) - f.Scope().Use(variable.Register) + scope := f.Scope() + variable.Scope = scope + + scope.variables[variable.Name] = variable + scope.Reserve(variable.Register) + scope.Use(variable.Register) } func (f *Function) useVariable(variable *Variable) { - for _, scope := range f.scopes { + for i, scope := range f.scopes { + if scope.inLoop && variable.Scope != scope { + continue + } + local := scope.variables[variable.Name] - if local != nil { - local.Alive-- + if local == nil { + continue } + local.Alive-- + if local.Alive < 0 { panic("incorrect number of variable use calls") } if local.Alive == 0 { if config.Comments { - f.Comment("%s died (%s)", local.Name, local.Register) + f.Comment("%s died (%s) in scope %d", local.Name, local.Register, i) } scope.Free(local.Register) diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index 6a798e5..c8e8258 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -12,9 +12,10 @@ func (f *Function) CompileLoop(loop *ast.Loop) error { f.count.loop++ label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) f.AddLabel(label) - f.pushScope(loop.Body) + scope := f.pushScope(loop.Body) + scope.inLoop = true err := f.CompileAST(loop.Body) - f.popScope() f.Jump(asm.JUMP, label) + f.popScope() return err } diff --git a/src/build/core/Scope.go b/src/build/core/Scope.go index a88187b..4cfcaeb 100644 --- a/src/build/core/Scope.go +++ b/src/build/core/Scope.go @@ -2,6 +2,7 @@ package core import ( "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/token" ) @@ -10,6 +11,7 @@ import ( type Scope struct { cpu.State variables map[string]*Variable + inLoop bool } // Scope returns the current scope. @@ -18,13 +20,14 @@ func (s *state) Scope() *Scope { } // pushScope pushes a new scope to the top of the stack. -func (s *state) pushScope(body ast.AST) { +func (f *Function) pushScope(body ast.AST) *Scope { scope := &Scope{} - if len(s.scopes) > 0 { - lastScope := s.scopes[len(s.scopes)-1] + if len(f.scopes) > 0 { + lastScope := f.scopes[len(f.scopes)-1] scope.State = lastScope.State scope.variables = make(map[string]*Variable, len(lastScope.variables)) + scope.inLoop = lastScope.inLoop for k, v := range lastScope.variables { count := ast.Count(body, token.Identifier, v.Name) @@ -44,10 +47,20 @@ func (s *state) pushScope(body ast.AST) { scope.variables = map[string]*Variable{} } - s.scopes = append(s.scopes, scope) + f.scopes = append(f.scopes, scope) + + if config.Comments { + f.Comment("scope %d start", len(f.scopes)) + } + + return scope } // popScope removes the scope at the top of the stack. -func (s *state) popScope() { - s.scopes = s.scopes[:len(s.scopes)-1] +func (f *Function) popScope() { + if config.Comments { + f.Comment("scope %d end", len(f.scopes)) + } + + f.scopes = f.scopes[:len(f.scopes)-1] } diff --git a/src/build/core/Variable.go b/src/build/core/Variable.go index b7dd6ad..4f31806 100644 --- a/src/build/core/Variable.go +++ b/src/build/core/Variable.go @@ -11,6 +11,7 @@ type Variable struct { Name string Register cpu.Register Alive int + Scope *Scope } // Variable returns the variable with the given name or `nil` if it doesn't exist. diff --git a/tests/programs/loop-lifetime.q b/tests/programs/loop-lifetime.q new file mode 100644 index 0000000..1262b7d --- /dev/null +++ b/tests/programs/loop-lifetime.q @@ -0,0 +1,17 @@ +main() { + n := 10 + x := 1 + + loop { + if n == 0 { + return + } + + f(x) + n -= 1 + } +} + +f(x) { + return x +} \ No newline at end of file diff --git a/tests/programs/loop.q b/tests/programs/loop.q new file mode 100644 index 0000000..c6456dc --- /dev/null +++ b/tests/programs/loop.q @@ -0,0 +1,11 @@ +main() { + n := 10 + + loop { + if n == 0 { + return + } + + n -= 1 + } +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 93dbd50..aa3a6d5 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -31,6 +31,8 @@ var programs = []struct { {"branch-or", "", 0}, {"branch-both", "", 0}, {"jump-near", "", 0}, + {"loop", "", 0}, + {"loop-lifetime", "", 0}, } func TestPrograms(t *testing.T) { From f9d72fe4905039564ebb21736e3a494a272c8daf Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 16 Jul 2024 20:22:28 +0200 Subject: [PATCH 0298/1012] Fixed variable lifetime in loops --- src/build/asm/Instructions.go | 8 ++++++-- src/build/ast/Count.go | 8 +++++++- src/build/ast/Parse.go | 4 ++++ src/build/core/CompileCall.go | 4 ++++ src/build/core/CompileDefinition.go | 23 ++++++++++++++++------- src/build/core/CompileLoop.go | 5 +++-- src/build/core/Scope.go | 25 +++++++++++++++++++------ src/build/core/Variable.go | 1 + tests/programs/loop-lifetime.q | 17 +++++++++++++++++ tests/programs/loop.q | 11 +++++++++++ tests/programs_test.go | 2 ++ 11 files changed, 90 insertions(+), 18 deletions(-) create mode 100644 tests/programs/loop-lifetime.q create mode 100644 tests/programs/loop.q diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 4c51e87..ced8cf5 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -22,8 +22,12 @@ func (a *Assembler) Call(name string) { // Return returns back to the caller. func (a *Assembler) Return() { - if len(a.Instructions) > 0 && a.Instructions[len(a.Instructions)-1].Mnemonic == RETURN { - return + if len(a.Instructions) > 0 { + lastMnemonic := a.Instructions[len(a.Instructions)-1].Mnemonic + + if lastMnemonic == RETURN || lastMnemonic == JUMP { + return + } } a.Instructions = append(a.Instructions, Instruction{Mnemonic: RETURN}) diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index b14482d..d15a29e 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -9,6 +9,10 @@ func Count(body AST, kind token.Kind, name string) int { for _, node := range body { switch node := node.(type) { case *Assign: + if node.Name.Kind == kind && node.Name.Text() == name { + count++ + } + count += node.Value.Count(kind, name) case *Call: @@ -18,7 +22,9 @@ func Count(body AST, kind token.Kind, name string) int { count += node.Value.Count(kind, name) case *Return: - count += node.Value.Count(kind, name) + if node.Value != nil { + count += node.Value.Count(kind, name) + } case *If: count += node.Condition.Count(kind, name) diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index eaaad01..eb55aec 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -30,6 +30,10 @@ func toASTNode(tokens token.List) (Node, error) { word := tokens[0].Text() if word == keyword.Return { + if len(tokens) == 1 { + return &Return{}, nil + } + value := expression.Parse(tokens[1:]) return &Return{Value: value}, nil } diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index f7b9dfb..9bb4f1f 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -51,6 +51,10 @@ func (f *Function) CompileCall(root *expression.Expression) error { f.Call(funcName) } + for _, register := range registers { + f.Scope().Free(register) + } + // Pop for i := len(f.cpu.General) - 1; i >= 0; i-- { register := f.cpu.General[i] diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 6a481a9..e591efa 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -44,26 +44,35 @@ func (f *Function) AddVariable(variable *Variable) { f.Comment("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive) } - f.Scope().variables[variable.Name] = variable - f.Scope().Reserve(variable.Register) - f.Scope().Use(variable.Register) + scope := f.Scope() + variable.Scope = scope + + scope.variables[variable.Name] = variable + scope.Reserve(variable.Register) + scope.Use(variable.Register) } func (f *Function) useVariable(variable *Variable) { - for _, scope := range f.scopes { + for i, scope := range f.scopes { + if scope.inLoop && variable.Scope != scope { + continue + } + local := scope.variables[variable.Name] - if local != nil { - local.Alive-- + if local == nil { + continue } + local.Alive-- + if local.Alive < 0 { panic("incorrect number of variable use calls") } if local.Alive == 0 { if config.Comments { - f.Comment("%s died (%s)", local.Name, local.Register) + f.Comment("%s died (%s) in scope %d", local.Name, local.Register, i) } scope.Free(local.Register) diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index 6a798e5..c8e8258 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -12,9 +12,10 @@ func (f *Function) CompileLoop(loop *ast.Loop) error { f.count.loop++ label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) f.AddLabel(label) - f.pushScope(loop.Body) + scope := f.pushScope(loop.Body) + scope.inLoop = true err := f.CompileAST(loop.Body) - f.popScope() f.Jump(asm.JUMP, label) + f.popScope() return err } diff --git a/src/build/core/Scope.go b/src/build/core/Scope.go index a88187b..4cfcaeb 100644 --- a/src/build/core/Scope.go +++ b/src/build/core/Scope.go @@ -2,6 +2,7 @@ package core import ( "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/token" ) @@ -10,6 +11,7 @@ import ( type Scope struct { cpu.State variables map[string]*Variable + inLoop bool } // Scope returns the current scope. @@ -18,13 +20,14 @@ func (s *state) Scope() *Scope { } // pushScope pushes a new scope to the top of the stack. -func (s *state) pushScope(body ast.AST) { +func (f *Function) pushScope(body ast.AST) *Scope { scope := &Scope{} - if len(s.scopes) > 0 { - lastScope := s.scopes[len(s.scopes)-1] + if len(f.scopes) > 0 { + lastScope := f.scopes[len(f.scopes)-1] scope.State = lastScope.State scope.variables = make(map[string]*Variable, len(lastScope.variables)) + scope.inLoop = lastScope.inLoop for k, v := range lastScope.variables { count := ast.Count(body, token.Identifier, v.Name) @@ -44,10 +47,20 @@ func (s *state) pushScope(body ast.AST) { scope.variables = map[string]*Variable{} } - s.scopes = append(s.scopes, scope) + f.scopes = append(f.scopes, scope) + + if config.Comments { + f.Comment("scope %d start", len(f.scopes)) + } + + return scope } // popScope removes the scope at the top of the stack. -func (s *state) popScope() { - s.scopes = s.scopes[:len(s.scopes)-1] +func (f *Function) popScope() { + if config.Comments { + f.Comment("scope %d end", len(f.scopes)) + } + + f.scopes = f.scopes[:len(f.scopes)-1] } diff --git a/src/build/core/Variable.go b/src/build/core/Variable.go index b7dd6ad..4f31806 100644 --- a/src/build/core/Variable.go +++ b/src/build/core/Variable.go @@ -11,6 +11,7 @@ type Variable struct { Name string Register cpu.Register Alive int + Scope *Scope } // Variable returns the variable with the given name or `nil` if it doesn't exist. diff --git a/tests/programs/loop-lifetime.q b/tests/programs/loop-lifetime.q new file mode 100644 index 0000000..1262b7d --- /dev/null +++ b/tests/programs/loop-lifetime.q @@ -0,0 +1,17 @@ +main() { + n := 10 + x := 1 + + loop { + if n == 0 { + return + } + + f(x) + n -= 1 + } +} + +f(x) { + return x +} \ No newline at end of file diff --git a/tests/programs/loop.q b/tests/programs/loop.q new file mode 100644 index 0000000..c6456dc --- /dev/null +++ b/tests/programs/loop.q @@ -0,0 +1,11 @@ +main() { + n := 10 + + loop { + if n == 0 { + return + } + + n -= 1 + } +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 93dbd50..aa3a6d5 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -31,6 +31,8 @@ var programs = []struct { {"branch-or", "", 0}, {"branch-both", "", 0}, {"jump-near", "", 0}, + {"loop", "", 0}, + {"loop-lifetime", "", 0}, } func TestPrograms(t *testing.T) { From 055d628130719333a2e214fcb5afe10f9f2b913c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 16 Jul 2024 23:32:39 +0200 Subject: [PATCH 0299/1012] Improved tokenizer test coverage --- src/build/token/Count_test.go | 16 ++ src/build/token/Kind_test.go | 27 ++ src/build/token/List_test.go | 18 ++ src/build/token/Token_test.go | 440 ++----------------------------- src/build/token/Tokenize_test.go | 402 ++++++++++++++++++++++++++++ 5 files changed, 489 insertions(+), 414 deletions(-) create mode 100644 src/build/token/Count_test.go create mode 100644 src/build/token/Kind_test.go create mode 100644 src/build/token/List_test.go create mode 100644 src/build/token/Tokenize_test.go diff --git a/src/build/token/Count_test.go b/src/build/token/Count_test.go new file mode 100644 index 0000000..0128e62 --- /dev/null +++ b/src/build/token/Count_test.go @@ -0,0 +1,16 @@ +package token_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/go/assert" +) + +func TestCount(t *testing.T) { + tokens := token.Tokenize([]byte(`a b b c c c`)) + assert.Equal(t, token.Count(tokens, token.Identifier, "a"), 1) + assert.Equal(t, token.Count(tokens, token.Identifier, "b"), 2) + assert.Equal(t, token.Count(tokens, token.Identifier, "c"), 3) + assert.Equal(t, token.Count(tokens, token.Identifier, "d"), 0) +} diff --git a/src/build/token/Kind_test.go b/src/build/token/Kind_test.go new file mode 100644 index 0000000..1db0382 --- /dev/null +++ b/src/build/token/Kind_test.go @@ -0,0 +1,27 @@ +package token_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/go/assert" +) + +func TestTokenKind(t *testing.T) { + assert.Equal(t, token.Invalid.String(), "Invalid") + assert.Equal(t, token.EOF.String(), "EOF") + assert.Equal(t, token.NewLine.String(), "NewLine") + assert.Equal(t, token.Identifier.String(), "Identifier") + assert.Equal(t, token.Keyword.String(), "Keyword") + assert.Equal(t, token.String.String(), "String") + assert.Equal(t, token.Number.String(), "Number") + assert.Equal(t, token.Operator.String(), "Operator") + assert.Equal(t, token.Separator.String(), "Separator") + assert.Equal(t, token.Comment.String(), "Comment") + assert.Equal(t, token.GroupStart.String(), "GroupStart") + assert.Equal(t, token.GroupEnd.String(), "GroupEnd") + assert.Equal(t, token.BlockStart.String(), "BlockStart") + assert.Equal(t, token.BlockEnd.String(), "BlockEnd") + assert.Equal(t, token.ArrayStart.String(), "ArrayStart") + assert.Equal(t, token.ArrayEnd.String(), "ArrayEnd") +} diff --git a/src/build/token/List_test.go b/src/build/token/List_test.go new file mode 100644 index 0000000..b4529f1 --- /dev/null +++ b/src/build/token/List_test.go @@ -0,0 +1,18 @@ +package token_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/go/assert" +) + +func TestIndexKind(t *testing.T) { + tokens := token.Tokenize([]byte("a{{}}")) + assert.Equal(t, tokens.IndexKind(token.NewLine), -1) + assert.Equal(t, tokens.LastIndexKind(token.NewLine), -1) + assert.Equal(t, tokens.IndexKind(token.BlockStart), 1) + assert.Equal(t, tokens.LastIndexKind(token.BlockStart), 2) + assert.Equal(t, tokens.IndexKind(token.BlockEnd), 3) + assert.Equal(t, tokens.LastIndexKind(token.BlockEnd), 4) +} diff --git a/src/build/token/Token_test.go b/src/build/token/Token_test.go index 1b166c7..47fc665 100644 --- a/src/build/token/Token_test.go +++ b/src/build/token/Token_test.go @@ -7,406 +7,37 @@ import ( "git.akyoto.dev/go/assert" ) -func TestFunction(t *testing.T) { - tokens := token.Tokenize([]byte("main(){}")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Identifier, - Bytes: []byte("main"), - Position: 0, - }, - { - Kind: token.GroupStart, - Bytes: []byte("("), - Position: 4, - }, - { - Kind: token.GroupEnd, - Bytes: []byte(")"), - Position: 5, - }, - { - Kind: token.BlockStart, - Bytes: []byte("{"), - Position: 6, - }, - { - Kind: token.BlockEnd, - Bytes: []byte("}"), - Position: 7, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 8, - }, - }) +func TestTokenEnd(t *testing.T) { + hello := token.Token{ + Kind: token.Identifier, + Bytes: []byte("hello"), + Position: 0, + } + + assert.Equal(t, hello.End(), 5) } -func TestKeyword(t *testing.T) { - tokens := token.Tokenize([]byte("return x")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Keyword, - Bytes: []byte("return"), - Position: 0, - }, - { - Kind: token.Identifier, - Bytes: []byte("x"), - Position: 7, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 8, - }, - }) +func TestTokenReset(t *testing.T) { + hello := token.Token{ + Kind: token.Identifier, + Bytes: []byte("hello"), + Position: 1, + } + + hello.Reset() + assert.Nil(t, hello.Bytes) + assert.Equal(t, hello.Position, 0) + assert.Equal(t, hello.Kind, token.Invalid) } -func TestArray(t *testing.T) { - tokens := token.Tokenize([]byte("array[i]")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Identifier, - Bytes: []byte("array"), - Position: 0, - }, - { - Kind: token.ArrayStart, - Bytes: []byte("["), - Position: 5, - }, - { - Kind: token.Identifier, - Bytes: []byte("i"), - Position: 6, - }, - { - Kind: token.ArrayEnd, - Bytes: []byte("]"), - Position: 7, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 8, - }, - }) -} +func TestTokenString(t *testing.T) { + hello := token.Token{ + Kind: token.Identifier, + Bytes: []byte("hello"), + Position: 0, + } -func TestCount(t *testing.T) { - tokens := token.Tokenize([]byte(`a b b c c c`)) - assert.Equal(t, token.Count(tokens, token.Identifier, "a"), 1) - assert.Equal(t, token.Count(tokens, token.Identifier, "b"), 2) - assert.Equal(t, token.Count(tokens, token.Identifier, "c"), 3) - assert.Equal(t, token.Count(tokens, token.Identifier, "d"), 0) -} - -func TestNewline(t *testing.T) { - tokens := token.Tokenize([]byte("\n\n")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.NewLine, - Bytes: []byte("\n"), - Position: 0, - }, - { - Kind: token.NewLine, - Bytes: []byte("\n"), - Position: 1, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 2, - }, - }) -} - -func TestNumber(t *testing.T) { - tokens := token.Tokenize([]byte(`123 456`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Number, - Bytes: []byte("123"), - Position: 0, - }, - { - Kind: token.Number, - Bytes: []byte("456"), - Position: 4, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 7, - }, - }) -} - -func TestOperator(t *testing.T) { - tokens := token.Tokenize([]byte(`+ - * /`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Operator, - Bytes: []byte("+"), - Position: 0, - }, - { - Kind: token.Operator, - Bytes: []byte("-"), - Position: 2, - }, - { - Kind: token.Operator, - Bytes: []byte("*"), - Position: 4, - }, - { - Kind: token.Operator, - Bytes: []byte("/"), - Position: 6, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 7, - }, - }) -} - -func TestOperatorAssign(t *testing.T) { - tokens := token.Tokenize([]byte(`+= -= *= /= ==`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Operator, - Bytes: []byte("+="), - Position: 0, - }, - { - Kind: token.Operator, - Bytes: []byte("-="), - Position: 3, - }, - { - Kind: token.Operator, - Bytes: []byte("*="), - Position: 6, - }, - { - Kind: token.Operator, - Bytes: []byte("/="), - Position: 9, - }, - { - Kind: token.Operator, - Bytes: []byte("=="), - Position: 12, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 14, - }, - }) -} - -func TestSeparator(t *testing.T) { - tokens := token.Tokenize([]byte("a,b,c")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Identifier, - Bytes: []byte("a"), - Position: 0, - }, - { - Kind: token.Separator, - Bytes: []byte(","), - Position: 1, - }, - { - Kind: token.Identifier, - Bytes: []byte("b"), - Position: 2, - }, - { - Kind: token.Separator, - Bytes: []byte(","), - Position: 3, - }, - { - Kind: token.Identifier, - Bytes: []byte("c"), - Position: 4, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 5, - }, - }) -} - -func TestComment(t *testing.T) { - tokens := token.Tokenize([]byte("// Hello\n// World")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Comment, - Bytes: []byte(`// Hello`), - Position: 0, - }, - { - Kind: token.NewLine, - Bytes: []byte("\n"), - Position: 8, - }, - { - Kind: token.Comment, - Bytes: []byte(`// World`), - Position: 9, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 17, - }, - }) - - tokens = token.Tokenize([]byte("// Hello\n")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Comment, - Bytes: []byte(`// Hello`), - Position: 0, - }, - { - Kind: token.NewLine, - Bytes: []byte("\n"), - Position: 8, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 9, - }, - }) - - tokens = token.Tokenize([]byte(`// Hello`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Comment, - Bytes: []byte(`// Hello`), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 8, - }, - }) - - tokens = token.Tokenize([]byte(`//`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Comment, - Bytes: []byte(`//`), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 2, - }, - }) - - tokens = token.Tokenize([]byte(`/`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Operator, - Bytes: []byte(`/`), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 1, - }, - }) -} - -func TestInvalid(t *testing.T) { - tokens := token.Tokenize([]byte(`@#`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Invalid, - Bytes: []byte(`@`), - Position: 0, - }, - { - Kind: token.Invalid, - Bytes: []byte(`#`), - Position: 1, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 2, - }, - }) -} - -func TestString(t *testing.T) { - tokens := token.Tokenize([]byte(`"Hello" "World"`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.String, - Bytes: []byte(`"Hello"`), - Position: 0, - }, - { - Kind: token.String, - Bytes: []byte(`"World"`), - Position: 8, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 15, - }, - }) -} - -func TestStringMultiline(t *testing.T) { - tokens := token.Tokenize([]byte("\"Hello\nWorld\"")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.String, - Bytes: []byte("\"Hello\nWorld\""), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 13, - }, - }) -} - -func TestStringEOF(t *testing.T) { - tokens := token.Tokenize([]byte(`"EOF`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.String, - Bytes: []byte(`"EOF`), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 4, - }, - }) + assert.Equal(t, hello.String(), "Identifier hello") } func TestTokenText(t *testing.T) { @@ -420,22 +51,3 @@ func TestTokenText(t *testing.T) { list := token.List{hello, comma, world} assert.Equal(t, list.String(), "hello, world") } - -func TestTokenKind(t *testing.T) { - assert.Equal(t, token.Invalid.String(), "Invalid") - assert.Equal(t, token.EOF.String(), "EOF") - assert.Equal(t, token.NewLine.String(), "NewLine") - assert.Equal(t, token.Identifier.String(), "Identifier") - assert.Equal(t, token.Keyword.String(), "Keyword") - assert.Equal(t, token.String.String(), "String") - assert.Equal(t, token.Number.String(), "Number") - assert.Equal(t, token.Operator.String(), "Operator") - assert.Equal(t, token.Separator.String(), "Separator") - assert.Equal(t, token.Comment.String(), "Comment") - assert.Equal(t, token.GroupStart.String(), "GroupStart") - assert.Equal(t, token.GroupEnd.String(), "GroupEnd") - assert.Equal(t, token.BlockStart.String(), "BlockStart") - assert.Equal(t, token.BlockEnd.String(), "BlockEnd") - assert.Equal(t, token.ArrayStart.String(), "ArrayStart") - assert.Equal(t, token.ArrayEnd.String(), "ArrayEnd") -} diff --git a/src/build/token/Tokenize_test.go b/src/build/token/Tokenize_test.go new file mode 100644 index 0000000..63e689d --- /dev/null +++ b/src/build/token/Tokenize_test.go @@ -0,0 +1,402 @@ +package token_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/go/assert" +) + +func TestFunction(t *testing.T) { + tokens := token.Tokenize([]byte("main(){}")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Identifier, + Bytes: []byte("main"), + Position: 0, + }, + { + Kind: token.GroupStart, + Bytes: []byte("("), + Position: 4, + }, + { + Kind: token.GroupEnd, + Bytes: []byte(")"), + Position: 5, + }, + { + Kind: token.BlockStart, + Bytes: []byte("{"), + Position: 6, + }, + { + Kind: token.BlockEnd, + Bytes: []byte("}"), + Position: 7, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, + }) +} + +func TestKeyword(t *testing.T) { + tokens := token.Tokenize([]byte("return x")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Keyword, + Bytes: []byte("return"), + Position: 0, + }, + { + Kind: token.Identifier, + Bytes: []byte("x"), + Position: 7, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, + }) +} + +func TestArray(t *testing.T) { + tokens := token.Tokenize([]byte("array[i]")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Identifier, + Bytes: []byte("array"), + Position: 0, + }, + { + Kind: token.ArrayStart, + Bytes: []byte("["), + Position: 5, + }, + { + Kind: token.Identifier, + Bytes: []byte("i"), + Position: 6, + }, + { + Kind: token.ArrayEnd, + Bytes: []byte("]"), + Position: 7, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, + }) +} + +func TestNewline(t *testing.T) { + tokens := token.Tokenize([]byte("\n\n")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.NewLine, + Bytes: []byte("\n"), + Position: 0, + }, + { + Kind: token.NewLine, + Bytes: []byte("\n"), + Position: 1, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 2, + }, + }) +} + +func TestNumber(t *testing.T) { + tokens := token.Tokenize([]byte(`123 456`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Number, + Bytes: []byte("123"), + Position: 0, + }, + { + Kind: token.Number, + Bytes: []byte("456"), + Position: 4, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 7, + }, + }) +} + +func TestOperator(t *testing.T) { + tokens := token.Tokenize([]byte(`+ - * /`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Operator, + Bytes: []byte("+"), + Position: 0, + }, + { + Kind: token.Operator, + Bytes: []byte("-"), + Position: 2, + }, + { + Kind: token.Operator, + Bytes: []byte("*"), + Position: 4, + }, + { + Kind: token.Operator, + Bytes: []byte("/"), + Position: 6, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 7, + }, + }) +} + +func TestOperatorAssign(t *testing.T) { + tokens := token.Tokenize([]byte(`+= -= *= /= ==`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Operator, + Bytes: []byte("+="), + Position: 0, + }, + { + Kind: token.Operator, + Bytes: []byte("-="), + Position: 3, + }, + { + Kind: token.Operator, + Bytes: []byte("*="), + Position: 6, + }, + { + Kind: token.Operator, + Bytes: []byte("/="), + Position: 9, + }, + { + Kind: token.Operator, + Bytes: []byte("=="), + Position: 12, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 14, + }, + }) +} + +func TestSeparator(t *testing.T) { + tokens := token.Tokenize([]byte("a,b,c")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Identifier, + Bytes: []byte("a"), + Position: 0, + }, + { + Kind: token.Separator, + Bytes: []byte(","), + Position: 1, + }, + { + Kind: token.Identifier, + Bytes: []byte("b"), + Position: 2, + }, + { + Kind: token.Separator, + Bytes: []byte(","), + Position: 3, + }, + { + Kind: token.Identifier, + Bytes: []byte("c"), + Position: 4, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 5, + }, + }) +} + +func TestComment(t *testing.T) { + tokens := token.Tokenize([]byte("// Hello\n// World")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Comment, + Bytes: []byte(`// Hello`), + Position: 0, + }, + { + Kind: token.NewLine, + Bytes: []byte("\n"), + Position: 8, + }, + { + Kind: token.Comment, + Bytes: []byte(`// World`), + Position: 9, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 17, + }, + }) + + tokens = token.Tokenize([]byte("// Hello\n")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Comment, + Bytes: []byte(`// Hello`), + Position: 0, + }, + { + Kind: token.NewLine, + Bytes: []byte("\n"), + Position: 8, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 9, + }, + }) + + tokens = token.Tokenize([]byte(`// Hello`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Comment, + Bytes: []byte(`// Hello`), + Position: 0, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, + }) + + tokens = token.Tokenize([]byte(`//`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Comment, + Bytes: []byte(`//`), + Position: 0, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 2, + }, + }) + + tokens = token.Tokenize([]byte(`/`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Operator, + Bytes: []byte(`/`), + Position: 0, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 1, + }, + }) +} + +func TestInvalid(t *testing.T) { + tokens := token.Tokenize([]byte(`@#`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Invalid, + Bytes: []byte(`@`), + Position: 0, + }, + { + Kind: token.Invalid, + Bytes: []byte(`#`), + Position: 1, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 2, + }, + }) +} + +func TestString(t *testing.T) { + tokens := token.Tokenize([]byte(`"Hello" "World"`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.String, + Bytes: []byte(`"Hello"`), + Position: 0, + }, + { + Kind: token.String, + Bytes: []byte(`"World"`), + Position: 8, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 15, + }, + }) +} + +func TestStringMultiline(t *testing.T) { + tokens := token.Tokenize([]byte("\"Hello\nWorld\"")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.String, + Bytes: []byte("\"Hello\nWorld\""), + Position: 0, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 13, + }, + }) +} + +func TestStringEOF(t *testing.T) { + tokens := token.Tokenize([]byte(`"EOF`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.String, + Bytes: []byte(`"EOF`), + Position: 0, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 4, + }, + }) +} From 8ec0e02dbe73beb8f77c57972d135c8b0ae6da7b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 16 Jul 2024 23:32:39 +0200 Subject: [PATCH 0300/1012] Improved tokenizer test coverage --- src/build/token/Count_test.go | 16 ++ src/build/token/Kind_test.go | 27 ++ src/build/token/List_test.go | 18 ++ src/build/token/Token_test.go | 440 ++----------------------------- src/build/token/Tokenize_test.go | 402 ++++++++++++++++++++++++++++ 5 files changed, 489 insertions(+), 414 deletions(-) create mode 100644 src/build/token/Count_test.go create mode 100644 src/build/token/Kind_test.go create mode 100644 src/build/token/List_test.go create mode 100644 src/build/token/Tokenize_test.go diff --git a/src/build/token/Count_test.go b/src/build/token/Count_test.go new file mode 100644 index 0000000..0128e62 --- /dev/null +++ b/src/build/token/Count_test.go @@ -0,0 +1,16 @@ +package token_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/go/assert" +) + +func TestCount(t *testing.T) { + tokens := token.Tokenize([]byte(`a b b c c c`)) + assert.Equal(t, token.Count(tokens, token.Identifier, "a"), 1) + assert.Equal(t, token.Count(tokens, token.Identifier, "b"), 2) + assert.Equal(t, token.Count(tokens, token.Identifier, "c"), 3) + assert.Equal(t, token.Count(tokens, token.Identifier, "d"), 0) +} diff --git a/src/build/token/Kind_test.go b/src/build/token/Kind_test.go new file mode 100644 index 0000000..1db0382 --- /dev/null +++ b/src/build/token/Kind_test.go @@ -0,0 +1,27 @@ +package token_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/go/assert" +) + +func TestTokenKind(t *testing.T) { + assert.Equal(t, token.Invalid.String(), "Invalid") + assert.Equal(t, token.EOF.String(), "EOF") + assert.Equal(t, token.NewLine.String(), "NewLine") + assert.Equal(t, token.Identifier.String(), "Identifier") + assert.Equal(t, token.Keyword.String(), "Keyword") + assert.Equal(t, token.String.String(), "String") + assert.Equal(t, token.Number.String(), "Number") + assert.Equal(t, token.Operator.String(), "Operator") + assert.Equal(t, token.Separator.String(), "Separator") + assert.Equal(t, token.Comment.String(), "Comment") + assert.Equal(t, token.GroupStart.String(), "GroupStart") + assert.Equal(t, token.GroupEnd.String(), "GroupEnd") + assert.Equal(t, token.BlockStart.String(), "BlockStart") + assert.Equal(t, token.BlockEnd.String(), "BlockEnd") + assert.Equal(t, token.ArrayStart.String(), "ArrayStart") + assert.Equal(t, token.ArrayEnd.String(), "ArrayEnd") +} diff --git a/src/build/token/List_test.go b/src/build/token/List_test.go new file mode 100644 index 0000000..b4529f1 --- /dev/null +++ b/src/build/token/List_test.go @@ -0,0 +1,18 @@ +package token_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/go/assert" +) + +func TestIndexKind(t *testing.T) { + tokens := token.Tokenize([]byte("a{{}}")) + assert.Equal(t, tokens.IndexKind(token.NewLine), -1) + assert.Equal(t, tokens.LastIndexKind(token.NewLine), -1) + assert.Equal(t, tokens.IndexKind(token.BlockStart), 1) + assert.Equal(t, tokens.LastIndexKind(token.BlockStart), 2) + assert.Equal(t, tokens.IndexKind(token.BlockEnd), 3) + assert.Equal(t, tokens.LastIndexKind(token.BlockEnd), 4) +} diff --git a/src/build/token/Token_test.go b/src/build/token/Token_test.go index 1b166c7..47fc665 100644 --- a/src/build/token/Token_test.go +++ b/src/build/token/Token_test.go @@ -7,406 +7,37 @@ import ( "git.akyoto.dev/go/assert" ) -func TestFunction(t *testing.T) { - tokens := token.Tokenize([]byte("main(){}")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Identifier, - Bytes: []byte("main"), - Position: 0, - }, - { - Kind: token.GroupStart, - Bytes: []byte("("), - Position: 4, - }, - { - Kind: token.GroupEnd, - Bytes: []byte(")"), - Position: 5, - }, - { - Kind: token.BlockStart, - Bytes: []byte("{"), - Position: 6, - }, - { - Kind: token.BlockEnd, - Bytes: []byte("}"), - Position: 7, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 8, - }, - }) +func TestTokenEnd(t *testing.T) { + hello := token.Token{ + Kind: token.Identifier, + Bytes: []byte("hello"), + Position: 0, + } + + assert.Equal(t, hello.End(), 5) } -func TestKeyword(t *testing.T) { - tokens := token.Tokenize([]byte("return x")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Keyword, - Bytes: []byte("return"), - Position: 0, - }, - { - Kind: token.Identifier, - Bytes: []byte("x"), - Position: 7, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 8, - }, - }) +func TestTokenReset(t *testing.T) { + hello := token.Token{ + Kind: token.Identifier, + Bytes: []byte("hello"), + Position: 1, + } + + hello.Reset() + assert.Nil(t, hello.Bytes) + assert.Equal(t, hello.Position, 0) + assert.Equal(t, hello.Kind, token.Invalid) } -func TestArray(t *testing.T) { - tokens := token.Tokenize([]byte("array[i]")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Identifier, - Bytes: []byte("array"), - Position: 0, - }, - { - Kind: token.ArrayStart, - Bytes: []byte("["), - Position: 5, - }, - { - Kind: token.Identifier, - Bytes: []byte("i"), - Position: 6, - }, - { - Kind: token.ArrayEnd, - Bytes: []byte("]"), - Position: 7, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 8, - }, - }) -} +func TestTokenString(t *testing.T) { + hello := token.Token{ + Kind: token.Identifier, + Bytes: []byte("hello"), + Position: 0, + } -func TestCount(t *testing.T) { - tokens := token.Tokenize([]byte(`a b b c c c`)) - assert.Equal(t, token.Count(tokens, token.Identifier, "a"), 1) - assert.Equal(t, token.Count(tokens, token.Identifier, "b"), 2) - assert.Equal(t, token.Count(tokens, token.Identifier, "c"), 3) - assert.Equal(t, token.Count(tokens, token.Identifier, "d"), 0) -} - -func TestNewline(t *testing.T) { - tokens := token.Tokenize([]byte("\n\n")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.NewLine, - Bytes: []byte("\n"), - Position: 0, - }, - { - Kind: token.NewLine, - Bytes: []byte("\n"), - Position: 1, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 2, - }, - }) -} - -func TestNumber(t *testing.T) { - tokens := token.Tokenize([]byte(`123 456`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Number, - Bytes: []byte("123"), - Position: 0, - }, - { - Kind: token.Number, - Bytes: []byte("456"), - Position: 4, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 7, - }, - }) -} - -func TestOperator(t *testing.T) { - tokens := token.Tokenize([]byte(`+ - * /`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Operator, - Bytes: []byte("+"), - Position: 0, - }, - { - Kind: token.Operator, - Bytes: []byte("-"), - Position: 2, - }, - { - Kind: token.Operator, - Bytes: []byte("*"), - Position: 4, - }, - { - Kind: token.Operator, - Bytes: []byte("/"), - Position: 6, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 7, - }, - }) -} - -func TestOperatorAssign(t *testing.T) { - tokens := token.Tokenize([]byte(`+= -= *= /= ==`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Operator, - Bytes: []byte("+="), - Position: 0, - }, - { - Kind: token.Operator, - Bytes: []byte("-="), - Position: 3, - }, - { - Kind: token.Operator, - Bytes: []byte("*="), - Position: 6, - }, - { - Kind: token.Operator, - Bytes: []byte("/="), - Position: 9, - }, - { - Kind: token.Operator, - Bytes: []byte("=="), - Position: 12, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 14, - }, - }) -} - -func TestSeparator(t *testing.T) { - tokens := token.Tokenize([]byte("a,b,c")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Identifier, - Bytes: []byte("a"), - Position: 0, - }, - { - Kind: token.Separator, - Bytes: []byte(","), - Position: 1, - }, - { - Kind: token.Identifier, - Bytes: []byte("b"), - Position: 2, - }, - { - Kind: token.Separator, - Bytes: []byte(","), - Position: 3, - }, - { - Kind: token.Identifier, - Bytes: []byte("c"), - Position: 4, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 5, - }, - }) -} - -func TestComment(t *testing.T) { - tokens := token.Tokenize([]byte("// Hello\n// World")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Comment, - Bytes: []byte(`// Hello`), - Position: 0, - }, - { - Kind: token.NewLine, - Bytes: []byte("\n"), - Position: 8, - }, - { - Kind: token.Comment, - Bytes: []byte(`// World`), - Position: 9, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 17, - }, - }) - - tokens = token.Tokenize([]byte("// Hello\n")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Comment, - Bytes: []byte(`// Hello`), - Position: 0, - }, - { - Kind: token.NewLine, - Bytes: []byte("\n"), - Position: 8, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 9, - }, - }) - - tokens = token.Tokenize([]byte(`// Hello`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Comment, - Bytes: []byte(`// Hello`), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 8, - }, - }) - - tokens = token.Tokenize([]byte(`//`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Comment, - Bytes: []byte(`//`), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 2, - }, - }) - - tokens = token.Tokenize([]byte(`/`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Operator, - Bytes: []byte(`/`), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 1, - }, - }) -} - -func TestInvalid(t *testing.T) { - tokens := token.Tokenize([]byte(`@#`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Invalid, - Bytes: []byte(`@`), - Position: 0, - }, - { - Kind: token.Invalid, - Bytes: []byte(`#`), - Position: 1, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 2, - }, - }) -} - -func TestString(t *testing.T) { - tokens := token.Tokenize([]byte(`"Hello" "World"`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.String, - Bytes: []byte(`"Hello"`), - Position: 0, - }, - { - Kind: token.String, - Bytes: []byte(`"World"`), - Position: 8, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 15, - }, - }) -} - -func TestStringMultiline(t *testing.T) { - tokens := token.Tokenize([]byte("\"Hello\nWorld\"")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.String, - Bytes: []byte("\"Hello\nWorld\""), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 13, - }, - }) -} - -func TestStringEOF(t *testing.T) { - tokens := token.Tokenize([]byte(`"EOF`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.String, - Bytes: []byte(`"EOF`), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 4, - }, - }) + assert.Equal(t, hello.String(), "Identifier hello") } func TestTokenText(t *testing.T) { @@ -420,22 +51,3 @@ func TestTokenText(t *testing.T) { list := token.List{hello, comma, world} assert.Equal(t, list.String(), "hello, world") } - -func TestTokenKind(t *testing.T) { - assert.Equal(t, token.Invalid.String(), "Invalid") - assert.Equal(t, token.EOF.String(), "EOF") - assert.Equal(t, token.NewLine.String(), "NewLine") - assert.Equal(t, token.Identifier.String(), "Identifier") - assert.Equal(t, token.Keyword.String(), "Keyword") - assert.Equal(t, token.String.String(), "String") - assert.Equal(t, token.Number.String(), "Number") - assert.Equal(t, token.Operator.String(), "Operator") - assert.Equal(t, token.Separator.String(), "Separator") - assert.Equal(t, token.Comment.String(), "Comment") - assert.Equal(t, token.GroupStart.String(), "GroupStart") - assert.Equal(t, token.GroupEnd.String(), "GroupEnd") - assert.Equal(t, token.BlockStart.String(), "BlockStart") - assert.Equal(t, token.BlockEnd.String(), "BlockEnd") - assert.Equal(t, token.ArrayStart.String(), "ArrayStart") - assert.Equal(t, token.ArrayEnd.String(), "ArrayEnd") -} diff --git a/src/build/token/Tokenize_test.go b/src/build/token/Tokenize_test.go new file mode 100644 index 0000000..63e689d --- /dev/null +++ b/src/build/token/Tokenize_test.go @@ -0,0 +1,402 @@ +package token_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/go/assert" +) + +func TestFunction(t *testing.T) { + tokens := token.Tokenize([]byte("main(){}")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Identifier, + Bytes: []byte("main"), + Position: 0, + }, + { + Kind: token.GroupStart, + Bytes: []byte("("), + Position: 4, + }, + { + Kind: token.GroupEnd, + Bytes: []byte(")"), + Position: 5, + }, + { + Kind: token.BlockStart, + Bytes: []byte("{"), + Position: 6, + }, + { + Kind: token.BlockEnd, + Bytes: []byte("}"), + Position: 7, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, + }) +} + +func TestKeyword(t *testing.T) { + tokens := token.Tokenize([]byte("return x")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Keyword, + Bytes: []byte("return"), + Position: 0, + }, + { + Kind: token.Identifier, + Bytes: []byte("x"), + Position: 7, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, + }) +} + +func TestArray(t *testing.T) { + tokens := token.Tokenize([]byte("array[i]")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Identifier, + Bytes: []byte("array"), + Position: 0, + }, + { + Kind: token.ArrayStart, + Bytes: []byte("["), + Position: 5, + }, + { + Kind: token.Identifier, + Bytes: []byte("i"), + Position: 6, + }, + { + Kind: token.ArrayEnd, + Bytes: []byte("]"), + Position: 7, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, + }) +} + +func TestNewline(t *testing.T) { + tokens := token.Tokenize([]byte("\n\n")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.NewLine, + Bytes: []byte("\n"), + Position: 0, + }, + { + Kind: token.NewLine, + Bytes: []byte("\n"), + Position: 1, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 2, + }, + }) +} + +func TestNumber(t *testing.T) { + tokens := token.Tokenize([]byte(`123 456`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Number, + Bytes: []byte("123"), + Position: 0, + }, + { + Kind: token.Number, + Bytes: []byte("456"), + Position: 4, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 7, + }, + }) +} + +func TestOperator(t *testing.T) { + tokens := token.Tokenize([]byte(`+ - * /`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Operator, + Bytes: []byte("+"), + Position: 0, + }, + { + Kind: token.Operator, + Bytes: []byte("-"), + Position: 2, + }, + { + Kind: token.Operator, + Bytes: []byte("*"), + Position: 4, + }, + { + Kind: token.Operator, + Bytes: []byte("/"), + Position: 6, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 7, + }, + }) +} + +func TestOperatorAssign(t *testing.T) { + tokens := token.Tokenize([]byte(`+= -= *= /= ==`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Operator, + Bytes: []byte("+="), + Position: 0, + }, + { + Kind: token.Operator, + Bytes: []byte("-="), + Position: 3, + }, + { + Kind: token.Operator, + Bytes: []byte("*="), + Position: 6, + }, + { + Kind: token.Operator, + Bytes: []byte("/="), + Position: 9, + }, + { + Kind: token.Operator, + Bytes: []byte("=="), + Position: 12, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 14, + }, + }) +} + +func TestSeparator(t *testing.T) { + tokens := token.Tokenize([]byte("a,b,c")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Identifier, + Bytes: []byte("a"), + Position: 0, + }, + { + Kind: token.Separator, + Bytes: []byte(","), + Position: 1, + }, + { + Kind: token.Identifier, + Bytes: []byte("b"), + Position: 2, + }, + { + Kind: token.Separator, + Bytes: []byte(","), + Position: 3, + }, + { + Kind: token.Identifier, + Bytes: []byte("c"), + Position: 4, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 5, + }, + }) +} + +func TestComment(t *testing.T) { + tokens := token.Tokenize([]byte("// Hello\n// World")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Comment, + Bytes: []byte(`// Hello`), + Position: 0, + }, + { + Kind: token.NewLine, + Bytes: []byte("\n"), + Position: 8, + }, + { + Kind: token.Comment, + Bytes: []byte(`// World`), + Position: 9, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 17, + }, + }) + + tokens = token.Tokenize([]byte("// Hello\n")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Comment, + Bytes: []byte(`// Hello`), + Position: 0, + }, + { + Kind: token.NewLine, + Bytes: []byte("\n"), + Position: 8, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 9, + }, + }) + + tokens = token.Tokenize([]byte(`// Hello`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Comment, + Bytes: []byte(`// Hello`), + Position: 0, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 8, + }, + }) + + tokens = token.Tokenize([]byte(`//`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Comment, + Bytes: []byte(`//`), + Position: 0, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 2, + }, + }) + + tokens = token.Tokenize([]byte(`/`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Operator, + Bytes: []byte(`/`), + Position: 0, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 1, + }, + }) +} + +func TestInvalid(t *testing.T) { + tokens := token.Tokenize([]byte(`@#`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.Invalid, + Bytes: []byte(`@`), + Position: 0, + }, + { + Kind: token.Invalid, + Bytes: []byte(`#`), + Position: 1, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 2, + }, + }) +} + +func TestString(t *testing.T) { + tokens := token.Tokenize([]byte(`"Hello" "World"`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.String, + Bytes: []byte(`"Hello"`), + Position: 0, + }, + { + Kind: token.String, + Bytes: []byte(`"World"`), + Position: 8, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 15, + }, + }) +} + +func TestStringMultiline(t *testing.T) { + tokens := token.Tokenize([]byte("\"Hello\nWorld\"")) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.String, + Bytes: []byte("\"Hello\nWorld\""), + Position: 0, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 13, + }, + }) +} + +func TestStringEOF(t *testing.T) { + tokens := token.Tokenize([]byte(`"EOF`)) + assert.DeepEqual(t, tokens, token.List{ + { + Kind: token.String, + Bytes: []byte(`"EOF`), + Position: 0, + }, + { + Kind: token.EOF, + Bytes: nil, + Position: 4, + }, + }) +} From 58eed722c1b481dac5ea172e281525b7b436ff3b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 17 Jul 2024 12:38:57 +0200 Subject: [PATCH 0301/1012] Removed unused struct fields --- src/build/core/CompileDefinition.go | 2 +- src/build/core/Scope.go | 3 +-- src/build/core/Variable.go | 4 +--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index e591efa..852fb4f 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -41,7 +41,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { func (f *Function) AddVariable(variable *Variable) { if config.Comments { - f.Comment("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive) + f.Comment("%s = %s (%d uses)", variable.Name, variable.Register, variable.Alive) } scope := f.Scope() diff --git a/src/build/core/Scope.go b/src/build/core/Scope.go index 4cfcaeb..7aeb163 100644 --- a/src/build/core/Scope.go +++ b/src/build/core/Scope.go @@ -9,9 +9,9 @@ import ( // Scope represents an independent code block. type Scope struct { - cpu.State variables map[string]*Variable inLoop bool + cpu.State } // Scope returns the current scope. @@ -37,7 +37,6 @@ func (f *Function) pushScope(body ast.AST) *Scope { } scope.variables[k] = &Variable{ - Value: v.Value, Name: v.Name, Register: v.Register, Alive: count, diff --git a/src/build/core/Variable.go b/src/build/core/Variable.go index 4f31806..cca96e8 100644 --- a/src/build/core/Variable.go +++ b/src/build/core/Variable.go @@ -2,16 +2,14 @@ package core import ( "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" ) // Variable represents a named register. type Variable struct { - Value *expression.Expression + Scope *Scope Name string Register cpu.Register Alive int - Scope *Scope } // Variable returns the variable with the given name or `nil` if it doesn't exist. From f4e4e49fcedc9541674214102f233a271d1f0a67 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 17 Jul 2024 12:38:57 +0200 Subject: [PATCH 0302/1012] Removed unused struct fields --- src/build/core/CompileDefinition.go | 2 +- src/build/core/Scope.go | 3 +-- src/build/core/Variable.go | 4 +--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index e591efa..852fb4f 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -41,7 +41,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { func (f *Function) AddVariable(variable *Variable) { if config.Comments { - f.Comment("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive) + f.Comment("%s = %s (%d uses)", variable.Name, variable.Register, variable.Alive) } scope := f.Scope() diff --git a/src/build/core/Scope.go b/src/build/core/Scope.go index 4cfcaeb..7aeb163 100644 --- a/src/build/core/Scope.go +++ b/src/build/core/Scope.go @@ -9,9 +9,9 @@ import ( // Scope represents an independent code block. type Scope struct { - cpu.State variables map[string]*Variable inLoop bool + cpu.State } // Scope returns the current scope. @@ -37,7 +37,6 @@ func (f *Function) pushScope(body ast.AST) *Scope { } scope.variables[k] = &Variable{ - Value: v.Value, Name: v.Name, Register: v.Register, Alive: count, diff --git a/src/build/core/Variable.go b/src/build/core/Variable.go index 4f31806..cca96e8 100644 --- a/src/build/core/Variable.go +++ b/src/build/core/Variable.go @@ -2,16 +2,14 @@ package core import ( "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" ) // Variable represents a named register. type Variable struct { - Value *expression.Expression + Scope *Scope Name string Register cpu.Register Alive int - Scope *Scope } // Variable returns the variable with the given name or `nil` if it doesn't exist. From 814364612f17d99443121ff99c2424bd63c06035 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 17 Jul 2024 15:23:57 +0200 Subject: [PATCH 0303/1012] Reduced memory allocations --- src/build/asm/Assembler.go | 9 +++++++++ src/build/core/Function.go | 1 - src/build/core/TokenToRegister.go | 4 ++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 6ad35c8..d539003 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -13,3 +13,12 @@ func (a *Assembler) Merge(b Assembler) { maps.Copy(a.Data, b.Data) a.Instructions = append(a.Instructions, b.Instructions...) } + +// SetData sets the data for the given label. +func (a *Assembler) SetData(label string, data []byte) { + if a.Data == nil { + a.Data = map[string][]byte{} + } + + a.Data[label] = data +} diff --git a/src/build/core/Function.go b/src/build/core/Function.go index b5c3c67..eef5bcd 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -27,7 +27,6 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { state: state{ assembler: asm.Assembler{ Instructions: make([]asm.Instruction, 0, 32), - Data: map[string][]byte{}, }, cpu: cpu.CPU{ All: x64.AllRegisters, diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index afedcb5..7f81b17 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -40,8 +40,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { case token.String: f.count.data++ label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data) - value := t.Text()[1 : len(t.Bytes)-1] - f.assembler.Data[label] = []byte(value) + value := t.Bytes[1 : len(t.Bytes)-1] + f.assembler.SetData(label, value) f.RegisterLabel(asm.MOVE, register, label) return nil From 8b2cdfa84171315f89cc6e8be83c68328b21c39e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 17 Jul 2024 15:23:57 +0200 Subject: [PATCH 0304/1012] Reduced memory allocations --- src/build/asm/Assembler.go | 9 +++++++++ src/build/core/Function.go | 1 - src/build/core/TokenToRegister.go | 4 ++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 6ad35c8..d539003 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -13,3 +13,12 @@ func (a *Assembler) Merge(b Assembler) { maps.Copy(a.Data, b.Data) a.Instructions = append(a.Instructions, b.Instructions...) } + +// SetData sets the data for the given label. +func (a *Assembler) SetData(label string, data []byte) { + if a.Data == nil { + a.Data = map[string][]byte{} + } + + a.Data[label] = data +} diff --git a/src/build/core/Function.go b/src/build/core/Function.go index b5c3c67..eef5bcd 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -27,7 +27,6 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { state: state{ assembler: asm.Assembler{ Instructions: make([]asm.Instruction, 0, 32), - Data: map[string][]byte{}, }, cpu: cpu.CPU{ All: x64.AllRegisters, diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index afedcb5..7f81b17 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -40,8 +40,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { case token.String: f.count.data++ label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data) - value := t.Text()[1 : len(t.Bytes)-1] - f.assembler.Data[label] = []byte(value) + value := t.Bytes[1 : len(t.Bytes)-1] + f.assembler.SetData(label, value) f.RegisterLabel(asm.MOVE, register, label) return nil From 48062065001e7b879f48b6dd4c1b858508193f84 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 17 Jul 2024 19:50:46 +0200 Subject: [PATCH 0305/1012] Added more tests --- src/build/core/CompileDefinition.go | 1 - src/build/cpu/Register_test.go | 13 +++++++++ src/build/cpu/State.go | 2 +- src/build/cpu/State_test.go | 45 +++++++++++++++++++++++++++++ src/build/fs/Walk_test.go | 7 ++++- 5 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 src/build/cpu/Register_test.go create mode 100644 src/build/cpu/State_test.go diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 852fb4f..a731841 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -48,7 +48,6 @@ func (f *Function) AddVariable(variable *Variable) { variable.Scope = scope scope.variables[variable.Name] = variable - scope.Reserve(variable.Register) scope.Use(variable.Register) } diff --git a/src/build/cpu/Register_test.go b/src/build/cpu/Register_test.go new file mode 100644 index 0000000..8c4c4ce --- /dev/null +++ b/src/build/cpu/Register_test.go @@ -0,0 +1,13 @@ +package cpu_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestRegisterString(t *testing.T) { + register := cpu.Register(1) + assert.Equal(t, "r1", register.String()) +} diff --git a/src/build/cpu/State.go b/src/build/cpu/State.go index af1637e..fb8659b 100644 --- a/src/build/cpu/State.go +++ b/src/build/cpu/State.go @@ -35,7 +35,7 @@ func (s *State) Use(reg Register) { // FindFree tries to find a free register in the given slice of registers. func (s *State) FindFree(registers []Register) (Register, bool) { for _, reg := range registers { - if !s.IsReserved(reg) { + if !s.IsReserved(reg) && !s.IsUsed(reg) { return reg, true } } diff --git a/src/build/cpu/State_test.go b/src/build/cpu/State_test.go new file mode 100644 index 0000000..90cdab7 --- /dev/null +++ b/src/build/cpu/State_test.go @@ -0,0 +1,45 @@ +package cpu_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestRegisterState(t *testing.T) { + s := cpu.State{} + assert.False(t, s.IsReserved(0)) + assert.False(t, s.IsUsed(0)) + s.Reserve(0) + assert.True(t, s.IsReserved(0)) + assert.False(t, s.IsUsed(0)) + s.Use(0) + assert.True(t, s.IsReserved(0)) + assert.True(t, s.IsUsed(0)) + s.Free(0) + assert.False(t, s.IsReserved(0)) + assert.False(t, s.IsUsed(0)) +} + +func TestFindFree(t *testing.T) { + s := cpu.State{} + s.Reserve(0) + s.Use(1) + + reg, found := s.FindFree([]cpu.Register{0, 1, 2, 3}) + assert.True(t, found) + assert.Equal(t, reg, 2) + + _, found = s.FindFree([]cpu.Register{0, 1}) + assert.False(t, found) +} + +func TestMustFindFree(t *testing.T) { + s := cpu.State{} + s.Reserve(0) + s.Use(1) + + reg := s.MustFindFree([]cpu.Register{0, 1, 2, 3}) + assert.Equal(t, reg, 2) +} diff --git a/src/build/fs/Walk_test.go b/src/build/fs/Walk_test.go index d3f6e17..82651ce 100644 --- a/src/build/fs/Walk_test.go +++ b/src/build/fs/Walk_test.go @@ -19,7 +19,12 @@ func TestWalk(t *testing.T) { assert.Contains(t, files, "Walk_test.go") } -func TestNonExisting(t *testing.T) { +func TestWalkFile(t *testing.T) { + err := fs.Walk("Walk.go", func(file string) {}) + assert.NotNil(t, err) +} + +func TestWalkNonExisting(t *testing.T) { err := fs.Walk("does-not-exist", func(file string) {}) assert.NotNil(t, err) } From 68e21c96e3e072dc46255aa9625a6ae1d0eeb603 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 17 Jul 2024 19:50:46 +0200 Subject: [PATCH 0306/1012] Added more tests --- src/build/core/CompileDefinition.go | 1 - src/build/cpu/Register_test.go | 13 +++++++++ src/build/cpu/State.go | 2 +- src/build/cpu/State_test.go | 45 +++++++++++++++++++++++++++++ src/build/fs/Walk_test.go | 7 ++++- 5 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 src/build/cpu/Register_test.go create mode 100644 src/build/cpu/State_test.go diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 852fb4f..a731841 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -48,7 +48,6 @@ func (f *Function) AddVariable(variable *Variable) { variable.Scope = scope scope.variables[variable.Name] = variable - scope.Reserve(variable.Register) scope.Use(variable.Register) } diff --git a/src/build/cpu/Register_test.go b/src/build/cpu/Register_test.go new file mode 100644 index 0000000..8c4c4ce --- /dev/null +++ b/src/build/cpu/Register_test.go @@ -0,0 +1,13 @@ +package cpu_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestRegisterString(t *testing.T) { + register := cpu.Register(1) + assert.Equal(t, "r1", register.String()) +} diff --git a/src/build/cpu/State.go b/src/build/cpu/State.go index af1637e..fb8659b 100644 --- a/src/build/cpu/State.go +++ b/src/build/cpu/State.go @@ -35,7 +35,7 @@ func (s *State) Use(reg Register) { // FindFree tries to find a free register in the given slice of registers. func (s *State) FindFree(registers []Register) (Register, bool) { for _, reg := range registers { - if !s.IsReserved(reg) { + if !s.IsReserved(reg) && !s.IsUsed(reg) { return reg, true } } diff --git a/src/build/cpu/State_test.go b/src/build/cpu/State_test.go new file mode 100644 index 0000000..90cdab7 --- /dev/null +++ b/src/build/cpu/State_test.go @@ -0,0 +1,45 @@ +package cpu_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestRegisterState(t *testing.T) { + s := cpu.State{} + assert.False(t, s.IsReserved(0)) + assert.False(t, s.IsUsed(0)) + s.Reserve(0) + assert.True(t, s.IsReserved(0)) + assert.False(t, s.IsUsed(0)) + s.Use(0) + assert.True(t, s.IsReserved(0)) + assert.True(t, s.IsUsed(0)) + s.Free(0) + assert.False(t, s.IsReserved(0)) + assert.False(t, s.IsUsed(0)) +} + +func TestFindFree(t *testing.T) { + s := cpu.State{} + s.Reserve(0) + s.Use(1) + + reg, found := s.FindFree([]cpu.Register{0, 1, 2, 3}) + assert.True(t, found) + assert.Equal(t, reg, 2) + + _, found = s.FindFree([]cpu.Register{0, 1}) + assert.False(t, found) +} + +func TestMustFindFree(t *testing.T) { + s := cpu.State{} + s.Reserve(0) + s.Use(1) + + reg := s.MustFindFree([]cpu.Register{0, 1, 2, 3}) + assert.Equal(t, reg, 2) +} diff --git a/src/build/fs/Walk_test.go b/src/build/fs/Walk_test.go index d3f6e17..82651ce 100644 --- a/src/build/fs/Walk_test.go +++ b/src/build/fs/Walk_test.go @@ -19,7 +19,12 @@ func TestWalk(t *testing.T) { assert.Contains(t, files, "Walk_test.go") } -func TestNonExisting(t *testing.T) { +func TestWalkFile(t *testing.T) { + err := fs.Walk("Walk.go", func(file string) {}) + assert.NotNil(t, err) +} + +func TestWalkNonExisting(t *testing.T) { err := fs.Walk("does-not-exist", func(file string) {}) assert.NotNil(t, err) } From bdb9312743230d1a37f95a8569f529c6596a8e5d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 17 Jul 2024 21:09:01 +0200 Subject: [PATCH 0307/1012] Reduced memory allocations --- src/build/token/Token.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/build/token/Token.go b/src/build/token/Token.go index e10f8bc..bf1208b 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -1,6 +1,9 @@ package token -import "fmt" +import ( + "fmt" + "unsafe" +) // Token represents a single element in a source file. // The characters that make up an identifier are grouped into a single token. @@ -30,5 +33,5 @@ func (t *Token) Reset() { // Text returns the token text. func (t *Token) Text() string { - return string(t.Bytes) + return unsafe.String(unsafe.SliceData(t.Bytes), len(t.Bytes)) } From f617e115cd3320ccbf76f087588a595d325073c8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 17 Jul 2024 21:09:01 +0200 Subject: [PATCH 0308/1012] Reduced memory allocations --- src/build/token/Token.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/build/token/Token.go b/src/build/token/Token.go index e10f8bc..bf1208b 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -1,6 +1,9 @@ package token -import "fmt" +import ( + "fmt" + "unsafe" +) // Token represents a single element in a source file. // The characters that make up an identifier are grouped into a single token. @@ -30,5 +33,5 @@ func (t *Token) Reset() { // Text returns the token text. func (t *Token) Text() string { - return string(t.Bytes) + return unsafe.String(unsafe.SliceData(t.Bytes), len(t.Bytes)) } From 35bc57b5fd7b499eea0361306f47e25844994aab Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 17 Jul 2024 22:28:06 +0200 Subject: [PATCH 0309/1012] Disabled garbage collection --- src/build/config/gc.go | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/build/config/gc.go diff --git a/src/build/config/gc.go b/src/build/config/gc.go new file mode 100644 index 0000000..522ae07 --- /dev/null +++ b/src/build/config/gc.go @@ -0,0 +1,7 @@ +package config + +import "runtime/debug" + +func init() { + debug.SetGCPercent(-1) +} From 6e3cc260927d86d383546d8c4d51d7856a6f86d7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 17 Jul 2024 22:28:06 +0200 Subject: [PATCH 0310/1012] Disabled garbage collection --- src/build/config/gc.go | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/build/config/gc.go diff --git a/src/build/config/gc.go b/src/build/config/gc.go new file mode 100644 index 0000000..522ae07 --- /dev/null +++ b/src/build/config/gc.go @@ -0,0 +1,7 @@ +package config + +import "runtime/debug" + +func init() { + debug.SetGCPercent(-1) +} From 6839473251b259233cb7bea025fe625be3cfb30b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 17 Jul 2024 23:04:00 +0200 Subject: [PATCH 0311/1012] Reduced memory allocations --- src/build/expression/Expression.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 8f99725..60b3794 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -28,6 +28,10 @@ func NewLeaf(t token.Token) *Expression { // AddChild adds a child to the expression. func (expr *Expression) AddChild(child *Expression) { + if expr.Children == nil { + expr.Children = make([]*Expression, 0, 2) + } + expr.Children = append(expr.Children, child) child.Parent = expr } From 77ccb8778f5e6c8b11035b67b4104381da938fb2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 17 Jul 2024 23:04:00 +0200 Subject: [PATCH 0312/1012] Reduced memory allocations --- src/build/expression/Expression.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 8f99725..60b3794 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -28,6 +28,10 @@ func NewLeaf(t token.Token) *Expression { // AddChild adds a child to the expression. func (expr *Expression) AddChild(child *Expression) { + if expr.Children == nil { + expr.Children = make([]*Expression, 0, 2) + } + expr.Children = append(expr.Children, child) child.Parent = expr } From 38043ca12af4ed4719eabd18de248573ee160592 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 18 Jul 2024 08:47:08 +0200 Subject: [PATCH 0313/1012] Added more tests --- src/build/arch/x64/Jump_test.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/build/arch/x64/Jump_test.go b/src/build/arch/x64/Jump_test.go index ba48db0..5c12133 100644 --- a/src/build/arch/x64/Jump_test.go +++ b/src/build/arch/x64/Jump_test.go @@ -7,7 +7,7 @@ import ( "git.akyoto.dev/go/assert" ) -func TestJump8(t *testing.T) { +func TestJump(t *testing.T) { usagePatterns := []struct { Offset int8 Code []byte @@ -29,3 +29,12 @@ func TestJump8(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestConditionalJump(t *testing.T) { + assert.DeepEqual(t, x64.Jump8IfEqual(nil, 1), []byte{0x74, 0x01}) + assert.DeepEqual(t, x64.Jump8IfNotEqual(nil, 1), []byte{0x75, 0x01}) + assert.DeepEqual(t, x64.Jump8IfLess(nil, 1), []byte{0x7C, 0x01}) + assert.DeepEqual(t, x64.Jump8IfGreaterOrEqual(nil, 1), []byte{0x7D, 0x01}) + assert.DeepEqual(t, x64.Jump8IfLessOrEqual(nil, 1), []byte{0x7E, 0x01}) + assert.DeepEqual(t, x64.Jump8IfGreater(nil, 1), []byte{0x7F, 0x01}) +} From c19ad2442807b61fd7268817c3ba150628bfd012 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 18 Jul 2024 08:47:08 +0200 Subject: [PATCH 0314/1012] Added more tests --- src/build/arch/x64/Jump_test.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/build/arch/x64/Jump_test.go b/src/build/arch/x64/Jump_test.go index ba48db0..5c12133 100644 --- a/src/build/arch/x64/Jump_test.go +++ b/src/build/arch/x64/Jump_test.go @@ -7,7 +7,7 @@ import ( "git.akyoto.dev/go/assert" ) -func TestJump8(t *testing.T) { +func TestJump(t *testing.T) { usagePatterns := []struct { Offset int8 Code []byte @@ -29,3 +29,12 @@ func TestJump8(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestConditionalJump(t *testing.T) { + assert.DeepEqual(t, x64.Jump8IfEqual(nil, 1), []byte{0x74, 0x01}) + assert.DeepEqual(t, x64.Jump8IfNotEqual(nil, 1), []byte{0x75, 0x01}) + assert.DeepEqual(t, x64.Jump8IfLess(nil, 1), []byte{0x7C, 0x01}) + assert.DeepEqual(t, x64.Jump8IfGreaterOrEqual(nil, 1), []byte{0x7D, 0x01}) + assert.DeepEqual(t, x64.Jump8IfLessOrEqual(nil, 1), []byte{0x7E, 0x01}) + assert.DeepEqual(t, x64.Jump8IfGreater(nil, 1), []byte{0x7F, 0x01}) +} From 61dc691c656375d2706e2c1cbc6c724c4dfc1683 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 18 Jul 2024 10:08:38 +0200 Subject: [PATCH 0315/1012] Separated compiler into its own package --- src/build/Build.go | 6 +- src/build/compiler/Compile.go | 75 +++++++++++++++++ src/build/{core => compiler}/Result.go | 20 ++--- src/build/core/Compile.go | 107 ++++++++++++++----------- src/build/core/CompileAllFunctions.go | 19 ----- src/build/core/CompileCall.go | 6 +- src/build/core/CompileDefinition.go | 8 +- src/build/core/Function.go | 67 +--------------- src/build/core/Instructions.go | 20 ++--- src/build/core/Scope.go | 2 +- src/build/core/{state.go => State.go} | 14 ++-- src/build/core/TokenToRegister.go | 2 +- src/build/core/Variable.go | 4 +- tests/examples_test.go | 16 ++++ 14 files changed, 199 insertions(+), 167 deletions(-) create mode 100644 src/build/compiler/Compile.go rename src/build/{core => compiler}/Result.go (79%) delete mode 100644 src/build/core/CompileAllFunctions.go rename src/build/core/{state.go => State.go} (85%) diff --git a/src/build/Build.go b/src/build/Build.go index bf90d2c..b292f4b 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -4,7 +4,7 @@ import ( "path/filepath" "strings" - "git.akyoto.dev/cli/q/src/build/core" + "git.akyoto.dev/cli/q/src/build/compiler" "git.akyoto.dev/cli/q/src/build/scanner" ) @@ -21,9 +21,9 @@ func New(files ...string) *Build { } // Run parses the input files and generates an executable file. -func (build *Build) Run() (core.Result, error) { +func (build *Build) Run() (compiler.Result, error) { functions, errors := scanner.Scan(build.Files) - return core.Compile(functions, errors) + return compiler.Compile(functions, errors) } // Executable returns the path to the executable. diff --git a/src/build/compiler/Compile.go b/src/build/compiler/Compile.go new file mode 100644 index 0000000..565fe28 --- /dev/null +++ b/src/build/compiler/Compile.go @@ -0,0 +1,75 @@ +package compiler + +import ( + "sync" + + "git.akyoto.dev/cli/q/src/build/core" + "git.akyoto.dev/cli/q/src/build/errors" +) + +// Compile waits for the scan to finish and compiles all functions. +func Compile(functions <-chan *core.Function, errs <-chan error) (Result, error) { + result := Result{} + all := map[string]*core.Function{} + + for functions != nil || errs != nil { + select { + case err, ok := <-errs: + if !ok { + errs = nil + continue + } + + return result, err + + case function, ok := <-functions: + if !ok { + functions = nil + continue + } + + function.Functions = all + all[function.Name] = function + } + } + + // Start parallel compilation + CompileFunctions(all) + + // Report errors if any occurred + for _, function := range all { + if function.Err != nil { + return result, function.Err + } + + result.InstructionCount += len(function.Assembler.Instructions) + result.DataCount += len(function.Assembler.Data) + } + + // Check for existence of `main` + main, exists := all["main"] + + if !exists { + return result, errors.MissingMainFunction + } + + result.Main = main + result.Functions = all + return result, nil +} + +// CompileFunctions starts a goroutine for each function compilation and waits for completion. +func CompileFunctions(functions map[string]*core.Function) { + wg := sync.WaitGroup{} + + for _, function := range functions { + wg.Add(1) + + go func() { + defer wg.Done() + function.Compile() + }() + } + + wg.Wait() +} diff --git a/src/build/core/Result.go b/src/build/compiler/Result.go similarity index 79% rename from src/build/core/Result.go rename to src/build/compiler/Result.go index 671a90a..2762d93 100644 --- a/src/build/core/Result.go +++ b/src/build/compiler/Result.go @@ -1,4 +1,4 @@ -package core +package compiler import ( "bufio" @@ -6,15 +6,17 @@ import ( "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/core" "git.akyoto.dev/cli/q/src/build/elf" "git.akyoto.dev/cli/q/src/build/os/linux" ) // Result contains all the compiled functions in a build. type Result struct { - Main *Function - Functions map[string]*Function + Main *core.Function + Functions map[string]*core.Function InstructionCount int + DataCount int } // finalize generates the final machine code. @@ -25,7 +27,7 @@ func (r *Result) finalize() ([]byte, []byte) { // a return address on the stack, which allows return statements in `main`. final := asm.Assembler{ Instructions: make([]asm.Instruction, 0, r.InstructionCount+4), - Data: map[string][]byte{}, + Data: make(map[string][]byte, r.DataCount), } final.Call("main") @@ -35,8 +37,8 @@ func (r *Result) finalize() ([]byte, []byte) { // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. - r.eachFunction(r.Main, map[*Function]bool{}, func(f *Function) { - final.Merge(f.assembler) + r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { + final.Merge(f.Assembler) }) code, data := final.Finalize() @@ -45,11 +47,11 @@ func (r *Result) finalize() ([]byte, []byte) { // eachFunction recursively finds all the calls to external functions. // It avoids calling the same function twice with the help of a hashmap. -func (r *Result) eachFunction(caller *Function, traversed map[*Function]bool, call func(*Function)) { +func (r *Result) eachFunction(caller *core.Function, traversed map[*core.Function]bool, call func(*core.Function)) { call(caller) traversed[caller] = true - for _, x := range caller.assembler.Instructions { + for _, x := range caller.Assembler.Instructions { if x.Mnemonic != asm.CALL { continue } @@ -71,7 +73,7 @@ func (r *Result) eachFunction(caller *Function, traversed map[*Function]bool, ca // PrintInstructions prints out the generated instructions. func (r *Result) PrintInstructions() { - r.eachFunction(r.Main, map[*Function]bool{}, func(f *Function) { + r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { f.PrintInstructions() }) } diff --git a/src/build/core/Compile.go b/src/build/core/Compile.go index edd8712..c581921 100644 --- a/src/build/core/Compile.go +++ b/src/build/core/Compile.go @@ -1,55 +1,66 @@ package core import ( + "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" ) -// Compile waits for the scan to finish and compiles all functions. -func Compile(functions <-chan *Function, errs <-chan error) (Result, error) { - result := Result{} - allFunctions := map[string]*Function{} - - for functions != nil || errs != nil { - select { - case err, ok := <-errs: - if !ok { - errs = nil - continue - } - - return result, err - - case function, ok := <-functions: - if !ok { - functions = nil - continue - } - - function.functions = allFunctions - allFunctions[function.Name] = function - } - } - - // Start parallel compilation - CompileAllFunctions(allFunctions) - - // Report errors if any occurred - for _, function := range allFunctions { - if function.err != nil { - return result, function.err - } - - result.InstructionCount += len(function.assembler.Instructions) - } - - // Check for existence of `main` - main, exists := allFunctions["main"] - - if !exists { - return result, errors.MissingMainFunction - } - - result.Main = main - result.Functions = allFunctions - return result, nil +// Compile turns a function into machine code. +func (f *Function) Compile() { + defer close(f.finished) + f.AddLabel(f.Name) + f.Err = f.CompileTokens(f.Body) + f.Return() +} + +// CompileTokens compiles a token list. +func (f *Function) CompileTokens(tokens token.List) error { + body, err := ast.Parse(tokens) + + if err != nil { + err.(*errors.Error).File = f.File + return err + } + + return f.CompileAST(body) +} + +// CompileAST compiles an abstract syntax tree. +func (f *Function) CompileAST(tree ast.AST) error { + for _, node := range tree { + err := f.CompileASTNode(node) + + if err != nil { + return err + } + } + + return nil +} + +// CompileASTNode compiles a node in the AST. +func (f *Function) CompileASTNode(node ast.Node) error { + switch node := node.(type) { + case *ast.Assign: + return f.CompileAssign(node) + + case *ast.Call: + return f.CompileCall(node.Expression) + + case *ast.Define: + return f.CompileDefinition(node) + + case *ast.Return: + return f.CompileReturn(node) + + case *ast.If: + return f.CompileIf(node) + + case *ast.Loop: + return f.CompileLoop(node) + + default: + panic("unknown AST type") + } } diff --git a/src/build/core/CompileAllFunctions.go b/src/build/core/CompileAllFunctions.go deleted file mode 100644 index 66d3ef9..0000000 --- a/src/build/core/CompileAllFunctions.go +++ /dev/null @@ -1,19 +0,0 @@ -package core - -import "sync" - -// CompileAllFunctions starts a goroutine for each function compilation and waits for completion. -func CompileAllFunctions(functions map[string]*Function) { - wg := sync.WaitGroup{} - - for _, function := range functions { - wg.Add(1) - - go func() { - defer wg.Done() - function.Compile() - }() - } - - wg.Wait() -} diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 9bb4f1f..ae04215 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -15,7 +15,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { isSyscall := funcName == "syscall" if !isSyscall { - _, exists := f.functions[funcName] + _, exists := f.Functions[funcName] if !exists { return errors.New(&errors.UnknownFunction{Name: funcName}, f.File, root.Children[0].Token.Position) @@ -52,6 +52,10 @@ func (f *Function) CompileCall(root *expression.Expression) error { } for _, register := range registers { + if register == f.cpu.Output[0] && root.Parent != nil { + continue + } + f.Scope().Free(register) } diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index a731841..52bafe3 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -63,6 +63,10 @@ func (f *Function) useVariable(variable *Variable) { continue } + if config.Comments { + f.Comment("%s (%s) used in scope %d", local.Name, local.Register, i) + } + local.Alive-- if local.Alive < 0 { @@ -71,7 +75,7 @@ func (f *Function) useVariable(variable *Variable) { if local.Alive == 0 { if config.Comments { - f.Comment("%s died (%s) in scope %d", local.Name, local.Register, i) + f.Comment("%s (%s) died in scope %d", local.Name, local.Register, i) } scope.Free(local.Register) @@ -87,7 +91,7 @@ func (f *Function) identifierExists(name string) bool { return true } - _, exists := f.functions[name] + _, exists := f.Functions[name] return exists } diff --git a/src/build/core/Function.go b/src/build/core/Function.go index eef5bcd..f154692 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -3,9 +3,7 @@ package core import ( "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" ) @@ -15,7 +13,7 @@ type Function struct { Name string File *fs.File Body token.List - state + State } // NewFunction creates a new function. @@ -24,8 +22,8 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { Name: name, File: file, Body: body, - state: state{ - assembler: asm.Assembler{ + State: State{ + Assembler: asm.Assembler{ Instructions: make([]asm.Instruction, 0, 32), }, cpu: cpu.CPU{ @@ -43,65 +41,6 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { } } -// Compile turns a function into machine code. -func (f *Function) Compile() { - defer close(f.finished) - f.AddLabel(f.Name) - f.err = f.CompileTokens(f.Body) - f.Return() -} - -// CompileTokens compiles a token list. -func (f *Function) CompileTokens(tokens token.List) error { - body, err := ast.Parse(tokens) - - if err != nil { - err.(*errors.Error).File = f.File - return err - } - - return f.CompileAST(body) -} - -// CompileAST compiles an abstract syntax tree. -func (f *Function) CompileAST(tree ast.AST) error { - for _, node := range tree { - err := f.CompileASTNode(node) - - if err != nil { - return err - } - } - - return nil -} - -// CompileASTNode compiles a node in the AST. -func (f *Function) CompileASTNode(node ast.Node) error { - switch node := node.(type) { - case *ast.Assign: - return f.CompileAssign(node) - - case *ast.Call: - return f.CompileCall(node.Expression) - - case *ast.Define: - return f.CompileDefinition(node) - - case *ast.Return: - return f.CompileReturn(node) - - case *ast.If: - return f.CompileIf(node) - - case *ast.Loop: - return f.CompileLoop(node) - - default: - panic("unknown AST type") - } -} - // String returns the function name. func (f *Function) String() string { return f.Name diff --git a/src/build/core/Instructions.go b/src/build/core/Instructions.go index 7a35222..9bdf8fa 100644 --- a/src/build/core/Instructions.go +++ b/src/build/core/Instructions.go @@ -9,28 +9,28 @@ import ( ) func (f *Function) AddLabel(label string) { - f.assembler.Label(asm.LABEL, label) + f.Assembler.Label(asm.LABEL, label) f.postInstruction() } func (f *Function) Call(label string) { - f.assembler.Call(label) + f.Assembler.Call(label) f.Scope().Use(f.cpu.Output[0]) f.postInstruction() } func (f *Function) Comment(format string, args ...any) { - f.assembler.Comment(fmt.Sprintf(format, args...)) + f.Assembler.Comment(fmt.Sprintf(format, args...)) f.postInstruction() } func (f *Function) Jump(mnemonic asm.Mnemonic, label string) { - f.assembler.Label(mnemonic, label) + f.Assembler.Label(mnemonic, label) f.postInstruction() } func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) { - f.assembler.Register(mnemonic, a) + f.Assembler.Register(mnemonic, a) if mnemonic == asm.POP { f.Scope().Use(a) @@ -44,7 +44,7 @@ func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) f.SaveRegister(a) } - f.assembler.RegisterNumber(mnemonic, a, b) + f.Assembler.RegisterNumber(mnemonic, a, b) if mnemonic == asm.MOVE { f.Scope().Use(a) @@ -58,7 +58,7 @@ func (f *Function) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, l f.SaveRegister(register) } - f.assembler.RegisterLabel(mnemonic, register, label) + f.Assembler.RegisterLabel(mnemonic, register, label) if mnemonic == asm.MOVE { f.Scope().Use(register) @@ -76,7 +76,7 @@ func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu f.SaveRegister(a) } - f.assembler.RegisterRegister(mnemonic, a, b) + f.Assembler.RegisterRegister(mnemonic, a, b) if mnemonic == asm.MOVE { f.Scope().Use(a) @@ -86,12 +86,12 @@ func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu } func (f *Function) Return() { - f.assembler.Return() + f.Assembler.Return() f.postInstruction() } func (f *Function) Syscall() { - f.assembler.Syscall() + f.Assembler.Syscall() f.Scope().Use(f.cpu.Output[0]) f.postInstruction() } diff --git a/src/build/core/Scope.go b/src/build/core/Scope.go index 7aeb163..655ab27 100644 --- a/src/build/core/Scope.go +++ b/src/build/core/Scope.go @@ -15,7 +15,7 @@ type Scope struct { } // Scope returns the current scope. -func (s *state) Scope() *Scope { +func (s *State) Scope() *Scope { return s.scopes[len(s.scopes)-1] } diff --git a/src/build/core/state.go b/src/build/core/State.go similarity index 85% rename from src/build/core/state.go rename to src/build/core/State.go index b345c69..a75c58c 100644 --- a/src/build/core/state.go +++ b/src/build/core/State.go @@ -9,14 +9,14 @@ import ( "git.akyoto.dev/go/color/ansi" ) -// state is the data structure we embed in each function to preserve compilation state. -type state struct { - err error +// State is the data structure we embed in each function to preserve compilation State. +type State struct { + Assembler asm.Assembler + Functions map[string]*Function + Err error scopes []*Scope - functions map[string]*Function registerHistory []uint64 finished chan struct{} - assembler asm.Assembler cpu cpu.CPU count counter } @@ -30,10 +30,10 @@ type counter struct { } // PrintInstructions shows the assembly instructions. -func (s *state) PrintInstructions() { +func (s *State) PrintInstructions() { ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮") - for i, x := range s.assembler.Instructions { + for i, x := range s.Assembler.Instructions { ansi.Dim.Print("│ ") switch x.Mnemonic { diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 7f81b17..85e8bf9 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -41,7 +41,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { f.count.data++ label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data) value := t.Bytes[1 : len(t.Bytes)-1] - f.assembler.SetData(label, value) + f.Assembler.SetData(label, value) f.RegisterLabel(asm.MOVE, register, label) return nil diff --git a/src/build/core/Variable.go b/src/build/core/Variable.go index cca96e8..2045aef 100644 --- a/src/build/core/Variable.go +++ b/src/build/core/Variable.go @@ -13,12 +13,12 @@ type Variable struct { } // Variable returns the variable with the given name or `nil` if it doesn't exist. -func (s *state) Variable(name string) *Variable { +func (s *State) Variable(name string) *Variable { return s.Scope().variables[name] } // VariableInRegister returns the variable that occupies the given register or `nil` if none occupy the register. -func (s *state) VariableInRegister(register cpu.Register) *Variable { +func (s *State) VariableInRegister(register cpu.Register) *Variable { for _, v := range s.Scope().variables { if v.Register == register { return v diff --git a/tests/examples_test.go b/tests/examples_test.go index d9f19f4..8af877d 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -3,6 +3,9 @@ package tests_test import ( "path/filepath" "testing" + + "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/go/assert" ) var examples = []struct { @@ -22,3 +25,16 @@ func TestExamples(t *testing.T) { }) } } + +func BenchmarkExamples(b *testing.B) { + for _, test := range examples { + b.Run(test.Name, func(b *testing.B) { + compiler := build.New(filepath.Join("..", "examples", test.Name)) + + for i := 0; i < b.N; i++ { + _, err := compiler.Run() + assert.Nil(b, err) + } + }) + } +} From 724794b4aab53c6b363a9349c329b8b6cde530c7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 18 Jul 2024 10:08:38 +0200 Subject: [PATCH 0316/1012] Separated compiler into its own package --- src/build/Build.go | 6 +- src/build/compiler/Compile.go | 75 +++++++++++++++++ src/build/{core => compiler}/Result.go | 20 ++--- src/build/core/Compile.go | 107 ++++++++++++++----------- src/build/core/CompileAllFunctions.go | 19 ----- src/build/core/CompileCall.go | 6 +- src/build/core/CompileDefinition.go | 8 +- src/build/core/Function.go | 67 +--------------- src/build/core/Instructions.go | 20 ++--- src/build/core/Scope.go | 2 +- src/build/core/{state.go => State.go} | 14 ++-- src/build/core/TokenToRegister.go | 2 +- src/build/core/Variable.go | 4 +- tests/examples_test.go | 16 ++++ 14 files changed, 199 insertions(+), 167 deletions(-) create mode 100644 src/build/compiler/Compile.go rename src/build/{core => compiler}/Result.go (79%) delete mode 100644 src/build/core/CompileAllFunctions.go rename src/build/core/{state.go => State.go} (85%) diff --git a/src/build/Build.go b/src/build/Build.go index bf90d2c..b292f4b 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -4,7 +4,7 @@ import ( "path/filepath" "strings" - "git.akyoto.dev/cli/q/src/build/core" + "git.akyoto.dev/cli/q/src/build/compiler" "git.akyoto.dev/cli/q/src/build/scanner" ) @@ -21,9 +21,9 @@ func New(files ...string) *Build { } // Run parses the input files and generates an executable file. -func (build *Build) Run() (core.Result, error) { +func (build *Build) Run() (compiler.Result, error) { functions, errors := scanner.Scan(build.Files) - return core.Compile(functions, errors) + return compiler.Compile(functions, errors) } // Executable returns the path to the executable. diff --git a/src/build/compiler/Compile.go b/src/build/compiler/Compile.go new file mode 100644 index 0000000..565fe28 --- /dev/null +++ b/src/build/compiler/Compile.go @@ -0,0 +1,75 @@ +package compiler + +import ( + "sync" + + "git.akyoto.dev/cli/q/src/build/core" + "git.akyoto.dev/cli/q/src/build/errors" +) + +// Compile waits for the scan to finish and compiles all functions. +func Compile(functions <-chan *core.Function, errs <-chan error) (Result, error) { + result := Result{} + all := map[string]*core.Function{} + + for functions != nil || errs != nil { + select { + case err, ok := <-errs: + if !ok { + errs = nil + continue + } + + return result, err + + case function, ok := <-functions: + if !ok { + functions = nil + continue + } + + function.Functions = all + all[function.Name] = function + } + } + + // Start parallel compilation + CompileFunctions(all) + + // Report errors if any occurred + for _, function := range all { + if function.Err != nil { + return result, function.Err + } + + result.InstructionCount += len(function.Assembler.Instructions) + result.DataCount += len(function.Assembler.Data) + } + + // Check for existence of `main` + main, exists := all["main"] + + if !exists { + return result, errors.MissingMainFunction + } + + result.Main = main + result.Functions = all + return result, nil +} + +// CompileFunctions starts a goroutine for each function compilation and waits for completion. +func CompileFunctions(functions map[string]*core.Function) { + wg := sync.WaitGroup{} + + for _, function := range functions { + wg.Add(1) + + go func() { + defer wg.Done() + function.Compile() + }() + } + + wg.Wait() +} diff --git a/src/build/core/Result.go b/src/build/compiler/Result.go similarity index 79% rename from src/build/core/Result.go rename to src/build/compiler/Result.go index 671a90a..2762d93 100644 --- a/src/build/core/Result.go +++ b/src/build/compiler/Result.go @@ -1,4 +1,4 @@ -package core +package compiler import ( "bufio" @@ -6,15 +6,17 @@ import ( "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/core" "git.akyoto.dev/cli/q/src/build/elf" "git.akyoto.dev/cli/q/src/build/os/linux" ) // Result contains all the compiled functions in a build. type Result struct { - Main *Function - Functions map[string]*Function + Main *core.Function + Functions map[string]*core.Function InstructionCount int + DataCount int } // finalize generates the final machine code. @@ -25,7 +27,7 @@ func (r *Result) finalize() ([]byte, []byte) { // a return address on the stack, which allows return statements in `main`. final := asm.Assembler{ Instructions: make([]asm.Instruction, 0, r.InstructionCount+4), - Data: map[string][]byte{}, + Data: make(map[string][]byte, r.DataCount), } final.Call("main") @@ -35,8 +37,8 @@ func (r *Result) finalize() ([]byte, []byte) { // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. - r.eachFunction(r.Main, map[*Function]bool{}, func(f *Function) { - final.Merge(f.assembler) + r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { + final.Merge(f.Assembler) }) code, data := final.Finalize() @@ -45,11 +47,11 @@ func (r *Result) finalize() ([]byte, []byte) { // eachFunction recursively finds all the calls to external functions. // It avoids calling the same function twice with the help of a hashmap. -func (r *Result) eachFunction(caller *Function, traversed map[*Function]bool, call func(*Function)) { +func (r *Result) eachFunction(caller *core.Function, traversed map[*core.Function]bool, call func(*core.Function)) { call(caller) traversed[caller] = true - for _, x := range caller.assembler.Instructions { + for _, x := range caller.Assembler.Instructions { if x.Mnemonic != asm.CALL { continue } @@ -71,7 +73,7 @@ func (r *Result) eachFunction(caller *Function, traversed map[*Function]bool, ca // PrintInstructions prints out the generated instructions. func (r *Result) PrintInstructions() { - r.eachFunction(r.Main, map[*Function]bool{}, func(f *Function) { + r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { f.PrintInstructions() }) } diff --git a/src/build/core/Compile.go b/src/build/core/Compile.go index edd8712..c581921 100644 --- a/src/build/core/Compile.go +++ b/src/build/core/Compile.go @@ -1,55 +1,66 @@ package core import ( + "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" ) -// Compile waits for the scan to finish and compiles all functions. -func Compile(functions <-chan *Function, errs <-chan error) (Result, error) { - result := Result{} - allFunctions := map[string]*Function{} - - for functions != nil || errs != nil { - select { - case err, ok := <-errs: - if !ok { - errs = nil - continue - } - - return result, err - - case function, ok := <-functions: - if !ok { - functions = nil - continue - } - - function.functions = allFunctions - allFunctions[function.Name] = function - } - } - - // Start parallel compilation - CompileAllFunctions(allFunctions) - - // Report errors if any occurred - for _, function := range allFunctions { - if function.err != nil { - return result, function.err - } - - result.InstructionCount += len(function.assembler.Instructions) - } - - // Check for existence of `main` - main, exists := allFunctions["main"] - - if !exists { - return result, errors.MissingMainFunction - } - - result.Main = main - result.Functions = allFunctions - return result, nil +// Compile turns a function into machine code. +func (f *Function) Compile() { + defer close(f.finished) + f.AddLabel(f.Name) + f.Err = f.CompileTokens(f.Body) + f.Return() +} + +// CompileTokens compiles a token list. +func (f *Function) CompileTokens(tokens token.List) error { + body, err := ast.Parse(tokens) + + if err != nil { + err.(*errors.Error).File = f.File + return err + } + + return f.CompileAST(body) +} + +// CompileAST compiles an abstract syntax tree. +func (f *Function) CompileAST(tree ast.AST) error { + for _, node := range tree { + err := f.CompileASTNode(node) + + if err != nil { + return err + } + } + + return nil +} + +// CompileASTNode compiles a node in the AST. +func (f *Function) CompileASTNode(node ast.Node) error { + switch node := node.(type) { + case *ast.Assign: + return f.CompileAssign(node) + + case *ast.Call: + return f.CompileCall(node.Expression) + + case *ast.Define: + return f.CompileDefinition(node) + + case *ast.Return: + return f.CompileReturn(node) + + case *ast.If: + return f.CompileIf(node) + + case *ast.Loop: + return f.CompileLoop(node) + + default: + panic("unknown AST type") + } } diff --git a/src/build/core/CompileAllFunctions.go b/src/build/core/CompileAllFunctions.go deleted file mode 100644 index 66d3ef9..0000000 --- a/src/build/core/CompileAllFunctions.go +++ /dev/null @@ -1,19 +0,0 @@ -package core - -import "sync" - -// CompileAllFunctions starts a goroutine for each function compilation and waits for completion. -func CompileAllFunctions(functions map[string]*Function) { - wg := sync.WaitGroup{} - - for _, function := range functions { - wg.Add(1) - - go func() { - defer wg.Done() - function.Compile() - }() - } - - wg.Wait() -} diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 9bb4f1f..ae04215 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -15,7 +15,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { isSyscall := funcName == "syscall" if !isSyscall { - _, exists := f.functions[funcName] + _, exists := f.Functions[funcName] if !exists { return errors.New(&errors.UnknownFunction{Name: funcName}, f.File, root.Children[0].Token.Position) @@ -52,6 +52,10 @@ func (f *Function) CompileCall(root *expression.Expression) error { } for _, register := range registers { + if register == f.cpu.Output[0] && root.Parent != nil { + continue + } + f.Scope().Free(register) } diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index a731841..52bafe3 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -63,6 +63,10 @@ func (f *Function) useVariable(variable *Variable) { continue } + if config.Comments { + f.Comment("%s (%s) used in scope %d", local.Name, local.Register, i) + } + local.Alive-- if local.Alive < 0 { @@ -71,7 +75,7 @@ func (f *Function) useVariable(variable *Variable) { if local.Alive == 0 { if config.Comments { - f.Comment("%s died (%s) in scope %d", local.Name, local.Register, i) + f.Comment("%s (%s) died in scope %d", local.Name, local.Register, i) } scope.Free(local.Register) @@ -87,7 +91,7 @@ func (f *Function) identifierExists(name string) bool { return true } - _, exists := f.functions[name] + _, exists := f.Functions[name] return exists } diff --git a/src/build/core/Function.go b/src/build/core/Function.go index eef5bcd..f154692 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -3,9 +3,7 @@ package core import ( "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" ) @@ -15,7 +13,7 @@ type Function struct { Name string File *fs.File Body token.List - state + State } // NewFunction creates a new function. @@ -24,8 +22,8 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { Name: name, File: file, Body: body, - state: state{ - assembler: asm.Assembler{ + State: State{ + Assembler: asm.Assembler{ Instructions: make([]asm.Instruction, 0, 32), }, cpu: cpu.CPU{ @@ -43,65 +41,6 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { } } -// Compile turns a function into machine code. -func (f *Function) Compile() { - defer close(f.finished) - f.AddLabel(f.Name) - f.err = f.CompileTokens(f.Body) - f.Return() -} - -// CompileTokens compiles a token list. -func (f *Function) CompileTokens(tokens token.List) error { - body, err := ast.Parse(tokens) - - if err != nil { - err.(*errors.Error).File = f.File - return err - } - - return f.CompileAST(body) -} - -// CompileAST compiles an abstract syntax tree. -func (f *Function) CompileAST(tree ast.AST) error { - for _, node := range tree { - err := f.CompileASTNode(node) - - if err != nil { - return err - } - } - - return nil -} - -// CompileASTNode compiles a node in the AST. -func (f *Function) CompileASTNode(node ast.Node) error { - switch node := node.(type) { - case *ast.Assign: - return f.CompileAssign(node) - - case *ast.Call: - return f.CompileCall(node.Expression) - - case *ast.Define: - return f.CompileDefinition(node) - - case *ast.Return: - return f.CompileReturn(node) - - case *ast.If: - return f.CompileIf(node) - - case *ast.Loop: - return f.CompileLoop(node) - - default: - panic("unknown AST type") - } -} - // String returns the function name. func (f *Function) String() string { return f.Name diff --git a/src/build/core/Instructions.go b/src/build/core/Instructions.go index 7a35222..9bdf8fa 100644 --- a/src/build/core/Instructions.go +++ b/src/build/core/Instructions.go @@ -9,28 +9,28 @@ import ( ) func (f *Function) AddLabel(label string) { - f.assembler.Label(asm.LABEL, label) + f.Assembler.Label(asm.LABEL, label) f.postInstruction() } func (f *Function) Call(label string) { - f.assembler.Call(label) + f.Assembler.Call(label) f.Scope().Use(f.cpu.Output[0]) f.postInstruction() } func (f *Function) Comment(format string, args ...any) { - f.assembler.Comment(fmt.Sprintf(format, args...)) + f.Assembler.Comment(fmt.Sprintf(format, args...)) f.postInstruction() } func (f *Function) Jump(mnemonic asm.Mnemonic, label string) { - f.assembler.Label(mnemonic, label) + f.Assembler.Label(mnemonic, label) f.postInstruction() } func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) { - f.assembler.Register(mnemonic, a) + f.Assembler.Register(mnemonic, a) if mnemonic == asm.POP { f.Scope().Use(a) @@ -44,7 +44,7 @@ func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) f.SaveRegister(a) } - f.assembler.RegisterNumber(mnemonic, a, b) + f.Assembler.RegisterNumber(mnemonic, a, b) if mnemonic == asm.MOVE { f.Scope().Use(a) @@ -58,7 +58,7 @@ func (f *Function) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, l f.SaveRegister(register) } - f.assembler.RegisterLabel(mnemonic, register, label) + f.Assembler.RegisterLabel(mnemonic, register, label) if mnemonic == asm.MOVE { f.Scope().Use(register) @@ -76,7 +76,7 @@ func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu f.SaveRegister(a) } - f.assembler.RegisterRegister(mnemonic, a, b) + f.Assembler.RegisterRegister(mnemonic, a, b) if mnemonic == asm.MOVE { f.Scope().Use(a) @@ -86,12 +86,12 @@ func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu } func (f *Function) Return() { - f.assembler.Return() + f.Assembler.Return() f.postInstruction() } func (f *Function) Syscall() { - f.assembler.Syscall() + f.Assembler.Syscall() f.Scope().Use(f.cpu.Output[0]) f.postInstruction() } diff --git a/src/build/core/Scope.go b/src/build/core/Scope.go index 7aeb163..655ab27 100644 --- a/src/build/core/Scope.go +++ b/src/build/core/Scope.go @@ -15,7 +15,7 @@ type Scope struct { } // Scope returns the current scope. -func (s *state) Scope() *Scope { +func (s *State) Scope() *Scope { return s.scopes[len(s.scopes)-1] } diff --git a/src/build/core/state.go b/src/build/core/State.go similarity index 85% rename from src/build/core/state.go rename to src/build/core/State.go index b345c69..a75c58c 100644 --- a/src/build/core/state.go +++ b/src/build/core/State.go @@ -9,14 +9,14 @@ import ( "git.akyoto.dev/go/color/ansi" ) -// state is the data structure we embed in each function to preserve compilation state. -type state struct { - err error +// State is the data structure we embed in each function to preserve compilation State. +type State struct { + Assembler asm.Assembler + Functions map[string]*Function + Err error scopes []*Scope - functions map[string]*Function registerHistory []uint64 finished chan struct{} - assembler asm.Assembler cpu cpu.CPU count counter } @@ -30,10 +30,10 @@ type counter struct { } // PrintInstructions shows the assembly instructions. -func (s *state) PrintInstructions() { +func (s *State) PrintInstructions() { ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮") - for i, x := range s.assembler.Instructions { + for i, x := range s.Assembler.Instructions { ansi.Dim.Print("│ ") switch x.Mnemonic { diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 7f81b17..85e8bf9 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -41,7 +41,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { f.count.data++ label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data) value := t.Bytes[1 : len(t.Bytes)-1] - f.assembler.SetData(label, value) + f.Assembler.SetData(label, value) f.RegisterLabel(asm.MOVE, register, label) return nil diff --git a/src/build/core/Variable.go b/src/build/core/Variable.go index cca96e8..2045aef 100644 --- a/src/build/core/Variable.go +++ b/src/build/core/Variable.go @@ -13,12 +13,12 @@ type Variable struct { } // Variable returns the variable with the given name or `nil` if it doesn't exist. -func (s *state) Variable(name string) *Variable { +func (s *State) Variable(name string) *Variable { return s.Scope().variables[name] } // VariableInRegister returns the variable that occupies the given register or `nil` if none occupy the register. -func (s *state) VariableInRegister(register cpu.Register) *Variable { +func (s *State) VariableInRegister(register cpu.Register) *Variable { for _, v := range s.Scope().variables { if v.Register == register { return v diff --git a/tests/examples_test.go b/tests/examples_test.go index d9f19f4..8af877d 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -3,6 +3,9 @@ package tests_test import ( "path/filepath" "testing" + + "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/go/assert" ) var examples = []struct { @@ -22,3 +25,16 @@ func TestExamples(t *testing.T) { }) } } + +func BenchmarkExamples(b *testing.B) { + for _, test := range examples { + b.Run(test.Name, func(b *testing.B) { + compiler := build.New(filepath.Join("..", "examples", test.Name)) + + for i := 0; i < b.N; i++ { + _, err := compiler.Run() + assert.Nil(b, err) + } + }) + } +} From 3e5adff5e51011f31389fde9e0d7628998411e54 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 18 Jul 2024 11:40:07 +0200 Subject: [PATCH 0317/1012] Improved variable lifetime comments --- src/build/core/CompileDefinition.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 52bafe3..bbc544b 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -63,10 +63,6 @@ func (f *Function) useVariable(variable *Variable) { continue } - if config.Comments { - f.Comment("%s (%s) used in scope %d", local.Name, local.Register, i) - } - local.Alive-- if local.Alive < 0 { @@ -79,6 +75,8 @@ func (f *Function) useVariable(variable *Variable) { } scope.Free(local.Register) + } else if config.Comments { + f.Comment("%s (%s) used in scope %d", local.Name, local.Register, i) } } } From fa373e7dc2ef5ac441672bdb81c6d1d0682e7b90 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 18 Jul 2024 11:40:07 +0200 Subject: [PATCH 0318/1012] Improved variable lifetime comments --- src/build/core/CompileDefinition.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 52bafe3..bbc544b 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -63,10 +63,6 @@ func (f *Function) useVariable(variable *Variable) { continue } - if config.Comments { - f.Comment("%s (%s) used in scope %d", local.Name, local.Register, i) - } - local.Alive-- if local.Alive < 0 { @@ -79,6 +75,8 @@ func (f *Function) useVariable(variable *Variable) { } scope.Free(local.Register) + } else if config.Comments { + f.Comment("%s (%s) used in scope %d", local.Name, local.Register, i) } } } From fcc4f8d2d910a7970e9a6a1da3e5ca3b82c0771b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 18 Jul 2024 18:08:30 +0200 Subject: [PATCH 0319/1012] Implemented package imports --- examples/factorial/factorial.q | 4 +- examples/fibonacci/fibonacci.q | 4 +- examples/hello/hello.q | 8 +- lib/sys/linux.q | 55 ++++++ src/build/config/gc.go | 7 - src/build/config/init.go | 53 ++++++ src/build/core/CompileCall.go | 10 +- src/build/keyword/Keyword.go | 2 + src/build/scanner/Scan.go | 280 ++-------------------------- src/build/scanner/Scanner.go | 14 ++ src/build/scanner/queue.go | 21 +++ src/build/scanner/queueDirectory.go | 24 +++ src/build/scanner/queueFile.go | 15 ++ src/build/scanner/scanFile.go | 238 +++++++++++++++++++++++ tests/programs/branch-and.q | 22 +-- tests/programs/branch-both.q | 26 ++- tests/programs/branch-or.q | 14 +- tests/programs/branch.q | 40 ++-- tests/programs/chained-calls.q | 8 +- tests/programs/jump-near.q | 10 +- tests/programs/nested-calls.q | 8 +- 21 files changed, 510 insertions(+), 353 deletions(-) create mode 100644 lib/sys/linux.q delete mode 100644 src/build/config/gc.go create mode 100644 src/build/config/init.go create mode 100644 src/build/scanner/Scanner.go create mode 100644 src/build/scanner/queue.go create mode 100644 src/build/scanner/queueDirectory.go create mode 100644 src/build/scanner/queueFile.go create mode 100644 src/build/scanner/scanFile.go diff --git a/examples/factorial/factorial.q b/examples/factorial/factorial.q index f198ec1..30315ec 100644 --- a/examples/factorial/factorial.q +++ b/examples/factorial/factorial.q @@ -1,5 +1,7 @@ +import sys + main() { - syscall(60, factorial(5)) + sys.exit(factorial(5)) } factorial(x) { diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q index e02a15c..505bf26 100644 --- a/examples/fibonacci/fibonacci.q +++ b/examples/fibonacci/fibonacci.q @@ -1,5 +1,7 @@ +import sys + main() { - syscall(60, fibonacci(10)) + sys.exit(fibonacci(10)) } fibonacci(x) { diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 85aa05c..6adfc54 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,11 +1,9 @@ +import sys + main() { print("Hello", 5) } print(address, length) { - write(1, address, length) -} - -write(fd, address, length) { - syscall(1, fd, address, length) + sys.write(1, address, length) } \ No newline at end of file diff --git a/lib/sys/linux.q b/lib/sys/linux.q new file mode 100644 index 0000000..9c44037 --- /dev/null +++ b/lib/sys/linux.q @@ -0,0 +1,55 @@ +read(fd, address, length) { + return syscall(0, fd, address, length) +} + +write(fd, address, length) { + syscall(1, fd, address, length) +} + +open(file, flags, mode) { + return syscall(2, file, flags, mode) +} + +close(fd) { + return syscall(3, fd) +} + +mmap(address, length, protection, flags) { + return syscall(9, address, length, protection, flags) +} + +munmap(address, length) { + return syscall(11, address, length) +} + +clone(flags, stack) { + return syscall(56, flags, stack) +} + +exit(code) { + syscall(60, code) +} + +getcwd(buffer, length) { + return syscall(79, buffer, length) +} + +chdir(path) { + return syscall(80, path) +} + +rename(old, new) { + return syscall(82, old, new) +} + +mkdir(path, mode) { + return syscall(83, path, mode) +} + +rmdir(path) { + return syscall(84, path) +} + +unlink(file) { + return syscall(87, file) +} diff --git a/src/build/config/gc.go b/src/build/config/gc.go deleted file mode 100644 index 522ae07..0000000 --- a/src/build/config/gc.go +++ /dev/null @@ -1,7 +0,0 @@ -package config - -import "runtime/debug" - -func init() { - debug.SetGCPercent(-1) -} diff --git a/src/build/config/init.go b/src/build/config/init.go new file mode 100644 index 0000000..31732e8 --- /dev/null +++ b/src/build/config/init.go @@ -0,0 +1,53 @@ +package config + +import ( + "os" + "path" + "path/filepath" + "runtime/debug" +) + +var ( + Executable string + Root string + Library string +) + +func init() { + debug.SetGCPercent(-1) + executable, err := os.Executable() + + if err != nil { + panic(err) + } + + Executable = executable + Root = filepath.Dir(executable) + Library = filepath.Join(Root, "lib") + stat, err := os.Stat(Library) + + if !os.IsNotExist(err) && stat != nil && stat.IsDir() { + return + } + + dir, err := os.Getwd() + + if err != nil { + panic(err) + } + + for { + Library = path.Join(dir, "lib") + stat, err := os.Stat(Library) + + if !os.IsNotExist(err) && stat != nil && stat.IsDir() { + return + } + + if dir == "/" { + panic("standard library not found") + } + + dir = filepath.Dir(dir) + } +} diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index ae04215..53250d9 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -11,7 +11,15 @@ import ( // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. func (f *Function) CompileCall(root *expression.Expression) error { - funcName := root.Children[0].Token.Text() + funcNameRoot := root.Children[0] + funcName := "" + + if funcNameRoot.IsLeaf() { + funcName = funcNameRoot.Token.Text() + } else { + funcName = funcNameRoot.Children[0].Token.Text() + funcNameRoot.Token.Text() + funcNameRoot.Children[1].Token.Text() + } + isSyscall := funcName == "syscall" if !isSyscall { diff --git a/src/build/keyword/Keyword.go b/src/build/keyword/Keyword.go index d744384..ef62575 100644 --- a/src/build/keyword/Keyword.go +++ b/src/build/keyword/Keyword.go @@ -2,6 +2,7 @@ package keyword const ( If = "if" + Import = "import" Loop = "loop" Return = "return" ) @@ -9,6 +10,7 @@ const ( // Map is a map of all keywords used in the language. var Map = map[string][]byte{ If: []byte(If), + Import: []byte(Import), Loop: []byte(Loop), Return: []byte(Return), } diff --git a/src/build/scanner/Scan.go b/src/build/scanner/Scan.go index eb3e760..b79d04f 100644 --- a/src/build/scanner/Scan.go +++ b/src/build/scanner/Scan.go @@ -1,278 +1,20 @@ package scanner -import ( - "os" - "path/filepath" - "strings" - "sync" +import "git.akyoto.dev/cli/q/src/build/core" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/core" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/fs" - "git.akyoto.dev/cli/q/src/build/token" -) - -// Scan scans the directory. +// Scan scans the list of files. func Scan(files []string) (<-chan *core.Function, <-chan error) { - functions := make(chan *core.Function) - errors := make(chan error) + scanner := Scanner{ + functions: make(chan *core.Function), + errors: make(chan error), + } go func() { - scanFiles(files, functions, errors) - close(functions) - close(errors) + scanner.queue(files...) + scanner.group.Wait() + close(scanner.functions) + close(scanner.errors) }() - return functions, errors -} - -// scanFiles scans the list of files without channel allocations. -func scanFiles(files []string, functions chan<- *core.Function, errors chan<- error) { - wg := sync.WaitGroup{} - - for _, file := range files { - stat, err := os.Stat(file) - - if err != nil { - errors <- err - return - } - - if stat.IsDir() { - err = fs.Walk(file, func(name string) { - if !strings.HasSuffix(name, ".q") { - return - } - - fullPath := filepath.Join(file, name) - wg.Add(1) - - go func() { - defer wg.Done() - err := scanFile(fullPath, functions) - - if err != nil { - errors <- err - } - }() - }) - - if err != nil { - errors <- err - } - } else { - wg.Add(1) - - go func() { - defer wg.Done() - err := scanFile(file, functions) - - if err != nil { - errors <- err - } - }() - } - } - - wg.Wait() -} - -// scanFile scans a single file. -func scanFile(path string, functions chan<- *core.Function) error { - contents, err := os.ReadFile(path) - - if err != nil { - return err - } - - tokens := token.Tokenize(contents) - - file := &fs.File{ - Tokens: tokens, - Path: path, - } - - var ( - i = 0 - groupLevel = 0 - blockLevel = 0 - nameStart = -1 - paramsStart = -1 - paramsEnd = -1 - bodyStart = -1 - ) - - for { - // Function name - for i < len(tokens) { - if tokens[i].Kind == token.Identifier { - nameStart = i - i++ - break - } - - if tokens[i].Kind == token.NewLine || tokens[i].Kind == token.Comment { - i++ - continue - } - - if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) - } - - if tokens[i].Kind == token.EOF { - return nil - } - - return errors.New(errors.ExpectedFunctionName, file, tokens[i].Position) - } - - // Function parameters - for i < len(tokens) { - if tokens[i].Kind == token.GroupStart { - groupLevel++ - i++ - - if groupLevel == 1 { - paramsStart = i - } - - continue - } - - if tokens[i].Kind == token.GroupEnd { - groupLevel-- - - if groupLevel < 0 { - return errors.New(errors.MissingGroupStart, file, tokens[i].Position) - } - - if groupLevel == 0 { - paramsEnd = i - i++ - break - } - - i++ - continue - } - - if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) - } - - if tokens[i].Kind == token.EOF { - if groupLevel > 0 { - return errors.New(errors.MissingGroupEnd, file, tokens[i].Position) - } - - if paramsStart == -1 { - return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) - } - - return nil - } - - if groupLevel > 0 { - i++ - continue - } - - return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) - } - - // Function definition - for i < len(tokens) { - if tokens[i].Kind == token.BlockStart { - blockLevel++ - i++ - - if blockLevel == 1 { - bodyStart = i - } - - continue - } - - if tokens[i].Kind == token.BlockEnd { - blockLevel-- - - if blockLevel < 0 { - return errors.New(errors.MissingBlockStart, file, tokens[i].Position) - } - - if blockLevel == 0 { - break - } - - i++ - continue - } - - if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) - } - - if tokens[i].Kind == token.EOF { - if blockLevel > 0 { - return errors.New(errors.MissingBlockEnd, file, tokens[i].Position) - } - - if bodyStart == -1 { - return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) - } - - return nil - } - - if blockLevel > 0 { - i++ - continue - } - - return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) - } - - name := tokens[nameStart].Text() - body := tokens[bodyStart:i] - function := core.NewFunction(name, file, body) - parameters := tokens[paramsStart:paramsEnd] - count := 0 - - err := expression.EachParameter(parameters, func(tokens token.List) error { - if len(tokens) != 1 { - return errors.New(errors.NotImplemented, file, tokens[0].Position) - } - - name := tokens[0].Text() - register := x64.CallRegisters[count] - uses := token.Count(function.Body, token.Identifier, name) - - if uses == 0 { - return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) - } - - variable := &core.Variable{ - Name: name, - Register: register, - Alive: uses, - } - - function.AddVariable(variable) - count++ - return nil - }) - - if err != nil { - return err - } - - functions <- function - nameStart = -1 - paramsStart = -1 - bodyStart = -1 - i++ - } + return scanner.functions, scanner.errors } diff --git a/src/build/scanner/Scanner.go b/src/build/scanner/Scanner.go new file mode 100644 index 0000000..9725e3b --- /dev/null +++ b/src/build/scanner/Scanner.go @@ -0,0 +1,14 @@ +package scanner + +import ( + "sync" + + "git.akyoto.dev/cli/q/src/build/core" +) + +// Scanner is used to scan files before the actual compilation step. +type Scanner struct { + functions chan *core.Function + errors chan error + group sync.WaitGroup +} diff --git a/src/build/scanner/queue.go b/src/build/scanner/queue.go new file mode 100644 index 0000000..6ccfc15 --- /dev/null +++ b/src/build/scanner/queue.go @@ -0,0 +1,21 @@ +package scanner + +import "os" + +// queue scans the list of files. +func (s *Scanner) queue(files ...string) { + for _, file := range files { + stat, err := os.Stat(file) + + if err != nil { + s.errors <- err + return + } + + if stat.IsDir() { + s.queueDirectory(file, "") + } else { + s.queueFile(file, "") + } + } +} diff --git a/src/build/scanner/queueDirectory.go b/src/build/scanner/queueDirectory.go new file mode 100644 index 0000000..21b32e8 --- /dev/null +++ b/src/build/scanner/queueDirectory.go @@ -0,0 +1,24 @@ +package scanner + +import ( + "path/filepath" + "strings" + + "git.akyoto.dev/cli/q/src/build/fs" +) + +// queueDirectory queues an entire directory to be scanned. +func (s *Scanner) queueDirectory(directory string, pkg string) { + err := fs.Walk(directory, func(name string) { + if !strings.HasSuffix(name, ".q") { + return + } + + fullPath := filepath.Join(directory, name) + s.queueFile(fullPath, pkg) + }) + + if err != nil { + s.errors <- err + } +} diff --git a/src/build/scanner/queueFile.go b/src/build/scanner/queueFile.go new file mode 100644 index 0000000..6981f7a --- /dev/null +++ b/src/build/scanner/queueFile.go @@ -0,0 +1,15 @@ +package scanner + +// queueFile queues a single file to be scanned. +func (s *Scanner) queueFile(file string, pkg string) { + s.group.Add(1) + + go func() { + defer s.group.Done() + err := s.scanFile(file, pkg) + + if err != nil { + s.errors <- err + } + }() +} diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go new file mode 100644 index 0000000..7407bd7 --- /dev/null +++ b/src/build/scanner/scanFile.go @@ -0,0 +1,238 @@ +package scanner + +import ( + "fmt" + "os" + "path/filepath" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/core" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/build/keyword" + "git.akyoto.dev/cli/q/src/build/token" +) + +// scanFile scans a single file. +func (s *Scanner) scanFile(path string, pkg string) error { + contents, err := os.ReadFile(path) + + if err != nil { + return err + } + + tokens := token.Tokenize(contents) + + file := &fs.File{ + Tokens: tokens, + Path: path, + } + + var ( + i = 0 + groupLevel = 0 + blockLevel = 0 + nameStart = -1 + paramsStart = -1 + paramsEnd = -1 + bodyStart = -1 + ) + + for { + for i < len(tokens) && tokens[i].Kind == token.Keyword && tokens[i].Text() == keyword.Import { + i++ + + if tokens[i].Kind != token.Identifier { + panic("expected package name") + } + + packageName := tokens[i].Text() + s.queueDirectory(filepath.Join(config.Library, packageName), packageName) + + i++ + + if tokens[i].Kind != token.NewLine && tokens[i].Kind != token.EOF { + panic("expected newline or eof") + } + + i++ + } + + // Function name + for i < len(tokens) { + if tokens[i].Kind == token.Identifier { + nameStart = i + i++ + break + } + + if tokens[i].Kind == token.NewLine || tokens[i].Kind == token.Comment { + i++ + continue + } + + if tokens[i].Kind == token.Invalid { + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + } + + if tokens[i].Kind == token.EOF { + return nil + } + + return errors.New(errors.ExpectedFunctionName, file, tokens[i].Position) + } + + // Function parameters + for i < len(tokens) { + if tokens[i].Kind == token.GroupStart { + groupLevel++ + i++ + + if groupLevel == 1 { + paramsStart = i + } + + continue + } + + if tokens[i].Kind == token.GroupEnd { + groupLevel-- + + if groupLevel < 0 { + return errors.New(errors.MissingGroupStart, file, tokens[i].Position) + } + + if groupLevel == 0 { + paramsEnd = i + i++ + break + } + + i++ + continue + } + + if tokens[i].Kind == token.Invalid { + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + } + + if tokens[i].Kind == token.EOF { + if groupLevel > 0 { + return errors.New(errors.MissingGroupEnd, file, tokens[i].Position) + } + + if paramsStart == -1 { + return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) + } + + return nil + } + + if groupLevel > 0 { + i++ + continue + } + + return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) + } + + // Function definition + for i < len(tokens) { + if tokens[i].Kind == token.BlockStart { + blockLevel++ + i++ + + if blockLevel == 1 { + bodyStart = i + } + + continue + } + + if tokens[i].Kind == token.BlockEnd { + blockLevel-- + + if blockLevel < 0 { + return errors.New(errors.MissingBlockStart, file, tokens[i].Position) + } + + if blockLevel == 0 { + break + } + + i++ + continue + } + + if tokens[i].Kind == token.Invalid { + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + } + + if tokens[i].Kind == token.EOF { + if blockLevel > 0 { + return errors.New(errors.MissingBlockEnd, file, tokens[i].Position) + } + + if bodyStart == -1 { + return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) + } + + return nil + } + + if blockLevel > 0 { + i++ + continue + } + + return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) + } + + name := tokens[nameStart].Text() + body := tokens[bodyStart:i] + + if pkg != "" { + name = fmt.Sprintf("%s.%s", pkg, name) + } + + function := core.NewFunction(name, file, body) + parameters := tokens[paramsStart:paramsEnd] + count := 0 + + err := expression.EachParameter(parameters, func(tokens token.List) error { + if len(tokens) != 1 { + return errors.New(errors.NotImplemented, file, tokens[0].Position) + } + + name := tokens[0].Text() + register := x64.CallRegisters[count] + uses := token.Count(function.Body, token.Identifier, name) + + if uses == 0 { + return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) + } + + variable := &core.Variable{ + Name: name, + Register: register, + Alive: uses, + } + + function.AddVariable(variable) + count++ + return nil + }) + + if err != nil { + return err + } + + s.functions <- function + nameStart = -1 + paramsStart = -1 + bodyStart = -1 + i++ + } +} diff --git a/tests/programs/branch-and.q b/tests/programs/branch-and.q index 6761f1e..9995b34 100644 --- a/tests/programs/branch-and.q +++ b/tests/programs/branch-and.q @@ -1,37 +1,35 @@ +import sys + main() { x := 0 if x != x && x != x { - exit(1) + sys.exit(1) } if x == x && x != x { - exit(1) + sys.exit(1) } if x != x && x == x { - exit(1) + sys.exit(1) } if x == x && x != x && x != x { - exit(1) + sys.exit(1) } if x != x && x == x && x != x { - exit(1) + sys.exit(1) } if x != x && x != x && x == x { - exit(1) + sys.exit(1) } if x == x && x == x && x == x { - exit(0) + sys.exit(0) } - exit(1) -} - -exit(x) { - syscall(60, x) + sys.exit(1) } \ No newline at end of file diff --git a/tests/programs/branch-both.q b/tests/programs/branch-both.q index b8e7029..ef35461 100644 --- a/tests/programs/branch-both.q +++ b/tests/programs/branch-both.q @@ -1,47 +1,45 @@ +import sys + main() { x := 0 if x == x && x != x || x != x && x != x { - exit(1) + sys.exit(1) } if x != x && x == x || x != x && x != x { - exit(1) + sys.exit(1) } if x != x && x != x || x == x && x != x { - exit(1) + sys.exit(1) } if x != x && x != x || x != x && x == x { - exit(1) + sys.exit(1) } if (x == x || x != x) && (x != x || x != x) { - exit(1) + sys.exit(1) } if (x != x || x == x) && (x != x || x != x) { - exit(1) + sys.exit(1) } if (x != x || x != x) && (x == x || x != x) { - exit(1) + sys.exit(1) } if (x != x || x != x) && (x != x || x == x) { - exit(1) + sys.exit(1) } if (x == x && x != x || x == x && x == x) && (x == x && x == x || x == x && x != x) { if (x != x || x == x) && (x != x || x != x) || (x == x || x != x) && (x != x || x == x) { - exit(0) + sys.exit(0) } } - exit(1) -} - -exit(x) { - syscall(60, x) + sys.exit(1) } \ No newline at end of file diff --git a/tests/programs/branch-or.q b/tests/programs/branch-or.q index da340a8..814f5b0 100644 --- a/tests/programs/branch-or.q +++ b/tests/programs/branch-or.q @@ -1,12 +1,14 @@ +import sys + main() { x := 0 if x != x || x != x { - exit(1) + sys.exit(1) } if x != x || x != x || x != x { - exit(1) + sys.exit(1) } if x == x || x != x { @@ -14,16 +16,12 @@ main() { if x == x || x != x || x != x { if x != x || x == x || x != x { if x != x || x != x || x == x { - exit(0) + sys.exit(0) } } } } } - exit(1) -} - -exit(x) { - syscall(60, x) + sys.exit(1) } \ No newline at end of file diff --git a/tests/programs/branch.q b/tests/programs/branch.q index 8887013..aaa393f 100644 --- a/tests/programs/branch.q +++ b/tests/programs/branch.q @@ -1,75 +1,73 @@ +import sys + main() { x := 0 if x != 0 { - exit(1) + sys.exit(1) } if x > 0 { - exit(1) + sys.exit(1) } if x < 0 { - exit(1) + sys.exit(1) } if 0 != x { - exit(1) + sys.exit(1) } if 0 > x { - exit(1) + sys.exit(1) } if 0 < x { - exit(1) + sys.exit(1) } if x >= 1 { - exit(1) + sys.exit(1) } if 1 <= x { - exit(1) + sys.exit(1) } if x + 1 != x + 1 { - exit(1) + sys.exit(1) } if x + 1 != inc(x) { - exit(1) + sys.exit(1) } if x - 1 != dec(x) { - exit(1) + sys.exit(1) } if inc(x) != x + 1 { - exit(1) + sys.exit(1) } if dec(x) != x - 1 { - exit(1) + sys.exit(1) } if x != inc(dec(x)) { - exit(1) + sys.exit(1) } if inc(dec(x)) != x { - exit(1) + sys.exit(1) } if x == 0 { - exit(0) + sys.exit(0) } - exit(1) -} - -exit(x) { - syscall(60, x) + sys.exit(1) } inc(x) { diff --git a/tests/programs/chained-calls.q b/tests/programs/chained-calls.q index 95cb219..49ddb84 100644 --- a/tests/programs/chained-calls.q +++ b/tests/programs/chained-calls.q @@ -1,9 +1,7 @@ -main() { - exit(f(1) + f(2) + f(3)) -} +import sys -exit(code) { - syscall(60, code) +main() { + sys.exit(f(1) + f(2) + f(3)) } f(x) { diff --git a/tests/programs/jump-near.q b/tests/programs/jump-near.q index 7af145d..af48760 100644 --- a/tests/programs/jump-near.q +++ b/tests/programs/jump-near.q @@ -1,3 +1,5 @@ +import sys + main() { x := 10 @@ -175,13 +177,13 @@ main() { fail() } - exit(0) + success() } fail() { - exit(1) + sys.exit(1) } -exit(code) { - syscall(60, code) +success() { + sys.exit(0) } \ No newline at end of file diff --git a/tests/programs/nested-calls.q b/tests/programs/nested-calls.q index 8844729..f226bcb 100644 --- a/tests/programs/nested-calls.q +++ b/tests/programs/nested-calls.q @@ -1,9 +1,7 @@ -main() { - exit(f(f(f(1)))) -} +import sys -exit(code) { - syscall(60, code) +main() { + sys.exit(f(f(f(1)))) } f(x) { From b34470a97d5983abe24f8c63d4983a269da91ccc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 18 Jul 2024 18:08:30 +0200 Subject: [PATCH 0320/1012] Implemented package imports --- examples/factorial/factorial.q | 4 +- examples/fibonacci/fibonacci.q | 4 +- examples/hello/hello.q | 8 +- lib/sys/linux.q | 55 ++++++ src/build/config/gc.go | 7 - src/build/config/init.go | 53 ++++++ src/build/core/CompileCall.go | 10 +- src/build/keyword/Keyword.go | 2 + src/build/scanner/Scan.go | 280 ++-------------------------- src/build/scanner/Scanner.go | 14 ++ src/build/scanner/queue.go | 21 +++ src/build/scanner/queueDirectory.go | 24 +++ src/build/scanner/queueFile.go | 15 ++ src/build/scanner/scanFile.go | 238 +++++++++++++++++++++++ tests/programs/branch-and.q | 22 +-- tests/programs/branch-both.q | 26 ++- tests/programs/branch-or.q | 14 +- tests/programs/branch.q | 40 ++-- tests/programs/chained-calls.q | 8 +- tests/programs/jump-near.q | 10 +- tests/programs/nested-calls.q | 8 +- 21 files changed, 510 insertions(+), 353 deletions(-) create mode 100644 lib/sys/linux.q delete mode 100644 src/build/config/gc.go create mode 100644 src/build/config/init.go create mode 100644 src/build/scanner/Scanner.go create mode 100644 src/build/scanner/queue.go create mode 100644 src/build/scanner/queueDirectory.go create mode 100644 src/build/scanner/queueFile.go create mode 100644 src/build/scanner/scanFile.go diff --git a/examples/factorial/factorial.q b/examples/factorial/factorial.q index f198ec1..30315ec 100644 --- a/examples/factorial/factorial.q +++ b/examples/factorial/factorial.q @@ -1,5 +1,7 @@ +import sys + main() { - syscall(60, factorial(5)) + sys.exit(factorial(5)) } factorial(x) { diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q index e02a15c..505bf26 100644 --- a/examples/fibonacci/fibonacci.q +++ b/examples/fibonacci/fibonacci.q @@ -1,5 +1,7 @@ +import sys + main() { - syscall(60, fibonacci(10)) + sys.exit(fibonacci(10)) } fibonacci(x) { diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 85aa05c..6adfc54 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,11 +1,9 @@ +import sys + main() { print("Hello", 5) } print(address, length) { - write(1, address, length) -} - -write(fd, address, length) { - syscall(1, fd, address, length) + sys.write(1, address, length) } \ No newline at end of file diff --git a/lib/sys/linux.q b/lib/sys/linux.q new file mode 100644 index 0000000..9c44037 --- /dev/null +++ b/lib/sys/linux.q @@ -0,0 +1,55 @@ +read(fd, address, length) { + return syscall(0, fd, address, length) +} + +write(fd, address, length) { + syscall(1, fd, address, length) +} + +open(file, flags, mode) { + return syscall(2, file, flags, mode) +} + +close(fd) { + return syscall(3, fd) +} + +mmap(address, length, protection, flags) { + return syscall(9, address, length, protection, flags) +} + +munmap(address, length) { + return syscall(11, address, length) +} + +clone(flags, stack) { + return syscall(56, flags, stack) +} + +exit(code) { + syscall(60, code) +} + +getcwd(buffer, length) { + return syscall(79, buffer, length) +} + +chdir(path) { + return syscall(80, path) +} + +rename(old, new) { + return syscall(82, old, new) +} + +mkdir(path, mode) { + return syscall(83, path, mode) +} + +rmdir(path) { + return syscall(84, path) +} + +unlink(file) { + return syscall(87, file) +} diff --git a/src/build/config/gc.go b/src/build/config/gc.go deleted file mode 100644 index 522ae07..0000000 --- a/src/build/config/gc.go +++ /dev/null @@ -1,7 +0,0 @@ -package config - -import "runtime/debug" - -func init() { - debug.SetGCPercent(-1) -} diff --git a/src/build/config/init.go b/src/build/config/init.go new file mode 100644 index 0000000..31732e8 --- /dev/null +++ b/src/build/config/init.go @@ -0,0 +1,53 @@ +package config + +import ( + "os" + "path" + "path/filepath" + "runtime/debug" +) + +var ( + Executable string + Root string + Library string +) + +func init() { + debug.SetGCPercent(-1) + executable, err := os.Executable() + + if err != nil { + panic(err) + } + + Executable = executable + Root = filepath.Dir(executable) + Library = filepath.Join(Root, "lib") + stat, err := os.Stat(Library) + + if !os.IsNotExist(err) && stat != nil && stat.IsDir() { + return + } + + dir, err := os.Getwd() + + if err != nil { + panic(err) + } + + for { + Library = path.Join(dir, "lib") + stat, err := os.Stat(Library) + + if !os.IsNotExist(err) && stat != nil && stat.IsDir() { + return + } + + if dir == "/" { + panic("standard library not found") + } + + dir = filepath.Dir(dir) + } +} diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index ae04215..53250d9 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -11,7 +11,15 @@ import ( // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. func (f *Function) CompileCall(root *expression.Expression) error { - funcName := root.Children[0].Token.Text() + funcNameRoot := root.Children[0] + funcName := "" + + if funcNameRoot.IsLeaf() { + funcName = funcNameRoot.Token.Text() + } else { + funcName = funcNameRoot.Children[0].Token.Text() + funcNameRoot.Token.Text() + funcNameRoot.Children[1].Token.Text() + } + isSyscall := funcName == "syscall" if !isSyscall { diff --git a/src/build/keyword/Keyword.go b/src/build/keyword/Keyword.go index d744384..ef62575 100644 --- a/src/build/keyword/Keyword.go +++ b/src/build/keyword/Keyword.go @@ -2,6 +2,7 @@ package keyword const ( If = "if" + Import = "import" Loop = "loop" Return = "return" ) @@ -9,6 +10,7 @@ const ( // Map is a map of all keywords used in the language. var Map = map[string][]byte{ If: []byte(If), + Import: []byte(Import), Loop: []byte(Loop), Return: []byte(Return), } diff --git a/src/build/scanner/Scan.go b/src/build/scanner/Scan.go index eb3e760..b79d04f 100644 --- a/src/build/scanner/Scan.go +++ b/src/build/scanner/Scan.go @@ -1,278 +1,20 @@ package scanner -import ( - "os" - "path/filepath" - "strings" - "sync" +import "git.akyoto.dev/cli/q/src/build/core" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/core" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/fs" - "git.akyoto.dev/cli/q/src/build/token" -) - -// Scan scans the directory. +// Scan scans the list of files. func Scan(files []string) (<-chan *core.Function, <-chan error) { - functions := make(chan *core.Function) - errors := make(chan error) + scanner := Scanner{ + functions: make(chan *core.Function), + errors: make(chan error), + } go func() { - scanFiles(files, functions, errors) - close(functions) - close(errors) + scanner.queue(files...) + scanner.group.Wait() + close(scanner.functions) + close(scanner.errors) }() - return functions, errors -} - -// scanFiles scans the list of files without channel allocations. -func scanFiles(files []string, functions chan<- *core.Function, errors chan<- error) { - wg := sync.WaitGroup{} - - for _, file := range files { - stat, err := os.Stat(file) - - if err != nil { - errors <- err - return - } - - if stat.IsDir() { - err = fs.Walk(file, func(name string) { - if !strings.HasSuffix(name, ".q") { - return - } - - fullPath := filepath.Join(file, name) - wg.Add(1) - - go func() { - defer wg.Done() - err := scanFile(fullPath, functions) - - if err != nil { - errors <- err - } - }() - }) - - if err != nil { - errors <- err - } - } else { - wg.Add(1) - - go func() { - defer wg.Done() - err := scanFile(file, functions) - - if err != nil { - errors <- err - } - }() - } - } - - wg.Wait() -} - -// scanFile scans a single file. -func scanFile(path string, functions chan<- *core.Function) error { - contents, err := os.ReadFile(path) - - if err != nil { - return err - } - - tokens := token.Tokenize(contents) - - file := &fs.File{ - Tokens: tokens, - Path: path, - } - - var ( - i = 0 - groupLevel = 0 - blockLevel = 0 - nameStart = -1 - paramsStart = -1 - paramsEnd = -1 - bodyStart = -1 - ) - - for { - // Function name - for i < len(tokens) { - if tokens[i].Kind == token.Identifier { - nameStart = i - i++ - break - } - - if tokens[i].Kind == token.NewLine || tokens[i].Kind == token.Comment { - i++ - continue - } - - if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) - } - - if tokens[i].Kind == token.EOF { - return nil - } - - return errors.New(errors.ExpectedFunctionName, file, tokens[i].Position) - } - - // Function parameters - for i < len(tokens) { - if tokens[i].Kind == token.GroupStart { - groupLevel++ - i++ - - if groupLevel == 1 { - paramsStart = i - } - - continue - } - - if tokens[i].Kind == token.GroupEnd { - groupLevel-- - - if groupLevel < 0 { - return errors.New(errors.MissingGroupStart, file, tokens[i].Position) - } - - if groupLevel == 0 { - paramsEnd = i - i++ - break - } - - i++ - continue - } - - if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) - } - - if tokens[i].Kind == token.EOF { - if groupLevel > 0 { - return errors.New(errors.MissingGroupEnd, file, tokens[i].Position) - } - - if paramsStart == -1 { - return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) - } - - return nil - } - - if groupLevel > 0 { - i++ - continue - } - - return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) - } - - // Function definition - for i < len(tokens) { - if tokens[i].Kind == token.BlockStart { - blockLevel++ - i++ - - if blockLevel == 1 { - bodyStart = i - } - - continue - } - - if tokens[i].Kind == token.BlockEnd { - blockLevel-- - - if blockLevel < 0 { - return errors.New(errors.MissingBlockStart, file, tokens[i].Position) - } - - if blockLevel == 0 { - break - } - - i++ - continue - } - - if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) - } - - if tokens[i].Kind == token.EOF { - if blockLevel > 0 { - return errors.New(errors.MissingBlockEnd, file, tokens[i].Position) - } - - if bodyStart == -1 { - return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) - } - - return nil - } - - if blockLevel > 0 { - i++ - continue - } - - return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) - } - - name := tokens[nameStart].Text() - body := tokens[bodyStart:i] - function := core.NewFunction(name, file, body) - parameters := tokens[paramsStart:paramsEnd] - count := 0 - - err := expression.EachParameter(parameters, func(tokens token.List) error { - if len(tokens) != 1 { - return errors.New(errors.NotImplemented, file, tokens[0].Position) - } - - name := tokens[0].Text() - register := x64.CallRegisters[count] - uses := token.Count(function.Body, token.Identifier, name) - - if uses == 0 { - return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) - } - - variable := &core.Variable{ - Name: name, - Register: register, - Alive: uses, - } - - function.AddVariable(variable) - count++ - return nil - }) - - if err != nil { - return err - } - - functions <- function - nameStart = -1 - paramsStart = -1 - bodyStart = -1 - i++ - } + return scanner.functions, scanner.errors } diff --git a/src/build/scanner/Scanner.go b/src/build/scanner/Scanner.go new file mode 100644 index 0000000..9725e3b --- /dev/null +++ b/src/build/scanner/Scanner.go @@ -0,0 +1,14 @@ +package scanner + +import ( + "sync" + + "git.akyoto.dev/cli/q/src/build/core" +) + +// Scanner is used to scan files before the actual compilation step. +type Scanner struct { + functions chan *core.Function + errors chan error + group sync.WaitGroup +} diff --git a/src/build/scanner/queue.go b/src/build/scanner/queue.go new file mode 100644 index 0000000..6ccfc15 --- /dev/null +++ b/src/build/scanner/queue.go @@ -0,0 +1,21 @@ +package scanner + +import "os" + +// queue scans the list of files. +func (s *Scanner) queue(files ...string) { + for _, file := range files { + stat, err := os.Stat(file) + + if err != nil { + s.errors <- err + return + } + + if stat.IsDir() { + s.queueDirectory(file, "") + } else { + s.queueFile(file, "") + } + } +} diff --git a/src/build/scanner/queueDirectory.go b/src/build/scanner/queueDirectory.go new file mode 100644 index 0000000..21b32e8 --- /dev/null +++ b/src/build/scanner/queueDirectory.go @@ -0,0 +1,24 @@ +package scanner + +import ( + "path/filepath" + "strings" + + "git.akyoto.dev/cli/q/src/build/fs" +) + +// queueDirectory queues an entire directory to be scanned. +func (s *Scanner) queueDirectory(directory string, pkg string) { + err := fs.Walk(directory, func(name string) { + if !strings.HasSuffix(name, ".q") { + return + } + + fullPath := filepath.Join(directory, name) + s.queueFile(fullPath, pkg) + }) + + if err != nil { + s.errors <- err + } +} diff --git a/src/build/scanner/queueFile.go b/src/build/scanner/queueFile.go new file mode 100644 index 0000000..6981f7a --- /dev/null +++ b/src/build/scanner/queueFile.go @@ -0,0 +1,15 @@ +package scanner + +// queueFile queues a single file to be scanned. +func (s *Scanner) queueFile(file string, pkg string) { + s.group.Add(1) + + go func() { + defer s.group.Done() + err := s.scanFile(file, pkg) + + if err != nil { + s.errors <- err + } + }() +} diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go new file mode 100644 index 0000000..7407bd7 --- /dev/null +++ b/src/build/scanner/scanFile.go @@ -0,0 +1,238 @@ +package scanner + +import ( + "fmt" + "os" + "path/filepath" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/core" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/build/keyword" + "git.akyoto.dev/cli/q/src/build/token" +) + +// scanFile scans a single file. +func (s *Scanner) scanFile(path string, pkg string) error { + contents, err := os.ReadFile(path) + + if err != nil { + return err + } + + tokens := token.Tokenize(contents) + + file := &fs.File{ + Tokens: tokens, + Path: path, + } + + var ( + i = 0 + groupLevel = 0 + blockLevel = 0 + nameStart = -1 + paramsStart = -1 + paramsEnd = -1 + bodyStart = -1 + ) + + for { + for i < len(tokens) && tokens[i].Kind == token.Keyword && tokens[i].Text() == keyword.Import { + i++ + + if tokens[i].Kind != token.Identifier { + panic("expected package name") + } + + packageName := tokens[i].Text() + s.queueDirectory(filepath.Join(config.Library, packageName), packageName) + + i++ + + if tokens[i].Kind != token.NewLine && tokens[i].Kind != token.EOF { + panic("expected newline or eof") + } + + i++ + } + + // Function name + for i < len(tokens) { + if tokens[i].Kind == token.Identifier { + nameStart = i + i++ + break + } + + if tokens[i].Kind == token.NewLine || tokens[i].Kind == token.Comment { + i++ + continue + } + + if tokens[i].Kind == token.Invalid { + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + } + + if tokens[i].Kind == token.EOF { + return nil + } + + return errors.New(errors.ExpectedFunctionName, file, tokens[i].Position) + } + + // Function parameters + for i < len(tokens) { + if tokens[i].Kind == token.GroupStart { + groupLevel++ + i++ + + if groupLevel == 1 { + paramsStart = i + } + + continue + } + + if tokens[i].Kind == token.GroupEnd { + groupLevel-- + + if groupLevel < 0 { + return errors.New(errors.MissingGroupStart, file, tokens[i].Position) + } + + if groupLevel == 0 { + paramsEnd = i + i++ + break + } + + i++ + continue + } + + if tokens[i].Kind == token.Invalid { + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + } + + if tokens[i].Kind == token.EOF { + if groupLevel > 0 { + return errors.New(errors.MissingGroupEnd, file, tokens[i].Position) + } + + if paramsStart == -1 { + return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) + } + + return nil + } + + if groupLevel > 0 { + i++ + continue + } + + return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) + } + + // Function definition + for i < len(tokens) { + if tokens[i].Kind == token.BlockStart { + blockLevel++ + i++ + + if blockLevel == 1 { + bodyStart = i + } + + continue + } + + if tokens[i].Kind == token.BlockEnd { + blockLevel-- + + if blockLevel < 0 { + return errors.New(errors.MissingBlockStart, file, tokens[i].Position) + } + + if blockLevel == 0 { + break + } + + i++ + continue + } + + if tokens[i].Kind == token.Invalid { + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + } + + if tokens[i].Kind == token.EOF { + if blockLevel > 0 { + return errors.New(errors.MissingBlockEnd, file, tokens[i].Position) + } + + if bodyStart == -1 { + return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) + } + + return nil + } + + if blockLevel > 0 { + i++ + continue + } + + return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) + } + + name := tokens[nameStart].Text() + body := tokens[bodyStart:i] + + if pkg != "" { + name = fmt.Sprintf("%s.%s", pkg, name) + } + + function := core.NewFunction(name, file, body) + parameters := tokens[paramsStart:paramsEnd] + count := 0 + + err := expression.EachParameter(parameters, func(tokens token.List) error { + if len(tokens) != 1 { + return errors.New(errors.NotImplemented, file, tokens[0].Position) + } + + name := tokens[0].Text() + register := x64.CallRegisters[count] + uses := token.Count(function.Body, token.Identifier, name) + + if uses == 0 { + return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) + } + + variable := &core.Variable{ + Name: name, + Register: register, + Alive: uses, + } + + function.AddVariable(variable) + count++ + return nil + }) + + if err != nil { + return err + } + + s.functions <- function + nameStart = -1 + paramsStart = -1 + bodyStart = -1 + i++ + } +} diff --git a/tests/programs/branch-and.q b/tests/programs/branch-and.q index 6761f1e..9995b34 100644 --- a/tests/programs/branch-and.q +++ b/tests/programs/branch-and.q @@ -1,37 +1,35 @@ +import sys + main() { x := 0 if x != x && x != x { - exit(1) + sys.exit(1) } if x == x && x != x { - exit(1) + sys.exit(1) } if x != x && x == x { - exit(1) + sys.exit(1) } if x == x && x != x && x != x { - exit(1) + sys.exit(1) } if x != x && x == x && x != x { - exit(1) + sys.exit(1) } if x != x && x != x && x == x { - exit(1) + sys.exit(1) } if x == x && x == x && x == x { - exit(0) + sys.exit(0) } - exit(1) -} - -exit(x) { - syscall(60, x) + sys.exit(1) } \ No newline at end of file diff --git a/tests/programs/branch-both.q b/tests/programs/branch-both.q index b8e7029..ef35461 100644 --- a/tests/programs/branch-both.q +++ b/tests/programs/branch-both.q @@ -1,47 +1,45 @@ +import sys + main() { x := 0 if x == x && x != x || x != x && x != x { - exit(1) + sys.exit(1) } if x != x && x == x || x != x && x != x { - exit(1) + sys.exit(1) } if x != x && x != x || x == x && x != x { - exit(1) + sys.exit(1) } if x != x && x != x || x != x && x == x { - exit(1) + sys.exit(1) } if (x == x || x != x) && (x != x || x != x) { - exit(1) + sys.exit(1) } if (x != x || x == x) && (x != x || x != x) { - exit(1) + sys.exit(1) } if (x != x || x != x) && (x == x || x != x) { - exit(1) + sys.exit(1) } if (x != x || x != x) && (x != x || x == x) { - exit(1) + sys.exit(1) } if (x == x && x != x || x == x && x == x) && (x == x && x == x || x == x && x != x) { if (x != x || x == x) && (x != x || x != x) || (x == x || x != x) && (x != x || x == x) { - exit(0) + sys.exit(0) } } - exit(1) -} - -exit(x) { - syscall(60, x) + sys.exit(1) } \ No newline at end of file diff --git a/tests/programs/branch-or.q b/tests/programs/branch-or.q index da340a8..814f5b0 100644 --- a/tests/programs/branch-or.q +++ b/tests/programs/branch-or.q @@ -1,12 +1,14 @@ +import sys + main() { x := 0 if x != x || x != x { - exit(1) + sys.exit(1) } if x != x || x != x || x != x { - exit(1) + sys.exit(1) } if x == x || x != x { @@ -14,16 +16,12 @@ main() { if x == x || x != x || x != x { if x != x || x == x || x != x { if x != x || x != x || x == x { - exit(0) + sys.exit(0) } } } } } - exit(1) -} - -exit(x) { - syscall(60, x) + sys.exit(1) } \ No newline at end of file diff --git a/tests/programs/branch.q b/tests/programs/branch.q index 8887013..aaa393f 100644 --- a/tests/programs/branch.q +++ b/tests/programs/branch.q @@ -1,75 +1,73 @@ +import sys + main() { x := 0 if x != 0 { - exit(1) + sys.exit(1) } if x > 0 { - exit(1) + sys.exit(1) } if x < 0 { - exit(1) + sys.exit(1) } if 0 != x { - exit(1) + sys.exit(1) } if 0 > x { - exit(1) + sys.exit(1) } if 0 < x { - exit(1) + sys.exit(1) } if x >= 1 { - exit(1) + sys.exit(1) } if 1 <= x { - exit(1) + sys.exit(1) } if x + 1 != x + 1 { - exit(1) + sys.exit(1) } if x + 1 != inc(x) { - exit(1) + sys.exit(1) } if x - 1 != dec(x) { - exit(1) + sys.exit(1) } if inc(x) != x + 1 { - exit(1) + sys.exit(1) } if dec(x) != x - 1 { - exit(1) + sys.exit(1) } if x != inc(dec(x)) { - exit(1) + sys.exit(1) } if inc(dec(x)) != x { - exit(1) + sys.exit(1) } if x == 0 { - exit(0) + sys.exit(0) } - exit(1) -} - -exit(x) { - syscall(60, x) + sys.exit(1) } inc(x) { diff --git a/tests/programs/chained-calls.q b/tests/programs/chained-calls.q index 95cb219..49ddb84 100644 --- a/tests/programs/chained-calls.q +++ b/tests/programs/chained-calls.q @@ -1,9 +1,7 @@ -main() { - exit(f(1) + f(2) + f(3)) -} +import sys -exit(code) { - syscall(60, code) +main() { + sys.exit(f(1) + f(2) + f(3)) } f(x) { diff --git a/tests/programs/jump-near.q b/tests/programs/jump-near.q index 7af145d..af48760 100644 --- a/tests/programs/jump-near.q +++ b/tests/programs/jump-near.q @@ -1,3 +1,5 @@ +import sys + main() { x := 10 @@ -175,13 +177,13 @@ main() { fail() } - exit(0) + success() } fail() { - exit(1) + sys.exit(1) } -exit(code) { - syscall(60, code) +success() { + sys.exit(0) } \ No newline at end of file diff --git a/tests/programs/nested-calls.q b/tests/programs/nested-calls.q index 8844729..f226bcb 100644 --- a/tests/programs/nested-calls.q +++ b/tests/programs/nested-calls.q @@ -1,9 +1,7 @@ -main() { - exit(f(f(f(1)))) -} +import sys -exit(code) { - syscall(60, code) +main() { + sys.exit(f(f(f(1)))) } f(x) { From 86175d7a537114de98cc3613025c7b70e3ed9a37 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 18 Jul 2024 21:10:27 +0200 Subject: [PATCH 0321/1012] Implemented echo example --- examples/echo/echo.q | 18 +++++++ lib/mem/alloc.q | 9 ++++ lib/sys/linux.q | 2 +- src/build/core/CompileDefinition.go | 23 +-------- ...nknownIdentifier4.q => UnknownFunction2.q} | 4 -- tests/errors/UnknownIdentifier3.q | 4 ++ tests/errors_test.go | 4 +- tests/examples_test.go | 15 +++--- tests/programs_test.go | 49 ++++++++++--------- 9 files changed, 70 insertions(+), 58 deletions(-) create mode 100644 examples/echo/echo.q create mode 100644 lib/mem/alloc.q rename tests/errors/{UnknownIdentifier4.q => UnknownFunction2.q} (55%) diff --git a/examples/echo/echo.q b/examples/echo/echo.q new file mode 100644 index 0000000..fb1159e --- /dev/null +++ b/examples/echo/echo.q @@ -0,0 +1,18 @@ +import mem +import sys + +main() { + length := 4096 + address := mem.alloc(length) + + loop { + n := sys.read(0, address, length) + + if n <= 0 { + mem.free(address, length) + return + } + + sys.write(1, address, n) + } +} \ No newline at end of file diff --git a/lib/mem/alloc.q b/lib/mem/alloc.q new file mode 100644 index 0000000..e03ece3 --- /dev/null +++ b/lib/mem/alloc.q @@ -0,0 +1,9 @@ +import sys + +alloc(length) { + return sys.mmap(0, length, 3, 290) +} + +free(address, length) { + return sys.munmap(address, length) +} \ No newline at end of file diff --git a/lib/sys/linux.q b/lib/sys/linux.q index 9c44037..68a8fbf 100644 --- a/lib/sys/linux.q +++ b/lib/sys/linux.q @@ -3,7 +3,7 @@ read(fd, address, length) { } write(fd, address, length) { - syscall(1, fd, address, length) + return syscall(1, fd, address, length) } open(file, flags, mode) { diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index bbc544b..bf215a1 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -22,21 +22,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) } - value := node.Value - - err := value.EachLeaf(func(leaf *expression.Expression) error { - if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) { - return errors.New(&errors.UnknownIdentifier{Name: leaf.Token.Text()}, f.File, leaf.Token.Position) - } - - return nil - }) - - if err != nil { - return err - } - - return f.storeVariableInRegister(name, value, uses) + return f.storeVariableInRegister(name, node.Value, uses) } func (f *Function) AddVariable(variable *Variable) { @@ -94,12 +80,7 @@ func (f *Function) identifierExists(name string) bool { } func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error { - reg, exists := f.Scope().FindFree(f.cpu.General) - - if !exists { - panic("no free registers") - } - + reg := f.Scope().MustFindFree(f.cpu.General) f.Scope().Reserve(reg) err := f.ExpressionToRegister(value, reg) diff --git a/tests/errors/UnknownIdentifier4.q b/tests/errors/UnknownFunction2.q similarity index 55% rename from tests/errors/UnknownIdentifier4.q rename to tests/errors/UnknownFunction2.q index a7387e2..c31b354 100644 --- a/tests/errors/UnknownIdentifier4.q +++ b/tests/errors/UnknownFunction2.q @@ -1,7 +1,3 @@ main() { x := 1 + f(x) -} - -f(x) { - return x } \ No newline at end of file diff --git a/tests/errors/UnknownIdentifier3.q b/tests/errors/UnknownIdentifier3.q index c31b354..a7387e2 100644 --- a/tests/errors/UnknownIdentifier3.q +++ b/tests/errors/UnknownIdentifier3.q @@ -1,3 +1,7 @@ main() { x := 1 + f(x) +} + +f(x) { + return x } \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 7782a67..6c08acd 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -34,10 +34,10 @@ var errs = []struct { {"MissingOperand2.q", errors.MissingOperand}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, + {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, - {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "f"}}, - {"UnknownIdentifier4.q", &errors.UnknownIdentifier{Name: "x"}}, + {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, } diff --git a/tests/examples_test.go b/tests/examples_test.go index 8af877d..17839db 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -9,19 +9,20 @@ import ( ) var examples = []struct { - Name string - ExpectedOutput string - ExpectedExitCode int + Name string + Input string + Output string + ExitCode int }{ - {"hello", "Hello", 0}, - {"factorial", "", 120}, - {"fibonacci", "", 55}, + {"hello", "", "Hello", 0}, + {"factorial", "", "", 120}, + {"fibonacci", "", "", 55}, } func TestExamples(t *testing.T) { for _, test := range examples { t.Run(test.Name, func(t *testing.T) { - run(t, filepath.Join("..", "examples", test.Name), test.ExpectedOutput, test.ExpectedExitCode) + run(t, filepath.Join("..", "examples", test.Name), test.Input, test.Output, test.ExitCode) }) } } diff --git a/tests/programs_test.go b/tests/programs_test.go index aa3a6d5..5d4e5ff 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -4,6 +4,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "testing" "git.akyoto.dev/cli/q/src/build" @@ -11,34 +12,35 @@ import ( ) var programs = []struct { - Name string - ExpectedOutput string - ExpectedExitCode int + Name string + Input string + Output string + ExitCode int }{ - {"empty", "", 0}, - {"math", "", 10}, - {"precedence", "", 10}, - {"square-sum", "", 25}, - {"chained-calls", "", 9}, - {"nested-calls", "", 4}, - {"param", "", 3}, - {"param-multi", "", 21}, - {"reuse", "", 3}, - {"return", "", 6}, - {"reassign", "", 2}, - {"branch", "", 0}, - {"branch-and", "", 0}, - {"branch-or", "", 0}, - {"branch-both", "", 0}, - {"jump-near", "", 0}, - {"loop", "", 0}, - {"loop-lifetime", "", 0}, + {"empty", "", "", 0}, + {"math", "", "", 10}, + {"precedence", "", "", 10}, + {"square-sum", "", "", 25}, + {"chained-calls", "", "", 9}, + {"nested-calls", "", "", 4}, + {"param", "", "", 3}, + {"param-multi", "", "", 21}, + {"reuse", "", "", 3}, + {"return", "", "", 6}, + {"reassign", "", "", 2}, + {"branch", "", "", 0}, + {"branch-and", "", "", 0}, + {"branch-or", "", "", 0}, + {"branch-both", "", "", 0}, + {"jump-near", "", "", 0}, + {"loop", "", "", 0}, + {"loop-lifetime", "", "", 0}, } func TestPrograms(t *testing.T) { for _, test := range programs { t.Run(test.Name, func(t *testing.T) { - run(t, filepath.Join("programs", test.Name+".q"), test.ExpectedOutput, test.ExpectedExitCode) + run(t, filepath.Join("programs", test.Name+".q"), test.Input, test.Output, test.ExitCode) }) } } @@ -57,7 +59,7 @@ func BenchmarkPrograms(b *testing.B) { } // run builds and runs the file to check if the output matches the expected output. -func run(t *testing.T, name string, expectedOutput string, expectedExitCode int) { +func run(t *testing.T, name string, input string, expectedOutput string, expectedExitCode int) { b := build.New(name) assert.True(t, len(b.Executable()) > 0) @@ -72,6 +74,7 @@ func run(t *testing.T, name string, expectedOutput string, expectedExitCode int) assert.True(t, stat.Size() > 0) cmd := exec.Command(b.Executable()) + cmd.Stdin = strings.NewReader(input) output, err := cmd.Output() exitCode := 0 From 824efbf4245959b194730f56e36085d8eea26084 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 18 Jul 2024 21:10:27 +0200 Subject: [PATCH 0322/1012] Implemented echo example --- examples/echo/echo.q | 18 +++++++ lib/mem/alloc.q | 9 ++++ lib/sys/linux.q | 2 +- src/build/core/CompileDefinition.go | 23 +-------- ...nknownIdentifier4.q => UnknownFunction2.q} | 4 -- tests/errors/UnknownIdentifier3.q | 4 ++ tests/errors_test.go | 4 +- tests/examples_test.go | 15 +++--- tests/programs_test.go | 49 ++++++++++--------- 9 files changed, 70 insertions(+), 58 deletions(-) create mode 100644 examples/echo/echo.q create mode 100644 lib/mem/alloc.q rename tests/errors/{UnknownIdentifier4.q => UnknownFunction2.q} (55%) diff --git a/examples/echo/echo.q b/examples/echo/echo.q new file mode 100644 index 0000000..fb1159e --- /dev/null +++ b/examples/echo/echo.q @@ -0,0 +1,18 @@ +import mem +import sys + +main() { + length := 4096 + address := mem.alloc(length) + + loop { + n := sys.read(0, address, length) + + if n <= 0 { + mem.free(address, length) + return + } + + sys.write(1, address, n) + } +} \ No newline at end of file diff --git a/lib/mem/alloc.q b/lib/mem/alloc.q new file mode 100644 index 0000000..e03ece3 --- /dev/null +++ b/lib/mem/alloc.q @@ -0,0 +1,9 @@ +import sys + +alloc(length) { + return sys.mmap(0, length, 3, 290) +} + +free(address, length) { + return sys.munmap(address, length) +} \ No newline at end of file diff --git a/lib/sys/linux.q b/lib/sys/linux.q index 9c44037..68a8fbf 100644 --- a/lib/sys/linux.q +++ b/lib/sys/linux.q @@ -3,7 +3,7 @@ read(fd, address, length) { } write(fd, address, length) { - syscall(1, fd, address, length) + return syscall(1, fd, address, length) } open(file, flags, mode) { diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index bbc544b..bf215a1 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -22,21 +22,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) } - value := node.Value - - err := value.EachLeaf(func(leaf *expression.Expression) error { - if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) { - return errors.New(&errors.UnknownIdentifier{Name: leaf.Token.Text()}, f.File, leaf.Token.Position) - } - - return nil - }) - - if err != nil { - return err - } - - return f.storeVariableInRegister(name, value, uses) + return f.storeVariableInRegister(name, node.Value, uses) } func (f *Function) AddVariable(variable *Variable) { @@ -94,12 +80,7 @@ func (f *Function) identifierExists(name string) bool { } func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error { - reg, exists := f.Scope().FindFree(f.cpu.General) - - if !exists { - panic("no free registers") - } - + reg := f.Scope().MustFindFree(f.cpu.General) f.Scope().Reserve(reg) err := f.ExpressionToRegister(value, reg) diff --git a/tests/errors/UnknownIdentifier4.q b/tests/errors/UnknownFunction2.q similarity index 55% rename from tests/errors/UnknownIdentifier4.q rename to tests/errors/UnknownFunction2.q index a7387e2..c31b354 100644 --- a/tests/errors/UnknownIdentifier4.q +++ b/tests/errors/UnknownFunction2.q @@ -1,7 +1,3 @@ main() { x := 1 + f(x) -} - -f(x) { - return x } \ No newline at end of file diff --git a/tests/errors/UnknownIdentifier3.q b/tests/errors/UnknownIdentifier3.q index c31b354..a7387e2 100644 --- a/tests/errors/UnknownIdentifier3.q +++ b/tests/errors/UnknownIdentifier3.q @@ -1,3 +1,7 @@ main() { x := 1 + f(x) +} + +f(x) { + return x } \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 7782a67..6c08acd 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -34,10 +34,10 @@ var errs = []struct { {"MissingOperand2.q", errors.MissingOperand}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, + {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, - {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "f"}}, - {"UnknownIdentifier4.q", &errors.UnknownIdentifier{Name: "x"}}, + {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, } diff --git a/tests/examples_test.go b/tests/examples_test.go index 8af877d..17839db 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -9,19 +9,20 @@ import ( ) var examples = []struct { - Name string - ExpectedOutput string - ExpectedExitCode int + Name string + Input string + Output string + ExitCode int }{ - {"hello", "Hello", 0}, - {"factorial", "", 120}, - {"fibonacci", "", 55}, + {"hello", "", "Hello", 0}, + {"factorial", "", "", 120}, + {"fibonacci", "", "", 55}, } func TestExamples(t *testing.T) { for _, test := range examples { t.Run(test.Name, func(t *testing.T) { - run(t, filepath.Join("..", "examples", test.Name), test.ExpectedOutput, test.ExpectedExitCode) + run(t, filepath.Join("..", "examples", test.Name), test.Input, test.Output, test.ExitCode) }) } } diff --git a/tests/programs_test.go b/tests/programs_test.go index aa3a6d5..5d4e5ff 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -4,6 +4,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "testing" "git.akyoto.dev/cli/q/src/build" @@ -11,34 +12,35 @@ import ( ) var programs = []struct { - Name string - ExpectedOutput string - ExpectedExitCode int + Name string + Input string + Output string + ExitCode int }{ - {"empty", "", 0}, - {"math", "", 10}, - {"precedence", "", 10}, - {"square-sum", "", 25}, - {"chained-calls", "", 9}, - {"nested-calls", "", 4}, - {"param", "", 3}, - {"param-multi", "", 21}, - {"reuse", "", 3}, - {"return", "", 6}, - {"reassign", "", 2}, - {"branch", "", 0}, - {"branch-and", "", 0}, - {"branch-or", "", 0}, - {"branch-both", "", 0}, - {"jump-near", "", 0}, - {"loop", "", 0}, - {"loop-lifetime", "", 0}, + {"empty", "", "", 0}, + {"math", "", "", 10}, + {"precedence", "", "", 10}, + {"square-sum", "", "", 25}, + {"chained-calls", "", "", 9}, + {"nested-calls", "", "", 4}, + {"param", "", "", 3}, + {"param-multi", "", "", 21}, + {"reuse", "", "", 3}, + {"return", "", "", 6}, + {"reassign", "", "", 2}, + {"branch", "", "", 0}, + {"branch-and", "", "", 0}, + {"branch-or", "", "", 0}, + {"branch-both", "", "", 0}, + {"jump-near", "", "", 0}, + {"loop", "", "", 0}, + {"loop-lifetime", "", "", 0}, } func TestPrograms(t *testing.T) { for _, test := range programs { t.Run(test.Name, func(t *testing.T) { - run(t, filepath.Join("programs", test.Name+".q"), test.ExpectedOutput, test.ExpectedExitCode) + run(t, filepath.Join("programs", test.Name+".q"), test.Input, test.Output, test.ExitCode) }) } } @@ -57,7 +59,7 @@ func BenchmarkPrograms(b *testing.B) { } // run builds and runs the file to check if the output matches the expected output. -func run(t *testing.T, name string, expectedOutput string, expectedExitCode int) { +func run(t *testing.T, name string, input string, expectedOutput string, expectedExitCode int) { b := build.New(name) assert.True(t, len(b.Executable()) > 0) @@ -72,6 +74,7 @@ func run(t *testing.T, name string, expectedOutput string, expectedExitCode int) assert.True(t, stat.Size() > 0) cmd := exec.Command(b.Executable()) + cmd.Stdin = strings.NewReader(input) output, err := cmd.Output() exitCode := 0 From 84b4731815ee8fd80e8e928f4344993809a896e5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 18 Jul 2024 23:06:48 +0200 Subject: [PATCH 0323/1012] Reduced file size when data is empty --- src/build/elf/ELF.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/build/elf/ELF.go b/src/build/elf/ELF.go index a2b1e4a..1cc8b57 100644 --- a/src/build/elf/ELF.go +++ b/src/build/elf/ELF.go @@ -81,6 +81,9 @@ func (elf *ELF) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &elf.DataHeader) writer.Write(elf.CodePadding) writer.Write(elf.Code) - writer.Write(elf.DataPadding) - writer.Write(elf.Data) + + if len(elf.Data) > 0 { + writer.Write(elf.DataPadding) + writer.Write(elf.Data) + } } From 733c12d41385cd37c07406e576ed78ad1719e772 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 18 Jul 2024 23:06:48 +0200 Subject: [PATCH 0324/1012] Reduced file size when data is empty --- src/build/elf/ELF.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/build/elf/ELF.go b/src/build/elf/ELF.go index a2b1e4a..1cc8b57 100644 --- a/src/build/elf/ELF.go +++ b/src/build/elf/ELF.go @@ -81,6 +81,9 @@ func (elf *ELF) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &elf.DataHeader) writer.Write(elf.CodePadding) writer.Write(elf.Code) - writer.Write(elf.DataPadding) - writer.Write(elf.Data) + + if len(elf.Data) > 0 { + writer.Write(elf.DataPadding) + writer.Write(elf.Data) + } } From b776775f8f3d0b9b25d769bf3563d951e98e7ee3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 19 Jul 2024 11:43:20 +0200 Subject: [PATCH 0325/1012] Fixed importing the same directory twice --- src/build/scanner/Scanner.go | 1 + src/build/scanner/queueDirectory.go | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/build/scanner/Scanner.go b/src/build/scanner/Scanner.go index 9725e3b..ff6ea26 100644 --- a/src/build/scanner/Scanner.go +++ b/src/build/scanner/Scanner.go @@ -11,4 +11,5 @@ type Scanner struct { functions chan *core.Function errors chan error group sync.WaitGroup + queued sync.Map } diff --git a/src/build/scanner/queueDirectory.go b/src/build/scanner/queueDirectory.go index 21b32e8..d21c8ce 100644 --- a/src/build/scanner/queueDirectory.go +++ b/src/build/scanner/queueDirectory.go @@ -9,6 +9,12 @@ import ( // queueDirectory queues an entire directory to be scanned. func (s *Scanner) queueDirectory(directory string, pkg string) { + _, loaded := s.queued.LoadOrStore(directory, nil) + + if loaded { + return + } + err := fs.Walk(directory, func(name string) { if !strings.HasSuffix(name, ".q") { return From 7490a32666a408038ec9c9cec3448a01809bda24 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 19 Jul 2024 11:43:20 +0200 Subject: [PATCH 0326/1012] Fixed importing the same directory twice --- src/build/scanner/Scanner.go | 1 + src/build/scanner/queueDirectory.go | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/build/scanner/Scanner.go b/src/build/scanner/Scanner.go index 9725e3b..ff6ea26 100644 --- a/src/build/scanner/Scanner.go +++ b/src/build/scanner/Scanner.go @@ -11,4 +11,5 @@ type Scanner struct { functions chan *core.Function errors chan error group sync.WaitGroup + queued sync.Map } diff --git a/src/build/scanner/queueDirectory.go b/src/build/scanner/queueDirectory.go index 21b32e8..d21c8ce 100644 --- a/src/build/scanner/queueDirectory.go +++ b/src/build/scanner/queueDirectory.go @@ -9,6 +9,12 @@ import ( // queueDirectory queues an entire directory to be scanned. func (s *Scanner) queueDirectory(directory string, pkg string) { + _, loaded := s.queued.LoadOrStore(directory, nil) + + if loaded { + return + } + err := fs.Walk(directory, func(name string) { if !strings.HasSuffix(name, ".q") { return From 2c2b6e93dbdb8bd4a738f81371fcdefe6e2ac905 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 20 Jul 2024 00:58:39 +0200 Subject: [PATCH 0327/1012] Improved x64 encoder --- src/build/arch/x64/Add.go | 4 +- src/build/arch/x64/Compare.go | 4 +- src/build/arch/x64/ModRM.go | 7 ++ src/build/arch/x64/Move.go | 2 +- src/build/arch/x64/Mul.go | 4 +- src/build/arch/x64/REX.go | 2 +- src/build/arch/x64/SIB.go | 16 ++++ src/build/arch/x64/SIB_test.go | 34 +++++++ src/build/arch/x64/Store.go | 52 ++++++++++ src/build/arch/x64/Store_test.go | 157 +++++++++++++++++++++++++++++++ src/build/arch/x64/Sub.go | 4 +- src/build/arch/x64/encode.go | 27 ++++++ src/build/arch/x64/encodeNum.go | 14 +++ src/build/arch/x64/regReg.go | 29 ------ src/build/arch/x64/regRegNum.go | 14 --- 15 files changed, 317 insertions(+), 53 deletions(-) create mode 100644 src/build/arch/x64/SIB.go create mode 100644 src/build/arch/x64/SIB_test.go create mode 100644 src/build/arch/x64/Store.go create mode 100644 src/build/arch/x64/Store_test.go create mode 100644 src/build/arch/x64/encode.go create mode 100644 src/build/arch/x64/encodeNum.go delete mode 100644 src/build/arch/x64/regReg.go delete mode 100644 src/build/arch/x64/regRegNum.go diff --git a/src/build/arch/x64/Add.go b/src/build/arch/x64/Add.go index b92cc60..b60a19b 100644 --- a/src/build/arch/x64/Add.go +++ b/src/build/arch/x64/Add.go @@ -6,10 +6,10 @@ import ( // AddRegisterNumber adds a number to the given register. func AddRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return regRegNum(code, 0, byte(destination), number, 0x83, 0x81) + return encodeNum(code, 1, AddressDirect, 0, byte(destination), number, 0x83, 0x81) } // AddRegisterRegister adds a register value into another register. func AddRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return regReg(code, byte(operand), byte(destination), 0x01) + return encode(code, 1, AddressDirect, byte(operand), byte(destination), 0x01) } diff --git a/src/build/arch/x64/Compare.go b/src/build/arch/x64/Compare.go index 5e49f7b..406d016 100644 --- a/src/build/arch/x64/Compare.go +++ b/src/build/arch/x64/Compare.go @@ -4,10 +4,10 @@ import "git.akyoto.dev/cli/q/src/build/cpu" // Compares the register with the number and sets the status flags in the EFLAGS register. func CompareRegisterNumber(code []byte, register cpu.Register, number int) []byte { - return regRegNum(code, 0b111, byte(register), number, 0x83, 0x81) + return encodeNum(code, 1, AddressDirect, 0b111, byte(register), number, 0x83, 0x81) } // CompareRegisterRegister compares a register with a register and sets the status flags in the EFLAGS register. func CompareRegisterRegister(code []byte, registerA cpu.Register, registerB cpu.Register) []byte { - return regReg(code, byte(registerB), byte(registerA), 0x39) + return encode(code, 1, AddressDirect, byte(registerB), byte(registerA), 0x39) } diff --git a/src/build/arch/x64/ModRM.go b/src/build/arch/x64/ModRM.go index b6ab57f..ca6a327 100644 --- a/src/build/arch/x64/ModRM.go +++ b/src/build/arch/x64/ModRM.go @@ -1,5 +1,12 @@ package x64 +const ( + AddressMemory = byte(0b00) + AddressMemoryOffset8 = byte(0b01) + AddressMemoryOffset32 = byte(0b10) + AddressDirect = byte(0b11) +) + // ModRM is used to generate a ModRM suffix. // - mod: 2 bits // - reg: 3 bits diff --git a/src/build/arch/x64/Move.go b/src/build/arch/x64/Move.go index e266d33..4245a4b 100644 --- a/src/build/arch/x64/Move.go +++ b/src/build/arch/x64/Move.go @@ -19,5 +19,5 @@ func MoveRegisterNumber32(code []byte, destination cpu.Register, number uint32) // MoveRegisterRegister64 moves a register value into another register. func MoveRegisterRegister64(code []byte, destination cpu.Register, source cpu.Register) []byte { - return regReg(code, byte(source), byte(destination), 0x89) + return encode(code, 1, AddressDirect, byte(source), byte(destination), 0x89) } diff --git a/src/build/arch/x64/Mul.go b/src/build/arch/x64/Mul.go index a041799..181b40c 100644 --- a/src/build/arch/x64/Mul.go +++ b/src/build/arch/x64/Mul.go @@ -4,10 +4,10 @@ import "git.akyoto.dev/cli/q/src/build/cpu" // MulRegisterNumber multiplies a register with a number. func MulRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return regRegNum(code, byte(destination), byte(destination), number, 0x6B, 0x69) + return encodeNum(code, 1, AddressDirect, byte(destination), byte(destination), number, 0x6B, 0x69) } // MulRegisterRegister multiplies a register with another register. func MulRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return regReg(code, byte(destination), byte(operand), 0x0F, 0xAF) + return encode(code, 1, AddressDirect, byte(destination), byte(operand), 0x0F, 0xAF) } diff --git a/src/build/arch/x64/REX.go b/src/build/arch/x64/REX.go index ba1fa1a..36d7e58 100644 --- a/src/build/arch/x64/REX.go +++ b/src/build/arch/x64/REX.go @@ -3,5 +3,5 @@ package x64 // REX is used to generate a REX prefix. // w, r, x and b can only be set to either 0 or 1. func REX(w, r, x, b byte) byte { - return 0b_0100_0000 | (w << 3) | (r << 2) | (x << 1) | b + return 0b0100_0000 | (w << 3) | (r << 2) | (x << 1) | b } diff --git a/src/build/arch/x64/SIB.go b/src/build/arch/x64/SIB.go new file mode 100644 index 0000000..030ffac --- /dev/null +++ b/src/build/arch/x64/SIB.go @@ -0,0 +1,16 @@ +package x64 + +const ( + Scale1 = byte(0b00) + Scale2 = byte(0b01) + Scale4 = byte(0b10) + Scale8 = byte(0b11) +) + +// SIB is used to generate an SIB byte. +// - scale: 2 bits +// - index: 3 bits +// - base: 3 bits +func SIB(scale byte, index byte, base byte) byte { + return (scale << 6) | (index << 3) | base +} diff --git a/src/build/arch/x64/SIB_test.go b/src/build/arch/x64/SIB_test.go new file mode 100644 index 0000000..690f04b --- /dev/null +++ b/src/build/arch/x64/SIB_test.go @@ -0,0 +1,34 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/go/assert" +) + +func TestSIB(t *testing.T) { + testData := []struct{ scale, index, base, expected byte }{ + {0b_00, 0b_111, 0b_000, 0b_00_111_000}, + {0b_00, 0b_110, 0b_001, 0b_00_110_001}, + {0b_00, 0b_101, 0b_010, 0b_00_101_010}, + {0b_00, 0b_100, 0b_011, 0b_00_100_011}, + {0b_00, 0b_011, 0b_100, 0b_00_011_100}, + {0b_00, 0b_010, 0b_101, 0b_00_010_101}, + {0b_00, 0b_001, 0b_110, 0b_00_001_110}, + {0b_00, 0b_000, 0b_111, 0b_00_000_111}, + {0b_11, 0b_111, 0b_000, 0b_11_111_000}, + {0b_11, 0b_110, 0b_001, 0b_11_110_001}, + {0b_11, 0b_101, 0b_010, 0b_11_101_010}, + {0b_11, 0b_100, 0b_011, 0b_11_100_011}, + {0b_11, 0b_011, 0b_100, 0b_11_011_100}, + {0b_11, 0b_010, 0b_101, 0b_11_010_101}, + {0b_11, 0b_001, 0b_110, 0b_11_001_110}, + {0b_11, 0b_000, 0b_111, 0b_11_000_111}, + } + + for _, test := range testData { + sib := x64.SIB(test.scale, test.index, test.base) + assert.Equal(t, sib, test.expected) + } +} diff --git a/src/build/arch/x64/Store.go b/src/build/arch/x64/Store.go new file mode 100644 index 0000000..e2df6a5 --- /dev/null +++ b/src/build/arch/x64/Store.go @@ -0,0 +1,52 @@ +package x64 + +import ( + "encoding/binary" + + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// StoreNumber stores a number into the memory address included in the given register. +func StoreNumber(code []byte, register cpu.Register, offset byte, byteCount byte, number int) []byte { + if byteCount == 2 { + code = append(code, 0x66) + } + + opCode := byte(0xC7) + + if byteCount == 1 { + opCode = 0xC6 + } + + mod := AddressMemory + + if offset != 0 || register == RBP || register == R13 { + mod = AddressMemoryOffset8 + } + + is64Bit := byte(0) + + if byteCount == 8 { + is64Bit = 1 + } + + code = encode(code, is64Bit, mod, 0b000, byte(register), opCode) + + if register == RSP || register == R12 { + code = append(code, SIB(0b00, 0b100, 0b100)) + } + + if mod == AddressMemoryOffset8 { + code = append(code, offset) + } + + switch byteCount { + case 8, 4: + return binary.LittleEndian.AppendUint32(code, uint32(number)) + + case 2: + return binary.LittleEndian.AppendUint16(code, uint16(number)) + } + + return append(code, byte(number)) +} diff --git a/src/build/arch/x64/Store_test.go b/src/build/arch/x64/Store_test.go new file mode 100644 index 0000000..6d278fb --- /dev/null +++ b/src/build/arch/x64/Store_test.go @@ -0,0 +1,157 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestStoreNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Offset byte + ByteCount byte + Number int + Code []byte + }{ + // No offset + {x64.RAX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RAX, 0, 4, 0x7F, []byte{0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RAX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x00, 0x7F, 0x00}}, + {x64.RAX, 0, 1, 0x7F, []byte{0xC6, 0x00, 0x7F}}, + {x64.RCX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RCX, 0, 4, 0x7F, []byte{0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RCX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x01, 0x7F, 0x00}}, + {x64.RCX, 0, 1, 0x7F, []byte{0xC6, 0x01, 0x7F}}, + {x64.RDX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDX, 0, 4, 0x7F, []byte{0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x02, 0x7F, 0x00}}, + {x64.RDX, 0, 1, 0x7F, []byte{0xC6, 0x02, 0x7F}}, + {x64.RBX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBX, 0, 4, 0x7F, []byte{0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x03, 0x7F, 0x00}}, + {x64.RBX, 0, 1, 0x7F, []byte{0xC6, 0x03, 0x7F}}, + {x64.RSP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSP, 0, 4, 0x7F, []byte{0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x04, 0x24, 0x7F, 0x00}}, + {x64.RSP, 0, 1, 0x7F, []byte{0xC6, 0x04, 0x24, 0x7F}}, + {x64.RBP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBP, 0, 4, 0x7F, []byte{0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x00, 0x7F, 0x00}}, + {x64.RBP, 0, 1, 0x7F, []byte{0xC6, 0x45, 0x00, 0x7F}}, + {x64.RSI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSI, 0, 4, 0x7F, []byte{0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x06, 0x7F, 0x00}}, + {x64.RSI, 0, 1, 0x7F, []byte{0xC6, 0x06, 0x7F}}, + {x64.RDI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDI, 0, 4, 0x7F, []byte{0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x07, 0x7F, 0x00}}, + {x64.RDI, 0, 1, 0x7F, []byte{0xC6, 0x07, 0x7F}}, + {x64.R8, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R8, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R8, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x00, 0x7F, 0x00}}, + {x64.R8, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x00, 0x7F}}, + {x64.R9, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R9, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R9, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x01, 0x7F, 0x00}}, + {x64.R9, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x01, 0x7F}}, + {x64.R10, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R10, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R10, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x02, 0x7F, 0x00}}, + {x64.R10, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x02, 0x7F}}, + {x64.R11, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R11, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R11, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x03, 0x7F, 0x00}}, + {x64.R11, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x03, 0x7F}}, + {x64.R12, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R12, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R12, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x24, 0x7F, 0x00}}, + {x64.R12, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x24, 0x7F}}, + {x64.R13, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R13, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R13, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x45, 0x00, 0x7F, 0x00}}, + {x64.R13, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x45, 0x00, 0x7F}}, + {x64.R14, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R14, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R14, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x06, 0x7F, 0x00}}, + {x64.R14, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x06, 0x7F}}, + {x64.R15, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R15, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R15, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x07, 0x7F, 0x00}}, + {x64.R15, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x07, 0x7F}}, + + // Offset of 1 + {x64.RAX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RAX, 1, 4, 0x7F, []byte{0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RAX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, + {x64.RAX, 1, 1, 0x7F, []byte{0xC6, 0x40, 0x01, 0x7F}}, + {x64.RCX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RCX, 1, 4, 0x7F, []byte{0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RCX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x41, 0x01, 0x7F, 0x00}}, + {x64.RCX, 1, 1, 0x7F, []byte{0xC6, 0x41, 0x01, 0x7F}}, + {x64.RDX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDX, 1, 4, 0x7F, []byte{0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x42, 0x01, 0x7F, 0x00}}, + {x64.RDX, 1, 1, 0x7F, []byte{0xC6, 0x42, 0x01, 0x7F}}, + {x64.RBX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBX, 1, 4, 0x7F, []byte{0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, + {x64.RBX, 1, 1, 0x7F, []byte{0xC6, 0x43, 0x01, 0x7F}}, + {x64.RDI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDI, 1, 4, 0x7F, []byte{0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, + {x64.RDI, 1, 1, 0x7F, []byte{0xC6, 0x47, 0x01, 0x7F}}, + {x64.RSI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSI, 1, 4, 0x7F, []byte{0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, + {x64.RSI, 1, 1, 0x7F, []byte{0xC6, 0x46, 0x01, 0x7F}}, + {x64.RBP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBP, 1, 4, 0x7F, []byte{0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, + {x64.RBP, 1, 1, 0x7F, []byte{0xC6, 0x45, 0x01, 0x7F}}, + {x64.RSP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSP, 1, 4, 0x7F, []byte{0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, + {x64.RSP, 1, 1, 0x7F, []byte{0xC6, 0x44, 0x24, 0x01, 0x7F}}, + {x64.R8, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R8, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R8, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, + {x64.R8, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x40, 0x01, 0x7F}}, + {x64.R9, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R9, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R9, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x41, 0x01, 0x7F, 0x00}}, + {x64.R9, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x41, 0x01, 0x7F}}, + {x64.R10, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R10, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R10, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x42, 0x01, 0x7F, 0x00}}, + {x64.R10, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x42, 0x01, 0x7F}}, + {x64.R11, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R11, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R11, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, + {x64.R11, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x43, 0x01, 0x7F}}, + {x64.R12, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R12, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R12, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, + {x64.R12, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x44, 0x24, 0x01, 0x7F}}, + {x64.R13, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R13, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R13, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, + {x64.R13, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x45, 0x01, 0x7F}}, + {x64.R14, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R14, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R14, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, + {x64.R14, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x46, 0x01, 0x7F}}, + {x64.R15, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R15, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R15, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, + {x64.R15, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x47, 0x01, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("store %dB [%s+%d], %d", pattern.ByteCount, pattern.Register, pattern.Offset, pattern.Number) + code := x64.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.ByteCount, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Sub.go b/src/build/arch/x64/Sub.go index 4dd9b23..ba1ce9b 100644 --- a/src/build/arch/x64/Sub.go +++ b/src/build/arch/x64/Sub.go @@ -6,10 +6,10 @@ import ( // SubRegisterNumber subtracts a number from the given register. func SubRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return regRegNum(code, 0b101, byte(destination), number, 0x83, 0x81) + return encodeNum(code, 1, AddressDirect, 0b101, byte(destination), number, 0x83, 0x81) } // SubRegisterRegister subtracts a register value from another register. func SubRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return regReg(code, byte(operand), byte(destination), 0x29) + return encode(code, 1, AddressDirect, byte(operand), byte(destination), 0x29) } diff --git a/src/build/arch/x64/encode.go b/src/build/arch/x64/encode.go new file mode 100644 index 0000000..10a1c30 --- /dev/null +++ b/src/build/arch/x64/encode.go @@ -0,0 +1,27 @@ +package x64 + +// encode is the core function that encodes an instruction. +func encode(code []byte, w byte, mod byte, reg byte, rm byte, opCodes ...byte) []byte { + r := byte(0) // Extension to the "reg" field in ModRM. + x := byte(0) // Extension to the SIB index field. + b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). + + if reg > 0b111 { + r = 1 + reg &= 0b111 + } + + if rm > 0b111 { + b = 1 + rm &= 0b111 + } + + if w != 0 || r != 0 || x != 0 || b != 0 { + code = append(code, REX(w, r, x, b)) + } + + code = append(code, opCodes...) + code = append(code, ModRM(mod, reg, rm)) + + return code +} diff --git a/src/build/arch/x64/encodeNum.go b/src/build/arch/x64/encodeNum.go new file mode 100644 index 0000000..6bcaec8 --- /dev/null +++ b/src/build/arch/x64/encodeNum.go @@ -0,0 +1,14 @@ +package x64 + +import "encoding/binary" + +// encodeNum encodes an instruction with up to two registers and a number parameter. +func encodeNum(code []byte, w byte, mod byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { + if SizeOf(int64(number)) == 1 { + code = encode(code, w, mod, reg, rm, opCode8) + return append(code, byte(number)) + } + + code = encode(code, w, mod, reg, rm, opCode32) + return binary.LittleEndian.AppendUint32(code, uint32(number)) +} diff --git a/src/build/arch/x64/regReg.go b/src/build/arch/x64/regReg.go deleted file mode 100644 index 57b0f00..0000000 --- a/src/build/arch/x64/regReg.go +++ /dev/null @@ -1,29 +0,0 @@ -package x64 - -// regReg encodes an operation using 2 registers. -func regReg(code []byte, reg byte, rm byte, opCodes ...byte) []byte { - w := byte(1) // Indicates a 64-bit register. - r := byte(0) // Extension to the "reg" field in ModRM. - x := byte(0) // Extension to the SIB index field. - b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). - mod := byte(0b11) // Direct addressing mode, no register offsets. - - if reg > 0b111 { - r = 1 - reg &= 0b111 - } - - if rm > 0b111 { - b = 1 - rm &= 0b111 - } - - rex := REX(w, r, x, b) - modRM := ModRM(mod, reg, rm) - - code = append(code, rex) - code = append(code, opCodes...) - code = append(code, modRM) - - return code -} diff --git a/src/build/arch/x64/regRegNum.go b/src/build/arch/x64/regRegNum.go deleted file mode 100644 index 61ffefb..0000000 --- a/src/build/arch/x64/regRegNum.go +++ /dev/null @@ -1,14 +0,0 @@ -package x64 - -import "encoding/binary" - -// regRegNum encodes an instruction with up to two registers and a number parameter. -func regRegNum(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { - if SizeOf(int64(number)) == 1 { - code = regReg(code, reg, rm, opCode8) - return append(code, byte(number)) - } - - code = regReg(code, reg, rm, opCode32) - return binary.LittleEndian.AppendUint32(code, uint32(number)) -} From 6b5dd4c687b1dfff09b450bcd66af8687629e4df Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 20 Jul 2024 00:58:39 +0200 Subject: [PATCH 0328/1012] Improved x64 encoder --- src/build/arch/x64/Add.go | 4 +- src/build/arch/x64/Compare.go | 4 +- src/build/arch/x64/ModRM.go | 7 ++ src/build/arch/x64/Move.go | 2 +- src/build/arch/x64/Mul.go | 4 +- src/build/arch/x64/REX.go | 2 +- src/build/arch/x64/SIB.go | 16 ++++ src/build/arch/x64/SIB_test.go | 34 +++++++ src/build/arch/x64/Store.go | 52 ++++++++++ src/build/arch/x64/Store_test.go | 157 +++++++++++++++++++++++++++++++ src/build/arch/x64/Sub.go | 4 +- src/build/arch/x64/encode.go | 27 ++++++ src/build/arch/x64/encodeNum.go | 14 +++ src/build/arch/x64/regReg.go | 29 ------ src/build/arch/x64/regRegNum.go | 14 --- 15 files changed, 317 insertions(+), 53 deletions(-) create mode 100644 src/build/arch/x64/SIB.go create mode 100644 src/build/arch/x64/SIB_test.go create mode 100644 src/build/arch/x64/Store.go create mode 100644 src/build/arch/x64/Store_test.go create mode 100644 src/build/arch/x64/encode.go create mode 100644 src/build/arch/x64/encodeNum.go delete mode 100644 src/build/arch/x64/regReg.go delete mode 100644 src/build/arch/x64/regRegNum.go diff --git a/src/build/arch/x64/Add.go b/src/build/arch/x64/Add.go index b92cc60..b60a19b 100644 --- a/src/build/arch/x64/Add.go +++ b/src/build/arch/x64/Add.go @@ -6,10 +6,10 @@ import ( // AddRegisterNumber adds a number to the given register. func AddRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return regRegNum(code, 0, byte(destination), number, 0x83, 0x81) + return encodeNum(code, 1, AddressDirect, 0, byte(destination), number, 0x83, 0x81) } // AddRegisterRegister adds a register value into another register. func AddRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return regReg(code, byte(operand), byte(destination), 0x01) + return encode(code, 1, AddressDirect, byte(operand), byte(destination), 0x01) } diff --git a/src/build/arch/x64/Compare.go b/src/build/arch/x64/Compare.go index 5e49f7b..406d016 100644 --- a/src/build/arch/x64/Compare.go +++ b/src/build/arch/x64/Compare.go @@ -4,10 +4,10 @@ import "git.akyoto.dev/cli/q/src/build/cpu" // Compares the register with the number and sets the status flags in the EFLAGS register. func CompareRegisterNumber(code []byte, register cpu.Register, number int) []byte { - return regRegNum(code, 0b111, byte(register), number, 0x83, 0x81) + return encodeNum(code, 1, AddressDirect, 0b111, byte(register), number, 0x83, 0x81) } // CompareRegisterRegister compares a register with a register and sets the status flags in the EFLAGS register. func CompareRegisterRegister(code []byte, registerA cpu.Register, registerB cpu.Register) []byte { - return regReg(code, byte(registerB), byte(registerA), 0x39) + return encode(code, 1, AddressDirect, byte(registerB), byte(registerA), 0x39) } diff --git a/src/build/arch/x64/ModRM.go b/src/build/arch/x64/ModRM.go index b6ab57f..ca6a327 100644 --- a/src/build/arch/x64/ModRM.go +++ b/src/build/arch/x64/ModRM.go @@ -1,5 +1,12 @@ package x64 +const ( + AddressMemory = byte(0b00) + AddressMemoryOffset8 = byte(0b01) + AddressMemoryOffset32 = byte(0b10) + AddressDirect = byte(0b11) +) + // ModRM is used to generate a ModRM suffix. // - mod: 2 bits // - reg: 3 bits diff --git a/src/build/arch/x64/Move.go b/src/build/arch/x64/Move.go index e266d33..4245a4b 100644 --- a/src/build/arch/x64/Move.go +++ b/src/build/arch/x64/Move.go @@ -19,5 +19,5 @@ func MoveRegisterNumber32(code []byte, destination cpu.Register, number uint32) // MoveRegisterRegister64 moves a register value into another register. func MoveRegisterRegister64(code []byte, destination cpu.Register, source cpu.Register) []byte { - return regReg(code, byte(source), byte(destination), 0x89) + return encode(code, 1, AddressDirect, byte(source), byte(destination), 0x89) } diff --git a/src/build/arch/x64/Mul.go b/src/build/arch/x64/Mul.go index a041799..181b40c 100644 --- a/src/build/arch/x64/Mul.go +++ b/src/build/arch/x64/Mul.go @@ -4,10 +4,10 @@ import "git.akyoto.dev/cli/q/src/build/cpu" // MulRegisterNumber multiplies a register with a number. func MulRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return regRegNum(code, byte(destination), byte(destination), number, 0x6B, 0x69) + return encodeNum(code, 1, AddressDirect, byte(destination), byte(destination), number, 0x6B, 0x69) } // MulRegisterRegister multiplies a register with another register. func MulRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return regReg(code, byte(destination), byte(operand), 0x0F, 0xAF) + return encode(code, 1, AddressDirect, byte(destination), byte(operand), 0x0F, 0xAF) } diff --git a/src/build/arch/x64/REX.go b/src/build/arch/x64/REX.go index ba1fa1a..36d7e58 100644 --- a/src/build/arch/x64/REX.go +++ b/src/build/arch/x64/REX.go @@ -3,5 +3,5 @@ package x64 // REX is used to generate a REX prefix. // w, r, x and b can only be set to either 0 or 1. func REX(w, r, x, b byte) byte { - return 0b_0100_0000 | (w << 3) | (r << 2) | (x << 1) | b + return 0b0100_0000 | (w << 3) | (r << 2) | (x << 1) | b } diff --git a/src/build/arch/x64/SIB.go b/src/build/arch/x64/SIB.go new file mode 100644 index 0000000..030ffac --- /dev/null +++ b/src/build/arch/x64/SIB.go @@ -0,0 +1,16 @@ +package x64 + +const ( + Scale1 = byte(0b00) + Scale2 = byte(0b01) + Scale4 = byte(0b10) + Scale8 = byte(0b11) +) + +// SIB is used to generate an SIB byte. +// - scale: 2 bits +// - index: 3 bits +// - base: 3 bits +func SIB(scale byte, index byte, base byte) byte { + return (scale << 6) | (index << 3) | base +} diff --git a/src/build/arch/x64/SIB_test.go b/src/build/arch/x64/SIB_test.go new file mode 100644 index 0000000..690f04b --- /dev/null +++ b/src/build/arch/x64/SIB_test.go @@ -0,0 +1,34 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/go/assert" +) + +func TestSIB(t *testing.T) { + testData := []struct{ scale, index, base, expected byte }{ + {0b_00, 0b_111, 0b_000, 0b_00_111_000}, + {0b_00, 0b_110, 0b_001, 0b_00_110_001}, + {0b_00, 0b_101, 0b_010, 0b_00_101_010}, + {0b_00, 0b_100, 0b_011, 0b_00_100_011}, + {0b_00, 0b_011, 0b_100, 0b_00_011_100}, + {0b_00, 0b_010, 0b_101, 0b_00_010_101}, + {0b_00, 0b_001, 0b_110, 0b_00_001_110}, + {0b_00, 0b_000, 0b_111, 0b_00_000_111}, + {0b_11, 0b_111, 0b_000, 0b_11_111_000}, + {0b_11, 0b_110, 0b_001, 0b_11_110_001}, + {0b_11, 0b_101, 0b_010, 0b_11_101_010}, + {0b_11, 0b_100, 0b_011, 0b_11_100_011}, + {0b_11, 0b_011, 0b_100, 0b_11_011_100}, + {0b_11, 0b_010, 0b_101, 0b_11_010_101}, + {0b_11, 0b_001, 0b_110, 0b_11_001_110}, + {0b_11, 0b_000, 0b_111, 0b_11_000_111}, + } + + for _, test := range testData { + sib := x64.SIB(test.scale, test.index, test.base) + assert.Equal(t, sib, test.expected) + } +} diff --git a/src/build/arch/x64/Store.go b/src/build/arch/x64/Store.go new file mode 100644 index 0000000..e2df6a5 --- /dev/null +++ b/src/build/arch/x64/Store.go @@ -0,0 +1,52 @@ +package x64 + +import ( + "encoding/binary" + + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// StoreNumber stores a number into the memory address included in the given register. +func StoreNumber(code []byte, register cpu.Register, offset byte, byteCount byte, number int) []byte { + if byteCount == 2 { + code = append(code, 0x66) + } + + opCode := byte(0xC7) + + if byteCount == 1 { + opCode = 0xC6 + } + + mod := AddressMemory + + if offset != 0 || register == RBP || register == R13 { + mod = AddressMemoryOffset8 + } + + is64Bit := byte(0) + + if byteCount == 8 { + is64Bit = 1 + } + + code = encode(code, is64Bit, mod, 0b000, byte(register), opCode) + + if register == RSP || register == R12 { + code = append(code, SIB(0b00, 0b100, 0b100)) + } + + if mod == AddressMemoryOffset8 { + code = append(code, offset) + } + + switch byteCount { + case 8, 4: + return binary.LittleEndian.AppendUint32(code, uint32(number)) + + case 2: + return binary.LittleEndian.AppendUint16(code, uint16(number)) + } + + return append(code, byte(number)) +} diff --git a/src/build/arch/x64/Store_test.go b/src/build/arch/x64/Store_test.go new file mode 100644 index 0000000..6d278fb --- /dev/null +++ b/src/build/arch/x64/Store_test.go @@ -0,0 +1,157 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestStoreNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Offset byte + ByteCount byte + Number int + Code []byte + }{ + // No offset + {x64.RAX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RAX, 0, 4, 0x7F, []byte{0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RAX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x00, 0x7F, 0x00}}, + {x64.RAX, 0, 1, 0x7F, []byte{0xC6, 0x00, 0x7F}}, + {x64.RCX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RCX, 0, 4, 0x7F, []byte{0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RCX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x01, 0x7F, 0x00}}, + {x64.RCX, 0, 1, 0x7F, []byte{0xC6, 0x01, 0x7F}}, + {x64.RDX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDX, 0, 4, 0x7F, []byte{0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x02, 0x7F, 0x00}}, + {x64.RDX, 0, 1, 0x7F, []byte{0xC6, 0x02, 0x7F}}, + {x64.RBX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBX, 0, 4, 0x7F, []byte{0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x03, 0x7F, 0x00}}, + {x64.RBX, 0, 1, 0x7F, []byte{0xC6, 0x03, 0x7F}}, + {x64.RSP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSP, 0, 4, 0x7F, []byte{0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x04, 0x24, 0x7F, 0x00}}, + {x64.RSP, 0, 1, 0x7F, []byte{0xC6, 0x04, 0x24, 0x7F}}, + {x64.RBP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBP, 0, 4, 0x7F, []byte{0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x00, 0x7F, 0x00}}, + {x64.RBP, 0, 1, 0x7F, []byte{0xC6, 0x45, 0x00, 0x7F}}, + {x64.RSI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSI, 0, 4, 0x7F, []byte{0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x06, 0x7F, 0x00}}, + {x64.RSI, 0, 1, 0x7F, []byte{0xC6, 0x06, 0x7F}}, + {x64.RDI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDI, 0, 4, 0x7F, []byte{0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x07, 0x7F, 0x00}}, + {x64.RDI, 0, 1, 0x7F, []byte{0xC6, 0x07, 0x7F}}, + {x64.R8, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R8, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R8, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x00, 0x7F, 0x00}}, + {x64.R8, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x00, 0x7F}}, + {x64.R9, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R9, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R9, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x01, 0x7F, 0x00}}, + {x64.R9, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x01, 0x7F}}, + {x64.R10, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R10, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R10, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x02, 0x7F, 0x00}}, + {x64.R10, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x02, 0x7F}}, + {x64.R11, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R11, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R11, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x03, 0x7F, 0x00}}, + {x64.R11, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x03, 0x7F}}, + {x64.R12, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R12, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R12, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x24, 0x7F, 0x00}}, + {x64.R12, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x24, 0x7F}}, + {x64.R13, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R13, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R13, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x45, 0x00, 0x7F, 0x00}}, + {x64.R13, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x45, 0x00, 0x7F}}, + {x64.R14, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R14, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R14, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x06, 0x7F, 0x00}}, + {x64.R14, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x06, 0x7F}}, + {x64.R15, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R15, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R15, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x07, 0x7F, 0x00}}, + {x64.R15, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x07, 0x7F}}, + + // Offset of 1 + {x64.RAX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RAX, 1, 4, 0x7F, []byte{0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RAX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, + {x64.RAX, 1, 1, 0x7F, []byte{0xC6, 0x40, 0x01, 0x7F}}, + {x64.RCX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RCX, 1, 4, 0x7F, []byte{0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RCX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x41, 0x01, 0x7F, 0x00}}, + {x64.RCX, 1, 1, 0x7F, []byte{0xC6, 0x41, 0x01, 0x7F}}, + {x64.RDX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDX, 1, 4, 0x7F, []byte{0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x42, 0x01, 0x7F, 0x00}}, + {x64.RDX, 1, 1, 0x7F, []byte{0xC6, 0x42, 0x01, 0x7F}}, + {x64.RBX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBX, 1, 4, 0x7F, []byte{0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, + {x64.RBX, 1, 1, 0x7F, []byte{0xC6, 0x43, 0x01, 0x7F}}, + {x64.RDI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDI, 1, 4, 0x7F, []byte{0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, + {x64.RDI, 1, 1, 0x7F, []byte{0xC6, 0x47, 0x01, 0x7F}}, + {x64.RSI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSI, 1, 4, 0x7F, []byte{0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, + {x64.RSI, 1, 1, 0x7F, []byte{0xC6, 0x46, 0x01, 0x7F}}, + {x64.RBP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBP, 1, 4, 0x7F, []byte{0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, + {x64.RBP, 1, 1, 0x7F, []byte{0xC6, 0x45, 0x01, 0x7F}}, + {x64.RSP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSP, 1, 4, 0x7F, []byte{0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, + {x64.RSP, 1, 1, 0x7F, []byte{0xC6, 0x44, 0x24, 0x01, 0x7F}}, + {x64.R8, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R8, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R8, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, + {x64.R8, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x40, 0x01, 0x7F}}, + {x64.R9, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R9, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R9, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x41, 0x01, 0x7F, 0x00}}, + {x64.R9, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x41, 0x01, 0x7F}}, + {x64.R10, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R10, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R10, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x42, 0x01, 0x7F, 0x00}}, + {x64.R10, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x42, 0x01, 0x7F}}, + {x64.R11, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R11, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R11, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, + {x64.R11, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x43, 0x01, 0x7F}}, + {x64.R12, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R12, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R12, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, + {x64.R12, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x44, 0x24, 0x01, 0x7F}}, + {x64.R13, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R13, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R13, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, + {x64.R13, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x45, 0x01, 0x7F}}, + {x64.R14, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R14, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R14, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, + {x64.R14, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x46, 0x01, 0x7F}}, + {x64.R15, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R15, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R15, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, + {x64.R15, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x47, 0x01, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("store %dB [%s+%d], %d", pattern.ByteCount, pattern.Register, pattern.Offset, pattern.Number) + code := x64.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.ByteCount, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Sub.go b/src/build/arch/x64/Sub.go index 4dd9b23..ba1ce9b 100644 --- a/src/build/arch/x64/Sub.go +++ b/src/build/arch/x64/Sub.go @@ -6,10 +6,10 @@ import ( // SubRegisterNumber subtracts a number from the given register. func SubRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return regRegNum(code, 0b101, byte(destination), number, 0x83, 0x81) + return encodeNum(code, 1, AddressDirect, 0b101, byte(destination), number, 0x83, 0x81) } // SubRegisterRegister subtracts a register value from another register. func SubRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return regReg(code, byte(operand), byte(destination), 0x29) + return encode(code, 1, AddressDirect, byte(operand), byte(destination), 0x29) } diff --git a/src/build/arch/x64/encode.go b/src/build/arch/x64/encode.go new file mode 100644 index 0000000..10a1c30 --- /dev/null +++ b/src/build/arch/x64/encode.go @@ -0,0 +1,27 @@ +package x64 + +// encode is the core function that encodes an instruction. +func encode(code []byte, w byte, mod byte, reg byte, rm byte, opCodes ...byte) []byte { + r := byte(0) // Extension to the "reg" field in ModRM. + x := byte(0) // Extension to the SIB index field. + b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). + + if reg > 0b111 { + r = 1 + reg &= 0b111 + } + + if rm > 0b111 { + b = 1 + rm &= 0b111 + } + + if w != 0 || r != 0 || x != 0 || b != 0 { + code = append(code, REX(w, r, x, b)) + } + + code = append(code, opCodes...) + code = append(code, ModRM(mod, reg, rm)) + + return code +} diff --git a/src/build/arch/x64/encodeNum.go b/src/build/arch/x64/encodeNum.go new file mode 100644 index 0000000..6bcaec8 --- /dev/null +++ b/src/build/arch/x64/encodeNum.go @@ -0,0 +1,14 @@ +package x64 + +import "encoding/binary" + +// encodeNum encodes an instruction with up to two registers and a number parameter. +func encodeNum(code []byte, w byte, mod byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { + if SizeOf(int64(number)) == 1 { + code = encode(code, w, mod, reg, rm, opCode8) + return append(code, byte(number)) + } + + code = encode(code, w, mod, reg, rm, opCode32) + return binary.LittleEndian.AppendUint32(code, uint32(number)) +} diff --git a/src/build/arch/x64/regReg.go b/src/build/arch/x64/regReg.go deleted file mode 100644 index 57b0f00..0000000 --- a/src/build/arch/x64/regReg.go +++ /dev/null @@ -1,29 +0,0 @@ -package x64 - -// regReg encodes an operation using 2 registers. -func regReg(code []byte, reg byte, rm byte, opCodes ...byte) []byte { - w := byte(1) // Indicates a 64-bit register. - r := byte(0) // Extension to the "reg" field in ModRM. - x := byte(0) // Extension to the SIB index field. - b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). - mod := byte(0b11) // Direct addressing mode, no register offsets. - - if reg > 0b111 { - r = 1 - reg &= 0b111 - } - - if rm > 0b111 { - b = 1 - rm &= 0b111 - } - - rex := REX(w, r, x, b) - modRM := ModRM(mod, reg, rm) - - code = append(code, rex) - code = append(code, opCodes...) - code = append(code, modRM) - - return code -} diff --git a/src/build/arch/x64/regRegNum.go b/src/build/arch/x64/regRegNum.go deleted file mode 100644 index 61ffefb..0000000 --- a/src/build/arch/x64/regRegNum.go +++ /dev/null @@ -1,14 +0,0 @@ -package x64 - -import "encoding/binary" - -// regRegNum encodes an instruction with up to two registers and a number parameter. -func regRegNum(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { - if SizeOf(int64(number)) == 1 { - code = regReg(code, reg, rm, opCode8) - return append(code, byte(number)) - } - - code = regReg(code, reg, rm, opCode32) - return binary.LittleEndian.AppendUint32(code, uint32(number)) -} From 53379ff5bc1fac3add592756c34f7c6f50000d21 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 20 Jul 2024 12:49:26 +0200 Subject: [PATCH 0329/1012] Improved x64 encoder --- src/build/arch/x64/Add.go | 4 +- src/build/arch/x64/Compare.go | 4 +- src/build/arch/x64/Move.go | 2 +- src/build/arch/x64/Mul.go | 4 +- src/build/arch/x64/Store.go | 72 ++++++++------- src/build/arch/x64/Store_test.go | 148 +++++++++++++++++++++++++++++++ src/build/arch/x64/Sub.go | 4 +- src/build/arch/x64/encode.go | 13 ++- src/build/arch/x64/encodeNum.go | 12 ++- 9 files changed, 214 insertions(+), 49 deletions(-) diff --git a/src/build/arch/x64/Add.go b/src/build/arch/x64/Add.go index b60a19b..2e55c7d 100644 --- a/src/build/arch/x64/Add.go +++ b/src/build/arch/x64/Add.go @@ -6,10 +6,10 @@ import ( // AddRegisterNumber adds a number to the given register. func AddRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, 1, AddressDirect, 0, byte(destination), number, 0x83, 0x81) + return encodeNum(code, AddressDirect, 0, destination, number, 0x83, 0x81) } // AddRegisterRegister adds a register value into another register. func AddRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, 1, AddressDirect, byte(operand), byte(destination), 0x01) + return encode(code, AddressDirect, operand, destination, 8, 0x01) } diff --git a/src/build/arch/x64/Compare.go b/src/build/arch/x64/Compare.go index 406d016..49b5ba6 100644 --- a/src/build/arch/x64/Compare.go +++ b/src/build/arch/x64/Compare.go @@ -4,10 +4,10 @@ import "git.akyoto.dev/cli/q/src/build/cpu" // Compares the register with the number and sets the status flags in the EFLAGS register. func CompareRegisterNumber(code []byte, register cpu.Register, number int) []byte { - return encodeNum(code, 1, AddressDirect, 0b111, byte(register), number, 0x83, 0x81) + return encodeNum(code, AddressDirect, 0b111, register, number, 0x83, 0x81) } // CompareRegisterRegister compares a register with a register and sets the status flags in the EFLAGS register. func CompareRegisterRegister(code []byte, registerA cpu.Register, registerB cpu.Register) []byte { - return encode(code, 1, AddressDirect, byte(registerB), byte(registerA), 0x39) + return encode(code, AddressDirect, registerB, registerA, 8, 0x39) } diff --git a/src/build/arch/x64/Move.go b/src/build/arch/x64/Move.go index 4245a4b..3ebf470 100644 --- a/src/build/arch/x64/Move.go +++ b/src/build/arch/x64/Move.go @@ -19,5 +19,5 @@ func MoveRegisterNumber32(code []byte, destination cpu.Register, number uint32) // MoveRegisterRegister64 moves a register value into another register. func MoveRegisterRegister64(code []byte, destination cpu.Register, source cpu.Register) []byte { - return encode(code, 1, AddressDirect, byte(source), byte(destination), 0x89) + return encode(code, AddressDirect, source, destination, 8, 0x89) } diff --git a/src/build/arch/x64/Mul.go b/src/build/arch/x64/Mul.go index 181b40c..b6d7883 100644 --- a/src/build/arch/x64/Mul.go +++ b/src/build/arch/x64/Mul.go @@ -4,10 +4,10 @@ import "git.akyoto.dev/cli/q/src/build/cpu" // MulRegisterNumber multiplies a register with a number. func MulRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, 1, AddressDirect, byte(destination), byte(destination), number, 0x6B, 0x69) + return encodeNum(code, AddressDirect, destination, destination, number, 0x6B, 0x69) } // MulRegisterRegister multiplies a register with another register. func MulRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, 1, AddressDirect, byte(destination), byte(operand), 0x0F, 0xAF) + return encode(code, AddressDirect, destination, operand, 8, 0x0F, 0xAF) } diff --git a/src/build/arch/x64/Store.go b/src/build/arch/x64/Store.go index e2df6a5..caa2e5c 100644 --- a/src/build/arch/x64/Store.go +++ b/src/build/arch/x64/Store.go @@ -7,40 +7,10 @@ import ( ) // StoreNumber stores a number into the memory address included in the given register. -func StoreNumber(code []byte, register cpu.Register, offset byte, byteCount byte, number int) []byte { - if byteCount == 2 { - code = append(code, 0x66) - } +func StoreNumber(code []byte, register cpu.Register, offset byte, numBytes byte, number int) []byte { + code = store(code, 0xC6, 0xC7, register, offset, numBytes, 0b000) - opCode := byte(0xC7) - - if byteCount == 1 { - opCode = 0xC6 - } - - mod := AddressMemory - - if offset != 0 || register == RBP || register == R13 { - mod = AddressMemoryOffset8 - } - - is64Bit := byte(0) - - if byteCount == 8 { - is64Bit = 1 - } - - code = encode(code, is64Bit, mod, 0b000, byte(register), opCode) - - if register == RSP || register == R12 { - code = append(code, SIB(0b00, 0b100, 0b100)) - } - - if mod == AddressMemoryOffset8 { - code = append(code, offset) - } - - switch byteCount { + switch numBytes { case 8, 4: return binary.LittleEndian.AppendUint32(code, uint32(number)) @@ -50,3 +20,39 @@ func StoreNumber(code []byte, register cpu.Register, offset byte, byteCount byte return append(code, byte(number)) } + +// StoreRegister stores the contents of the `source` register into the memory address included in the given register. +func StoreRegister(code []byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { + return store(code, 0x88, 0x89, register, offset, numBytes, source) +} + +// store encodes a write to memory. +func store(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { + if numBytes == 2 { + code = append(code, 0x66) + } + + opCode := opCode32 + + if numBytes == 1 { + opCode = opCode8 + } + + mod := AddressMemory + + if offset != 0 || register == RBP || register == R13 { + mod = AddressMemoryOffset8 + } + + code = encode(code, mod, source, register, numBytes, opCode) + + if register == RSP || register == R12 { + code = append(code, SIB(0b00, 0b100, 0b100)) + } + + if mod == AddressMemoryOffset8 { + code = append(code, offset) + } + + return code +} diff --git a/src/build/arch/x64/Store_test.go b/src/build/arch/x64/Store_test.go index 6d278fb..2d7432a 100644 --- a/src/build/arch/x64/Store_test.go +++ b/src/build/arch/x64/Store_test.go @@ -155,3 +155,151 @@ func TestStoreNumber(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestStoreRegister(t *testing.T) { + usagePatterns := []struct { + RegisterTo cpu.Register + Offset byte + ByteCount byte + RegisterFrom cpu.Register + Code []byte + }{ + // No offset + {x64.RAX, 0, 8, x64.R15, []byte{0x4C, 0x89, 0x38}}, + {x64.RAX, 0, 4, x64.R15, []byte{0x44, 0x89, 0x38}}, + {x64.RAX, 0, 2, x64.R15, []byte{0x66, 0x44, 0x89, 0x38}}, + {x64.RAX, 0, 1, x64.R15, []byte{0x44, 0x88, 0x38}}, + {x64.RCX, 0, 8, x64.R14, []byte{0x4C, 0x89, 0x31}}, + {x64.RCX, 0, 4, x64.R14, []byte{0x44, 0x89, 0x31}}, + {x64.RCX, 0, 2, x64.R14, []byte{0x66, 0x44, 0x89, 0x31}}, + {x64.RCX, 0, 1, x64.R14, []byte{0x44, 0x88, 0x31}}, + {x64.RDX, 0, 8, x64.R13, []byte{0x4C, 0x89, 0x2A}}, + {x64.RDX, 0, 4, x64.R13, []byte{0x44, 0x89, 0x2A}}, + {x64.RDX, 0, 2, x64.R13, []byte{0x66, 0x44, 0x89, 0x2A}}, + {x64.RDX, 0, 1, x64.R13, []byte{0x44, 0x88, 0x2A}}, + {x64.RBX, 0, 8, x64.R12, []byte{0x4C, 0x89, 0x23}}, + {x64.RBX, 0, 4, x64.R12, []byte{0x44, 0x89, 0x23}}, + {x64.RBX, 0, 2, x64.R12, []byte{0x66, 0x44, 0x89, 0x23}}, + {x64.RBX, 0, 1, x64.R12, []byte{0x44, 0x88, 0x23}}, + {x64.RDI, 0, 8, x64.R11, []byte{0x4C, 0x89, 0x1F}}, + {x64.RDI, 0, 4, x64.R11, []byte{0x44, 0x89, 0x1F}}, + {x64.RDI, 0, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x1F}}, + {x64.RDI, 0, 1, x64.R11, []byte{0x44, 0x88, 0x1F}}, + {x64.RSI, 0, 8, x64.R10, []byte{0x4C, 0x89, 0x16}}, + {x64.RSI, 0, 4, x64.R10, []byte{0x44, 0x89, 0x16}}, + {x64.RSI, 0, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x16}}, + {x64.RSI, 0, 1, x64.R10, []byte{0x44, 0x88, 0x16}}, + {x64.RBP, 0, 8, x64.R9, []byte{0x4C, 0x89, 0x4D, 0x00}}, + {x64.RBP, 0, 4, x64.R9, []byte{0x44, 0x89, 0x4D, 0x00}}, + {x64.RBP, 0, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x4D, 0x00}}, + {x64.RBP, 0, 1, x64.R9, []byte{0x44, 0x88, 0x4D, 0x00}}, + {x64.RSP, 0, 8, x64.R8, []byte{0x4C, 0x89, 0x04, 0x24}}, + {x64.RSP, 0, 4, x64.R8, []byte{0x44, 0x89, 0x04, 0x24}}, + {x64.RSP, 0, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x04, 0x24}}, + {x64.RSP, 0, 1, x64.R8, []byte{0x44, 0x88, 0x04, 0x24}}, + {x64.R8, 0, 8, x64.RDI, []byte{0x49, 0x89, 0x38}}, + {x64.R8, 0, 4, x64.RDI, []byte{0x41, 0x89, 0x38}}, + {x64.R8, 0, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x38}}, + {x64.R8, 0, 1, x64.RDI, []byte{0x41, 0x88, 0x38}}, + {x64.R9, 0, 8, x64.RSI, []byte{0x49, 0x89, 0x31}}, + {x64.R9, 0, 4, x64.RSI, []byte{0x41, 0x89, 0x31}}, + {x64.R9, 0, 2, x64.RSI, []byte{0x66, 0x41, 0x89, 0x31}}, + {x64.R9, 0, 1, x64.RSI, []byte{0x41, 0x88, 0x31}}, + {x64.R10, 0, 8, x64.RBP, []byte{0x49, 0x89, 0x2A}}, + {x64.R10, 0, 4, x64.RBP, []byte{0x41, 0x89, 0x2A}}, + {x64.R10, 0, 2, x64.RBP, []byte{0x66, 0x41, 0x89, 0x2A}}, + {x64.R10, 0, 1, x64.RBP, []byte{0x41, 0x88, 0x2A}}, + {x64.R11, 0, 8, x64.RSP, []byte{0x49, 0x89, 0x23}}, + {x64.R11, 0, 4, x64.RSP, []byte{0x41, 0x89, 0x23}}, + {x64.R11, 0, 2, x64.RSP, []byte{0x66, 0x41, 0x89, 0x23}}, + {x64.R11, 0, 1, x64.RSP, []byte{0x41, 0x88, 0x23}}, + {x64.R12, 0, 8, x64.RBX, []byte{0x49, 0x89, 0x1C, 0x24}}, + {x64.R12, 0, 4, x64.RBX, []byte{0x41, 0x89, 0x1C, 0x24}}, + {x64.R12, 0, 2, x64.RBX, []byte{0x66, 0x41, 0x89, 0x1C, 0x24}}, + {x64.R12, 0, 1, x64.RBX, []byte{0x41, 0x88, 0x1C, 0x24}}, + {x64.R13, 0, 8, x64.RDX, []byte{0x49, 0x89, 0x55, 0x00}}, + {x64.R13, 0, 4, x64.RDX, []byte{0x41, 0x89, 0x55, 0x00}}, + {x64.R13, 0, 2, x64.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x00}}, + {x64.R13, 0, 1, x64.RDX, []byte{0x41, 0x88, 0x55, 0x00}}, + {x64.R14, 0, 8, x64.RCX, []byte{0x49, 0x89, 0x0E}}, + {x64.R14, 0, 4, x64.RCX, []byte{0x41, 0x89, 0x0E}}, + {x64.R14, 0, 2, x64.RCX, []byte{0x66, 0x41, 0x89, 0x0E}}, + {x64.R14, 0, 1, x64.RCX, []byte{0x41, 0x88, 0x0E}}, + {x64.R15, 0, 8, x64.RAX, []byte{0x49, 0x89, 0x07}}, + {x64.R15, 0, 4, x64.RAX, []byte{0x41, 0x89, 0x07}}, + {x64.R15, 0, 2, x64.RAX, []byte{0x66, 0x41, 0x89, 0x07}}, + {x64.R15, 0, 1, x64.RAX, []byte{0x41, 0x88, 0x07}}, + + // Offset of 1 + {x64.RAX, 1, 8, x64.R15, []byte{0x4C, 0x89, 0x78, 0x01}}, + {x64.RAX, 1, 4, x64.R15, []byte{0x44, 0x89, 0x78, 0x01}}, + {x64.RAX, 1, 2, x64.R15, []byte{0x66, 0x44, 0x89, 0x78, 0x01}}, + {x64.RAX, 1, 1, x64.R15, []byte{0x44, 0x88, 0x78, 0x01}}, + {x64.RCX, 1, 8, x64.R14, []byte{0x4C, 0x89, 0x71, 0x01}}, + {x64.RCX, 1, 4, x64.R14, []byte{0x44, 0x89, 0x71, 0x01}}, + {x64.RCX, 1, 2, x64.R14, []byte{0x66, 0x44, 0x89, 0x71, 0x01}}, + {x64.RCX, 1, 1, x64.R14, []byte{0x44, 0x88, 0x71, 0x01}}, + {x64.RDX, 1, 8, x64.R13, []byte{0x4C, 0x89, 0x6A, 0x01}}, + {x64.RDX, 1, 4, x64.R13, []byte{0x44, 0x89, 0x6A, 0x01}}, + {x64.RDX, 1, 2, x64.R13, []byte{0x66, 0x44, 0x89, 0x6A, 0x01}}, + {x64.RDX, 1, 1, x64.R13, []byte{0x44, 0x88, 0x6A, 0x01}}, + {x64.RBX, 1, 8, x64.R12, []byte{0x4C, 0x89, 0x63, 0x01}}, + {x64.RBX, 1, 4, x64.R12, []byte{0x44, 0x89, 0x63, 0x01}}, + {x64.RBX, 1, 2, x64.R12, []byte{0x66, 0x44, 0x89, 0x63, 0x01}}, + {x64.RBX, 1, 1, x64.R12, []byte{0x44, 0x88, 0x63, 0x01}}, + {x64.RDI, 1, 8, x64.R11, []byte{0x4C, 0x89, 0x5F, 0x01}}, + {x64.RDI, 1, 4, x64.R11, []byte{0x44, 0x89, 0x5F, 0x01}}, + {x64.RDI, 1, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x5F, 0x01}}, + {x64.RDI, 1, 1, x64.R11, []byte{0x44, 0x88, 0x5F, 0x01}}, + {x64.RSI, 1, 8, x64.R10, []byte{0x4C, 0x89, 0x56, 0x01}}, + {x64.RSI, 1, 4, x64.R10, []byte{0x44, 0x89, 0x56, 0x01}}, + {x64.RSI, 1, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x56, 0x01}}, + {x64.RSI, 1, 1, x64.R10, []byte{0x44, 0x88, 0x56, 0x01}}, + {x64.RBP, 1, 8, x64.R9, []byte{0x4C, 0x89, 0x4D, 0x01}}, + {x64.RBP, 1, 4, x64.R9, []byte{0x44, 0x89, 0x4D, 0x01}}, + {x64.RBP, 1, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x4D, 0x01}}, + {x64.RBP, 1, 1, x64.R9, []byte{0x44, 0x88, 0x4D, 0x01}}, + {x64.RSP, 1, 8, x64.R8, []byte{0x4C, 0x89, 0x44, 0x24, 0x01}}, + {x64.RSP, 1, 4, x64.R8, []byte{0x44, 0x89, 0x44, 0x24, 0x01}}, + {x64.RSP, 1, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x44, 0x24, 0x01}}, + {x64.RSP, 1, 1, x64.R8, []byte{0x44, 0x88, 0x44, 0x24, 01}}, + {x64.R8, 1, 8, x64.RDI, []byte{0x49, 0x89, 0x78, 0x01}}, + {x64.R8, 1, 4, x64.RDI, []byte{0x41, 0x89, 0x78, 0x01}}, + {x64.R8, 1, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x78, 0x01}}, + {x64.R8, 1, 1, x64.RDI, []byte{0x41, 0x88, 0x78, 0x01}}, + {x64.R9, 1, 8, x64.RSI, []byte{0x49, 0x89, 0x71, 0x01}}, + {x64.R9, 1, 4, x64.RSI, []byte{0x41, 0x89, 0x71, 0x01}}, + {x64.R9, 1, 2, x64.RSI, []byte{0x66, 0x41, 0x89, 0x71, 0x01}}, + {x64.R9, 1, 1, x64.RSI, []byte{0x41, 0x88, 0x71, 0x01}}, + {x64.R10, 1, 8, x64.RBP, []byte{0x49, 0x89, 0x6A, 0x01}}, + {x64.R10, 1, 4, x64.RBP, []byte{0x41, 0x89, 0x6A, 0x01}}, + {x64.R10, 1, 2, x64.RBP, []byte{0x66, 0x41, 0x89, 0x6A, 0x01}}, + {x64.R10, 1, 1, x64.RBP, []byte{0x41, 0x88, 0x6A, 0x01}}, + {x64.R11, 1, 8, x64.RSP, []byte{0x49, 0x89, 0x63, 0x01}}, + {x64.R11, 1, 4, x64.RSP, []byte{0x41, 0x89, 0x63, 0x01}}, + {x64.R11, 1, 2, x64.RSP, []byte{0x66, 0x41, 0x89, 0x63, 0x01}}, + {x64.R11, 1, 1, x64.RSP, []byte{0x41, 0x88, 0x63, 0x01}}, + {x64.R12, 1, 8, x64.RBX, []byte{0x49, 0x89, 0x5C, 0x24, 0x01}}, + {x64.R12, 1, 4, x64.RBX, []byte{0x41, 0x89, 0x5C, 0x24, 0x01}}, + {x64.R12, 1, 2, x64.RBX, []byte{0x66, 0x41, 0x89, 0x5C, 0x24, 0x01}}, + {x64.R12, 1, 1, x64.RBX, []byte{0x41, 0x88, 0x5C, 0x24, 01}}, + {x64.R13, 1, 8, x64.RDX, []byte{0x49, 0x89, 0x55, 0x01}}, + {x64.R13, 1, 4, x64.RDX, []byte{0x41, 0x89, 0x55, 0x01}}, + {x64.R13, 1, 2, x64.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x01}}, + {x64.R13, 1, 1, x64.RDX, []byte{0x41, 0x88, 0x55, 0x01}}, + {x64.R14, 1, 8, x64.RCX, []byte{0x49, 0x89, 0x4E, 0x01}}, + {x64.R14, 1, 4, x64.RCX, []byte{0x41, 0x89, 0x4E, 0x01}}, + {x64.R14, 1, 2, x64.RCX, []byte{0x66, 0x41, 0x89, 0x4E, 0x01}}, + {x64.R14, 1, 1, x64.RCX, []byte{0x41, 0x88, 0x4E, 0x01}}, + {x64.R15, 1, 8, x64.RAX, []byte{0x49, 0x89, 0x47, 0x01}}, + {x64.R15, 1, 4, x64.RAX, []byte{0x41, 0x89, 0x47, 0x01}}, + {x64.R15, 1, 2, x64.RAX, []byte{0x66, 0x41, 0x89, 0x47, 0x01}}, + {x64.R15, 1, 1, x64.RAX, []byte{0x41, 0x88, 0x47, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("store %dB [%s+%d], %s", pattern.ByteCount, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) + code := x64.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.ByteCount, pattern.RegisterFrom) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Sub.go b/src/build/arch/x64/Sub.go index ba1ce9b..268fcc6 100644 --- a/src/build/arch/x64/Sub.go +++ b/src/build/arch/x64/Sub.go @@ -6,10 +6,10 @@ import ( // SubRegisterNumber subtracts a number from the given register. func SubRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, 1, AddressDirect, 0b101, byte(destination), number, 0x83, 0x81) + return encodeNum(code, AddressDirect, 0b101, destination, number, 0x83, 0x81) } // SubRegisterRegister subtracts a register value from another register. func SubRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, 1, AddressDirect, byte(operand), byte(destination), 0x29) + return encode(code, AddressDirect, operand, destination, 8, 0x29) } diff --git a/src/build/arch/x64/encode.go b/src/build/arch/x64/encode.go index 10a1c30..606f6e2 100644 --- a/src/build/arch/x64/encode.go +++ b/src/build/arch/x64/encode.go @@ -1,11 +1,18 @@ package x64 +import "git.akyoto.dev/cli/q/src/build/cpu" + // encode is the core function that encodes an instruction. -func encode(code []byte, w byte, mod byte, reg byte, rm byte, opCodes ...byte) []byte { +func encode(code []byte, mod byte, reg cpu.Register, rm cpu.Register, numBytes byte, opCodes ...byte) []byte { + w := byte(0) // Indicates a 64-bit register. r := byte(0) // Extension to the "reg" field in ModRM. x := byte(0) // Extension to the SIB index field. b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). + if numBytes == 8 { + w = 1 + } + if reg > 0b111 { r = 1 reg &= 0b111 @@ -16,12 +23,12 @@ func encode(code []byte, w byte, mod byte, reg byte, rm byte, opCodes ...byte) [ rm &= 0b111 } - if w != 0 || r != 0 || x != 0 || b != 0 { + if w != 0 || r != 0 || x != 0 || b != 0 || (numBytes == 1 && (reg == RSP || reg == RBP || reg == RSI || reg == RDI)) { code = append(code, REX(w, r, x, b)) } code = append(code, opCodes...) - code = append(code, ModRM(mod, reg, rm)) + code = append(code, ModRM(mod, byte(reg), byte(rm))) return code } diff --git a/src/build/arch/x64/encodeNum.go b/src/build/arch/x64/encodeNum.go index 6bcaec8..15efe03 100644 --- a/src/build/arch/x64/encodeNum.go +++ b/src/build/arch/x64/encodeNum.go @@ -1,14 +1,18 @@ package x64 -import "encoding/binary" +import ( + "encoding/binary" + + "git.akyoto.dev/cli/q/src/build/cpu" +) // encodeNum encodes an instruction with up to two registers and a number parameter. -func encodeNum(code []byte, w byte, mod byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { +func encodeNum(code []byte, mod byte, reg cpu.Register, rm cpu.Register, number int, opCode8 byte, opCode32 byte) []byte { if SizeOf(int64(number)) == 1 { - code = encode(code, w, mod, reg, rm, opCode8) + code = encode(code, mod, reg, rm, 8, opCode8) return append(code, byte(number)) } - code = encode(code, w, mod, reg, rm, opCode32) + code = encode(code, mod, reg, rm, 8, opCode32) return binary.LittleEndian.AppendUint32(code, uint32(number)) } From 7c2e373562c4760dd384e022b138dc75c32058bb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 20 Jul 2024 12:49:26 +0200 Subject: [PATCH 0330/1012] Improved x64 encoder --- src/build/arch/x64/Add.go | 4 +- src/build/arch/x64/Compare.go | 4 +- src/build/arch/x64/Move.go | 2 +- src/build/arch/x64/Mul.go | 4 +- src/build/arch/x64/Store.go | 72 ++++++++------- src/build/arch/x64/Store_test.go | 148 +++++++++++++++++++++++++++++++ src/build/arch/x64/Sub.go | 4 +- src/build/arch/x64/encode.go | 13 ++- src/build/arch/x64/encodeNum.go | 12 ++- 9 files changed, 214 insertions(+), 49 deletions(-) diff --git a/src/build/arch/x64/Add.go b/src/build/arch/x64/Add.go index b60a19b..2e55c7d 100644 --- a/src/build/arch/x64/Add.go +++ b/src/build/arch/x64/Add.go @@ -6,10 +6,10 @@ import ( // AddRegisterNumber adds a number to the given register. func AddRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, 1, AddressDirect, 0, byte(destination), number, 0x83, 0x81) + return encodeNum(code, AddressDirect, 0, destination, number, 0x83, 0x81) } // AddRegisterRegister adds a register value into another register. func AddRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, 1, AddressDirect, byte(operand), byte(destination), 0x01) + return encode(code, AddressDirect, operand, destination, 8, 0x01) } diff --git a/src/build/arch/x64/Compare.go b/src/build/arch/x64/Compare.go index 406d016..49b5ba6 100644 --- a/src/build/arch/x64/Compare.go +++ b/src/build/arch/x64/Compare.go @@ -4,10 +4,10 @@ import "git.akyoto.dev/cli/q/src/build/cpu" // Compares the register with the number and sets the status flags in the EFLAGS register. func CompareRegisterNumber(code []byte, register cpu.Register, number int) []byte { - return encodeNum(code, 1, AddressDirect, 0b111, byte(register), number, 0x83, 0x81) + return encodeNum(code, AddressDirect, 0b111, register, number, 0x83, 0x81) } // CompareRegisterRegister compares a register with a register and sets the status flags in the EFLAGS register. func CompareRegisterRegister(code []byte, registerA cpu.Register, registerB cpu.Register) []byte { - return encode(code, 1, AddressDirect, byte(registerB), byte(registerA), 0x39) + return encode(code, AddressDirect, registerB, registerA, 8, 0x39) } diff --git a/src/build/arch/x64/Move.go b/src/build/arch/x64/Move.go index 4245a4b..3ebf470 100644 --- a/src/build/arch/x64/Move.go +++ b/src/build/arch/x64/Move.go @@ -19,5 +19,5 @@ func MoveRegisterNumber32(code []byte, destination cpu.Register, number uint32) // MoveRegisterRegister64 moves a register value into another register. func MoveRegisterRegister64(code []byte, destination cpu.Register, source cpu.Register) []byte { - return encode(code, 1, AddressDirect, byte(source), byte(destination), 0x89) + return encode(code, AddressDirect, source, destination, 8, 0x89) } diff --git a/src/build/arch/x64/Mul.go b/src/build/arch/x64/Mul.go index 181b40c..b6d7883 100644 --- a/src/build/arch/x64/Mul.go +++ b/src/build/arch/x64/Mul.go @@ -4,10 +4,10 @@ import "git.akyoto.dev/cli/q/src/build/cpu" // MulRegisterNumber multiplies a register with a number. func MulRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, 1, AddressDirect, byte(destination), byte(destination), number, 0x6B, 0x69) + return encodeNum(code, AddressDirect, destination, destination, number, 0x6B, 0x69) } // MulRegisterRegister multiplies a register with another register. func MulRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, 1, AddressDirect, byte(destination), byte(operand), 0x0F, 0xAF) + return encode(code, AddressDirect, destination, operand, 8, 0x0F, 0xAF) } diff --git a/src/build/arch/x64/Store.go b/src/build/arch/x64/Store.go index e2df6a5..caa2e5c 100644 --- a/src/build/arch/x64/Store.go +++ b/src/build/arch/x64/Store.go @@ -7,40 +7,10 @@ import ( ) // StoreNumber stores a number into the memory address included in the given register. -func StoreNumber(code []byte, register cpu.Register, offset byte, byteCount byte, number int) []byte { - if byteCount == 2 { - code = append(code, 0x66) - } +func StoreNumber(code []byte, register cpu.Register, offset byte, numBytes byte, number int) []byte { + code = store(code, 0xC6, 0xC7, register, offset, numBytes, 0b000) - opCode := byte(0xC7) - - if byteCount == 1 { - opCode = 0xC6 - } - - mod := AddressMemory - - if offset != 0 || register == RBP || register == R13 { - mod = AddressMemoryOffset8 - } - - is64Bit := byte(0) - - if byteCount == 8 { - is64Bit = 1 - } - - code = encode(code, is64Bit, mod, 0b000, byte(register), opCode) - - if register == RSP || register == R12 { - code = append(code, SIB(0b00, 0b100, 0b100)) - } - - if mod == AddressMemoryOffset8 { - code = append(code, offset) - } - - switch byteCount { + switch numBytes { case 8, 4: return binary.LittleEndian.AppendUint32(code, uint32(number)) @@ -50,3 +20,39 @@ func StoreNumber(code []byte, register cpu.Register, offset byte, byteCount byte return append(code, byte(number)) } + +// StoreRegister stores the contents of the `source` register into the memory address included in the given register. +func StoreRegister(code []byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { + return store(code, 0x88, 0x89, register, offset, numBytes, source) +} + +// store encodes a write to memory. +func store(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { + if numBytes == 2 { + code = append(code, 0x66) + } + + opCode := opCode32 + + if numBytes == 1 { + opCode = opCode8 + } + + mod := AddressMemory + + if offset != 0 || register == RBP || register == R13 { + mod = AddressMemoryOffset8 + } + + code = encode(code, mod, source, register, numBytes, opCode) + + if register == RSP || register == R12 { + code = append(code, SIB(0b00, 0b100, 0b100)) + } + + if mod == AddressMemoryOffset8 { + code = append(code, offset) + } + + return code +} diff --git a/src/build/arch/x64/Store_test.go b/src/build/arch/x64/Store_test.go index 6d278fb..2d7432a 100644 --- a/src/build/arch/x64/Store_test.go +++ b/src/build/arch/x64/Store_test.go @@ -155,3 +155,151 @@ func TestStoreNumber(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestStoreRegister(t *testing.T) { + usagePatterns := []struct { + RegisterTo cpu.Register + Offset byte + ByteCount byte + RegisterFrom cpu.Register + Code []byte + }{ + // No offset + {x64.RAX, 0, 8, x64.R15, []byte{0x4C, 0x89, 0x38}}, + {x64.RAX, 0, 4, x64.R15, []byte{0x44, 0x89, 0x38}}, + {x64.RAX, 0, 2, x64.R15, []byte{0x66, 0x44, 0x89, 0x38}}, + {x64.RAX, 0, 1, x64.R15, []byte{0x44, 0x88, 0x38}}, + {x64.RCX, 0, 8, x64.R14, []byte{0x4C, 0x89, 0x31}}, + {x64.RCX, 0, 4, x64.R14, []byte{0x44, 0x89, 0x31}}, + {x64.RCX, 0, 2, x64.R14, []byte{0x66, 0x44, 0x89, 0x31}}, + {x64.RCX, 0, 1, x64.R14, []byte{0x44, 0x88, 0x31}}, + {x64.RDX, 0, 8, x64.R13, []byte{0x4C, 0x89, 0x2A}}, + {x64.RDX, 0, 4, x64.R13, []byte{0x44, 0x89, 0x2A}}, + {x64.RDX, 0, 2, x64.R13, []byte{0x66, 0x44, 0x89, 0x2A}}, + {x64.RDX, 0, 1, x64.R13, []byte{0x44, 0x88, 0x2A}}, + {x64.RBX, 0, 8, x64.R12, []byte{0x4C, 0x89, 0x23}}, + {x64.RBX, 0, 4, x64.R12, []byte{0x44, 0x89, 0x23}}, + {x64.RBX, 0, 2, x64.R12, []byte{0x66, 0x44, 0x89, 0x23}}, + {x64.RBX, 0, 1, x64.R12, []byte{0x44, 0x88, 0x23}}, + {x64.RDI, 0, 8, x64.R11, []byte{0x4C, 0x89, 0x1F}}, + {x64.RDI, 0, 4, x64.R11, []byte{0x44, 0x89, 0x1F}}, + {x64.RDI, 0, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x1F}}, + {x64.RDI, 0, 1, x64.R11, []byte{0x44, 0x88, 0x1F}}, + {x64.RSI, 0, 8, x64.R10, []byte{0x4C, 0x89, 0x16}}, + {x64.RSI, 0, 4, x64.R10, []byte{0x44, 0x89, 0x16}}, + {x64.RSI, 0, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x16}}, + {x64.RSI, 0, 1, x64.R10, []byte{0x44, 0x88, 0x16}}, + {x64.RBP, 0, 8, x64.R9, []byte{0x4C, 0x89, 0x4D, 0x00}}, + {x64.RBP, 0, 4, x64.R9, []byte{0x44, 0x89, 0x4D, 0x00}}, + {x64.RBP, 0, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x4D, 0x00}}, + {x64.RBP, 0, 1, x64.R9, []byte{0x44, 0x88, 0x4D, 0x00}}, + {x64.RSP, 0, 8, x64.R8, []byte{0x4C, 0x89, 0x04, 0x24}}, + {x64.RSP, 0, 4, x64.R8, []byte{0x44, 0x89, 0x04, 0x24}}, + {x64.RSP, 0, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x04, 0x24}}, + {x64.RSP, 0, 1, x64.R8, []byte{0x44, 0x88, 0x04, 0x24}}, + {x64.R8, 0, 8, x64.RDI, []byte{0x49, 0x89, 0x38}}, + {x64.R8, 0, 4, x64.RDI, []byte{0x41, 0x89, 0x38}}, + {x64.R8, 0, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x38}}, + {x64.R8, 0, 1, x64.RDI, []byte{0x41, 0x88, 0x38}}, + {x64.R9, 0, 8, x64.RSI, []byte{0x49, 0x89, 0x31}}, + {x64.R9, 0, 4, x64.RSI, []byte{0x41, 0x89, 0x31}}, + {x64.R9, 0, 2, x64.RSI, []byte{0x66, 0x41, 0x89, 0x31}}, + {x64.R9, 0, 1, x64.RSI, []byte{0x41, 0x88, 0x31}}, + {x64.R10, 0, 8, x64.RBP, []byte{0x49, 0x89, 0x2A}}, + {x64.R10, 0, 4, x64.RBP, []byte{0x41, 0x89, 0x2A}}, + {x64.R10, 0, 2, x64.RBP, []byte{0x66, 0x41, 0x89, 0x2A}}, + {x64.R10, 0, 1, x64.RBP, []byte{0x41, 0x88, 0x2A}}, + {x64.R11, 0, 8, x64.RSP, []byte{0x49, 0x89, 0x23}}, + {x64.R11, 0, 4, x64.RSP, []byte{0x41, 0x89, 0x23}}, + {x64.R11, 0, 2, x64.RSP, []byte{0x66, 0x41, 0x89, 0x23}}, + {x64.R11, 0, 1, x64.RSP, []byte{0x41, 0x88, 0x23}}, + {x64.R12, 0, 8, x64.RBX, []byte{0x49, 0x89, 0x1C, 0x24}}, + {x64.R12, 0, 4, x64.RBX, []byte{0x41, 0x89, 0x1C, 0x24}}, + {x64.R12, 0, 2, x64.RBX, []byte{0x66, 0x41, 0x89, 0x1C, 0x24}}, + {x64.R12, 0, 1, x64.RBX, []byte{0x41, 0x88, 0x1C, 0x24}}, + {x64.R13, 0, 8, x64.RDX, []byte{0x49, 0x89, 0x55, 0x00}}, + {x64.R13, 0, 4, x64.RDX, []byte{0x41, 0x89, 0x55, 0x00}}, + {x64.R13, 0, 2, x64.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x00}}, + {x64.R13, 0, 1, x64.RDX, []byte{0x41, 0x88, 0x55, 0x00}}, + {x64.R14, 0, 8, x64.RCX, []byte{0x49, 0x89, 0x0E}}, + {x64.R14, 0, 4, x64.RCX, []byte{0x41, 0x89, 0x0E}}, + {x64.R14, 0, 2, x64.RCX, []byte{0x66, 0x41, 0x89, 0x0E}}, + {x64.R14, 0, 1, x64.RCX, []byte{0x41, 0x88, 0x0E}}, + {x64.R15, 0, 8, x64.RAX, []byte{0x49, 0x89, 0x07}}, + {x64.R15, 0, 4, x64.RAX, []byte{0x41, 0x89, 0x07}}, + {x64.R15, 0, 2, x64.RAX, []byte{0x66, 0x41, 0x89, 0x07}}, + {x64.R15, 0, 1, x64.RAX, []byte{0x41, 0x88, 0x07}}, + + // Offset of 1 + {x64.RAX, 1, 8, x64.R15, []byte{0x4C, 0x89, 0x78, 0x01}}, + {x64.RAX, 1, 4, x64.R15, []byte{0x44, 0x89, 0x78, 0x01}}, + {x64.RAX, 1, 2, x64.R15, []byte{0x66, 0x44, 0x89, 0x78, 0x01}}, + {x64.RAX, 1, 1, x64.R15, []byte{0x44, 0x88, 0x78, 0x01}}, + {x64.RCX, 1, 8, x64.R14, []byte{0x4C, 0x89, 0x71, 0x01}}, + {x64.RCX, 1, 4, x64.R14, []byte{0x44, 0x89, 0x71, 0x01}}, + {x64.RCX, 1, 2, x64.R14, []byte{0x66, 0x44, 0x89, 0x71, 0x01}}, + {x64.RCX, 1, 1, x64.R14, []byte{0x44, 0x88, 0x71, 0x01}}, + {x64.RDX, 1, 8, x64.R13, []byte{0x4C, 0x89, 0x6A, 0x01}}, + {x64.RDX, 1, 4, x64.R13, []byte{0x44, 0x89, 0x6A, 0x01}}, + {x64.RDX, 1, 2, x64.R13, []byte{0x66, 0x44, 0x89, 0x6A, 0x01}}, + {x64.RDX, 1, 1, x64.R13, []byte{0x44, 0x88, 0x6A, 0x01}}, + {x64.RBX, 1, 8, x64.R12, []byte{0x4C, 0x89, 0x63, 0x01}}, + {x64.RBX, 1, 4, x64.R12, []byte{0x44, 0x89, 0x63, 0x01}}, + {x64.RBX, 1, 2, x64.R12, []byte{0x66, 0x44, 0x89, 0x63, 0x01}}, + {x64.RBX, 1, 1, x64.R12, []byte{0x44, 0x88, 0x63, 0x01}}, + {x64.RDI, 1, 8, x64.R11, []byte{0x4C, 0x89, 0x5F, 0x01}}, + {x64.RDI, 1, 4, x64.R11, []byte{0x44, 0x89, 0x5F, 0x01}}, + {x64.RDI, 1, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x5F, 0x01}}, + {x64.RDI, 1, 1, x64.R11, []byte{0x44, 0x88, 0x5F, 0x01}}, + {x64.RSI, 1, 8, x64.R10, []byte{0x4C, 0x89, 0x56, 0x01}}, + {x64.RSI, 1, 4, x64.R10, []byte{0x44, 0x89, 0x56, 0x01}}, + {x64.RSI, 1, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x56, 0x01}}, + {x64.RSI, 1, 1, x64.R10, []byte{0x44, 0x88, 0x56, 0x01}}, + {x64.RBP, 1, 8, x64.R9, []byte{0x4C, 0x89, 0x4D, 0x01}}, + {x64.RBP, 1, 4, x64.R9, []byte{0x44, 0x89, 0x4D, 0x01}}, + {x64.RBP, 1, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x4D, 0x01}}, + {x64.RBP, 1, 1, x64.R9, []byte{0x44, 0x88, 0x4D, 0x01}}, + {x64.RSP, 1, 8, x64.R8, []byte{0x4C, 0x89, 0x44, 0x24, 0x01}}, + {x64.RSP, 1, 4, x64.R8, []byte{0x44, 0x89, 0x44, 0x24, 0x01}}, + {x64.RSP, 1, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x44, 0x24, 0x01}}, + {x64.RSP, 1, 1, x64.R8, []byte{0x44, 0x88, 0x44, 0x24, 01}}, + {x64.R8, 1, 8, x64.RDI, []byte{0x49, 0x89, 0x78, 0x01}}, + {x64.R8, 1, 4, x64.RDI, []byte{0x41, 0x89, 0x78, 0x01}}, + {x64.R8, 1, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x78, 0x01}}, + {x64.R8, 1, 1, x64.RDI, []byte{0x41, 0x88, 0x78, 0x01}}, + {x64.R9, 1, 8, x64.RSI, []byte{0x49, 0x89, 0x71, 0x01}}, + {x64.R9, 1, 4, x64.RSI, []byte{0x41, 0x89, 0x71, 0x01}}, + {x64.R9, 1, 2, x64.RSI, []byte{0x66, 0x41, 0x89, 0x71, 0x01}}, + {x64.R9, 1, 1, x64.RSI, []byte{0x41, 0x88, 0x71, 0x01}}, + {x64.R10, 1, 8, x64.RBP, []byte{0x49, 0x89, 0x6A, 0x01}}, + {x64.R10, 1, 4, x64.RBP, []byte{0x41, 0x89, 0x6A, 0x01}}, + {x64.R10, 1, 2, x64.RBP, []byte{0x66, 0x41, 0x89, 0x6A, 0x01}}, + {x64.R10, 1, 1, x64.RBP, []byte{0x41, 0x88, 0x6A, 0x01}}, + {x64.R11, 1, 8, x64.RSP, []byte{0x49, 0x89, 0x63, 0x01}}, + {x64.R11, 1, 4, x64.RSP, []byte{0x41, 0x89, 0x63, 0x01}}, + {x64.R11, 1, 2, x64.RSP, []byte{0x66, 0x41, 0x89, 0x63, 0x01}}, + {x64.R11, 1, 1, x64.RSP, []byte{0x41, 0x88, 0x63, 0x01}}, + {x64.R12, 1, 8, x64.RBX, []byte{0x49, 0x89, 0x5C, 0x24, 0x01}}, + {x64.R12, 1, 4, x64.RBX, []byte{0x41, 0x89, 0x5C, 0x24, 0x01}}, + {x64.R12, 1, 2, x64.RBX, []byte{0x66, 0x41, 0x89, 0x5C, 0x24, 0x01}}, + {x64.R12, 1, 1, x64.RBX, []byte{0x41, 0x88, 0x5C, 0x24, 01}}, + {x64.R13, 1, 8, x64.RDX, []byte{0x49, 0x89, 0x55, 0x01}}, + {x64.R13, 1, 4, x64.RDX, []byte{0x41, 0x89, 0x55, 0x01}}, + {x64.R13, 1, 2, x64.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x01}}, + {x64.R13, 1, 1, x64.RDX, []byte{0x41, 0x88, 0x55, 0x01}}, + {x64.R14, 1, 8, x64.RCX, []byte{0x49, 0x89, 0x4E, 0x01}}, + {x64.R14, 1, 4, x64.RCX, []byte{0x41, 0x89, 0x4E, 0x01}}, + {x64.R14, 1, 2, x64.RCX, []byte{0x66, 0x41, 0x89, 0x4E, 0x01}}, + {x64.R14, 1, 1, x64.RCX, []byte{0x41, 0x88, 0x4E, 0x01}}, + {x64.R15, 1, 8, x64.RAX, []byte{0x49, 0x89, 0x47, 0x01}}, + {x64.R15, 1, 4, x64.RAX, []byte{0x41, 0x89, 0x47, 0x01}}, + {x64.R15, 1, 2, x64.RAX, []byte{0x66, 0x41, 0x89, 0x47, 0x01}}, + {x64.R15, 1, 1, x64.RAX, []byte{0x41, 0x88, 0x47, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("store %dB [%s+%d], %s", pattern.ByteCount, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) + code := x64.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.ByteCount, pattern.RegisterFrom) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Sub.go b/src/build/arch/x64/Sub.go index ba1ce9b..268fcc6 100644 --- a/src/build/arch/x64/Sub.go +++ b/src/build/arch/x64/Sub.go @@ -6,10 +6,10 @@ import ( // SubRegisterNumber subtracts a number from the given register. func SubRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, 1, AddressDirect, 0b101, byte(destination), number, 0x83, 0x81) + return encodeNum(code, AddressDirect, 0b101, destination, number, 0x83, 0x81) } // SubRegisterRegister subtracts a register value from another register. func SubRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, 1, AddressDirect, byte(operand), byte(destination), 0x29) + return encode(code, AddressDirect, operand, destination, 8, 0x29) } diff --git a/src/build/arch/x64/encode.go b/src/build/arch/x64/encode.go index 10a1c30..606f6e2 100644 --- a/src/build/arch/x64/encode.go +++ b/src/build/arch/x64/encode.go @@ -1,11 +1,18 @@ package x64 +import "git.akyoto.dev/cli/q/src/build/cpu" + // encode is the core function that encodes an instruction. -func encode(code []byte, w byte, mod byte, reg byte, rm byte, opCodes ...byte) []byte { +func encode(code []byte, mod byte, reg cpu.Register, rm cpu.Register, numBytes byte, opCodes ...byte) []byte { + w := byte(0) // Indicates a 64-bit register. r := byte(0) // Extension to the "reg" field in ModRM. x := byte(0) // Extension to the SIB index field. b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). + if numBytes == 8 { + w = 1 + } + if reg > 0b111 { r = 1 reg &= 0b111 @@ -16,12 +23,12 @@ func encode(code []byte, w byte, mod byte, reg byte, rm byte, opCodes ...byte) [ rm &= 0b111 } - if w != 0 || r != 0 || x != 0 || b != 0 { + if w != 0 || r != 0 || x != 0 || b != 0 || (numBytes == 1 && (reg == RSP || reg == RBP || reg == RSI || reg == RDI)) { code = append(code, REX(w, r, x, b)) } code = append(code, opCodes...) - code = append(code, ModRM(mod, reg, rm)) + code = append(code, ModRM(mod, byte(reg), byte(rm))) return code } diff --git a/src/build/arch/x64/encodeNum.go b/src/build/arch/x64/encodeNum.go index 6bcaec8..15efe03 100644 --- a/src/build/arch/x64/encodeNum.go +++ b/src/build/arch/x64/encodeNum.go @@ -1,14 +1,18 @@ package x64 -import "encoding/binary" +import ( + "encoding/binary" + + "git.akyoto.dev/cli/q/src/build/cpu" +) // encodeNum encodes an instruction with up to two registers and a number parameter. -func encodeNum(code []byte, w byte, mod byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { +func encodeNum(code []byte, mod byte, reg cpu.Register, rm cpu.Register, number int, opCode8 byte, opCode32 byte) []byte { if SizeOf(int64(number)) == 1 { - code = encode(code, w, mod, reg, rm, opCode8) + code = encode(code, mod, reg, rm, 8, opCode8) return append(code, byte(number)) } - code = encode(code, w, mod, reg, rm, opCode32) + code = encode(code, mod, reg, rm, 8, opCode32) return binary.LittleEndian.AppendUint32(code, uint32(number)) } From 5dee0777e712edcf471056aa36c92603498eea0c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 20 Jul 2024 15:48:42 +0200 Subject: [PATCH 0331/1012] Implemented x64 memory loads --- src/build/arch/x64/Load.go | 8 ++ src/build/arch/x64/Load_test.go | 157 +++++++++++++++++++++++++++++ src/build/arch/x64/Store.go | 35 +------ src/build/arch/x64/Store_test.go | 108 ++++++++++---------- src/build/arch/x64/memoryAccess.go | 34 +++++++ 5 files changed, 255 insertions(+), 87 deletions(-) create mode 100644 src/build/arch/x64/Load.go create mode 100644 src/build/arch/x64/Load_test.go create mode 100644 src/build/arch/x64/memoryAccess.go diff --git a/src/build/arch/x64/Load.go b/src/build/arch/x64/Load.go new file mode 100644 index 0000000..fbef5b0 --- /dev/null +++ b/src/build/arch/x64/Load.go @@ -0,0 +1,8 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// LoadRegister loads from memory into a register. +func LoadRegister(code []byte, destination cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { + return memoryAccess(code, 0x8A, 0x8B, source, offset, numBytes, destination) +} diff --git a/src/build/arch/x64/Load_test.go b/src/build/arch/x64/Load_test.go new file mode 100644 index 0000000..9cdcc62 --- /dev/null +++ b/src/build/arch/x64/Load_test.go @@ -0,0 +1,157 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestLoadRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Offset byte + NumBytes byte + Code []byte + }{ + // No offset + {x64.RAX, x64.R15, 0, 8, []byte{0x49, 0x8B, 0x07}}, + {x64.RAX, x64.R15, 0, 4, []byte{0x41, 0x8B, 0x07}}, + {x64.RAX, x64.R15, 0, 2, []byte{0x66, 0x41, 0x8B, 0x07}}, + {x64.RAX, x64.R15, 0, 1, []byte{0x41, 0x8A, 0x07}}, + {x64.RCX, x64.R14, 0, 8, []byte{0x49, 0x8B, 0x0E}}, + {x64.RCX, x64.R14, 0, 4, []byte{0x41, 0x8B, 0x0E}}, + {x64.RCX, x64.R14, 0, 2, []byte{0x66, 0x41, 0x8B, 0x0E}}, + {x64.RCX, x64.R14, 0, 1, []byte{0x41, 0x8A, 0x0E}}, + {x64.RDX, x64.R13, 0, 8, []byte{0x49, 0x8B, 0x55, 0x00}}, + {x64.RDX, x64.R13, 0, 4, []byte{0x41, 0x8B, 0x55, 0x00}}, + {x64.RDX, x64.R13, 0, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x00}}, + {x64.RDX, x64.R13, 0, 1, []byte{0x41, 0x8A, 0x55, 0x00}}, + {x64.RBX, x64.R12, 0, 8, []byte{0x49, 0x8B, 0x1C, 0x24}}, + {x64.RBX, x64.R12, 0, 4, []byte{0x41, 0x8B, 0x1C, 0x24}}, + {x64.RBX, x64.R12, 0, 2, []byte{0x66, 0x41, 0x8B, 0x1C, 0x24}}, + {x64.RBX, x64.R12, 0, 1, []byte{0x41, 0x8A, 0x1C, 0x24}}, + {x64.RSP, x64.R11, 0, 8, []byte{0x49, 0x8B, 0x23}}, + {x64.RSP, x64.R11, 0, 4, []byte{0x41, 0x8B, 0x23}}, + {x64.RSP, x64.R11, 0, 2, []byte{0x66, 0x41, 0x8B, 0x23}}, + {x64.RSP, x64.R11, 0, 1, []byte{0x41, 0x8A, 0x23}}, + {x64.RBP, x64.R10, 0, 8, []byte{0x49, 0x8B, 0x2A}}, + {x64.RBP, x64.R10, 0, 4, []byte{0x41, 0x8B, 0x2A}}, + {x64.RBP, x64.R10, 0, 2, []byte{0x66, 0x41, 0x8B, 0x2A}}, + {x64.RBP, x64.R10, 0, 1, []byte{0x41, 0x8A, 0x2A}}, + {x64.RSI, x64.R9, 0, 8, []byte{0x49, 0x8B, 0x31}}, + {x64.RSI, x64.R9, 0, 4, []byte{0x41, 0x8B, 0x31}}, + {x64.RSI, x64.R9, 0, 2, []byte{0x66, 0x41, 0x8B, 0x31}}, + {x64.RSI, x64.R9, 0, 1, []byte{0x41, 0x8A, 0x31}}, + {x64.RDI, x64.R8, 0, 8, []byte{0x49, 0x8B, 0x38}}, + {x64.RDI, x64.R8, 0, 4, []byte{0x41, 0x8B, 0x38}}, + {x64.RDI, x64.R8, 0, 2, []byte{0x66, 0x41, 0x8B, 0x38}}, + {x64.RDI, x64.R8, 0, 1, []byte{0x41, 0x8A, 0x38}}, + {x64.R8, x64.RDI, 0, 8, []byte{0x4C, 0x8B, 0x07}}, + {x64.R8, x64.RDI, 0, 4, []byte{0x44, 0x8B, 0x07}}, + {x64.R8, x64.RDI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x07}}, + {x64.R8, x64.RDI, 0, 1, []byte{0x44, 0x8A, 0x07}}, + {x64.R9, x64.RSI, 0, 8, []byte{0x4C, 0x8B, 0x0E}}, + {x64.R9, x64.RSI, 0, 4, []byte{0x44, 0x8B, 0x0E}}, + {x64.R9, x64.RSI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x0E}}, + {x64.R9, x64.RSI, 0, 1, []byte{0x44, 0x8A, 0x0E}}, + {x64.R10, x64.RBP, 0, 8, []byte{0x4C, 0x8B, 0x55, 0x00}}, + {x64.R10, x64.RBP, 0, 4, []byte{0x44, 0x8B, 0x55, 0x00}}, + {x64.R10, x64.RBP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x00}}, + {x64.R10, x64.RBP, 0, 1, []byte{0x44, 0x8A, 0x55, 0x00}}, + {x64.R11, x64.RSP, 0, 8, []byte{0x4C, 0x8B, 0x1C, 0x24}}, + {x64.R11, x64.RSP, 0, 4, []byte{0x44, 0x8B, 0x1C, 0x24}}, + {x64.R11, x64.RSP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x1C, 0x24}}, + {x64.R11, x64.RSP, 0, 1, []byte{0x44, 0x8A, 0x1C, 0x24}}, + {x64.R12, x64.RBX, 0, 8, []byte{0x4C, 0x8B, 0x23}}, + {x64.R12, x64.RBX, 0, 4, []byte{0x44, 0x8B, 0x23}}, + {x64.R12, x64.RBX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x23}}, + {x64.R12, x64.RBX, 0, 1, []byte{0x44, 0x8A, 0x23}}, + {x64.R13, x64.RDX, 0, 8, []byte{0x4C, 0x8B, 0x2A}}, + {x64.R13, x64.RDX, 0, 4, []byte{0x44, 0x8B, 0x2A}}, + {x64.R13, x64.RDX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x2A}}, + {x64.R13, x64.RDX, 0, 1, []byte{0x44, 0x8A, 0x2A}}, + {x64.R14, x64.RCX, 0, 8, []byte{0x4C, 0x8B, 0x31}}, + {x64.R14, x64.RCX, 0, 4, []byte{0x44, 0x8B, 0x31}}, + {x64.R14, x64.RCX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x31}}, + {x64.R14, x64.RCX, 0, 1, []byte{0x44, 0x8A, 0x31}}, + {x64.R15, x64.RAX, 0, 8, []byte{0x4C, 0x8B, 0x38}}, + {x64.R15, x64.RAX, 0, 4, []byte{0x44, 0x8B, 0x38}}, + {x64.R15, x64.RAX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x38}}, + {x64.R15, x64.RAX, 0, 1, []byte{0x44, 0x8A, 0x38}}, + + // Offset of 1 + {x64.RAX, x64.R15, 1, 8, []byte{0x49, 0x8B, 0x47, 0x01}}, + {x64.RAX, x64.R15, 1, 4, []byte{0x41, 0x8B, 0x47, 0x01}}, + {x64.RAX, x64.R15, 1, 2, []byte{0x66, 0x41, 0x8B, 0x47, 0x01}}, + {x64.RAX, x64.R15, 1, 1, []byte{0x41, 0x8A, 0x47, 0x01}}, + {x64.RCX, x64.R14, 1, 8, []byte{0x49, 0x8B, 0x4E, 0x01}}, + {x64.RCX, x64.R14, 1, 4, []byte{0x41, 0x8B, 0x4E, 0x01}}, + {x64.RCX, x64.R14, 1, 2, []byte{0x66, 0x41, 0x8B, 0x4E, 0x01}}, + {x64.RCX, x64.R14, 1, 1, []byte{0x41, 0x8A, 0x4E, 0x01}}, + {x64.RDX, x64.R13, 1, 8, []byte{0x49, 0x8B, 0x55, 0x01}}, + {x64.RDX, x64.R13, 1, 4, []byte{0x41, 0x8B, 0x55, 0x01}}, + {x64.RDX, x64.R13, 1, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x01}}, + {x64.RDX, x64.R13, 1, 1, []byte{0x41, 0x8A, 0x55, 0x01}}, + {x64.RBX, x64.R12, 1, 8, []byte{0x49, 0x8B, 0x5C, 0x24, 0x01}}, + {x64.RBX, x64.R12, 1, 4, []byte{0x41, 0x8B, 0x5C, 0x24, 0x01}}, + {x64.RBX, x64.R12, 1, 2, []byte{0x66, 0x41, 0x8B, 0x5C, 0x24, 0x01}}, + {x64.RBX, x64.R12, 1, 1, []byte{0x41, 0x8A, 0x5C, 0x24, 0x01}}, + {x64.RSP, x64.R11, 1, 8, []byte{0x49, 0x8B, 0x63, 0x01}}, + {x64.RSP, x64.R11, 1, 4, []byte{0x41, 0x8B, 0x63, 0x01}}, + {x64.RSP, x64.R11, 1, 2, []byte{0x66, 0x41, 0x8B, 0x63, 0x01}}, + {x64.RSP, x64.R11, 1, 1, []byte{0x41, 0x8A, 0x63, 0x01}}, + {x64.RBP, x64.R10, 1, 8, []byte{0x49, 0x8B, 0x6A, 0x01}}, + {x64.RBP, x64.R10, 1, 4, []byte{0x41, 0x8B, 0x6A, 0x01}}, + {x64.RBP, x64.R10, 1, 2, []byte{0x66, 0x41, 0x8B, 0x6A, 0x01}}, + {x64.RBP, x64.R10, 1, 1, []byte{0x41, 0x8A, 0x6A, 0x01}}, + {x64.RSI, x64.R9, 1, 8, []byte{0x49, 0x8B, 0x71, 0x01}}, + {x64.RSI, x64.R9, 1, 4, []byte{0x41, 0x8B, 0x71, 0x01}}, + {x64.RSI, x64.R9, 1, 2, []byte{0x66, 0x41, 0x8B, 0x71, 0x01}}, + {x64.RSI, x64.R9, 1, 1, []byte{0x41, 0x8A, 0x71, 0x01}}, + {x64.RDI, x64.R8, 1, 8, []byte{0x49, 0x8B, 0x78, 0x01}}, + {x64.RDI, x64.R8, 1, 4, []byte{0x41, 0x8B, 0x78, 0x01}}, + {x64.RDI, x64.R8, 1, 2, []byte{0x66, 0x41, 0x8B, 0x78, 0x01}}, + {x64.RDI, x64.R8, 1, 1, []byte{0x41, 0x8A, 0x78, 0x01}}, + {x64.R8, x64.RDI, 1, 8, []byte{0x4C, 0x8B, 0x47, 0x01}}, + {x64.R8, x64.RDI, 1, 4, []byte{0x44, 0x8B, 0x47, 0x01}}, + {x64.R8, x64.RDI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x47, 0x01}}, + {x64.R8, x64.RDI, 1, 1, []byte{0x44, 0x8A, 0x47, 0x01}}, + {x64.R9, x64.RSI, 1, 8, []byte{0x4C, 0x8B, 0x4E, 0x01}}, + {x64.R9, x64.RSI, 1, 4, []byte{0x44, 0x8B, 0x4E, 0x01}}, + {x64.R9, x64.RSI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x4E, 0x01}}, + {x64.R9, x64.RSI, 1, 1, []byte{0x44, 0x8A, 0x4E, 0x01}}, + {x64.R10, x64.RBP, 1, 8, []byte{0x4C, 0x8B, 0x55, 0x01}}, + {x64.R10, x64.RBP, 1, 4, []byte{0x44, 0x8B, 0x55, 0x01}}, + {x64.R10, x64.RBP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x01}}, + {x64.R10, x64.RBP, 1, 1, []byte{0x44, 0x8A, 0x55, 0x01}}, + {x64.R11, x64.RSP, 1, 8, []byte{0x4C, 0x8B, 0x5C, 0x24, 0x01}}, + {x64.R11, x64.RSP, 1, 4, []byte{0x44, 0x8B, 0x5C, 0x24, 0x01}}, + {x64.R11, x64.RSP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x5C, 0x24, 0x01}}, + {x64.R11, x64.RSP, 1, 1, []byte{0x44, 0x8A, 0x5C, 0x24, 0x01}}, + {x64.R12, x64.RBX, 1, 8, []byte{0x4C, 0x8B, 0x63, 0x01}}, + {x64.R12, x64.RBX, 1, 4, []byte{0x44, 0x8B, 0x63, 0x01}}, + {x64.R12, x64.RBX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x63, 0x01}}, + {x64.R12, x64.RBX, 1, 1, []byte{0x44, 0x8A, 0x63, 0x01}}, + {x64.R13, x64.RDX, 1, 8, []byte{0x4C, 0x8B, 0x6A, 0x01}}, + {x64.R13, x64.RDX, 1, 4, []byte{0x44, 0x8B, 0x6A, 0x01}}, + {x64.R13, x64.RDX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x6A, 0x01}}, + {x64.R13, x64.RDX, 1, 1, []byte{0x44, 0x8A, 0x6A, 0x01}}, + {x64.R14, x64.RCX, 1, 8, []byte{0x4C, 0x8B, 0x71, 0x01}}, + {x64.R14, x64.RCX, 1, 4, []byte{0x44, 0x8B, 0x71, 0x01}}, + {x64.R14, x64.RCX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x71, 0x01}}, + {x64.R14, x64.RCX, 1, 1, []byte{0x44, 0x8A, 0x71, 0x01}}, + {x64.R15, x64.RAX, 1, 8, []byte{0x4C, 0x8B, 0x78, 0x01}}, + {x64.R15, x64.RAX, 1, 4, []byte{0x44, 0x8B, 0x78, 0x01}}, + {x64.R15, x64.RAX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x78, 0x01}}, + {x64.R15, x64.RAX, 1, 1, []byte{0x44, 0x8A, 0x78, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("load %dB %s, [%s+%d]", pattern.NumBytes, pattern.Destination, pattern.Source, pattern.Offset) + code := x64.LoadRegister(nil, pattern.Destination, pattern.Offset, pattern.NumBytes, pattern.Source) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Store.go b/src/build/arch/x64/Store.go index caa2e5c..7a37870 100644 --- a/src/build/arch/x64/Store.go +++ b/src/build/arch/x64/Store.go @@ -8,7 +8,7 @@ import ( // StoreNumber stores a number into the memory address included in the given register. func StoreNumber(code []byte, register cpu.Register, offset byte, numBytes byte, number int) []byte { - code = store(code, 0xC6, 0xC7, register, offset, numBytes, 0b000) + code = memoryAccess(code, 0xC6, 0xC7, register, offset, numBytes, 0b000) switch numBytes { case 8, 4: @@ -23,36 +23,5 @@ func StoreNumber(code []byte, register cpu.Register, offset byte, numBytes byte, // StoreRegister stores the contents of the `source` register into the memory address included in the given register. func StoreRegister(code []byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { - return store(code, 0x88, 0x89, register, offset, numBytes, source) -} - -// store encodes a write to memory. -func store(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { - if numBytes == 2 { - code = append(code, 0x66) - } - - opCode := opCode32 - - if numBytes == 1 { - opCode = opCode8 - } - - mod := AddressMemory - - if offset != 0 || register == RBP || register == R13 { - mod = AddressMemoryOffset8 - } - - code = encode(code, mod, source, register, numBytes, opCode) - - if register == RSP || register == R12 { - code = append(code, SIB(0b00, 0b100, 0b100)) - } - - if mod == AddressMemoryOffset8 { - code = append(code, offset) - } - - return code + return memoryAccess(code, 0x88, 0x89, register, offset, numBytes, source) } diff --git a/src/build/arch/x64/Store_test.go b/src/build/arch/x64/Store_test.go index 2d7432a..03f2224 100644 --- a/src/build/arch/x64/Store_test.go +++ b/src/build/arch/x64/Store_test.go @@ -10,11 +10,11 @@ import ( func TestStoreNumber(t *testing.T) { usagePatterns := []struct { - Register cpu.Register - Offset byte - ByteCount byte - Number int - Code []byte + Register cpu.Register + Offset byte + NumBytes byte + Number int + Code []byte }{ // No offset {x64.RAX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, @@ -99,22 +99,22 @@ func TestStoreNumber(t *testing.T) { {x64.RBX, 1, 4, 0x7F, []byte{0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, {x64.RBX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, {x64.RBX, 1, 1, 0x7F, []byte{0xC6, 0x43, 0x01, 0x7F}}, - {x64.RDI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDI, 1, 4, 0x7F, []byte{0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, - {x64.RDI, 1, 1, 0x7F, []byte{0xC6, 0x47, 0x01, 0x7F}}, - {x64.RSI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSI, 1, 4, 0x7F, []byte{0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, - {x64.RSI, 1, 1, 0x7F, []byte{0xC6, 0x46, 0x01, 0x7F}}, - {x64.RBP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBP, 1, 4, 0x7F, []byte{0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, - {x64.RBP, 1, 1, 0x7F, []byte{0xC6, 0x45, 0x01, 0x7F}}, {x64.RSP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, {x64.RSP, 1, 4, 0x7F, []byte{0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, {x64.RSP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, {x64.RSP, 1, 1, 0x7F, []byte{0xC6, 0x44, 0x24, 0x01, 0x7F}}, + {x64.RBP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBP, 1, 4, 0x7F, []byte{0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, + {x64.RBP, 1, 1, 0x7F, []byte{0xC6, 0x45, 0x01, 0x7F}}, + {x64.RSI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSI, 1, 4, 0x7F, []byte{0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, + {x64.RSI, 1, 1, 0x7F, []byte{0xC6, 0x46, 0x01, 0x7F}}, + {x64.RDI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDI, 1, 4, 0x7F, []byte{0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, + {x64.RDI, 1, 1, 0x7F, []byte{0xC6, 0x47, 0x01, 0x7F}}, {x64.R8, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, {x64.R8, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, {x64.R8, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, @@ -150,8 +150,8 @@ func TestStoreNumber(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%d], %d", pattern.ByteCount, pattern.Register, pattern.Offset, pattern.Number) - code := x64.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.ByteCount, pattern.Number) + t.Logf("store %dB [%s+%d], %d", pattern.NumBytes, pattern.Register, pattern.Offset, pattern.Number) + code := x64.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.NumBytes, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } @@ -160,7 +160,7 @@ func TestStoreRegister(t *testing.T) { usagePatterns := []struct { RegisterTo cpu.Register Offset byte - ByteCount byte + NumBytes byte RegisterFrom cpu.Register Code []byte }{ @@ -181,22 +181,22 @@ func TestStoreRegister(t *testing.T) { {x64.RBX, 0, 4, x64.R12, []byte{0x44, 0x89, 0x23}}, {x64.RBX, 0, 2, x64.R12, []byte{0x66, 0x44, 0x89, 0x23}}, {x64.RBX, 0, 1, x64.R12, []byte{0x44, 0x88, 0x23}}, - {x64.RDI, 0, 8, x64.R11, []byte{0x4C, 0x89, 0x1F}}, - {x64.RDI, 0, 4, x64.R11, []byte{0x44, 0x89, 0x1F}}, - {x64.RDI, 0, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x1F}}, - {x64.RDI, 0, 1, x64.R11, []byte{0x44, 0x88, 0x1F}}, - {x64.RSI, 0, 8, x64.R10, []byte{0x4C, 0x89, 0x16}}, - {x64.RSI, 0, 4, x64.R10, []byte{0x44, 0x89, 0x16}}, - {x64.RSI, 0, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x16}}, - {x64.RSI, 0, 1, x64.R10, []byte{0x44, 0x88, 0x16}}, - {x64.RBP, 0, 8, x64.R9, []byte{0x4C, 0x89, 0x4D, 0x00}}, - {x64.RBP, 0, 4, x64.R9, []byte{0x44, 0x89, 0x4D, 0x00}}, - {x64.RBP, 0, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x4D, 0x00}}, - {x64.RBP, 0, 1, x64.R9, []byte{0x44, 0x88, 0x4D, 0x00}}, - {x64.RSP, 0, 8, x64.R8, []byte{0x4C, 0x89, 0x04, 0x24}}, - {x64.RSP, 0, 4, x64.R8, []byte{0x44, 0x89, 0x04, 0x24}}, - {x64.RSP, 0, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x04, 0x24}}, - {x64.RSP, 0, 1, x64.R8, []byte{0x44, 0x88, 0x04, 0x24}}, + {x64.RSP, 0, 8, x64.R11, []byte{0x4C, 0x89, 0x1C, 0x24}}, + {x64.RSP, 0, 4, x64.R11, []byte{0x44, 0x89, 0x1C, 0x24}}, + {x64.RSP, 0, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x1C, 0x24}}, + {x64.RSP, 0, 1, x64.R11, []byte{0x44, 0x88, 0x1C, 0x24}}, + {x64.RBP, 0, 8, x64.R10, []byte{0x4C, 0x89, 0x55, 0x00}}, + {x64.RBP, 0, 4, x64.R10, []byte{0x44, 0x89, 0x55, 0x00}}, + {x64.RBP, 0, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x00}}, + {x64.RBP, 0, 1, x64.R10, []byte{0x44, 0x88, 0x55, 0x00}}, + {x64.RSI, 0, 8, x64.R9, []byte{0x4C, 0x89, 0x0E}}, + {x64.RSI, 0, 4, x64.R9, []byte{0x44, 0x89, 0x0E}}, + {x64.RSI, 0, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x0E}}, + {x64.RSI, 0, 1, x64.R9, []byte{0x44, 0x88, 0x0E}}, + {x64.RDI, 0, 8, x64.R8, []byte{0x4C, 0x89, 0x07}}, + {x64.RDI, 0, 4, x64.R8, []byte{0x44, 0x89, 0x07}}, + {x64.RDI, 0, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x07}}, + {x64.RDI, 0, 1, x64.R8, []byte{0x44, 0x88, 0x07}}, {x64.R8, 0, 8, x64.RDI, []byte{0x49, 0x89, 0x38}}, {x64.R8, 0, 4, x64.RDI, []byte{0x41, 0x89, 0x38}}, {x64.R8, 0, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x38}}, @@ -247,22 +247,22 @@ func TestStoreRegister(t *testing.T) { {x64.RBX, 1, 4, x64.R12, []byte{0x44, 0x89, 0x63, 0x01}}, {x64.RBX, 1, 2, x64.R12, []byte{0x66, 0x44, 0x89, 0x63, 0x01}}, {x64.RBX, 1, 1, x64.R12, []byte{0x44, 0x88, 0x63, 0x01}}, - {x64.RDI, 1, 8, x64.R11, []byte{0x4C, 0x89, 0x5F, 0x01}}, - {x64.RDI, 1, 4, x64.R11, []byte{0x44, 0x89, 0x5F, 0x01}}, - {x64.RDI, 1, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x5F, 0x01}}, - {x64.RDI, 1, 1, x64.R11, []byte{0x44, 0x88, 0x5F, 0x01}}, - {x64.RSI, 1, 8, x64.R10, []byte{0x4C, 0x89, 0x56, 0x01}}, - {x64.RSI, 1, 4, x64.R10, []byte{0x44, 0x89, 0x56, 0x01}}, - {x64.RSI, 1, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x56, 0x01}}, - {x64.RSI, 1, 1, x64.R10, []byte{0x44, 0x88, 0x56, 0x01}}, - {x64.RBP, 1, 8, x64.R9, []byte{0x4C, 0x89, 0x4D, 0x01}}, - {x64.RBP, 1, 4, x64.R9, []byte{0x44, 0x89, 0x4D, 0x01}}, - {x64.RBP, 1, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x4D, 0x01}}, - {x64.RBP, 1, 1, x64.R9, []byte{0x44, 0x88, 0x4D, 0x01}}, - {x64.RSP, 1, 8, x64.R8, []byte{0x4C, 0x89, 0x44, 0x24, 0x01}}, - {x64.RSP, 1, 4, x64.R8, []byte{0x44, 0x89, 0x44, 0x24, 0x01}}, - {x64.RSP, 1, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x44, 0x24, 0x01}}, - {x64.RSP, 1, 1, x64.R8, []byte{0x44, 0x88, 0x44, 0x24, 01}}, + {x64.RSP, 1, 8, x64.R11, []byte{0x4C, 0x89, 0x5C, 0x24, 0x01}}, + {x64.RSP, 1, 4, x64.R11, []byte{0x44, 0x89, 0x5C, 0x24, 0x01}}, + {x64.RSP, 1, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x5C, 0x24, 0x01}}, + {x64.RSP, 1, 1, x64.R11, []byte{0x44, 0x88, 0x5C, 0x24, 0x01}}, + {x64.RBP, 1, 8, x64.R10, []byte{0x4C, 0x89, 0x55, 0x01}}, + {x64.RBP, 1, 4, x64.R10, []byte{0x44, 0x89, 0x55, 0x01}}, + {x64.RBP, 1, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x01}}, + {x64.RBP, 1, 1, x64.R10, []byte{0x44, 0x88, 0x55, 0x01}}, + {x64.RSI, 1, 8, x64.R9, []byte{0x4C, 0x89, 0x4E, 0x01}}, + {x64.RSI, 1, 4, x64.R9, []byte{0x44, 0x89, 0x4E, 0x01}}, + {x64.RSI, 1, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x4E, 0x01}}, + {x64.RSI, 1, 1, x64.R9, []byte{0x44, 0x88, 0x4E, 0x01}}, + {x64.RDI, 1, 8, x64.R8, []byte{0x4C, 0x89, 0x47, 0x01}}, + {x64.RDI, 1, 4, x64.R8, []byte{0x44, 0x89, 0x47, 0x01}}, + {x64.RDI, 1, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x47, 0x01}}, + {x64.RDI, 1, 1, x64.R8, []byte{0x44, 0x88, 0x47, 0x01}}, {x64.R8, 1, 8, x64.RDI, []byte{0x49, 0x89, 0x78, 0x01}}, {x64.R8, 1, 4, x64.RDI, []byte{0x41, 0x89, 0x78, 0x01}}, {x64.R8, 1, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x78, 0x01}}, @@ -298,8 +298,8 @@ func TestStoreRegister(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%d], %s", pattern.ByteCount, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) - code := x64.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.ByteCount, pattern.RegisterFrom) + t.Logf("store %dB [%s+%d], %s", pattern.NumBytes, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) + code := x64.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.NumBytes, pattern.RegisterFrom) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/memoryAccess.go b/src/build/arch/x64/memoryAccess.go new file mode 100644 index 0000000..b8cafa5 --- /dev/null +++ b/src/build/arch/x64/memoryAccess.go @@ -0,0 +1,34 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// memoryAccess encodes a memory access. +func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { + if numBytes == 2 { + code = append(code, 0x66) + } + + opCode := opCode32 + + if numBytes == 1 { + opCode = opCode8 + } + + mod := AddressMemory + + if offset != 0 || register == RBP || register == R13 { + mod = AddressMemoryOffset8 + } + + code = encode(code, mod, source, register, numBytes, opCode) + + if register == RSP || register == R12 { + code = append(code, SIB(Scale1, 0b100, 0b100)) + } + + if mod == AddressMemoryOffset8 { + code = append(code, offset) + } + + return code +} From d35c07ed1cf7f50a8733498be0cb13e7beebdb89 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 20 Jul 2024 15:48:42 +0200 Subject: [PATCH 0332/1012] Implemented x64 memory loads --- src/build/arch/x64/Load.go | 8 ++ src/build/arch/x64/Load_test.go | 157 +++++++++++++++++++++++++++++ src/build/arch/x64/Store.go | 35 +------ src/build/arch/x64/Store_test.go | 108 ++++++++++---------- src/build/arch/x64/memoryAccess.go | 34 +++++++ 5 files changed, 255 insertions(+), 87 deletions(-) create mode 100644 src/build/arch/x64/Load.go create mode 100644 src/build/arch/x64/Load_test.go create mode 100644 src/build/arch/x64/memoryAccess.go diff --git a/src/build/arch/x64/Load.go b/src/build/arch/x64/Load.go new file mode 100644 index 0000000..fbef5b0 --- /dev/null +++ b/src/build/arch/x64/Load.go @@ -0,0 +1,8 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// LoadRegister loads from memory into a register. +func LoadRegister(code []byte, destination cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { + return memoryAccess(code, 0x8A, 0x8B, source, offset, numBytes, destination) +} diff --git a/src/build/arch/x64/Load_test.go b/src/build/arch/x64/Load_test.go new file mode 100644 index 0000000..9cdcc62 --- /dev/null +++ b/src/build/arch/x64/Load_test.go @@ -0,0 +1,157 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestLoadRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Offset byte + NumBytes byte + Code []byte + }{ + // No offset + {x64.RAX, x64.R15, 0, 8, []byte{0x49, 0x8B, 0x07}}, + {x64.RAX, x64.R15, 0, 4, []byte{0x41, 0x8B, 0x07}}, + {x64.RAX, x64.R15, 0, 2, []byte{0x66, 0x41, 0x8B, 0x07}}, + {x64.RAX, x64.R15, 0, 1, []byte{0x41, 0x8A, 0x07}}, + {x64.RCX, x64.R14, 0, 8, []byte{0x49, 0x8B, 0x0E}}, + {x64.RCX, x64.R14, 0, 4, []byte{0x41, 0x8B, 0x0E}}, + {x64.RCX, x64.R14, 0, 2, []byte{0x66, 0x41, 0x8B, 0x0E}}, + {x64.RCX, x64.R14, 0, 1, []byte{0x41, 0x8A, 0x0E}}, + {x64.RDX, x64.R13, 0, 8, []byte{0x49, 0x8B, 0x55, 0x00}}, + {x64.RDX, x64.R13, 0, 4, []byte{0x41, 0x8B, 0x55, 0x00}}, + {x64.RDX, x64.R13, 0, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x00}}, + {x64.RDX, x64.R13, 0, 1, []byte{0x41, 0x8A, 0x55, 0x00}}, + {x64.RBX, x64.R12, 0, 8, []byte{0x49, 0x8B, 0x1C, 0x24}}, + {x64.RBX, x64.R12, 0, 4, []byte{0x41, 0x8B, 0x1C, 0x24}}, + {x64.RBX, x64.R12, 0, 2, []byte{0x66, 0x41, 0x8B, 0x1C, 0x24}}, + {x64.RBX, x64.R12, 0, 1, []byte{0x41, 0x8A, 0x1C, 0x24}}, + {x64.RSP, x64.R11, 0, 8, []byte{0x49, 0x8B, 0x23}}, + {x64.RSP, x64.R11, 0, 4, []byte{0x41, 0x8B, 0x23}}, + {x64.RSP, x64.R11, 0, 2, []byte{0x66, 0x41, 0x8B, 0x23}}, + {x64.RSP, x64.R11, 0, 1, []byte{0x41, 0x8A, 0x23}}, + {x64.RBP, x64.R10, 0, 8, []byte{0x49, 0x8B, 0x2A}}, + {x64.RBP, x64.R10, 0, 4, []byte{0x41, 0x8B, 0x2A}}, + {x64.RBP, x64.R10, 0, 2, []byte{0x66, 0x41, 0x8B, 0x2A}}, + {x64.RBP, x64.R10, 0, 1, []byte{0x41, 0x8A, 0x2A}}, + {x64.RSI, x64.R9, 0, 8, []byte{0x49, 0x8B, 0x31}}, + {x64.RSI, x64.R9, 0, 4, []byte{0x41, 0x8B, 0x31}}, + {x64.RSI, x64.R9, 0, 2, []byte{0x66, 0x41, 0x8B, 0x31}}, + {x64.RSI, x64.R9, 0, 1, []byte{0x41, 0x8A, 0x31}}, + {x64.RDI, x64.R8, 0, 8, []byte{0x49, 0x8B, 0x38}}, + {x64.RDI, x64.R8, 0, 4, []byte{0x41, 0x8B, 0x38}}, + {x64.RDI, x64.R8, 0, 2, []byte{0x66, 0x41, 0x8B, 0x38}}, + {x64.RDI, x64.R8, 0, 1, []byte{0x41, 0x8A, 0x38}}, + {x64.R8, x64.RDI, 0, 8, []byte{0x4C, 0x8B, 0x07}}, + {x64.R8, x64.RDI, 0, 4, []byte{0x44, 0x8B, 0x07}}, + {x64.R8, x64.RDI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x07}}, + {x64.R8, x64.RDI, 0, 1, []byte{0x44, 0x8A, 0x07}}, + {x64.R9, x64.RSI, 0, 8, []byte{0x4C, 0x8B, 0x0E}}, + {x64.R9, x64.RSI, 0, 4, []byte{0x44, 0x8B, 0x0E}}, + {x64.R9, x64.RSI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x0E}}, + {x64.R9, x64.RSI, 0, 1, []byte{0x44, 0x8A, 0x0E}}, + {x64.R10, x64.RBP, 0, 8, []byte{0x4C, 0x8B, 0x55, 0x00}}, + {x64.R10, x64.RBP, 0, 4, []byte{0x44, 0x8B, 0x55, 0x00}}, + {x64.R10, x64.RBP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x00}}, + {x64.R10, x64.RBP, 0, 1, []byte{0x44, 0x8A, 0x55, 0x00}}, + {x64.R11, x64.RSP, 0, 8, []byte{0x4C, 0x8B, 0x1C, 0x24}}, + {x64.R11, x64.RSP, 0, 4, []byte{0x44, 0x8B, 0x1C, 0x24}}, + {x64.R11, x64.RSP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x1C, 0x24}}, + {x64.R11, x64.RSP, 0, 1, []byte{0x44, 0x8A, 0x1C, 0x24}}, + {x64.R12, x64.RBX, 0, 8, []byte{0x4C, 0x8B, 0x23}}, + {x64.R12, x64.RBX, 0, 4, []byte{0x44, 0x8B, 0x23}}, + {x64.R12, x64.RBX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x23}}, + {x64.R12, x64.RBX, 0, 1, []byte{0x44, 0x8A, 0x23}}, + {x64.R13, x64.RDX, 0, 8, []byte{0x4C, 0x8B, 0x2A}}, + {x64.R13, x64.RDX, 0, 4, []byte{0x44, 0x8B, 0x2A}}, + {x64.R13, x64.RDX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x2A}}, + {x64.R13, x64.RDX, 0, 1, []byte{0x44, 0x8A, 0x2A}}, + {x64.R14, x64.RCX, 0, 8, []byte{0x4C, 0x8B, 0x31}}, + {x64.R14, x64.RCX, 0, 4, []byte{0x44, 0x8B, 0x31}}, + {x64.R14, x64.RCX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x31}}, + {x64.R14, x64.RCX, 0, 1, []byte{0x44, 0x8A, 0x31}}, + {x64.R15, x64.RAX, 0, 8, []byte{0x4C, 0x8B, 0x38}}, + {x64.R15, x64.RAX, 0, 4, []byte{0x44, 0x8B, 0x38}}, + {x64.R15, x64.RAX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x38}}, + {x64.R15, x64.RAX, 0, 1, []byte{0x44, 0x8A, 0x38}}, + + // Offset of 1 + {x64.RAX, x64.R15, 1, 8, []byte{0x49, 0x8B, 0x47, 0x01}}, + {x64.RAX, x64.R15, 1, 4, []byte{0x41, 0x8B, 0x47, 0x01}}, + {x64.RAX, x64.R15, 1, 2, []byte{0x66, 0x41, 0x8B, 0x47, 0x01}}, + {x64.RAX, x64.R15, 1, 1, []byte{0x41, 0x8A, 0x47, 0x01}}, + {x64.RCX, x64.R14, 1, 8, []byte{0x49, 0x8B, 0x4E, 0x01}}, + {x64.RCX, x64.R14, 1, 4, []byte{0x41, 0x8B, 0x4E, 0x01}}, + {x64.RCX, x64.R14, 1, 2, []byte{0x66, 0x41, 0x8B, 0x4E, 0x01}}, + {x64.RCX, x64.R14, 1, 1, []byte{0x41, 0x8A, 0x4E, 0x01}}, + {x64.RDX, x64.R13, 1, 8, []byte{0x49, 0x8B, 0x55, 0x01}}, + {x64.RDX, x64.R13, 1, 4, []byte{0x41, 0x8B, 0x55, 0x01}}, + {x64.RDX, x64.R13, 1, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x01}}, + {x64.RDX, x64.R13, 1, 1, []byte{0x41, 0x8A, 0x55, 0x01}}, + {x64.RBX, x64.R12, 1, 8, []byte{0x49, 0x8B, 0x5C, 0x24, 0x01}}, + {x64.RBX, x64.R12, 1, 4, []byte{0x41, 0x8B, 0x5C, 0x24, 0x01}}, + {x64.RBX, x64.R12, 1, 2, []byte{0x66, 0x41, 0x8B, 0x5C, 0x24, 0x01}}, + {x64.RBX, x64.R12, 1, 1, []byte{0x41, 0x8A, 0x5C, 0x24, 0x01}}, + {x64.RSP, x64.R11, 1, 8, []byte{0x49, 0x8B, 0x63, 0x01}}, + {x64.RSP, x64.R11, 1, 4, []byte{0x41, 0x8B, 0x63, 0x01}}, + {x64.RSP, x64.R11, 1, 2, []byte{0x66, 0x41, 0x8B, 0x63, 0x01}}, + {x64.RSP, x64.R11, 1, 1, []byte{0x41, 0x8A, 0x63, 0x01}}, + {x64.RBP, x64.R10, 1, 8, []byte{0x49, 0x8B, 0x6A, 0x01}}, + {x64.RBP, x64.R10, 1, 4, []byte{0x41, 0x8B, 0x6A, 0x01}}, + {x64.RBP, x64.R10, 1, 2, []byte{0x66, 0x41, 0x8B, 0x6A, 0x01}}, + {x64.RBP, x64.R10, 1, 1, []byte{0x41, 0x8A, 0x6A, 0x01}}, + {x64.RSI, x64.R9, 1, 8, []byte{0x49, 0x8B, 0x71, 0x01}}, + {x64.RSI, x64.R9, 1, 4, []byte{0x41, 0x8B, 0x71, 0x01}}, + {x64.RSI, x64.R9, 1, 2, []byte{0x66, 0x41, 0x8B, 0x71, 0x01}}, + {x64.RSI, x64.R9, 1, 1, []byte{0x41, 0x8A, 0x71, 0x01}}, + {x64.RDI, x64.R8, 1, 8, []byte{0x49, 0x8B, 0x78, 0x01}}, + {x64.RDI, x64.R8, 1, 4, []byte{0x41, 0x8B, 0x78, 0x01}}, + {x64.RDI, x64.R8, 1, 2, []byte{0x66, 0x41, 0x8B, 0x78, 0x01}}, + {x64.RDI, x64.R8, 1, 1, []byte{0x41, 0x8A, 0x78, 0x01}}, + {x64.R8, x64.RDI, 1, 8, []byte{0x4C, 0x8B, 0x47, 0x01}}, + {x64.R8, x64.RDI, 1, 4, []byte{0x44, 0x8B, 0x47, 0x01}}, + {x64.R8, x64.RDI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x47, 0x01}}, + {x64.R8, x64.RDI, 1, 1, []byte{0x44, 0x8A, 0x47, 0x01}}, + {x64.R9, x64.RSI, 1, 8, []byte{0x4C, 0x8B, 0x4E, 0x01}}, + {x64.R9, x64.RSI, 1, 4, []byte{0x44, 0x8B, 0x4E, 0x01}}, + {x64.R9, x64.RSI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x4E, 0x01}}, + {x64.R9, x64.RSI, 1, 1, []byte{0x44, 0x8A, 0x4E, 0x01}}, + {x64.R10, x64.RBP, 1, 8, []byte{0x4C, 0x8B, 0x55, 0x01}}, + {x64.R10, x64.RBP, 1, 4, []byte{0x44, 0x8B, 0x55, 0x01}}, + {x64.R10, x64.RBP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x01}}, + {x64.R10, x64.RBP, 1, 1, []byte{0x44, 0x8A, 0x55, 0x01}}, + {x64.R11, x64.RSP, 1, 8, []byte{0x4C, 0x8B, 0x5C, 0x24, 0x01}}, + {x64.R11, x64.RSP, 1, 4, []byte{0x44, 0x8B, 0x5C, 0x24, 0x01}}, + {x64.R11, x64.RSP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x5C, 0x24, 0x01}}, + {x64.R11, x64.RSP, 1, 1, []byte{0x44, 0x8A, 0x5C, 0x24, 0x01}}, + {x64.R12, x64.RBX, 1, 8, []byte{0x4C, 0x8B, 0x63, 0x01}}, + {x64.R12, x64.RBX, 1, 4, []byte{0x44, 0x8B, 0x63, 0x01}}, + {x64.R12, x64.RBX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x63, 0x01}}, + {x64.R12, x64.RBX, 1, 1, []byte{0x44, 0x8A, 0x63, 0x01}}, + {x64.R13, x64.RDX, 1, 8, []byte{0x4C, 0x8B, 0x6A, 0x01}}, + {x64.R13, x64.RDX, 1, 4, []byte{0x44, 0x8B, 0x6A, 0x01}}, + {x64.R13, x64.RDX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x6A, 0x01}}, + {x64.R13, x64.RDX, 1, 1, []byte{0x44, 0x8A, 0x6A, 0x01}}, + {x64.R14, x64.RCX, 1, 8, []byte{0x4C, 0x8B, 0x71, 0x01}}, + {x64.R14, x64.RCX, 1, 4, []byte{0x44, 0x8B, 0x71, 0x01}}, + {x64.R14, x64.RCX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x71, 0x01}}, + {x64.R14, x64.RCX, 1, 1, []byte{0x44, 0x8A, 0x71, 0x01}}, + {x64.R15, x64.RAX, 1, 8, []byte{0x4C, 0x8B, 0x78, 0x01}}, + {x64.R15, x64.RAX, 1, 4, []byte{0x44, 0x8B, 0x78, 0x01}}, + {x64.R15, x64.RAX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x78, 0x01}}, + {x64.R15, x64.RAX, 1, 1, []byte{0x44, 0x8A, 0x78, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("load %dB %s, [%s+%d]", pattern.NumBytes, pattern.Destination, pattern.Source, pattern.Offset) + code := x64.LoadRegister(nil, pattern.Destination, pattern.Offset, pattern.NumBytes, pattern.Source) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Store.go b/src/build/arch/x64/Store.go index caa2e5c..7a37870 100644 --- a/src/build/arch/x64/Store.go +++ b/src/build/arch/x64/Store.go @@ -8,7 +8,7 @@ import ( // StoreNumber stores a number into the memory address included in the given register. func StoreNumber(code []byte, register cpu.Register, offset byte, numBytes byte, number int) []byte { - code = store(code, 0xC6, 0xC7, register, offset, numBytes, 0b000) + code = memoryAccess(code, 0xC6, 0xC7, register, offset, numBytes, 0b000) switch numBytes { case 8, 4: @@ -23,36 +23,5 @@ func StoreNumber(code []byte, register cpu.Register, offset byte, numBytes byte, // StoreRegister stores the contents of the `source` register into the memory address included in the given register. func StoreRegister(code []byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { - return store(code, 0x88, 0x89, register, offset, numBytes, source) -} - -// store encodes a write to memory. -func store(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { - if numBytes == 2 { - code = append(code, 0x66) - } - - opCode := opCode32 - - if numBytes == 1 { - opCode = opCode8 - } - - mod := AddressMemory - - if offset != 0 || register == RBP || register == R13 { - mod = AddressMemoryOffset8 - } - - code = encode(code, mod, source, register, numBytes, opCode) - - if register == RSP || register == R12 { - code = append(code, SIB(0b00, 0b100, 0b100)) - } - - if mod == AddressMemoryOffset8 { - code = append(code, offset) - } - - return code + return memoryAccess(code, 0x88, 0x89, register, offset, numBytes, source) } diff --git a/src/build/arch/x64/Store_test.go b/src/build/arch/x64/Store_test.go index 2d7432a..03f2224 100644 --- a/src/build/arch/x64/Store_test.go +++ b/src/build/arch/x64/Store_test.go @@ -10,11 +10,11 @@ import ( func TestStoreNumber(t *testing.T) { usagePatterns := []struct { - Register cpu.Register - Offset byte - ByteCount byte - Number int - Code []byte + Register cpu.Register + Offset byte + NumBytes byte + Number int + Code []byte }{ // No offset {x64.RAX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, @@ -99,22 +99,22 @@ func TestStoreNumber(t *testing.T) { {x64.RBX, 1, 4, 0x7F, []byte{0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, {x64.RBX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, {x64.RBX, 1, 1, 0x7F, []byte{0xC6, 0x43, 0x01, 0x7F}}, - {x64.RDI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDI, 1, 4, 0x7F, []byte{0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, - {x64.RDI, 1, 1, 0x7F, []byte{0xC6, 0x47, 0x01, 0x7F}}, - {x64.RSI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSI, 1, 4, 0x7F, []byte{0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, - {x64.RSI, 1, 1, 0x7F, []byte{0xC6, 0x46, 0x01, 0x7F}}, - {x64.RBP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBP, 1, 4, 0x7F, []byte{0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, - {x64.RBP, 1, 1, 0x7F, []byte{0xC6, 0x45, 0x01, 0x7F}}, {x64.RSP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, {x64.RSP, 1, 4, 0x7F, []byte{0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, {x64.RSP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, {x64.RSP, 1, 1, 0x7F, []byte{0xC6, 0x44, 0x24, 0x01, 0x7F}}, + {x64.RBP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBP, 1, 4, 0x7F, []byte{0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, + {x64.RBP, 1, 1, 0x7F, []byte{0xC6, 0x45, 0x01, 0x7F}}, + {x64.RSI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSI, 1, 4, 0x7F, []byte{0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, + {x64.RSI, 1, 1, 0x7F, []byte{0xC6, 0x46, 0x01, 0x7F}}, + {x64.RDI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDI, 1, 4, 0x7F, []byte{0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, + {x64.RDI, 1, 1, 0x7F, []byte{0xC6, 0x47, 0x01, 0x7F}}, {x64.R8, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, {x64.R8, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, {x64.R8, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, @@ -150,8 +150,8 @@ func TestStoreNumber(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%d], %d", pattern.ByteCount, pattern.Register, pattern.Offset, pattern.Number) - code := x64.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.ByteCount, pattern.Number) + t.Logf("store %dB [%s+%d], %d", pattern.NumBytes, pattern.Register, pattern.Offset, pattern.Number) + code := x64.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.NumBytes, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } @@ -160,7 +160,7 @@ func TestStoreRegister(t *testing.T) { usagePatterns := []struct { RegisterTo cpu.Register Offset byte - ByteCount byte + NumBytes byte RegisterFrom cpu.Register Code []byte }{ @@ -181,22 +181,22 @@ func TestStoreRegister(t *testing.T) { {x64.RBX, 0, 4, x64.R12, []byte{0x44, 0x89, 0x23}}, {x64.RBX, 0, 2, x64.R12, []byte{0x66, 0x44, 0x89, 0x23}}, {x64.RBX, 0, 1, x64.R12, []byte{0x44, 0x88, 0x23}}, - {x64.RDI, 0, 8, x64.R11, []byte{0x4C, 0x89, 0x1F}}, - {x64.RDI, 0, 4, x64.R11, []byte{0x44, 0x89, 0x1F}}, - {x64.RDI, 0, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x1F}}, - {x64.RDI, 0, 1, x64.R11, []byte{0x44, 0x88, 0x1F}}, - {x64.RSI, 0, 8, x64.R10, []byte{0x4C, 0x89, 0x16}}, - {x64.RSI, 0, 4, x64.R10, []byte{0x44, 0x89, 0x16}}, - {x64.RSI, 0, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x16}}, - {x64.RSI, 0, 1, x64.R10, []byte{0x44, 0x88, 0x16}}, - {x64.RBP, 0, 8, x64.R9, []byte{0x4C, 0x89, 0x4D, 0x00}}, - {x64.RBP, 0, 4, x64.R9, []byte{0x44, 0x89, 0x4D, 0x00}}, - {x64.RBP, 0, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x4D, 0x00}}, - {x64.RBP, 0, 1, x64.R9, []byte{0x44, 0x88, 0x4D, 0x00}}, - {x64.RSP, 0, 8, x64.R8, []byte{0x4C, 0x89, 0x04, 0x24}}, - {x64.RSP, 0, 4, x64.R8, []byte{0x44, 0x89, 0x04, 0x24}}, - {x64.RSP, 0, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x04, 0x24}}, - {x64.RSP, 0, 1, x64.R8, []byte{0x44, 0x88, 0x04, 0x24}}, + {x64.RSP, 0, 8, x64.R11, []byte{0x4C, 0x89, 0x1C, 0x24}}, + {x64.RSP, 0, 4, x64.R11, []byte{0x44, 0x89, 0x1C, 0x24}}, + {x64.RSP, 0, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x1C, 0x24}}, + {x64.RSP, 0, 1, x64.R11, []byte{0x44, 0x88, 0x1C, 0x24}}, + {x64.RBP, 0, 8, x64.R10, []byte{0x4C, 0x89, 0x55, 0x00}}, + {x64.RBP, 0, 4, x64.R10, []byte{0x44, 0x89, 0x55, 0x00}}, + {x64.RBP, 0, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x00}}, + {x64.RBP, 0, 1, x64.R10, []byte{0x44, 0x88, 0x55, 0x00}}, + {x64.RSI, 0, 8, x64.R9, []byte{0x4C, 0x89, 0x0E}}, + {x64.RSI, 0, 4, x64.R9, []byte{0x44, 0x89, 0x0E}}, + {x64.RSI, 0, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x0E}}, + {x64.RSI, 0, 1, x64.R9, []byte{0x44, 0x88, 0x0E}}, + {x64.RDI, 0, 8, x64.R8, []byte{0x4C, 0x89, 0x07}}, + {x64.RDI, 0, 4, x64.R8, []byte{0x44, 0x89, 0x07}}, + {x64.RDI, 0, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x07}}, + {x64.RDI, 0, 1, x64.R8, []byte{0x44, 0x88, 0x07}}, {x64.R8, 0, 8, x64.RDI, []byte{0x49, 0x89, 0x38}}, {x64.R8, 0, 4, x64.RDI, []byte{0x41, 0x89, 0x38}}, {x64.R8, 0, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x38}}, @@ -247,22 +247,22 @@ func TestStoreRegister(t *testing.T) { {x64.RBX, 1, 4, x64.R12, []byte{0x44, 0x89, 0x63, 0x01}}, {x64.RBX, 1, 2, x64.R12, []byte{0x66, 0x44, 0x89, 0x63, 0x01}}, {x64.RBX, 1, 1, x64.R12, []byte{0x44, 0x88, 0x63, 0x01}}, - {x64.RDI, 1, 8, x64.R11, []byte{0x4C, 0x89, 0x5F, 0x01}}, - {x64.RDI, 1, 4, x64.R11, []byte{0x44, 0x89, 0x5F, 0x01}}, - {x64.RDI, 1, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x5F, 0x01}}, - {x64.RDI, 1, 1, x64.R11, []byte{0x44, 0x88, 0x5F, 0x01}}, - {x64.RSI, 1, 8, x64.R10, []byte{0x4C, 0x89, 0x56, 0x01}}, - {x64.RSI, 1, 4, x64.R10, []byte{0x44, 0x89, 0x56, 0x01}}, - {x64.RSI, 1, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x56, 0x01}}, - {x64.RSI, 1, 1, x64.R10, []byte{0x44, 0x88, 0x56, 0x01}}, - {x64.RBP, 1, 8, x64.R9, []byte{0x4C, 0x89, 0x4D, 0x01}}, - {x64.RBP, 1, 4, x64.R9, []byte{0x44, 0x89, 0x4D, 0x01}}, - {x64.RBP, 1, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x4D, 0x01}}, - {x64.RBP, 1, 1, x64.R9, []byte{0x44, 0x88, 0x4D, 0x01}}, - {x64.RSP, 1, 8, x64.R8, []byte{0x4C, 0x89, 0x44, 0x24, 0x01}}, - {x64.RSP, 1, 4, x64.R8, []byte{0x44, 0x89, 0x44, 0x24, 0x01}}, - {x64.RSP, 1, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x44, 0x24, 0x01}}, - {x64.RSP, 1, 1, x64.R8, []byte{0x44, 0x88, 0x44, 0x24, 01}}, + {x64.RSP, 1, 8, x64.R11, []byte{0x4C, 0x89, 0x5C, 0x24, 0x01}}, + {x64.RSP, 1, 4, x64.R11, []byte{0x44, 0x89, 0x5C, 0x24, 0x01}}, + {x64.RSP, 1, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x5C, 0x24, 0x01}}, + {x64.RSP, 1, 1, x64.R11, []byte{0x44, 0x88, 0x5C, 0x24, 0x01}}, + {x64.RBP, 1, 8, x64.R10, []byte{0x4C, 0x89, 0x55, 0x01}}, + {x64.RBP, 1, 4, x64.R10, []byte{0x44, 0x89, 0x55, 0x01}}, + {x64.RBP, 1, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x01}}, + {x64.RBP, 1, 1, x64.R10, []byte{0x44, 0x88, 0x55, 0x01}}, + {x64.RSI, 1, 8, x64.R9, []byte{0x4C, 0x89, 0x4E, 0x01}}, + {x64.RSI, 1, 4, x64.R9, []byte{0x44, 0x89, 0x4E, 0x01}}, + {x64.RSI, 1, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x4E, 0x01}}, + {x64.RSI, 1, 1, x64.R9, []byte{0x44, 0x88, 0x4E, 0x01}}, + {x64.RDI, 1, 8, x64.R8, []byte{0x4C, 0x89, 0x47, 0x01}}, + {x64.RDI, 1, 4, x64.R8, []byte{0x44, 0x89, 0x47, 0x01}}, + {x64.RDI, 1, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x47, 0x01}}, + {x64.RDI, 1, 1, x64.R8, []byte{0x44, 0x88, 0x47, 0x01}}, {x64.R8, 1, 8, x64.RDI, []byte{0x49, 0x89, 0x78, 0x01}}, {x64.R8, 1, 4, x64.RDI, []byte{0x41, 0x89, 0x78, 0x01}}, {x64.R8, 1, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x78, 0x01}}, @@ -298,8 +298,8 @@ func TestStoreRegister(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%d], %s", pattern.ByteCount, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) - code := x64.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.ByteCount, pattern.RegisterFrom) + t.Logf("store %dB [%s+%d], %s", pattern.NumBytes, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) + code := x64.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.NumBytes, pattern.RegisterFrom) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/memoryAccess.go b/src/build/arch/x64/memoryAccess.go new file mode 100644 index 0000000..b8cafa5 --- /dev/null +++ b/src/build/arch/x64/memoryAccess.go @@ -0,0 +1,34 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// memoryAccess encodes a memory access. +func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { + if numBytes == 2 { + code = append(code, 0x66) + } + + opCode := opCode32 + + if numBytes == 1 { + opCode = opCode8 + } + + mod := AddressMemory + + if offset != 0 || register == RBP || register == R13 { + mod = AddressMemoryOffset8 + } + + code = encode(code, mod, source, register, numBytes, opCode) + + if register == RSP || register == R12 { + code = append(code, SIB(Scale1, 0b100, 0b100)) + } + + if mod == AddressMemoryOffset8 { + code = append(code, offset) + } + + return code +} From 52e87a8885476534b67cdbed93780da201c7f0ee Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 20 Jul 2024 17:35:26 +0200 Subject: [PATCH 0333/1012] Implemented array storage --- examples/array/array.q | 13 +++++++ src/build/arch/x64/Load.go | 4 +- src/build/arch/x64/Load_test.go | 6 +-- src/build/arch/x64/Store.go | 10 ++--- src/build/arch/x64/Store_test.go | 12 +++--- src/build/asm/Finalize.go | 6 +++ src/build/asm/Memory.go | 9 +++++ src/build/asm/MemoryNumber.go | 27 +++++++++++++ src/build/asm/Mnemonic.go | 6 +++ src/build/ast/Assign.go | 9 +---- src/build/ast/Count.go | 6 +-- src/build/ast/Parse.go | 4 +- src/build/core/CompileAssign.go | 51 ++++++++++++++++++++++--- src/build/expression/Expression_test.go | 4 ++ src/build/expression/Operator.go | 1 + src/build/expression/Parse.go | 26 +++++++++---- tests/examples_test.go | 1 + 17 files changed, 150 insertions(+), 45 deletions(-) create mode 100644 examples/array/array.q create mode 100644 src/build/asm/Memory.go create mode 100644 src/build/asm/MemoryNumber.go diff --git a/examples/array/array.q b/examples/array/array.q new file mode 100644 index 0000000..2cdf875 --- /dev/null +++ b/examples/array/array.q @@ -0,0 +1,13 @@ +import mem +import sys + +main() { + length := 4 + address := mem.alloc(length) + address[0] = 65 + address[1] = 66 + address[2] = 67 + address[3] = 68 + sys.write(1, address, length) + mem.free(address, length) +} \ No newline at end of file diff --git a/src/build/arch/x64/Load.go b/src/build/arch/x64/Load.go index fbef5b0..18e36d2 100644 --- a/src/build/arch/x64/Load.go +++ b/src/build/arch/x64/Load.go @@ -3,6 +3,6 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" // LoadRegister loads from memory into a register. -func LoadRegister(code []byte, destination cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { - return memoryAccess(code, 0x8A, 0x8B, source, offset, numBytes, destination) +func LoadRegister(code []byte, destination cpu.Register, offset byte, length byte, source cpu.Register) []byte { + return memoryAccess(code, 0x8A, 0x8B, source, offset, length, destination) } diff --git a/src/build/arch/x64/Load_test.go b/src/build/arch/x64/Load_test.go index 9cdcc62..f906a28 100644 --- a/src/build/arch/x64/Load_test.go +++ b/src/build/arch/x64/Load_test.go @@ -13,7 +13,7 @@ func TestLoadRegister(t *testing.T) { Destination cpu.Register Source cpu.Register Offset byte - NumBytes byte + Length byte Code []byte }{ // No offset @@ -150,8 +150,8 @@ func TestLoadRegister(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("load %dB %s, [%s+%d]", pattern.NumBytes, pattern.Destination, pattern.Source, pattern.Offset) - code := x64.LoadRegister(nil, pattern.Destination, pattern.Offset, pattern.NumBytes, pattern.Source) + t.Logf("load %dB %s, [%s+%d]", pattern.Length, pattern.Destination, pattern.Source, pattern.Offset) + code := x64.LoadRegister(nil, pattern.Destination, pattern.Offset, pattern.Length, pattern.Source) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/Store.go b/src/build/arch/x64/Store.go index 7a37870..f7c8f5b 100644 --- a/src/build/arch/x64/Store.go +++ b/src/build/arch/x64/Store.go @@ -7,10 +7,10 @@ import ( ) // StoreNumber stores a number into the memory address included in the given register. -func StoreNumber(code []byte, register cpu.Register, offset byte, numBytes byte, number int) []byte { - code = memoryAccess(code, 0xC6, 0xC7, register, offset, numBytes, 0b000) +func StoreNumber(code []byte, register cpu.Register, offset byte, length byte, number int) []byte { + code = memoryAccess(code, 0xC6, 0xC7, register, offset, length, 0b000) - switch numBytes { + switch length { case 8, 4: return binary.LittleEndian.AppendUint32(code, uint32(number)) @@ -22,6 +22,6 @@ func StoreNumber(code []byte, register cpu.Register, offset byte, numBytes byte, } // StoreRegister stores the contents of the `source` register into the memory address included in the given register. -func StoreRegister(code []byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { - return memoryAccess(code, 0x88, 0x89, register, offset, numBytes, source) +func StoreRegister(code []byte, register cpu.Register, offset byte, length byte, source cpu.Register) []byte { + return memoryAccess(code, 0x88, 0x89, register, offset, length, source) } diff --git a/src/build/arch/x64/Store_test.go b/src/build/arch/x64/Store_test.go index 03f2224..81758d6 100644 --- a/src/build/arch/x64/Store_test.go +++ b/src/build/arch/x64/Store_test.go @@ -12,7 +12,7 @@ func TestStoreNumber(t *testing.T) { usagePatterns := []struct { Register cpu.Register Offset byte - NumBytes byte + Length byte Number int Code []byte }{ @@ -150,8 +150,8 @@ func TestStoreNumber(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%d], %d", pattern.NumBytes, pattern.Register, pattern.Offset, pattern.Number) - code := x64.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.NumBytes, pattern.Number) + t.Logf("store %dB [%s+%d], %d", pattern.Length, pattern.Register, pattern.Offset, pattern.Number) + code := x64.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.Length, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } @@ -160,7 +160,7 @@ func TestStoreRegister(t *testing.T) { usagePatterns := []struct { RegisterTo cpu.Register Offset byte - NumBytes byte + Length byte RegisterFrom cpu.Register Code []byte }{ @@ -298,8 +298,8 @@ func TestStoreRegister(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%d], %s", pattern.NumBytes, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) - code := x64.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.NumBytes, pattern.RegisterFrom) + t.Logf("store %dB [%s+%d], %s", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) + code := x64.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index 415d594..a34920a 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -165,6 +165,12 @@ func (a Assembler) Finalize() ([]byte, []byte) { case RETURN: code = x64.Return(code) + case STORE: + switch operands := x.Data.(type) { + case *MemoryNumber: + code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) + } + case SYSCALL: code = x64.Syscall(code) diff --git a/src/build/asm/Memory.go b/src/build/asm/Memory.go new file mode 100644 index 0000000..ef209ec --- /dev/null +++ b/src/build/asm/Memory.go @@ -0,0 +1,9 @@ +package asm + +import "git.akyoto.dev/cli/q/src/build/cpu" + +type Memory struct { + Base cpu.Register + Offset byte + Length byte +} diff --git a/src/build/asm/MemoryNumber.go b/src/build/asm/MemoryNumber.go new file mode 100644 index 0000000..0923bd1 --- /dev/null +++ b/src/build/asm/MemoryNumber.go @@ -0,0 +1,27 @@ +package asm + +import ( + "fmt" +) + +// MemoryNumber operates with a memory address and a number. +type MemoryNumber struct { + Address Memory + Number int +} + +// String returns a human readable version. +func (data *MemoryNumber) String() string { + return fmt.Sprintf("%dB [%s+%d], %d", data.Address.Length, data.Address.Base, data.Address.Offset, data.Number) +} + +// MemoryNumber adds an instruction with a memory address and a number. +func (a *Assembler) MemoryNumber(mnemonic Mnemonic, address Memory, number int) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &MemoryNumber{ + Address: address, + Number: number, + }, + }) +} diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index d25bc99..a1967a7 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -18,10 +18,12 @@ const ( JUMP MUL LABEL + LOAD MOVE POP PUSH RETURN + STORE SUB SYSCALL ) @@ -55,6 +57,8 @@ func (m Mnemonic) String() string { return "jump if >=" case LABEL: return "label" + case LOAD: + return "load" case MOVE: return "move" case MUL: @@ -67,6 +71,8 @@ func (m Mnemonic) String() string { return "return" case SUB: return "sub" + case STORE: + return "store" case SYSCALL: return "syscall" default: diff --git a/src/build/ast/Assign.go b/src/build/ast/Assign.go index daee95d..32c6819 100644 --- a/src/build/ast/Assign.go +++ b/src/build/ast/Assign.go @@ -1,19 +1,14 @@ package ast import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" ) // Assign represents an assignment to an existing variable or memory location. type Assign struct { - Value *expression.Expression - Name token.Token - Operator token.Token + Expression *expression.Expression } func (node *Assign) String() string { - return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) + return node.Expression.String() } diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index d15a29e..0cc6986 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -9,11 +9,7 @@ func Count(body AST, kind token.Kind, name string) int { for _, node := range body { switch node := node.(type) { case *Assign: - if node.Name.Kind == kind && node.Name.Text() == name { - count++ - } - - count += node.Value.Count(kind, name) + count += node.Expression.Count(kind, name) case *Call: count += node.Expression.Count(kind, name) diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index eb55aec..eae5666 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -86,9 +86,7 @@ func toASTNode(tokens token.List) (Node, error) { return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) } - name := expr.Children[0].Token - value := expr.Children[1] - return &Assign{Name: name, Value: value, Operator: expr.Token}, nil + return &Assign{Expression: expr}, nil case IsFunctionCall(expr): return &Call{Expression: expr}, nil diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index 460ef05..fd48f87 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -1,19 +1,58 @@ package core import ( + "strconv" + + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" ) // CompileAssign compiles an assign statement. func (f *Function) CompileAssign(node *ast.Assign) error { - name := node.Name.Text() - variable := f.Variable(name) + operator := node.Expression.Token + left := node.Expression.Children[0] + right := node.Expression.Children[1] - if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Name.Position) + if left.IsLeaf() { + name := left.Token.Text() + variable := f.Variable(name) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) + } + + defer f.useVariable(variable) + return f.Execute(operator, variable.Register, right) } - defer f.useVariable(variable) - return f.Execute(node.Operator, variable.Register, node.Value) + if left.Token.Kind == token.Operator && left.Token.Text() == "@" { + name := left.Children[0].Token.Text() + variable := f.Variable(name) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position) + } + + defer f.useVariable(variable) + + index := left.Children[1] + offset, err := strconv.Atoi(index.Token.Text()) + + if err != nil { + return err + } + + num, err := strconv.Atoi(right.Token.Text()) + + if err != nil { + return err + } + + f.Assembler.MemoryNumber(asm.STORE, asm.Memory{Base: variable.Register, Offset: byte(offset), Length: 1}, num) + return nil + } + + return errors.New(errors.NotImplemented, f.File, left.Token.Position) } diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index 4e7ea8d..6794da8 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -79,6 +79,10 @@ func TestParse(t *testing.T) { {"Function calls 27", "sum(a,b)*2+15*4", "(+ (* (λ sum a b) 2) (* 15 4))"}, {"Package function calls", "math.sum(a,b)", "(λ (. math sum) a b)"}, {"Package function calls 2", "generic.math.sum(a,b)", "(λ (. (. generic math) sum) a b)"}, + {"Array access", "a[0]", "(@ a 0)"}, + {"Array access 2", "a[b+c]", "(@ a (+ b c))"}, + {"Array access 3", "a.b[c]", "(@ (. a b) c)"}, + {"Array access 4", "a.b[c+d]", "(@ (. a b) (+ c d))"}, } for _, test := range tests { diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index 07fc5a1..b2d15e1 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -18,6 +18,7 @@ type Operator struct { var Operators = map[string]*Operator{ ".": {".", 13, 2}, "λ": {"λ", 12, 1}, + "@": {"@", 12, 2}, "!": {"!", 11, 1}, "*": {"*", 10, 2}, "/": {"/", 10, 2}, diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index ee90a0b..33dd062 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -6,7 +6,10 @@ import ( "git.akyoto.dev/cli/q/src/build/token" ) -var call = []byte("λ") +var ( + call = []byte("λ") + array = []byte("@") +) // Parse generates an expression tree from tokens. func Parse(tokens token.List) *Expression { @@ -18,7 +21,7 @@ func Parse(tokens token.List) *Expression { ) for i, t := range tokens { - if t.Kind == token.GroupStart { + if t.Kind == token.GroupStart || t.Kind == token.ArrayStart { groupLevel++ if groupLevel == 1 { @@ -28,23 +31,30 @@ func Parse(tokens token.List) *Expression { continue } - if t.Kind == token.GroupEnd { + if t.Kind == token.GroupEnd || t.Kind == token.ArrayEnd { groupLevel-- if groupLevel != 0 { continue } - isFunctionCall := isComplete(cursor) - - if isFunctionCall { + // Function call or array access + if isComplete(cursor) { parameters := NewList(tokens[groupPosition:i]) node := New() node.Token.Kind = token.Operator node.Token.Position = tokens[groupPosition].Position - node.Token.Bytes = call - node.Precedence = precedence("λ") + + switch t.Kind { + case token.GroupEnd: + node.Token.Bytes = call + node.Precedence = precedence("λ") + + case token.ArrayEnd: + node.Token.Bytes = array + node.Precedence = precedence("@") + } if cursor.Token.Kind == token.Operator && node.Precedence > cursor.Precedence { cursor.LastChild().Replace(node) diff --git a/tests/examples_test.go b/tests/examples_test.go index 17839db..03139f0 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -17,6 +17,7 @@ var examples = []struct { {"hello", "", "Hello", 0}, {"factorial", "", "", 120}, {"fibonacci", "", "", 55}, + {"array", "", "ABCD", 0}, } func TestExamples(t *testing.T) { From 155df7c44ce937a47214addc27a0dc4a74ef30f4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 20 Jul 2024 17:35:26 +0200 Subject: [PATCH 0334/1012] Implemented array storage --- examples/array/array.q | 13 +++++++ src/build/arch/x64/Load.go | 4 +- src/build/arch/x64/Load_test.go | 6 +-- src/build/arch/x64/Store.go | 10 ++--- src/build/arch/x64/Store_test.go | 12 +++--- src/build/asm/Finalize.go | 6 +++ src/build/asm/Memory.go | 9 +++++ src/build/asm/MemoryNumber.go | 27 +++++++++++++ src/build/asm/Mnemonic.go | 6 +++ src/build/ast/Assign.go | 9 +---- src/build/ast/Count.go | 6 +-- src/build/ast/Parse.go | 4 +- src/build/core/CompileAssign.go | 51 ++++++++++++++++++++++--- src/build/expression/Expression_test.go | 4 ++ src/build/expression/Operator.go | 1 + src/build/expression/Parse.go | 26 +++++++++---- tests/examples_test.go | 1 + 17 files changed, 150 insertions(+), 45 deletions(-) create mode 100644 examples/array/array.q create mode 100644 src/build/asm/Memory.go create mode 100644 src/build/asm/MemoryNumber.go diff --git a/examples/array/array.q b/examples/array/array.q new file mode 100644 index 0000000..2cdf875 --- /dev/null +++ b/examples/array/array.q @@ -0,0 +1,13 @@ +import mem +import sys + +main() { + length := 4 + address := mem.alloc(length) + address[0] = 65 + address[1] = 66 + address[2] = 67 + address[3] = 68 + sys.write(1, address, length) + mem.free(address, length) +} \ No newline at end of file diff --git a/src/build/arch/x64/Load.go b/src/build/arch/x64/Load.go index fbef5b0..18e36d2 100644 --- a/src/build/arch/x64/Load.go +++ b/src/build/arch/x64/Load.go @@ -3,6 +3,6 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" // LoadRegister loads from memory into a register. -func LoadRegister(code []byte, destination cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { - return memoryAccess(code, 0x8A, 0x8B, source, offset, numBytes, destination) +func LoadRegister(code []byte, destination cpu.Register, offset byte, length byte, source cpu.Register) []byte { + return memoryAccess(code, 0x8A, 0x8B, source, offset, length, destination) } diff --git a/src/build/arch/x64/Load_test.go b/src/build/arch/x64/Load_test.go index 9cdcc62..f906a28 100644 --- a/src/build/arch/x64/Load_test.go +++ b/src/build/arch/x64/Load_test.go @@ -13,7 +13,7 @@ func TestLoadRegister(t *testing.T) { Destination cpu.Register Source cpu.Register Offset byte - NumBytes byte + Length byte Code []byte }{ // No offset @@ -150,8 +150,8 @@ func TestLoadRegister(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("load %dB %s, [%s+%d]", pattern.NumBytes, pattern.Destination, pattern.Source, pattern.Offset) - code := x64.LoadRegister(nil, pattern.Destination, pattern.Offset, pattern.NumBytes, pattern.Source) + t.Logf("load %dB %s, [%s+%d]", pattern.Length, pattern.Destination, pattern.Source, pattern.Offset) + code := x64.LoadRegister(nil, pattern.Destination, pattern.Offset, pattern.Length, pattern.Source) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/Store.go b/src/build/arch/x64/Store.go index 7a37870..f7c8f5b 100644 --- a/src/build/arch/x64/Store.go +++ b/src/build/arch/x64/Store.go @@ -7,10 +7,10 @@ import ( ) // StoreNumber stores a number into the memory address included in the given register. -func StoreNumber(code []byte, register cpu.Register, offset byte, numBytes byte, number int) []byte { - code = memoryAccess(code, 0xC6, 0xC7, register, offset, numBytes, 0b000) +func StoreNumber(code []byte, register cpu.Register, offset byte, length byte, number int) []byte { + code = memoryAccess(code, 0xC6, 0xC7, register, offset, length, 0b000) - switch numBytes { + switch length { case 8, 4: return binary.LittleEndian.AppendUint32(code, uint32(number)) @@ -22,6 +22,6 @@ func StoreNumber(code []byte, register cpu.Register, offset byte, numBytes byte, } // StoreRegister stores the contents of the `source` register into the memory address included in the given register. -func StoreRegister(code []byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { - return memoryAccess(code, 0x88, 0x89, register, offset, numBytes, source) +func StoreRegister(code []byte, register cpu.Register, offset byte, length byte, source cpu.Register) []byte { + return memoryAccess(code, 0x88, 0x89, register, offset, length, source) } diff --git a/src/build/arch/x64/Store_test.go b/src/build/arch/x64/Store_test.go index 03f2224..81758d6 100644 --- a/src/build/arch/x64/Store_test.go +++ b/src/build/arch/x64/Store_test.go @@ -12,7 +12,7 @@ func TestStoreNumber(t *testing.T) { usagePatterns := []struct { Register cpu.Register Offset byte - NumBytes byte + Length byte Number int Code []byte }{ @@ -150,8 +150,8 @@ func TestStoreNumber(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%d], %d", pattern.NumBytes, pattern.Register, pattern.Offset, pattern.Number) - code := x64.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.NumBytes, pattern.Number) + t.Logf("store %dB [%s+%d], %d", pattern.Length, pattern.Register, pattern.Offset, pattern.Number) + code := x64.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.Length, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } @@ -160,7 +160,7 @@ func TestStoreRegister(t *testing.T) { usagePatterns := []struct { RegisterTo cpu.Register Offset byte - NumBytes byte + Length byte RegisterFrom cpu.Register Code []byte }{ @@ -298,8 +298,8 @@ func TestStoreRegister(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%d], %s", pattern.NumBytes, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) - code := x64.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.NumBytes, pattern.RegisterFrom) + t.Logf("store %dB [%s+%d], %s", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) + code := x64.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index 415d594..a34920a 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -165,6 +165,12 @@ func (a Assembler) Finalize() ([]byte, []byte) { case RETURN: code = x64.Return(code) + case STORE: + switch operands := x.Data.(type) { + case *MemoryNumber: + code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) + } + case SYSCALL: code = x64.Syscall(code) diff --git a/src/build/asm/Memory.go b/src/build/asm/Memory.go new file mode 100644 index 0000000..ef209ec --- /dev/null +++ b/src/build/asm/Memory.go @@ -0,0 +1,9 @@ +package asm + +import "git.akyoto.dev/cli/q/src/build/cpu" + +type Memory struct { + Base cpu.Register + Offset byte + Length byte +} diff --git a/src/build/asm/MemoryNumber.go b/src/build/asm/MemoryNumber.go new file mode 100644 index 0000000..0923bd1 --- /dev/null +++ b/src/build/asm/MemoryNumber.go @@ -0,0 +1,27 @@ +package asm + +import ( + "fmt" +) + +// MemoryNumber operates with a memory address and a number. +type MemoryNumber struct { + Address Memory + Number int +} + +// String returns a human readable version. +func (data *MemoryNumber) String() string { + return fmt.Sprintf("%dB [%s+%d], %d", data.Address.Length, data.Address.Base, data.Address.Offset, data.Number) +} + +// MemoryNumber adds an instruction with a memory address and a number. +func (a *Assembler) MemoryNumber(mnemonic Mnemonic, address Memory, number int) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &MemoryNumber{ + Address: address, + Number: number, + }, + }) +} diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index d25bc99..a1967a7 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -18,10 +18,12 @@ const ( JUMP MUL LABEL + LOAD MOVE POP PUSH RETURN + STORE SUB SYSCALL ) @@ -55,6 +57,8 @@ func (m Mnemonic) String() string { return "jump if >=" case LABEL: return "label" + case LOAD: + return "load" case MOVE: return "move" case MUL: @@ -67,6 +71,8 @@ func (m Mnemonic) String() string { return "return" case SUB: return "sub" + case STORE: + return "store" case SYSCALL: return "syscall" default: diff --git a/src/build/ast/Assign.go b/src/build/ast/Assign.go index daee95d..32c6819 100644 --- a/src/build/ast/Assign.go +++ b/src/build/ast/Assign.go @@ -1,19 +1,14 @@ package ast import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" ) // Assign represents an assignment to an existing variable or memory location. type Assign struct { - Value *expression.Expression - Name token.Token - Operator token.Token + Expression *expression.Expression } func (node *Assign) String() string { - return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) + return node.Expression.String() } diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index d15a29e..0cc6986 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -9,11 +9,7 @@ func Count(body AST, kind token.Kind, name string) int { for _, node := range body { switch node := node.(type) { case *Assign: - if node.Name.Kind == kind && node.Name.Text() == name { - count++ - } - - count += node.Value.Count(kind, name) + count += node.Expression.Count(kind, name) case *Call: count += node.Expression.Count(kind, name) diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index eb55aec..eae5666 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -86,9 +86,7 @@ func toASTNode(tokens token.List) (Node, error) { return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) } - name := expr.Children[0].Token - value := expr.Children[1] - return &Assign{Name: name, Value: value, Operator: expr.Token}, nil + return &Assign{Expression: expr}, nil case IsFunctionCall(expr): return &Call{Expression: expr}, nil diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index 460ef05..fd48f87 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -1,19 +1,58 @@ package core import ( + "strconv" + + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" ) // CompileAssign compiles an assign statement. func (f *Function) CompileAssign(node *ast.Assign) error { - name := node.Name.Text() - variable := f.Variable(name) + operator := node.Expression.Token + left := node.Expression.Children[0] + right := node.Expression.Children[1] - if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Name.Position) + if left.IsLeaf() { + name := left.Token.Text() + variable := f.Variable(name) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) + } + + defer f.useVariable(variable) + return f.Execute(operator, variable.Register, right) } - defer f.useVariable(variable) - return f.Execute(node.Operator, variable.Register, node.Value) + if left.Token.Kind == token.Operator && left.Token.Text() == "@" { + name := left.Children[0].Token.Text() + variable := f.Variable(name) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position) + } + + defer f.useVariable(variable) + + index := left.Children[1] + offset, err := strconv.Atoi(index.Token.Text()) + + if err != nil { + return err + } + + num, err := strconv.Atoi(right.Token.Text()) + + if err != nil { + return err + } + + f.Assembler.MemoryNumber(asm.STORE, asm.Memory{Base: variable.Register, Offset: byte(offset), Length: 1}, num) + return nil + } + + return errors.New(errors.NotImplemented, f.File, left.Token.Position) } diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index 4e7ea8d..6794da8 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -79,6 +79,10 @@ func TestParse(t *testing.T) { {"Function calls 27", "sum(a,b)*2+15*4", "(+ (* (λ sum a b) 2) (* 15 4))"}, {"Package function calls", "math.sum(a,b)", "(λ (. math sum) a b)"}, {"Package function calls 2", "generic.math.sum(a,b)", "(λ (. (. generic math) sum) a b)"}, + {"Array access", "a[0]", "(@ a 0)"}, + {"Array access 2", "a[b+c]", "(@ a (+ b c))"}, + {"Array access 3", "a.b[c]", "(@ (. a b) c)"}, + {"Array access 4", "a.b[c+d]", "(@ (. a b) (+ c d))"}, } for _, test := range tests { diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index 07fc5a1..b2d15e1 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -18,6 +18,7 @@ type Operator struct { var Operators = map[string]*Operator{ ".": {".", 13, 2}, "λ": {"λ", 12, 1}, + "@": {"@", 12, 2}, "!": {"!", 11, 1}, "*": {"*", 10, 2}, "/": {"/", 10, 2}, diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index ee90a0b..33dd062 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -6,7 +6,10 @@ import ( "git.akyoto.dev/cli/q/src/build/token" ) -var call = []byte("λ") +var ( + call = []byte("λ") + array = []byte("@") +) // Parse generates an expression tree from tokens. func Parse(tokens token.List) *Expression { @@ -18,7 +21,7 @@ func Parse(tokens token.List) *Expression { ) for i, t := range tokens { - if t.Kind == token.GroupStart { + if t.Kind == token.GroupStart || t.Kind == token.ArrayStart { groupLevel++ if groupLevel == 1 { @@ -28,23 +31,30 @@ func Parse(tokens token.List) *Expression { continue } - if t.Kind == token.GroupEnd { + if t.Kind == token.GroupEnd || t.Kind == token.ArrayEnd { groupLevel-- if groupLevel != 0 { continue } - isFunctionCall := isComplete(cursor) - - if isFunctionCall { + // Function call or array access + if isComplete(cursor) { parameters := NewList(tokens[groupPosition:i]) node := New() node.Token.Kind = token.Operator node.Token.Position = tokens[groupPosition].Position - node.Token.Bytes = call - node.Precedence = precedence("λ") + + switch t.Kind { + case token.GroupEnd: + node.Token.Bytes = call + node.Precedence = precedence("λ") + + case token.ArrayEnd: + node.Token.Bytes = array + node.Precedence = precedence("@") + } if cursor.Token.Kind == token.Operator && node.Precedence > cursor.Precedence { cursor.LastChild().Replace(node) diff --git a/tests/examples_test.go b/tests/examples_test.go index 17839db..03139f0 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -17,6 +17,7 @@ var examples = []struct { {"hello", "", "Hello", 0}, {"factorial", "", "", 120}, {"fibonacci", "", "", 55}, + {"array", "", "ABCD", 0}, } func TestExamples(t *testing.T) { From fd66296826ed70011d8191c1d621b4124890b953 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 20 Jul 2024 17:46:40 +0200 Subject: [PATCH 0335/1012] Fixed verbose output --- src/build/core/CompileAssign.go | 2 +- src/build/core/Instructions.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index fd48f87..489b28e 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -50,7 +50,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return err } - f.Assembler.MemoryNumber(asm.STORE, asm.Memory{Base: variable.Register, Offset: byte(offset), Length: 1}, num) + f.MemoryNumber(asm.STORE, asm.Memory{Base: variable.Register, Offset: byte(offset), Length: 1}, num) return nil } diff --git a/src/build/core/Instructions.go b/src/build/core/Instructions.go index 9bdf8fa..44e8116 100644 --- a/src/build/core/Instructions.go +++ b/src/build/core/Instructions.go @@ -29,6 +29,11 @@ func (f *Function) Jump(mnemonic asm.Mnemonic, label string) { f.postInstruction() } +func (f *Function) MemoryNumber(mnemonic asm.Mnemonic, a asm.Memory, b int) { + f.Assembler.MemoryNumber(mnemonic, a, b) + f.postInstruction() +} + func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) { f.Assembler.Register(mnemonic, a) From 263c0cfb8bf8a154de9498577e6c769b4d57d9c2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 20 Jul 2024 17:46:40 +0200 Subject: [PATCH 0336/1012] Fixed verbose output --- src/build/core/CompileAssign.go | 2 +- src/build/core/Instructions.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index fd48f87..489b28e 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -50,7 +50,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return err } - f.Assembler.MemoryNumber(asm.STORE, asm.Memory{Base: variable.Register, Offset: byte(offset), Length: 1}, num) + f.MemoryNumber(asm.STORE, asm.Memory{Base: variable.Register, Offset: byte(offset), Length: 1}, num) return nil } diff --git a/src/build/core/Instructions.go b/src/build/core/Instructions.go index 9bdf8fa..44e8116 100644 --- a/src/build/core/Instructions.go +++ b/src/build/core/Instructions.go @@ -29,6 +29,11 @@ func (f *Function) Jump(mnemonic asm.Mnemonic, label string) { f.postInstruction() } +func (f *Function) MemoryNumber(mnemonic asm.Mnemonic, a asm.Memory, b int) { + f.Assembler.MemoryNumber(mnemonic, a, b) + f.postInstruction() +} + func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) { f.Assembler.Register(mnemonic, a) From 8e725da9c6bb2648e16361aefba95630e9c2728c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 20 Jul 2024 23:20:23 +0200 Subject: [PATCH 0337/1012] Added scope package --- src/build/core/AddVariable.go | 18 +++++ src/build/core/Comment.go | 11 +++ src/build/core/Compare.go | 6 +- src/build/core/Compile.go | 57 --------------- src/build/core/CompileAST.go | 18 +++++ src/build/core/CompileASTNode.go | 31 ++++++++ src/build/core/CompileAssign.go | 4 +- src/build/core/CompileCall.go | 6 +- src/build/core/CompileCondition.go | 37 ---------- src/build/core/CompileDefinition.go | 70 ------------------- src/build/core/CompileIf.go | 4 +- src/build/core/CompileLoop.go | 6 +- src/build/core/CompileTokens.go | 19 +++++ src/build/core/Execute.go | 4 +- src/build/core/ExecuteLeaf.go | 2 +- src/build/core/ExpressionToRegister.go | 6 +- src/build/core/Function.go | 56 +++++---------- src/build/core/Instructions.go | 34 +++------ src/build/core/JumpIfFalse.go | 23 ++++++ src/build/core/JumpIfTrue.go | 23 ++++++ src/build/core/NewFunction.go | 35 ++++++++++ .../core/{State.go => PrintInstructions.go} | 29 ++------ src/build/core/SaveRegister.go | 6 +- src/build/core/Scope.go | 65 ----------------- src/build/core/TokenToRegister.go | 2 +- src/build/core/Variable.go | 29 -------- src/build/core/VariableByName.go | 8 +++ src/build/core/VariableInRegister.go | 17 +++++ src/build/core/identifierExists.go | 13 ++++ src/build/core/postInstruction.go | 11 +++ src/build/core/storeVariableInRegister.go | 20 ++++++ src/build/core/useVariable.go | 36 ++++++++++ src/build/scanner/Scanner.go | 2 +- src/build/scanner/scanFile.go | 3 +- src/build/scope/Scope.go | 12 ++++ src/build/scope/Stack.go | 51 ++++++++++++++ src/build/scope/Variable.go | 13 ++++ 37 files changed, 416 insertions(+), 371 deletions(-) create mode 100644 src/build/core/AddVariable.go create mode 100644 src/build/core/Comment.go create mode 100644 src/build/core/CompileAST.go create mode 100644 src/build/core/CompileASTNode.go create mode 100644 src/build/core/CompileTokens.go create mode 100644 src/build/core/JumpIfFalse.go create mode 100644 src/build/core/JumpIfTrue.go create mode 100644 src/build/core/NewFunction.go rename src/build/core/{State.go => PrintInstructions.go} (64%) delete mode 100644 src/build/core/Scope.go delete mode 100644 src/build/core/Variable.go create mode 100644 src/build/core/VariableByName.go create mode 100644 src/build/core/VariableInRegister.go create mode 100644 src/build/core/identifierExists.go create mode 100644 src/build/core/postInstruction.go create mode 100644 src/build/core/storeVariableInRegister.go create mode 100644 src/build/core/useVariable.go create mode 100644 src/build/scope/Scope.go create mode 100644 src/build/scope/Stack.go create mode 100644 src/build/scope/Variable.go diff --git a/src/build/core/AddVariable.go b/src/build/core/AddVariable.go new file mode 100644 index 0000000..522f1df --- /dev/null +++ b/src/build/core/AddVariable.go @@ -0,0 +1,18 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/scope" +) + +// AddVariable adds a new variable to the current scope. +func (f *Function) AddVariable(variable *scope.Variable) { + if config.Comments { + f.Comment("%s = %s (%d uses)", variable.Name, variable.Register, variable.Alive) + } + + scope := f.CurrentScope() + variable.Scope = scope + scope.Variables[variable.Name] = variable + scope.Use(variable.Register) +} diff --git a/src/build/core/Comment.go b/src/build/core/Comment.go new file mode 100644 index 0000000..d5f0590 --- /dev/null +++ b/src/build/core/Comment.go @@ -0,0 +1,11 @@ +package core + +import ( + "fmt" +) + +// Comment adds a comment to the assembler. +func (f *Function) Comment(format string, args ...any) { + f.Assembler.Comment(fmt.Sprintf(format, args...)) + f.postInstruction() +} diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index ca19ada..3be29ec 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -14,7 +14,7 @@ func (f *Function) Compare(comparison *expression.Expression) error { if left.IsLeaf() && left.Token.Kind == token.Identifier { name := left.Token.Text() - variable := f.Variable(name) + variable := f.VariableByName(name) if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) @@ -34,13 +34,13 @@ func (f *Function) Compare(comparison *expression.Expression) error { return f.Execute(comparison.Token, f.cpu.Output[0], right) } - tmp := f.Scope().MustFindFree(f.cpu.General) + tmp := f.CurrentScope().MustFindFree(f.cpu.General) err := f.ExpressionToRegister(left, tmp) if err != nil { return err } - defer f.Scope().Free(tmp) + defer f.CurrentScope().Free(tmp) return f.Execute(comparison.Token, tmp, right) } diff --git a/src/build/core/Compile.go b/src/build/core/Compile.go index c581921..4c2cb94 100644 --- a/src/build/core/Compile.go +++ b/src/build/core/Compile.go @@ -1,11 +1,5 @@ package core -import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" -) - // Compile turns a function into machine code. func (f *Function) Compile() { defer close(f.finished) @@ -13,54 +7,3 @@ func (f *Function) Compile() { f.Err = f.CompileTokens(f.Body) f.Return() } - -// CompileTokens compiles a token list. -func (f *Function) CompileTokens(tokens token.List) error { - body, err := ast.Parse(tokens) - - if err != nil { - err.(*errors.Error).File = f.File - return err - } - - return f.CompileAST(body) -} - -// CompileAST compiles an abstract syntax tree. -func (f *Function) CompileAST(tree ast.AST) error { - for _, node := range tree { - err := f.CompileASTNode(node) - - if err != nil { - return err - } - } - - return nil -} - -// CompileASTNode compiles a node in the AST. -func (f *Function) CompileASTNode(node ast.Node) error { - switch node := node.(type) { - case *ast.Assign: - return f.CompileAssign(node) - - case *ast.Call: - return f.CompileCall(node.Expression) - - case *ast.Define: - return f.CompileDefinition(node) - - case *ast.Return: - return f.CompileReturn(node) - - case *ast.If: - return f.CompileIf(node) - - case *ast.Loop: - return f.CompileLoop(node) - - default: - panic("unknown AST type") - } -} diff --git a/src/build/core/CompileAST.go b/src/build/core/CompileAST.go new file mode 100644 index 0000000..a2faba8 --- /dev/null +++ b/src/build/core/CompileAST.go @@ -0,0 +1,18 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" +) + +// CompileAST compiles an abstract syntax tree. +func (f *Function) CompileAST(tree ast.AST) error { + for _, node := range tree { + err := f.CompileASTNode(node) + + if err != nil { + return err + } + } + + return nil +} diff --git a/src/build/core/CompileASTNode.go b/src/build/core/CompileASTNode.go new file mode 100644 index 0000000..34ce620 --- /dev/null +++ b/src/build/core/CompileASTNode.go @@ -0,0 +1,31 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" +) + +// CompileASTNode compiles a node in the AST. +func (f *Function) CompileASTNode(node ast.Node) error { + switch node := node.(type) { + case *ast.Assign: + return f.CompileAssign(node) + + case *ast.Call: + return f.CompileCall(node.Expression) + + case *ast.Define: + return f.CompileDefinition(node) + + case *ast.Return: + return f.CompileReturn(node) + + case *ast.If: + return f.CompileIf(node) + + case *ast.Loop: + return f.CompileLoop(node) + + default: + panic("unknown AST type") + } +} diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index 489b28e..384630d 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -17,7 +17,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { if left.IsLeaf() { name := left.Token.Text() - variable := f.Variable(name) + variable := f.VariableByName(name) if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) @@ -29,7 +29,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { if left.Token.Kind == token.Operator && left.Token.Text() == "@" { name := left.Children[0].Token.Text() - variable := f.Variable(name) + variable := f.VariableByName(name) if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position) diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 53250d9..4c2adc6 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -47,7 +47,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { // Push for _, register := range f.cpu.General { - if f.Scope().IsUsed(register) { + if f.CurrentScope().IsUsed(register) { f.Register(asm.PUSH, register) } } @@ -64,14 +64,14 @@ func (f *Function) CompileCall(root *expression.Expression) error { continue } - f.Scope().Free(register) + f.CurrentScope().Free(register) } // Pop for i := len(f.cpu.General) - 1; i >= 0; i-- { register := f.cpu.General[i] - if f.Scope().IsUsed(register) { + if f.CurrentScope().IsUsed(register) { f.Register(asm.POP, register) } } diff --git a/src/build/core/CompileCondition.go b/src/build/core/CompileCondition.go index b257cee..621a7e7 100644 --- a/src/build/core/CompileCondition.go +++ b/src/build/core/CompileCondition.go @@ -3,7 +3,6 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/expression" ) @@ -74,39 +73,3 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab return err } } - -// JumpIfFalse jumps to the label if the previous comparison was false. -func (f *Function) JumpIfFalse(operator string, label string) { - switch operator { - case "==": - f.Jump(asm.JNE, label) - case "!=": - f.Jump(asm.JE, label) - case ">": - f.Jump(asm.JLE, label) - case "<": - f.Jump(asm.JGE, label) - case ">=": - f.Jump(asm.JL, label) - case "<=": - f.Jump(asm.JG, label) - } -} - -// JumpIfTrue jumps to the label if the previous comparison was true. -func (f *Function) JumpIfTrue(operator string, label string) { - switch operator { - case "==": - f.Jump(asm.JE, label) - case "!=": - f.Jump(asm.JNE, label) - case ">": - f.Jump(asm.JG, label) - case "<": - f.Jump(asm.JL, label) - case ">=": - f.Jump(asm.JGE, label) - case "<=": - f.Jump(asm.JLE, label) - } -} diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index bf215a1..a8fa1eb 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -2,9 +2,7 @@ package core import ( "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" ) @@ -24,71 +22,3 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return f.storeVariableInRegister(name, node.Value, uses) } - -func (f *Function) AddVariable(variable *Variable) { - if config.Comments { - f.Comment("%s = %s (%d uses)", variable.Name, variable.Register, variable.Alive) - } - - scope := f.Scope() - variable.Scope = scope - - scope.variables[variable.Name] = variable - scope.Use(variable.Register) -} - -func (f *Function) useVariable(variable *Variable) { - for i, scope := range f.scopes { - if scope.inLoop && variable.Scope != scope { - continue - } - - local := scope.variables[variable.Name] - - if local == nil { - continue - } - - local.Alive-- - - if local.Alive < 0 { - panic("incorrect number of variable use calls") - } - - if local.Alive == 0 { - if config.Comments { - f.Comment("%s (%s) died in scope %d", local.Name, local.Register, i) - } - - scope.Free(local.Register) - } else if config.Comments { - f.Comment("%s (%s) used in scope %d", local.Name, local.Register, i) - } - } -} - -// identifierExists returns true if the identifier has been defined. -func (f *Function) identifierExists(name string) bool { - variable := f.Variable(name) - - if variable != nil { - return true - } - - _, exists := f.Functions[name] - return exists -} - -func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error { - reg := f.Scope().MustFindFree(f.cpu.General) - f.Scope().Reserve(reg) - err := f.ExpressionToRegister(value, reg) - - f.AddVariable(&Variable{ - Name: name, - Register: reg, - Alive: uses, - }) - - return err -} diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 4d63a44..1b61c3c 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -18,9 +18,9 @@ func (f *Function) CompileIf(branch *ast.If) error { } f.AddLabel(success) - f.pushScope(branch.Body) + f.PushScope(branch.Body) err = f.CompileAST(branch.Body) - f.popScope() + f.PopScope() f.AddLabel(fail) return err } diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index c8e8258..de9d8b6 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -12,10 +12,10 @@ func (f *Function) CompileLoop(loop *ast.Loop) error { f.count.loop++ label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) f.AddLabel(label) - scope := f.pushScope(loop.Body) - scope.inLoop = true + scope := f.PushScope(loop.Body) + scope.InLoop = true err := f.CompileAST(loop.Body) f.Jump(asm.JUMP, label) - f.popScope() + f.PopScope() return err } diff --git a/src/build/core/CompileTokens.go b/src/build/core/CompileTokens.go new file mode 100644 index 0000000..7b41804 --- /dev/null +++ b/src/build/core/CompileTokens.go @@ -0,0 +1,19 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" +) + +// CompileTokens compiles a token list. +func (f *Function) CompileTokens(tokens token.List) error { + body, err := ast.Parse(tokens) + + if err != nil { + err.(*errors.Error).File = f.File + return err + } + + return f.CompileAST(body) +} diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index 22d687a..f8c8484 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -23,8 +23,8 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteRegisterRegister(operation, register, f.cpu.Output[0]) } - tmp := f.Scope().MustFindFree(f.cpu.General) - defer f.Scope().Free(tmp) + tmp := f.CurrentScope().MustFindFree(f.cpu.General) + defer f.CurrentScope().Free(tmp) err := f.ExpressionToRegister(value, tmp) diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index 3b0c5f6..947518d 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -13,7 +13,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope switch operand.Kind { case token.Identifier: name := operand.Text() - variable := f.Variable(name) + variable := f.VariableByName(name) if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 9de8671..f2b2fa1 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -34,14 +34,14 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp final := register if f.UsesRegister(right, register) { - register = f.Scope().MustFindFree(f.cpu.General) + register = f.CurrentScope().MustFindFree(f.cpu.General) if config.Comments { f.Comment("temporary register %s", register) } } - f.Scope().Reserve(register) + f.CurrentScope().Reserve(register) err := f.ExpressionToRegister(left, register) if err != nil { @@ -52,7 +52,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if register != final { f.RegisterRegister(asm.MOVE, final, register) - f.Scope().Free(register) + f.CurrentScope().Free(register) } return err diff --git a/src/build/core/Function.go b/src/build/core/Function.go index f154692..79e1b31 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -1,52 +1,32 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" ) // Function represents the smallest unit of code. type Function struct { - Name string - File *fs.File - Body token.List - State + scope.Stack + Name string + File *fs.File + Body token.List + Assembler asm.Assembler + Functions map[string]*Function + Err error + registerHistory []uint64 + finished chan struct{} + cpu cpu.CPU + count counter } -// NewFunction creates a new function. -func NewFunction(name string, file *fs.File, body token.List) *Function { - return &Function{ - Name: name, - File: file, - Body: body, - State: State{ - Assembler: asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 32), - }, - cpu: cpu.CPU{ - All: x64.AllRegisters, - Input: x64.CallRegisters, - General: x64.GeneralRegisters, - Syscall: x64.SyscallRegisters, - Output: x64.ReturnValueRegisters, - }, - scopes: []*Scope{ - {variables: map[string]*Variable{}}, - }, - finished: make(chan struct{}), - }, - } -} - -// String returns the function name. -func (f *Function) String() string { - return f.Name -} - -// Wait will block until the compilation finishes. -func (f *Function) Wait() { - <-f.finished +// counter stores how often a certain statement appeared so we can generate a unique label from it. +type counter struct { + branch int + data int + loop int + subBranch int } diff --git a/src/build/core/Instructions.go b/src/build/core/Instructions.go index 44e8116..e06dcff 100644 --- a/src/build/core/Instructions.go +++ b/src/build/core/Instructions.go @@ -1,10 +1,7 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" ) @@ -15,12 +12,7 @@ func (f *Function) AddLabel(label string) { func (f *Function) Call(label string) { f.Assembler.Call(label) - f.Scope().Use(f.cpu.Output[0]) - f.postInstruction() -} - -func (f *Function) Comment(format string, args ...any) { - f.Assembler.Comment(fmt.Sprintf(format, args...)) + f.CurrentScope().Use(f.cpu.Output[0]) f.postInstruction() } @@ -38,35 +30,35 @@ func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) { f.Assembler.Register(mnemonic, a) if mnemonic == asm.POP { - f.Scope().Use(a) + f.CurrentScope().Use(a) } f.postInstruction() } func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { - if f.Scope().IsUsed(a) && isDestructive(mnemonic) { + if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) { f.SaveRegister(a) } f.Assembler.RegisterNumber(mnemonic, a, b) if mnemonic == asm.MOVE { - f.Scope().Use(a) + f.CurrentScope().Use(a) } f.postInstruction() } func (f *Function) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { - if f.Scope().IsUsed(register) && isDestructive(mnemonic) { + if f.CurrentScope().IsUsed(register) && isDestructive(mnemonic) { f.SaveRegister(register) } f.Assembler.RegisterLabel(mnemonic, register, label) if mnemonic == asm.MOVE { - f.Scope().Use(register) + f.CurrentScope().Use(register) } f.postInstruction() @@ -77,14 +69,14 @@ func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu return } - if f.Scope().IsUsed(a) && isDestructive(mnemonic) { + if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) { f.SaveRegister(a) } f.Assembler.RegisterRegister(mnemonic, a, b) if mnemonic == asm.MOVE { - f.Scope().Use(a) + f.CurrentScope().Use(a) } f.postInstruction() @@ -97,18 +89,10 @@ func (f *Function) Return() { func (f *Function) Syscall() { f.Assembler.Syscall() - f.Scope().Use(f.cpu.Output[0]) + f.CurrentScope().Use(f.cpu.Output[0]) f.postInstruction() } -func (f *Function) postInstruction() { - if !config.Assembler { - return - } - - f.registerHistory = append(f.registerHistory, f.Scope().Used) -} - func isDestructive(mnemonic asm.Mnemonic) bool { switch mnemonic { case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV: diff --git a/src/build/core/JumpIfFalse.go b/src/build/core/JumpIfFalse.go new file mode 100644 index 0000000..baedd51 --- /dev/null +++ b/src/build/core/JumpIfFalse.go @@ -0,0 +1,23 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" +) + +// JumpIfFalse jumps to the label if the previous comparison was false. +func (f *Function) JumpIfFalse(operator string, label string) { + switch operator { + case "==": + f.Jump(asm.JNE, label) + case "!=": + f.Jump(asm.JE, label) + case ">": + f.Jump(asm.JLE, label) + case "<": + f.Jump(asm.JGE, label) + case ">=": + f.Jump(asm.JL, label) + case "<=": + f.Jump(asm.JG, label) + } +} diff --git a/src/build/core/JumpIfTrue.go b/src/build/core/JumpIfTrue.go new file mode 100644 index 0000000..4c04a0b --- /dev/null +++ b/src/build/core/JumpIfTrue.go @@ -0,0 +1,23 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" +) + +// JumpIfTrue jumps to the label if the previous comparison was true. +func (f *Function) JumpIfTrue(operator string, label string) { + switch operator { + case "==": + f.Jump(asm.JE, label) + case "!=": + f.Jump(asm.JNE, label) + case ">": + f.Jump(asm.JG, label) + case "<": + f.Jump(asm.JL, label) + case ">=": + f.Jump(asm.JGE, label) + case "<=": + f.Jump(asm.JLE, label) + } +} diff --git a/src/build/core/NewFunction.go b/src/build/core/NewFunction.go new file mode 100644 index 0000000..1394c4e --- /dev/null +++ b/src/build/core/NewFunction.go @@ -0,0 +1,35 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/build/scope" + "git.akyoto.dev/cli/q/src/build/token" +) + +// NewFunction creates a new function. +func NewFunction(name string, file *fs.File, body token.List) *Function { + return &Function{ + Name: name, + File: file, + Body: body, + Assembler: asm.Assembler{ + Instructions: make([]asm.Instruction, 0, 32), + }, + Stack: scope.Stack{ + Scopes: []*scope.Scope{ + {Variables: map[string]*scope.Variable{}}, + }, + }, + cpu: cpu.CPU{ + All: x64.AllRegisters, + Input: x64.CallRegisters, + General: x64.GeneralRegisters, + Syscall: x64.SyscallRegisters, + Output: x64.ReturnValueRegisters, + }, + finished: make(chan struct{}), + } +} diff --git a/src/build/core/State.go b/src/build/core/PrintInstructions.go similarity index 64% rename from src/build/core/State.go rename to src/build/core/PrintInstructions.go index a75c58c..0c78ba4 100644 --- a/src/build/core/State.go +++ b/src/build/core/PrintInstructions.go @@ -5,35 +5,14 @@ import ( "fmt" "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/go/color/ansi" ) -// State is the data structure we embed in each function to preserve compilation State. -type State struct { - Assembler asm.Assembler - Functions map[string]*Function - Err error - scopes []*Scope - registerHistory []uint64 - finished chan struct{} - cpu cpu.CPU - count counter -} - -// counter stores how often a certain statement appeared so we can generate a unique label from it. -type counter struct { - branch int - data int - loop int - subBranch int -} - // PrintInstructions shows the assembly instructions. -func (s *State) PrintInstructions() { +func (f *Function) PrintInstructions() { ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮") - for i, x := range s.Assembler.Instructions { + for i, x := range f.Assembler.Instructions { ansi.Dim.Print("│ ") switch x.Mnemonic { @@ -54,9 +33,9 @@ func (s *State) PrintInstructions() { } registers := bytes.Buffer{} - used := s.registerHistory[i] + used := f.registerHistory[i] - for _, reg := range s.cpu.All { + for _, reg := range f.cpu.All { if used&(1< 0 { - lastScope := f.scopes[len(f.scopes)-1] - scope.State = lastScope.State - scope.variables = make(map[string]*Variable, len(lastScope.variables)) - scope.inLoop = lastScope.inLoop - - for k, v := range lastScope.variables { - count := ast.Count(body, token.Identifier, v.Name) - - if count == 0 { - continue - } - - scope.variables[k] = &Variable{ - Name: v.Name, - Register: v.Register, - Alive: count, - } - } - } else { - scope.variables = map[string]*Variable{} - } - - f.scopes = append(f.scopes, scope) - - if config.Comments { - f.Comment("scope %d start", len(f.scopes)) - } - - return scope -} - -// popScope removes the scope at the top of the stack. -func (f *Function) popScope() { - if config.Comments { - f.Comment("scope %d end", len(f.scopes)) - } - - f.scopes = f.scopes[:len(f.scopes)-1] -} diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 85e8bf9..a40f471 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -16,7 +16,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { switch t.Kind { case token.Identifier: name := t.Text() - variable := f.Variable(name) + variable := f.VariableByName(name) if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) diff --git a/src/build/core/Variable.go b/src/build/core/Variable.go deleted file mode 100644 index 2045aef..0000000 --- a/src/build/core/Variable.go +++ /dev/null @@ -1,29 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/cpu" -) - -// Variable represents a named register. -type Variable struct { - Scope *Scope - Name string - Register cpu.Register - Alive int -} - -// Variable returns the variable with the given name or `nil` if it doesn't exist. -func (s *State) Variable(name string) *Variable { - return s.Scope().variables[name] -} - -// VariableInRegister returns the variable that occupies the given register or `nil` if none occupy the register. -func (s *State) VariableInRegister(register cpu.Register) *Variable { - for _, v := range s.Scope().variables { - if v.Register == register { - return v - } - } - - return nil -} diff --git a/src/build/core/VariableByName.go b/src/build/core/VariableByName.go new file mode 100644 index 0000000..a7b9ff7 --- /dev/null +++ b/src/build/core/VariableByName.go @@ -0,0 +1,8 @@ +package core + +import "git.akyoto.dev/cli/q/src/build/scope" + +// VariableByName returns the variable with the given name or `nil` if it doesn't exist. +func (f *Function) VariableByName(name string) *scope.Variable { + return f.CurrentScope().Variables[name] +} diff --git a/src/build/core/VariableInRegister.go b/src/build/core/VariableInRegister.go new file mode 100644 index 0000000..4666141 --- /dev/null +++ b/src/build/core/VariableInRegister.go @@ -0,0 +1,17 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/scope" +) + +// VariableInRegister returns the variable that occupies the given register or `nil` if none occupy the register. +func (f *Function) VariableInRegister(register cpu.Register) *scope.Variable { + for _, v := range f.CurrentScope().Variables { + if v.Register == register { + return v + } + } + + return nil +} diff --git a/src/build/core/identifierExists.go b/src/build/core/identifierExists.go new file mode 100644 index 0000000..96bd55f --- /dev/null +++ b/src/build/core/identifierExists.go @@ -0,0 +1,13 @@ +package core + +// identifierExists returns true if the identifier has been defined. +func (f *Function) identifierExists(name string) bool { + variable := f.VariableByName(name) + + if variable != nil { + return true + } + + _, exists := f.Functions[name] + return exists +} diff --git a/src/build/core/postInstruction.go b/src/build/core/postInstruction.go new file mode 100644 index 0000000..b4c3d66 --- /dev/null +++ b/src/build/core/postInstruction.go @@ -0,0 +1,11 @@ +package core + +import "git.akyoto.dev/cli/q/src/build/config" + +func (f *Function) postInstruction() { + if !config.Assembler { + return + } + + f.registerHistory = append(f.registerHistory, f.CurrentScope().Used) +} diff --git a/src/build/core/storeVariableInRegister.go b/src/build/core/storeVariableInRegister.go new file mode 100644 index 0000000..7adf578 --- /dev/null +++ b/src/build/core/storeVariableInRegister.go @@ -0,0 +1,20 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/scope" +) + +func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error { + reg := f.CurrentScope().MustFindFree(f.cpu.General) + f.CurrentScope().Reserve(reg) + err := f.ExpressionToRegister(value, reg) + + f.AddVariable(&scope.Variable{ + Name: name, + Register: reg, + Alive: uses, + }) + + return err +} diff --git a/src/build/core/useVariable.go b/src/build/core/useVariable.go new file mode 100644 index 0000000..8109f20 --- /dev/null +++ b/src/build/core/useVariable.go @@ -0,0 +1,36 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/scope" +) + +func (f *Function) useVariable(variable *scope.Variable) { + for i, scope := range f.Scopes { + if scope.InLoop && variable.Scope != scope { + continue + } + + local := scope.Variables[variable.Name] + + if local == nil { + continue + } + + local.Alive-- + + if local.Alive < 0 { + panic("incorrect number of variable use calls") + } + + if local.Alive == 0 { + if config.Comments { + f.Comment("%s (%s) died in scope %d", local.Name, local.Register, i) + } + + scope.Free(local.Register) + } else if config.Comments { + f.Comment("%s (%s) used in scope %d", local.Name, local.Register, i) + } + } +} diff --git a/src/build/scanner/Scanner.go b/src/build/scanner/Scanner.go index ff6ea26..0c829ac 100644 --- a/src/build/scanner/Scanner.go +++ b/src/build/scanner/Scanner.go @@ -10,6 +10,6 @@ import ( type Scanner struct { functions chan *core.Function errors chan error - group sync.WaitGroup queued sync.Map + group sync.WaitGroup } diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index 7407bd7..b075fc6 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -12,6 +12,7 @@ import ( "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/keyword" + "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" ) @@ -214,7 +215,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) } - variable := &core.Variable{ + variable := &scope.Variable{ Name: name, Register: register, Alive: uses, diff --git a/src/build/scope/Scope.go b/src/build/scope/Scope.go new file mode 100644 index 0000000..793bb27 --- /dev/null +++ b/src/build/scope/Scope.go @@ -0,0 +1,12 @@ +package scope + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// Scope represents an independent code block. +type Scope struct { + Variables map[string]*Variable + InLoop bool + cpu.State +} diff --git a/src/build/scope/Stack.go b/src/build/scope/Stack.go new file mode 100644 index 0000000..5f634ff --- /dev/null +++ b/src/build/scope/Stack.go @@ -0,0 +1,51 @@ +package scope + +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/token" +) + +type Stack struct { + Scopes []*Scope +} + +// CurrentScope returns the current scope. +func (stack *Stack) CurrentScope() *Scope { + return stack.Scopes[len(stack.Scopes)-1] +} + +// PopScope removes the scope at the top of the stack. +func (stack *Stack) PopScope() { + stack.Scopes = stack.Scopes[:len(stack.Scopes)-1] +} + +// PushScope pushes a new scope to the top of the stack. +func (stack *Stack) PushScope(body ast.AST) *Scope { + s := &Scope{} + + if len(stack.Scopes) > 0 { + lastScope := stack.Scopes[len(stack.Scopes)-1] + s.State = lastScope.State + s.Variables = make(map[string]*Variable, len(lastScope.Variables)) + s.InLoop = lastScope.InLoop + + for k, v := range lastScope.Variables { + count := ast.Count(body, token.Identifier, v.Name) + + if count == 0 { + continue + } + + s.Variables[k] = &Variable{ + Name: v.Name, + Register: v.Register, + Alive: count, + } + } + } else { + s.Variables = map[string]*Variable{} + } + + stack.Scopes = append(stack.Scopes, s) + return s +} diff --git a/src/build/scope/Variable.go b/src/build/scope/Variable.go new file mode 100644 index 0000000..940d595 --- /dev/null +++ b/src/build/scope/Variable.go @@ -0,0 +1,13 @@ +package scope + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// Variable represents a named register. +type Variable struct { + Scope *Scope + Name string + Register cpu.Register + Alive int +} From 43cdac55723ab7863bec8cdda5222c432052e493 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 20 Jul 2024 23:20:23 +0200 Subject: [PATCH 0338/1012] Added scope package --- src/build/core/AddVariable.go | 18 +++++ src/build/core/Comment.go | 11 +++ src/build/core/Compare.go | 6 +- src/build/core/Compile.go | 57 --------------- src/build/core/CompileAST.go | 18 +++++ src/build/core/CompileASTNode.go | 31 ++++++++ src/build/core/CompileAssign.go | 4 +- src/build/core/CompileCall.go | 6 +- src/build/core/CompileCondition.go | 37 ---------- src/build/core/CompileDefinition.go | 70 ------------------- src/build/core/CompileIf.go | 4 +- src/build/core/CompileLoop.go | 6 +- src/build/core/CompileTokens.go | 19 +++++ src/build/core/Execute.go | 4 +- src/build/core/ExecuteLeaf.go | 2 +- src/build/core/ExpressionToRegister.go | 6 +- src/build/core/Function.go | 56 +++++---------- src/build/core/Instructions.go | 34 +++------ src/build/core/JumpIfFalse.go | 23 ++++++ src/build/core/JumpIfTrue.go | 23 ++++++ src/build/core/NewFunction.go | 35 ++++++++++ .../core/{State.go => PrintInstructions.go} | 29 ++------ src/build/core/SaveRegister.go | 6 +- src/build/core/Scope.go | 65 ----------------- src/build/core/TokenToRegister.go | 2 +- src/build/core/Variable.go | 29 -------- src/build/core/VariableByName.go | 8 +++ src/build/core/VariableInRegister.go | 17 +++++ src/build/core/identifierExists.go | 13 ++++ src/build/core/postInstruction.go | 11 +++ src/build/core/storeVariableInRegister.go | 20 ++++++ src/build/core/useVariable.go | 36 ++++++++++ src/build/scanner/Scanner.go | 2 +- src/build/scanner/scanFile.go | 3 +- src/build/scope/Scope.go | 12 ++++ src/build/scope/Stack.go | 51 ++++++++++++++ src/build/scope/Variable.go | 13 ++++ 37 files changed, 416 insertions(+), 371 deletions(-) create mode 100644 src/build/core/AddVariable.go create mode 100644 src/build/core/Comment.go create mode 100644 src/build/core/CompileAST.go create mode 100644 src/build/core/CompileASTNode.go create mode 100644 src/build/core/CompileTokens.go create mode 100644 src/build/core/JumpIfFalse.go create mode 100644 src/build/core/JumpIfTrue.go create mode 100644 src/build/core/NewFunction.go rename src/build/core/{State.go => PrintInstructions.go} (64%) delete mode 100644 src/build/core/Scope.go delete mode 100644 src/build/core/Variable.go create mode 100644 src/build/core/VariableByName.go create mode 100644 src/build/core/VariableInRegister.go create mode 100644 src/build/core/identifierExists.go create mode 100644 src/build/core/postInstruction.go create mode 100644 src/build/core/storeVariableInRegister.go create mode 100644 src/build/core/useVariable.go create mode 100644 src/build/scope/Scope.go create mode 100644 src/build/scope/Stack.go create mode 100644 src/build/scope/Variable.go diff --git a/src/build/core/AddVariable.go b/src/build/core/AddVariable.go new file mode 100644 index 0000000..522f1df --- /dev/null +++ b/src/build/core/AddVariable.go @@ -0,0 +1,18 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/scope" +) + +// AddVariable adds a new variable to the current scope. +func (f *Function) AddVariable(variable *scope.Variable) { + if config.Comments { + f.Comment("%s = %s (%d uses)", variable.Name, variable.Register, variable.Alive) + } + + scope := f.CurrentScope() + variable.Scope = scope + scope.Variables[variable.Name] = variable + scope.Use(variable.Register) +} diff --git a/src/build/core/Comment.go b/src/build/core/Comment.go new file mode 100644 index 0000000..d5f0590 --- /dev/null +++ b/src/build/core/Comment.go @@ -0,0 +1,11 @@ +package core + +import ( + "fmt" +) + +// Comment adds a comment to the assembler. +func (f *Function) Comment(format string, args ...any) { + f.Assembler.Comment(fmt.Sprintf(format, args...)) + f.postInstruction() +} diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index ca19ada..3be29ec 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -14,7 +14,7 @@ func (f *Function) Compare(comparison *expression.Expression) error { if left.IsLeaf() && left.Token.Kind == token.Identifier { name := left.Token.Text() - variable := f.Variable(name) + variable := f.VariableByName(name) if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) @@ -34,13 +34,13 @@ func (f *Function) Compare(comparison *expression.Expression) error { return f.Execute(comparison.Token, f.cpu.Output[0], right) } - tmp := f.Scope().MustFindFree(f.cpu.General) + tmp := f.CurrentScope().MustFindFree(f.cpu.General) err := f.ExpressionToRegister(left, tmp) if err != nil { return err } - defer f.Scope().Free(tmp) + defer f.CurrentScope().Free(tmp) return f.Execute(comparison.Token, tmp, right) } diff --git a/src/build/core/Compile.go b/src/build/core/Compile.go index c581921..4c2cb94 100644 --- a/src/build/core/Compile.go +++ b/src/build/core/Compile.go @@ -1,11 +1,5 @@ package core -import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" -) - // Compile turns a function into machine code. func (f *Function) Compile() { defer close(f.finished) @@ -13,54 +7,3 @@ func (f *Function) Compile() { f.Err = f.CompileTokens(f.Body) f.Return() } - -// CompileTokens compiles a token list. -func (f *Function) CompileTokens(tokens token.List) error { - body, err := ast.Parse(tokens) - - if err != nil { - err.(*errors.Error).File = f.File - return err - } - - return f.CompileAST(body) -} - -// CompileAST compiles an abstract syntax tree. -func (f *Function) CompileAST(tree ast.AST) error { - for _, node := range tree { - err := f.CompileASTNode(node) - - if err != nil { - return err - } - } - - return nil -} - -// CompileASTNode compiles a node in the AST. -func (f *Function) CompileASTNode(node ast.Node) error { - switch node := node.(type) { - case *ast.Assign: - return f.CompileAssign(node) - - case *ast.Call: - return f.CompileCall(node.Expression) - - case *ast.Define: - return f.CompileDefinition(node) - - case *ast.Return: - return f.CompileReturn(node) - - case *ast.If: - return f.CompileIf(node) - - case *ast.Loop: - return f.CompileLoop(node) - - default: - panic("unknown AST type") - } -} diff --git a/src/build/core/CompileAST.go b/src/build/core/CompileAST.go new file mode 100644 index 0000000..a2faba8 --- /dev/null +++ b/src/build/core/CompileAST.go @@ -0,0 +1,18 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" +) + +// CompileAST compiles an abstract syntax tree. +func (f *Function) CompileAST(tree ast.AST) error { + for _, node := range tree { + err := f.CompileASTNode(node) + + if err != nil { + return err + } + } + + return nil +} diff --git a/src/build/core/CompileASTNode.go b/src/build/core/CompileASTNode.go new file mode 100644 index 0000000..34ce620 --- /dev/null +++ b/src/build/core/CompileASTNode.go @@ -0,0 +1,31 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" +) + +// CompileASTNode compiles a node in the AST. +func (f *Function) CompileASTNode(node ast.Node) error { + switch node := node.(type) { + case *ast.Assign: + return f.CompileAssign(node) + + case *ast.Call: + return f.CompileCall(node.Expression) + + case *ast.Define: + return f.CompileDefinition(node) + + case *ast.Return: + return f.CompileReturn(node) + + case *ast.If: + return f.CompileIf(node) + + case *ast.Loop: + return f.CompileLoop(node) + + default: + panic("unknown AST type") + } +} diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index 489b28e..384630d 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -17,7 +17,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { if left.IsLeaf() { name := left.Token.Text() - variable := f.Variable(name) + variable := f.VariableByName(name) if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) @@ -29,7 +29,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { if left.Token.Kind == token.Operator && left.Token.Text() == "@" { name := left.Children[0].Token.Text() - variable := f.Variable(name) + variable := f.VariableByName(name) if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position) diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 53250d9..4c2adc6 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -47,7 +47,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { // Push for _, register := range f.cpu.General { - if f.Scope().IsUsed(register) { + if f.CurrentScope().IsUsed(register) { f.Register(asm.PUSH, register) } } @@ -64,14 +64,14 @@ func (f *Function) CompileCall(root *expression.Expression) error { continue } - f.Scope().Free(register) + f.CurrentScope().Free(register) } // Pop for i := len(f.cpu.General) - 1; i >= 0; i-- { register := f.cpu.General[i] - if f.Scope().IsUsed(register) { + if f.CurrentScope().IsUsed(register) { f.Register(asm.POP, register) } } diff --git a/src/build/core/CompileCondition.go b/src/build/core/CompileCondition.go index b257cee..621a7e7 100644 --- a/src/build/core/CompileCondition.go +++ b/src/build/core/CompileCondition.go @@ -3,7 +3,6 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/expression" ) @@ -74,39 +73,3 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab return err } } - -// JumpIfFalse jumps to the label if the previous comparison was false. -func (f *Function) JumpIfFalse(operator string, label string) { - switch operator { - case "==": - f.Jump(asm.JNE, label) - case "!=": - f.Jump(asm.JE, label) - case ">": - f.Jump(asm.JLE, label) - case "<": - f.Jump(asm.JGE, label) - case ">=": - f.Jump(asm.JL, label) - case "<=": - f.Jump(asm.JG, label) - } -} - -// JumpIfTrue jumps to the label if the previous comparison was true. -func (f *Function) JumpIfTrue(operator string, label string) { - switch operator { - case "==": - f.Jump(asm.JE, label) - case "!=": - f.Jump(asm.JNE, label) - case ">": - f.Jump(asm.JG, label) - case "<": - f.Jump(asm.JL, label) - case ">=": - f.Jump(asm.JGE, label) - case "<=": - f.Jump(asm.JLE, label) - } -} diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index bf215a1..a8fa1eb 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -2,9 +2,7 @@ package core import ( "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" ) @@ -24,71 +22,3 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return f.storeVariableInRegister(name, node.Value, uses) } - -func (f *Function) AddVariable(variable *Variable) { - if config.Comments { - f.Comment("%s = %s (%d uses)", variable.Name, variable.Register, variable.Alive) - } - - scope := f.Scope() - variable.Scope = scope - - scope.variables[variable.Name] = variable - scope.Use(variable.Register) -} - -func (f *Function) useVariable(variable *Variable) { - for i, scope := range f.scopes { - if scope.inLoop && variable.Scope != scope { - continue - } - - local := scope.variables[variable.Name] - - if local == nil { - continue - } - - local.Alive-- - - if local.Alive < 0 { - panic("incorrect number of variable use calls") - } - - if local.Alive == 0 { - if config.Comments { - f.Comment("%s (%s) died in scope %d", local.Name, local.Register, i) - } - - scope.Free(local.Register) - } else if config.Comments { - f.Comment("%s (%s) used in scope %d", local.Name, local.Register, i) - } - } -} - -// identifierExists returns true if the identifier has been defined. -func (f *Function) identifierExists(name string) bool { - variable := f.Variable(name) - - if variable != nil { - return true - } - - _, exists := f.Functions[name] - return exists -} - -func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error { - reg := f.Scope().MustFindFree(f.cpu.General) - f.Scope().Reserve(reg) - err := f.ExpressionToRegister(value, reg) - - f.AddVariable(&Variable{ - Name: name, - Register: reg, - Alive: uses, - }) - - return err -} diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 4d63a44..1b61c3c 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -18,9 +18,9 @@ func (f *Function) CompileIf(branch *ast.If) error { } f.AddLabel(success) - f.pushScope(branch.Body) + f.PushScope(branch.Body) err = f.CompileAST(branch.Body) - f.popScope() + f.PopScope() f.AddLabel(fail) return err } diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index c8e8258..de9d8b6 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -12,10 +12,10 @@ func (f *Function) CompileLoop(loop *ast.Loop) error { f.count.loop++ label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) f.AddLabel(label) - scope := f.pushScope(loop.Body) - scope.inLoop = true + scope := f.PushScope(loop.Body) + scope.InLoop = true err := f.CompileAST(loop.Body) f.Jump(asm.JUMP, label) - f.popScope() + f.PopScope() return err } diff --git a/src/build/core/CompileTokens.go b/src/build/core/CompileTokens.go new file mode 100644 index 0000000..7b41804 --- /dev/null +++ b/src/build/core/CompileTokens.go @@ -0,0 +1,19 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" +) + +// CompileTokens compiles a token list. +func (f *Function) CompileTokens(tokens token.List) error { + body, err := ast.Parse(tokens) + + if err != nil { + err.(*errors.Error).File = f.File + return err + } + + return f.CompileAST(body) +} diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index 22d687a..f8c8484 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -23,8 +23,8 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteRegisterRegister(operation, register, f.cpu.Output[0]) } - tmp := f.Scope().MustFindFree(f.cpu.General) - defer f.Scope().Free(tmp) + tmp := f.CurrentScope().MustFindFree(f.cpu.General) + defer f.CurrentScope().Free(tmp) err := f.ExpressionToRegister(value, tmp) diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index 3b0c5f6..947518d 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -13,7 +13,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope switch operand.Kind { case token.Identifier: name := operand.Text() - variable := f.Variable(name) + variable := f.VariableByName(name) if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 9de8671..f2b2fa1 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -34,14 +34,14 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp final := register if f.UsesRegister(right, register) { - register = f.Scope().MustFindFree(f.cpu.General) + register = f.CurrentScope().MustFindFree(f.cpu.General) if config.Comments { f.Comment("temporary register %s", register) } } - f.Scope().Reserve(register) + f.CurrentScope().Reserve(register) err := f.ExpressionToRegister(left, register) if err != nil { @@ -52,7 +52,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if register != final { f.RegisterRegister(asm.MOVE, final, register) - f.Scope().Free(register) + f.CurrentScope().Free(register) } return err diff --git a/src/build/core/Function.go b/src/build/core/Function.go index f154692..79e1b31 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -1,52 +1,32 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" ) // Function represents the smallest unit of code. type Function struct { - Name string - File *fs.File - Body token.List - State + scope.Stack + Name string + File *fs.File + Body token.List + Assembler asm.Assembler + Functions map[string]*Function + Err error + registerHistory []uint64 + finished chan struct{} + cpu cpu.CPU + count counter } -// NewFunction creates a new function. -func NewFunction(name string, file *fs.File, body token.List) *Function { - return &Function{ - Name: name, - File: file, - Body: body, - State: State{ - Assembler: asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 32), - }, - cpu: cpu.CPU{ - All: x64.AllRegisters, - Input: x64.CallRegisters, - General: x64.GeneralRegisters, - Syscall: x64.SyscallRegisters, - Output: x64.ReturnValueRegisters, - }, - scopes: []*Scope{ - {variables: map[string]*Variable{}}, - }, - finished: make(chan struct{}), - }, - } -} - -// String returns the function name. -func (f *Function) String() string { - return f.Name -} - -// Wait will block until the compilation finishes. -func (f *Function) Wait() { - <-f.finished +// counter stores how often a certain statement appeared so we can generate a unique label from it. +type counter struct { + branch int + data int + loop int + subBranch int } diff --git a/src/build/core/Instructions.go b/src/build/core/Instructions.go index 44e8116..e06dcff 100644 --- a/src/build/core/Instructions.go +++ b/src/build/core/Instructions.go @@ -1,10 +1,7 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" ) @@ -15,12 +12,7 @@ func (f *Function) AddLabel(label string) { func (f *Function) Call(label string) { f.Assembler.Call(label) - f.Scope().Use(f.cpu.Output[0]) - f.postInstruction() -} - -func (f *Function) Comment(format string, args ...any) { - f.Assembler.Comment(fmt.Sprintf(format, args...)) + f.CurrentScope().Use(f.cpu.Output[0]) f.postInstruction() } @@ -38,35 +30,35 @@ func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) { f.Assembler.Register(mnemonic, a) if mnemonic == asm.POP { - f.Scope().Use(a) + f.CurrentScope().Use(a) } f.postInstruction() } func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { - if f.Scope().IsUsed(a) && isDestructive(mnemonic) { + if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) { f.SaveRegister(a) } f.Assembler.RegisterNumber(mnemonic, a, b) if mnemonic == asm.MOVE { - f.Scope().Use(a) + f.CurrentScope().Use(a) } f.postInstruction() } func (f *Function) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { - if f.Scope().IsUsed(register) && isDestructive(mnemonic) { + if f.CurrentScope().IsUsed(register) && isDestructive(mnemonic) { f.SaveRegister(register) } f.Assembler.RegisterLabel(mnemonic, register, label) if mnemonic == asm.MOVE { - f.Scope().Use(register) + f.CurrentScope().Use(register) } f.postInstruction() @@ -77,14 +69,14 @@ func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu return } - if f.Scope().IsUsed(a) && isDestructive(mnemonic) { + if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) { f.SaveRegister(a) } f.Assembler.RegisterRegister(mnemonic, a, b) if mnemonic == asm.MOVE { - f.Scope().Use(a) + f.CurrentScope().Use(a) } f.postInstruction() @@ -97,18 +89,10 @@ func (f *Function) Return() { func (f *Function) Syscall() { f.Assembler.Syscall() - f.Scope().Use(f.cpu.Output[0]) + f.CurrentScope().Use(f.cpu.Output[0]) f.postInstruction() } -func (f *Function) postInstruction() { - if !config.Assembler { - return - } - - f.registerHistory = append(f.registerHistory, f.Scope().Used) -} - func isDestructive(mnemonic asm.Mnemonic) bool { switch mnemonic { case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV: diff --git a/src/build/core/JumpIfFalse.go b/src/build/core/JumpIfFalse.go new file mode 100644 index 0000000..baedd51 --- /dev/null +++ b/src/build/core/JumpIfFalse.go @@ -0,0 +1,23 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" +) + +// JumpIfFalse jumps to the label if the previous comparison was false. +func (f *Function) JumpIfFalse(operator string, label string) { + switch operator { + case "==": + f.Jump(asm.JNE, label) + case "!=": + f.Jump(asm.JE, label) + case ">": + f.Jump(asm.JLE, label) + case "<": + f.Jump(asm.JGE, label) + case ">=": + f.Jump(asm.JL, label) + case "<=": + f.Jump(asm.JG, label) + } +} diff --git a/src/build/core/JumpIfTrue.go b/src/build/core/JumpIfTrue.go new file mode 100644 index 0000000..4c04a0b --- /dev/null +++ b/src/build/core/JumpIfTrue.go @@ -0,0 +1,23 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" +) + +// JumpIfTrue jumps to the label if the previous comparison was true. +func (f *Function) JumpIfTrue(operator string, label string) { + switch operator { + case "==": + f.Jump(asm.JE, label) + case "!=": + f.Jump(asm.JNE, label) + case ">": + f.Jump(asm.JG, label) + case "<": + f.Jump(asm.JL, label) + case ">=": + f.Jump(asm.JGE, label) + case "<=": + f.Jump(asm.JLE, label) + } +} diff --git a/src/build/core/NewFunction.go b/src/build/core/NewFunction.go new file mode 100644 index 0000000..1394c4e --- /dev/null +++ b/src/build/core/NewFunction.go @@ -0,0 +1,35 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/build/scope" + "git.akyoto.dev/cli/q/src/build/token" +) + +// NewFunction creates a new function. +func NewFunction(name string, file *fs.File, body token.List) *Function { + return &Function{ + Name: name, + File: file, + Body: body, + Assembler: asm.Assembler{ + Instructions: make([]asm.Instruction, 0, 32), + }, + Stack: scope.Stack{ + Scopes: []*scope.Scope{ + {Variables: map[string]*scope.Variable{}}, + }, + }, + cpu: cpu.CPU{ + All: x64.AllRegisters, + Input: x64.CallRegisters, + General: x64.GeneralRegisters, + Syscall: x64.SyscallRegisters, + Output: x64.ReturnValueRegisters, + }, + finished: make(chan struct{}), + } +} diff --git a/src/build/core/State.go b/src/build/core/PrintInstructions.go similarity index 64% rename from src/build/core/State.go rename to src/build/core/PrintInstructions.go index a75c58c..0c78ba4 100644 --- a/src/build/core/State.go +++ b/src/build/core/PrintInstructions.go @@ -5,35 +5,14 @@ import ( "fmt" "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/go/color/ansi" ) -// State is the data structure we embed in each function to preserve compilation State. -type State struct { - Assembler asm.Assembler - Functions map[string]*Function - Err error - scopes []*Scope - registerHistory []uint64 - finished chan struct{} - cpu cpu.CPU - count counter -} - -// counter stores how often a certain statement appeared so we can generate a unique label from it. -type counter struct { - branch int - data int - loop int - subBranch int -} - // PrintInstructions shows the assembly instructions. -func (s *State) PrintInstructions() { +func (f *Function) PrintInstructions() { ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮") - for i, x := range s.Assembler.Instructions { + for i, x := range f.Assembler.Instructions { ansi.Dim.Print("│ ") switch x.Mnemonic { @@ -54,9 +33,9 @@ func (s *State) PrintInstructions() { } registers := bytes.Buffer{} - used := s.registerHistory[i] + used := f.registerHistory[i] - for _, reg := range s.cpu.All { + for _, reg := range f.cpu.All { if used&(1< 0 { - lastScope := f.scopes[len(f.scopes)-1] - scope.State = lastScope.State - scope.variables = make(map[string]*Variable, len(lastScope.variables)) - scope.inLoop = lastScope.inLoop - - for k, v := range lastScope.variables { - count := ast.Count(body, token.Identifier, v.Name) - - if count == 0 { - continue - } - - scope.variables[k] = &Variable{ - Name: v.Name, - Register: v.Register, - Alive: count, - } - } - } else { - scope.variables = map[string]*Variable{} - } - - f.scopes = append(f.scopes, scope) - - if config.Comments { - f.Comment("scope %d start", len(f.scopes)) - } - - return scope -} - -// popScope removes the scope at the top of the stack. -func (f *Function) popScope() { - if config.Comments { - f.Comment("scope %d end", len(f.scopes)) - } - - f.scopes = f.scopes[:len(f.scopes)-1] -} diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 85e8bf9..a40f471 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -16,7 +16,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { switch t.Kind { case token.Identifier: name := t.Text() - variable := f.Variable(name) + variable := f.VariableByName(name) if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) diff --git a/src/build/core/Variable.go b/src/build/core/Variable.go deleted file mode 100644 index 2045aef..0000000 --- a/src/build/core/Variable.go +++ /dev/null @@ -1,29 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/cpu" -) - -// Variable represents a named register. -type Variable struct { - Scope *Scope - Name string - Register cpu.Register - Alive int -} - -// Variable returns the variable with the given name or `nil` if it doesn't exist. -func (s *State) Variable(name string) *Variable { - return s.Scope().variables[name] -} - -// VariableInRegister returns the variable that occupies the given register or `nil` if none occupy the register. -func (s *State) VariableInRegister(register cpu.Register) *Variable { - for _, v := range s.Scope().variables { - if v.Register == register { - return v - } - } - - return nil -} diff --git a/src/build/core/VariableByName.go b/src/build/core/VariableByName.go new file mode 100644 index 0000000..a7b9ff7 --- /dev/null +++ b/src/build/core/VariableByName.go @@ -0,0 +1,8 @@ +package core + +import "git.akyoto.dev/cli/q/src/build/scope" + +// VariableByName returns the variable with the given name or `nil` if it doesn't exist. +func (f *Function) VariableByName(name string) *scope.Variable { + return f.CurrentScope().Variables[name] +} diff --git a/src/build/core/VariableInRegister.go b/src/build/core/VariableInRegister.go new file mode 100644 index 0000000..4666141 --- /dev/null +++ b/src/build/core/VariableInRegister.go @@ -0,0 +1,17 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/scope" +) + +// VariableInRegister returns the variable that occupies the given register or `nil` if none occupy the register. +func (f *Function) VariableInRegister(register cpu.Register) *scope.Variable { + for _, v := range f.CurrentScope().Variables { + if v.Register == register { + return v + } + } + + return nil +} diff --git a/src/build/core/identifierExists.go b/src/build/core/identifierExists.go new file mode 100644 index 0000000..96bd55f --- /dev/null +++ b/src/build/core/identifierExists.go @@ -0,0 +1,13 @@ +package core + +// identifierExists returns true if the identifier has been defined. +func (f *Function) identifierExists(name string) bool { + variable := f.VariableByName(name) + + if variable != nil { + return true + } + + _, exists := f.Functions[name] + return exists +} diff --git a/src/build/core/postInstruction.go b/src/build/core/postInstruction.go new file mode 100644 index 0000000..b4c3d66 --- /dev/null +++ b/src/build/core/postInstruction.go @@ -0,0 +1,11 @@ +package core + +import "git.akyoto.dev/cli/q/src/build/config" + +func (f *Function) postInstruction() { + if !config.Assembler { + return + } + + f.registerHistory = append(f.registerHistory, f.CurrentScope().Used) +} diff --git a/src/build/core/storeVariableInRegister.go b/src/build/core/storeVariableInRegister.go new file mode 100644 index 0000000..7adf578 --- /dev/null +++ b/src/build/core/storeVariableInRegister.go @@ -0,0 +1,20 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/scope" +) + +func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error { + reg := f.CurrentScope().MustFindFree(f.cpu.General) + f.CurrentScope().Reserve(reg) + err := f.ExpressionToRegister(value, reg) + + f.AddVariable(&scope.Variable{ + Name: name, + Register: reg, + Alive: uses, + }) + + return err +} diff --git a/src/build/core/useVariable.go b/src/build/core/useVariable.go new file mode 100644 index 0000000..8109f20 --- /dev/null +++ b/src/build/core/useVariable.go @@ -0,0 +1,36 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/scope" +) + +func (f *Function) useVariable(variable *scope.Variable) { + for i, scope := range f.Scopes { + if scope.InLoop && variable.Scope != scope { + continue + } + + local := scope.Variables[variable.Name] + + if local == nil { + continue + } + + local.Alive-- + + if local.Alive < 0 { + panic("incorrect number of variable use calls") + } + + if local.Alive == 0 { + if config.Comments { + f.Comment("%s (%s) died in scope %d", local.Name, local.Register, i) + } + + scope.Free(local.Register) + } else if config.Comments { + f.Comment("%s (%s) used in scope %d", local.Name, local.Register, i) + } + } +} diff --git a/src/build/scanner/Scanner.go b/src/build/scanner/Scanner.go index ff6ea26..0c829ac 100644 --- a/src/build/scanner/Scanner.go +++ b/src/build/scanner/Scanner.go @@ -10,6 +10,6 @@ import ( type Scanner struct { functions chan *core.Function errors chan error - group sync.WaitGroup queued sync.Map + group sync.WaitGroup } diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index 7407bd7..b075fc6 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -12,6 +12,7 @@ import ( "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/keyword" + "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" ) @@ -214,7 +215,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) } - variable := &core.Variable{ + variable := &scope.Variable{ Name: name, Register: register, Alive: uses, diff --git a/src/build/scope/Scope.go b/src/build/scope/Scope.go new file mode 100644 index 0000000..793bb27 --- /dev/null +++ b/src/build/scope/Scope.go @@ -0,0 +1,12 @@ +package scope + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// Scope represents an independent code block. +type Scope struct { + Variables map[string]*Variable + InLoop bool + cpu.State +} diff --git a/src/build/scope/Stack.go b/src/build/scope/Stack.go new file mode 100644 index 0000000..5f634ff --- /dev/null +++ b/src/build/scope/Stack.go @@ -0,0 +1,51 @@ +package scope + +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/token" +) + +type Stack struct { + Scopes []*Scope +} + +// CurrentScope returns the current scope. +func (stack *Stack) CurrentScope() *Scope { + return stack.Scopes[len(stack.Scopes)-1] +} + +// PopScope removes the scope at the top of the stack. +func (stack *Stack) PopScope() { + stack.Scopes = stack.Scopes[:len(stack.Scopes)-1] +} + +// PushScope pushes a new scope to the top of the stack. +func (stack *Stack) PushScope(body ast.AST) *Scope { + s := &Scope{} + + if len(stack.Scopes) > 0 { + lastScope := stack.Scopes[len(stack.Scopes)-1] + s.State = lastScope.State + s.Variables = make(map[string]*Variable, len(lastScope.Variables)) + s.InLoop = lastScope.InLoop + + for k, v := range lastScope.Variables { + count := ast.Count(body, token.Identifier, v.Name) + + if count == 0 { + continue + } + + s.Variables[k] = &Variable{ + Name: v.Name, + Register: v.Register, + Alive: count, + } + } + } else { + s.Variables = map[string]*Variable{} + } + + stack.Scopes = append(stack.Scopes, s) + return s +} diff --git a/src/build/scope/Variable.go b/src/build/scope/Variable.go new file mode 100644 index 0000000..940d595 --- /dev/null +++ b/src/build/scope/Variable.go @@ -0,0 +1,13 @@ +package scope + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// Variable represents a named register. +type Variable struct { + Scope *Scope + Name string + Register cpu.Register + Alive int +} From 7bfd0e731d35e229bc79ab792e9decaffd71cb96 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 20 Jul 2024 23:33:07 +0200 Subject: [PATCH 0339/1012] Reduced token size --- src/build/errors/Error.go | 8 ++++---- src/build/token/Position.go | 4 ++++ src/build/token/Token.go | 6 +++--- src/build/token/Tokenize.go | 20 ++++++++++---------- 4 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 src/build/token/Position.go diff --git a/src/build/errors/Error.go b/src/build/errors/Error.go index 0821e25..f0c54ef 100644 --- a/src/build/errors/Error.go +++ b/src/build/errors/Error.go @@ -14,13 +14,13 @@ type Error struct { Err error File *fs.File Stack string - Position int + Position token.Position } // New generates an error message at the current token position. // The error message is clickable in popular editors and leads you // directly to the faulty file at the given line and position. -func New(err error, file *fs.File, position int) *Error { +func New(err error, file *fs.File, position token.Position) *Error { return &Error{ Err: err, File: file, @@ -48,12 +48,12 @@ func (e *Error) Error() string { for _, t := range e.File.Tokens { if t.Position >= e.Position { - column = e.Position - lineStart + column = int(e.Position) - lineStart break } if t.Kind == token.NewLine { - lineStart = t.Position + lineStart = int(t.Position) line++ } } diff --git a/src/build/token/Position.go b/src/build/token/Position.go new file mode 100644 index 0000000..2227037 --- /dev/null +++ b/src/build/token/Position.go @@ -0,0 +1,4 @@ +package token + +// Position is the data type for storing file offsets. +type Position = uint32 diff --git a/src/build/token/Token.go b/src/build/token/Token.go index bf1208b..71dd1e5 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -10,13 +10,13 @@ import ( // This makes parsing easier and allows us to do better syntax checks. type Token struct { Bytes []byte - Position int + Position Position Kind Kind } // End returns the position after the token. -func (t *Token) End() int { - return t.Position + len(t.Bytes) +func (t *Token) End() Position { + return t.Position + Position(len(t.Bytes)) } // String creates a human readable representation for debugging purposes. diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 343411c..649f376 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -18,11 +18,11 @@ var ( // Tokenize turns the file contents into a list of tokens. func Tokenize(buffer []byte) List { var ( - i int + i Position tokens = make(List, 0, 8+len(buffer)/2) ) - for i < len(buffer) { + for i < Position(len(buffer)) { switch buffer[i] { case ' ', '\t': case ',': @@ -42,11 +42,11 @@ func Tokenize(buffer []byte) List { case '\n': tokens = append(tokens, Token{Kind: NewLine, Position: i, Bytes: newLineBytes}) case '/': - if i+1 >= len(buffer) || buffer[i+1] != '/' { + if i+1 >= Position(len(buffer)) || buffer[i+1] != '/' { position := i i++ - for i < len(buffer) && isOperator(buffer[i]) { + for i < Position(len(buffer)) && isOperator(buffer[i]) { i++ } @@ -54,7 +54,7 @@ func Tokenize(buffer []byte) List { } else { position := i - for i < len(buffer) && buffer[i] != '\n' { + for i < Position(len(buffer)) && buffer[i] != '\n' { i++ } @@ -65,10 +65,10 @@ func Tokenize(buffer []byte) List { case '"': start := i - end := len(buffer) + end := Position(len(buffer)) i++ - for i < len(buffer) { + for i < Position(len(buffer)) { if buffer[i] == '"' { end = i + 1 i++ @@ -86,7 +86,7 @@ func Tokenize(buffer []byte) List { position := i i++ - for i < len(buffer) && isIdentifier(buffer[i]) { + for i < Position(len(buffer)) && isIdentifier(buffer[i]) { i++ } @@ -106,7 +106,7 @@ func Tokenize(buffer []byte) List { position := i i++ - for i < len(buffer) && isNumber(buffer[i]) { + for i < Position(len(buffer)) && isNumber(buffer[i]) { i++ } @@ -118,7 +118,7 @@ func Tokenize(buffer []byte) List { position := i i++ - for i < len(buffer) && isOperator(buffer[i]) { + for i < Position(len(buffer)) && isOperator(buffer[i]) { i++ } From ca36d34cb9ad0e750b41e752c5500196844796ff Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 20 Jul 2024 23:33:07 +0200 Subject: [PATCH 0340/1012] Reduced token size --- src/build/errors/Error.go | 8 ++++---- src/build/token/Position.go | 4 ++++ src/build/token/Token.go | 6 +++--- src/build/token/Tokenize.go | 20 ++++++++++---------- 4 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 src/build/token/Position.go diff --git a/src/build/errors/Error.go b/src/build/errors/Error.go index 0821e25..f0c54ef 100644 --- a/src/build/errors/Error.go +++ b/src/build/errors/Error.go @@ -14,13 +14,13 @@ type Error struct { Err error File *fs.File Stack string - Position int + Position token.Position } // New generates an error message at the current token position. // The error message is clickable in popular editors and leads you // directly to the faulty file at the given line and position. -func New(err error, file *fs.File, position int) *Error { +func New(err error, file *fs.File, position token.Position) *Error { return &Error{ Err: err, File: file, @@ -48,12 +48,12 @@ func (e *Error) Error() string { for _, t := range e.File.Tokens { if t.Position >= e.Position { - column = e.Position - lineStart + column = int(e.Position) - lineStart break } if t.Kind == token.NewLine { - lineStart = t.Position + lineStart = int(t.Position) line++ } } diff --git a/src/build/token/Position.go b/src/build/token/Position.go new file mode 100644 index 0000000..2227037 --- /dev/null +++ b/src/build/token/Position.go @@ -0,0 +1,4 @@ +package token + +// Position is the data type for storing file offsets. +type Position = uint32 diff --git a/src/build/token/Token.go b/src/build/token/Token.go index bf1208b..71dd1e5 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -10,13 +10,13 @@ import ( // This makes parsing easier and allows us to do better syntax checks. type Token struct { Bytes []byte - Position int + Position Position Kind Kind } // End returns the position after the token. -func (t *Token) End() int { - return t.Position + len(t.Bytes) +func (t *Token) End() Position { + return t.Position + Position(len(t.Bytes)) } // String creates a human readable representation for debugging purposes. diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 343411c..649f376 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -18,11 +18,11 @@ var ( // Tokenize turns the file contents into a list of tokens. func Tokenize(buffer []byte) List { var ( - i int + i Position tokens = make(List, 0, 8+len(buffer)/2) ) - for i < len(buffer) { + for i < Position(len(buffer)) { switch buffer[i] { case ' ', '\t': case ',': @@ -42,11 +42,11 @@ func Tokenize(buffer []byte) List { case '\n': tokens = append(tokens, Token{Kind: NewLine, Position: i, Bytes: newLineBytes}) case '/': - if i+1 >= len(buffer) || buffer[i+1] != '/' { + if i+1 >= Position(len(buffer)) || buffer[i+1] != '/' { position := i i++ - for i < len(buffer) && isOperator(buffer[i]) { + for i < Position(len(buffer)) && isOperator(buffer[i]) { i++ } @@ -54,7 +54,7 @@ func Tokenize(buffer []byte) List { } else { position := i - for i < len(buffer) && buffer[i] != '\n' { + for i < Position(len(buffer)) && buffer[i] != '\n' { i++ } @@ -65,10 +65,10 @@ func Tokenize(buffer []byte) List { case '"': start := i - end := len(buffer) + end := Position(len(buffer)) i++ - for i < len(buffer) { + for i < Position(len(buffer)) { if buffer[i] == '"' { end = i + 1 i++ @@ -86,7 +86,7 @@ func Tokenize(buffer []byte) List { position := i i++ - for i < len(buffer) && isIdentifier(buffer[i]) { + for i < Position(len(buffer)) && isIdentifier(buffer[i]) { i++ } @@ -106,7 +106,7 @@ func Tokenize(buffer []byte) List { position := i i++ - for i < len(buffer) && isNumber(buffer[i]) { + for i < Position(len(buffer)) && isNumber(buffer[i]) { i++ } @@ -118,7 +118,7 @@ func Tokenize(buffer []byte) List { position := i i++ - for i < len(buffer) && isOperator(buffer[i]) { + for i < Position(len(buffer)) && isOperator(buffer[i]) { i++ } From 1e3705df559ad57e3af106847c8fbb3c3ffcd798 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 21 Jul 2024 14:35:06 +0200 Subject: [PATCH 0341/1012] Reduced token size --- src/build/ast/AST.go | 4 +- src/build/ast/Assign.go | 4 - src/build/ast/Call.go | 4 - src/build/ast/Count.go | 16 +- src/build/ast/Define.go | 6 - src/build/ast/If.go | 6 - src/build/ast/Loop.go | 6 - src/build/ast/Parse.go | 37 +- src/build/ast/Return.go | 6 - src/build/core/Compare.go | 2 +- src/build/core/CompileAssign.go | 10 +- src/build/core/CompileCall.go | 4 +- src/build/core/CompileCondition.go | 25 +- src/build/core/CompileDefinition.go | 4 +- src/build/core/CompileIf.go | 2 +- src/build/core/CompileLoop.go | 2 +- src/build/core/CompileTokens.go | 4 +- src/build/core/ExecuteLeaf.go | 6 +- src/build/core/ExecuteRegisterNumber.go | 16 +- src/build/core/ExecuteRegisterRegister.go | 16 +- src/build/core/Function.go | 2 +- src/build/core/JumpIfFalse.go | 15 +- src/build/core/JumpIfTrue.go | 15 +- src/build/core/NewFunction.go | 2 +- src/build/core/TokenToRegister.go | 6 +- src/build/expression/Expression.go | 24 +- src/build/expression/Expression_test.go | 30 +- src/build/expression/Operator.go | 68 +-- src/build/expression/Parse.go | 27 +- src/build/fs/File.go | 1 + src/build/keyword/Keyword.go | 16 - src/build/scanner/scanFile.go | 20 +- src/build/scope/Stack.go | 4 +- src/build/token/Count.go | 4 +- src/build/token/Count_test.go | 11 +- src/build/token/Keywords.go | 9 + src/build/token/Kind.go | 127 ++--- src/build/token/Kind_test.go | 27 - src/build/token/Length.go | 4 + src/build/token/List.go | 21 - src/build/token/Operators.go | 39 ++ src/build/token/Token.go | 38 +- src/build/token/Token_test.go | 41 +- src/build/token/Tokenize.go | 53 +- src/build/token/Tokenize_test.go | 521 +++++++----------- ...{InvalidOperator.q => InvalidCharacter4.q} | 0 tests/errors_test.go | 2 +- 47 files changed, 543 insertions(+), 764 deletions(-) delete mode 100644 src/build/keyword/Keyword.go create mode 100644 src/build/token/Keywords.go delete mode 100644 src/build/token/Kind_test.go create mode 100644 src/build/token/Length.go create mode 100644 src/build/token/Operators.go rename tests/errors/{InvalidOperator.q => InvalidCharacter4.q} (100%) diff --git a/src/build/ast/AST.go b/src/build/ast/AST.go index 1c8f6dd..c7c4c47 100644 --- a/src/build/ast/AST.go +++ b/src/build/ast/AST.go @@ -1,6 +1,4 @@ package ast -import "fmt" - -type Node fmt.Stringer +type Node any type AST []Node diff --git a/src/build/ast/Assign.go b/src/build/ast/Assign.go index 32c6819..161f968 100644 --- a/src/build/ast/Assign.go +++ b/src/build/ast/Assign.go @@ -8,7 +8,3 @@ import ( type Assign struct { Expression *expression.Expression } - -func (node *Assign) String() string { - return node.Expression.String() -} diff --git a/src/build/ast/Call.go b/src/build/ast/Call.go index d7825b3..6bd7dec 100644 --- a/src/build/ast/Call.go +++ b/src/build/ast/Call.go @@ -6,7 +6,3 @@ import "git.akyoto.dev/cli/q/src/build/expression" type Call struct { Expression *expression.Expression } - -func (node *Call) String() string { - return node.Expression.String() -} diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index 0cc6986..b14c652 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -3,31 +3,31 @@ package ast import "git.akyoto.dev/cli/q/src/build/token" // Count counts how often the given token appears in the AST. -func Count(body AST, kind token.Kind, name string) int { +func Count(body AST, buffer []byte, kind token.Kind, name string) int { count := 0 for _, node := range body { switch node := node.(type) { case *Assign: - count += node.Expression.Count(kind, name) + count += node.Expression.Count(buffer, kind, name) case *Call: - count += node.Expression.Count(kind, name) + count += node.Expression.Count(buffer, kind, name) case *Define: - count += node.Value.Count(kind, name) + count += node.Value.Count(buffer, kind, name) case *Return: if node.Value != nil { - count += node.Value.Count(kind, name) + count += node.Value.Count(buffer, kind, name) } case *If: - count += node.Condition.Count(kind, name) - count += Count(node.Body, kind, name) + count += node.Condition.Count(buffer, kind, name) + count += Count(node.Body, buffer, kind, name) case *Loop: - count += Count(node.Body, kind, name) + count += Count(node.Body, buffer, kind, name) default: panic("unknown AST type") diff --git a/src/build/ast/Define.go b/src/build/ast/Define.go index 6751491..64f0c15 100644 --- a/src/build/ast/Define.go +++ b/src/build/ast/Define.go @@ -1,8 +1,6 @@ package ast import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" ) @@ -12,7 +10,3 @@ type Define struct { Value *expression.Expression Name token.Token } - -func (node *Define) String() string { - return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) -} diff --git a/src/build/ast/If.go b/src/build/ast/If.go index 47d98d4..4275f34 100644 --- a/src/build/ast/If.go +++ b/src/build/ast/If.go @@ -1,8 +1,6 @@ package ast import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/expression" ) @@ -11,7 +9,3 @@ type If struct { Condition *expression.Expression Body AST } - -func (node *If) String() string { - return fmt.Sprintf("(if %s %s)", node.Condition, node.Body) -} diff --git a/src/build/ast/Loop.go b/src/build/ast/Loop.go index 5b0dc33..e1ca210 100644 --- a/src/build/ast/Loop.go +++ b/src/build/ast/Loop.go @@ -1,12 +1,6 @@ package ast -import "fmt" - // Loop represents a block of repeatable statements. type Loop struct { Body AST } - -func (node *Loop) String() string { - return fmt.Sprintf("(loop %s)", node.Body) -} diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index eae5666..5610e1a 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -3,16 +3,15 @@ package ast import ( "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/keyword" "git.akyoto.dev/cli/q/src/build/token" ) // Parse generates an AST from a list of tokens. -func Parse(tokens token.List) (AST, error) { +func Parse(tokens []token.Token, buffer []byte) (AST, error) { tree := make(AST, 0, len(tokens)/64) err := EachInstruction(tokens, func(instruction token.List) error { - node, err := toASTNode(instruction) + node, err := toASTNode(instruction, buffer) if err == nil && node != nil { tree = append(tree, node) @@ -25,11 +24,9 @@ func Parse(tokens token.List) (AST, error) { } // toASTNode generates an AST node from an instruction. -func toASTNode(tokens token.List) (Node, error) { - if tokens[0].Kind == token.Keyword { - word := tokens[0].Text() - - if word == keyword.Return { +func toASTNode(tokens token.List, buffer []byte) (Node, error) { + if tokens[0].IsKeyword() { + if tokens[0].Kind == token.Return { if len(tokens) == 1 { return &Return{}, nil } @@ -38,7 +35,7 @@ func toASTNode(tokens token.List) (Node, error) { return &Return{Value: value}, nil } - if keywordHasBlock(word) { + if keywordHasBlock(tokens[0].Kind) { blockStart := tokens.IndexKind(token.BlockStart) blockEnd := tokens.LastIndexKind(token.BlockEnd) @@ -50,19 +47,19 @@ func toASTNode(tokens token.List) (Node, error) { return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) } - body, err := Parse(tokens[blockStart+1 : blockEnd]) + body, err := Parse(tokens[blockStart+1:blockEnd], buffer) - switch word { - case keyword.If: + switch tokens[0].Kind { + case token.If: condition := expression.Parse(tokens[1:blockStart]) return &If{Condition: condition, Body: body}, err - case keyword.Loop: + case token.Loop: return &Loop{Body: body}, err } } - return nil, errors.New(&errors.KeywordNotImplemented{Keyword: word}, nil, tokens[0].Position) + return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text(buffer)}, nil, tokens[0].Position) } expr := expression.Parse(tokens) @@ -92,26 +89,26 @@ func toASTNode(tokens token.List) (Node, error) { return &Call{Expression: expr}, nil default: - return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, nil, expr.Token.Position) + return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text(buffer)}, nil, expr.Token.Position) } } // IsAssignment returns true if the expression is an assignment. func IsAssignment(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '=' + return expr.Token.IsAssignment() } // IsFunctionCall returns true if the expression is a function call. func IsFunctionCall(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" + return expr.Token.Kind == token.Call } // IsVariableDefinition returns true if the expression is a variable definition. func IsVariableDefinition(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" + return expr.Token.Kind == token.Define } // keywordHasBlock returns true if the keyword requires a block. -func keywordHasBlock(word string) bool { - return word == keyword.If || word == keyword.Loop +func keywordHasBlock(kind token.Kind) bool { + return kind == token.If || kind == token.Loop } diff --git a/src/build/ast/Return.go b/src/build/ast/Return.go index a56520f..37574e2 100644 --- a/src/build/ast/Return.go +++ b/src/build/ast/Return.go @@ -1,8 +1,6 @@ package ast import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/expression" ) @@ -10,7 +8,3 @@ import ( type Return struct { Value *expression.Expression } - -func (node *Return) String() string { - return fmt.Sprintf("(return %s)", node.Value) -} diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 3be29ec..6cd0b5d 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -13,7 +13,7 @@ func (f *Function) Compare(comparison *expression.Expression) error { right := comparison.Children[1] if left.IsLeaf() && left.Token.Kind == token.Identifier { - name := left.Token.Text() + name := left.Token.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index 384630d..4d3e08b 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -16,7 +16,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { right := node.Expression.Children[1] if left.IsLeaf() { - name := left.Token.Text() + name := left.Token.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { @@ -27,8 +27,8 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return f.Execute(operator, variable.Register, right) } - if left.Token.Kind == token.Operator && left.Token.Text() == "@" { - name := left.Children[0].Token.Text() + if left.Token.Kind == token.Array { + name := left.Children[0].Token.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { @@ -38,13 +38,13 @@ func (f *Function) CompileAssign(node *ast.Assign) error { defer f.useVariable(variable) index := left.Children[1] - offset, err := strconv.Atoi(index.Token.Text()) + offset, err := strconv.Atoi(index.Token.Text(f.File.Bytes)) if err != nil { return err } - num, err := strconv.Atoi(right.Token.Text()) + num, err := strconv.Atoi(right.Token.Text(f.File.Bytes)) if err != nil { return err diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 4c2adc6..3270d01 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -15,9 +15,9 @@ func (f *Function) CompileCall(root *expression.Expression) error { funcName := "" if funcNameRoot.IsLeaf() { - funcName = funcNameRoot.Token.Text() + funcName = funcNameRoot.Token.Text(f.File.Bytes) } else { - funcName = funcNameRoot.Children[0].Token.Text() + funcNameRoot.Token.Text() + funcNameRoot.Children[1].Token.Text() + funcName = funcNameRoot.Children[0].Token.Text(f.File.Bytes) + funcNameRoot.Token.Text(f.File.Bytes) + funcNameRoot.Children[1].Token.Text(f.File.Bytes) } isSyscall := funcName == "syscall" diff --git a/src/build/core/CompileCondition.go b/src/build/core/CompileCondition.go index 621a7e7..54e0ab0 100644 --- a/src/build/core/CompileCondition.go +++ b/src/build/core/CompileCondition.go @@ -4,12 +4,13 @@ import ( "fmt" "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" ) // CompileCondition inserts code to jump to the start label or end label depending on the truth of the condition. func (f *Function) CompileCondition(condition *expression.Expression, successLabel string, failLabel string) error { - switch condition.Token.Text() { - case "||": + switch condition.Token.Kind { + case token.LogicalOr: f.count.subBranch++ leftFailLabel := fmt.Sprintf("%s_false_%d", f.Name, f.count.subBranch) @@ -21,22 +22,22 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab return err } - f.JumpIfTrue(left.Token.Text(), successLabel) + f.JumpIfTrue(left.Token.Kind, successLabel) // Right f.AddLabel(leftFailLabel) right := condition.Children[1] err = f.CompileCondition(right, successLabel, failLabel) - if condition.Parent != nil && condition.Parent.Token.Text() == "||" && condition != condition.Parent.LastChild() { - f.JumpIfTrue(right.Token.Text(), successLabel) + if condition.Parent != nil && condition.Parent.Token.Kind == token.LogicalOr && condition != condition.Parent.LastChild() { + f.JumpIfTrue(right.Token.Kind, successLabel) } else { - f.JumpIfFalse(right.Token.Text(), failLabel) + f.JumpIfFalse(right.Token.Kind, failLabel) } return err - case "&&": + case token.LogicalAnd: f.count.subBranch++ leftSuccessLabel := fmt.Sprintf("%s_true_%d", f.Name, f.count.subBranch) @@ -48,17 +49,17 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab return err } - f.JumpIfFalse(left.Token.Text(), failLabel) + f.JumpIfFalse(left.Token.Kind, failLabel) // Right f.AddLabel(leftSuccessLabel) right := condition.Children[1] err = f.CompileCondition(right, successLabel, failLabel) - if condition.Parent != nil && condition.Parent.Token.Text() == "||" && condition != condition.Parent.LastChild() { - f.JumpIfTrue(right.Token.Text(), successLabel) + if condition.Parent != nil && condition.Parent.Token.Kind == token.LogicalOr && condition != condition.Parent.LastChild() { + f.JumpIfTrue(right.Token.Kind, successLabel) } else { - f.JumpIfFalse(right.Token.Text(), failLabel) + f.JumpIfFalse(right.Token.Kind, failLabel) } return err @@ -67,7 +68,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab err := f.Compare(condition) if condition.Parent == nil { - f.JumpIfFalse(condition.Token.Text(), failLabel) + f.JumpIfFalse(condition.Token.Kind, failLabel) } return err diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index a8fa1eb..c5473b1 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -8,13 +8,13 @@ import ( // CompileDefinition compiles a variable definition. func (f *Function) CompileDefinition(node *ast.Define) error { - name := node.Name.Text() + name := node.Name.Text(f.File.Bytes) if f.identifierExists(name) { return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, node.Name.Position) } - uses := token.Count(f.Body, token.Identifier, name) - 1 + uses := token.Count(f.Body, f.File.Bytes, token.Identifier, name) - 1 if uses == 0 { return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 1b61c3c..3eeb4ae 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -18,7 +18,7 @@ func (f *Function) CompileIf(branch *ast.If) error { } f.AddLabel(success) - f.PushScope(branch.Body) + f.PushScope(branch.Body, f.File.Bytes) err = f.CompileAST(branch.Body) f.PopScope() f.AddLabel(fail) diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index de9d8b6..a1b116a 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -12,7 +12,7 @@ func (f *Function) CompileLoop(loop *ast.Loop) error { f.count.loop++ label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) f.AddLabel(label) - scope := f.PushScope(loop.Body) + scope := f.PushScope(loop.Body, f.File.Bytes) scope.InLoop = true err := f.CompileAST(loop.Body) f.Jump(asm.JUMP, label) diff --git a/src/build/core/CompileTokens.go b/src/build/core/CompileTokens.go index 7b41804..634c5d7 100644 --- a/src/build/core/CompileTokens.go +++ b/src/build/core/CompileTokens.go @@ -7,8 +7,8 @@ import ( ) // CompileTokens compiles a token list. -func (f *Function) CompileTokens(tokens token.List) error { - body, err := ast.Parse(tokens) +func (f *Function) CompileTokens(tokens []token.Token) error { + body, err := ast.Parse(tokens, f.File.Bytes) if err != nil { err.(*errors.Error).File = f.File diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index 947518d..d6cc124 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -12,7 +12,7 @@ import ( func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error { switch operand.Kind { case token.Identifier: - name := operand.Text() + name := operand.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { @@ -23,7 +23,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope return f.ExecuteRegisterRegister(operation, register, variable.Register) case token.Number: - value := operand.Text() + value := operand.Text(f.File.Bytes) number, err := strconv.Atoi(value) if err != nil { @@ -33,7 +33,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope return f.ExecuteRegisterNumber(operation, register, number) case token.String: - if operation.Text() == "=" { + if operation.Kind == token.Assign { return f.TokenToRegister(operand, register) } } diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index 19b07ea..43675a2 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -9,27 +9,27 @@ import ( // ExecuteRegisterNumber performs an operation on a register and a number. func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { - switch operation.Text() { - case "+", "+=": + switch operation.Kind { + case token.Add, token.AddAssign: f.RegisterNumber(asm.ADD, register, number) - case "-", "-=": + case token.Sub, token.SubAssign: f.RegisterNumber(asm.SUB, register, number) - case "*", "*=": + case token.Mul, token.MulAssign: f.RegisterNumber(asm.MUL, register, number) - case "/", "/=": + case token.Div, token.DivAssign: f.RegisterNumber(asm.DIV, register, number) - case "==", "!=", "<", "<=", ">", ">=": + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: f.RegisterNumber(asm.COMPARE, register, number) - case "=": + case token.Assign: f.RegisterNumber(asm.MOVE, register, number) default: - return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) + return errors.New(&errors.InvalidOperator{Operator: operation.Text(f.File.Bytes)}, f.File, operation.Position) } return nil diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index c163e23..8206b4f 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -9,27 +9,27 @@ import ( // ExecuteRegisterRegister performs an operation on two registers. func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { - switch operation.Text() { - case "+", "+=": + switch operation.Kind { + case token.Add, token.AddAssign: f.RegisterRegister(asm.ADD, destination, source) - case "-", "-=": + case token.Sub, token.SubAssign: f.RegisterRegister(asm.SUB, destination, source) - case "*", "*=": + case token.Mul, token.MulAssign: f.RegisterRegister(asm.MUL, destination, source) - case "/", "/=": + case token.Div, token.DivAssign: f.RegisterRegister(asm.DIV, destination, source) - case "==", "!=", "<", "<=", ">", ">=": + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: f.RegisterRegister(asm.COMPARE, destination, source) - case "=": + case token.Assign: f.RegisterRegister(asm.MOVE, destination, source) default: - return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) + return errors.New(&errors.InvalidOperator{Operator: operation.Text(f.File.Bytes)}, f.File, operation.Position) } return nil diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 79e1b31..36ac29d 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -13,7 +13,7 @@ type Function struct { scope.Stack Name string File *fs.File - Body token.List + Body []token.Token Assembler asm.Assembler Functions map[string]*Function Err error diff --git a/src/build/core/JumpIfFalse.go b/src/build/core/JumpIfFalse.go index baedd51..042a676 100644 --- a/src/build/core/JumpIfFalse.go +++ b/src/build/core/JumpIfFalse.go @@ -2,22 +2,23 @@ package core import ( "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/token" ) // JumpIfFalse jumps to the label if the previous comparison was false. -func (f *Function) JumpIfFalse(operator string, label string) { +func (f *Function) JumpIfFalse(operator token.Kind, label string) { switch operator { - case "==": + case token.Equal: f.Jump(asm.JNE, label) - case "!=": + case token.NotEqual: f.Jump(asm.JE, label) - case ">": + case token.Greater: f.Jump(asm.JLE, label) - case "<": + case token.Less: f.Jump(asm.JGE, label) - case ">=": + case token.GreaterEqual: f.Jump(asm.JL, label) - case "<=": + case token.LessEqual: f.Jump(asm.JG, label) } } diff --git a/src/build/core/JumpIfTrue.go b/src/build/core/JumpIfTrue.go index 4c04a0b..059618e 100644 --- a/src/build/core/JumpIfTrue.go +++ b/src/build/core/JumpIfTrue.go @@ -2,22 +2,23 @@ package core import ( "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/token" ) // JumpIfTrue jumps to the label if the previous comparison was true. -func (f *Function) JumpIfTrue(operator string, label string) { +func (f *Function) JumpIfTrue(operator token.Kind, label string) { switch operator { - case "==": + case token.Equal: f.Jump(asm.JE, label) - case "!=": + case token.NotEqual: f.Jump(asm.JNE, label) - case ">": + case token.Greater: f.Jump(asm.JG, label) - case "<": + case token.Less: f.Jump(asm.JL, label) - case ">=": + case token.GreaterEqual: f.Jump(asm.JGE, label) - case "<=": + case token.LessEqual: f.Jump(asm.JLE, label) } } diff --git a/src/build/core/NewFunction.go b/src/build/core/NewFunction.go index 1394c4e..2dc683d 100644 --- a/src/build/core/NewFunction.go +++ b/src/build/core/NewFunction.go @@ -10,7 +10,7 @@ import ( ) // NewFunction creates a new function. -func NewFunction(name string, file *fs.File, body token.List) *Function { +func NewFunction(name string, file *fs.File, body []token.Token) *Function { return &Function{ Name: name, File: file, diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index a40f471..f048fac 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -15,7 +15,7 @@ import ( func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { switch t.Kind { case token.Identifier: - name := t.Text() + name := t.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { @@ -27,7 +27,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return nil case token.Number: - value := t.Text() + value := t.Text(f.File.Bytes) n, err := strconv.Atoi(value) if err != nil { @@ -40,7 +40,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { case token.String: f.count.data++ label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data) - value := t.Bytes[1 : len(t.Bytes)-1] + value := t.Bytes(f.File.Bytes)[1 : t.Length-1] f.Assembler.SetData(label, value) f.RegisterLabel(asm.MOVE, register, label) return nil diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 60b3794..5027b8e 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -37,11 +37,11 @@ func (expr *Expression) AddChild(child *Expression) { } // Count counts how often the given token appears in the expression. -func (expr *Expression) Count(kind token.Kind, name string) int { +func (expr *Expression) Count(buffer []byte, kind token.Kind, name string) int { count := 0 expr.EachLeaf(func(leaf *Expression) error { - if leaf.Token.Kind == kind && leaf.Token.Text() == name { + if leaf.Token.Kind == kind && leaf.Token.Text(buffer) == name { count++ } @@ -112,25 +112,33 @@ func (expr *Expression) LastChild() *Expression { } // String generates a textual representation of the expression. -func (expr *Expression) String() string { +func (expr *Expression) String(data []byte) string { builder := strings.Builder{} - expr.write(&builder) + expr.write(&builder, data) return builder.String() } // write generates a textual representation of the expression. -func (expr *Expression) write(builder *strings.Builder) { +func (expr *Expression) write(builder *strings.Builder, data []byte) { if expr.IsLeaf() { - builder.WriteString(expr.Token.Text()) + builder.WriteString(expr.Token.Text(data)) return } builder.WriteByte('(') - builder.WriteString(expr.Token.Text()) + + switch expr.Token.Kind { + case token.Call: + builder.WriteString("λ") + case token.Array: + builder.WriteString("@") + default: + builder.WriteString(expr.Token.Text(data)) + } for _, child := range expr.Children { builder.WriteByte(' ') - child.write(builder) + child.write(builder, data) } builder.WriteByte(')') diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index 6794da8..a313cf3 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -95,7 +95,7 @@ func TestParse(t *testing.T) { defer expr.Reset() assert.NotNil(t, expr) - assert.Equal(t, expr.String(), test.Result) + assert.Equal(t, expr.String(src), test.Result) }) } } @@ -104,11 +104,11 @@ func TestCount(t *testing.T) { src := []byte("(a+b-c*d)+(a*b-c+d)") tokens := token.Tokenize(src) expr := expression.Parse(tokens) - assert.Equal(t, expr.Count(token.Identifier, "a"), 2) - assert.Equal(t, expr.Count(token.Identifier, "b"), 2) - assert.Equal(t, expr.Count(token.Identifier, "c"), 2) - assert.Equal(t, expr.Count(token.Identifier, "d"), 2) - assert.Equal(t, expr.Count(token.Identifier, "e"), 0) + assert.Equal(t, expr.Count(src, token.Identifier, "a"), 2) + assert.Equal(t, expr.Count(src, token.Identifier, "b"), 2) + assert.Equal(t, expr.Count(src, token.Identifier, "c"), 2) + assert.Equal(t, expr.Count(src, token.Identifier, "d"), 2) + assert.Equal(t, expr.Count(src, token.Identifier, "e"), 0) } func TestEachLeaf(t *testing.T) { @@ -118,7 +118,7 @@ func TestEachLeaf(t *testing.T) { leaves := []string{} err := expr.EachLeaf(func(leaf *expression.Expression) error { - leaves = append(leaves, leaf.Token.Text()) + leaves = append(leaves, leaf.Token.Text(src)) return nil }) @@ -140,7 +140,7 @@ func TestEachParameter(t *testing.T) { err := expression.EachParameter(tokens, func(parameter token.List) error { expr := expression.Parse(parameter) - parameters = append(parameters, expr.String()) + parameters = append(parameters, expr.String(src)) return nil }) @@ -178,17 +178,3 @@ func TestNilGroup(t *testing.T) { expr := expression.Parse(tokens) assert.Nil(t, expr) } - -func TestInvalidOperator(t *testing.T) { - src := []byte("a +++ 2") - tokens := token.Tokenize(src) - expr := expression.Parse(tokens) - assert.Equal(t, expr.String(), "(+++ a 2)") -} - -func TestInvalidOperatorCall(t *testing.T) { - src := []byte("+++()") - tokens := token.Tokenize(src) - expr := expression.Parse(tokens) - assert.NotNil(t, expr) -} diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index b2d15e1..1d9e18b 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -15,39 +15,39 @@ type Operator struct { // Operators defines the operators used in the language. // The number corresponds to the operator priority and can not be zero. -var Operators = map[string]*Operator{ - ".": {".", 13, 2}, - "λ": {"λ", 12, 1}, - "@": {"@", 12, 2}, - "!": {"!", 11, 1}, - "*": {"*", 10, 2}, - "/": {"/", 10, 2}, - "%": {"%", 10, 2}, - "+": {"+", 9, 2}, - "-": {"-", 9, 2}, - ">>": {">>", 8, 2}, - "<<": {"<<", 8, 2}, - "&": {"&", 7, 2}, - "^": {"^", 6, 2}, - "|": {"|", 5, 2}, +var Operators = map[token.Kind]*Operator{ + token.Period: {".", 13, 2}, + token.Call: {"λ", 12, 1}, + token.Array: {"@", 12, 2}, + token.Not: {"!", 11, 1}, + token.Mul: {"*", 10, 2}, + token.Div: {"/", 10, 2}, + token.Mod: {"%", 10, 2}, + token.Add: {"+", 9, 2}, + token.Sub: {"-", 9, 2}, + token.Shr: {">>", 8, 2}, + token.Shl: {"<<", 8, 2}, + token.And: {"&", 7, 2}, + token.Xor: {"^", 6, 2}, + token.Or: {"|", 5, 2}, - ">": {">", 4, 2}, - "<": {"<", 4, 2}, - ">=": {">=", 4, 2}, - "<=": {"<=", 4, 2}, - "==": {"==", 3, 2}, - "!=": {"!=", 3, 2}, - "&&": {"&&", 2, 2}, - "||": {"||", 1, 2}, + token.Greater: {">", 4, 2}, + token.Less: {"<", 4, 2}, + token.GreaterEqual: {">=", 4, 2}, + token.LessEqual: {"<=", 4, 2}, + token.Equal: {"==", 3, 2}, + token.NotEqual: {"!=", 3, 2}, + token.LogicalAnd: {"&&", 2, 2}, + token.LogicalOr: {"||", 1, 2}, - "=": {"=", math.MinInt8, 2}, - ":=": {":=", math.MinInt8, 2}, - "+=": {"+=", math.MinInt8, 2}, - "-=": {"-=", math.MinInt8, 2}, - "*=": {"*=", math.MinInt8, 2}, - "/=": {"/=", math.MinInt8, 2}, - ">>=": {">>=", math.MinInt8, 2}, - "<<=": {"<<=", math.MinInt8, 2}, + token.Assign: {"=", math.MinInt8, 2}, + token.Define: {":=", math.MinInt8, 2}, + token.AddAssign: {"+=", math.MinInt8, 2}, + token.SubAssign: {"-=", math.MinInt8, 2}, + token.MulAssign: {"*=", math.MinInt8, 2}, + token.DivAssign: {"/=", math.MinInt8, 2}, + token.ShrAssign: {">>=", math.MinInt8, 2}, + token.ShlAssign: {"<<=", math.MinInt8, 2}, } func isComplete(expr *Expression) bool { @@ -59,14 +59,14 @@ func isComplete(expr *Expression) bool { return true } - if expr.Token.Kind == token.Operator && len(expr.Children) == numOperands(expr.Token.Text()) { + if expr.Token.IsOperator() && len(expr.Children) == numOperands(expr.Token.Kind) { return true } return false } -func numOperands(symbol string) int { +func numOperands(symbol token.Kind) int { operator, exists := Operators[symbol] if !exists { @@ -76,7 +76,7 @@ func numOperands(symbol string) int { return operator.Operands } -func precedence(symbol string) int8 { +func precedence(symbol token.Kind) int8 { operator, exists := Operators[symbol] if !exists { diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index 33dd062..f2e8b56 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -6,13 +6,8 @@ import ( "git.akyoto.dev/cli/q/src/build/token" ) -var ( - call = []byte("λ") - array = []byte("@") -) - // Parse generates an expression tree from tokens. -func Parse(tokens token.List) *Expression { +func Parse(tokens []token.Token) *Expression { var ( cursor *Expression root *Expression @@ -43,20 +38,18 @@ func Parse(tokens token.List) *Expression { parameters := NewList(tokens[groupPosition:i]) node := New() - node.Token.Kind = token.Operator node.Token.Position = tokens[groupPosition].Position switch t.Kind { case token.GroupEnd: - node.Token.Bytes = call - node.Precedence = precedence("λ") - + node.Token.Kind = token.Call case token.ArrayEnd: - node.Token.Bytes = array - node.Precedence = precedence("@") + node.Token.Kind = token.Array } - if cursor.Token.Kind == token.Operator && node.Precedence > cursor.Precedence { + node.Precedence = precedence(node.Token.Kind) + + if cursor.Token.IsOperator() && node.Precedence > cursor.Precedence { cursor.LastChild().Replace(node) } else { if cursor == root { @@ -108,18 +101,18 @@ func Parse(tokens token.List) *Expression { continue } - if t.Kind == token.Operator { + if t.IsOperator() { if cursor == nil { cursor = NewLeaf(t) - cursor.Precedence = precedence(t.Text()) + cursor.Precedence = precedence(t.Kind) root = cursor continue } node := NewLeaf(t) - node.Precedence = precedence(t.Text()) + node.Precedence = precedence(t.Kind) - if cursor.Token.Kind == token.Operator { + if cursor.Token.IsOperator() { oldPrecedence := cursor.Precedence newPrecedence := node.Precedence diff --git a/src/build/fs/File.go b/src/build/fs/File.go index d4fd1cf..1a10a1d 100644 --- a/src/build/fs/File.go +++ b/src/build/fs/File.go @@ -5,5 +5,6 @@ import "git.akyoto.dev/cli/q/src/build/token" // File represents a single source file. type File struct { Path string + Bytes []byte Tokens token.List } diff --git a/src/build/keyword/Keyword.go b/src/build/keyword/Keyword.go deleted file mode 100644 index ef62575..0000000 --- a/src/build/keyword/Keyword.go +++ /dev/null @@ -1,16 +0,0 @@ -package keyword - -const ( - If = "if" - Import = "import" - Loop = "loop" - Return = "return" -) - -// Map is a map of all keywords used in the language. -var Map = map[string][]byte{ - If: []byte(If), - Import: []byte(Import), - Loop: []byte(Loop), - Return: []byte(Return), -} diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index b075fc6..e724767 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -11,7 +11,6 @@ import ( "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" - "git.akyoto.dev/cli/q/src/build/keyword" "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" ) @@ -27,8 +26,9 @@ func (s *Scanner) scanFile(path string, pkg string) error { tokens := token.Tokenize(contents) file := &fs.File{ - Tokens: tokens, Path: path, + Bytes: contents, + Tokens: tokens, } var ( @@ -42,14 +42,14 @@ func (s *Scanner) scanFile(path string, pkg string) error { ) for { - for i < len(tokens) && tokens[i].Kind == token.Keyword && tokens[i].Text() == keyword.Import { + for i < len(tokens) && tokens[i].Kind == token.Import { i++ if tokens[i].Kind != token.Identifier { panic("expected package name") } - packageName := tokens[i].Text() + packageName := tokens[i].Text(contents) s.queueDirectory(filepath.Join(config.Library, packageName), packageName) i++ @@ -75,7 +75,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { } if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(contents)}, file, tokens[i].Position) } if tokens[i].Kind == token.EOF { @@ -116,7 +116,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { } if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(contents)}, file, tokens[i].Position) } if tokens[i].Kind == token.EOF { @@ -168,7 +168,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { } if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(contents)}, file, tokens[i].Position) } if tokens[i].Kind == token.EOF { @@ -191,7 +191,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) } - name := tokens[nameStart].Text() + name := tokens[nameStart].Text(contents) body := tokens[bodyStart:i] if pkg != "" { @@ -207,9 +207,9 @@ func (s *Scanner) scanFile(path string, pkg string) error { return errors.New(errors.NotImplemented, file, tokens[0].Position) } - name := tokens[0].Text() + name := tokens[0].Text(contents) register := x64.CallRegisters[count] - uses := token.Count(function.Body, token.Identifier, name) + uses := token.Count(function.Body, contents, token.Identifier, name) if uses == 0 { return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) diff --git a/src/build/scope/Stack.go b/src/build/scope/Stack.go index 5f634ff..1760f6b 100644 --- a/src/build/scope/Stack.go +++ b/src/build/scope/Stack.go @@ -20,7 +20,7 @@ func (stack *Stack) PopScope() { } // PushScope pushes a new scope to the top of the stack. -func (stack *Stack) PushScope(body ast.AST) *Scope { +func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { s := &Scope{} if len(stack.Scopes) > 0 { @@ -30,7 +30,7 @@ func (stack *Stack) PushScope(body ast.AST) *Scope { s.InLoop = lastScope.InLoop for k, v := range lastScope.Variables { - count := ast.Count(body, token.Identifier, v.Name) + count := ast.Count(body, buffer, token.Identifier, v.Name) if count == 0 { continue diff --git a/src/build/token/Count.go b/src/build/token/Count.go index ae83143..e83fff6 100644 --- a/src/build/token/Count.go +++ b/src/build/token/Count.go @@ -1,11 +1,11 @@ package token // Count counts how often the given token appears in the token list. -func Count(tokens List, kind Kind, name string) int { +func Count(tokens []Token, buffer []byte, kind Kind, name string) int { count := 0 for _, t := range tokens { - if t.Kind == Identifier && t.Text() == name { + if t.Kind == Identifier && t.Text(buffer) == name { count++ } } diff --git a/src/build/token/Count_test.go b/src/build/token/Count_test.go index 0128e62..8742d13 100644 --- a/src/build/token/Count_test.go +++ b/src/build/token/Count_test.go @@ -8,9 +8,10 @@ import ( ) func TestCount(t *testing.T) { - tokens := token.Tokenize([]byte(`a b b c c c`)) - assert.Equal(t, token.Count(tokens, token.Identifier, "a"), 1) - assert.Equal(t, token.Count(tokens, token.Identifier, "b"), 2) - assert.Equal(t, token.Count(tokens, token.Identifier, "c"), 3) - assert.Equal(t, token.Count(tokens, token.Identifier, "d"), 0) + buffer := []byte(`a b b c c c`) + tokens := token.Tokenize(buffer) + assert.Equal(t, token.Count(tokens, buffer, token.Identifier, "a"), 1) + assert.Equal(t, token.Count(tokens, buffer, token.Identifier, "b"), 2) + assert.Equal(t, token.Count(tokens, buffer, token.Identifier, "c"), 3) + assert.Equal(t, token.Count(tokens, buffer, token.Identifier, "d"), 0) } diff --git a/src/build/token/Keywords.go b/src/build/token/Keywords.go new file mode 100644 index 0000000..c75fd75 --- /dev/null +++ b/src/build/token/Keywords.go @@ -0,0 +1,9 @@ +package token + +// Keywords is a map of all keywords used in the language. +var Keywords = map[string]Kind{ + "if": If, + "import": Import, + "loop": Loop, + "return": Return, +} diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index b221442..1df7adf 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -4,73 +4,62 @@ package token type Kind uint8 const ( - // Invalid represents an invalid token. - Invalid Kind = iota - - // EOF represents the end of file. - EOF - - // NewLine represents the newline character. - NewLine - - // Identifier represents a series of characters used to identify a variable or function. - Identifier - - // Keyword represents a language keyword. - Keyword - - // String represents an uninterpreted series of characters in the source code. - String - - // Number represents a series of numerical characters. - Number - - // Operator represents a mathematical operator. - Operator - - // Separator represents a comma. - Separator - - // Comment represents a comment. - Comment - - // GroupStart represents '('. - GroupStart - - // GroupEnd represents ')'. - GroupEnd - - // BlockStart represents '{'. - BlockStart - - // BlockEnd represents '}'. - BlockEnd - - // ArrayStart represents '['. - ArrayStart - - // ArrayEnd represents ']'. - ArrayEnd + Invalid Kind = iota // Invalid is an invalid token. + EOF // EOF is the end of file. + NewLine // NewLine is the newline character. + Identifier // Identifier is a series of characters used to identify a variable or function. + Number // Number is a series of numerical characters. + String // String is an uninterpreted series of characters in the source code. + Comment // Comment is a comment. + Separator // , + GroupStart // ( + GroupEnd // ) + BlockStart // { + BlockEnd // } + ArrayStart // [ + ArrayEnd // ] + _keywords // + If // if + Import // import + Loop // loop + Return // return + _keywordsEnd // + _operators // + Add // + + Sub // - + Mul // * + Div // / + Mod // % + And // & + Or // | + Xor // ^ + Shl // << + Shr // >> + LogicalAnd // && + LogicalOr // || + Equal // == + Less // < + Greater // > + Not // ! + NotEqual // != + LessEqual // <= + GreaterEqual // >= + Define // := + Period // . + Call // x() + Array // [x] + _assignments // + Assign // = + AddAssign // += + SubAssign // -= + MulAssign // *= + DivAssign // /= + ModAssign // %= + AndAssign // &= + OrAssign // |= + XorAssign // ^= + ShlAssign // <<= + ShrAssign // >>= + _assignmentsEnd // + _operatorsEnd // ) - -// String returns the text representation. -func (kind Kind) String() string { - return [...]string{ - "Invalid", - "EOF", - "NewLine", - "Identifier", - "Keyword", - "String", - "Number", - "Operator", - "Separator", - "Comment", - "GroupStart", - "GroupEnd", - "BlockStart", - "BlockEnd", - "ArrayStart", - "ArrayEnd", - }[kind] -} diff --git a/src/build/token/Kind_test.go b/src/build/token/Kind_test.go deleted file mode 100644 index 1db0382..0000000 --- a/src/build/token/Kind_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package token_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/go/assert" -) - -func TestTokenKind(t *testing.T) { - assert.Equal(t, token.Invalid.String(), "Invalid") - assert.Equal(t, token.EOF.String(), "EOF") - assert.Equal(t, token.NewLine.String(), "NewLine") - assert.Equal(t, token.Identifier.String(), "Identifier") - assert.Equal(t, token.Keyword.String(), "Keyword") - assert.Equal(t, token.String.String(), "String") - assert.Equal(t, token.Number.String(), "Number") - assert.Equal(t, token.Operator.String(), "Operator") - assert.Equal(t, token.Separator.String(), "Separator") - assert.Equal(t, token.Comment.String(), "Comment") - assert.Equal(t, token.GroupStart.String(), "GroupStart") - assert.Equal(t, token.GroupEnd.String(), "GroupEnd") - assert.Equal(t, token.BlockStart.String(), "BlockStart") - assert.Equal(t, token.BlockEnd.String(), "BlockEnd") - assert.Equal(t, token.ArrayStart.String(), "ArrayStart") - assert.Equal(t, token.ArrayEnd.String(), "ArrayEnd") -} diff --git a/src/build/token/Length.go b/src/build/token/Length.go new file mode 100644 index 0000000..6711706 --- /dev/null +++ b/src/build/token/Length.go @@ -0,0 +1,4 @@ +package token + +// Length is the data type for storing token lengths. +type Length = uint16 diff --git a/src/build/token/List.go b/src/build/token/List.go index 1b725b6..7e57693 100644 --- a/src/build/token/List.go +++ b/src/build/token/List.go @@ -1,9 +1,5 @@ package token -import ( - "bytes" -) - // List is a slice of tokens. type List []Token @@ -28,20 +24,3 @@ func (list List) LastIndexKind(kind Kind) int { return -1 } - -// String implements string serialization. -func (list List) String() string { - builder := bytes.Buffer{} - var last Token - - for _, t := range list { - if last.Kind == Keyword || last.Kind == Separator || last.Kind == Operator || t.Kind == Operator { - builder.WriteByte(' ') - } - - builder.Write(t.Bytes) - last = t - } - - return builder.String() -} diff --git a/src/build/token/Operators.go b/src/build/token/Operators.go new file mode 100644 index 0000000..842fb94 --- /dev/null +++ b/src/build/token/Operators.go @@ -0,0 +1,39 @@ +package token + +// Operators is a map of all operators used in the language. +var Operators = map[string]Kind{ + ".": Period, + "=": Assign, + ":=": Define, + "+": Add, + "-": Sub, + "*": Mul, + "/": Div, + "%": Mod, + "&": And, + "|": Or, + "^": Xor, + "<<": Shl, + ">>": Shr, + "&&": LogicalAnd, + "||": LogicalOr, + "!": Not, + "==": Equal, + "!=": NotEqual, + ">": Greater, + "<": Less, + ">=": GreaterEqual, + "<=": LessEqual, + "+=": AddAssign, + "-=": SubAssign, + "*=": MulAssign, + "/=": DivAssign, + "%=": ModAssign, + "&=": AndAssign, + "|=": OrAssign, + "^=": XorAssign, + "<<=": ShlAssign, + ">>=": ShrAssign, + "λ": Call, + "@": Array, +} diff --git a/src/build/token/Token.go b/src/build/token/Token.go index 71dd1e5..134f054 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -1,7 +1,6 @@ package token import ( - "fmt" "unsafe" ) @@ -9,29 +8,44 @@ import ( // The characters that make up an identifier are grouped into a single token. // This makes parsing easier and allows us to do better syntax checks. type Token struct { - Bytes []byte Position Position + Length Length Kind Kind } -// End returns the position after the token. -func (t *Token) End() Position { - return t.Position + Position(len(t.Bytes)) +// Bytes returns the byte slice. +func (t Token) Bytes(buffer []byte) []byte { + return buffer[t.Position : t.Position+Position(t.Length)] } -// String creates a human readable representation for debugging purposes. -func (t *Token) String() string { - return fmt.Sprintf("%s %s", t.Kind, t.Text()) +// End returns the position after the token. +func (t Token) End() Position { + return t.Position + Position(t.Length) +} + +// IsAssignment returns true if the token is an assignment operator. +func (t Token) IsAssignment() bool { + return t.Kind > _assignments && t.Kind < _assignmentsEnd +} + +// IsKeyword returns true if the token is a keyword. +func (t Token) IsKeyword() bool { + return t.Kind > _keywords && t.Kind < _keywordsEnd +} + +// IsOperator returns true if the token is an operator. +func (t Token) IsOperator() bool { + return t.Kind > _operators && t.Kind < _operatorsEnd } // Reset resets the token to default values. func (t *Token) Reset() { - t.Kind = Invalid t.Position = 0 - t.Bytes = nil + t.Length = 0 + t.Kind = Invalid } // Text returns the token text. -func (t *Token) Text() string { - return unsafe.String(unsafe.SliceData(t.Bytes), len(t.Bytes)) +func (t Token) Text(buffer []byte) string { + return unsafe.String(unsafe.SliceData(t.Bytes(buffer)), t.Length) } diff --git a/src/build/token/Token_test.go b/src/build/token/Token_test.go index 47fc665..ba825cc 100644 --- a/src/build/token/Token_test.go +++ b/src/build/token/Token_test.go @@ -10,8 +10,8 @@ import ( func TestTokenEnd(t *testing.T) { hello := token.Token{ Kind: token.Identifier, - Bytes: []byte("hello"), Position: 0, + Length: 5, } assert.Equal(t, hello.End(), 5) @@ -20,34 +20,33 @@ func TestTokenEnd(t *testing.T) { func TestTokenReset(t *testing.T) { hello := token.Token{ Kind: token.Identifier, - Bytes: []byte("hello"), Position: 1, + Length: 5, } hello.Reset() - assert.Nil(t, hello.Bytes) assert.Equal(t, hello.Position, 0) + assert.Equal(t, hello.Length, 0) assert.Equal(t, hello.Kind, token.Invalid) } -func TestTokenString(t *testing.T) { - hello := token.Token{ - Kind: token.Identifier, - Bytes: []byte("hello"), - Position: 0, - } - - assert.Equal(t, hello.String(), "Identifier hello") -} - func TestTokenText(t *testing.T) { - hello := token.Token{Kind: token.Identifier, Bytes: []byte("hello"), Position: 0} - comma := token.Token{Kind: token.Separator, Bytes: []byte(","), Position: 5} - world := token.Token{Kind: token.Identifier, Bytes: []byte("world"), Position: 7} + buffer := []byte("hello, world") + hello := token.Token{Kind: token.Identifier, Position: 0, Length: 5} + comma := token.Token{Kind: token.Separator, Position: 5, Length: 1} + world := token.Token{Kind: token.Identifier, Position: 7, Length: 5} - assert.Equal(t, hello.Text(), "hello") - assert.Equal(t, world.Text(), "world") - - list := token.List{hello, comma, world} - assert.Equal(t, list.String(), "hello, world") + assert.Equal(t, hello.Text(buffer), "hello") + assert.Equal(t, comma.Text(buffer), ",") + assert.Equal(t, world.Text(buffer), "world") +} + +func TestTokenGroups(t *testing.T) { + assignment := token.Token{Kind: token.Assign} + operator := token.Token{Kind: token.Add} + keyword := token.Token{Kind: token.If} + + assert.True(t, assignment.IsAssignment()) + assert.True(t, operator.IsOperator()) + assert.True(t, keyword.IsKeyword()) } diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 649f376..0060958 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -1,20 +1,5 @@ package token -import "git.akyoto.dev/cli/q/src/build/keyword" - -// Pre-allocate these byte buffers so we can re-use them -// instead of allocating a new buffer every time. -var ( - groupStartBytes = []byte{'('} - groupEndBytes = []byte{')'} - blockStartBytes = []byte{'{'} - blockEndBytes = []byte{'}'} - arrayStartBytes = []byte{'['} - arrayEndBytes = []byte{']'} - separatorBytes = []byte{','} - newLineBytes = []byte{'\n'} -) - // Tokenize turns the file contents into a list of tokens. func Tokenize(buffer []byte) List { var ( @@ -26,21 +11,21 @@ func Tokenize(buffer []byte) List { switch buffer[i] { case ' ', '\t': case ',': - tokens = append(tokens, Token{Kind: Separator, Position: i, Bytes: separatorBytes}) + tokens = append(tokens, Token{Kind: Separator, Position: i, Length: 1}) case '(': - tokens = append(tokens, Token{Kind: GroupStart, Position: i, Bytes: groupStartBytes}) + tokens = append(tokens, Token{Kind: GroupStart, Position: i, Length: 1}) case ')': - tokens = append(tokens, Token{Kind: GroupEnd, Position: i, Bytes: groupEndBytes}) + tokens = append(tokens, Token{Kind: GroupEnd, Position: i, Length: 1}) case '{': - tokens = append(tokens, Token{Kind: BlockStart, Position: i, Bytes: blockStartBytes}) + tokens = append(tokens, Token{Kind: BlockStart, Position: i, Length: 1}) case '}': - tokens = append(tokens, Token{Kind: BlockEnd, Position: i, Bytes: blockEndBytes}) + tokens = append(tokens, Token{Kind: BlockEnd, Position: i, Length: 1}) case '[': - tokens = append(tokens, Token{Kind: ArrayStart, Position: i, Bytes: arrayStartBytes}) + tokens = append(tokens, Token{Kind: ArrayStart, Position: i, Length: 1}) case ']': - tokens = append(tokens, Token{Kind: ArrayEnd, Position: i, Bytes: arrayEndBytes}) + tokens = append(tokens, Token{Kind: ArrayEnd, Position: i, Length: 1}) case '\n': - tokens = append(tokens, Token{Kind: NewLine, Position: i, Bytes: newLineBytes}) + tokens = append(tokens, Token{Kind: NewLine, Position: i, Length: 1}) case '/': if i+1 >= Position(len(buffer)) || buffer[i+1] != '/' { position := i @@ -50,7 +35,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: Operator, Position: position, Bytes: buffer[position:i]}) + tokens = append(tokens, Token{Kind: Operators[string(buffer[position:i])], Position: position, Length: Length(i - position)}) } else { position := i @@ -58,7 +43,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: Comment, Position: position, Bytes: buffer[position:i]}) + tokens = append(tokens, Token{Kind: Comment, Position: position, Length: Length(i - position)}) } continue @@ -78,7 +63,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: String, Position: start, Bytes: buffer[start:end]}) + tokens = append(tokens, Token{Kind: String, Position: start, Length: Length(end - start)}) continue default: @@ -91,14 +76,14 @@ func Tokenize(buffer []byte) List { } identifier := buffer[position:i] - keyword, isKeyword := keyword.Map[string(identifier)] + kind := Identifier + keyword, isKeyword := Keywords[string(identifier)] if isKeyword { - tokens = append(tokens, Token{Kind: Keyword, Position: position, Bytes: keyword}) - } else { - tokens = append(tokens, Token{Kind: Identifier, Position: position, Bytes: identifier}) + kind = keyword } + tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(len(identifier))}) continue } @@ -110,7 +95,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: Number, Position: position, Bytes: buffer[position:i]}) + tokens = append(tokens, Token{Kind: Number, Position: position, Length: Length(i - position)}) continue } @@ -122,17 +107,17 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: Operator, Position: position, Bytes: buffer[position:i]}) + tokens = append(tokens, Token{Kind: Operators[string(buffer[position:i])], Position: position, Length: Length(i - position)}) continue } - tokens = append(tokens, Token{Kind: Invalid, Position: i, Bytes: buffer[i : i+1]}) + tokens = append(tokens, Token{Kind: Invalid, Position: i, Length: 1}) } i++ } - tokens = append(tokens, Token{Kind: EOF, Position: i, Bytes: nil}) + tokens = append(tokens, Token{Kind: EOF, Position: i, Length: 0}) return tokens } diff --git a/src/build/token/Tokenize_test.go b/src/build/token/Tokenize_test.go index 63e689d..19f57e5 100644 --- a/src/build/token/Tokenize_test.go +++ b/src/build/token/Tokenize_test.go @@ -9,394 +9,243 @@ import ( func TestFunction(t *testing.T) { tokens := token.Tokenize([]byte("main(){}")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Identifier, - Bytes: []byte("main"), - Position: 0, - }, - { - Kind: token.GroupStart, - Bytes: []byte("("), - Position: 4, - }, - { - Kind: token.GroupEnd, - Bytes: []byte(")"), - Position: 5, - }, - { - Kind: token.BlockStart, - Bytes: []byte("{"), - Position: 6, - }, - { - Kind: token.BlockEnd, - Bytes: []byte("}"), - Position: 7, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 8, - }, - }) + + expected := []token.Kind{ + token.Identifier, + token.GroupStart, + token.GroupEnd, + token.BlockStart, + token.BlockEnd, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestKeyword(t *testing.T) { tokens := token.Tokenize([]byte("return x")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Keyword, - Bytes: []byte("return"), - Position: 0, - }, - { - Kind: token.Identifier, - Bytes: []byte("x"), - Position: 7, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 8, - }, - }) + + expected := []token.Kind{ + token.Return, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestArray(t *testing.T) { tokens := token.Tokenize([]byte("array[i]")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Identifier, - Bytes: []byte("array"), - Position: 0, - }, - { - Kind: token.ArrayStart, - Bytes: []byte("["), - Position: 5, - }, - { - Kind: token.Identifier, - Bytes: []byte("i"), - Position: 6, - }, - { - Kind: token.ArrayEnd, - Bytes: []byte("]"), - Position: 7, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 8, - }, - }) + + expected := []token.Kind{ + token.Identifier, + token.ArrayStart, + token.Identifier, + token.ArrayEnd, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestNewline(t *testing.T) { tokens := token.Tokenize([]byte("\n\n")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.NewLine, - Bytes: []byte("\n"), - Position: 0, - }, - { - Kind: token.NewLine, - Bytes: []byte("\n"), - Position: 1, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 2, - }, - }) + + expected := []token.Kind{ + token.NewLine, + token.NewLine, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestNumber(t *testing.T) { tokens := token.Tokenize([]byte(`123 456`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Number, - Bytes: []byte("123"), - Position: 0, - }, - { - Kind: token.Number, - Bytes: []byte("456"), - Position: 4, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 7, - }, - }) + + expected := []token.Kind{ + token.Number, + token.Number, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestOperator(t *testing.T) { tokens := token.Tokenize([]byte(`+ - * /`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Operator, - Bytes: []byte("+"), - Position: 0, - }, - { - Kind: token.Operator, - Bytes: []byte("-"), - Position: 2, - }, - { - Kind: token.Operator, - Bytes: []byte("*"), - Position: 4, - }, - { - Kind: token.Operator, - Bytes: []byte("/"), - Position: 6, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 7, - }, - }) + + expected := []token.Kind{ + token.Add, + token.Sub, + token.Mul, + token.Div, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestOperatorAssign(t *testing.T) { - tokens := token.Tokenize([]byte(`+= -= *= /= ==`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Operator, - Bytes: []byte("+="), - Position: 0, - }, - { - Kind: token.Operator, - Bytes: []byte("-="), - Position: 3, - }, - { - Kind: token.Operator, - Bytes: []byte("*="), - Position: 6, - }, - { - Kind: token.Operator, - Bytes: []byte("/="), - Position: 9, - }, - { - Kind: token.Operator, - Bytes: []byte("=="), - Position: 12, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 14, - }, - }) + tokens := token.Tokenize([]byte(`+= -= *= /= &= |= ^= <<= >>=`)) + + expected := []token.Kind{ + token.AddAssign, + token.SubAssign, + token.MulAssign, + token.DivAssign, + token.AndAssign, + token.OrAssign, + token.XorAssign, + token.ShlAssign, + token.ShrAssign, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestSeparator(t *testing.T) { tokens := token.Tokenize([]byte("a,b,c")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Identifier, - Bytes: []byte("a"), - Position: 0, - }, - { - Kind: token.Separator, - Bytes: []byte(","), - Position: 1, - }, - { - Kind: token.Identifier, - Bytes: []byte("b"), - Position: 2, - }, - { - Kind: token.Separator, - Bytes: []byte(","), - Position: 3, - }, - { - Kind: token.Identifier, - Bytes: []byte("c"), - Position: 4, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 5, - }, - }) + + expected := []token.Kind{ + token.Identifier, + token.Separator, + token.Identifier, + token.Separator, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestComment(t *testing.T) { tokens := token.Tokenize([]byte("// Hello\n// World")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Comment, - Bytes: []byte(`// Hello`), - Position: 0, - }, - { - Kind: token.NewLine, - Bytes: []byte("\n"), - Position: 8, - }, - { - Kind: token.Comment, - Bytes: []byte(`// World`), - Position: 9, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 17, - }, - }) + + expected := []token.Kind{ + token.Comment, + token.NewLine, + token.Comment, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } tokens = token.Tokenize([]byte("// Hello\n")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Comment, - Bytes: []byte(`// Hello`), - Position: 0, - }, - { - Kind: token.NewLine, - Bytes: []byte("\n"), - Position: 8, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 9, - }, - }) + + expected = []token.Kind{ + token.Comment, + token.NewLine, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } tokens = token.Tokenize([]byte(`// Hello`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Comment, - Bytes: []byte(`// Hello`), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 8, - }, - }) + + expected = []token.Kind{ + token.Comment, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } tokens = token.Tokenize([]byte(`//`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Comment, - Bytes: []byte(`//`), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 2, - }, - }) + + expected = []token.Kind{ + token.Comment, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } tokens = token.Tokenize([]byte(`/`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Operator, - Bytes: []byte(`/`), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 1, - }, - }) + + expected = []token.Kind{ + token.Div, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestInvalid(t *testing.T) { - tokens := token.Tokenize([]byte(`@#`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Invalid, - Bytes: []byte(`@`), - Position: 0, - }, - { - Kind: token.Invalid, - Bytes: []byte(`#`), - Position: 1, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 2, - }, - }) + tokens := token.Tokenize([]byte(`##`)) + + expected := []token.Kind{ + token.Invalid, + token.Invalid, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestString(t *testing.T) { tokens := token.Tokenize([]byte(`"Hello" "World"`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.String, - Bytes: []byte(`"Hello"`), - Position: 0, - }, - { - Kind: token.String, - Bytes: []byte(`"World"`), - Position: 8, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 15, - }, - }) + + expected := []token.Kind{ + token.String, + token.String, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestStringMultiline(t *testing.T) { tokens := token.Tokenize([]byte("\"Hello\nWorld\"")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.String, - Bytes: []byte("\"Hello\nWorld\""), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 13, - }, - }) + + expected := []token.Kind{ + token.String, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestStringEOF(t *testing.T) { tokens := token.Tokenize([]byte(`"EOF`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.String, - Bytes: []byte(`"EOF`), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 4, - }, - }) + + expected := []token.Kind{ + token.String, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } diff --git a/tests/errors/InvalidOperator.q b/tests/errors/InvalidCharacter4.q similarity index 100% rename from tests/errors/InvalidOperator.q rename to tests/errors/InvalidCharacter4.q diff --git a/tests/errors_test.go b/tests/errors_test.go index 6c08acd..5b28ee2 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -21,10 +21,10 @@ var errs = []struct { {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}}, {"InvalidExpression.q", errors.InvalidExpression}, - {"InvalidOperator.q", &errors.InvalidOperator{Operator: "+++"}}, {"InvalidCharacter.q", &errors.InvalidCharacter{Character: "@"}}, {"InvalidCharacter2.q", &errors.InvalidCharacter{Character: "@"}}, {"InvalidCharacter3.q", &errors.InvalidCharacter{Character: "@"}}, + {"InvalidCharacter4.q", &errors.InvalidCharacter{Character: "+++"}}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, From 04ba68a07501d811dcc7440a8b915e5eb92eccb6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 21 Jul 2024 14:35:06 +0200 Subject: [PATCH 0342/1012] Reduced token size --- src/build/ast/AST.go | 4 +- src/build/ast/Assign.go | 4 - src/build/ast/Call.go | 4 - src/build/ast/Count.go | 16 +- src/build/ast/Define.go | 6 - src/build/ast/If.go | 6 - src/build/ast/Loop.go | 6 - src/build/ast/Parse.go | 37 +- src/build/ast/Return.go | 6 - src/build/core/Compare.go | 2 +- src/build/core/CompileAssign.go | 10 +- src/build/core/CompileCall.go | 4 +- src/build/core/CompileCondition.go | 25 +- src/build/core/CompileDefinition.go | 4 +- src/build/core/CompileIf.go | 2 +- src/build/core/CompileLoop.go | 2 +- src/build/core/CompileTokens.go | 4 +- src/build/core/ExecuteLeaf.go | 6 +- src/build/core/ExecuteRegisterNumber.go | 16 +- src/build/core/ExecuteRegisterRegister.go | 16 +- src/build/core/Function.go | 2 +- src/build/core/JumpIfFalse.go | 15 +- src/build/core/JumpIfTrue.go | 15 +- src/build/core/NewFunction.go | 2 +- src/build/core/TokenToRegister.go | 6 +- src/build/expression/Expression.go | 24 +- src/build/expression/Expression_test.go | 30 +- src/build/expression/Operator.go | 68 +-- src/build/expression/Parse.go | 27 +- src/build/fs/File.go | 1 + src/build/keyword/Keyword.go | 16 - src/build/scanner/scanFile.go | 20 +- src/build/scope/Stack.go | 4 +- src/build/token/Count.go | 4 +- src/build/token/Count_test.go | 11 +- src/build/token/Keywords.go | 9 + src/build/token/Kind.go | 127 ++--- src/build/token/Kind_test.go | 27 - src/build/token/Length.go | 4 + src/build/token/List.go | 21 - src/build/token/Operators.go | 39 ++ src/build/token/Token.go | 38 +- src/build/token/Token_test.go | 41 +- src/build/token/Tokenize.go | 53 +- src/build/token/Tokenize_test.go | 521 +++++++----------- ...{InvalidOperator.q => InvalidCharacter4.q} | 0 tests/errors_test.go | 2 +- 47 files changed, 543 insertions(+), 764 deletions(-) delete mode 100644 src/build/keyword/Keyword.go create mode 100644 src/build/token/Keywords.go delete mode 100644 src/build/token/Kind_test.go create mode 100644 src/build/token/Length.go create mode 100644 src/build/token/Operators.go rename tests/errors/{InvalidOperator.q => InvalidCharacter4.q} (100%) diff --git a/src/build/ast/AST.go b/src/build/ast/AST.go index 1c8f6dd..c7c4c47 100644 --- a/src/build/ast/AST.go +++ b/src/build/ast/AST.go @@ -1,6 +1,4 @@ package ast -import "fmt" - -type Node fmt.Stringer +type Node any type AST []Node diff --git a/src/build/ast/Assign.go b/src/build/ast/Assign.go index 32c6819..161f968 100644 --- a/src/build/ast/Assign.go +++ b/src/build/ast/Assign.go @@ -8,7 +8,3 @@ import ( type Assign struct { Expression *expression.Expression } - -func (node *Assign) String() string { - return node.Expression.String() -} diff --git a/src/build/ast/Call.go b/src/build/ast/Call.go index d7825b3..6bd7dec 100644 --- a/src/build/ast/Call.go +++ b/src/build/ast/Call.go @@ -6,7 +6,3 @@ import "git.akyoto.dev/cli/q/src/build/expression" type Call struct { Expression *expression.Expression } - -func (node *Call) String() string { - return node.Expression.String() -} diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index 0cc6986..b14c652 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -3,31 +3,31 @@ package ast import "git.akyoto.dev/cli/q/src/build/token" // Count counts how often the given token appears in the AST. -func Count(body AST, kind token.Kind, name string) int { +func Count(body AST, buffer []byte, kind token.Kind, name string) int { count := 0 for _, node := range body { switch node := node.(type) { case *Assign: - count += node.Expression.Count(kind, name) + count += node.Expression.Count(buffer, kind, name) case *Call: - count += node.Expression.Count(kind, name) + count += node.Expression.Count(buffer, kind, name) case *Define: - count += node.Value.Count(kind, name) + count += node.Value.Count(buffer, kind, name) case *Return: if node.Value != nil { - count += node.Value.Count(kind, name) + count += node.Value.Count(buffer, kind, name) } case *If: - count += node.Condition.Count(kind, name) - count += Count(node.Body, kind, name) + count += node.Condition.Count(buffer, kind, name) + count += Count(node.Body, buffer, kind, name) case *Loop: - count += Count(node.Body, kind, name) + count += Count(node.Body, buffer, kind, name) default: panic("unknown AST type") diff --git a/src/build/ast/Define.go b/src/build/ast/Define.go index 6751491..64f0c15 100644 --- a/src/build/ast/Define.go +++ b/src/build/ast/Define.go @@ -1,8 +1,6 @@ package ast import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" ) @@ -12,7 +10,3 @@ type Define struct { Value *expression.Expression Name token.Token } - -func (node *Define) String() string { - return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) -} diff --git a/src/build/ast/If.go b/src/build/ast/If.go index 47d98d4..4275f34 100644 --- a/src/build/ast/If.go +++ b/src/build/ast/If.go @@ -1,8 +1,6 @@ package ast import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/expression" ) @@ -11,7 +9,3 @@ type If struct { Condition *expression.Expression Body AST } - -func (node *If) String() string { - return fmt.Sprintf("(if %s %s)", node.Condition, node.Body) -} diff --git a/src/build/ast/Loop.go b/src/build/ast/Loop.go index 5b0dc33..e1ca210 100644 --- a/src/build/ast/Loop.go +++ b/src/build/ast/Loop.go @@ -1,12 +1,6 @@ package ast -import "fmt" - // Loop represents a block of repeatable statements. type Loop struct { Body AST } - -func (node *Loop) String() string { - return fmt.Sprintf("(loop %s)", node.Body) -} diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index eae5666..5610e1a 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -3,16 +3,15 @@ package ast import ( "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/keyword" "git.akyoto.dev/cli/q/src/build/token" ) // Parse generates an AST from a list of tokens. -func Parse(tokens token.List) (AST, error) { +func Parse(tokens []token.Token, buffer []byte) (AST, error) { tree := make(AST, 0, len(tokens)/64) err := EachInstruction(tokens, func(instruction token.List) error { - node, err := toASTNode(instruction) + node, err := toASTNode(instruction, buffer) if err == nil && node != nil { tree = append(tree, node) @@ -25,11 +24,9 @@ func Parse(tokens token.List) (AST, error) { } // toASTNode generates an AST node from an instruction. -func toASTNode(tokens token.List) (Node, error) { - if tokens[0].Kind == token.Keyword { - word := tokens[0].Text() - - if word == keyword.Return { +func toASTNode(tokens token.List, buffer []byte) (Node, error) { + if tokens[0].IsKeyword() { + if tokens[0].Kind == token.Return { if len(tokens) == 1 { return &Return{}, nil } @@ -38,7 +35,7 @@ func toASTNode(tokens token.List) (Node, error) { return &Return{Value: value}, nil } - if keywordHasBlock(word) { + if keywordHasBlock(tokens[0].Kind) { blockStart := tokens.IndexKind(token.BlockStart) blockEnd := tokens.LastIndexKind(token.BlockEnd) @@ -50,19 +47,19 @@ func toASTNode(tokens token.List) (Node, error) { return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) } - body, err := Parse(tokens[blockStart+1 : blockEnd]) + body, err := Parse(tokens[blockStart+1:blockEnd], buffer) - switch word { - case keyword.If: + switch tokens[0].Kind { + case token.If: condition := expression.Parse(tokens[1:blockStart]) return &If{Condition: condition, Body: body}, err - case keyword.Loop: + case token.Loop: return &Loop{Body: body}, err } } - return nil, errors.New(&errors.KeywordNotImplemented{Keyword: word}, nil, tokens[0].Position) + return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text(buffer)}, nil, tokens[0].Position) } expr := expression.Parse(tokens) @@ -92,26 +89,26 @@ func toASTNode(tokens token.List) (Node, error) { return &Call{Expression: expr}, nil default: - return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, nil, expr.Token.Position) + return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text(buffer)}, nil, expr.Token.Position) } } // IsAssignment returns true if the expression is an assignment. func IsAssignment(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '=' + return expr.Token.IsAssignment() } // IsFunctionCall returns true if the expression is a function call. func IsFunctionCall(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" + return expr.Token.Kind == token.Call } // IsVariableDefinition returns true if the expression is a variable definition. func IsVariableDefinition(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" + return expr.Token.Kind == token.Define } // keywordHasBlock returns true if the keyword requires a block. -func keywordHasBlock(word string) bool { - return word == keyword.If || word == keyword.Loop +func keywordHasBlock(kind token.Kind) bool { + return kind == token.If || kind == token.Loop } diff --git a/src/build/ast/Return.go b/src/build/ast/Return.go index a56520f..37574e2 100644 --- a/src/build/ast/Return.go +++ b/src/build/ast/Return.go @@ -1,8 +1,6 @@ package ast import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/expression" ) @@ -10,7 +8,3 @@ import ( type Return struct { Value *expression.Expression } - -func (node *Return) String() string { - return fmt.Sprintf("(return %s)", node.Value) -} diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 3be29ec..6cd0b5d 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -13,7 +13,7 @@ func (f *Function) Compare(comparison *expression.Expression) error { right := comparison.Children[1] if left.IsLeaf() && left.Token.Kind == token.Identifier { - name := left.Token.Text() + name := left.Token.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index 384630d..4d3e08b 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -16,7 +16,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { right := node.Expression.Children[1] if left.IsLeaf() { - name := left.Token.Text() + name := left.Token.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { @@ -27,8 +27,8 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return f.Execute(operator, variable.Register, right) } - if left.Token.Kind == token.Operator && left.Token.Text() == "@" { - name := left.Children[0].Token.Text() + if left.Token.Kind == token.Array { + name := left.Children[0].Token.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { @@ -38,13 +38,13 @@ func (f *Function) CompileAssign(node *ast.Assign) error { defer f.useVariable(variable) index := left.Children[1] - offset, err := strconv.Atoi(index.Token.Text()) + offset, err := strconv.Atoi(index.Token.Text(f.File.Bytes)) if err != nil { return err } - num, err := strconv.Atoi(right.Token.Text()) + num, err := strconv.Atoi(right.Token.Text(f.File.Bytes)) if err != nil { return err diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 4c2adc6..3270d01 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -15,9 +15,9 @@ func (f *Function) CompileCall(root *expression.Expression) error { funcName := "" if funcNameRoot.IsLeaf() { - funcName = funcNameRoot.Token.Text() + funcName = funcNameRoot.Token.Text(f.File.Bytes) } else { - funcName = funcNameRoot.Children[0].Token.Text() + funcNameRoot.Token.Text() + funcNameRoot.Children[1].Token.Text() + funcName = funcNameRoot.Children[0].Token.Text(f.File.Bytes) + funcNameRoot.Token.Text(f.File.Bytes) + funcNameRoot.Children[1].Token.Text(f.File.Bytes) } isSyscall := funcName == "syscall" diff --git a/src/build/core/CompileCondition.go b/src/build/core/CompileCondition.go index 621a7e7..54e0ab0 100644 --- a/src/build/core/CompileCondition.go +++ b/src/build/core/CompileCondition.go @@ -4,12 +4,13 @@ import ( "fmt" "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" ) // CompileCondition inserts code to jump to the start label or end label depending on the truth of the condition. func (f *Function) CompileCondition(condition *expression.Expression, successLabel string, failLabel string) error { - switch condition.Token.Text() { - case "||": + switch condition.Token.Kind { + case token.LogicalOr: f.count.subBranch++ leftFailLabel := fmt.Sprintf("%s_false_%d", f.Name, f.count.subBranch) @@ -21,22 +22,22 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab return err } - f.JumpIfTrue(left.Token.Text(), successLabel) + f.JumpIfTrue(left.Token.Kind, successLabel) // Right f.AddLabel(leftFailLabel) right := condition.Children[1] err = f.CompileCondition(right, successLabel, failLabel) - if condition.Parent != nil && condition.Parent.Token.Text() == "||" && condition != condition.Parent.LastChild() { - f.JumpIfTrue(right.Token.Text(), successLabel) + if condition.Parent != nil && condition.Parent.Token.Kind == token.LogicalOr && condition != condition.Parent.LastChild() { + f.JumpIfTrue(right.Token.Kind, successLabel) } else { - f.JumpIfFalse(right.Token.Text(), failLabel) + f.JumpIfFalse(right.Token.Kind, failLabel) } return err - case "&&": + case token.LogicalAnd: f.count.subBranch++ leftSuccessLabel := fmt.Sprintf("%s_true_%d", f.Name, f.count.subBranch) @@ -48,17 +49,17 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab return err } - f.JumpIfFalse(left.Token.Text(), failLabel) + f.JumpIfFalse(left.Token.Kind, failLabel) // Right f.AddLabel(leftSuccessLabel) right := condition.Children[1] err = f.CompileCondition(right, successLabel, failLabel) - if condition.Parent != nil && condition.Parent.Token.Text() == "||" && condition != condition.Parent.LastChild() { - f.JumpIfTrue(right.Token.Text(), successLabel) + if condition.Parent != nil && condition.Parent.Token.Kind == token.LogicalOr && condition != condition.Parent.LastChild() { + f.JumpIfTrue(right.Token.Kind, successLabel) } else { - f.JumpIfFalse(right.Token.Text(), failLabel) + f.JumpIfFalse(right.Token.Kind, failLabel) } return err @@ -67,7 +68,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab err := f.Compare(condition) if condition.Parent == nil { - f.JumpIfFalse(condition.Token.Text(), failLabel) + f.JumpIfFalse(condition.Token.Kind, failLabel) } return err diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index a8fa1eb..c5473b1 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -8,13 +8,13 @@ import ( // CompileDefinition compiles a variable definition. func (f *Function) CompileDefinition(node *ast.Define) error { - name := node.Name.Text() + name := node.Name.Text(f.File.Bytes) if f.identifierExists(name) { return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, node.Name.Position) } - uses := token.Count(f.Body, token.Identifier, name) - 1 + uses := token.Count(f.Body, f.File.Bytes, token.Identifier, name) - 1 if uses == 0 { return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 1b61c3c..3eeb4ae 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -18,7 +18,7 @@ func (f *Function) CompileIf(branch *ast.If) error { } f.AddLabel(success) - f.PushScope(branch.Body) + f.PushScope(branch.Body, f.File.Bytes) err = f.CompileAST(branch.Body) f.PopScope() f.AddLabel(fail) diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index de9d8b6..a1b116a 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -12,7 +12,7 @@ func (f *Function) CompileLoop(loop *ast.Loop) error { f.count.loop++ label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) f.AddLabel(label) - scope := f.PushScope(loop.Body) + scope := f.PushScope(loop.Body, f.File.Bytes) scope.InLoop = true err := f.CompileAST(loop.Body) f.Jump(asm.JUMP, label) diff --git a/src/build/core/CompileTokens.go b/src/build/core/CompileTokens.go index 7b41804..634c5d7 100644 --- a/src/build/core/CompileTokens.go +++ b/src/build/core/CompileTokens.go @@ -7,8 +7,8 @@ import ( ) // CompileTokens compiles a token list. -func (f *Function) CompileTokens(tokens token.List) error { - body, err := ast.Parse(tokens) +func (f *Function) CompileTokens(tokens []token.Token) error { + body, err := ast.Parse(tokens, f.File.Bytes) if err != nil { err.(*errors.Error).File = f.File diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index 947518d..d6cc124 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -12,7 +12,7 @@ import ( func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error { switch operand.Kind { case token.Identifier: - name := operand.Text() + name := operand.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { @@ -23,7 +23,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope return f.ExecuteRegisterRegister(operation, register, variable.Register) case token.Number: - value := operand.Text() + value := operand.Text(f.File.Bytes) number, err := strconv.Atoi(value) if err != nil { @@ -33,7 +33,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope return f.ExecuteRegisterNumber(operation, register, number) case token.String: - if operation.Text() == "=" { + if operation.Kind == token.Assign { return f.TokenToRegister(operand, register) } } diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index 19b07ea..43675a2 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -9,27 +9,27 @@ import ( // ExecuteRegisterNumber performs an operation on a register and a number. func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { - switch operation.Text() { - case "+", "+=": + switch operation.Kind { + case token.Add, token.AddAssign: f.RegisterNumber(asm.ADD, register, number) - case "-", "-=": + case token.Sub, token.SubAssign: f.RegisterNumber(asm.SUB, register, number) - case "*", "*=": + case token.Mul, token.MulAssign: f.RegisterNumber(asm.MUL, register, number) - case "/", "/=": + case token.Div, token.DivAssign: f.RegisterNumber(asm.DIV, register, number) - case "==", "!=", "<", "<=", ">", ">=": + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: f.RegisterNumber(asm.COMPARE, register, number) - case "=": + case token.Assign: f.RegisterNumber(asm.MOVE, register, number) default: - return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) + return errors.New(&errors.InvalidOperator{Operator: operation.Text(f.File.Bytes)}, f.File, operation.Position) } return nil diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index c163e23..8206b4f 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -9,27 +9,27 @@ import ( // ExecuteRegisterRegister performs an operation on two registers. func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { - switch operation.Text() { - case "+", "+=": + switch operation.Kind { + case token.Add, token.AddAssign: f.RegisterRegister(asm.ADD, destination, source) - case "-", "-=": + case token.Sub, token.SubAssign: f.RegisterRegister(asm.SUB, destination, source) - case "*", "*=": + case token.Mul, token.MulAssign: f.RegisterRegister(asm.MUL, destination, source) - case "/", "/=": + case token.Div, token.DivAssign: f.RegisterRegister(asm.DIV, destination, source) - case "==", "!=", "<", "<=", ">", ">=": + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: f.RegisterRegister(asm.COMPARE, destination, source) - case "=": + case token.Assign: f.RegisterRegister(asm.MOVE, destination, source) default: - return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) + return errors.New(&errors.InvalidOperator{Operator: operation.Text(f.File.Bytes)}, f.File, operation.Position) } return nil diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 79e1b31..36ac29d 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -13,7 +13,7 @@ type Function struct { scope.Stack Name string File *fs.File - Body token.List + Body []token.Token Assembler asm.Assembler Functions map[string]*Function Err error diff --git a/src/build/core/JumpIfFalse.go b/src/build/core/JumpIfFalse.go index baedd51..042a676 100644 --- a/src/build/core/JumpIfFalse.go +++ b/src/build/core/JumpIfFalse.go @@ -2,22 +2,23 @@ package core import ( "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/token" ) // JumpIfFalse jumps to the label if the previous comparison was false. -func (f *Function) JumpIfFalse(operator string, label string) { +func (f *Function) JumpIfFalse(operator token.Kind, label string) { switch operator { - case "==": + case token.Equal: f.Jump(asm.JNE, label) - case "!=": + case token.NotEqual: f.Jump(asm.JE, label) - case ">": + case token.Greater: f.Jump(asm.JLE, label) - case "<": + case token.Less: f.Jump(asm.JGE, label) - case ">=": + case token.GreaterEqual: f.Jump(asm.JL, label) - case "<=": + case token.LessEqual: f.Jump(asm.JG, label) } } diff --git a/src/build/core/JumpIfTrue.go b/src/build/core/JumpIfTrue.go index 4c04a0b..059618e 100644 --- a/src/build/core/JumpIfTrue.go +++ b/src/build/core/JumpIfTrue.go @@ -2,22 +2,23 @@ package core import ( "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/token" ) // JumpIfTrue jumps to the label if the previous comparison was true. -func (f *Function) JumpIfTrue(operator string, label string) { +func (f *Function) JumpIfTrue(operator token.Kind, label string) { switch operator { - case "==": + case token.Equal: f.Jump(asm.JE, label) - case "!=": + case token.NotEqual: f.Jump(asm.JNE, label) - case ">": + case token.Greater: f.Jump(asm.JG, label) - case "<": + case token.Less: f.Jump(asm.JL, label) - case ">=": + case token.GreaterEqual: f.Jump(asm.JGE, label) - case "<=": + case token.LessEqual: f.Jump(asm.JLE, label) } } diff --git a/src/build/core/NewFunction.go b/src/build/core/NewFunction.go index 1394c4e..2dc683d 100644 --- a/src/build/core/NewFunction.go +++ b/src/build/core/NewFunction.go @@ -10,7 +10,7 @@ import ( ) // NewFunction creates a new function. -func NewFunction(name string, file *fs.File, body token.List) *Function { +func NewFunction(name string, file *fs.File, body []token.Token) *Function { return &Function{ Name: name, File: file, diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index a40f471..f048fac 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -15,7 +15,7 @@ import ( func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { switch t.Kind { case token.Identifier: - name := t.Text() + name := t.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { @@ -27,7 +27,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return nil case token.Number: - value := t.Text() + value := t.Text(f.File.Bytes) n, err := strconv.Atoi(value) if err != nil { @@ -40,7 +40,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { case token.String: f.count.data++ label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data) - value := t.Bytes[1 : len(t.Bytes)-1] + value := t.Bytes(f.File.Bytes)[1 : t.Length-1] f.Assembler.SetData(label, value) f.RegisterLabel(asm.MOVE, register, label) return nil diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 60b3794..5027b8e 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -37,11 +37,11 @@ func (expr *Expression) AddChild(child *Expression) { } // Count counts how often the given token appears in the expression. -func (expr *Expression) Count(kind token.Kind, name string) int { +func (expr *Expression) Count(buffer []byte, kind token.Kind, name string) int { count := 0 expr.EachLeaf(func(leaf *Expression) error { - if leaf.Token.Kind == kind && leaf.Token.Text() == name { + if leaf.Token.Kind == kind && leaf.Token.Text(buffer) == name { count++ } @@ -112,25 +112,33 @@ func (expr *Expression) LastChild() *Expression { } // String generates a textual representation of the expression. -func (expr *Expression) String() string { +func (expr *Expression) String(data []byte) string { builder := strings.Builder{} - expr.write(&builder) + expr.write(&builder, data) return builder.String() } // write generates a textual representation of the expression. -func (expr *Expression) write(builder *strings.Builder) { +func (expr *Expression) write(builder *strings.Builder, data []byte) { if expr.IsLeaf() { - builder.WriteString(expr.Token.Text()) + builder.WriteString(expr.Token.Text(data)) return } builder.WriteByte('(') - builder.WriteString(expr.Token.Text()) + + switch expr.Token.Kind { + case token.Call: + builder.WriteString("λ") + case token.Array: + builder.WriteString("@") + default: + builder.WriteString(expr.Token.Text(data)) + } for _, child := range expr.Children { builder.WriteByte(' ') - child.write(builder) + child.write(builder, data) } builder.WriteByte(')') diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index 6794da8..a313cf3 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -95,7 +95,7 @@ func TestParse(t *testing.T) { defer expr.Reset() assert.NotNil(t, expr) - assert.Equal(t, expr.String(), test.Result) + assert.Equal(t, expr.String(src), test.Result) }) } } @@ -104,11 +104,11 @@ func TestCount(t *testing.T) { src := []byte("(a+b-c*d)+(a*b-c+d)") tokens := token.Tokenize(src) expr := expression.Parse(tokens) - assert.Equal(t, expr.Count(token.Identifier, "a"), 2) - assert.Equal(t, expr.Count(token.Identifier, "b"), 2) - assert.Equal(t, expr.Count(token.Identifier, "c"), 2) - assert.Equal(t, expr.Count(token.Identifier, "d"), 2) - assert.Equal(t, expr.Count(token.Identifier, "e"), 0) + assert.Equal(t, expr.Count(src, token.Identifier, "a"), 2) + assert.Equal(t, expr.Count(src, token.Identifier, "b"), 2) + assert.Equal(t, expr.Count(src, token.Identifier, "c"), 2) + assert.Equal(t, expr.Count(src, token.Identifier, "d"), 2) + assert.Equal(t, expr.Count(src, token.Identifier, "e"), 0) } func TestEachLeaf(t *testing.T) { @@ -118,7 +118,7 @@ func TestEachLeaf(t *testing.T) { leaves := []string{} err := expr.EachLeaf(func(leaf *expression.Expression) error { - leaves = append(leaves, leaf.Token.Text()) + leaves = append(leaves, leaf.Token.Text(src)) return nil }) @@ -140,7 +140,7 @@ func TestEachParameter(t *testing.T) { err := expression.EachParameter(tokens, func(parameter token.List) error { expr := expression.Parse(parameter) - parameters = append(parameters, expr.String()) + parameters = append(parameters, expr.String(src)) return nil }) @@ -178,17 +178,3 @@ func TestNilGroup(t *testing.T) { expr := expression.Parse(tokens) assert.Nil(t, expr) } - -func TestInvalidOperator(t *testing.T) { - src := []byte("a +++ 2") - tokens := token.Tokenize(src) - expr := expression.Parse(tokens) - assert.Equal(t, expr.String(), "(+++ a 2)") -} - -func TestInvalidOperatorCall(t *testing.T) { - src := []byte("+++()") - tokens := token.Tokenize(src) - expr := expression.Parse(tokens) - assert.NotNil(t, expr) -} diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index b2d15e1..1d9e18b 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -15,39 +15,39 @@ type Operator struct { // Operators defines the operators used in the language. // The number corresponds to the operator priority and can not be zero. -var Operators = map[string]*Operator{ - ".": {".", 13, 2}, - "λ": {"λ", 12, 1}, - "@": {"@", 12, 2}, - "!": {"!", 11, 1}, - "*": {"*", 10, 2}, - "/": {"/", 10, 2}, - "%": {"%", 10, 2}, - "+": {"+", 9, 2}, - "-": {"-", 9, 2}, - ">>": {">>", 8, 2}, - "<<": {"<<", 8, 2}, - "&": {"&", 7, 2}, - "^": {"^", 6, 2}, - "|": {"|", 5, 2}, +var Operators = map[token.Kind]*Operator{ + token.Period: {".", 13, 2}, + token.Call: {"λ", 12, 1}, + token.Array: {"@", 12, 2}, + token.Not: {"!", 11, 1}, + token.Mul: {"*", 10, 2}, + token.Div: {"/", 10, 2}, + token.Mod: {"%", 10, 2}, + token.Add: {"+", 9, 2}, + token.Sub: {"-", 9, 2}, + token.Shr: {">>", 8, 2}, + token.Shl: {"<<", 8, 2}, + token.And: {"&", 7, 2}, + token.Xor: {"^", 6, 2}, + token.Or: {"|", 5, 2}, - ">": {">", 4, 2}, - "<": {"<", 4, 2}, - ">=": {">=", 4, 2}, - "<=": {"<=", 4, 2}, - "==": {"==", 3, 2}, - "!=": {"!=", 3, 2}, - "&&": {"&&", 2, 2}, - "||": {"||", 1, 2}, + token.Greater: {">", 4, 2}, + token.Less: {"<", 4, 2}, + token.GreaterEqual: {">=", 4, 2}, + token.LessEqual: {"<=", 4, 2}, + token.Equal: {"==", 3, 2}, + token.NotEqual: {"!=", 3, 2}, + token.LogicalAnd: {"&&", 2, 2}, + token.LogicalOr: {"||", 1, 2}, - "=": {"=", math.MinInt8, 2}, - ":=": {":=", math.MinInt8, 2}, - "+=": {"+=", math.MinInt8, 2}, - "-=": {"-=", math.MinInt8, 2}, - "*=": {"*=", math.MinInt8, 2}, - "/=": {"/=", math.MinInt8, 2}, - ">>=": {">>=", math.MinInt8, 2}, - "<<=": {"<<=", math.MinInt8, 2}, + token.Assign: {"=", math.MinInt8, 2}, + token.Define: {":=", math.MinInt8, 2}, + token.AddAssign: {"+=", math.MinInt8, 2}, + token.SubAssign: {"-=", math.MinInt8, 2}, + token.MulAssign: {"*=", math.MinInt8, 2}, + token.DivAssign: {"/=", math.MinInt8, 2}, + token.ShrAssign: {">>=", math.MinInt8, 2}, + token.ShlAssign: {"<<=", math.MinInt8, 2}, } func isComplete(expr *Expression) bool { @@ -59,14 +59,14 @@ func isComplete(expr *Expression) bool { return true } - if expr.Token.Kind == token.Operator && len(expr.Children) == numOperands(expr.Token.Text()) { + if expr.Token.IsOperator() && len(expr.Children) == numOperands(expr.Token.Kind) { return true } return false } -func numOperands(symbol string) int { +func numOperands(symbol token.Kind) int { operator, exists := Operators[symbol] if !exists { @@ -76,7 +76,7 @@ func numOperands(symbol string) int { return operator.Operands } -func precedence(symbol string) int8 { +func precedence(symbol token.Kind) int8 { operator, exists := Operators[symbol] if !exists { diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index 33dd062..f2e8b56 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -6,13 +6,8 @@ import ( "git.akyoto.dev/cli/q/src/build/token" ) -var ( - call = []byte("λ") - array = []byte("@") -) - // Parse generates an expression tree from tokens. -func Parse(tokens token.List) *Expression { +func Parse(tokens []token.Token) *Expression { var ( cursor *Expression root *Expression @@ -43,20 +38,18 @@ func Parse(tokens token.List) *Expression { parameters := NewList(tokens[groupPosition:i]) node := New() - node.Token.Kind = token.Operator node.Token.Position = tokens[groupPosition].Position switch t.Kind { case token.GroupEnd: - node.Token.Bytes = call - node.Precedence = precedence("λ") - + node.Token.Kind = token.Call case token.ArrayEnd: - node.Token.Bytes = array - node.Precedence = precedence("@") + node.Token.Kind = token.Array } - if cursor.Token.Kind == token.Operator && node.Precedence > cursor.Precedence { + node.Precedence = precedence(node.Token.Kind) + + if cursor.Token.IsOperator() && node.Precedence > cursor.Precedence { cursor.LastChild().Replace(node) } else { if cursor == root { @@ -108,18 +101,18 @@ func Parse(tokens token.List) *Expression { continue } - if t.Kind == token.Operator { + if t.IsOperator() { if cursor == nil { cursor = NewLeaf(t) - cursor.Precedence = precedence(t.Text()) + cursor.Precedence = precedence(t.Kind) root = cursor continue } node := NewLeaf(t) - node.Precedence = precedence(t.Text()) + node.Precedence = precedence(t.Kind) - if cursor.Token.Kind == token.Operator { + if cursor.Token.IsOperator() { oldPrecedence := cursor.Precedence newPrecedence := node.Precedence diff --git a/src/build/fs/File.go b/src/build/fs/File.go index d4fd1cf..1a10a1d 100644 --- a/src/build/fs/File.go +++ b/src/build/fs/File.go @@ -5,5 +5,6 @@ import "git.akyoto.dev/cli/q/src/build/token" // File represents a single source file. type File struct { Path string + Bytes []byte Tokens token.List } diff --git a/src/build/keyword/Keyword.go b/src/build/keyword/Keyword.go deleted file mode 100644 index ef62575..0000000 --- a/src/build/keyword/Keyword.go +++ /dev/null @@ -1,16 +0,0 @@ -package keyword - -const ( - If = "if" - Import = "import" - Loop = "loop" - Return = "return" -) - -// Map is a map of all keywords used in the language. -var Map = map[string][]byte{ - If: []byte(If), - Import: []byte(Import), - Loop: []byte(Loop), - Return: []byte(Return), -} diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index b075fc6..e724767 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -11,7 +11,6 @@ import ( "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" - "git.akyoto.dev/cli/q/src/build/keyword" "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" ) @@ -27,8 +26,9 @@ func (s *Scanner) scanFile(path string, pkg string) error { tokens := token.Tokenize(contents) file := &fs.File{ - Tokens: tokens, Path: path, + Bytes: contents, + Tokens: tokens, } var ( @@ -42,14 +42,14 @@ func (s *Scanner) scanFile(path string, pkg string) error { ) for { - for i < len(tokens) && tokens[i].Kind == token.Keyword && tokens[i].Text() == keyword.Import { + for i < len(tokens) && tokens[i].Kind == token.Import { i++ if tokens[i].Kind != token.Identifier { panic("expected package name") } - packageName := tokens[i].Text() + packageName := tokens[i].Text(contents) s.queueDirectory(filepath.Join(config.Library, packageName), packageName) i++ @@ -75,7 +75,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { } if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(contents)}, file, tokens[i].Position) } if tokens[i].Kind == token.EOF { @@ -116,7 +116,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { } if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(contents)}, file, tokens[i].Position) } if tokens[i].Kind == token.EOF { @@ -168,7 +168,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { } if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text()}, file, tokens[i].Position) + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(contents)}, file, tokens[i].Position) } if tokens[i].Kind == token.EOF { @@ -191,7 +191,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) } - name := tokens[nameStart].Text() + name := tokens[nameStart].Text(contents) body := tokens[bodyStart:i] if pkg != "" { @@ -207,9 +207,9 @@ func (s *Scanner) scanFile(path string, pkg string) error { return errors.New(errors.NotImplemented, file, tokens[0].Position) } - name := tokens[0].Text() + name := tokens[0].Text(contents) register := x64.CallRegisters[count] - uses := token.Count(function.Body, token.Identifier, name) + uses := token.Count(function.Body, contents, token.Identifier, name) if uses == 0 { return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) diff --git a/src/build/scope/Stack.go b/src/build/scope/Stack.go index 5f634ff..1760f6b 100644 --- a/src/build/scope/Stack.go +++ b/src/build/scope/Stack.go @@ -20,7 +20,7 @@ func (stack *Stack) PopScope() { } // PushScope pushes a new scope to the top of the stack. -func (stack *Stack) PushScope(body ast.AST) *Scope { +func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { s := &Scope{} if len(stack.Scopes) > 0 { @@ -30,7 +30,7 @@ func (stack *Stack) PushScope(body ast.AST) *Scope { s.InLoop = lastScope.InLoop for k, v := range lastScope.Variables { - count := ast.Count(body, token.Identifier, v.Name) + count := ast.Count(body, buffer, token.Identifier, v.Name) if count == 0 { continue diff --git a/src/build/token/Count.go b/src/build/token/Count.go index ae83143..e83fff6 100644 --- a/src/build/token/Count.go +++ b/src/build/token/Count.go @@ -1,11 +1,11 @@ package token // Count counts how often the given token appears in the token list. -func Count(tokens List, kind Kind, name string) int { +func Count(tokens []Token, buffer []byte, kind Kind, name string) int { count := 0 for _, t := range tokens { - if t.Kind == Identifier && t.Text() == name { + if t.Kind == Identifier && t.Text(buffer) == name { count++ } } diff --git a/src/build/token/Count_test.go b/src/build/token/Count_test.go index 0128e62..8742d13 100644 --- a/src/build/token/Count_test.go +++ b/src/build/token/Count_test.go @@ -8,9 +8,10 @@ import ( ) func TestCount(t *testing.T) { - tokens := token.Tokenize([]byte(`a b b c c c`)) - assert.Equal(t, token.Count(tokens, token.Identifier, "a"), 1) - assert.Equal(t, token.Count(tokens, token.Identifier, "b"), 2) - assert.Equal(t, token.Count(tokens, token.Identifier, "c"), 3) - assert.Equal(t, token.Count(tokens, token.Identifier, "d"), 0) + buffer := []byte(`a b b c c c`) + tokens := token.Tokenize(buffer) + assert.Equal(t, token.Count(tokens, buffer, token.Identifier, "a"), 1) + assert.Equal(t, token.Count(tokens, buffer, token.Identifier, "b"), 2) + assert.Equal(t, token.Count(tokens, buffer, token.Identifier, "c"), 3) + assert.Equal(t, token.Count(tokens, buffer, token.Identifier, "d"), 0) } diff --git a/src/build/token/Keywords.go b/src/build/token/Keywords.go new file mode 100644 index 0000000..c75fd75 --- /dev/null +++ b/src/build/token/Keywords.go @@ -0,0 +1,9 @@ +package token + +// Keywords is a map of all keywords used in the language. +var Keywords = map[string]Kind{ + "if": If, + "import": Import, + "loop": Loop, + "return": Return, +} diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index b221442..1df7adf 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -4,73 +4,62 @@ package token type Kind uint8 const ( - // Invalid represents an invalid token. - Invalid Kind = iota - - // EOF represents the end of file. - EOF - - // NewLine represents the newline character. - NewLine - - // Identifier represents a series of characters used to identify a variable or function. - Identifier - - // Keyword represents a language keyword. - Keyword - - // String represents an uninterpreted series of characters in the source code. - String - - // Number represents a series of numerical characters. - Number - - // Operator represents a mathematical operator. - Operator - - // Separator represents a comma. - Separator - - // Comment represents a comment. - Comment - - // GroupStart represents '('. - GroupStart - - // GroupEnd represents ')'. - GroupEnd - - // BlockStart represents '{'. - BlockStart - - // BlockEnd represents '}'. - BlockEnd - - // ArrayStart represents '['. - ArrayStart - - // ArrayEnd represents ']'. - ArrayEnd + Invalid Kind = iota // Invalid is an invalid token. + EOF // EOF is the end of file. + NewLine // NewLine is the newline character. + Identifier // Identifier is a series of characters used to identify a variable or function. + Number // Number is a series of numerical characters. + String // String is an uninterpreted series of characters in the source code. + Comment // Comment is a comment. + Separator // , + GroupStart // ( + GroupEnd // ) + BlockStart // { + BlockEnd // } + ArrayStart // [ + ArrayEnd // ] + _keywords // + If // if + Import // import + Loop // loop + Return // return + _keywordsEnd // + _operators // + Add // + + Sub // - + Mul // * + Div // / + Mod // % + And // & + Or // | + Xor // ^ + Shl // << + Shr // >> + LogicalAnd // && + LogicalOr // || + Equal // == + Less // < + Greater // > + Not // ! + NotEqual // != + LessEqual // <= + GreaterEqual // >= + Define // := + Period // . + Call // x() + Array // [x] + _assignments // + Assign // = + AddAssign // += + SubAssign // -= + MulAssign // *= + DivAssign // /= + ModAssign // %= + AndAssign // &= + OrAssign // |= + XorAssign // ^= + ShlAssign // <<= + ShrAssign // >>= + _assignmentsEnd // + _operatorsEnd // ) - -// String returns the text representation. -func (kind Kind) String() string { - return [...]string{ - "Invalid", - "EOF", - "NewLine", - "Identifier", - "Keyword", - "String", - "Number", - "Operator", - "Separator", - "Comment", - "GroupStart", - "GroupEnd", - "BlockStart", - "BlockEnd", - "ArrayStart", - "ArrayEnd", - }[kind] -} diff --git a/src/build/token/Kind_test.go b/src/build/token/Kind_test.go deleted file mode 100644 index 1db0382..0000000 --- a/src/build/token/Kind_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package token_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/go/assert" -) - -func TestTokenKind(t *testing.T) { - assert.Equal(t, token.Invalid.String(), "Invalid") - assert.Equal(t, token.EOF.String(), "EOF") - assert.Equal(t, token.NewLine.String(), "NewLine") - assert.Equal(t, token.Identifier.String(), "Identifier") - assert.Equal(t, token.Keyword.String(), "Keyword") - assert.Equal(t, token.String.String(), "String") - assert.Equal(t, token.Number.String(), "Number") - assert.Equal(t, token.Operator.String(), "Operator") - assert.Equal(t, token.Separator.String(), "Separator") - assert.Equal(t, token.Comment.String(), "Comment") - assert.Equal(t, token.GroupStart.String(), "GroupStart") - assert.Equal(t, token.GroupEnd.String(), "GroupEnd") - assert.Equal(t, token.BlockStart.String(), "BlockStart") - assert.Equal(t, token.BlockEnd.String(), "BlockEnd") - assert.Equal(t, token.ArrayStart.String(), "ArrayStart") - assert.Equal(t, token.ArrayEnd.String(), "ArrayEnd") -} diff --git a/src/build/token/Length.go b/src/build/token/Length.go new file mode 100644 index 0000000..6711706 --- /dev/null +++ b/src/build/token/Length.go @@ -0,0 +1,4 @@ +package token + +// Length is the data type for storing token lengths. +type Length = uint16 diff --git a/src/build/token/List.go b/src/build/token/List.go index 1b725b6..7e57693 100644 --- a/src/build/token/List.go +++ b/src/build/token/List.go @@ -1,9 +1,5 @@ package token -import ( - "bytes" -) - // List is a slice of tokens. type List []Token @@ -28,20 +24,3 @@ func (list List) LastIndexKind(kind Kind) int { return -1 } - -// String implements string serialization. -func (list List) String() string { - builder := bytes.Buffer{} - var last Token - - for _, t := range list { - if last.Kind == Keyword || last.Kind == Separator || last.Kind == Operator || t.Kind == Operator { - builder.WriteByte(' ') - } - - builder.Write(t.Bytes) - last = t - } - - return builder.String() -} diff --git a/src/build/token/Operators.go b/src/build/token/Operators.go new file mode 100644 index 0000000..842fb94 --- /dev/null +++ b/src/build/token/Operators.go @@ -0,0 +1,39 @@ +package token + +// Operators is a map of all operators used in the language. +var Operators = map[string]Kind{ + ".": Period, + "=": Assign, + ":=": Define, + "+": Add, + "-": Sub, + "*": Mul, + "/": Div, + "%": Mod, + "&": And, + "|": Or, + "^": Xor, + "<<": Shl, + ">>": Shr, + "&&": LogicalAnd, + "||": LogicalOr, + "!": Not, + "==": Equal, + "!=": NotEqual, + ">": Greater, + "<": Less, + ">=": GreaterEqual, + "<=": LessEqual, + "+=": AddAssign, + "-=": SubAssign, + "*=": MulAssign, + "/=": DivAssign, + "%=": ModAssign, + "&=": AndAssign, + "|=": OrAssign, + "^=": XorAssign, + "<<=": ShlAssign, + ">>=": ShrAssign, + "λ": Call, + "@": Array, +} diff --git a/src/build/token/Token.go b/src/build/token/Token.go index 71dd1e5..134f054 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -1,7 +1,6 @@ package token import ( - "fmt" "unsafe" ) @@ -9,29 +8,44 @@ import ( // The characters that make up an identifier are grouped into a single token. // This makes parsing easier and allows us to do better syntax checks. type Token struct { - Bytes []byte Position Position + Length Length Kind Kind } -// End returns the position after the token. -func (t *Token) End() Position { - return t.Position + Position(len(t.Bytes)) +// Bytes returns the byte slice. +func (t Token) Bytes(buffer []byte) []byte { + return buffer[t.Position : t.Position+Position(t.Length)] } -// String creates a human readable representation for debugging purposes. -func (t *Token) String() string { - return fmt.Sprintf("%s %s", t.Kind, t.Text()) +// End returns the position after the token. +func (t Token) End() Position { + return t.Position + Position(t.Length) +} + +// IsAssignment returns true if the token is an assignment operator. +func (t Token) IsAssignment() bool { + return t.Kind > _assignments && t.Kind < _assignmentsEnd +} + +// IsKeyword returns true if the token is a keyword. +func (t Token) IsKeyword() bool { + return t.Kind > _keywords && t.Kind < _keywordsEnd +} + +// IsOperator returns true if the token is an operator. +func (t Token) IsOperator() bool { + return t.Kind > _operators && t.Kind < _operatorsEnd } // Reset resets the token to default values. func (t *Token) Reset() { - t.Kind = Invalid t.Position = 0 - t.Bytes = nil + t.Length = 0 + t.Kind = Invalid } // Text returns the token text. -func (t *Token) Text() string { - return unsafe.String(unsafe.SliceData(t.Bytes), len(t.Bytes)) +func (t Token) Text(buffer []byte) string { + return unsafe.String(unsafe.SliceData(t.Bytes(buffer)), t.Length) } diff --git a/src/build/token/Token_test.go b/src/build/token/Token_test.go index 47fc665..ba825cc 100644 --- a/src/build/token/Token_test.go +++ b/src/build/token/Token_test.go @@ -10,8 +10,8 @@ import ( func TestTokenEnd(t *testing.T) { hello := token.Token{ Kind: token.Identifier, - Bytes: []byte("hello"), Position: 0, + Length: 5, } assert.Equal(t, hello.End(), 5) @@ -20,34 +20,33 @@ func TestTokenEnd(t *testing.T) { func TestTokenReset(t *testing.T) { hello := token.Token{ Kind: token.Identifier, - Bytes: []byte("hello"), Position: 1, + Length: 5, } hello.Reset() - assert.Nil(t, hello.Bytes) assert.Equal(t, hello.Position, 0) + assert.Equal(t, hello.Length, 0) assert.Equal(t, hello.Kind, token.Invalid) } -func TestTokenString(t *testing.T) { - hello := token.Token{ - Kind: token.Identifier, - Bytes: []byte("hello"), - Position: 0, - } - - assert.Equal(t, hello.String(), "Identifier hello") -} - func TestTokenText(t *testing.T) { - hello := token.Token{Kind: token.Identifier, Bytes: []byte("hello"), Position: 0} - comma := token.Token{Kind: token.Separator, Bytes: []byte(","), Position: 5} - world := token.Token{Kind: token.Identifier, Bytes: []byte("world"), Position: 7} + buffer := []byte("hello, world") + hello := token.Token{Kind: token.Identifier, Position: 0, Length: 5} + comma := token.Token{Kind: token.Separator, Position: 5, Length: 1} + world := token.Token{Kind: token.Identifier, Position: 7, Length: 5} - assert.Equal(t, hello.Text(), "hello") - assert.Equal(t, world.Text(), "world") - - list := token.List{hello, comma, world} - assert.Equal(t, list.String(), "hello, world") + assert.Equal(t, hello.Text(buffer), "hello") + assert.Equal(t, comma.Text(buffer), ",") + assert.Equal(t, world.Text(buffer), "world") +} + +func TestTokenGroups(t *testing.T) { + assignment := token.Token{Kind: token.Assign} + operator := token.Token{Kind: token.Add} + keyword := token.Token{Kind: token.If} + + assert.True(t, assignment.IsAssignment()) + assert.True(t, operator.IsOperator()) + assert.True(t, keyword.IsKeyword()) } diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 649f376..0060958 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -1,20 +1,5 @@ package token -import "git.akyoto.dev/cli/q/src/build/keyword" - -// Pre-allocate these byte buffers so we can re-use them -// instead of allocating a new buffer every time. -var ( - groupStartBytes = []byte{'('} - groupEndBytes = []byte{')'} - blockStartBytes = []byte{'{'} - blockEndBytes = []byte{'}'} - arrayStartBytes = []byte{'['} - arrayEndBytes = []byte{']'} - separatorBytes = []byte{','} - newLineBytes = []byte{'\n'} -) - // Tokenize turns the file contents into a list of tokens. func Tokenize(buffer []byte) List { var ( @@ -26,21 +11,21 @@ func Tokenize(buffer []byte) List { switch buffer[i] { case ' ', '\t': case ',': - tokens = append(tokens, Token{Kind: Separator, Position: i, Bytes: separatorBytes}) + tokens = append(tokens, Token{Kind: Separator, Position: i, Length: 1}) case '(': - tokens = append(tokens, Token{Kind: GroupStart, Position: i, Bytes: groupStartBytes}) + tokens = append(tokens, Token{Kind: GroupStart, Position: i, Length: 1}) case ')': - tokens = append(tokens, Token{Kind: GroupEnd, Position: i, Bytes: groupEndBytes}) + tokens = append(tokens, Token{Kind: GroupEnd, Position: i, Length: 1}) case '{': - tokens = append(tokens, Token{Kind: BlockStart, Position: i, Bytes: blockStartBytes}) + tokens = append(tokens, Token{Kind: BlockStart, Position: i, Length: 1}) case '}': - tokens = append(tokens, Token{Kind: BlockEnd, Position: i, Bytes: blockEndBytes}) + tokens = append(tokens, Token{Kind: BlockEnd, Position: i, Length: 1}) case '[': - tokens = append(tokens, Token{Kind: ArrayStart, Position: i, Bytes: arrayStartBytes}) + tokens = append(tokens, Token{Kind: ArrayStart, Position: i, Length: 1}) case ']': - tokens = append(tokens, Token{Kind: ArrayEnd, Position: i, Bytes: arrayEndBytes}) + tokens = append(tokens, Token{Kind: ArrayEnd, Position: i, Length: 1}) case '\n': - tokens = append(tokens, Token{Kind: NewLine, Position: i, Bytes: newLineBytes}) + tokens = append(tokens, Token{Kind: NewLine, Position: i, Length: 1}) case '/': if i+1 >= Position(len(buffer)) || buffer[i+1] != '/' { position := i @@ -50,7 +35,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: Operator, Position: position, Bytes: buffer[position:i]}) + tokens = append(tokens, Token{Kind: Operators[string(buffer[position:i])], Position: position, Length: Length(i - position)}) } else { position := i @@ -58,7 +43,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: Comment, Position: position, Bytes: buffer[position:i]}) + tokens = append(tokens, Token{Kind: Comment, Position: position, Length: Length(i - position)}) } continue @@ -78,7 +63,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: String, Position: start, Bytes: buffer[start:end]}) + tokens = append(tokens, Token{Kind: String, Position: start, Length: Length(end - start)}) continue default: @@ -91,14 +76,14 @@ func Tokenize(buffer []byte) List { } identifier := buffer[position:i] - keyword, isKeyword := keyword.Map[string(identifier)] + kind := Identifier + keyword, isKeyword := Keywords[string(identifier)] if isKeyword { - tokens = append(tokens, Token{Kind: Keyword, Position: position, Bytes: keyword}) - } else { - tokens = append(tokens, Token{Kind: Identifier, Position: position, Bytes: identifier}) + kind = keyword } + tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(len(identifier))}) continue } @@ -110,7 +95,7 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: Number, Position: position, Bytes: buffer[position:i]}) + tokens = append(tokens, Token{Kind: Number, Position: position, Length: Length(i - position)}) continue } @@ -122,17 +107,17 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: Operator, Position: position, Bytes: buffer[position:i]}) + tokens = append(tokens, Token{Kind: Operators[string(buffer[position:i])], Position: position, Length: Length(i - position)}) continue } - tokens = append(tokens, Token{Kind: Invalid, Position: i, Bytes: buffer[i : i+1]}) + tokens = append(tokens, Token{Kind: Invalid, Position: i, Length: 1}) } i++ } - tokens = append(tokens, Token{Kind: EOF, Position: i, Bytes: nil}) + tokens = append(tokens, Token{Kind: EOF, Position: i, Length: 0}) return tokens } diff --git a/src/build/token/Tokenize_test.go b/src/build/token/Tokenize_test.go index 63e689d..19f57e5 100644 --- a/src/build/token/Tokenize_test.go +++ b/src/build/token/Tokenize_test.go @@ -9,394 +9,243 @@ import ( func TestFunction(t *testing.T) { tokens := token.Tokenize([]byte("main(){}")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Identifier, - Bytes: []byte("main"), - Position: 0, - }, - { - Kind: token.GroupStart, - Bytes: []byte("("), - Position: 4, - }, - { - Kind: token.GroupEnd, - Bytes: []byte(")"), - Position: 5, - }, - { - Kind: token.BlockStart, - Bytes: []byte("{"), - Position: 6, - }, - { - Kind: token.BlockEnd, - Bytes: []byte("}"), - Position: 7, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 8, - }, - }) + + expected := []token.Kind{ + token.Identifier, + token.GroupStart, + token.GroupEnd, + token.BlockStart, + token.BlockEnd, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestKeyword(t *testing.T) { tokens := token.Tokenize([]byte("return x")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Keyword, - Bytes: []byte("return"), - Position: 0, - }, - { - Kind: token.Identifier, - Bytes: []byte("x"), - Position: 7, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 8, - }, - }) + + expected := []token.Kind{ + token.Return, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestArray(t *testing.T) { tokens := token.Tokenize([]byte("array[i]")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Identifier, - Bytes: []byte("array"), - Position: 0, - }, - { - Kind: token.ArrayStart, - Bytes: []byte("["), - Position: 5, - }, - { - Kind: token.Identifier, - Bytes: []byte("i"), - Position: 6, - }, - { - Kind: token.ArrayEnd, - Bytes: []byte("]"), - Position: 7, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 8, - }, - }) + + expected := []token.Kind{ + token.Identifier, + token.ArrayStart, + token.Identifier, + token.ArrayEnd, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestNewline(t *testing.T) { tokens := token.Tokenize([]byte("\n\n")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.NewLine, - Bytes: []byte("\n"), - Position: 0, - }, - { - Kind: token.NewLine, - Bytes: []byte("\n"), - Position: 1, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 2, - }, - }) + + expected := []token.Kind{ + token.NewLine, + token.NewLine, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestNumber(t *testing.T) { tokens := token.Tokenize([]byte(`123 456`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Number, - Bytes: []byte("123"), - Position: 0, - }, - { - Kind: token.Number, - Bytes: []byte("456"), - Position: 4, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 7, - }, - }) + + expected := []token.Kind{ + token.Number, + token.Number, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestOperator(t *testing.T) { tokens := token.Tokenize([]byte(`+ - * /`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Operator, - Bytes: []byte("+"), - Position: 0, - }, - { - Kind: token.Operator, - Bytes: []byte("-"), - Position: 2, - }, - { - Kind: token.Operator, - Bytes: []byte("*"), - Position: 4, - }, - { - Kind: token.Operator, - Bytes: []byte("/"), - Position: 6, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 7, - }, - }) + + expected := []token.Kind{ + token.Add, + token.Sub, + token.Mul, + token.Div, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestOperatorAssign(t *testing.T) { - tokens := token.Tokenize([]byte(`+= -= *= /= ==`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Operator, - Bytes: []byte("+="), - Position: 0, - }, - { - Kind: token.Operator, - Bytes: []byte("-="), - Position: 3, - }, - { - Kind: token.Operator, - Bytes: []byte("*="), - Position: 6, - }, - { - Kind: token.Operator, - Bytes: []byte("/="), - Position: 9, - }, - { - Kind: token.Operator, - Bytes: []byte("=="), - Position: 12, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 14, - }, - }) + tokens := token.Tokenize([]byte(`+= -= *= /= &= |= ^= <<= >>=`)) + + expected := []token.Kind{ + token.AddAssign, + token.SubAssign, + token.MulAssign, + token.DivAssign, + token.AndAssign, + token.OrAssign, + token.XorAssign, + token.ShlAssign, + token.ShrAssign, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestSeparator(t *testing.T) { tokens := token.Tokenize([]byte("a,b,c")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Identifier, - Bytes: []byte("a"), - Position: 0, - }, - { - Kind: token.Separator, - Bytes: []byte(","), - Position: 1, - }, - { - Kind: token.Identifier, - Bytes: []byte("b"), - Position: 2, - }, - { - Kind: token.Separator, - Bytes: []byte(","), - Position: 3, - }, - { - Kind: token.Identifier, - Bytes: []byte("c"), - Position: 4, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 5, - }, - }) + + expected := []token.Kind{ + token.Identifier, + token.Separator, + token.Identifier, + token.Separator, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestComment(t *testing.T) { tokens := token.Tokenize([]byte("// Hello\n// World")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Comment, - Bytes: []byte(`// Hello`), - Position: 0, - }, - { - Kind: token.NewLine, - Bytes: []byte("\n"), - Position: 8, - }, - { - Kind: token.Comment, - Bytes: []byte(`// World`), - Position: 9, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 17, - }, - }) + + expected := []token.Kind{ + token.Comment, + token.NewLine, + token.Comment, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } tokens = token.Tokenize([]byte("// Hello\n")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Comment, - Bytes: []byte(`// Hello`), - Position: 0, - }, - { - Kind: token.NewLine, - Bytes: []byte("\n"), - Position: 8, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 9, - }, - }) + + expected = []token.Kind{ + token.Comment, + token.NewLine, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } tokens = token.Tokenize([]byte(`// Hello`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Comment, - Bytes: []byte(`// Hello`), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 8, - }, - }) + + expected = []token.Kind{ + token.Comment, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } tokens = token.Tokenize([]byte(`//`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Comment, - Bytes: []byte(`//`), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 2, - }, - }) + + expected = []token.Kind{ + token.Comment, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } tokens = token.Tokenize([]byte(`/`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Operator, - Bytes: []byte(`/`), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 1, - }, - }) + + expected = []token.Kind{ + token.Div, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestInvalid(t *testing.T) { - tokens := token.Tokenize([]byte(`@#`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.Invalid, - Bytes: []byte(`@`), - Position: 0, - }, - { - Kind: token.Invalid, - Bytes: []byte(`#`), - Position: 1, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 2, - }, - }) + tokens := token.Tokenize([]byte(`##`)) + + expected := []token.Kind{ + token.Invalid, + token.Invalid, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestString(t *testing.T) { tokens := token.Tokenize([]byte(`"Hello" "World"`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.String, - Bytes: []byte(`"Hello"`), - Position: 0, - }, - { - Kind: token.String, - Bytes: []byte(`"World"`), - Position: 8, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 15, - }, - }) + + expected := []token.Kind{ + token.String, + token.String, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestStringMultiline(t *testing.T) { tokens := token.Tokenize([]byte("\"Hello\nWorld\"")) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.String, - Bytes: []byte("\"Hello\nWorld\""), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 13, - }, - }) + + expected := []token.Kind{ + token.String, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } func TestStringEOF(t *testing.T) { tokens := token.Tokenize([]byte(`"EOF`)) - assert.DeepEqual(t, tokens, token.List{ - { - Kind: token.String, - Bytes: []byte(`"EOF`), - Position: 0, - }, - { - Kind: token.EOF, - Bytes: nil, - Position: 4, - }, - }) + + expected := []token.Kind{ + token.String, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } } diff --git a/tests/errors/InvalidOperator.q b/tests/errors/InvalidCharacter4.q similarity index 100% rename from tests/errors/InvalidOperator.q rename to tests/errors/InvalidCharacter4.q diff --git a/tests/errors_test.go b/tests/errors_test.go index 6c08acd..5b28ee2 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -21,10 +21,10 @@ var errs = []struct { {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}}, {"InvalidExpression.q", errors.InvalidExpression}, - {"InvalidOperator.q", &errors.InvalidOperator{Operator: "+++"}}, {"InvalidCharacter.q", &errors.InvalidCharacter{Character: "@"}}, {"InvalidCharacter2.q", &errors.InvalidCharacter{Character: "@"}}, {"InvalidCharacter3.q", &errors.InvalidCharacter{Character: "@"}}, + {"InvalidCharacter4.q", &errors.InvalidCharacter{Character: "+++"}}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, From d35498b77a5d9b41fb378ac2e22105fa98facb0f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 21 Jul 2024 15:45:49 +0200 Subject: [PATCH 0343/1012] Reduced memory usage --- src/build/core/AddVariable.go | 13 +++++++++---- src/build/core/NewFunction.go | 6 ++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/build/core/AddVariable.go b/src/build/core/AddVariable.go index 522f1df..8b36a56 100644 --- a/src/build/core/AddVariable.go +++ b/src/build/core/AddVariable.go @@ -11,8 +11,13 @@ func (f *Function) AddVariable(variable *scope.Variable) { f.Comment("%s = %s (%d uses)", variable.Name, variable.Register, variable.Alive) } - scope := f.CurrentScope() - variable.Scope = scope - scope.Variables[variable.Name] = variable - scope.Use(variable.Register) + s := f.CurrentScope() + variable.Scope = s + + if s.Variables == nil { + s.Variables = map[string]*scope.Variable{} + } + + s.Variables[variable.Name] = variable + s.Use(variable.Register) } diff --git a/src/build/core/NewFunction.go b/src/build/core/NewFunction.go index 2dc683d..8ad25ba 100644 --- a/src/build/core/NewFunction.go +++ b/src/build/core/NewFunction.go @@ -16,12 +16,10 @@ func NewFunction(name string, file *fs.File, body []token.Token) *Function { File: file, Body: body, Assembler: asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 32), + Instructions: make([]asm.Instruction, 0, 8), }, Stack: scope.Stack{ - Scopes: []*scope.Scope{ - {Variables: map[string]*scope.Variable{}}, - }, + Scopes: []*scope.Scope{{}}, }, cpu: cpu.CPU{ All: x64.AllRegisters, From 3ea2f280d98916bdecc6eada44f7672d23134575 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 21 Jul 2024 15:45:49 +0200 Subject: [PATCH 0344/1012] Reduced memory usage --- src/build/core/AddVariable.go | 13 +++++++++---- src/build/core/NewFunction.go | 6 ++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/build/core/AddVariable.go b/src/build/core/AddVariable.go index 522f1df..8b36a56 100644 --- a/src/build/core/AddVariable.go +++ b/src/build/core/AddVariable.go @@ -11,8 +11,13 @@ func (f *Function) AddVariable(variable *scope.Variable) { f.Comment("%s = %s (%d uses)", variable.Name, variable.Register, variable.Alive) } - scope := f.CurrentScope() - variable.Scope = scope - scope.Variables[variable.Name] = variable - scope.Use(variable.Register) + s := f.CurrentScope() + variable.Scope = s + + if s.Variables == nil { + s.Variables = map[string]*scope.Variable{} + } + + s.Variables[variable.Name] = variable + s.Use(variable.Register) } diff --git a/src/build/core/NewFunction.go b/src/build/core/NewFunction.go index 2dc683d..8ad25ba 100644 --- a/src/build/core/NewFunction.go +++ b/src/build/core/NewFunction.go @@ -16,12 +16,10 @@ func NewFunction(name string, file *fs.File, body []token.Token) *Function { File: file, Body: body, Assembler: asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 32), + Instructions: make([]asm.Instruction, 0, 8), }, Stack: scope.Stack{ - Scopes: []*scope.Scope{ - {Variables: map[string]*scope.Variable{}}, - }, + Scopes: []*scope.Scope{{}}, }, cpu: cpu.CPU{ All: x64.AllRegisters, From eaeeba495eccb37bc1ff0fb4a0b2c2b46bab547f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 21 Jul 2024 21:30:37 +0200 Subject: [PATCH 0345/1012] Added socket syscalls --- lib/sys/linux.q | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/sys/linux.q b/lib/sys/linux.q index 68a8fbf..d582d1c 100644 --- a/lib/sys/linux.q +++ b/lib/sys/linux.q @@ -30,6 +30,22 @@ exit(code) { syscall(60, code) } +socket(family, type, protocol) { + return syscall(41, family, type, protocol) +} + +accept(fd, address, length) { + return syscall(43, fd, address, length) +} + +bind(fd, address, length) { + return syscall(49, fd, address, length) +} + +listen(fd, backlog) { + return syscall(50, fd, backlog) +} + getcwd(buffer, length) { return syscall(79, buffer, length) } From e91e894046044e37376a13a67154b82a137840eb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 21 Jul 2024 21:30:37 +0200 Subject: [PATCH 0346/1012] Added socket syscalls --- lib/sys/linux.q | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/sys/linux.q b/lib/sys/linux.q index 68a8fbf..d582d1c 100644 --- a/lib/sys/linux.q +++ b/lib/sys/linux.q @@ -30,6 +30,22 @@ exit(code) { syscall(60, code) } +socket(family, type, protocol) { + return syscall(41, family, type, protocol) +} + +accept(fd, address, length) { + return syscall(43, fd, address, length) +} + +bind(fd, address, length) { + return syscall(49, fd, address, length) +} + +listen(fd, backlog) { + return syscall(50, fd, backlog) +} + getcwd(buffer, length) { return syscall(79, buffer, length) } From f7645104fb9432984c1f23b82aae49419b974537 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 22 Jul 2024 15:32:16 +0200 Subject: [PATCH 0347/1012] Implemented runes --- examples/array/array.q | 11 ++++---- src/build/core/CompileAssign.go | 14 ++++++---- src/build/core/ExecuteLeaf.go | 7 ++--- src/build/core/Number.go | 36 +++++++++++++++++++++++++ src/build/core/TokenToRegister.go | 8 +++--- src/build/errors/CompileErrors.go | 6 +++-- src/build/errors/NumberExceedsBounds.go | 15 +++++++++++ src/build/expression/Parse.go | 2 +- src/build/token/Kind.go | 1 + src/build/token/Tokenize.go | 13 ++++++--- tests/examples_test.go | 2 +- 11 files changed, 88 insertions(+), 27 deletions(-) create mode 100644 src/build/core/Number.go create mode 100644 src/build/errors/NumberExceedsBounds.go diff --git a/examples/array/array.q b/examples/array/array.q index 2cdf875..a57691a 100644 --- a/examples/array/array.q +++ b/examples/array/array.q @@ -2,12 +2,13 @@ import mem import sys main() { - length := 4 + length := 5 address := mem.alloc(length) - address[0] = 65 - address[1] = 66 - address[2] = 67 - address[3] = 68 + address[0] = 'H' + address[1] = 'e' + address[2] = 'l' + address[3] = 'l' + address[4] = 'o' sys.write(1, address, length) mem.free(address, length) } \ No newline at end of file diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index 4d3e08b..767a2df 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -1,8 +1,6 @@ package core import ( - "strconv" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" @@ -38,19 +36,25 @@ func (f *Function) CompileAssign(node *ast.Assign) error { defer f.useVariable(variable) index := left.Children[1] - offset, err := strconv.Atoi(index.Token.Text(f.File.Bytes)) + offset, _, err := f.Number(index.Token) if err != nil { return err } - num, err := strconv.Atoi(right.Token.Text(f.File.Bytes)) + number, size, err := f.Number(right.Token) if err != nil { return err } - f.MemoryNumber(asm.STORE, asm.Memory{Base: variable.Register, Offset: byte(offset), Length: 1}, num) + elementSize := byte(1) + + if size != elementSize { + return errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: elementSize, Size: size}, f.File, right.Token.Position) + } + + f.MemoryNumber(asm.STORE, asm.Memory{Base: variable.Register, Offset: byte(offset), Length: elementSize}, number) return nil } diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index d6cc124..8dbda36 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -1,8 +1,6 @@ package core import ( - "strconv" - "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/token" @@ -22,9 +20,8 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope defer f.useVariable(variable) return f.ExecuteRegisterRegister(operation, register, variable.Register) - case token.Number: - value := operand.Text(f.File.Bytes) - number, err := strconv.Atoi(value) + case token.Number, token.Rune: + number, _, err := f.Number(operand) if err != nil { return err diff --git a/src/build/core/Number.go b/src/build/core/Number.go new file mode 100644 index 0000000..a3d2f47 --- /dev/null +++ b/src/build/core/Number.go @@ -0,0 +1,36 @@ +package core + +import ( + "strconv" + "unicode/utf8" + + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Number tries to convert the token into a numeric value. +func (f *Function) Number(t token.Token) (int, byte, error) { + switch t.Kind { + case token.Number: + number, err := strconv.Atoi(t.Text(f.File.Bytes)) + return number, 8, err + + case token.Rune: + r := t.Bytes(f.File.Bytes) + r = r[1 : len(r)-1] + + if len(r) == 0 { + return 0, 0, errors.New(errors.InvalidRune, f.File, t.Position+1) + } + + number, size := utf8.DecodeRune(r) + + if len(r) > size { + return 0, 0, errors.New(errors.InvalidRune, f.File, t.Position+1) + } + + return int(number), byte(size), nil + } + + return 0, 0, errors.New(errors.InvalidNumber, f.File, t.Position) +} diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index f048fac..36af04f 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -2,7 +2,6 @@ package core import ( "fmt" - "strconv" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" @@ -26,15 +25,14 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { f.RegisterRegister(asm.MOVE, register, variable.Register) return nil - case token.Number: - value := t.Text(f.File.Bytes) - n, err := strconv.Atoi(value) + case token.Number, token.Rune: + number, _, err := f.Number(t) if err != nil { return err } - f.RegisterNumber(asm.MOVE, register, n) + f.RegisterNumber(asm.MOVE, register, number) return nil case token.String: diff --git a/src/build/errors/CompileErrors.go b/src/build/errors/CompileErrors.go index 886d1f8..3aab709 100644 --- a/src/build/errors/CompileErrors.go +++ b/src/build/errors/CompileErrors.go @@ -1,9 +1,11 @@ package errors var ( - InvalidStatement = &Base{"Invalid statement"} + InvalidNumber = &Base{"Invalid number"} InvalidExpression = &Base{"Invalid expression"} - MissingOperand = &Base{"Missing operand"} + InvalidRune = &Base{"Invalid rune"} + InvalidStatement = &Base{"Invalid statement"} MissingMainFunction = &Base{"Missing main function"} + MissingOperand = &Base{"Missing operand"} NotImplemented = &Base{"Not implemented"} ) diff --git a/src/build/errors/NumberExceedsBounds.go b/src/build/errors/NumberExceedsBounds.go new file mode 100644 index 0000000..4d33f43 --- /dev/null +++ b/src/build/errors/NumberExceedsBounds.go @@ -0,0 +1,15 @@ +package errors + +import "fmt" + +// NumberExceedsBounds error is created when the number doesn't fit into the destination. +type NumberExceedsBounds struct { + Number int + Size byte + ExpectedSize byte +} + +// Error generates the string representation. +func (err *NumberExceedsBounds) Error() string { + return fmt.Sprintf("Number %d needs %d bytes but the maximum is %d bytes", err.Number, err.Size, err.ExpectedSize) +} diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index f2e8b56..7fbd6ad 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -89,7 +89,7 @@ func Parse(tokens []token.Token) *Expression { continue } - if t.Kind == token.Identifier || t.Kind == token.Number || t.Kind == token.String { + if t.Kind == token.Identifier || t.Kind == token.Number || t.Kind == token.String || t.Kind == token.Rune { if cursor != nil { node := NewLeaf(t) cursor.AddChild(node) diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 1df7adf..53997c4 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -9,6 +9,7 @@ const ( NewLine // NewLine is the newline character. Identifier // Identifier is a series of characters used to identify a variable or function. Number // Number is a series of numerical characters. + Rune // Rune is a single unicode code point. String // String is an uninterpreted series of characters in the source code. Comment // Comment is a comment. Separator // , diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 0060958..f0d282c 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -48,13 +48,14 @@ func Tokenize(buffer []byte) List { continue - case '"': + case '"', '\'': + limiter := buffer[i] start := i end := Position(len(buffer)) i++ for i < Position(len(buffer)) { - if buffer[i] == '"' { + if buffer[i] == limiter { end = i + 1 i++ break @@ -63,7 +64,13 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: String, Position: start, Length: Length(end - start)}) + kind := String + + if limiter == '\'' { + kind = Rune + } + + tokens = append(tokens, Token{Kind: kind, Position: start, Length: Length(end - start)}) continue default: diff --git a/tests/examples_test.go b/tests/examples_test.go index 03139f0..e4b083a 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -17,7 +17,7 @@ var examples = []struct { {"hello", "", "Hello", 0}, {"factorial", "", "", 120}, {"fibonacci", "", "", 55}, - {"array", "", "ABCD", 0}, + {"array", "", "Hello", 0}, } func TestExamples(t *testing.T) { From 21017e63787412ca345dd9160330d83415798355 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 22 Jul 2024 15:32:16 +0200 Subject: [PATCH 0348/1012] Implemented runes --- examples/array/array.q | 11 ++++---- src/build/core/CompileAssign.go | 14 ++++++---- src/build/core/ExecuteLeaf.go | 7 ++--- src/build/core/Number.go | 36 +++++++++++++++++++++++++ src/build/core/TokenToRegister.go | 8 +++--- src/build/errors/CompileErrors.go | 6 +++-- src/build/errors/NumberExceedsBounds.go | 15 +++++++++++ src/build/expression/Parse.go | 2 +- src/build/token/Kind.go | 1 + src/build/token/Tokenize.go | 13 ++++++--- tests/examples_test.go | 2 +- 11 files changed, 88 insertions(+), 27 deletions(-) create mode 100644 src/build/core/Number.go create mode 100644 src/build/errors/NumberExceedsBounds.go diff --git a/examples/array/array.q b/examples/array/array.q index 2cdf875..a57691a 100644 --- a/examples/array/array.q +++ b/examples/array/array.q @@ -2,12 +2,13 @@ import mem import sys main() { - length := 4 + length := 5 address := mem.alloc(length) - address[0] = 65 - address[1] = 66 - address[2] = 67 - address[3] = 68 + address[0] = 'H' + address[1] = 'e' + address[2] = 'l' + address[3] = 'l' + address[4] = 'o' sys.write(1, address, length) mem.free(address, length) } \ No newline at end of file diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index 4d3e08b..767a2df 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -1,8 +1,6 @@ package core import ( - "strconv" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" @@ -38,19 +36,25 @@ func (f *Function) CompileAssign(node *ast.Assign) error { defer f.useVariable(variable) index := left.Children[1] - offset, err := strconv.Atoi(index.Token.Text(f.File.Bytes)) + offset, _, err := f.Number(index.Token) if err != nil { return err } - num, err := strconv.Atoi(right.Token.Text(f.File.Bytes)) + number, size, err := f.Number(right.Token) if err != nil { return err } - f.MemoryNumber(asm.STORE, asm.Memory{Base: variable.Register, Offset: byte(offset), Length: 1}, num) + elementSize := byte(1) + + if size != elementSize { + return errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: elementSize, Size: size}, f.File, right.Token.Position) + } + + f.MemoryNumber(asm.STORE, asm.Memory{Base: variable.Register, Offset: byte(offset), Length: elementSize}, number) return nil } diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index d6cc124..8dbda36 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -1,8 +1,6 @@ package core import ( - "strconv" - "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/token" @@ -22,9 +20,8 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope defer f.useVariable(variable) return f.ExecuteRegisterRegister(operation, register, variable.Register) - case token.Number: - value := operand.Text(f.File.Bytes) - number, err := strconv.Atoi(value) + case token.Number, token.Rune: + number, _, err := f.Number(operand) if err != nil { return err diff --git a/src/build/core/Number.go b/src/build/core/Number.go new file mode 100644 index 0000000..a3d2f47 --- /dev/null +++ b/src/build/core/Number.go @@ -0,0 +1,36 @@ +package core + +import ( + "strconv" + "unicode/utf8" + + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Number tries to convert the token into a numeric value. +func (f *Function) Number(t token.Token) (int, byte, error) { + switch t.Kind { + case token.Number: + number, err := strconv.Atoi(t.Text(f.File.Bytes)) + return number, 8, err + + case token.Rune: + r := t.Bytes(f.File.Bytes) + r = r[1 : len(r)-1] + + if len(r) == 0 { + return 0, 0, errors.New(errors.InvalidRune, f.File, t.Position+1) + } + + number, size := utf8.DecodeRune(r) + + if len(r) > size { + return 0, 0, errors.New(errors.InvalidRune, f.File, t.Position+1) + } + + return int(number), byte(size), nil + } + + return 0, 0, errors.New(errors.InvalidNumber, f.File, t.Position) +} diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index f048fac..36af04f 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -2,7 +2,6 @@ package core import ( "fmt" - "strconv" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" @@ -26,15 +25,14 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { f.RegisterRegister(asm.MOVE, register, variable.Register) return nil - case token.Number: - value := t.Text(f.File.Bytes) - n, err := strconv.Atoi(value) + case token.Number, token.Rune: + number, _, err := f.Number(t) if err != nil { return err } - f.RegisterNumber(asm.MOVE, register, n) + f.RegisterNumber(asm.MOVE, register, number) return nil case token.String: diff --git a/src/build/errors/CompileErrors.go b/src/build/errors/CompileErrors.go index 886d1f8..3aab709 100644 --- a/src/build/errors/CompileErrors.go +++ b/src/build/errors/CompileErrors.go @@ -1,9 +1,11 @@ package errors var ( - InvalidStatement = &Base{"Invalid statement"} + InvalidNumber = &Base{"Invalid number"} InvalidExpression = &Base{"Invalid expression"} - MissingOperand = &Base{"Missing operand"} + InvalidRune = &Base{"Invalid rune"} + InvalidStatement = &Base{"Invalid statement"} MissingMainFunction = &Base{"Missing main function"} + MissingOperand = &Base{"Missing operand"} NotImplemented = &Base{"Not implemented"} ) diff --git a/src/build/errors/NumberExceedsBounds.go b/src/build/errors/NumberExceedsBounds.go new file mode 100644 index 0000000..4d33f43 --- /dev/null +++ b/src/build/errors/NumberExceedsBounds.go @@ -0,0 +1,15 @@ +package errors + +import "fmt" + +// NumberExceedsBounds error is created when the number doesn't fit into the destination. +type NumberExceedsBounds struct { + Number int + Size byte + ExpectedSize byte +} + +// Error generates the string representation. +func (err *NumberExceedsBounds) Error() string { + return fmt.Sprintf("Number %d needs %d bytes but the maximum is %d bytes", err.Number, err.Size, err.ExpectedSize) +} diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index f2e8b56..7fbd6ad 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -89,7 +89,7 @@ func Parse(tokens []token.Token) *Expression { continue } - if t.Kind == token.Identifier || t.Kind == token.Number || t.Kind == token.String { + if t.Kind == token.Identifier || t.Kind == token.Number || t.Kind == token.String || t.Kind == token.Rune { if cursor != nil { node := NewLeaf(t) cursor.AddChild(node) diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 1df7adf..53997c4 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -9,6 +9,7 @@ const ( NewLine // NewLine is the newline character. Identifier // Identifier is a series of characters used to identify a variable or function. Number // Number is a series of numerical characters. + Rune // Rune is a single unicode code point. String // String is an uninterpreted series of characters in the source code. Comment // Comment is a comment. Separator // , diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 0060958..f0d282c 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -48,13 +48,14 @@ func Tokenize(buffer []byte) List { continue - case '"': + case '"', '\'': + limiter := buffer[i] start := i end := Position(len(buffer)) i++ for i < Position(len(buffer)) { - if buffer[i] == '"' { + if buffer[i] == limiter { end = i + 1 i++ break @@ -63,7 +64,13 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: String, Position: start, Length: Length(end - start)}) + kind := String + + if limiter == '\'' { + kind = Rune + } + + tokens = append(tokens, Token{Kind: kind, Position: start, Length: Length(end - start)}) continue default: diff --git a/tests/examples_test.go b/tests/examples_test.go index 03139f0..e4b083a 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -17,7 +17,7 @@ var examples = []struct { {"hello", "", "Hello", 0}, {"factorial", "", "", 120}, {"fibonacci", "", "", 55}, - {"array", "", "ABCD", 0}, + {"array", "", "Hello", 0}, } func TestExamples(t *testing.T) { From 2b002f03c41cc65a0dc82947a46cb9c5be6c18a4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 22 Jul 2024 15:56:22 +0200 Subject: [PATCH 0349/1012] Simplified system command --- src/build/config/init.go | 32 +++++++++++++++++++------------- src/cli/System.go | 27 +++++++-------------------- 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/build/config/init.go b/src/build/config/init.go index 31732e8..5f0f972 100644 --- a/src/build/config/init.go +++ b/src/build/config/init.go @@ -8,33 +8,39 @@ import ( ) var ( - Executable string - Root string - Library string + Executable string + Root string + Library string + WorkingDirectory string ) func init() { debug.SetGCPercent(-1) - executable, err := os.Executable() + + var err error + Executable, err = os.Executable() if err != nil { panic(err) } - Executable = executable - Root = filepath.Dir(executable) + WorkingDirectory, err = os.Getwd() + + if err != nil { + panic(err) + } + + Root = filepath.Dir(Executable) Library = filepath.Join(Root, "lib") stat, err := os.Stat(Library) - if !os.IsNotExist(err) && stat != nil && stat.IsDir() { - return + if os.IsNotExist(err) || stat == nil || !stat.IsDir() { + findLibrary() } +} - dir, err := os.Getwd() - - if err != nil { - panic(err) - } +func findLibrary() { + dir := WorkingDirectory for { Library = path.Join(dir, "lib") diff --git a/src/cli/System.go b/src/cli/System.go index 831576c..5a26b37 100644 --- a/src/cli/System.go +++ b/src/cli/System.go @@ -2,35 +2,22 @@ package cli import ( "fmt" - "os" "runtime" + "strconv" + + "git.akyoto.dev/cli/q/src/build/config" ) // System shows system information. func System(args []string) int { line := "%-19s%s\n" - fmt.Printf(line, "Platform:", runtime.GOOS) fmt.Printf(line, "Architecture:", runtime.GOARCH) fmt.Printf(line, "Go:", runtime.Version()) - - // Directory - directory, err := os.Getwd() - - if err == nil { - fmt.Printf(line, "Directory:", directory) - } else { - fmt.Printf(line, "Directory:", err.Error()) - } - - // Compiler - executable, err := os.Executable() - - if err == nil { - fmt.Printf(line, "Compiler:", executable) - } else { - fmt.Printf(line, "Compiler:", err.Error()) - } + fmt.Printf(line, "Directory:", config.WorkingDirectory) + fmt.Printf(line, "Compiler:", config.Executable) + fmt.Printf(line, "Library:", config.Library) + fmt.Printf(line, "Threads:", strconv.Itoa(runtime.NumCPU())) return 0 } From 1cee7fcaa07bb7e1b3568245ac236677ba6e83bc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 22 Jul 2024 15:56:22 +0200 Subject: [PATCH 0350/1012] Simplified system command --- src/build/config/init.go | 32 +++++++++++++++++++------------- src/cli/System.go | 27 +++++++-------------------- 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/build/config/init.go b/src/build/config/init.go index 31732e8..5f0f972 100644 --- a/src/build/config/init.go +++ b/src/build/config/init.go @@ -8,33 +8,39 @@ import ( ) var ( - Executable string - Root string - Library string + Executable string + Root string + Library string + WorkingDirectory string ) func init() { debug.SetGCPercent(-1) - executable, err := os.Executable() + + var err error + Executable, err = os.Executable() if err != nil { panic(err) } - Executable = executable - Root = filepath.Dir(executable) + WorkingDirectory, err = os.Getwd() + + if err != nil { + panic(err) + } + + Root = filepath.Dir(Executable) Library = filepath.Join(Root, "lib") stat, err := os.Stat(Library) - if !os.IsNotExist(err) && stat != nil && stat.IsDir() { - return + if os.IsNotExist(err) || stat == nil || !stat.IsDir() { + findLibrary() } +} - dir, err := os.Getwd() - - if err != nil { - panic(err) - } +func findLibrary() { + dir := WorkingDirectory for { Library = path.Join(dir, "lib") diff --git a/src/cli/System.go b/src/cli/System.go index 831576c..5a26b37 100644 --- a/src/cli/System.go +++ b/src/cli/System.go @@ -2,35 +2,22 @@ package cli import ( "fmt" - "os" "runtime" + "strconv" + + "git.akyoto.dev/cli/q/src/build/config" ) // System shows system information. func System(args []string) int { line := "%-19s%s\n" - fmt.Printf(line, "Platform:", runtime.GOOS) fmt.Printf(line, "Architecture:", runtime.GOARCH) fmt.Printf(line, "Go:", runtime.Version()) - - // Directory - directory, err := os.Getwd() - - if err == nil { - fmt.Printf(line, "Directory:", directory) - } else { - fmt.Printf(line, "Directory:", err.Error()) - } - - // Compiler - executable, err := os.Executable() - - if err == nil { - fmt.Printf(line, "Compiler:", executable) - } else { - fmt.Printf(line, "Compiler:", err.Error()) - } + fmt.Printf(line, "Directory:", config.WorkingDirectory) + fmt.Printf(line, "Compiler:", config.Executable) + fmt.Printf(line, "Library:", config.Library) + fmt.Printf(line, "Threads:", strconv.Itoa(runtime.NumCPU())) return 0 } From 6276562777eaf858c1c1b6defd657ad87d1129b2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 22 Jul 2024 17:49:44 +0200 Subject: [PATCH 0351/1012] Reduced memory usage --- src/build/core/AddVariable.go | 10 +--------- src/build/core/VariableByName.go | 2 +- src/build/core/useVariable.go | 2 +- src/build/scope/Scope.go | 20 +++++++++++++++++++- src/build/scope/Stack.go | 10 ++++------ 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/build/core/AddVariable.go b/src/build/core/AddVariable.go index 8b36a56..b04ed1f 100644 --- a/src/build/core/AddVariable.go +++ b/src/build/core/AddVariable.go @@ -11,13 +11,5 @@ func (f *Function) AddVariable(variable *scope.Variable) { f.Comment("%s = %s (%d uses)", variable.Name, variable.Register, variable.Alive) } - s := f.CurrentScope() - variable.Scope = s - - if s.Variables == nil { - s.Variables = map[string]*scope.Variable{} - } - - s.Variables[variable.Name] = variable - s.Use(variable.Register) + f.CurrentScope().AddVariable(variable) } diff --git a/src/build/core/VariableByName.go b/src/build/core/VariableByName.go index a7b9ff7..8cce143 100644 --- a/src/build/core/VariableByName.go +++ b/src/build/core/VariableByName.go @@ -4,5 +4,5 @@ import "git.akyoto.dev/cli/q/src/build/scope" // VariableByName returns the variable with the given name or `nil` if it doesn't exist. func (f *Function) VariableByName(name string) *scope.Variable { - return f.CurrentScope().Variables[name] + return f.CurrentScope().VariableByName(name) } diff --git a/src/build/core/useVariable.go b/src/build/core/useVariable.go index 8109f20..35c058a 100644 --- a/src/build/core/useVariable.go +++ b/src/build/core/useVariable.go @@ -11,7 +11,7 @@ func (f *Function) useVariable(variable *scope.Variable) { continue } - local := scope.Variables[variable.Name] + local := scope.VariableByName(variable.Name) if local == nil { continue diff --git a/src/build/scope/Scope.go b/src/build/scope/Scope.go index 793bb27..da9a1a3 100644 --- a/src/build/scope/Scope.go +++ b/src/build/scope/Scope.go @@ -6,7 +6,25 @@ import ( // Scope represents an independent code block. type Scope struct { - Variables map[string]*Variable + Variables []*Variable InLoop bool cpu.State } + +// AddVariable adds a new variable to the current scope. +func (s *Scope) AddVariable(variable *Variable) { + variable.Scope = s + s.Variables = append(s.Variables, variable) + s.Use(variable.Register) +} + +// VariableByName returns the variable with the given name or `nil` if it doesn't exist. +func (s *Scope) VariableByName(name string) *Variable { + for _, v := range s.Variables { + if v.Name == name { + return v + } + } + + return nil +} diff --git a/src/build/scope/Stack.go b/src/build/scope/Stack.go index 1760f6b..2258cdd 100644 --- a/src/build/scope/Stack.go +++ b/src/build/scope/Stack.go @@ -26,24 +26,22 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { if len(stack.Scopes) > 0 { lastScope := stack.Scopes[len(stack.Scopes)-1] s.State = lastScope.State - s.Variables = make(map[string]*Variable, len(lastScope.Variables)) + s.Variables = make([]*Variable, 0, len(lastScope.Variables)) s.InLoop = lastScope.InLoop - for k, v := range lastScope.Variables { + for _, v := range lastScope.Variables { count := ast.Count(body, buffer, token.Identifier, v.Name) if count == 0 { continue } - s.Variables[k] = &Variable{ + s.Variables = append(s.Variables, &Variable{ Name: v.Name, Register: v.Register, Alive: count, - } + }) } - } else { - s.Variables = map[string]*Variable{} } stack.Scopes = append(stack.Scopes, s) From 504111734f701472a1bf76017166e5816103f862 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 22 Jul 2024 17:49:44 +0200 Subject: [PATCH 0352/1012] Reduced memory usage --- src/build/core/AddVariable.go | 10 +--------- src/build/core/VariableByName.go | 2 +- src/build/core/useVariable.go | 2 +- src/build/scope/Scope.go | 20 +++++++++++++++++++- src/build/scope/Stack.go | 10 ++++------ 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/build/core/AddVariable.go b/src/build/core/AddVariable.go index 8b36a56..b04ed1f 100644 --- a/src/build/core/AddVariable.go +++ b/src/build/core/AddVariable.go @@ -11,13 +11,5 @@ func (f *Function) AddVariable(variable *scope.Variable) { f.Comment("%s = %s (%d uses)", variable.Name, variable.Register, variable.Alive) } - s := f.CurrentScope() - variable.Scope = s - - if s.Variables == nil { - s.Variables = map[string]*scope.Variable{} - } - - s.Variables[variable.Name] = variable - s.Use(variable.Register) + f.CurrentScope().AddVariable(variable) } diff --git a/src/build/core/VariableByName.go b/src/build/core/VariableByName.go index a7b9ff7..8cce143 100644 --- a/src/build/core/VariableByName.go +++ b/src/build/core/VariableByName.go @@ -4,5 +4,5 @@ import "git.akyoto.dev/cli/q/src/build/scope" // VariableByName returns the variable with the given name or `nil` if it doesn't exist. func (f *Function) VariableByName(name string) *scope.Variable { - return f.CurrentScope().Variables[name] + return f.CurrentScope().VariableByName(name) } diff --git a/src/build/core/useVariable.go b/src/build/core/useVariable.go index 8109f20..35c058a 100644 --- a/src/build/core/useVariable.go +++ b/src/build/core/useVariable.go @@ -11,7 +11,7 @@ func (f *Function) useVariable(variable *scope.Variable) { continue } - local := scope.Variables[variable.Name] + local := scope.VariableByName(variable.Name) if local == nil { continue diff --git a/src/build/scope/Scope.go b/src/build/scope/Scope.go index 793bb27..da9a1a3 100644 --- a/src/build/scope/Scope.go +++ b/src/build/scope/Scope.go @@ -6,7 +6,25 @@ import ( // Scope represents an independent code block. type Scope struct { - Variables map[string]*Variable + Variables []*Variable InLoop bool cpu.State } + +// AddVariable adds a new variable to the current scope. +func (s *Scope) AddVariable(variable *Variable) { + variable.Scope = s + s.Variables = append(s.Variables, variable) + s.Use(variable.Register) +} + +// VariableByName returns the variable with the given name or `nil` if it doesn't exist. +func (s *Scope) VariableByName(name string) *Variable { + for _, v := range s.Variables { + if v.Name == name { + return v + } + } + + return nil +} diff --git a/src/build/scope/Stack.go b/src/build/scope/Stack.go index 1760f6b..2258cdd 100644 --- a/src/build/scope/Stack.go +++ b/src/build/scope/Stack.go @@ -26,24 +26,22 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { if len(stack.Scopes) > 0 { lastScope := stack.Scopes[len(stack.Scopes)-1] s.State = lastScope.State - s.Variables = make(map[string]*Variable, len(lastScope.Variables)) + s.Variables = make([]*Variable, 0, len(lastScope.Variables)) s.InLoop = lastScope.InLoop - for k, v := range lastScope.Variables { + for _, v := range lastScope.Variables { count := ast.Count(body, buffer, token.Identifier, v.Name) if count == 0 { continue } - s.Variables[k] = &Variable{ + s.Variables = append(s.Variables, &Variable{ Name: v.Name, Register: v.Register, Alive: count, - } + }) } - } else { - s.Variables = map[string]*Variable{} } stack.Scopes = append(stack.Scopes, s) From c3234361f7c063be9dd5aac5c553f8a7e8544821 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 22 Jul 2024 19:10:14 +0200 Subject: [PATCH 0353/1012] Simplified variable usage --- src/build/core/Compare.go | 2 +- src/build/core/CompileAssign.go | 4 ++-- src/build/core/ExecuteLeaf.go | 2 +- src/build/core/TokenToRegister.go | 2 +- src/build/core/useVariable.go | 36 ------------------------------- src/build/scope/Scope.go | 3 ++- src/build/scope/Stack.go | 22 +++++++++++++++++++ src/build/scope/Variable.go | 18 ++++++++++++++-- 8 files changed, 45 insertions(+), 44 deletions(-) delete mode 100644 src/build/core/useVariable.go diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 6cd0b5d..4351c4a 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -20,7 +20,7 @@ func (f *Function) Compare(comparison *expression.Expression) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) } - defer f.useVariable(variable) + defer f.UseVariable(variable) return f.Execute(comparison.Token, variable.Register, right) } diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index 767a2df..b811ab3 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -21,7 +21,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) } - defer f.useVariable(variable) + defer f.UseVariable(variable) return f.Execute(operator, variable.Register, right) } @@ -33,7 +33,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position) } - defer f.useVariable(variable) + defer f.UseVariable(variable) index := left.Children[1] offset, _, err := f.Number(index.Token) diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index 8dbda36..6dd7645 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -17,7 +17,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) } - defer f.useVariable(variable) + defer f.UseVariable(variable) return f.ExecuteRegisterRegister(operation, register, variable.Register) case token.Number, token.Rune: diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 36af04f..cbdd1c9 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -21,7 +21,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - f.useVariable(variable) + f.UseVariable(variable) f.RegisterRegister(asm.MOVE, register, variable.Register) return nil diff --git a/src/build/core/useVariable.go b/src/build/core/useVariable.go deleted file mode 100644 index 35c058a..0000000 --- a/src/build/core/useVariable.go +++ /dev/null @@ -1,36 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/scope" -) - -func (f *Function) useVariable(variable *scope.Variable) { - for i, scope := range f.Scopes { - if scope.InLoop && variable.Scope != scope { - continue - } - - local := scope.VariableByName(variable.Name) - - if local == nil { - continue - } - - local.Alive-- - - if local.Alive < 0 { - panic("incorrect number of variable use calls") - } - - if local.Alive == 0 { - if config.Comments { - f.Comment("%s (%s) died in scope %d", local.Name, local.Register, i) - } - - scope.Free(local.Register) - } else if config.Comments { - f.Comment("%s (%s) used in scope %d", local.Name, local.Register, i) - } - } -} diff --git a/src/build/scope/Scope.go b/src/build/scope/Scope.go index da9a1a3..42021e6 100644 --- a/src/build/scope/Scope.go +++ b/src/build/scope/Scope.go @@ -7,13 +7,14 @@ import ( // Scope represents an independent code block. type Scope struct { Variables []*Variable + Depth uint8 InLoop bool cpu.State } // AddVariable adds a new variable to the current scope. func (s *Scope) AddVariable(variable *Variable) { - variable.Scope = s + variable.Depth = s.Depth s.Variables = append(s.Variables, variable) s.Use(variable.Register) } diff --git a/src/build/scope/Stack.go b/src/build/scope/Stack.go index 2258cdd..32acf1f 100644 --- a/src/build/scope/Stack.go +++ b/src/build/scope/Stack.go @@ -40,6 +40,7 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { Name: v.Name, Register: v.Register, Alive: count, + Depth: uint8(len(stack.Scopes)), }) } } @@ -47,3 +48,24 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { stack.Scopes = append(stack.Scopes, s) return s } + +// UseVariable reduces the lifetime of the variable in all scopes. +func (stack *Stack) UseVariable(variable *Variable) { + for depth, scope := range stack.Scopes { + if scope.InLoop && variable.Depth != uint8(depth) { + continue + } + + local := scope.VariableByName(variable.Name) + + if local == nil { + continue + } + + local.Use() + + if !local.IsAlive() { + scope.Free(local.Register) + } + } +} diff --git a/src/build/scope/Variable.go b/src/build/scope/Variable.go index 940d595..e1a54c3 100644 --- a/src/build/scope/Variable.go +++ b/src/build/scope/Variable.go @@ -6,8 +6,22 @@ import ( // Variable represents a named register. type Variable struct { - Scope *Scope Name string - Register cpu.Register Alive int + Depth uint8 + Register cpu.Register +} + +// IsAlive returns true if the variable is still alive. +func (v *Variable) IsAlive() bool { + return v.Alive > 0 +} + +// Use reduces the lifetime counter by one. +func (v *Variable) Use() { + v.Alive-- + + if v.Alive < 0 { + panic("incorrect number of variable use calls") + } } From c027208369c5403df2c07fe31adf78cc0d3bc63c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 22 Jul 2024 19:10:14 +0200 Subject: [PATCH 0354/1012] Simplified variable usage --- src/build/core/Compare.go | 2 +- src/build/core/CompileAssign.go | 4 ++-- src/build/core/ExecuteLeaf.go | 2 +- src/build/core/TokenToRegister.go | 2 +- src/build/core/useVariable.go | 36 ------------------------------- src/build/scope/Scope.go | 3 ++- src/build/scope/Stack.go | 22 +++++++++++++++++++ src/build/scope/Variable.go | 18 ++++++++++++++-- 8 files changed, 45 insertions(+), 44 deletions(-) delete mode 100644 src/build/core/useVariable.go diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 6cd0b5d..4351c4a 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -20,7 +20,7 @@ func (f *Function) Compare(comparison *expression.Expression) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) } - defer f.useVariable(variable) + defer f.UseVariable(variable) return f.Execute(comparison.Token, variable.Register, right) } diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index 767a2df..b811ab3 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -21,7 +21,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) } - defer f.useVariable(variable) + defer f.UseVariable(variable) return f.Execute(operator, variable.Register, right) } @@ -33,7 +33,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position) } - defer f.useVariable(variable) + defer f.UseVariable(variable) index := left.Children[1] offset, _, err := f.Number(index.Token) diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index 8dbda36..6dd7645 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -17,7 +17,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) } - defer f.useVariable(variable) + defer f.UseVariable(variable) return f.ExecuteRegisterRegister(operation, register, variable.Register) case token.Number, token.Rune: diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 36af04f..cbdd1c9 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -21,7 +21,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - f.useVariable(variable) + f.UseVariable(variable) f.RegisterRegister(asm.MOVE, register, variable.Register) return nil diff --git a/src/build/core/useVariable.go b/src/build/core/useVariable.go deleted file mode 100644 index 35c058a..0000000 --- a/src/build/core/useVariable.go +++ /dev/null @@ -1,36 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/scope" -) - -func (f *Function) useVariable(variable *scope.Variable) { - for i, scope := range f.Scopes { - if scope.InLoop && variable.Scope != scope { - continue - } - - local := scope.VariableByName(variable.Name) - - if local == nil { - continue - } - - local.Alive-- - - if local.Alive < 0 { - panic("incorrect number of variable use calls") - } - - if local.Alive == 0 { - if config.Comments { - f.Comment("%s (%s) died in scope %d", local.Name, local.Register, i) - } - - scope.Free(local.Register) - } else if config.Comments { - f.Comment("%s (%s) used in scope %d", local.Name, local.Register, i) - } - } -} diff --git a/src/build/scope/Scope.go b/src/build/scope/Scope.go index da9a1a3..42021e6 100644 --- a/src/build/scope/Scope.go +++ b/src/build/scope/Scope.go @@ -7,13 +7,14 @@ import ( // Scope represents an independent code block. type Scope struct { Variables []*Variable + Depth uint8 InLoop bool cpu.State } // AddVariable adds a new variable to the current scope. func (s *Scope) AddVariable(variable *Variable) { - variable.Scope = s + variable.Depth = s.Depth s.Variables = append(s.Variables, variable) s.Use(variable.Register) } diff --git a/src/build/scope/Stack.go b/src/build/scope/Stack.go index 2258cdd..32acf1f 100644 --- a/src/build/scope/Stack.go +++ b/src/build/scope/Stack.go @@ -40,6 +40,7 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { Name: v.Name, Register: v.Register, Alive: count, + Depth: uint8(len(stack.Scopes)), }) } } @@ -47,3 +48,24 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { stack.Scopes = append(stack.Scopes, s) return s } + +// UseVariable reduces the lifetime of the variable in all scopes. +func (stack *Stack) UseVariable(variable *Variable) { + for depth, scope := range stack.Scopes { + if scope.InLoop && variable.Depth != uint8(depth) { + continue + } + + local := scope.VariableByName(variable.Name) + + if local == nil { + continue + } + + local.Use() + + if !local.IsAlive() { + scope.Free(local.Register) + } + } +} diff --git a/src/build/scope/Variable.go b/src/build/scope/Variable.go index 940d595..e1a54c3 100644 --- a/src/build/scope/Variable.go +++ b/src/build/scope/Variable.go @@ -6,8 +6,22 @@ import ( // Variable represents a named register. type Variable struct { - Scope *Scope Name string - Register cpu.Register Alive int + Depth uint8 + Register cpu.Register +} + +// IsAlive returns true if the variable is still alive. +func (v *Variable) IsAlive() bool { + return v.Alive > 0 +} + +// Use reduces the lifetime counter by one. +func (v *Variable) Use() { + v.Alive-- + + if v.Alive < 0 { + panic("incorrect number of variable use calls") + } } From 2170798b0f4c88ff97b5ea73926b41352ba55519 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 22 Jul 2024 22:54:24 +0200 Subject: [PATCH 0355/1012] Reduced memory usage --- src/build/ast/Count.go | 4 ++-- src/build/core/CompileDefinition.go | 13 ++++++++++++- src/build/core/storeVariableInRegister.go | 20 -------------------- src/build/expression/Expression.go | 4 ++-- src/build/scope/Variable.go | 8 ++++---- src/build/token/Count.go | 4 ++-- 6 files changed, 22 insertions(+), 31 deletions(-) delete mode 100644 src/build/core/storeVariableInRegister.go diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index b14c652..0c0c4a8 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -3,8 +3,8 @@ package ast import "git.akyoto.dev/cli/q/src/build/token" // Count counts how often the given token appears in the AST. -func Count(body AST, buffer []byte, kind token.Kind, name string) int { - count := 0 +func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { + count := uint8(0) for _, node := range body { switch node := node.(type) { diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index c5473b1..716aa28 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -3,6 +3,7 @@ package core import ( "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" ) @@ -20,5 +21,15 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) } - return f.storeVariableInRegister(name, node.Value, uses) + register := f.CurrentScope().MustFindFree(f.cpu.General) + f.CurrentScope().Reserve(register) + err := f.ExpressionToRegister(node.Value, register) + + f.AddVariable(&scope.Variable{ + Name: name, + Register: register, + Alive: uses, + }) + + return err } diff --git a/src/build/core/storeVariableInRegister.go b/src/build/core/storeVariableInRegister.go deleted file mode 100644 index 7adf578..0000000 --- a/src/build/core/storeVariableInRegister.go +++ /dev/null @@ -1,20 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/scope" -) - -func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error { - reg := f.CurrentScope().MustFindFree(f.cpu.General) - f.CurrentScope().Reserve(reg) - err := f.ExpressionToRegister(value, reg) - - f.AddVariable(&scope.Variable{ - Name: name, - Register: reg, - Alive: uses, - }) - - return err -} diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 5027b8e..7b39b12 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -37,8 +37,8 @@ func (expr *Expression) AddChild(child *Expression) { } // Count counts how often the given token appears in the expression. -func (expr *Expression) Count(buffer []byte, kind token.Kind, name string) int { - count := 0 +func (expr *Expression) Count(buffer []byte, kind token.Kind, name string) uint8 { + count := uint8(0) expr.EachLeaf(func(leaf *Expression) error { if leaf.Token.Kind == kind && leaf.Token.Text(buffer) == name { diff --git a/src/build/scope/Variable.go b/src/build/scope/Variable.go index e1a54c3..28e845a 100644 --- a/src/build/scope/Variable.go +++ b/src/build/scope/Variable.go @@ -7,7 +7,7 @@ import ( // Variable represents a named register. type Variable struct { Name string - Alive int + Alive uint8 Depth uint8 Register cpu.Register } @@ -19,9 +19,9 @@ func (v *Variable) IsAlive() bool { // Use reduces the lifetime counter by one. func (v *Variable) Use() { - v.Alive-- - - if v.Alive < 0 { + if v.Alive == 0 { panic("incorrect number of variable use calls") } + + v.Alive-- } diff --git a/src/build/token/Count.go b/src/build/token/Count.go index e83fff6..cf460a3 100644 --- a/src/build/token/Count.go +++ b/src/build/token/Count.go @@ -1,8 +1,8 @@ package token // Count counts how often the given token appears in the token list. -func Count(tokens []Token, buffer []byte, kind Kind, name string) int { - count := 0 +func Count(tokens []Token, buffer []byte, kind Kind, name string) uint8 { + count := uint8(0) for _, t := range tokens { if t.Kind == Identifier && t.Text(buffer) == name { From eb160afd91a2406d1090171f8258307823d0bb37 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 22 Jul 2024 22:54:24 +0200 Subject: [PATCH 0356/1012] Reduced memory usage --- src/build/ast/Count.go | 4 ++-- src/build/core/CompileDefinition.go | 13 ++++++++++++- src/build/core/storeVariableInRegister.go | 20 -------------------- src/build/expression/Expression.go | 4 ++-- src/build/scope/Variable.go | 8 ++++---- src/build/token/Count.go | 4 ++-- 6 files changed, 22 insertions(+), 31 deletions(-) delete mode 100644 src/build/core/storeVariableInRegister.go diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index b14c652..0c0c4a8 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -3,8 +3,8 @@ package ast import "git.akyoto.dev/cli/q/src/build/token" // Count counts how often the given token appears in the AST. -func Count(body AST, buffer []byte, kind token.Kind, name string) int { - count := 0 +func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { + count := uint8(0) for _, node := range body { switch node := node.(type) { diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index c5473b1..716aa28 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -3,6 +3,7 @@ package core import ( "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" ) @@ -20,5 +21,15 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) } - return f.storeVariableInRegister(name, node.Value, uses) + register := f.CurrentScope().MustFindFree(f.cpu.General) + f.CurrentScope().Reserve(register) + err := f.ExpressionToRegister(node.Value, register) + + f.AddVariable(&scope.Variable{ + Name: name, + Register: register, + Alive: uses, + }) + + return err } diff --git a/src/build/core/storeVariableInRegister.go b/src/build/core/storeVariableInRegister.go deleted file mode 100644 index 7adf578..0000000 --- a/src/build/core/storeVariableInRegister.go +++ /dev/null @@ -1,20 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/scope" -) - -func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error { - reg := f.CurrentScope().MustFindFree(f.cpu.General) - f.CurrentScope().Reserve(reg) - err := f.ExpressionToRegister(value, reg) - - f.AddVariable(&scope.Variable{ - Name: name, - Register: reg, - Alive: uses, - }) - - return err -} diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 5027b8e..7b39b12 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -37,8 +37,8 @@ func (expr *Expression) AddChild(child *Expression) { } // Count counts how often the given token appears in the expression. -func (expr *Expression) Count(buffer []byte, kind token.Kind, name string) int { - count := 0 +func (expr *Expression) Count(buffer []byte, kind token.Kind, name string) uint8 { + count := uint8(0) expr.EachLeaf(func(leaf *Expression) error { if leaf.Token.Kind == kind && leaf.Token.Text(buffer) == name { diff --git a/src/build/scope/Variable.go b/src/build/scope/Variable.go index e1a54c3..28e845a 100644 --- a/src/build/scope/Variable.go +++ b/src/build/scope/Variable.go @@ -7,7 +7,7 @@ import ( // Variable represents a named register. type Variable struct { Name string - Alive int + Alive uint8 Depth uint8 Register cpu.Register } @@ -19,9 +19,9 @@ func (v *Variable) IsAlive() bool { // Use reduces the lifetime counter by one. func (v *Variable) Use() { - v.Alive-- - - if v.Alive < 0 { + if v.Alive == 0 { panic("incorrect number of variable use calls") } + + v.Alive-- } diff --git a/src/build/token/Count.go b/src/build/token/Count.go index e83fff6..cf460a3 100644 --- a/src/build/token/Count.go +++ b/src/build/token/Count.go @@ -1,8 +1,8 @@ package token // Count counts how often the given token appears in the token list. -func Count(tokens []Token, buffer []byte, kind Kind, name string) int { - count := 0 +func Count(tokens []Token, buffer []byte, kind Kind, name string) uint8 { + count := uint8(0) for _, t := range tokens { if t.Kind == Identifier && t.Text(buffer) == name { From dc5456b8205d761cdf04e9804ca527f7d0e4f7c8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 23 Jul 2024 10:47:27 +0200 Subject: [PATCH 0357/1012] Improved x64 documentation --- src/build/arch/x64/ModRM.go | 19 +++++++++++-------- src/build/arch/x64/SIB.go | 19 +++++++++++-------- src/build/arch/x64/encode.go | 2 +- src/build/arch/x64/encodeNum.go | 2 +- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/build/arch/x64/ModRM.go b/src/build/arch/x64/ModRM.go index ca6a327..bb6852e 100644 --- a/src/build/arch/x64/ModRM.go +++ b/src/build/arch/x64/ModRM.go @@ -1,16 +1,19 @@ package x64 +// AddressMode encodes the addressing mode. +type AddressMode = byte + const ( - AddressMemory = byte(0b00) - AddressMemoryOffset8 = byte(0b01) - AddressMemoryOffset32 = byte(0b10) - AddressDirect = byte(0b11) + AddressMemory = AddressMode(0b00) + AddressMemoryOffset8 = AddressMode(0b01) + AddressMemoryOffset32 = AddressMode(0b10) + AddressDirect = AddressMode(0b11) ) // ModRM is used to generate a ModRM suffix. -// - mod: 2 bits -// - reg: 3 bits -// - rm: 3 bits -func ModRM(mod byte, reg byte, rm byte) byte { +// - mod: 2 bits. The addressing mode. +// - reg: 3 bits. Register reference or opcode extension. +// - rm: 3 bits. Register operand. +func ModRM(mod AddressMode, reg byte, rm byte) byte { return (mod << 6) | (reg << 3) | rm } diff --git a/src/build/arch/x64/SIB.go b/src/build/arch/x64/SIB.go index 030ffac..438024d 100644 --- a/src/build/arch/x64/SIB.go +++ b/src/build/arch/x64/SIB.go @@ -1,16 +1,19 @@ package x64 +// ScaleFactor encodes the scale factor. +type ScaleFactor = byte + const ( - Scale1 = byte(0b00) - Scale2 = byte(0b01) - Scale4 = byte(0b10) - Scale8 = byte(0b11) + Scale1 = ScaleFactor(0b00) + Scale2 = ScaleFactor(0b01) + Scale4 = ScaleFactor(0b10) + Scale8 = ScaleFactor(0b11) ) // SIB is used to generate an SIB byte. -// - scale: 2 bits -// - index: 3 bits -// - base: 3 bits -func SIB(scale byte, index byte, base byte) byte { +// - scale: 2 bits. Multiplies the value of the index. +// - index: 3 bits. Specifies the index register. +// - base: 3 bits. Specifies the base register. +func SIB(scale ScaleFactor, index byte, base byte) byte { return (scale << 6) | (index << 3) | base } diff --git a/src/build/arch/x64/encode.go b/src/build/arch/x64/encode.go index 606f6e2..570076d 100644 --- a/src/build/arch/x64/encode.go +++ b/src/build/arch/x64/encode.go @@ -3,7 +3,7 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" // encode is the core function that encodes an instruction. -func encode(code []byte, mod byte, reg cpu.Register, rm cpu.Register, numBytes byte, opCodes ...byte) []byte { +func encode(code []byte, mod AddressMode, reg cpu.Register, rm cpu.Register, numBytes byte, opCodes ...byte) []byte { w := byte(0) // Indicates a 64-bit register. r := byte(0) // Extension to the "reg" field in ModRM. x := byte(0) // Extension to the SIB index field. diff --git a/src/build/arch/x64/encodeNum.go b/src/build/arch/x64/encodeNum.go index 15efe03..2053dfb 100644 --- a/src/build/arch/x64/encodeNum.go +++ b/src/build/arch/x64/encodeNum.go @@ -7,7 +7,7 @@ import ( ) // encodeNum encodes an instruction with up to two registers and a number parameter. -func encodeNum(code []byte, mod byte, reg cpu.Register, rm cpu.Register, number int, opCode8 byte, opCode32 byte) []byte { +func encodeNum(code []byte, mod AddressMode, reg cpu.Register, rm cpu.Register, number int, opCode8 byte, opCode32 byte) []byte { if SizeOf(int64(number)) == 1 { code = encode(code, mod, reg, rm, 8, opCode8) return append(code, byte(number)) From d6db1b10969a9f508865d65f025cc30ea93518bd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 23 Jul 2024 10:47:27 +0200 Subject: [PATCH 0358/1012] Improved x64 documentation --- src/build/arch/x64/ModRM.go | 19 +++++++++++-------- src/build/arch/x64/SIB.go | 19 +++++++++++-------- src/build/arch/x64/encode.go | 2 +- src/build/arch/x64/encodeNum.go | 2 +- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/build/arch/x64/ModRM.go b/src/build/arch/x64/ModRM.go index ca6a327..bb6852e 100644 --- a/src/build/arch/x64/ModRM.go +++ b/src/build/arch/x64/ModRM.go @@ -1,16 +1,19 @@ package x64 +// AddressMode encodes the addressing mode. +type AddressMode = byte + const ( - AddressMemory = byte(0b00) - AddressMemoryOffset8 = byte(0b01) - AddressMemoryOffset32 = byte(0b10) - AddressDirect = byte(0b11) + AddressMemory = AddressMode(0b00) + AddressMemoryOffset8 = AddressMode(0b01) + AddressMemoryOffset32 = AddressMode(0b10) + AddressDirect = AddressMode(0b11) ) // ModRM is used to generate a ModRM suffix. -// - mod: 2 bits -// - reg: 3 bits -// - rm: 3 bits -func ModRM(mod byte, reg byte, rm byte) byte { +// - mod: 2 bits. The addressing mode. +// - reg: 3 bits. Register reference or opcode extension. +// - rm: 3 bits. Register operand. +func ModRM(mod AddressMode, reg byte, rm byte) byte { return (mod << 6) | (reg << 3) | rm } diff --git a/src/build/arch/x64/SIB.go b/src/build/arch/x64/SIB.go index 030ffac..438024d 100644 --- a/src/build/arch/x64/SIB.go +++ b/src/build/arch/x64/SIB.go @@ -1,16 +1,19 @@ package x64 +// ScaleFactor encodes the scale factor. +type ScaleFactor = byte + const ( - Scale1 = byte(0b00) - Scale2 = byte(0b01) - Scale4 = byte(0b10) - Scale8 = byte(0b11) + Scale1 = ScaleFactor(0b00) + Scale2 = ScaleFactor(0b01) + Scale4 = ScaleFactor(0b10) + Scale8 = ScaleFactor(0b11) ) // SIB is used to generate an SIB byte. -// - scale: 2 bits -// - index: 3 bits -// - base: 3 bits -func SIB(scale byte, index byte, base byte) byte { +// - scale: 2 bits. Multiplies the value of the index. +// - index: 3 bits. Specifies the index register. +// - base: 3 bits. Specifies the base register. +func SIB(scale ScaleFactor, index byte, base byte) byte { return (scale << 6) | (index << 3) | base } diff --git a/src/build/arch/x64/encode.go b/src/build/arch/x64/encode.go index 606f6e2..570076d 100644 --- a/src/build/arch/x64/encode.go +++ b/src/build/arch/x64/encode.go @@ -3,7 +3,7 @@ package x64 import "git.akyoto.dev/cli/q/src/build/cpu" // encode is the core function that encodes an instruction. -func encode(code []byte, mod byte, reg cpu.Register, rm cpu.Register, numBytes byte, opCodes ...byte) []byte { +func encode(code []byte, mod AddressMode, reg cpu.Register, rm cpu.Register, numBytes byte, opCodes ...byte) []byte { w := byte(0) // Indicates a 64-bit register. r := byte(0) // Extension to the "reg" field in ModRM. x := byte(0) // Extension to the SIB index field. diff --git a/src/build/arch/x64/encodeNum.go b/src/build/arch/x64/encodeNum.go index 15efe03..2053dfb 100644 --- a/src/build/arch/x64/encodeNum.go +++ b/src/build/arch/x64/encodeNum.go @@ -7,7 +7,7 @@ import ( ) // encodeNum encodes an instruction with up to two registers and a number parameter. -func encodeNum(code []byte, mod byte, reg cpu.Register, rm cpu.Register, number int, opCode8 byte, opCode32 byte) []byte { +func encodeNum(code []byte, mod AddressMode, reg cpu.Register, rm cpu.Register, number int, opCode8 byte, opCode32 byte) []byte { if SizeOf(int64(number)) == 1 { code = encode(code, mod, reg, rm, 8, opCode8) return append(code, byte(number)) From abba96245508fed6f213f4298384fd694678dade Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 23 Jul 2024 16:41:21 +0200 Subject: [PATCH 0359/1012] Improved separation of concerns --- src/build/config/config.go | 1 - src/build/core/AddVariable.go | 15 --- src/build/core/Comment.go | 11 -- src/build/core/Compare.go | 4 +- src/build/core/Compile.go | 1 - src/build/core/CompileCall.go | 14 +-- src/build/core/CompileDefinition.go | 4 +- src/build/core/CompileReturn.go | 2 +- src/build/core/Execute.go | 4 +- src/build/core/ExpressionToRegister.go | 11 +- src/build/core/Function.go | 22 ++-- ...dentifierExists.go => IdentifierExists.go} | 4 +- src/build/core/Instructions.go | 103 ------------------ src/build/core/NewFunction.go | 28 ++--- src/build/core/PrintInstructions.go | 4 +- src/build/core/UsesRegister.go | 4 +- src/build/core/VariableByName.go | 8 -- src/build/core/VariableInRegister.go | 17 --- src/build/core/postInstruction.go | 11 -- src/build/scope/Stack.go | 22 ++++ src/build/z/AddLabel.go | 8 ++ src/build/z/Call.go | 7 ++ src/build/z/Comment.go | 8 ++ src/build/z/Compiler.go | 15 +++ src/build/z/Jump.go | 8 ++ src/build/z/MemoryNumber.go | 8 ++ src/build/z/Register.go | 16 +++ src/build/z/RegisterLabel.go | 20 ++++ src/build/z/RegisterNumber.go | 20 ++++ src/build/z/RegisterRegister.go | 24 ++++ src/build/z/Return.go | 6 + src/build/{core => z}/SaveRegister.go | 16 +-- src/build/z/Syscall.go | 7 ++ src/build/z/isDestructive.go | 12 ++ src/build/z/postInstruction.go | 11 ++ src/cli/Build.go | 3 - 36 files changed, 243 insertions(+), 236 deletions(-) delete mode 100644 src/build/core/AddVariable.go delete mode 100644 src/build/core/Comment.go rename src/build/core/{identifierExists.go => IdentifierExists.go} (54%) delete mode 100644 src/build/core/Instructions.go delete mode 100644 src/build/core/VariableByName.go delete mode 100644 src/build/core/VariableInRegister.go delete mode 100644 src/build/core/postInstruction.go create mode 100644 src/build/z/AddLabel.go create mode 100644 src/build/z/Call.go create mode 100644 src/build/z/Comment.go create mode 100644 src/build/z/Compiler.go create mode 100644 src/build/z/Jump.go create mode 100644 src/build/z/MemoryNumber.go create mode 100644 src/build/z/Register.go create mode 100644 src/build/z/RegisterLabel.go create mode 100644 src/build/z/RegisterNumber.go create mode 100644 src/build/z/RegisterRegister.go create mode 100644 src/build/z/Return.go rename src/build/{core => z}/SaveRegister.go (63%) create mode 100644 src/build/z/Syscall.go create mode 100644 src/build/z/isDestructive.go create mode 100644 src/build/z/postInstruction.go diff --git a/src/build/config/config.go b/src/build/config/config.go index 0697fa1..99791d2 100644 --- a/src/build/config/config.go +++ b/src/build/config/config.go @@ -16,6 +16,5 @@ const ( var ( Assembler = false - Comments = false Dry = false ) diff --git a/src/build/core/AddVariable.go b/src/build/core/AddVariable.go deleted file mode 100644 index b04ed1f..0000000 --- a/src/build/core/AddVariable.go +++ /dev/null @@ -1,15 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/scope" -) - -// AddVariable adds a new variable to the current scope. -func (f *Function) AddVariable(variable *scope.Variable) { - if config.Comments { - f.Comment("%s = %s (%d uses)", variable.Name, variable.Register, variable.Alive) - } - - f.CurrentScope().AddVariable(variable) -} diff --git a/src/build/core/Comment.go b/src/build/core/Comment.go deleted file mode 100644 index d5f0590..0000000 --- a/src/build/core/Comment.go +++ /dev/null @@ -1,11 +0,0 @@ -package core - -import ( - "fmt" -) - -// Comment adds a comment to the assembler. -func (f *Function) Comment(format string, args ...any) { - f.Assembler.Comment(fmt.Sprintf(format, args...)) - f.postInstruction() -} diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 4351c4a..7eb92f7 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -31,10 +31,10 @@ func (f *Function) Compare(comparison *expression.Expression) error { return err } - return f.Execute(comparison.Token, f.cpu.Output[0], right) + return f.Execute(comparison.Token, f.CPU.Output[0], right) } - tmp := f.CurrentScope().MustFindFree(f.cpu.General) + tmp := f.CurrentScope().MustFindFree(f.CPU.General) err := f.ExpressionToRegister(left, tmp) if err != nil { diff --git a/src/build/core/Compile.go b/src/build/core/Compile.go index 4c2cb94..41c079d 100644 --- a/src/build/core/Compile.go +++ b/src/build/core/Compile.go @@ -2,7 +2,6 @@ package core // Compile turns a function into machine code. func (f *Function) Compile() { - defer close(f.finished) f.AddLabel(f.Name) f.Err = f.CompileTokens(f.Body) f.Return() diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 3270d01..358d4f8 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -31,10 +31,10 @@ func (f *Function) CompileCall(root *expression.Expression) error { } parameters := root.Children[1:] - registers := f.cpu.Input[:len(parameters)] + registers := f.CPU.Input[:len(parameters)] if isSyscall { - registers = f.cpu.Syscall[:len(parameters)] + registers = f.CPU.Syscall[:len(parameters)] } err := f.ExpressionsToRegisters(parameters, registers) @@ -43,10 +43,10 @@ func (f *Function) CompileCall(root *expression.Expression) error { return err } - f.SaveRegister(f.cpu.Output[0]) + f.SaveRegister(f.CPU.Output[0]) // Push - for _, register := range f.cpu.General { + for _, register := range f.CPU.General { if f.CurrentScope().IsUsed(register) { f.Register(asm.PUSH, register) } @@ -60,7 +60,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { } for _, register := range registers { - if register == f.cpu.Output[0] && root.Parent != nil { + if register == f.CPU.Output[0] && root.Parent != nil { continue } @@ -68,8 +68,8 @@ func (f *Function) CompileCall(root *expression.Expression) error { } // Pop - for i := len(f.cpu.General) - 1; i >= 0; i-- { - register := f.cpu.General[i] + for i := len(f.CPU.General) - 1; i >= 0; i-- { + register := f.CPU.General[i] if f.CurrentScope().IsUsed(register) { f.Register(asm.POP, register) diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 716aa28..71745b5 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -11,7 +11,7 @@ import ( func (f *Function) CompileDefinition(node *ast.Define) error { name := node.Name.Text(f.File.Bytes) - if f.identifierExists(name) { + if f.IdentifierExists(name) { return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, node.Name.Position) } @@ -21,7 +21,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) } - register := f.CurrentScope().MustFindFree(f.cpu.General) + register := f.CurrentScope().MustFindFree(f.CPU.General) f.CurrentScope().Reserve(register) err := f.ExpressionToRegister(node.Value, register) diff --git a/src/build/core/CompileReturn.go b/src/build/core/CompileReturn.go index 57219c4..26f1cab 100644 --- a/src/build/core/CompileReturn.go +++ b/src/build/core/CompileReturn.go @@ -12,5 +12,5 @@ func (f *Function) CompileReturn(node *ast.Return) error { return nil } - return f.ExpressionToRegister(node.Value, f.cpu.Output[0]) + return f.ExpressionToRegister(node.Value, f.CPU.Output[0]) } diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index f8c8484..09f2dec 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -20,10 +20,10 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return err } - return f.ExecuteRegisterRegister(operation, register, f.cpu.Output[0]) + return f.ExecuteRegisterRegister(operation, register, f.CPU.Output[0]) } - tmp := f.CurrentScope().MustFindFree(f.cpu.General) + tmp := f.CurrentScope().MustFindFree(f.CPU.General) defer f.CurrentScope().Free(tmp) err := f.ExpressionToRegister(value, tmp) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index f2b2fa1..c900c7b 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -3,7 +3,6 @@ package core import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" @@ -18,8 +17,8 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if ast.IsFunctionCall(node) { err := f.CompileCall(node) - if register != f.cpu.Output[0] { - f.RegisterRegister(asm.MOVE, register, f.cpu.Output[0]) + if register != f.CPU.Output[0] { + f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) } return err @@ -34,11 +33,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp final := register if f.UsesRegister(right, register) { - register = f.CurrentScope().MustFindFree(f.cpu.General) - - if config.Comments { - f.Comment("temporary register %s", register) - } + register = f.CurrentScope().MustFindFree(f.CPU.General) } f.CurrentScope().Reserve(register) diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 36ac29d..65b9252 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -1,26 +1,20 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/fs" - "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/z" ) // Function represents the smallest unit of code. type Function struct { - scope.Stack - Name string - File *fs.File - Body []token.Token - Assembler asm.Assembler - Functions map[string]*Function - Err error - registerHistory []uint64 - finished chan struct{} - cpu cpu.CPU - count counter + z.Compiler + Name string + File *fs.File + Body []token.Token + Functions map[string]*Function + Err error + count counter } // counter stores how often a certain statement appeared so we can generate a unique label from it. diff --git a/src/build/core/identifierExists.go b/src/build/core/IdentifierExists.go similarity index 54% rename from src/build/core/identifierExists.go rename to src/build/core/IdentifierExists.go index 96bd55f..efa3ed7 100644 --- a/src/build/core/identifierExists.go +++ b/src/build/core/IdentifierExists.go @@ -1,7 +1,7 @@ package core -// identifierExists returns true if the identifier has been defined. -func (f *Function) identifierExists(name string) bool { +// IdentifierExists returns true if the identifier has been defined. +func (f *Function) IdentifierExists(name string) bool { variable := f.VariableByName(name) if variable != nil { diff --git a/src/build/core/Instructions.go b/src/build/core/Instructions.go deleted file mode 100644 index e06dcff..0000000 --- a/src/build/core/Instructions.go +++ /dev/null @@ -1,103 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" -) - -func (f *Function) AddLabel(label string) { - f.Assembler.Label(asm.LABEL, label) - f.postInstruction() -} - -func (f *Function) Call(label string) { - f.Assembler.Call(label) - f.CurrentScope().Use(f.cpu.Output[0]) - f.postInstruction() -} - -func (f *Function) Jump(mnemonic asm.Mnemonic, label string) { - f.Assembler.Label(mnemonic, label) - f.postInstruction() -} - -func (f *Function) MemoryNumber(mnemonic asm.Mnemonic, a asm.Memory, b int) { - f.Assembler.MemoryNumber(mnemonic, a, b) - f.postInstruction() -} - -func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) { - f.Assembler.Register(mnemonic, a) - - if mnemonic == asm.POP { - f.CurrentScope().Use(a) - } - - f.postInstruction() -} - -func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { - if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) { - f.SaveRegister(a) - } - - f.Assembler.RegisterNumber(mnemonic, a, b) - - if mnemonic == asm.MOVE { - f.CurrentScope().Use(a) - } - - f.postInstruction() -} - -func (f *Function) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { - if f.CurrentScope().IsUsed(register) && isDestructive(mnemonic) { - f.SaveRegister(register) - } - - f.Assembler.RegisterLabel(mnemonic, register, label) - - if mnemonic == asm.MOVE { - f.CurrentScope().Use(register) - } - - f.postInstruction() -} - -func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) { - if mnemonic == asm.MOVE && a == b { - return - } - - if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) { - f.SaveRegister(a) - } - - f.Assembler.RegisterRegister(mnemonic, a, b) - - if mnemonic == asm.MOVE { - f.CurrentScope().Use(a) - } - - f.postInstruction() -} - -func (f *Function) Return() { - f.Assembler.Return() - f.postInstruction() -} - -func (f *Function) Syscall() { - f.Assembler.Syscall() - f.CurrentScope().Use(f.cpu.Output[0]) - f.postInstruction() -} - -func isDestructive(mnemonic asm.Mnemonic) bool { - switch mnemonic { - case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV: - return true - default: - return false - } -} diff --git a/src/build/core/NewFunction.go b/src/build/core/NewFunction.go index 8ad25ba..f73aedb 100644 --- a/src/build/core/NewFunction.go +++ b/src/build/core/NewFunction.go @@ -7,6 +7,7 @@ import ( "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/z" ) // NewFunction creates a new function. @@ -15,19 +16,20 @@ func NewFunction(name string, file *fs.File, body []token.Token) *Function { Name: name, File: file, Body: body, - Assembler: asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 8), + Compiler: z.Compiler{ + Assembler: asm.Assembler{ + Instructions: make([]asm.Instruction, 0, 8), + }, + Stack: scope.Stack{ + Scopes: []*scope.Scope{{}}, + }, + CPU: cpu.CPU{ + All: x64.AllRegisters, + Input: x64.CallRegisters, + General: x64.GeneralRegisters, + Syscall: x64.SyscallRegisters, + Output: x64.ReturnValueRegisters, + }, }, - Stack: scope.Stack{ - Scopes: []*scope.Scope{{}}, - }, - cpu: cpu.CPU{ - All: x64.AllRegisters, - Input: x64.CallRegisters, - General: x64.GeneralRegisters, - Syscall: x64.SyscallRegisters, - Output: x64.ReturnValueRegisters, - }, - finished: make(chan struct{}), } } diff --git a/src/build/core/PrintInstructions.go b/src/build/core/PrintInstructions.go index 0c78ba4..a824f3a 100644 --- a/src/build/core/PrintInstructions.go +++ b/src/build/core/PrintInstructions.go @@ -33,9 +33,9 @@ func (f *Function) PrintInstructions() { } registers := bytes.Buffer{} - used := f.registerHistory[i] + used := f.RegisterHistory[i] - for _, reg := range f.cpu.All { + for _, reg := range f.CPU.All { if used&(1< Date: Tue, 23 Jul 2024 16:41:21 +0200 Subject: [PATCH 0360/1012] Improved separation of concerns --- src/build/config/config.go | 1 - src/build/core/AddVariable.go | 15 --- src/build/core/Comment.go | 11 -- src/build/core/Compare.go | 4 +- src/build/core/Compile.go | 1 - src/build/core/CompileCall.go | 14 +-- src/build/core/CompileDefinition.go | 4 +- src/build/core/CompileReturn.go | 2 +- src/build/core/Execute.go | 4 +- src/build/core/ExpressionToRegister.go | 11 +- src/build/core/Function.go | 22 ++-- ...dentifierExists.go => IdentifierExists.go} | 4 +- src/build/core/Instructions.go | 103 ------------------ src/build/core/NewFunction.go | 28 ++--- src/build/core/PrintInstructions.go | 4 +- src/build/core/UsesRegister.go | 4 +- src/build/core/VariableByName.go | 8 -- src/build/core/VariableInRegister.go | 17 --- src/build/core/postInstruction.go | 11 -- src/build/scope/Stack.go | 22 ++++ src/build/z/AddLabel.go | 8 ++ src/build/z/Call.go | 7 ++ src/build/z/Comment.go | 8 ++ src/build/z/Compiler.go | 15 +++ src/build/z/Jump.go | 8 ++ src/build/z/MemoryNumber.go | 8 ++ src/build/z/Register.go | 16 +++ src/build/z/RegisterLabel.go | 20 ++++ src/build/z/RegisterNumber.go | 20 ++++ src/build/z/RegisterRegister.go | 24 ++++ src/build/z/Return.go | 6 + src/build/{core => z}/SaveRegister.go | 16 +-- src/build/z/Syscall.go | 7 ++ src/build/z/isDestructive.go | 12 ++ src/build/z/postInstruction.go | 11 ++ src/cli/Build.go | 3 - 36 files changed, 243 insertions(+), 236 deletions(-) delete mode 100644 src/build/core/AddVariable.go delete mode 100644 src/build/core/Comment.go rename src/build/core/{identifierExists.go => IdentifierExists.go} (54%) delete mode 100644 src/build/core/Instructions.go delete mode 100644 src/build/core/VariableByName.go delete mode 100644 src/build/core/VariableInRegister.go delete mode 100644 src/build/core/postInstruction.go create mode 100644 src/build/z/AddLabel.go create mode 100644 src/build/z/Call.go create mode 100644 src/build/z/Comment.go create mode 100644 src/build/z/Compiler.go create mode 100644 src/build/z/Jump.go create mode 100644 src/build/z/MemoryNumber.go create mode 100644 src/build/z/Register.go create mode 100644 src/build/z/RegisterLabel.go create mode 100644 src/build/z/RegisterNumber.go create mode 100644 src/build/z/RegisterRegister.go create mode 100644 src/build/z/Return.go rename src/build/{core => z}/SaveRegister.go (63%) create mode 100644 src/build/z/Syscall.go create mode 100644 src/build/z/isDestructive.go create mode 100644 src/build/z/postInstruction.go diff --git a/src/build/config/config.go b/src/build/config/config.go index 0697fa1..99791d2 100644 --- a/src/build/config/config.go +++ b/src/build/config/config.go @@ -16,6 +16,5 @@ const ( var ( Assembler = false - Comments = false Dry = false ) diff --git a/src/build/core/AddVariable.go b/src/build/core/AddVariable.go deleted file mode 100644 index b04ed1f..0000000 --- a/src/build/core/AddVariable.go +++ /dev/null @@ -1,15 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/scope" -) - -// AddVariable adds a new variable to the current scope. -func (f *Function) AddVariable(variable *scope.Variable) { - if config.Comments { - f.Comment("%s = %s (%d uses)", variable.Name, variable.Register, variable.Alive) - } - - f.CurrentScope().AddVariable(variable) -} diff --git a/src/build/core/Comment.go b/src/build/core/Comment.go deleted file mode 100644 index d5f0590..0000000 --- a/src/build/core/Comment.go +++ /dev/null @@ -1,11 +0,0 @@ -package core - -import ( - "fmt" -) - -// Comment adds a comment to the assembler. -func (f *Function) Comment(format string, args ...any) { - f.Assembler.Comment(fmt.Sprintf(format, args...)) - f.postInstruction() -} diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 4351c4a..7eb92f7 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -31,10 +31,10 @@ func (f *Function) Compare(comparison *expression.Expression) error { return err } - return f.Execute(comparison.Token, f.cpu.Output[0], right) + return f.Execute(comparison.Token, f.CPU.Output[0], right) } - tmp := f.CurrentScope().MustFindFree(f.cpu.General) + tmp := f.CurrentScope().MustFindFree(f.CPU.General) err := f.ExpressionToRegister(left, tmp) if err != nil { diff --git a/src/build/core/Compile.go b/src/build/core/Compile.go index 4c2cb94..41c079d 100644 --- a/src/build/core/Compile.go +++ b/src/build/core/Compile.go @@ -2,7 +2,6 @@ package core // Compile turns a function into machine code. func (f *Function) Compile() { - defer close(f.finished) f.AddLabel(f.Name) f.Err = f.CompileTokens(f.Body) f.Return() diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 3270d01..358d4f8 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -31,10 +31,10 @@ func (f *Function) CompileCall(root *expression.Expression) error { } parameters := root.Children[1:] - registers := f.cpu.Input[:len(parameters)] + registers := f.CPU.Input[:len(parameters)] if isSyscall { - registers = f.cpu.Syscall[:len(parameters)] + registers = f.CPU.Syscall[:len(parameters)] } err := f.ExpressionsToRegisters(parameters, registers) @@ -43,10 +43,10 @@ func (f *Function) CompileCall(root *expression.Expression) error { return err } - f.SaveRegister(f.cpu.Output[0]) + f.SaveRegister(f.CPU.Output[0]) // Push - for _, register := range f.cpu.General { + for _, register := range f.CPU.General { if f.CurrentScope().IsUsed(register) { f.Register(asm.PUSH, register) } @@ -60,7 +60,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { } for _, register := range registers { - if register == f.cpu.Output[0] && root.Parent != nil { + if register == f.CPU.Output[0] && root.Parent != nil { continue } @@ -68,8 +68,8 @@ func (f *Function) CompileCall(root *expression.Expression) error { } // Pop - for i := len(f.cpu.General) - 1; i >= 0; i-- { - register := f.cpu.General[i] + for i := len(f.CPU.General) - 1; i >= 0; i-- { + register := f.CPU.General[i] if f.CurrentScope().IsUsed(register) { f.Register(asm.POP, register) diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 716aa28..71745b5 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -11,7 +11,7 @@ import ( func (f *Function) CompileDefinition(node *ast.Define) error { name := node.Name.Text(f.File.Bytes) - if f.identifierExists(name) { + if f.IdentifierExists(name) { return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, node.Name.Position) } @@ -21,7 +21,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) } - register := f.CurrentScope().MustFindFree(f.cpu.General) + register := f.CurrentScope().MustFindFree(f.CPU.General) f.CurrentScope().Reserve(register) err := f.ExpressionToRegister(node.Value, register) diff --git a/src/build/core/CompileReturn.go b/src/build/core/CompileReturn.go index 57219c4..26f1cab 100644 --- a/src/build/core/CompileReturn.go +++ b/src/build/core/CompileReturn.go @@ -12,5 +12,5 @@ func (f *Function) CompileReturn(node *ast.Return) error { return nil } - return f.ExpressionToRegister(node.Value, f.cpu.Output[0]) + return f.ExpressionToRegister(node.Value, f.CPU.Output[0]) } diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index f8c8484..09f2dec 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -20,10 +20,10 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return err } - return f.ExecuteRegisterRegister(operation, register, f.cpu.Output[0]) + return f.ExecuteRegisterRegister(operation, register, f.CPU.Output[0]) } - tmp := f.CurrentScope().MustFindFree(f.cpu.General) + tmp := f.CurrentScope().MustFindFree(f.CPU.General) defer f.CurrentScope().Free(tmp) err := f.ExpressionToRegister(value, tmp) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index f2b2fa1..c900c7b 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -3,7 +3,6 @@ package core import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" @@ -18,8 +17,8 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if ast.IsFunctionCall(node) { err := f.CompileCall(node) - if register != f.cpu.Output[0] { - f.RegisterRegister(asm.MOVE, register, f.cpu.Output[0]) + if register != f.CPU.Output[0] { + f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) } return err @@ -34,11 +33,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp final := register if f.UsesRegister(right, register) { - register = f.CurrentScope().MustFindFree(f.cpu.General) - - if config.Comments { - f.Comment("temporary register %s", register) - } + register = f.CurrentScope().MustFindFree(f.CPU.General) } f.CurrentScope().Reserve(register) diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 36ac29d..65b9252 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -1,26 +1,20 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/fs" - "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/z" ) // Function represents the smallest unit of code. type Function struct { - scope.Stack - Name string - File *fs.File - Body []token.Token - Assembler asm.Assembler - Functions map[string]*Function - Err error - registerHistory []uint64 - finished chan struct{} - cpu cpu.CPU - count counter + z.Compiler + Name string + File *fs.File + Body []token.Token + Functions map[string]*Function + Err error + count counter } // counter stores how often a certain statement appeared so we can generate a unique label from it. diff --git a/src/build/core/identifierExists.go b/src/build/core/IdentifierExists.go similarity index 54% rename from src/build/core/identifierExists.go rename to src/build/core/IdentifierExists.go index 96bd55f..efa3ed7 100644 --- a/src/build/core/identifierExists.go +++ b/src/build/core/IdentifierExists.go @@ -1,7 +1,7 @@ package core -// identifierExists returns true if the identifier has been defined. -func (f *Function) identifierExists(name string) bool { +// IdentifierExists returns true if the identifier has been defined. +func (f *Function) IdentifierExists(name string) bool { variable := f.VariableByName(name) if variable != nil { diff --git a/src/build/core/Instructions.go b/src/build/core/Instructions.go deleted file mode 100644 index e06dcff..0000000 --- a/src/build/core/Instructions.go +++ /dev/null @@ -1,103 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" -) - -func (f *Function) AddLabel(label string) { - f.Assembler.Label(asm.LABEL, label) - f.postInstruction() -} - -func (f *Function) Call(label string) { - f.Assembler.Call(label) - f.CurrentScope().Use(f.cpu.Output[0]) - f.postInstruction() -} - -func (f *Function) Jump(mnemonic asm.Mnemonic, label string) { - f.Assembler.Label(mnemonic, label) - f.postInstruction() -} - -func (f *Function) MemoryNumber(mnemonic asm.Mnemonic, a asm.Memory, b int) { - f.Assembler.MemoryNumber(mnemonic, a, b) - f.postInstruction() -} - -func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) { - f.Assembler.Register(mnemonic, a) - - if mnemonic == asm.POP { - f.CurrentScope().Use(a) - } - - f.postInstruction() -} - -func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { - if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) { - f.SaveRegister(a) - } - - f.Assembler.RegisterNumber(mnemonic, a, b) - - if mnemonic == asm.MOVE { - f.CurrentScope().Use(a) - } - - f.postInstruction() -} - -func (f *Function) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { - if f.CurrentScope().IsUsed(register) && isDestructive(mnemonic) { - f.SaveRegister(register) - } - - f.Assembler.RegisterLabel(mnemonic, register, label) - - if mnemonic == asm.MOVE { - f.CurrentScope().Use(register) - } - - f.postInstruction() -} - -func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) { - if mnemonic == asm.MOVE && a == b { - return - } - - if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) { - f.SaveRegister(a) - } - - f.Assembler.RegisterRegister(mnemonic, a, b) - - if mnemonic == asm.MOVE { - f.CurrentScope().Use(a) - } - - f.postInstruction() -} - -func (f *Function) Return() { - f.Assembler.Return() - f.postInstruction() -} - -func (f *Function) Syscall() { - f.Assembler.Syscall() - f.CurrentScope().Use(f.cpu.Output[0]) - f.postInstruction() -} - -func isDestructive(mnemonic asm.Mnemonic) bool { - switch mnemonic { - case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV: - return true - default: - return false - } -} diff --git a/src/build/core/NewFunction.go b/src/build/core/NewFunction.go index 8ad25ba..f73aedb 100644 --- a/src/build/core/NewFunction.go +++ b/src/build/core/NewFunction.go @@ -7,6 +7,7 @@ import ( "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/z" ) // NewFunction creates a new function. @@ -15,19 +16,20 @@ func NewFunction(name string, file *fs.File, body []token.Token) *Function { Name: name, File: file, Body: body, - Assembler: asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 8), + Compiler: z.Compiler{ + Assembler: asm.Assembler{ + Instructions: make([]asm.Instruction, 0, 8), + }, + Stack: scope.Stack{ + Scopes: []*scope.Scope{{}}, + }, + CPU: cpu.CPU{ + All: x64.AllRegisters, + Input: x64.CallRegisters, + General: x64.GeneralRegisters, + Syscall: x64.SyscallRegisters, + Output: x64.ReturnValueRegisters, + }, }, - Stack: scope.Stack{ - Scopes: []*scope.Scope{{}}, - }, - cpu: cpu.CPU{ - All: x64.AllRegisters, - Input: x64.CallRegisters, - General: x64.GeneralRegisters, - Syscall: x64.SyscallRegisters, - Output: x64.ReturnValueRegisters, - }, - finished: make(chan struct{}), } } diff --git a/src/build/core/PrintInstructions.go b/src/build/core/PrintInstructions.go index 0c78ba4..a824f3a 100644 --- a/src/build/core/PrintInstructions.go +++ b/src/build/core/PrintInstructions.go @@ -33,9 +33,9 @@ func (f *Function) PrintInstructions() { } registers := bytes.Buffer{} - used := f.registerHistory[i] + used := f.RegisterHistory[i] - for _, reg := range f.cpu.All { + for _, reg := range f.CPU.All { if used&(1< Date: Tue, 23 Jul 2024 22:14:23 +0200 Subject: [PATCH 0361/1012] Simplified code --- src/build/core/Compare.go | 6 +++--- src/build/core/CompileCall.go | 9 +++++---- src/build/core/CompileDefinition.go | 3 +-- src/build/core/Execute.go | 4 ++-- src/build/core/ExpressionToRegister.go | 5 ++--- src/build/core/Function.go | 4 ++-- src/build/core/NewFunction.go | 4 ++-- src/build/core/TokenToRegister.go | 2 +- src/build/{z => register}/AddLabel.go | 4 ++-- src/build/register/Call.go | 7 +++++++ src/build/{z => register}/Comment.go | 4 ++-- src/build/register/FreeRegister.go | 8 ++++++++ src/build/{z => register}/Jump.go | 4 ++-- src/build/{z/Compiler.go => register/Machine.go} | 6 +++--- src/build/{z => register}/MemoryNumber.go | 4 ++-- src/build/register/NewRegister.go | 10 ++++++++++ src/build/{z => register}/Register.go | 6 +++--- src/build/register/RegisterIsUsed.go | 8 ++++++++ src/build/{z => register}/RegisterLabel.go | 8 ++++---- src/build/{z => register}/RegisterNumber.go | 8 ++++---- src/build/{z => register}/RegisterRegister.go | 8 ++++---- src/build/{z => register}/Return.go | 4 ++-- src/build/{z => register}/SaveRegister.go | 9 ++++----- src/build/register/Syscall.go | 7 +++++++ src/build/register/UseRegister.go | 8 ++++++++ src/build/{z => register}/isDestructive.go | 2 +- src/build/{z => register}/postInstruction.go | 4 ++-- src/build/z/Call.go | 7 ------- src/build/z/Syscall.go | 7 ------- tests/programs/branch.q | 4 ++++ 30 files changed, 105 insertions(+), 69 deletions(-) rename src/build/{z => register}/AddLabel.go (63%) create mode 100644 src/build/register/Call.go rename src/build/{z => register}/Comment.go (54%) create mode 100644 src/build/register/FreeRegister.go rename src/build/{z => register}/Jump.go (57%) rename src/build/{z/Compiler.go => register/Machine.go} (72%) rename src/build/{z => register}/MemoryNumber.go (54%) create mode 100644 src/build/register/NewRegister.go rename src/build/{z => register}/Register.go (62%) create mode 100644 src/build/register/RegisterIsUsed.go rename src/build/{z => register}/RegisterLabel.go (52%) rename src/build/{z => register}/RegisterNumber.go (54%) rename src/build/{z => register}/RegisterRegister.go (58%) rename src/build/{z => register}/Return.go (50%) rename src/build/{z => register}/SaveRegister.go (69%) create mode 100644 src/build/register/Syscall.go create mode 100644 src/build/register/UseRegister.go rename src/build/{z => register}/isDestructive.go (92%) rename src/build/{z => register}/postInstruction.go (74%) delete mode 100644 src/build/z/Call.go delete mode 100644 src/build/z/Syscall.go diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 7eb92f7..0ccfe3c 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -31,16 +31,16 @@ func (f *Function) Compare(comparison *expression.Expression) error { return err } - return f.Execute(comparison.Token, f.CPU.Output[0], right) + return f.ExecuteLeaf(comparison.Token, f.CPU.Output[0], right.Token) } - tmp := f.CurrentScope().MustFindFree(f.CPU.General) + tmp := f.NewRegister() err := f.ExpressionToRegister(left, tmp) if err != nil { return err } - defer f.CurrentScope().Free(tmp) + defer f.FreeRegister(tmp) return f.Execute(comparison.Token, tmp, right) } diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 358d4f8..1441ee9 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -11,8 +11,8 @@ import ( // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. func (f *Function) CompileCall(root *expression.Expression) error { - funcNameRoot := root.Children[0] funcName := "" + funcNameRoot := root.Children[0] if funcNameRoot.IsLeaf() { funcName = funcNameRoot.Token.Text(f.File.Bytes) @@ -47,7 +47,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { // Push for _, register := range f.CPU.General { - if f.CurrentScope().IsUsed(register) { + if f.RegisterIsUsed(register) { f.Register(asm.PUSH, register) } } @@ -59,19 +59,20 @@ func (f *Function) CompileCall(root *expression.Expression) error { f.Call(funcName) } + // Free parameter registers for _, register := range registers { if register == f.CPU.Output[0] && root.Parent != nil { continue } - f.CurrentScope().Free(register) + f.FreeRegister(register) } // Pop for i := len(f.CPU.General) - 1; i >= 0; i-- { register := f.CPU.General[i] - if f.CurrentScope().IsUsed(register) { + if f.RegisterIsUsed(register) { f.Register(asm.POP, register) } } diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 71745b5..6e1943f 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -21,8 +21,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) } - register := f.CurrentScope().MustFindFree(f.CPU.General) - f.CurrentScope().Reserve(register) + register := f.NewRegister() err := f.ExpressionToRegister(node.Value, register) f.AddVariable(&scope.Variable{ diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index 09f2dec..76a54bf 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -23,8 +23,8 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteRegisterRegister(operation, register, f.CPU.Output[0]) } - tmp := f.CurrentScope().MustFindFree(f.CPU.General) - defer f.CurrentScope().Free(tmp) + tmp := f.NewRegister() + defer f.FreeRegister(tmp) err := f.ExpressionToRegister(value, tmp) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index c900c7b..212b7ed 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -33,10 +33,9 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp final := register if f.UsesRegister(right, register) { - register = f.CurrentScope().MustFindFree(f.CPU.General) + register = f.NewRegister() } - f.CurrentScope().Reserve(register) err := f.ExpressionToRegister(left, register) if err != nil { @@ -47,7 +46,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if register != final { f.RegisterRegister(asm.MOVE, final, register) - f.CurrentScope().Free(register) + f.FreeRegister(register) } return err diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 65b9252..f926956 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -2,13 +2,13 @@ package core import ( "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/build/register" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/build/z" ) // Function represents the smallest unit of code. type Function struct { - z.Compiler + register.Machine Name string File *fs.File Body []token.Token diff --git a/src/build/core/NewFunction.go b/src/build/core/NewFunction.go index f73aedb..d4bc8f3 100644 --- a/src/build/core/NewFunction.go +++ b/src/build/core/NewFunction.go @@ -5,9 +5,9 @@ import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/build/register" "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/build/z" ) // NewFunction creates a new function. @@ -16,7 +16,7 @@ func NewFunction(name string, file *fs.File, body []token.Token) *Function { Name: name, File: file, Body: body, - Compiler: z.Compiler{ + Machine: register.Machine{ Assembler: asm.Assembler{ Instructions: make([]asm.Instruction, 0, 8), }, diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index cbdd1c9..50e2b77 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -21,8 +21,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - f.UseVariable(variable) f.RegisterRegister(asm.MOVE, register, variable.Register) + f.UseVariable(variable) return nil case token.Number, token.Rune: diff --git a/src/build/z/AddLabel.go b/src/build/register/AddLabel.go similarity index 63% rename from src/build/z/AddLabel.go rename to src/build/register/AddLabel.go index ccc18a8..3189ee0 100644 --- a/src/build/z/AddLabel.go +++ b/src/build/register/AddLabel.go @@ -1,8 +1,8 @@ -package z +package register import "git.akyoto.dev/cli/q/src/build/asm" -func (f *Compiler) AddLabel(label string) { +func (f *Machine) AddLabel(label string) { f.Assembler.Label(asm.LABEL, label) f.postInstruction() } diff --git a/src/build/register/Call.go b/src/build/register/Call.go new file mode 100644 index 0000000..6008dbd --- /dev/null +++ b/src/build/register/Call.go @@ -0,0 +1,7 @@ +package register + +func (f *Machine) Call(label string) { + f.Assembler.Call(label) + f.UseRegister(f.CPU.Output[0]) + f.postInstruction() +} diff --git a/src/build/z/Comment.go b/src/build/register/Comment.go similarity index 54% rename from src/build/z/Comment.go rename to src/build/register/Comment.go index d795f3b..ca86f0f 100644 --- a/src/build/z/Comment.go +++ b/src/build/register/Comment.go @@ -1,8 +1,8 @@ -package z +package register import "fmt" -func (f *Compiler) Comment(format string, args ...any) { +func (f *Machine) Comment(format string, args ...any) { f.Assembler.Comment(fmt.Sprintf(format, args...)) f.postInstruction() } diff --git a/src/build/register/FreeRegister.go b/src/build/register/FreeRegister.go new file mode 100644 index 0000000..5c459b7 --- /dev/null +++ b/src/build/register/FreeRegister.go @@ -0,0 +1,8 @@ +package register + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// FreeRegister frees a register. +func (f *Machine) FreeRegister(register cpu.Register) { + f.CurrentScope().Free(register) +} diff --git a/src/build/z/Jump.go b/src/build/register/Jump.go similarity index 57% rename from src/build/z/Jump.go rename to src/build/register/Jump.go index 5938ed5..ff77d01 100644 --- a/src/build/z/Jump.go +++ b/src/build/register/Jump.go @@ -1,8 +1,8 @@ -package z +package register import "git.akyoto.dev/cli/q/src/build/asm" -func (f *Compiler) Jump(mnemonic asm.Mnemonic, label string) { +func (f *Machine) Jump(mnemonic asm.Mnemonic, label string) { f.Assembler.Label(mnemonic, label) f.postInstruction() } diff --git a/src/build/z/Compiler.go b/src/build/register/Machine.go similarity index 72% rename from src/build/z/Compiler.go rename to src/build/register/Machine.go index b93e895..50a22b7 100644 --- a/src/build/z/Compiler.go +++ b/src/build/register/Machine.go @@ -1,4 +1,4 @@ -package z +package register import ( "git.akyoto.dev/cli/q/src/build/asm" @@ -6,8 +6,8 @@ import ( "git.akyoto.dev/cli/q/src/build/scope" ) -// Compiler is a register usage aware assembler. -type Compiler struct { +// Machine is a register usage aware assembler. +type Machine struct { scope.Stack Assembler asm.Assembler CPU cpu.CPU diff --git a/src/build/z/MemoryNumber.go b/src/build/register/MemoryNumber.go similarity index 54% rename from src/build/z/MemoryNumber.go rename to src/build/register/MemoryNumber.go index 55ab175..6e99fda 100644 --- a/src/build/z/MemoryNumber.go +++ b/src/build/register/MemoryNumber.go @@ -1,8 +1,8 @@ -package z +package register import "git.akyoto.dev/cli/q/src/build/asm" -func (f *Compiler) MemoryNumber(mnemonic asm.Mnemonic, a asm.Memory, b int) { +func (f *Machine) MemoryNumber(mnemonic asm.Mnemonic, a asm.Memory, b int) { f.Assembler.MemoryNumber(mnemonic, a, b) f.postInstruction() } diff --git a/src/build/register/NewRegister.go b/src/build/register/NewRegister.go new file mode 100644 index 0000000..fb2300c --- /dev/null +++ b/src/build/register/NewRegister.go @@ -0,0 +1,10 @@ +package register + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// NewRegister reserves a register. +func (f *Machine) NewRegister() cpu.Register { + register := f.CurrentScope().MustFindFree(f.CPU.General) + f.CurrentScope().Reserve(register) + return register +} diff --git a/src/build/z/Register.go b/src/build/register/Register.go similarity index 62% rename from src/build/z/Register.go rename to src/build/register/Register.go index 6244121..89f0ca1 100644 --- a/src/build/z/Register.go +++ b/src/build/register/Register.go @@ -1,15 +1,15 @@ -package z +package register import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" ) -func (f *Compiler) Register(mnemonic asm.Mnemonic, a cpu.Register) { +func (f *Machine) Register(mnemonic asm.Mnemonic, a cpu.Register) { f.Assembler.Register(mnemonic, a) if mnemonic == asm.POP { - f.CurrentScope().Use(a) + f.UseRegister(a) } f.postInstruction() diff --git a/src/build/register/RegisterIsUsed.go b/src/build/register/RegisterIsUsed.go new file mode 100644 index 0000000..0ba6d0e --- /dev/null +++ b/src/build/register/RegisterIsUsed.go @@ -0,0 +1,8 @@ +package register + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// RegisterIsUsed reserves a register. +func (f *Machine) RegisterIsUsed(register cpu.Register) bool { + return f.CurrentScope().IsUsed(register) +} diff --git a/src/build/z/RegisterLabel.go b/src/build/register/RegisterLabel.go similarity index 52% rename from src/build/z/RegisterLabel.go rename to src/build/register/RegisterLabel.go index 6047d05..7778e5a 100644 --- a/src/build/z/RegisterLabel.go +++ b/src/build/register/RegisterLabel.go @@ -1,19 +1,19 @@ -package z +package register import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" ) -func (f *Compiler) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { - if f.CurrentScope().IsUsed(register) && isDestructive(mnemonic) { +func (f *Machine) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { + if f.RegisterIsUsed(register) && isDestructive(mnemonic) { f.SaveRegister(register) } f.Assembler.RegisterLabel(mnemonic, register, label) if mnemonic == asm.MOVE { - f.CurrentScope().Use(register) + f.UseRegister(register) } f.postInstruction() diff --git a/src/build/z/RegisterNumber.go b/src/build/register/RegisterNumber.go similarity index 54% rename from src/build/z/RegisterNumber.go rename to src/build/register/RegisterNumber.go index 14f84ae..ce9a41f 100644 --- a/src/build/z/RegisterNumber.go +++ b/src/build/register/RegisterNumber.go @@ -1,19 +1,19 @@ -package z +package register import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" ) -func (f *Compiler) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { - if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) { +func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { + if f.RegisterIsUsed(a) && isDestructive(mnemonic) { f.SaveRegister(a) } f.Assembler.RegisterNumber(mnemonic, a, b) if mnemonic == asm.MOVE { - f.CurrentScope().Use(a) + f.UseRegister(a) } f.postInstruction() diff --git a/src/build/z/RegisterRegister.go b/src/build/register/RegisterRegister.go similarity index 58% rename from src/build/z/RegisterRegister.go rename to src/build/register/RegisterRegister.go index 7370955..a969ca5 100644 --- a/src/build/z/RegisterRegister.go +++ b/src/build/register/RegisterRegister.go @@ -1,23 +1,23 @@ -package z +package register import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" ) -func (f *Compiler) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) { +func (f *Machine) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) { if mnemonic == asm.MOVE && a == b { return } - if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) { + if f.RegisterIsUsed(a) && isDestructive(mnemonic) { f.SaveRegister(a) } f.Assembler.RegisterRegister(mnemonic, a, b) if mnemonic == asm.MOVE { - f.CurrentScope().Use(a) + f.UseRegister(a) } f.postInstruction() diff --git a/src/build/z/Return.go b/src/build/register/Return.go similarity index 50% rename from src/build/z/Return.go rename to src/build/register/Return.go index 596584b..b9e09b7 100644 --- a/src/build/z/Return.go +++ b/src/build/register/Return.go @@ -1,6 +1,6 @@ -package z +package register -func (f *Compiler) Return() { +func (f *Machine) Return() { f.Assembler.Return() f.postInstruction() } diff --git a/src/build/z/SaveRegister.go b/src/build/register/SaveRegister.go similarity index 69% rename from src/build/z/SaveRegister.go rename to src/build/register/SaveRegister.go index dacacf5..1c20818 100644 --- a/src/build/z/SaveRegister.go +++ b/src/build/register/SaveRegister.go @@ -1,4 +1,4 @@ -package z +package register import ( "git.akyoto.dev/cli/q/src/build/asm" @@ -6,8 +6,8 @@ import ( ) // SaveRegister attempts to move a variable occupying this register to another register. -func (f *Compiler) SaveRegister(register cpu.Register) { - if !f.CurrentScope().IsUsed(register) { +func (f *Machine) SaveRegister(register cpu.Register) { + if !f.RegisterIsUsed(register) { return } @@ -23,8 +23,7 @@ func (f *Compiler) SaveRegister(register cpu.Register) { return } - newRegister := f.CurrentScope().MustFindFree(f.CPU.General) - f.CurrentScope().Reserve(newRegister) + newRegister := f.NewRegister() f.RegisterRegister(asm.MOVE, newRegister, register) variable.Register = newRegister } diff --git a/src/build/register/Syscall.go b/src/build/register/Syscall.go new file mode 100644 index 0000000..ace09d7 --- /dev/null +++ b/src/build/register/Syscall.go @@ -0,0 +1,7 @@ +package register + +func (f *Machine) Syscall() { + f.Assembler.Syscall() + f.UseRegister(f.CPU.Output[0]) + f.postInstruction() +} diff --git a/src/build/register/UseRegister.go b/src/build/register/UseRegister.go new file mode 100644 index 0000000..ce45d81 --- /dev/null +++ b/src/build/register/UseRegister.go @@ -0,0 +1,8 @@ +package register + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// Use marks a register to be currently in use. +func (f *Machine) UseRegister(register cpu.Register) { + f.CurrentScope().Use(register) +} diff --git a/src/build/z/isDestructive.go b/src/build/register/isDestructive.go similarity index 92% rename from src/build/z/isDestructive.go rename to src/build/register/isDestructive.go index 2165fcb..086ceca 100644 --- a/src/build/z/isDestructive.go +++ b/src/build/register/isDestructive.go @@ -1,4 +1,4 @@ -package z +package register import "git.akyoto.dev/cli/q/src/build/asm" diff --git a/src/build/z/postInstruction.go b/src/build/register/postInstruction.go similarity index 74% rename from src/build/z/postInstruction.go rename to src/build/register/postInstruction.go index a9166e4..d3e2b5d 100644 --- a/src/build/z/postInstruction.go +++ b/src/build/register/postInstruction.go @@ -1,8 +1,8 @@ -package z +package register import "git.akyoto.dev/cli/q/src/build/config" -func (f *Compiler) postInstruction() { +func (f *Machine) postInstruction() { if !config.Assembler { return } diff --git a/src/build/z/Call.go b/src/build/z/Call.go deleted file mode 100644 index f1284bb..0000000 --- a/src/build/z/Call.go +++ /dev/null @@ -1,7 +0,0 @@ -package z - -func (f *Compiler) Call(label string) { - f.Assembler.Call(label) - f.CurrentScope().Use(f.CPU.Output[0]) - f.postInstruction() -} diff --git a/src/build/z/Syscall.go b/src/build/z/Syscall.go deleted file mode 100644 index dfd2199..0000000 --- a/src/build/z/Syscall.go +++ /dev/null @@ -1,7 +0,0 @@ -package z - -func (f *Compiler) Syscall() { - f.Assembler.Syscall() - f.CurrentScope().Use(f.CPU.Output[0]) - f.postInstruction() -} diff --git a/tests/programs/branch.q b/tests/programs/branch.q index aaa393f..9e6f514 100644 --- a/tests/programs/branch.q +++ b/tests/programs/branch.q @@ -63,6 +63,10 @@ main() { sys.exit(1) } + if inc(x) == dec(x) { + sys.exit(1) + } + if x == 0 { sys.exit(0) } From 26214366e37a04b26d1267bc6da440cc86939ff2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 23 Jul 2024 22:14:23 +0200 Subject: [PATCH 0362/1012] Simplified code --- src/build/core/Compare.go | 6 +++--- src/build/core/CompileCall.go | 9 +++++---- src/build/core/CompileDefinition.go | 3 +-- src/build/core/Execute.go | 4 ++-- src/build/core/ExpressionToRegister.go | 5 ++--- src/build/core/Function.go | 4 ++-- src/build/core/NewFunction.go | 4 ++-- src/build/core/TokenToRegister.go | 2 +- src/build/{z => register}/AddLabel.go | 4 ++-- src/build/register/Call.go | 7 +++++++ src/build/{z => register}/Comment.go | 4 ++-- src/build/register/FreeRegister.go | 8 ++++++++ src/build/{z => register}/Jump.go | 4 ++-- src/build/{z/Compiler.go => register/Machine.go} | 6 +++--- src/build/{z => register}/MemoryNumber.go | 4 ++-- src/build/register/NewRegister.go | 10 ++++++++++ src/build/{z => register}/Register.go | 6 +++--- src/build/register/RegisterIsUsed.go | 8 ++++++++ src/build/{z => register}/RegisterLabel.go | 8 ++++---- src/build/{z => register}/RegisterNumber.go | 8 ++++---- src/build/{z => register}/RegisterRegister.go | 8 ++++---- src/build/{z => register}/Return.go | 4 ++-- src/build/{z => register}/SaveRegister.go | 9 ++++----- src/build/register/Syscall.go | 7 +++++++ src/build/register/UseRegister.go | 8 ++++++++ src/build/{z => register}/isDestructive.go | 2 +- src/build/{z => register}/postInstruction.go | 4 ++-- src/build/z/Call.go | 7 ------- src/build/z/Syscall.go | 7 ------- tests/programs/branch.q | 4 ++++ 30 files changed, 105 insertions(+), 69 deletions(-) rename src/build/{z => register}/AddLabel.go (63%) create mode 100644 src/build/register/Call.go rename src/build/{z => register}/Comment.go (54%) create mode 100644 src/build/register/FreeRegister.go rename src/build/{z => register}/Jump.go (57%) rename src/build/{z/Compiler.go => register/Machine.go} (72%) rename src/build/{z => register}/MemoryNumber.go (54%) create mode 100644 src/build/register/NewRegister.go rename src/build/{z => register}/Register.go (62%) create mode 100644 src/build/register/RegisterIsUsed.go rename src/build/{z => register}/RegisterLabel.go (52%) rename src/build/{z => register}/RegisterNumber.go (54%) rename src/build/{z => register}/RegisterRegister.go (58%) rename src/build/{z => register}/Return.go (50%) rename src/build/{z => register}/SaveRegister.go (69%) create mode 100644 src/build/register/Syscall.go create mode 100644 src/build/register/UseRegister.go rename src/build/{z => register}/isDestructive.go (92%) rename src/build/{z => register}/postInstruction.go (74%) delete mode 100644 src/build/z/Call.go delete mode 100644 src/build/z/Syscall.go diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 7eb92f7..0ccfe3c 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -31,16 +31,16 @@ func (f *Function) Compare(comparison *expression.Expression) error { return err } - return f.Execute(comparison.Token, f.CPU.Output[0], right) + return f.ExecuteLeaf(comparison.Token, f.CPU.Output[0], right.Token) } - tmp := f.CurrentScope().MustFindFree(f.CPU.General) + tmp := f.NewRegister() err := f.ExpressionToRegister(left, tmp) if err != nil { return err } - defer f.CurrentScope().Free(tmp) + defer f.FreeRegister(tmp) return f.Execute(comparison.Token, tmp, right) } diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 358d4f8..1441ee9 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -11,8 +11,8 @@ import ( // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. func (f *Function) CompileCall(root *expression.Expression) error { - funcNameRoot := root.Children[0] funcName := "" + funcNameRoot := root.Children[0] if funcNameRoot.IsLeaf() { funcName = funcNameRoot.Token.Text(f.File.Bytes) @@ -47,7 +47,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { // Push for _, register := range f.CPU.General { - if f.CurrentScope().IsUsed(register) { + if f.RegisterIsUsed(register) { f.Register(asm.PUSH, register) } } @@ -59,19 +59,20 @@ func (f *Function) CompileCall(root *expression.Expression) error { f.Call(funcName) } + // Free parameter registers for _, register := range registers { if register == f.CPU.Output[0] && root.Parent != nil { continue } - f.CurrentScope().Free(register) + f.FreeRegister(register) } // Pop for i := len(f.CPU.General) - 1; i >= 0; i-- { register := f.CPU.General[i] - if f.CurrentScope().IsUsed(register) { + if f.RegisterIsUsed(register) { f.Register(asm.POP, register) } } diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 71745b5..6e1943f 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -21,8 +21,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) } - register := f.CurrentScope().MustFindFree(f.CPU.General) - f.CurrentScope().Reserve(register) + register := f.NewRegister() err := f.ExpressionToRegister(node.Value, register) f.AddVariable(&scope.Variable{ diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index 09f2dec..76a54bf 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -23,8 +23,8 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteRegisterRegister(operation, register, f.CPU.Output[0]) } - tmp := f.CurrentScope().MustFindFree(f.CPU.General) - defer f.CurrentScope().Free(tmp) + tmp := f.NewRegister() + defer f.FreeRegister(tmp) err := f.ExpressionToRegister(value, tmp) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index c900c7b..212b7ed 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -33,10 +33,9 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp final := register if f.UsesRegister(right, register) { - register = f.CurrentScope().MustFindFree(f.CPU.General) + register = f.NewRegister() } - f.CurrentScope().Reserve(register) err := f.ExpressionToRegister(left, register) if err != nil { @@ -47,7 +46,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if register != final { f.RegisterRegister(asm.MOVE, final, register) - f.CurrentScope().Free(register) + f.FreeRegister(register) } return err diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 65b9252..f926956 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -2,13 +2,13 @@ package core import ( "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/build/register" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/build/z" ) // Function represents the smallest unit of code. type Function struct { - z.Compiler + register.Machine Name string File *fs.File Body []token.Token diff --git a/src/build/core/NewFunction.go b/src/build/core/NewFunction.go index f73aedb..d4bc8f3 100644 --- a/src/build/core/NewFunction.go +++ b/src/build/core/NewFunction.go @@ -5,9 +5,9 @@ import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/build/register" "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/build/z" ) // NewFunction creates a new function. @@ -16,7 +16,7 @@ func NewFunction(name string, file *fs.File, body []token.Token) *Function { Name: name, File: file, Body: body, - Compiler: z.Compiler{ + Machine: register.Machine{ Assembler: asm.Assembler{ Instructions: make([]asm.Instruction, 0, 8), }, diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index cbdd1c9..50e2b77 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -21,8 +21,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - f.UseVariable(variable) f.RegisterRegister(asm.MOVE, register, variable.Register) + f.UseVariable(variable) return nil case token.Number, token.Rune: diff --git a/src/build/z/AddLabel.go b/src/build/register/AddLabel.go similarity index 63% rename from src/build/z/AddLabel.go rename to src/build/register/AddLabel.go index ccc18a8..3189ee0 100644 --- a/src/build/z/AddLabel.go +++ b/src/build/register/AddLabel.go @@ -1,8 +1,8 @@ -package z +package register import "git.akyoto.dev/cli/q/src/build/asm" -func (f *Compiler) AddLabel(label string) { +func (f *Machine) AddLabel(label string) { f.Assembler.Label(asm.LABEL, label) f.postInstruction() } diff --git a/src/build/register/Call.go b/src/build/register/Call.go new file mode 100644 index 0000000..6008dbd --- /dev/null +++ b/src/build/register/Call.go @@ -0,0 +1,7 @@ +package register + +func (f *Machine) Call(label string) { + f.Assembler.Call(label) + f.UseRegister(f.CPU.Output[0]) + f.postInstruction() +} diff --git a/src/build/z/Comment.go b/src/build/register/Comment.go similarity index 54% rename from src/build/z/Comment.go rename to src/build/register/Comment.go index d795f3b..ca86f0f 100644 --- a/src/build/z/Comment.go +++ b/src/build/register/Comment.go @@ -1,8 +1,8 @@ -package z +package register import "fmt" -func (f *Compiler) Comment(format string, args ...any) { +func (f *Machine) Comment(format string, args ...any) { f.Assembler.Comment(fmt.Sprintf(format, args...)) f.postInstruction() } diff --git a/src/build/register/FreeRegister.go b/src/build/register/FreeRegister.go new file mode 100644 index 0000000..5c459b7 --- /dev/null +++ b/src/build/register/FreeRegister.go @@ -0,0 +1,8 @@ +package register + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// FreeRegister frees a register. +func (f *Machine) FreeRegister(register cpu.Register) { + f.CurrentScope().Free(register) +} diff --git a/src/build/z/Jump.go b/src/build/register/Jump.go similarity index 57% rename from src/build/z/Jump.go rename to src/build/register/Jump.go index 5938ed5..ff77d01 100644 --- a/src/build/z/Jump.go +++ b/src/build/register/Jump.go @@ -1,8 +1,8 @@ -package z +package register import "git.akyoto.dev/cli/q/src/build/asm" -func (f *Compiler) Jump(mnemonic asm.Mnemonic, label string) { +func (f *Machine) Jump(mnemonic asm.Mnemonic, label string) { f.Assembler.Label(mnemonic, label) f.postInstruction() } diff --git a/src/build/z/Compiler.go b/src/build/register/Machine.go similarity index 72% rename from src/build/z/Compiler.go rename to src/build/register/Machine.go index b93e895..50a22b7 100644 --- a/src/build/z/Compiler.go +++ b/src/build/register/Machine.go @@ -1,4 +1,4 @@ -package z +package register import ( "git.akyoto.dev/cli/q/src/build/asm" @@ -6,8 +6,8 @@ import ( "git.akyoto.dev/cli/q/src/build/scope" ) -// Compiler is a register usage aware assembler. -type Compiler struct { +// Machine is a register usage aware assembler. +type Machine struct { scope.Stack Assembler asm.Assembler CPU cpu.CPU diff --git a/src/build/z/MemoryNumber.go b/src/build/register/MemoryNumber.go similarity index 54% rename from src/build/z/MemoryNumber.go rename to src/build/register/MemoryNumber.go index 55ab175..6e99fda 100644 --- a/src/build/z/MemoryNumber.go +++ b/src/build/register/MemoryNumber.go @@ -1,8 +1,8 @@ -package z +package register import "git.akyoto.dev/cli/q/src/build/asm" -func (f *Compiler) MemoryNumber(mnemonic asm.Mnemonic, a asm.Memory, b int) { +func (f *Machine) MemoryNumber(mnemonic asm.Mnemonic, a asm.Memory, b int) { f.Assembler.MemoryNumber(mnemonic, a, b) f.postInstruction() } diff --git a/src/build/register/NewRegister.go b/src/build/register/NewRegister.go new file mode 100644 index 0000000..fb2300c --- /dev/null +++ b/src/build/register/NewRegister.go @@ -0,0 +1,10 @@ +package register + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// NewRegister reserves a register. +func (f *Machine) NewRegister() cpu.Register { + register := f.CurrentScope().MustFindFree(f.CPU.General) + f.CurrentScope().Reserve(register) + return register +} diff --git a/src/build/z/Register.go b/src/build/register/Register.go similarity index 62% rename from src/build/z/Register.go rename to src/build/register/Register.go index 6244121..89f0ca1 100644 --- a/src/build/z/Register.go +++ b/src/build/register/Register.go @@ -1,15 +1,15 @@ -package z +package register import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" ) -func (f *Compiler) Register(mnemonic asm.Mnemonic, a cpu.Register) { +func (f *Machine) Register(mnemonic asm.Mnemonic, a cpu.Register) { f.Assembler.Register(mnemonic, a) if mnemonic == asm.POP { - f.CurrentScope().Use(a) + f.UseRegister(a) } f.postInstruction() diff --git a/src/build/register/RegisterIsUsed.go b/src/build/register/RegisterIsUsed.go new file mode 100644 index 0000000..0ba6d0e --- /dev/null +++ b/src/build/register/RegisterIsUsed.go @@ -0,0 +1,8 @@ +package register + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// RegisterIsUsed reserves a register. +func (f *Machine) RegisterIsUsed(register cpu.Register) bool { + return f.CurrentScope().IsUsed(register) +} diff --git a/src/build/z/RegisterLabel.go b/src/build/register/RegisterLabel.go similarity index 52% rename from src/build/z/RegisterLabel.go rename to src/build/register/RegisterLabel.go index 6047d05..7778e5a 100644 --- a/src/build/z/RegisterLabel.go +++ b/src/build/register/RegisterLabel.go @@ -1,19 +1,19 @@ -package z +package register import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" ) -func (f *Compiler) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { - if f.CurrentScope().IsUsed(register) && isDestructive(mnemonic) { +func (f *Machine) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { + if f.RegisterIsUsed(register) && isDestructive(mnemonic) { f.SaveRegister(register) } f.Assembler.RegisterLabel(mnemonic, register, label) if mnemonic == asm.MOVE { - f.CurrentScope().Use(register) + f.UseRegister(register) } f.postInstruction() diff --git a/src/build/z/RegisterNumber.go b/src/build/register/RegisterNumber.go similarity index 54% rename from src/build/z/RegisterNumber.go rename to src/build/register/RegisterNumber.go index 14f84ae..ce9a41f 100644 --- a/src/build/z/RegisterNumber.go +++ b/src/build/register/RegisterNumber.go @@ -1,19 +1,19 @@ -package z +package register import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" ) -func (f *Compiler) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { - if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) { +func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { + if f.RegisterIsUsed(a) && isDestructive(mnemonic) { f.SaveRegister(a) } f.Assembler.RegisterNumber(mnemonic, a, b) if mnemonic == asm.MOVE { - f.CurrentScope().Use(a) + f.UseRegister(a) } f.postInstruction() diff --git a/src/build/z/RegisterRegister.go b/src/build/register/RegisterRegister.go similarity index 58% rename from src/build/z/RegisterRegister.go rename to src/build/register/RegisterRegister.go index 7370955..a969ca5 100644 --- a/src/build/z/RegisterRegister.go +++ b/src/build/register/RegisterRegister.go @@ -1,23 +1,23 @@ -package z +package register import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" ) -func (f *Compiler) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) { +func (f *Machine) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) { if mnemonic == asm.MOVE && a == b { return } - if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) { + if f.RegisterIsUsed(a) && isDestructive(mnemonic) { f.SaveRegister(a) } f.Assembler.RegisterRegister(mnemonic, a, b) if mnemonic == asm.MOVE { - f.CurrentScope().Use(a) + f.UseRegister(a) } f.postInstruction() diff --git a/src/build/z/Return.go b/src/build/register/Return.go similarity index 50% rename from src/build/z/Return.go rename to src/build/register/Return.go index 596584b..b9e09b7 100644 --- a/src/build/z/Return.go +++ b/src/build/register/Return.go @@ -1,6 +1,6 @@ -package z +package register -func (f *Compiler) Return() { +func (f *Machine) Return() { f.Assembler.Return() f.postInstruction() } diff --git a/src/build/z/SaveRegister.go b/src/build/register/SaveRegister.go similarity index 69% rename from src/build/z/SaveRegister.go rename to src/build/register/SaveRegister.go index dacacf5..1c20818 100644 --- a/src/build/z/SaveRegister.go +++ b/src/build/register/SaveRegister.go @@ -1,4 +1,4 @@ -package z +package register import ( "git.akyoto.dev/cli/q/src/build/asm" @@ -6,8 +6,8 @@ import ( ) // SaveRegister attempts to move a variable occupying this register to another register. -func (f *Compiler) SaveRegister(register cpu.Register) { - if !f.CurrentScope().IsUsed(register) { +func (f *Machine) SaveRegister(register cpu.Register) { + if !f.RegisterIsUsed(register) { return } @@ -23,8 +23,7 @@ func (f *Compiler) SaveRegister(register cpu.Register) { return } - newRegister := f.CurrentScope().MustFindFree(f.CPU.General) - f.CurrentScope().Reserve(newRegister) + newRegister := f.NewRegister() f.RegisterRegister(asm.MOVE, newRegister, register) variable.Register = newRegister } diff --git a/src/build/register/Syscall.go b/src/build/register/Syscall.go new file mode 100644 index 0000000..ace09d7 --- /dev/null +++ b/src/build/register/Syscall.go @@ -0,0 +1,7 @@ +package register + +func (f *Machine) Syscall() { + f.Assembler.Syscall() + f.UseRegister(f.CPU.Output[0]) + f.postInstruction() +} diff --git a/src/build/register/UseRegister.go b/src/build/register/UseRegister.go new file mode 100644 index 0000000..ce45d81 --- /dev/null +++ b/src/build/register/UseRegister.go @@ -0,0 +1,8 @@ +package register + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// Use marks a register to be currently in use. +func (f *Machine) UseRegister(register cpu.Register) { + f.CurrentScope().Use(register) +} diff --git a/src/build/z/isDestructive.go b/src/build/register/isDestructive.go similarity index 92% rename from src/build/z/isDestructive.go rename to src/build/register/isDestructive.go index 2165fcb..086ceca 100644 --- a/src/build/z/isDestructive.go +++ b/src/build/register/isDestructive.go @@ -1,4 +1,4 @@ -package z +package register import "git.akyoto.dev/cli/q/src/build/asm" diff --git a/src/build/z/postInstruction.go b/src/build/register/postInstruction.go similarity index 74% rename from src/build/z/postInstruction.go rename to src/build/register/postInstruction.go index a9166e4..d3e2b5d 100644 --- a/src/build/z/postInstruction.go +++ b/src/build/register/postInstruction.go @@ -1,8 +1,8 @@ -package z +package register import "git.akyoto.dev/cli/q/src/build/config" -func (f *Compiler) postInstruction() { +func (f *Machine) postInstruction() { if !config.Assembler { return } diff --git a/src/build/z/Call.go b/src/build/z/Call.go deleted file mode 100644 index f1284bb..0000000 --- a/src/build/z/Call.go +++ /dev/null @@ -1,7 +0,0 @@ -package z - -func (f *Compiler) Call(label string) { - f.Assembler.Call(label) - f.CurrentScope().Use(f.CPU.Output[0]) - f.postInstruction() -} diff --git a/src/build/z/Syscall.go b/src/build/z/Syscall.go deleted file mode 100644 index dfd2199..0000000 --- a/src/build/z/Syscall.go +++ /dev/null @@ -1,7 +0,0 @@ -package z - -func (f *Compiler) Syscall() { - f.Assembler.Syscall() - f.CurrentScope().Use(f.CPU.Output[0]) - f.postInstruction() -} diff --git a/tests/programs/branch.q b/tests/programs/branch.q index aaa393f..9e6f514 100644 --- a/tests/programs/branch.q +++ b/tests/programs/branch.q @@ -63,6 +63,10 @@ main() { sys.exit(1) } + if inc(x) == dec(x) { + sys.exit(1) + } + if x == 0 { sys.exit(0) } From da8e8f11d9861e796c03075119e309179b78ef54 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 23 Jul 2024 22:24:48 +0200 Subject: [PATCH 0363/1012] Updated dependencies --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 97c0628..52bbab9 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,4 @@ require ( git.akyoto.dev/go/color v0.1.1 ) -require golang.org/x/sys v0.21.0 // indirect +require golang.org/x/sys v0.22.0 // indirect diff --git a/go.sum b/go.sum index c3e0b31..5e65b25 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,5 @@ git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= git.akyoto.dev/go/color v0.1.1 h1:mMAoMIwLBPNy7ocRSxdsCFs7onPC3GfDEiJErCneqRE= git.akyoto.dev/go/color v0.1.1/go.mod h1:ywOjoD0O0sk6bIn92uAJf7mErlEFCuQInL84y4Lqi3Q= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= From 975b4711d390fde92b3ff315b03462c943ea2cfe Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 23 Jul 2024 22:24:48 +0200 Subject: [PATCH 0364/1012] Updated dependencies --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 97c0628..52bbab9 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,4 @@ require ( git.akyoto.dev/go/color v0.1.1 ) -require golang.org/x/sys v0.21.0 // indirect +require golang.org/x/sys v0.22.0 // indirect diff --git a/go.sum b/go.sum index c3e0b31..5e65b25 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,5 @@ git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= git.akyoto.dev/go/color v0.1.1 h1:mMAoMIwLBPNy7ocRSxdsCFs7onPC3GfDEiJErCneqRE= git.akyoto.dev/go/color v0.1.1/go.mod h1:ywOjoD0O0sk6bIn92uAJf7mErlEFCuQInL84y4Lqi3Q= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= From 26dfe5b5eb959d8ca23fc41d1061c25d206c89d7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 24 Jul 2024 11:50:23 +0200 Subject: [PATCH 0365/1012] Improved tokenizer performance --- src/build/token/Keywords.go | 9 --------- src/build/token/Tokenize.go | 23 ++++++++++++++++++++--- 2 files changed, 20 insertions(+), 12 deletions(-) delete mode 100644 src/build/token/Keywords.go diff --git a/src/build/token/Keywords.go b/src/build/token/Keywords.go deleted file mode 100644 index c75fd75..0000000 --- a/src/build/token/Keywords.go +++ /dev/null @@ -1,9 +0,0 @@ -package token - -// Keywords is a map of all keywords used in the language. -var Keywords = map[string]Kind{ - "if": If, - "import": Import, - "loop": Loop, - "return": Return, -} diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index f0d282c..05fa306 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -1,5 +1,7 @@ package token +import "bytes" + // Tokenize turns the file contents into a list of tokens. func Tokenize(buffer []byte) List { var ( @@ -84,10 +86,25 @@ func Tokenize(buffer []byte) List { identifier := buffer[position:i] kind := Identifier - keyword, isKeyword := Keywords[string(identifier)] - if isKeyword { - kind = keyword + switch identifier[0] { + case 'i': + switch { + case bytes.Equal(identifier, []byte("if")): + kind = If + case bytes.Equal(identifier, []byte("import")): + kind = Import + } + case 'l': + switch { + case bytes.Equal(identifier, []byte("loop")): + kind = Loop + } + case 'r': + switch { + case bytes.Equal(identifier, []byte("return")): + kind = Return + } } tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(len(identifier))}) From 89fbc233eb93df93dbeab50eaa6efc28b689a1ca Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 24 Jul 2024 11:50:23 +0200 Subject: [PATCH 0366/1012] Improved tokenizer performance --- src/build/token/Keywords.go | 9 --------- src/build/token/Tokenize.go | 23 ++++++++++++++++++++--- 2 files changed, 20 insertions(+), 12 deletions(-) delete mode 100644 src/build/token/Keywords.go diff --git a/src/build/token/Keywords.go b/src/build/token/Keywords.go deleted file mode 100644 index c75fd75..0000000 --- a/src/build/token/Keywords.go +++ /dev/null @@ -1,9 +0,0 @@ -package token - -// Keywords is a map of all keywords used in the language. -var Keywords = map[string]Kind{ - "if": If, - "import": Import, - "loop": Loop, - "return": Return, -} diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index f0d282c..05fa306 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -1,5 +1,7 @@ package token +import "bytes" + // Tokenize turns the file contents into a list of tokens. func Tokenize(buffer []byte) List { var ( @@ -84,10 +86,25 @@ func Tokenize(buffer []byte) List { identifier := buffer[position:i] kind := Identifier - keyword, isKeyword := Keywords[string(identifier)] - if isKeyword { - kind = keyword + switch identifier[0] { + case 'i': + switch { + case bytes.Equal(identifier, []byte("if")): + kind = If + case bytes.Equal(identifier, []byte("import")): + kind = Import + } + case 'l': + switch { + case bytes.Equal(identifier, []byte("loop")): + kind = Loop + } + case 'r': + switch { + case bytes.Equal(identifier, []byte("return")): + kind = Return + } } tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(len(identifier))}) From ee563230a89ad4b9310b32bc3ec22b04a878cfb9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 24 Jul 2024 11:55:35 +0200 Subject: [PATCH 0367/1012] Improved tokenizer performance --- src/build/token/Tokenize.go | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 05fa306..23bf106 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -1,7 +1,5 @@ package token -import "bytes" - // Tokenize turns the file contents into a list of tokens. func Tokenize(buffer []byte) List { var ( @@ -87,24 +85,15 @@ func Tokenize(buffer []byte) List { identifier := buffer[position:i] kind := Identifier - switch identifier[0] { - case 'i': - switch { - case bytes.Equal(identifier, []byte("if")): - kind = If - case bytes.Equal(identifier, []byte("import")): - kind = Import - } - case 'l': - switch { - case bytes.Equal(identifier, []byte("loop")): - kind = Loop - } - case 'r': - switch { - case bytes.Equal(identifier, []byte("return")): - kind = Return - } + switch string(identifier) { + case "if": + kind = If + case "import": + kind = Import + case "loop": + kind = Loop + case "return": + kind = Return } tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(len(identifier))}) From b9dd5c1b59bf86bc4f6e94c6fd85ad5f524da346 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 24 Jul 2024 11:55:35 +0200 Subject: [PATCH 0368/1012] Improved tokenizer performance --- src/build/token/Tokenize.go | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 05fa306..23bf106 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -1,7 +1,5 @@ package token -import "bytes" - // Tokenize turns the file contents into a list of tokens. func Tokenize(buffer []byte) List { var ( @@ -87,24 +85,15 @@ func Tokenize(buffer []byte) List { identifier := buffer[position:i] kind := Identifier - switch identifier[0] { - case 'i': - switch { - case bytes.Equal(identifier, []byte("if")): - kind = If - case bytes.Equal(identifier, []byte("import")): - kind = Import - } - case 'l': - switch { - case bytes.Equal(identifier, []byte("loop")): - kind = Loop - } - case 'r': - switch { - case bytes.Equal(identifier, []byte("return")): - kind = Return - } + switch string(identifier) { + case "if": + kind = If + case "import": + kind = Import + case "loop": + kind = Loop + case "return": + kind = Return } tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(len(identifier))}) From 264be81b61fe2b6fe6055877d4777dddf7919401 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 24 Jul 2024 12:14:51 +0200 Subject: [PATCH 0369/1012] Improved tokenizer performance --- src/build/token/Operators.go | 39 ----------------- src/build/token/Tokenize.go | 82 +++++++++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 41 deletions(-) delete mode 100644 src/build/token/Operators.go diff --git a/src/build/token/Operators.go b/src/build/token/Operators.go deleted file mode 100644 index 842fb94..0000000 --- a/src/build/token/Operators.go +++ /dev/null @@ -1,39 +0,0 @@ -package token - -// Operators is a map of all operators used in the language. -var Operators = map[string]Kind{ - ".": Period, - "=": Assign, - ":=": Define, - "+": Add, - "-": Sub, - "*": Mul, - "/": Div, - "%": Mod, - "&": And, - "|": Or, - "^": Xor, - "<<": Shl, - ">>": Shr, - "&&": LogicalAnd, - "||": LogicalOr, - "!": Not, - "==": Equal, - "!=": NotEqual, - ">": Greater, - "<": Less, - ">=": GreaterEqual, - "<=": LessEqual, - "+=": AddAssign, - "-=": SubAssign, - "*=": MulAssign, - "/=": DivAssign, - "%=": ModAssign, - "&=": AndAssign, - "|=": OrAssign, - "^=": XorAssign, - "<<=": ShlAssign, - ">>=": ShrAssign, - "λ": Call, - "@": Array, -} diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 23bf106..a6a34fc 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -35,7 +35,16 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: Operators[string(buffer[position:i])], Position: position, Length: Length(i - position)}) + kind := Invalid + + switch string(buffer[position:i]) { + case "/": + kind = Div + case "/=": + kind = DivAssign + } + + tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(i - position)}) } else { position := i @@ -120,7 +129,76 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: Operators[string(buffer[position:i])], Position: position, Length: Length(i - position)}) + kind := Invalid + + switch string(buffer[position:i]) { + case "!": + kind = Not + case "!=": + kind = NotEqual + case "%": + kind = Mod + case "%=": + kind = ModAssign + case "&": + kind = And + case "&&": + kind = LogicalAnd + case "&=": + kind = AndAssign + case "*": + kind = Mul + case "*=": + kind = MulAssign + case "+": + kind = Add + case "+=": + kind = AddAssign + case "-": + kind = Sub + case "-=": + kind = SubAssign + case ".": + kind = Period + case "/": + kind = Div + case "/=": + kind = DivAssign + case ":=": + kind = Define + case "<": + kind = Less + case "<<": + kind = Shl + case "<<=": + kind = ShlAssign + case "<=": + kind = LessEqual + case "=": + kind = Assign + case "==": + kind = Equal + case ">": + kind = Greater + case ">=": + kind = GreaterEqual + case ">>": + kind = Shr + case ">>=": + kind = ShrAssign + case "^": + kind = Xor + case "^=": + kind = XorAssign + case "|": + kind = Or + case "|=": + kind = OrAssign + case "||": + kind = LogicalOr + } + + tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(i - position)}) continue } From 70792acb12435a1a7d53daa058577cd387a4dd8a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 24 Jul 2024 12:14:51 +0200 Subject: [PATCH 0370/1012] Improved tokenizer performance --- src/build/token/Operators.go | 39 ----------------- src/build/token/Tokenize.go | 82 +++++++++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 41 deletions(-) delete mode 100644 src/build/token/Operators.go diff --git a/src/build/token/Operators.go b/src/build/token/Operators.go deleted file mode 100644 index 842fb94..0000000 --- a/src/build/token/Operators.go +++ /dev/null @@ -1,39 +0,0 @@ -package token - -// Operators is a map of all operators used in the language. -var Operators = map[string]Kind{ - ".": Period, - "=": Assign, - ":=": Define, - "+": Add, - "-": Sub, - "*": Mul, - "/": Div, - "%": Mod, - "&": And, - "|": Or, - "^": Xor, - "<<": Shl, - ">>": Shr, - "&&": LogicalAnd, - "||": LogicalOr, - "!": Not, - "==": Equal, - "!=": NotEqual, - ">": Greater, - "<": Less, - ">=": GreaterEqual, - "<=": LessEqual, - "+=": AddAssign, - "-=": SubAssign, - "*=": MulAssign, - "/=": DivAssign, - "%=": ModAssign, - "&=": AndAssign, - "|=": OrAssign, - "^=": XorAssign, - "<<=": ShlAssign, - ">>=": ShrAssign, - "λ": Call, - "@": Array, -} diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 23bf106..a6a34fc 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -35,7 +35,16 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: Operators[string(buffer[position:i])], Position: position, Length: Length(i - position)}) + kind := Invalid + + switch string(buffer[position:i]) { + case "/": + kind = Div + case "/=": + kind = DivAssign + } + + tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(i - position)}) } else { position := i @@ -120,7 +129,76 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: Operators[string(buffer[position:i])], Position: position, Length: Length(i - position)}) + kind := Invalid + + switch string(buffer[position:i]) { + case "!": + kind = Not + case "!=": + kind = NotEqual + case "%": + kind = Mod + case "%=": + kind = ModAssign + case "&": + kind = And + case "&&": + kind = LogicalAnd + case "&=": + kind = AndAssign + case "*": + kind = Mul + case "*=": + kind = MulAssign + case "+": + kind = Add + case "+=": + kind = AddAssign + case "-": + kind = Sub + case "-=": + kind = SubAssign + case ".": + kind = Period + case "/": + kind = Div + case "/=": + kind = DivAssign + case ":=": + kind = Define + case "<": + kind = Less + case "<<": + kind = Shl + case "<<=": + kind = ShlAssign + case "<=": + kind = LessEqual + case "=": + kind = Assign + case "==": + kind = Equal + case ">": + kind = Greater + case ">=": + kind = GreaterEqual + case ">>": + kind = Shr + case ">>=": + kind = ShrAssign + case "^": + kind = Xor + case "^=": + kind = XorAssign + case "|": + kind = Or + case "|=": + kind = OrAssign + case "||": + kind = LogicalOr + } + + tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(i - position)}) continue } From fac8dba7eef287ee33ad84605b02a06ea8d1ba0f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 24 Jul 2024 19:39:13 +0200 Subject: [PATCH 0371/1012] Added a few todo points --- README.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e4b8a45..3e1a870 100644 --- a/README.md +++ b/README.md @@ -96,10 +96,84 @@ This is what generates the AST from tokens. This is what generates expressions from tokens. +## Todo + +### Compiler + +- [x] Tokenizer +- [x] Scanner +- [x] Functions +- [x] Variables +- [x] Error messages +- [x] Expression parser +- [x] Function calls +- [x] Parallel compilation +- [x] Syscalls +- [x] Variable lifetimes +- [x] Branches +- [x] Loops +- [ ] Data structures +- [ ] Type system +- [ ] Type operator: `|` (`User | Error`) +- [ ] Hexadecimal, octal and binary literals +- [ ] Error handling +- [ ] Multiple return values +- [ ] Threading library +- [ ] Self-hosted compiler + +### Keywords + +- [ ] `for` +- [x] `if` +- [x] `import` +- [x] `loop` +- [x] `return` +- [ ] `switch` + +### Optimizations + +- [x] Exclude unused functions +- [ ] Expression folding +- [ ] Function call inlining +- [ ] Loop unrolls + +### Linter + +- [x] Unused variables +- [x] Unused parameters +- [ ] Unused imports +- [ ] Unnecessary newlines +- [ ] Ineffective assignments + +### Operators + +- [x] `=` +- [x] `+`, `-`, `*`, `/` +- [x] `+=`, `-=`, `*=`, `/=` +- [x] `==`, `!=`, `<`, `<=`, `>`, `>=` +- [x] `&&`, `||` +- [ ] `%` +- [ ] `<<`, `>>` +- [ ] `<<=`, `>>=` +- [ ] `&`, `|`, `^` +- [ ] `&=`, `|=`, `^=` + +### Architecture + +- [ ] arm64 +- [ ] wasm +- [x] x86-64 + +### Platform + +- [x] Linux +- [ ] Mac +- [ ] Windows + ## Tests ```shell -go test ./... -v +go test ./... -v -cover ``` ## Benchmarks From 65cc81032b625a878c4e4d5b951dcc53cbbc9a7e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 24 Jul 2024 19:39:13 +0200 Subject: [PATCH 0372/1012] Added a few todo points --- README.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e4b8a45..3e1a870 100644 --- a/README.md +++ b/README.md @@ -96,10 +96,84 @@ This is what generates the AST from tokens. This is what generates expressions from tokens. +## Todo + +### Compiler + +- [x] Tokenizer +- [x] Scanner +- [x] Functions +- [x] Variables +- [x] Error messages +- [x] Expression parser +- [x] Function calls +- [x] Parallel compilation +- [x] Syscalls +- [x] Variable lifetimes +- [x] Branches +- [x] Loops +- [ ] Data structures +- [ ] Type system +- [ ] Type operator: `|` (`User | Error`) +- [ ] Hexadecimal, octal and binary literals +- [ ] Error handling +- [ ] Multiple return values +- [ ] Threading library +- [ ] Self-hosted compiler + +### Keywords + +- [ ] `for` +- [x] `if` +- [x] `import` +- [x] `loop` +- [x] `return` +- [ ] `switch` + +### Optimizations + +- [x] Exclude unused functions +- [ ] Expression folding +- [ ] Function call inlining +- [ ] Loop unrolls + +### Linter + +- [x] Unused variables +- [x] Unused parameters +- [ ] Unused imports +- [ ] Unnecessary newlines +- [ ] Ineffective assignments + +### Operators + +- [x] `=` +- [x] `+`, `-`, `*`, `/` +- [x] `+=`, `-=`, `*=`, `/=` +- [x] `==`, `!=`, `<`, `<=`, `>`, `>=` +- [x] `&&`, `||` +- [ ] `%` +- [ ] `<<`, `>>` +- [ ] `<<=`, `>>=` +- [ ] `&`, `|`, `^` +- [ ] `&=`, `|=`, `^=` + +### Architecture + +- [ ] arm64 +- [ ] wasm +- [x] x86-64 + +### Platform + +- [x] Linux +- [ ] Mac +- [ ] Windows + ## Tests ```shell -go test ./... -v +go test ./... -v -cover ``` ## Benchmarks From a31731a84dd8da34ad95dc1026dacf0ded955941 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 25 Jul 2024 12:11:55 +0200 Subject: [PATCH 0373/1012] Implemented modulo operation --- src/build/asm/Finalize.go | 3 ++ src/build/asm/Mnemonic.go | 3 ++ src/build/asm/divide.go | 25 +++++++++++++++ src/build/core/ExecuteRegisterNumber.go | 3 ++ src/build/core/ExecuteRegisterRegister.go | 3 ++ tests/programs/remainder.q | 37 +++++++++++++++++++++++ tests/programs_test.go | 1 + 7 files changed, 75 insertions(+) create mode 100644 tests/programs/remainder.q diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index a34920a..0d68d91 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -119,6 +119,9 @@ func (a Assembler) Finalize() ([]byte, []byte) { case LABEL: labels[x.Data.(*Label).Name] = Address(len(code)) + case MODULO: + code = modulo(code, x.Data) + case MOVE: switch operands := x.Data.(type) { case *RegisterNumber: diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index a1967a7..223666a 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -19,6 +19,7 @@ const ( MUL LABEL LOAD + MODULO MOVE POP PUSH @@ -59,6 +60,8 @@ func (m Mnemonic) String() string { return "label" case LOAD: return "load" + case MODULO: + return "mod" case MOVE: return "move" case MUL: diff --git a/src/build/asm/divide.go b/src/build/asm/divide.go index 09a879b..e795c63 100644 --- a/src/build/asm/divide.go +++ b/src/build/asm/divide.go @@ -41,3 +41,28 @@ func divide(code []byte, data any) []byte { code = x64.PopRegister(code, x64.RDX) return code } + +// modulo calculates the division remainder on x64 machines. +func modulo(code []byte, data any) []byte { + code = x64.PushRegister(code, x64.RDX) + code = x64.PushRegister(code, x64.RAX) + + switch operands := data.(type) { + case *RegisterNumber: + code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) + code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Register) + code = x64.MoveRegisterRegister64(code, operands.Register, x64.RDX) + + case *RegisterRegister: + code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Source) + code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RDX) + } + + code = x64.PopRegister(code, x64.RAX) + code = x64.PopRegister(code, x64.RDX) + return code +} diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index 43675a2..d870264 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -22,6 +22,9 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg case token.Div, token.DivAssign: f.RegisterNumber(asm.DIV, register, number) + case token.Mod, token.ModAssign: + f.RegisterNumber(asm.MODULO, register, number) + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: f.RegisterNumber(asm.COMPARE, register, number) diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index 8206b4f..97ee94b 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -22,6 +22,9 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cp case token.Div, token.DivAssign: f.RegisterRegister(asm.DIV, destination, source) + case token.Mod, token.ModAssign: + f.RegisterRegister(asm.MODULO, destination, source) + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: f.RegisterRegister(asm.COMPARE, destination, source) diff --git a/tests/programs/remainder.q b/tests/programs/remainder.q new file mode 100644 index 0000000..392c6c8 --- /dev/null +++ b/tests/programs/remainder.q @@ -0,0 +1,37 @@ +import sys + +main() { + if 0 % 1 != 0 { + sys.exit(1) + } + + if 1 % 1 != 0 { + sys.exit(1) + } + + if 2 % 1 != 0 { + sys.exit(1) + } + + if 0 % 2 != 0 { + sys.exit(1) + } + + if 1 % 2 != 1 { + sys.exit(1) + } + + if 2 % 2 != 0 { + sys.exit(1) + } + + if 3 % 2 != 1 { + sys.exit(1) + } + + if 256 % 10 != 6 { + sys.exit(1) + } + + sys.exit(0) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 5d4e5ff..08a1e12 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -35,6 +35,7 @@ var programs = []struct { {"jump-near", "", "", 0}, {"loop", "", "", 0}, {"loop-lifetime", "", "", 0}, + {"remainder", "", "", 0}, } func TestPrograms(t *testing.T) { From 60b8ff130836c6567c6315253bd9a2f8e402eb26 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 25 Jul 2024 12:11:55 +0200 Subject: [PATCH 0374/1012] Implemented modulo operation --- src/build/asm/Finalize.go | 3 ++ src/build/asm/Mnemonic.go | 3 ++ src/build/asm/divide.go | 25 +++++++++++++++ src/build/core/ExecuteRegisterNumber.go | 3 ++ src/build/core/ExecuteRegisterRegister.go | 3 ++ tests/programs/remainder.q | 37 +++++++++++++++++++++++ tests/programs_test.go | 1 + 7 files changed, 75 insertions(+) create mode 100644 tests/programs/remainder.q diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index a34920a..0d68d91 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -119,6 +119,9 @@ func (a Assembler) Finalize() ([]byte, []byte) { case LABEL: labels[x.Data.(*Label).Name] = Address(len(code)) + case MODULO: + code = modulo(code, x.Data) + case MOVE: switch operands := x.Data.(type) { case *RegisterNumber: diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index a1967a7..223666a 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -19,6 +19,7 @@ const ( MUL LABEL LOAD + MODULO MOVE POP PUSH @@ -59,6 +60,8 @@ func (m Mnemonic) String() string { return "label" case LOAD: return "load" + case MODULO: + return "mod" case MOVE: return "move" case MUL: diff --git a/src/build/asm/divide.go b/src/build/asm/divide.go index 09a879b..e795c63 100644 --- a/src/build/asm/divide.go +++ b/src/build/asm/divide.go @@ -41,3 +41,28 @@ func divide(code []byte, data any) []byte { code = x64.PopRegister(code, x64.RDX) return code } + +// modulo calculates the division remainder on x64 machines. +func modulo(code []byte, data any) []byte { + code = x64.PushRegister(code, x64.RDX) + code = x64.PushRegister(code, x64.RAX) + + switch operands := data.(type) { + case *RegisterNumber: + code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) + code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Register) + code = x64.MoveRegisterRegister64(code, operands.Register, x64.RDX) + + case *RegisterRegister: + code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Source) + code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RDX) + } + + code = x64.PopRegister(code, x64.RAX) + code = x64.PopRegister(code, x64.RDX) + return code +} diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index 43675a2..d870264 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -22,6 +22,9 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg case token.Div, token.DivAssign: f.RegisterNumber(asm.DIV, register, number) + case token.Mod, token.ModAssign: + f.RegisterNumber(asm.MODULO, register, number) + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: f.RegisterNumber(asm.COMPARE, register, number) diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index 8206b4f..97ee94b 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -22,6 +22,9 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cp case token.Div, token.DivAssign: f.RegisterRegister(asm.DIV, destination, source) + case token.Mod, token.ModAssign: + f.RegisterRegister(asm.MODULO, destination, source) + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: f.RegisterRegister(asm.COMPARE, destination, source) diff --git a/tests/programs/remainder.q b/tests/programs/remainder.q new file mode 100644 index 0000000..392c6c8 --- /dev/null +++ b/tests/programs/remainder.q @@ -0,0 +1,37 @@ +import sys + +main() { + if 0 % 1 != 0 { + sys.exit(1) + } + + if 1 % 1 != 0 { + sys.exit(1) + } + + if 2 % 1 != 0 { + sys.exit(1) + } + + if 0 % 2 != 0 { + sys.exit(1) + } + + if 1 % 2 != 1 { + sys.exit(1) + } + + if 2 % 2 != 0 { + sys.exit(1) + } + + if 3 % 2 != 1 { + sys.exit(1) + } + + if 256 % 10 != 6 { + sys.exit(1) + } + + sys.exit(0) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 5d4e5ff..08a1e12 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -35,6 +35,7 @@ var programs = []struct { {"jump-near", "", "", 0}, {"loop", "", "", 0}, {"loop-lifetime", "", "", 0}, + {"remainder", "", "", 0}, } func TestPrograms(t *testing.T) { From f68c50247f9a6424e05235d72d365d422a19a707 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 25 Jul 2024 12:21:10 +0200 Subject: [PATCH 0375/1012] Improved modulo tests --- README.md | 15 +++++++-------- tests/programs/remainder.q | 11 ++++++++++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3e1a870..89f7fe0 100644 --- a/README.md +++ b/README.md @@ -147,16 +147,15 @@ This is what generates expressions from tokens. ### Operators -- [x] `=` -- [x] `+`, `-`, `*`, `/` -- [x] `+=`, `-=`, `*=`, `/=` -- [x] `==`, `!=`, `<`, `<=`, `>`, `>=` -- [x] `&&`, `||` -- [ ] `%` -- [ ] `<<`, `>>` -- [ ] `<<=`, `>>=` +- [x] `=`, `:=` +- [x] `+`, `-`, `*`, `/`, `%` +- [x] `+=`, `-=`, `*=`, `/=`, `%=` - [ ] `&`, `|`, `^` - [ ] `&=`, `|=`, `^=` +- [ ] `<<`, `>>` +- [ ] `<<=`, `>>=` +- [x] `==`, `!=`, `<`, `<=`, `>`, `>=` +- [x] `&&`, `||` ### Architecture diff --git a/tests/programs/remainder.q b/tests/programs/remainder.q index 392c6c8..2bbe4f5 100644 --- a/tests/programs/remainder.q +++ b/tests/programs/remainder.q @@ -29,7 +29,16 @@ main() { sys.exit(1) } - if 256 % 10 != 6 { + x := 256 + x %= 100 + + if x != 56 { + sys.exit(1) + } + + x %= 10 + + if x != 6 { sys.exit(1) } From b8900b518ab22c82e8aacaa2262ccf921883a802 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 25 Jul 2024 12:21:10 +0200 Subject: [PATCH 0376/1012] Improved modulo tests --- README.md | 15 +++++++-------- tests/programs/remainder.q | 11 ++++++++++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3e1a870..89f7fe0 100644 --- a/README.md +++ b/README.md @@ -147,16 +147,15 @@ This is what generates expressions from tokens. ### Operators -- [x] `=` -- [x] `+`, `-`, `*`, `/` -- [x] `+=`, `-=`, `*=`, `/=` -- [x] `==`, `!=`, `<`, `<=`, `>`, `>=` -- [x] `&&`, `||` -- [ ] `%` -- [ ] `<<`, `>>` -- [ ] `<<=`, `>>=` +- [x] `=`, `:=` +- [x] `+`, `-`, `*`, `/`, `%` +- [x] `+=`, `-=`, `*=`, `/=`, `%=` - [ ] `&`, `|`, `^` - [ ] `&=`, `|=`, `^=` +- [ ] `<<`, `>>` +- [ ] `<<=`, `>>=` +- [x] `==`, `!=`, `<`, `<=`, `>`, `>=` +- [x] `&&`, `||` ### Architecture diff --git a/tests/programs/remainder.q b/tests/programs/remainder.q index 392c6c8..2bbe4f5 100644 --- a/tests/programs/remainder.q +++ b/tests/programs/remainder.q @@ -29,7 +29,16 @@ main() { sys.exit(1) } - if 256 % 10 != 6 { + x := 256 + x %= 100 + + if x != 56 { + sys.exit(1) + } + + x %= 10 + + if x != 6 { sys.exit(1) } From a7d8d6ae20921e4f7c12ad931a33bc9d9234fbfe Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 25 Jul 2024 14:17:51 +0200 Subject: [PATCH 0377/1012] Implemented bitwise operations --- README.md | 8 +-- src/build/arch/x64/And.go | 15 ++++++ src/build/arch/x64/Or.go | 15 ++++++ src/build/arch/x64/Xor.go | 15 ++++++ src/build/asm/Finalize.go | 24 +++++++++ src/build/asm/Mnemonic.go | 9 ++++ src/build/core/ExecuteRegisterNumber.go | 9 ++++ src/build/core/ExecuteRegisterRegister.go | 9 ++++ tests/programs/bitwise-and.q | 61 +++++++++++++++++++++++ tests/programs/bitwise-or.q | 61 +++++++++++++++++++++++ tests/programs/bitwise-xor.q | 61 +++++++++++++++++++++++ tests/programs_test.go | 5 +- 12 files changed, 287 insertions(+), 5 deletions(-) create mode 100644 src/build/arch/x64/And.go create mode 100644 src/build/arch/x64/Or.go create mode 100644 src/build/arch/x64/Xor.go create mode 100644 tests/programs/bitwise-and.q create mode 100644 tests/programs/bitwise-or.q create mode 100644 tests/programs/bitwise-xor.q diff --git a/README.md b/README.md index 89f7fe0..8fcdcac 100644 --- a/README.md +++ b/README.md @@ -150,12 +150,12 @@ This is what generates expressions from tokens. - [x] `=`, `:=` - [x] `+`, `-`, `*`, `/`, `%` - [x] `+=`, `-=`, `*=`, `/=`, `%=` -- [ ] `&`, `|`, `^` -- [ ] `&=`, `|=`, `^=` -- [ ] `<<`, `>>` -- [ ] `<<=`, `>>=` +- [x] `&`, `|`, `^` +- [x] `&=`, `|=`, `^=` - [x] `==`, `!=`, `<`, `<=`, `>`, `>=` - [x] `&&`, `||` +- [ ] `<<`, `>>` +- [ ] `<<=`, `>>=` ### Architecture diff --git a/src/build/arch/x64/And.go b/src/build/arch/x64/And.go new file mode 100644 index 0000000..e5d8fcb --- /dev/null +++ b/src/build/arch/x64/And.go @@ -0,0 +1,15 @@ +package x64 + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// AndRegisterNumber performs a bitwise AND using a register and a number. +func AndRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, 0b100, destination, number, 0x83, 0x81) +} + +// AndRegisterRegister performs a bitwise AND using two registers. +func AndRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, operand, destination, 8, 0x21) +} diff --git a/src/build/arch/x64/Or.go b/src/build/arch/x64/Or.go new file mode 100644 index 0000000..2252925 --- /dev/null +++ b/src/build/arch/x64/Or.go @@ -0,0 +1,15 @@ +package x64 + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// OrRegisterNumber performs a bitwise OR using a register and a number. +func OrRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, 0b001, destination, number, 0x83, 0x81) +} + +// OrRegisterRegister performs a bitwise OR using two registers. +func OrRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, operand, destination, 8, 0x09) +} diff --git a/src/build/arch/x64/Xor.go b/src/build/arch/x64/Xor.go new file mode 100644 index 0000000..305dce2 --- /dev/null +++ b/src/build/arch/x64/Xor.go @@ -0,0 +1,15 @@ +package x64 + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// XorRegisterNumber performs a bitwise XOR using a register and a number. +func XorRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, 0b110, destination, number, 0x83, 0x81) +} + +// XorRegisterRegister performs a bitwise XOR using two registers. +func XorRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, operand, destination, 8, 0x31) +} diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index 0d68d91..c7bdc94 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -26,6 +26,14 @@ func (a Assembler) Finalize() ([]byte, []byte) { code = x64.AddRegisterRegister(code, operands.Destination, operands.Source) } + case AND: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.AndRegisterNumber(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.AndRegisterRegister(code, operands.Destination, operands.Source) + } + case SUB: switch operands := x.Data.(type) { case *RegisterNumber: @@ -153,6 +161,14 @@ func (a Assembler) Finalize() ([]byte, []byte) { }) } + case OR: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.OrRegisterNumber(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.OrRegisterRegister(code, operands.Destination, operands.Source) + } + case POP: switch operands := x.Data.(type) { case *Register: @@ -177,6 +193,14 @@ func (a Assembler) Finalize() ([]byte, []byte) { case SYSCALL: code = x64.Syscall(code) + case XOR: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.XorRegisterNumber(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.XorRegisterRegister(code, operands.Destination, operands.Source) + } + default: panic("unknown mnemonic: " + x.Mnemonic.String()) } diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 223666a..7ed32ca 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -5,6 +5,7 @@ type Mnemonic uint8 const ( NONE Mnemonic = iota ADD + AND CALL COMMENT COMPARE @@ -21,12 +22,14 @@ const ( LOAD MODULO MOVE + OR POP PUSH RETURN STORE SUB SYSCALL + XOR ) // String returns a human readable version. @@ -34,6 +37,8 @@ func (m Mnemonic) String() string { switch m { case ADD: return "add" + case AND: + return "and" case CALL: return "call" case COMMENT: @@ -66,6 +71,8 @@ func (m Mnemonic) String() string { return "move" case MUL: return "mul" + case OR: + return "or" case POP: return "pop" case PUSH: @@ -78,6 +85,8 @@ func (m Mnemonic) String() string { return "store" case SYSCALL: return "syscall" + case XOR: + return "xor" default: return "" } diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index d870264..6f29527 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -25,6 +25,15 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg case token.Mod, token.ModAssign: f.RegisterNumber(asm.MODULO, register, number) + case token.And, token.AndAssign: + f.RegisterNumber(asm.AND, register, number) + + case token.Or, token.OrAssign: + f.RegisterNumber(asm.OR, register, number) + + case token.Xor, token.XorAssign: + f.RegisterNumber(asm.XOR, register, number) + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: f.RegisterNumber(asm.COMPARE, register, number) diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index 97ee94b..bea03e9 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -25,6 +25,15 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cp case token.Mod, token.ModAssign: f.RegisterRegister(asm.MODULO, destination, source) + case token.And, token.AndAssign: + f.RegisterRegister(asm.AND, destination, source) + + case token.Or, token.OrAssign: + f.RegisterRegister(asm.OR, destination, source) + + case token.Xor, token.XorAssign: + f.RegisterRegister(asm.XOR, destination, source) + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: f.RegisterRegister(asm.COMPARE, destination, source) diff --git a/tests/programs/bitwise-and.q b/tests/programs/bitwise-and.q new file mode 100644 index 0000000..fda2dcc --- /dev/null +++ b/tests/programs/bitwise-and.q @@ -0,0 +1,61 @@ +import sys + +main() { + if 0 & 0 != 0 { + sys.exit(1) + } + + if 0 & 1 != 0 { + sys.exit(2) + } + + if 1 & 0 != 0 { + sys.exit(3) + } + + if 1 & 1 != 1 { + sys.exit(4) + } + + if 1 & 2 != 0 { + sys.exit(5) + } + + if 1 & 3 != 1 { + sys.exit(6) + } + + if 2 & 0 != 0 { + sys.exit(7) + } + + if 2 & 1 != 0 { + sys.exit(8) + } + + if 2 & 2 != 2 { + sys.exit(9) + } + + if 2 & 3 != 2 { + sys.exit(10) + } + + if 3 & 0 != 0 { + sys.exit(11) + } + + if 3 & 1 != 1 { + sys.exit(12) + } + + if 3 & 2 != 2 { + sys.exit(13) + } + + if 3 & 3 != 3 { + sys.exit(14) + } + + sys.exit(0) +} \ No newline at end of file diff --git a/tests/programs/bitwise-or.q b/tests/programs/bitwise-or.q new file mode 100644 index 0000000..6ea338f --- /dev/null +++ b/tests/programs/bitwise-or.q @@ -0,0 +1,61 @@ +import sys + +main() { + if 0 | 0 != 0 { + sys.exit(1) + } + + if 0 | 1 != 1 { + sys.exit(2) + } + + if 1 | 0 != 1 { + sys.exit(3) + } + + if 1 | 1 != 1 { + sys.exit(4) + } + + if 1 | 2 != 3 { + sys.exit(5) + } + + if 1 | 3 != 3 { + sys.exit(6) + } + + if 2 | 0 != 2 { + sys.exit(7) + } + + if 2 | 1 != 3 { + sys.exit(8) + } + + if 2 | 2 != 2 { + sys.exit(9) + } + + if 2 | 3 != 3 { + sys.exit(10) + } + + if 3 | 0 != 3 { + sys.exit(11) + } + + if 3 | 1 != 3 { + sys.exit(12) + } + + if 3 | 2 != 3 { + sys.exit(13) + } + + if 3 | 3 != 3 { + sys.exit(14) + } + + sys.exit(0) +} \ No newline at end of file diff --git a/tests/programs/bitwise-xor.q b/tests/programs/bitwise-xor.q new file mode 100644 index 0000000..f648051 --- /dev/null +++ b/tests/programs/bitwise-xor.q @@ -0,0 +1,61 @@ +import sys + +main() { + if 0 ^ 0 != 0 { + sys.exit(1) + } + + if 0 ^ 1 != 1 { + sys.exit(2) + } + + if 1 ^ 0 != 1 { + sys.exit(3) + } + + if 1 ^ 1 != 0 { + sys.exit(4) + } + + if 1 ^ 2 != 3 { + sys.exit(5) + } + + if 1 ^ 3 != 2 { + sys.exit(6) + } + + if 2 ^ 0 != 2 { + sys.exit(7) + } + + if 2 ^ 1 != 3 { + sys.exit(8) + } + + if 2 ^ 2 != 0 { + sys.exit(9) + } + + if 2 ^ 3 != 1 { + sys.exit(10) + } + + if 3 ^ 0 != 3 { + sys.exit(11) + } + + if 3 ^ 1 != 2 { + sys.exit(12) + } + + if 3 ^ 2 != 1 { + sys.exit(13) + } + + if 3 ^ 3 != 0 { + sys.exit(14) + } + + sys.exit(0) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 08a1e12..6eac176 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -32,10 +32,13 @@ var programs = []struct { {"branch-and", "", "", 0}, {"branch-or", "", "", 0}, {"branch-both", "", "", 0}, + {"bitwise-and", "", "", 0}, + {"bitwise-or", "", "", 0}, + {"bitwise-xor", "", "", 0}, + {"remainder", "", "", 0}, {"jump-near", "", "", 0}, {"loop", "", "", 0}, {"loop-lifetime", "", "", 0}, - {"remainder", "", "", 0}, } func TestPrograms(t *testing.T) { From d1a8f0d66cabf4ebfd2d642df08f466f95b7b80d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 25 Jul 2024 14:17:51 +0200 Subject: [PATCH 0378/1012] Implemented bitwise operations --- README.md | 8 +-- src/build/arch/x64/And.go | 15 ++++++ src/build/arch/x64/Or.go | 15 ++++++ src/build/arch/x64/Xor.go | 15 ++++++ src/build/asm/Finalize.go | 24 +++++++++ src/build/asm/Mnemonic.go | 9 ++++ src/build/core/ExecuteRegisterNumber.go | 9 ++++ src/build/core/ExecuteRegisterRegister.go | 9 ++++ tests/programs/bitwise-and.q | 61 +++++++++++++++++++++++ tests/programs/bitwise-or.q | 61 +++++++++++++++++++++++ tests/programs/bitwise-xor.q | 61 +++++++++++++++++++++++ tests/programs_test.go | 5 +- 12 files changed, 287 insertions(+), 5 deletions(-) create mode 100644 src/build/arch/x64/And.go create mode 100644 src/build/arch/x64/Or.go create mode 100644 src/build/arch/x64/Xor.go create mode 100644 tests/programs/bitwise-and.q create mode 100644 tests/programs/bitwise-or.q create mode 100644 tests/programs/bitwise-xor.q diff --git a/README.md b/README.md index 89f7fe0..8fcdcac 100644 --- a/README.md +++ b/README.md @@ -150,12 +150,12 @@ This is what generates expressions from tokens. - [x] `=`, `:=` - [x] `+`, `-`, `*`, `/`, `%` - [x] `+=`, `-=`, `*=`, `/=`, `%=` -- [ ] `&`, `|`, `^` -- [ ] `&=`, `|=`, `^=` -- [ ] `<<`, `>>` -- [ ] `<<=`, `>>=` +- [x] `&`, `|`, `^` +- [x] `&=`, `|=`, `^=` - [x] `==`, `!=`, `<`, `<=`, `>`, `>=` - [x] `&&`, `||` +- [ ] `<<`, `>>` +- [ ] `<<=`, `>>=` ### Architecture diff --git a/src/build/arch/x64/And.go b/src/build/arch/x64/And.go new file mode 100644 index 0000000..e5d8fcb --- /dev/null +++ b/src/build/arch/x64/And.go @@ -0,0 +1,15 @@ +package x64 + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// AndRegisterNumber performs a bitwise AND using a register and a number. +func AndRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, 0b100, destination, number, 0x83, 0x81) +} + +// AndRegisterRegister performs a bitwise AND using two registers. +func AndRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, operand, destination, 8, 0x21) +} diff --git a/src/build/arch/x64/Or.go b/src/build/arch/x64/Or.go new file mode 100644 index 0000000..2252925 --- /dev/null +++ b/src/build/arch/x64/Or.go @@ -0,0 +1,15 @@ +package x64 + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// OrRegisterNumber performs a bitwise OR using a register and a number. +func OrRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, 0b001, destination, number, 0x83, 0x81) +} + +// OrRegisterRegister performs a bitwise OR using two registers. +func OrRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, operand, destination, 8, 0x09) +} diff --git a/src/build/arch/x64/Xor.go b/src/build/arch/x64/Xor.go new file mode 100644 index 0000000..305dce2 --- /dev/null +++ b/src/build/arch/x64/Xor.go @@ -0,0 +1,15 @@ +package x64 + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// XorRegisterNumber performs a bitwise XOR using a register and a number. +func XorRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, 0b110, destination, number, 0x83, 0x81) +} + +// XorRegisterRegister performs a bitwise XOR using two registers. +func XorRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, operand, destination, 8, 0x31) +} diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index 0d68d91..c7bdc94 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -26,6 +26,14 @@ func (a Assembler) Finalize() ([]byte, []byte) { code = x64.AddRegisterRegister(code, operands.Destination, operands.Source) } + case AND: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.AndRegisterNumber(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.AndRegisterRegister(code, operands.Destination, operands.Source) + } + case SUB: switch operands := x.Data.(type) { case *RegisterNumber: @@ -153,6 +161,14 @@ func (a Assembler) Finalize() ([]byte, []byte) { }) } + case OR: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.OrRegisterNumber(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.OrRegisterRegister(code, operands.Destination, operands.Source) + } + case POP: switch operands := x.Data.(type) { case *Register: @@ -177,6 +193,14 @@ func (a Assembler) Finalize() ([]byte, []byte) { case SYSCALL: code = x64.Syscall(code) + case XOR: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.XorRegisterNumber(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.XorRegisterRegister(code, operands.Destination, operands.Source) + } + default: panic("unknown mnemonic: " + x.Mnemonic.String()) } diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 223666a..7ed32ca 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -5,6 +5,7 @@ type Mnemonic uint8 const ( NONE Mnemonic = iota ADD + AND CALL COMMENT COMPARE @@ -21,12 +22,14 @@ const ( LOAD MODULO MOVE + OR POP PUSH RETURN STORE SUB SYSCALL + XOR ) // String returns a human readable version. @@ -34,6 +37,8 @@ func (m Mnemonic) String() string { switch m { case ADD: return "add" + case AND: + return "and" case CALL: return "call" case COMMENT: @@ -66,6 +71,8 @@ func (m Mnemonic) String() string { return "move" case MUL: return "mul" + case OR: + return "or" case POP: return "pop" case PUSH: @@ -78,6 +85,8 @@ func (m Mnemonic) String() string { return "store" case SYSCALL: return "syscall" + case XOR: + return "xor" default: return "" } diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index d870264..6f29527 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -25,6 +25,15 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg case token.Mod, token.ModAssign: f.RegisterNumber(asm.MODULO, register, number) + case token.And, token.AndAssign: + f.RegisterNumber(asm.AND, register, number) + + case token.Or, token.OrAssign: + f.RegisterNumber(asm.OR, register, number) + + case token.Xor, token.XorAssign: + f.RegisterNumber(asm.XOR, register, number) + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: f.RegisterNumber(asm.COMPARE, register, number) diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index 97ee94b..bea03e9 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -25,6 +25,15 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cp case token.Mod, token.ModAssign: f.RegisterRegister(asm.MODULO, destination, source) + case token.And, token.AndAssign: + f.RegisterRegister(asm.AND, destination, source) + + case token.Or, token.OrAssign: + f.RegisterRegister(asm.OR, destination, source) + + case token.Xor, token.XorAssign: + f.RegisterRegister(asm.XOR, destination, source) + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: f.RegisterRegister(asm.COMPARE, destination, source) diff --git a/tests/programs/bitwise-and.q b/tests/programs/bitwise-and.q new file mode 100644 index 0000000..fda2dcc --- /dev/null +++ b/tests/programs/bitwise-and.q @@ -0,0 +1,61 @@ +import sys + +main() { + if 0 & 0 != 0 { + sys.exit(1) + } + + if 0 & 1 != 0 { + sys.exit(2) + } + + if 1 & 0 != 0 { + sys.exit(3) + } + + if 1 & 1 != 1 { + sys.exit(4) + } + + if 1 & 2 != 0 { + sys.exit(5) + } + + if 1 & 3 != 1 { + sys.exit(6) + } + + if 2 & 0 != 0 { + sys.exit(7) + } + + if 2 & 1 != 0 { + sys.exit(8) + } + + if 2 & 2 != 2 { + sys.exit(9) + } + + if 2 & 3 != 2 { + sys.exit(10) + } + + if 3 & 0 != 0 { + sys.exit(11) + } + + if 3 & 1 != 1 { + sys.exit(12) + } + + if 3 & 2 != 2 { + sys.exit(13) + } + + if 3 & 3 != 3 { + sys.exit(14) + } + + sys.exit(0) +} \ No newline at end of file diff --git a/tests/programs/bitwise-or.q b/tests/programs/bitwise-or.q new file mode 100644 index 0000000..6ea338f --- /dev/null +++ b/tests/programs/bitwise-or.q @@ -0,0 +1,61 @@ +import sys + +main() { + if 0 | 0 != 0 { + sys.exit(1) + } + + if 0 | 1 != 1 { + sys.exit(2) + } + + if 1 | 0 != 1 { + sys.exit(3) + } + + if 1 | 1 != 1 { + sys.exit(4) + } + + if 1 | 2 != 3 { + sys.exit(5) + } + + if 1 | 3 != 3 { + sys.exit(6) + } + + if 2 | 0 != 2 { + sys.exit(7) + } + + if 2 | 1 != 3 { + sys.exit(8) + } + + if 2 | 2 != 2 { + sys.exit(9) + } + + if 2 | 3 != 3 { + sys.exit(10) + } + + if 3 | 0 != 3 { + sys.exit(11) + } + + if 3 | 1 != 3 { + sys.exit(12) + } + + if 3 | 2 != 3 { + sys.exit(13) + } + + if 3 | 3 != 3 { + sys.exit(14) + } + + sys.exit(0) +} \ No newline at end of file diff --git a/tests/programs/bitwise-xor.q b/tests/programs/bitwise-xor.q new file mode 100644 index 0000000..f648051 --- /dev/null +++ b/tests/programs/bitwise-xor.q @@ -0,0 +1,61 @@ +import sys + +main() { + if 0 ^ 0 != 0 { + sys.exit(1) + } + + if 0 ^ 1 != 1 { + sys.exit(2) + } + + if 1 ^ 0 != 1 { + sys.exit(3) + } + + if 1 ^ 1 != 0 { + sys.exit(4) + } + + if 1 ^ 2 != 3 { + sys.exit(5) + } + + if 1 ^ 3 != 2 { + sys.exit(6) + } + + if 2 ^ 0 != 2 { + sys.exit(7) + } + + if 2 ^ 1 != 3 { + sys.exit(8) + } + + if 2 ^ 2 != 0 { + sys.exit(9) + } + + if 2 ^ 3 != 1 { + sys.exit(10) + } + + if 3 ^ 0 != 3 { + sys.exit(11) + } + + if 3 ^ 1 != 2 { + sys.exit(12) + } + + if 3 ^ 2 != 1 { + sys.exit(13) + } + + if 3 ^ 3 != 0 { + sys.exit(14) + } + + sys.exit(0) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 08a1e12..6eac176 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -32,10 +32,13 @@ var programs = []struct { {"branch-and", "", "", 0}, {"branch-or", "", "", 0}, {"branch-both", "", "", 0}, + {"bitwise-and", "", "", 0}, + {"bitwise-or", "", "", 0}, + {"bitwise-xor", "", "", 0}, + {"remainder", "", "", 0}, {"jump-near", "", "", 0}, {"loop", "", "", 0}, {"loop-lifetime", "", "", 0}, - {"remainder", "", "", 0}, } func TestPrograms(t *testing.T) { From f0a1b97f2dc145997b28e2be30c41bd2fee30fd0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 25 Jul 2024 15:47:19 +0200 Subject: [PATCH 0379/1012] Implemented bit shifts --- README.md | 4 +- src/build/arch/x64/Shift.go | 17 ++++++++ src/build/asm/Finalize.go | 12 ++++++ src/build/asm/Mnemonic.go | 6 +++ src/build/core/ExecuteRegisterNumber.go | 6 +++ tests/programs/shift.q | 57 +++++++++++++++++++++++++ tests/programs_test.go | 1 + 7 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 src/build/arch/x64/Shift.go create mode 100644 tests/programs/shift.q diff --git a/README.md b/README.md index 8fcdcac..988d4c8 100644 --- a/README.md +++ b/README.md @@ -152,10 +152,10 @@ This is what generates expressions from tokens. - [x] `+=`, `-=`, `*=`, `/=`, `%=` - [x] `&`, `|`, `^` - [x] `&=`, `|=`, `^=` +- [x] `<<`, `>>` +- [x] `<<=`, `>>=` - [x] `==`, `!=`, `<`, `<=`, `>`, `>=` - [x] `&&`, `||` -- [ ] `<<`, `>>` -- [ ] `<<=`, `>>=` ### Architecture diff --git a/src/build/arch/x64/Shift.go b/src/build/arch/x64/Shift.go new file mode 100644 index 0000000..2f88cb1 --- /dev/null +++ b/src/build/arch/x64/Shift.go @@ -0,0 +1,17 @@ +package x64 + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// ShiftLeftNumber shifts the register value by `bitCount` bits to the left. +func ShiftLeftNumber(code []byte, register cpu.Register, bitCount byte) []byte { + code = encode(code, AddressDirect, 0b100, register, 8, 0xC1) + return append(code, bitCount) +} + +// ShiftRightSignedNumber shifts the signed register value by `bitCount` bits to the right. +func ShiftRightSignedNumber(code []byte, register cpu.Register, bitCount byte) []byte { + code = encode(code, AddressDirect, 0b111, register, 8, 0xC1) + return append(code, bitCount) +} diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index c7bdc94..cdd5633 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -184,6 +184,18 @@ func (a Assembler) Finalize() ([]byte, []byte) { case RETURN: code = x64.Return(code) + case SHIFTL: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.ShiftLeftNumber(code, operands.Register, byte(operands.Number)&0b111111) + } + + case SHIFTRS: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.ShiftRightSignedNumber(code, operands.Register, byte(operands.Number)&0b111111) + } + case STORE: switch operands := x.Data.(type) { case *MemoryNumber: diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 7ed32ca..78070f8 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -26,6 +26,8 @@ const ( POP PUSH RETURN + SHIFTL + SHIFTRS STORE SUB SYSCALL @@ -79,6 +81,10 @@ func (m Mnemonic) String() string { return "push" case RETURN: return "return" + case SHIFTL: + return "shift l" + case SHIFTRS: + return "shift rs" case SUB: return "sub" case STORE: diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index 6f29527..e2e4171 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -34,6 +34,12 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg case token.Xor, token.XorAssign: f.RegisterNumber(asm.XOR, register, number) + case token.Shl, token.ShlAssign: + f.RegisterNumber(asm.SHIFTL, register, number) + + case token.Shr, token.ShrAssign: + f.RegisterNumber(asm.SHIFTRS, register, number) + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: f.RegisterNumber(asm.COMPARE, register, number) diff --git a/tests/programs/shift.q b/tests/programs/shift.q new file mode 100644 index 0000000..1fd1ddf --- /dev/null +++ b/tests/programs/shift.q @@ -0,0 +1,57 @@ +import sys + +main() { + if 0 << 0 != 0 { + sys.exit(1) + } + + if 0 >> 0 != 0 { + sys.exit(2) + } + + if 1 << 0 != 1 { + sys.exit(3) + } + + if 1 >> 0 != 1 { + sys.exit(4) + } + + if 1 >> 1 != 0 { + sys.exit(5) + } + + if 1 << 1 != 2 { + sys.exit(6) + } + + if 1 << 2 != 4 { + sys.exit(7) + } + + if 1 << 3 != 8 { + sys.exit(8) + } + + if 1 << 4 != 16 { + sys.exit(9) + } + + if 16 >> 1 != 8 { + sys.exit(10) + } + + if 16 >> 2 != 4 { + sys.exit(11) + } + + if 16 >> 3 != 2 { + sys.exit(12) + } + + if 16 >> 4 != 1 { + sys.exit(13) + } + + sys.exit(0) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 6eac176..8e8bf54 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -35,6 +35,7 @@ var programs = []struct { {"bitwise-and", "", "", 0}, {"bitwise-or", "", "", 0}, {"bitwise-xor", "", "", 0}, + {"shift", "", "", 0}, {"remainder", "", "", 0}, {"jump-near", "", "", 0}, {"loop", "", "", 0}, From f0bc5039eeb045e462cba73a3afff1373d00c2b7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 25 Jul 2024 15:47:19 +0200 Subject: [PATCH 0380/1012] Implemented bit shifts --- README.md | 4 +- src/build/arch/x64/Shift.go | 17 ++++++++ src/build/asm/Finalize.go | 12 ++++++ src/build/asm/Mnemonic.go | 6 +++ src/build/core/ExecuteRegisterNumber.go | 6 +++ tests/programs/shift.q | 57 +++++++++++++++++++++++++ tests/programs_test.go | 1 + 7 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 src/build/arch/x64/Shift.go create mode 100644 tests/programs/shift.q diff --git a/README.md b/README.md index 8fcdcac..988d4c8 100644 --- a/README.md +++ b/README.md @@ -152,10 +152,10 @@ This is what generates expressions from tokens. - [x] `+=`, `-=`, `*=`, `/=`, `%=` - [x] `&`, `|`, `^` - [x] `&=`, `|=`, `^=` +- [x] `<<`, `>>` +- [x] `<<=`, `>>=` - [x] `==`, `!=`, `<`, `<=`, `>`, `>=` - [x] `&&`, `||` -- [ ] `<<`, `>>` -- [ ] `<<=`, `>>=` ### Architecture diff --git a/src/build/arch/x64/Shift.go b/src/build/arch/x64/Shift.go new file mode 100644 index 0000000..2f88cb1 --- /dev/null +++ b/src/build/arch/x64/Shift.go @@ -0,0 +1,17 @@ +package x64 + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// ShiftLeftNumber shifts the register value by `bitCount` bits to the left. +func ShiftLeftNumber(code []byte, register cpu.Register, bitCount byte) []byte { + code = encode(code, AddressDirect, 0b100, register, 8, 0xC1) + return append(code, bitCount) +} + +// ShiftRightSignedNumber shifts the signed register value by `bitCount` bits to the right. +func ShiftRightSignedNumber(code []byte, register cpu.Register, bitCount byte) []byte { + code = encode(code, AddressDirect, 0b111, register, 8, 0xC1) + return append(code, bitCount) +} diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index c7bdc94..cdd5633 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -184,6 +184,18 @@ func (a Assembler) Finalize() ([]byte, []byte) { case RETURN: code = x64.Return(code) + case SHIFTL: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.ShiftLeftNumber(code, operands.Register, byte(operands.Number)&0b111111) + } + + case SHIFTRS: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.ShiftRightSignedNumber(code, operands.Register, byte(operands.Number)&0b111111) + } + case STORE: switch operands := x.Data.(type) { case *MemoryNumber: diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 7ed32ca..78070f8 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -26,6 +26,8 @@ const ( POP PUSH RETURN + SHIFTL + SHIFTRS STORE SUB SYSCALL @@ -79,6 +81,10 @@ func (m Mnemonic) String() string { return "push" case RETURN: return "return" + case SHIFTL: + return "shift l" + case SHIFTRS: + return "shift rs" case SUB: return "sub" case STORE: diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index 6f29527..e2e4171 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -34,6 +34,12 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg case token.Xor, token.XorAssign: f.RegisterNumber(asm.XOR, register, number) + case token.Shl, token.ShlAssign: + f.RegisterNumber(asm.SHIFTL, register, number) + + case token.Shr, token.ShrAssign: + f.RegisterNumber(asm.SHIFTRS, register, number) + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: f.RegisterNumber(asm.COMPARE, register, number) diff --git a/tests/programs/shift.q b/tests/programs/shift.q new file mode 100644 index 0000000..1fd1ddf --- /dev/null +++ b/tests/programs/shift.q @@ -0,0 +1,57 @@ +import sys + +main() { + if 0 << 0 != 0 { + sys.exit(1) + } + + if 0 >> 0 != 0 { + sys.exit(2) + } + + if 1 << 0 != 1 { + sys.exit(3) + } + + if 1 >> 0 != 1 { + sys.exit(4) + } + + if 1 >> 1 != 0 { + sys.exit(5) + } + + if 1 << 1 != 2 { + sys.exit(6) + } + + if 1 << 2 != 4 { + sys.exit(7) + } + + if 1 << 3 != 8 { + sys.exit(8) + } + + if 1 << 4 != 16 { + sys.exit(9) + } + + if 16 >> 1 != 8 { + sys.exit(10) + } + + if 16 >> 2 != 4 { + sys.exit(11) + } + + if 16 >> 3 != 2 { + sys.exit(12) + } + + if 16 >> 4 != 1 { + sys.exit(13) + } + + sys.exit(0) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 6eac176..8e8bf54 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -35,6 +35,7 @@ var programs = []struct { {"bitwise-and", "", "", 0}, {"bitwise-or", "", "", 0}, {"bitwise-xor", "", "", 0}, + {"shift", "", "", 0}, {"remainder", "", "", 0}, {"jump-near", "", "", 0}, {"loop", "", "", 0}, From e4f041be41c623e2167bece6dd4100ae51804f0b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 25 Jul 2024 16:47:25 +0200 Subject: [PATCH 0381/1012] Implemented assert keyword --- README.md | 1 + src/build/ast/Assert.go | 10 ++++++++++ src/build/ast/Parse.go | 9 +++++++++ src/build/compiler/Result.go | 7 ++++++- src/build/core/Compile.go | 4 ++++ src/build/core/CompileASTNode.go | 3 +++ src/build/core/CompileAssert.go | 29 +++++++++++++++++++++++++++++ src/build/core/Defer.go | 6 ++++++ src/build/core/Function.go | 2 ++ src/build/errors/CompileErrors.go | 1 + src/build/token/Kind.go | 1 + src/build/token/Tokenize.go | 2 ++ tests/programs/assert.q | 6 ++++++ tests/programs_test.go | 1 + 14 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/build/ast/Assert.go create mode 100644 src/build/core/CompileAssert.go create mode 100644 src/build/core/Defer.go create mode 100644 tests/programs/assert.q diff --git a/README.md b/README.md index 988d4c8..d636314 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ This is what generates expressions from tokens. ### Keywords +- [x] `assert` - [ ] `for` - [x] `if` - [x] `import` diff --git a/src/build/ast/Assert.go b/src/build/ast/Assert.go new file mode 100644 index 0000000..0f537db --- /dev/null +++ b/src/build/ast/Assert.go @@ -0,0 +1,10 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/build/expression" +) + +// Assert represents a condition that must be true, otherwise the program stops. +type Assert struct { + Condition *expression.Expression +} diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index 5610e1a..a7c0e0e 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -35,6 +35,15 @@ func toASTNode(tokens token.List, buffer []byte) (Node, error) { return &Return{Value: value}, nil } + if tokens[0].Kind == token.Assert { + if len(tokens) == 1 { + return nil, errors.New(errors.MissingExpression, nil, tokens[0].End()) + } + + condition := expression.Parse(tokens[1:]) + return &Assert{Condition: condition}, nil + } + if keywordHasBlock(tokens[0].Kind) { blockStart := tokens.IndexKind(token.BlockStart) blockEnd := tokens.LastIndexKind(token.BlockEnd) diff --git a/src/build/compiler/Result.go b/src/build/compiler/Result.go index 2762d93..ee7c8c1 100644 --- a/src/build/compiler/Result.go +++ b/src/build/compiler/Result.go @@ -26,7 +26,7 @@ func (r *Result) finalize() ([]byte, []byte) { // The reason we call `main` instead of using `main` itself is to place // a return address on the stack, which allows return statements in `main`. final := asm.Assembler{ - Instructions: make([]asm.Instruction, 0, r.InstructionCount+4), + Instructions: make([]asm.Instruction, 0, r.InstructionCount+8), Data: make(map[string][]byte, r.DataCount), } @@ -35,6 +35,11 @@ func (r *Result) finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0) final.Syscall() + final.Label(asm.LABEL, "_crash") + final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 1) + final.Syscall() + // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { diff --git a/src/build/core/Compile.go b/src/build/core/Compile.go index 41c079d..4898f69 100644 --- a/src/build/core/Compile.go +++ b/src/build/core/Compile.go @@ -5,4 +5,8 @@ func (f *Function) Compile() { f.AddLabel(f.Name) f.Err = f.CompileTokens(f.Body) f.Return() + + for _, call := range f.deferred { + call() + } } diff --git a/src/build/core/CompileASTNode.go b/src/build/core/CompileASTNode.go index 34ce620..16cb0bd 100644 --- a/src/build/core/CompileASTNode.go +++ b/src/build/core/CompileASTNode.go @@ -7,6 +7,9 @@ import ( // CompileASTNode compiles a node in the AST. func (f *Function) CompileASTNode(node ast.Node) error { switch node := node.(type) { + case *ast.Assert: + return f.CompileAssert(node) + case *ast.Assign: return f.CompileAssign(node) diff --git a/src/build/core/CompileAssert.go b/src/build/core/CompileAssert.go new file mode 100644 index 0000000..2715e44 --- /dev/null +++ b/src/build/core/CompileAssert.go @@ -0,0 +1,29 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" +) + +// CompileAssert compiles an assertion. +func (f *Function) CompileAssert(assert *ast.Assert) error { + f.count.assert++ + success := fmt.Sprintf("%s_assert_%d_true", f.Name, f.count.assert) + fail := fmt.Sprintf("%s_assert_%d_false", f.Name, f.count.assert) + err := f.CompileCondition(assert.Condition, success, fail) + + if err != nil { + return err + } + + f.AddLabel(success) + + f.Defer(func() { + f.AddLabel(fail) + f.Jump(asm.JUMP, "_crash") + }) + + return err +} diff --git a/src/build/core/Defer.go b/src/build/core/Defer.go new file mode 100644 index 0000000..0313a31 --- /dev/null +++ b/src/build/core/Defer.go @@ -0,0 +1,6 @@ +package core + +// Defer executes the callback at the end of function compilation. +func (f *Function) Defer(call func()) { + f.deferred = append(f.deferred, call) +} diff --git a/src/build/core/Function.go b/src/build/core/Function.go index f926956..29fe007 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -14,11 +14,13 @@ type Function struct { Body []token.Token Functions map[string]*Function Err error + deferred []func() count counter } // counter stores how often a certain statement appeared so we can generate a unique label from it. type counter struct { + assert int branch int data int loop int diff --git a/src/build/errors/CompileErrors.go b/src/build/errors/CompileErrors.go index 3aab709..5f65804 100644 --- a/src/build/errors/CompileErrors.go +++ b/src/build/errors/CompileErrors.go @@ -5,6 +5,7 @@ var ( InvalidExpression = &Base{"Invalid expression"} InvalidRune = &Base{"Invalid rune"} InvalidStatement = &Base{"Invalid statement"} + MissingExpression = &Base{"Missing expression"} MissingMainFunction = &Base{"Missing main function"} MissingOperand = &Base{"Missing operand"} NotImplemented = &Base{"Not implemented"} diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 53997c4..b9490db 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -20,6 +20,7 @@ const ( ArrayStart // [ ArrayEnd // ] _keywords // + Assert // assert If // if Import // import Loop // loop diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index a6a34fc..610a36f 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -95,6 +95,8 @@ func Tokenize(buffer []byte) List { kind := Identifier switch string(identifier) { + case "assert": + kind = Assert case "if": kind = If case "import": diff --git a/tests/programs/assert.q b/tests/programs/assert.q new file mode 100644 index 0000000..9c3123e --- /dev/null +++ b/tests/programs/assert.q @@ -0,0 +1,6 @@ +main() { + assert 1 != 0 + assert 1 == 0 || 1 != 0 + assert 1 != 0 && 2 != 0 + assert 1 == 0 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 8e8bf54..ee99875 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -40,6 +40,7 @@ var programs = []struct { {"jump-near", "", "", 0}, {"loop", "", "", 0}, {"loop-lifetime", "", "", 0}, + {"assert", "", "", 1}, } func TestPrograms(t *testing.T) { From f4dd9004becefab6ebb48f8852394d88ae1d4741 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 25 Jul 2024 16:47:25 +0200 Subject: [PATCH 0382/1012] Implemented assert keyword --- README.md | 1 + src/build/ast/Assert.go | 10 ++++++++++ src/build/ast/Parse.go | 9 +++++++++ src/build/compiler/Result.go | 7 ++++++- src/build/core/Compile.go | 4 ++++ src/build/core/CompileASTNode.go | 3 +++ src/build/core/CompileAssert.go | 29 +++++++++++++++++++++++++++++ src/build/core/Defer.go | 6 ++++++ src/build/core/Function.go | 2 ++ src/build/errors/CompileErrors.go | 1 + src/build/token/Kind.go | 1 + src/build/token/Tokenize.go | 2 ++ tests/programs/assert.q | 6 ++++++ tests/programs_test.go | 1 + 14 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/build/ast/Assert.go create mode 100644 src/build/core/CompileAssert.go create mode 100644 src/build/core/Defer.go create mode 100644 tests/programs/assert.q diff --git a/README.md b/README.md index 988d4c8..d636314 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ This is what generates expressions from tokens. ### Keywords +- [x] `assert` - [ ] `for` - [x] `if` - [x] `import` diff --git a/src/build/ast/Assert.go b/src/build/ast/Assert.go new file mode 100644 index 0000000..0f537db --- /dev/null +++ b/src/build/ast/Assert.go @@ -0,0 +1,10 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/build/expression" +) + +// Assert represents a condition that must be true, otherwise the program stops. +type Assert struct { + Condition *expression.Expression +} diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index 5610e1a..a7c0e0e 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -35,6 +35,15 @@ func toASTNode(tokens token.List, buffer []byte) (Node, error) { return &Return{Value: value}, nil } + if tokens[0].Kind == token.Assert { + if len(tokens) == 1 { + return nil, errors.New(errors.MissingExpression, nil, tokens[0].End()) + } + + condition := expression.Parse(tokens[1:]) + return &Assert{Condition: condition}, nil + } + if keywordHasBlock(tokens[0].Kind) { blockStart := tokens.IndexKind(token.BlockStart) blockEnd := tokens.LastIndexKind(token.BlockEnd) diff --git a/src/build/compiler/Result.go b/src/build/compiler/Result.go index 2762d93..ee7c8c1 100644 --- a/src/build/compiler/Result.go +++ b/src/build/compiler/Result.go @@ -26,7 +26,7 @@ func (r *Result) finalize() ([]byte, []byte) { // The reason we call `main` instead of using `main` itself is to place // a return address on the stack, which allows return statements in `main`. final := asm.Assembler{ - Instructions: make([]asm.Instruction, 0, r.InstructionCount+4), + Instructions: make([]asm.Instruction, 0, r.InstructionCount+8), Data: make(map[string][]byte, r.DataCount), } @@ -35,6 +35,11 @@ func (r *Result) finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0) final.Syscall() + final.Label(asm.LABEL, "_crash") + final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 1) + final.Syscall() + // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { diff --git a/src/build/core/Compile.go b/src/build/core/Compile.go index 41c079d..4898f69 100644 --- a/src/build/core/Compile.go +++ b/src/build/core/Compile.go @@ -5,4 +5,8 @@ func (f *Function) Compile() { f.AddLabel(f.Name) f.Err = f.CompileTokens(f.Body) f.Return() + + for _, call := range f.deferred { + call() + } } diff --git a/src/build/core/CompileASTNode.go b/src/build/core/CompileASTNode.go index 34ce620..16cb0bd 100644 --- a/src/build/core/CompileASTNode.go +++ b/src/build/core/CompileASTNode.go @@ -7,6 +7,9 @@ import ( // CompileASTNode compiles a node in the AST. func (f *Function) CompileASTNode(node ast.Node) error { switch node := node.(type) { + case *ast.Assert: + return f.CompileAssert(node) + case *ast.Assign: return f.CompileAssign(node) diff --git a/src/build/core/CompileAssert.go b/src/build/core/CompileAssert.go new file mode 100644 index 0000000..2715e44 --- /dev/null +++ b/src/build/core/CompileAssert.go @@ -0,0 +1,29 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" +) + +// CompileAssert compiles an assertion. +func (f *Function) CompileAssert(assert *ast.Assert) error { + f.count.assert++ + success := fmt.Sprintf("%s_assert_%d_true", f.Name, f.count.assert) + fail := fmt.Sprintf("%s_assert_%d_false", f.Name, f.count.assert) + err := f.CompileCondition(assert.Condition, success, fail) + + if err != nil { + return err + } + + f.AddLabel(success) + + f.Defer(func() { + f.AddLabel(fail) + f.Jump(asm.JUMP, "_crash") + }) + + return err +} diff --git a/src/build/core/Defer.go b/src/build/core/Defer.go new file mode 100644 index 0000000..0313a31 --- /dev/null +++ b/src/build/core/Defer.go @@ -0,0 +1,6 @@ +package core + +// Defer executes the callback at the end of function compilation. +func (f *Function) Defer(call func()) { + f.deferred = append(f.deferred, call) +} diff --git a/src/build/core/Function.go b/src/build/core/Function.go index f926956..29fe007 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -14,11 +14,13 @@ type Function struct { Body []token.Token Functions map[string]*Function Err error + deferred []func() count counter } // counter stores how often a certain statement appeared so we can generate a unique label from it. type counter struct { + assert int branch int data int loop int diff --git a/src/build/errors/CompileErrors.go b/src/build/errors/CompileErrors.go index 3aab709..5f65804 100644 --- a/src/build/errors/CompileErrors.go +++ b/src/build/errors/CompileErrors.go @@ -5,6 +5,7 @@ var ( InvalidExpression = &Base{"Invalid expression"} InvalidRune = &Base{"Invalid rune"} InvalidStatement = &Base{"Invalid statement"} + MissingExpression = &Base{"Missing expression"} MissingMainFunction = &Base{"Missing main function"} MissingOperand = &Base{"Missing operand"} NotImplemented = &Base{"Not implemented"} diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 53997c4..b9490db 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -20,6 +20,7 @@ const ( ArrayStart // [ ArrayEnd // ] _keywords // + Assert // assert If // if Import // import Loop // loop diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index a6a34fc..610a36f 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -95,6 +95,8 @@ func Tokenize(buffer []byte) List { kind := Identifier switch string(identifier) { + case "assert": + kind = Assert case "if": kind = If case "import": diff --git a/tests/programs/assert.q b/tests/programs/assert.q new file mode 100644 index 0000000..9c3123e --- /dev/null +++ b/tests/programs/assert.q @@ -0,0 +1,6 @@ +main() { + assert 1 != 0 + assert 1 == 0 || 1 != 0 + assert 1 != 0 && 2 != 0 + assert 1 == 0 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 8e8bf54..ee99875 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -40,6 +40,7 @@ var programs = []struct { {"jump-near", "", "", 0}, {"loop", "", "", 0}, {"loop-lifetime", "", "", 0}, + {"assert", "", "", 1}, } func TestPrograms(t *testing.T) { From 8b1af4bea7dacc71ffd44d5ef54c8c28f189420e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 26 Jul 2024 12:50:47 +0200 Subject: [PATCH 0383/1012] Implemented number output --- examples/itoa/itoa.q | 5 ++++ lib/io/io.q | 22 ++++++++++++++ src/build/asm/Finalize.go | 2 ++ src/build/asm/MemoryRegister.go | 29 ++++++++++++++++++ src/build/asm/divide.go | 16 +--------- src/build/core/CompileAssign.go | 34 +++------------------ src/build/core/CompileAssignArray.go | 37 +++++++++++++++++++++++ src/build/core/CompileAssignDivision.go | 40 +++++++++++++++++++++++++ src/build/core/ExpressionToMemory.go | 32 ++++++++++++++++++++ src/build/register/MemoryRegister.go | 11 +++++++ src/build/token/Kind.go | 2 +- tests/examples_test.go | 1 + 12 files changed, 185 insertions(+), 46 deletions(-) create mode 100644 examples/itoa/itoa.q create mode 100644 lib/io/io.q create mode 100644 src/build/asm/MemoryRegister.go create mode 100644 src/build/core/CompileAssignArray.go create mode 100644 src/build/core/CompileAssignDivision.go create mode 100644 src/build/core/ExpressionToMemory.go create mode 100644 src/build/register/MemoryRegister.go diff --git a/examples/itoa/itoa.q b/examples/itoa/itoa.q new file mode 100644 index 0000000..6bfac19 --- /dev/null +++ b/examples/itoa/itoa.q @@ -0,0 +1,5 @@ +import io + +main() { + io.printNum(2147483647) +} \ No newline at end of file diff --git a/lib/io/io.q b/lib/io/io.q new file mode 100644 index 0000000..1f6e8fe --- /dev/null +++ b/lib/io/io.q @@ -0,0 +1,22 @@ +import mem +import sys + +printNum(x) { + length := 20 + buffer := mem.alloc(length) + end := buffer + length + tmp := end + digit := 0 + + loop { + x, digit = x / 10 + tmp -= 1 + tmp[0] = '0' + digit + + if x == 0 { + sys.write(1, tmp, end - tmp) + mem.free(buffer, length) + return + } + } +} \ No newline at end of file diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index cdd5633..e2adf3f 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -200,6 +200,8 @@ func (a Assembler) Finalize() ([]byte, []byte) { switch operands := x.Data.(type) { case *MemoryNumber: code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) + case *MemoryRegister: + code = x64.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) } case SYSCALL: diff --git a/src/build/asm/MemoryRegister.go b/src/build/asm/MemoryRegister.go new file mode 100644 index 0000000..927a97c --- /dev/null +++ b/src/build/asm/MemoryRegister.go @@ -0,0 +1,29 @@ +package asm + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// MemoryRegister operates with a memory address and a number. +type MemoryRegister struct { + Address Memory + Register cpu.Register +} + +// String returns a human readable version. +func (data *MemoryRegister) String() string { + return fmt.Sprintf("%dB [%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.Offset, data.Register) +} + +// MemoryRegister adds an instruction with a memory address and a number. +func (a *Assembler) MemoryRegister(mnemonic Mnemonic, address Memory, register cpu.Register) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &MemoryRegister{ + Address: address, + Register: register, + }, + }) +} diff --git a/src/build/asm/divide.go b/src/build/asm/divide.go index e795c63..9c42a08 100644 --- a/src/build/asm/divide.go +++ b/src/build/asm/divide.go @@ -4,24 +4,18 @@ import "git.akyoto.dev/cli/q/src/build/arch/x64" // divide implements the division on x64 machines. func divide(code []byte, data any) []byte { - code = x64.PushRegister(code, x64.RDX) - switch operands := data.(type) { case *RegisterNumber: if operands.Register == x64.RAX { - code = x64.PushRegister(code, x64.RCX) code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number)) code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, x64.RCX) - code = x64.PopRegister(code, x64.RCX) } else { - code = x64.PushRegister(code, x64.RAX) code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, operands.Register) code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX) - code = x64.PopRegister(code, x64.RAX) } case *RegisterRegister: @@ -29,24 +23,18 @@ func divide(code []byte, data any) []byte { code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, operands.Source) } else { - code = x64.PushRegister(code, x64.RAX) code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Source) code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX) - code = x64.PopRegister(code, x64.RAX) } } - - code = x64.PopRegister(code, x64.RDX) return code } // modulo calculates the division remainder on x64 machines. func modulo(code []byte, data any) []byte { - code = x64.PushRegister(code, x64.RDX) - code = x64.PushRegister(code, x64.RAX) - switch operands := data.(type) { case *RegisterNumber: code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) @@ -62,7 +50,5 @@ func modulo(code []byte, data any) []byte { code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RDX) } - code = x64.PopRegister(code, x64.RAX) - code = x64.PopRegister(code, x64.RDX) return code } diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index b811ab3..d157792 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -1,7 +1,6 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/token" @@ -26,36 +25,11 @@ func (f *Function) CompileAssign(node *ast.Assign) error { } if left.Token.Kind == token.Array { - name := left.Children[0].Token.Text(f.File.Bytes) - variable := f.VariableByName(name) + return f.CompileAssignArray(node) + } - if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position) - } - - defer f.UseVariable(variable) - - index := left.Children[1] - offset, _, err := f.Number(index.Token) - - if err != nil { - return err - } - - number, size, err := f.Number(right.Token) - - if err != nil { - return err - } - - elementSize := byte(1) - - if size != elementSize { - return errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: elementSize, Size: size}, f.File, right.Token.Position) - } - - f.MemoryNumber(asm.STORE, asm.Memory{Base: variable.Register, Offset: byte(offset), Length: elementSize}, number) - return nil + if left.Token.Kind == token.Separator && right.Token.Kind == token.Div { + return f.CompileAssignDivision(node) } return errors.New(errors.NotImplemented, f.File, left.Token.Position) diff --git a/src/build/core/CompileAssignArray.go b/src/build/core/CompileAssignArray.go new file mode 100644 index 0000000..f519ea0 --- /dev/null +++ b/src/build/core/CompileAssignArray.go @@ -0,0 +1,37 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/errors" +) + +// CompileAssignArray compiles an assign statement for array elements. +func (f *Function) CompileAssignArray(node *ast.Assign) error { + left := node.Expression.Children[0] + right := node.Expression.Children[1] + + name := left.Children[0].Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position) + } + + defer f.UseVariable(variable) + + index := left.Children[1] + offset, _, err := f.Number(index.Token) + + if err != nil { + return err + } + + memory := asm.Memory{ + Base: variable.Register, + Offset: byte(offset), + Length: byte(1), + } + + return f.ExpressionToMemory(right, memory) +} diff --git a/src/build/core/CompileAssignDivision.go b/src/build/core/CompileAssignDivision.go new file mode 100644 index 0000000..de383c9 --- /dev/null +++ b/src/build/core/CompileAssignDivision.go @@ -0,0 +1,40 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/errors" +) + +// CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right. +func (f *Function) CompileAssignDivision(node *ast.Assign) error { + left := node.Expression.Children[0] + right := node.Expression.Children[1] + + quotient := left.Children[0] + name := quotient.Token.Text(f.File.Bytes) + quotientVariable := f.VariableByName(name) + + if quotientVariable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, quotient.Token.Position) + } + + remainder := left.Children[1] + name = remainder.Token.Text(f.File.Bytes) + remainderVariable := f.VariableByName(name) + + if remainderVariable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, remainder.Token.Position) + } + + dividend := right.Children[0] + name = dividend.Token.Text(f.File.Bytes) + dividendVariable := f.VariableByName(name) + + divisor := right.Children[1] + err := f.Execute(right.Token, dividendVariable.Register, divisor) + f.RegisterRegister(asm.MOVE, quotientVariable.Register, x64.RAX) + f.RegisterRegister(asm.MOVE, remainderVariable.Register, x64.RDX) + return err +} diff --git a/src/build/core/ExpressionToMemory.go b/src/build/core/ExpressionToMemory.go new file mode 100644 index 0000000..af7ec37 --- /dev/null +++ b/src/build/core/ExpressionToMemory.go @@ -0,0 +1,32 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// ExpressionToMemory puts the result of an expression into the specified memory address. +func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) error { + if node.IsLeaf() && (node.Token.Kind == token.Number || node.Token.Kind == token.Rune) { + number, size, err := f.Number(node.Token) + + if err != nil { + return err + } + + if size != memory.Length { + return errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) + } + + f.MemoryNumber(asm.STORE, memory, number) + return nil + } + + tmp := f.NewRegister() + defer f.FreeRegister(tmp) + err := f.ExpressionToRegister(node, tmp) + f.MemoryRegister(asm.STORE, memory, tmp) + return err +} diff --git a/src/build/register/MemoryRegister.go b/src/build/register/MemoryRegister.go new file mode 100644 index 0000000..6ad3bc0 --- /dev/null +++ b/src/build/register/MemoryRegister.go @@ -0,0 +1,11 @@ +package register + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" +) + +func (f *Machine) MemoryRegister(mnemonic asm.Mnemonic, a asm.Memory, b cpu.Register) { + f.Assembler.MemoryRegister(mnemonic, a, b) + f.postInstruction() +} diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index b9490db..33df902 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -12,7 +12,6 @@ const ( Rune // Rune is a single unicode code point. String // String is an uninterpreted series of characters in the source code. Comment // Comment is a comment. - Separator // , GroupStart // ( GroupEnd // ) BlockStart // { @@ -50,6 +49,7 @@ const ( Period // . Call // x() Array // [x] + Separator // , _assignments // Assign // = AddAssign // += diff --git a/tests/examples_test.go b/tests/examples_test.go index e4b083a..94c223a 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -18,6 +18,7 @@ var examples = []struct { {"factorial", "", "", 120}, {"fibonacci", "", "", 55}, {"array", "", "Hello", 0}, + {"itoa", "", "2147483647", 0}, } func TestExamples(t *testing.T) { From 123738f88c9142e8ec2d813e5f2e449f0cb7cdc6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 26 Jul 2024 12:50:47 +0200 Subject: [PATCH 0384/1012] Implemented number output --- examples/itoa/itoa.q | 5 ++++ lib/io/io.q | 22 ++++++++++++++ src/build/asm/Finalize.go | 2 ++ src/build/asm/MemoryRegister.go | 29 ++++++++++++++++++ src/build/asm/divide.go | 16 +--------- src/build/core/CompileAssign.go | 34 +++------------------ src/build/core/CompileAssignArray.go | 37 +++++++++++++++++++++++ src/build/core/CompileAssignDivision.go | 40 +++++++++++++++++++++++++ src/build/core/ExpressionToMemory.go | 32 ++++++++++++++++++++ src/build/register/MemoryRegister.go | 11 +++++++ src/build/token/Kind.go | 2 +- tests/examples_test.go | 1 + 12 files changed, 185 insertions(+), 46 deletions(-) create mode 100644 examples/itoa/itoa.q create mode 100644 lib/io/io.q create mode 100644 src/build/asm/MemoryRegister.go create mode 100644 src/build/core/CompileAssignArray.go create mode 100644 src/build/core/CompileAssignDivision.go create mode 100644 src/build/core/ExpressionToMemory.go create mode 100644 src/build/register/MemoryRegister.go diff --git a/examples/itoa/itoa.q b/examples/itoa/itoa.q new file mode 100644 index 0000000..6bfac19 --- /dev/null +++ b/examples/itoa/itoa.q @@ -0,0 +1,5 @@ +import io + +main() { + io.printNum(2147483647) +} \ No newline at end of file diff --git a/lib/io/io.q b/lib/io/io.q new file mode 100644 index 0000000..1f6e8fe --- /dev/null +++ b/lib/io/io.q @@ -0,0 +1,22 @@ +import mem +import sys + +printNum(x) { + length := 20 + buffer := mem.alloc(length) + end := buffer + length + tmp := end + digit := 0 + + loop { + x, digit = x / 10 + tmp -= 1 + tmp[0] = '0' + digit + + if x == 0 { + sys.write(1, tmp, end - tmp) + mem.free(buffer, length) + return + } + } +} \ No newline at end of file diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index cdd5633..e2adf3f 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -200,6 +200,8 @@ func (a Assembler) Finalize() ([]byte, []byte) { switch operands := x.Data.(type) { case *MemoryNumber: code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) + case *MemoryRegister: + code = x64.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) } case SYSCALL: diff --git a/src/build/asm/MemoryRegister.go b/src/build/asm/MemoryRegister.go new file mode 100644 index 0000000..927a97c --- /dev/null +++ b/src/build/asm/MemoryRegister.go @@ -0,0 +1,29 @@ +package asm + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// MemoryRegister operates with a memory address and a number. +type MemoryRegister struct { + Address Memory + Register cpu.Register +} + +// String returns a human readable version. +func (data *MemoryRegister) String() string { + return fmt.Sprintf("%dB [%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.Offset, data.Register) +} + +// MemoryRegister adds an instruction with a memory address and a number. +func (a *Assembler) MemoryRegister(mnemonic Mnemonic, address Memory, register cpu.Register) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &MemoryRegister{ + Address: address, + Register: register, + }, + }) +} diff --git a/src/build/asm/divide.go b/src/build/asm/divide.go index e795c63..9c42a08 100644 --- a/src/build/asm/divide.go +++ b/src/build/asm/divide.go @@ -4,24 +4,18 @@ import "git.akyoto.dev/cli/q/src/build/arch/x64" // divide implements the division on x64 machines. func divide(code []byte, data any) []byte { - code = x64.PushRegister(code, x64.RDX) - switch operands := data.(type) { case *RegisterNumber: if operands.Register == x64.RAX { - code = x64.PushRegister(code, x64.RCX) code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number)) code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, x64.RCX) - code = x64.PopRegister(code, x64.RCX) } else { - code = x64.PushRegister(code, x64.RAX) code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, operands.Register) code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX) - code = x64.PopRegister(code, x64.RAX) } case *RegisterRegister: @@ -29,24 +23,18 @@ func divide(code []byte, data any) []byte { code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, operands.Source) } else { - code = x64.PushRegister(code, x64.RAX) code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Source) code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX) - code = x64.PopRegister(code, x64.RAX) } } - - code = x64.PopRegister(code, x64.RDX) return code } // modulo calculates the division remainder on x64 machines. func modulo(code []byte, data any) []byte { - code = x64.PushRegister(code, x64.RDX) - code = x64.PushRegister(code, x64.RAX) - switch operands := data.(type) { case *RegisterNumber: code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) @@ -62,7 +50,5 @@ func modulo(code []byte, data any) []byte { code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RDX) } - code = x64.PopRegister(code, x64.RAX) - code = x64.PopRegister(code, x64.RDX) return code } diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index b811ab3..d157792 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -1,7 +1,6 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/token" @@ -26,36 +25,11 @@ func (f *Function) CompileAssign(node *ast.Assign) error { } if left.Token.Kind == token.Array { - name := left.Children[0].Token.Text(f.File.Bytes) - variable := f.VariableByName(name) + return f.CompileAssignArray(node) + } - if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position) - } - - defer f.UseVariable(variable) - - index := left.Children[1] - offset, _, err := f.Number(index.Token) - - if err != nil { - return err - } - - number, size, err := f.Number(right.Token) - - if err != nil { - return err - } - - elementSize := byte(1) - - if size != elementSize { - return errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: elementSize, Size: size}, f.File, right.Token.Position) - } - - f.MemoryNumber(asm.STORE, asm.Memory{Base: variable.Register, Offset: byte(offset), Length: elementSize}, number) - return nil + if left.Token.Kind == token.Separator && right.Token.Kind == token.Div { + return f.CompileAssignDivision(node) } return errors.New(errors.NotImplemented, f.File, left.Token.Position) diff --git a/src/build/core/CompileAssignArray.go b/src/build/core/CompileAssignArray.go new file mode 100644 index 0000000..f519ea0 --- /dev/null +++ b/src/build/core/CompileAssignArray.go @@ -0,0 +1,37 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/errors" +) + +// CompileAssignArray compiles an assign statement for array elements. +func (f *Function) CompileAssignArray(node *ast.Assign) error { + left := node.Expression.Children[0] + right := node.Expression.Children[1] + + name := left.Children[0].Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position) + } + + defer f.UseVariable(variable) + + index := left.Children[1] + offset, _, err := f.Number(index.Token) + + if err != nil { + return err + } + + memory := asm.Memory{ + Base: variable.Register, + Offset: byte(offset), + Length: byte(1), + } + + return f.ExpressionToMemory(right, memory) +} diff --git a/src/build/core/CompileAssignDivision.go b/src/build/core/CompileAssignDivision.go new file mode 100644 index 0000000..de383c9 --- /dev/null +++ b/src/build/core/CompileAssignDivision.go @@ -0,0 +1,40 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/errors" +) + +// CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right. +func (f *Function) CompileAssignDivision(node *ast.Assign) error { + left := node.Expression.Children[0] + right := node.Expression.Children[1] + + quotient := left.Children[0] + name := quotient.Token.Text(f.File.Bytes) + quotientVariable := f.VariableByName(name) + + if quotientVariable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, quotient.Token.Position) + } + + remainder := left.Children[1] + name = remainder.Token.Text(f.File.Bytes) + remainderVariable := f.VariableByName(name) + + if remainderVariable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, remainder.Token.Position) + } + + dividend := right.Children[0] + name = dividend.Token.Text(f.File.Bytes) + dividendVariable := f.VariableByName(name) + + divisor := right.Children[1] + err := f.Execute(right.Token, dividendVariable.Register, divisor) + f.RegisterRegister(asm.MOVE, quotientVariable.Register, x64.RAX) + f.RegisterRegister(asm.MOVE, remainderVariable.Register, x64.RDX) + return err +} diff --git a/src/build/core/ExpressionToMemory.go b/src/build/core/ExpressionToMemory.go new file mode 100644 index 0000000..af7ec37 --- /dev/null +++ b/src/build/core/ExpressionToMemory.go @@ -0,0 +1,32 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// ExpressionToMemory puts the result of an expression into the specified memory address. +func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) error { + if node.IsLeaf() && (node.Token.Kind == token.Number || node.Token.Kind == token.Rune) { + number, size, err := f.Number(node.Token) + + if err != nil { + return err + } + + if size != memory.Length { + return errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) + } + + f.MemoryNumber(asm.STORE, memory, number) + return nil + } + + tmp := f.NewRegister() + defer f.FreeRegister(tmp) + err := f.ExpressionToRegister(node, tmp) + f.MemoryRegister(asm.STORE, memory, tmp) + return err +} diff --git a/src/build/register/MemoryRegister.go b/src/build/register/MemoryRegister.go new file mode 100644 index 0000000..6ad3bc0 --- /dev/null +++ b/src/build/register/MemoryRegister.go @@ -0,0 +1,11 @@ +package register + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" +) + +func (f *Machine) MemoryRegister(mnemonic asm.Mnemonic, a asm.Memory, b cpu.Register) { + f.Assembler.MemoryRegister(mnemonic, a, b) + f.postInstruction() +} diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index b9490db..33df902 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -12,7 +12,6 @@ const ( Rune // Rune is a single unicode code point. String // String is an uninterpreted series of characters in the source code. Comment // Comment is a comment. - Separator // , GroupStart // ( GroupEnd // ) BlockStart // { @@ -50,6 +49,7 @@ const ( Period // . Call // x() Array // [x] + Separator // , _assignments // Assign // = AddAssign // += diff --git a/tests/examples_test.go b/tests/examples_test.go index e4b083a..94c223a 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -18,6 +18,7 @@ var examples = []struct { {"factorial", "", "", 120}, {"fibonacci", "", "", 55}, {"array", "", "Hello", 0}, + {"itoa", "", "2147483647", 0}, } func TestExamples(t *testing.T) { From de608a74a73741309392a80489537cf038de9a54 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 26 Jul 2024 16:19:13 +0200 Subject: [PATCH 0385/1012] Implemented 64-bit move --- examples/itoa/itoa.q | 4 ++-- lib/{io/io.q => log/number.q} | 2 +- src/build/arch/x64/Move.go | 30 +++++++++++++++++++++++------- src/build/arch/x64/Move_test.go | 21 +++++++++++++++++++-- src/build/arch/x64/x64_test.go | 4 ++-- src/build/asm/Finalize.go | 6 +++--- src/build/asm/divide.go | 22 +++++++++++----------- tests/examples_test.go | 2 +- 8 files changed, 62 insertions(+), 29 deletions(-) rename lib/{io/io.q => log/number.q} (95%) diff --git a/examples/itoa/itoa.q b/examples/itoa/itoa.q index 6bfac19..ee4d224 100644 --- a/examples/itoa/itoa.q +++ b/examples/itoa/itoa.q @@ -1,5 +1,5 @@ -import io +import log main() { - io.printNum(2147483647) + log.number(9223372036854775807) } \ No newline at end of file diff --git a/lib/io/io.q b/lib/log/number.q similarity index 95% rename from lib/io/io.q rename to lib/log/number.q index 1f6e8fe..58456ad 100644 --- a/lib/io/io.q +++ b/lib/log/number.q @@ -1,7 +1,7 @@ import mem import sys -printNum(x) { +number(x) { length := 20 buffer := mem.alloc(length) end := buffer + length diff --git a/src/build/arch/x64/Move.go b/src/build/arch/x64/Move.go index 3ebf470..ad6dbda 100644 --- a/src/build/arch/x64/Move.go +++ b/src/build/arch/x64/Move.go @@ -6,18 +6,34 @@ import ( "git.akyoto.dev/cli/q/src/build/cpu" ) -// MoveRegisterNumber32 moves a 32 bit integer into the given register. -func MoveRegisterNumber32(code []byte, destination cpu.Register, number uint32) []byte { +// MoveRegisterNumber moves an integer into the given register. +func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + w := byte(0) + b := byte(0) + + if SizeOf(int64(number)) == 8 { + w = 1 + } + if destination > 0b111 { - code = append(code, REX(0, 0, 0, 1)) + b = 1 destination &= 0b111 } - code = append(code, 0xb8+byte(destination)) - return binary.LittleEndian.AppendUint32(code, number) + if w != 0 || b != 0 { + code = append(code, REX(w, 0, 0, b)) + } + + code = append(code, 0xB8+byte(destination)) + + if w == 1 { + return binary.LittleEndian.AppendUint64(code, uint64(number)) + } else { + return binary.LittleEndian.AppendUint32(code, uint32(number)) + } } -// MoveRegisterRegister64 moves a register value into another register. -func MoveRegisterRegister64(code []byte, destination cpu.Register, source cpu.Register) []byte { +// MoveRegisterRegister moves a register value into another register. +func MoveRegisterRegister(code []byte, destination cpu.Register, source cpu.Register) []byte { return encode(code, AddressDirect, source, destination, 8, 0x89) } diff --git a/src/build/arch/x64/Move_test.go b/src/build/arch/x64/Move_test.go index 7d5251e..cbbe134 100644 --- a/src/build/arch/x64/Move_test.go +++ b/src/build/arch/x64/Move_test.go @@ -30,11 +30,28 @@ func TestMoveRegisterNumber(t *testing.T) { {x64.R13, 0x7FFFFFFF, []byte{0x41, 0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.R14, 0x7FFFFFFF, []byte{0x41, 0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.R15, 0x7FFFFFFF, []byte{0x41, 0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, + + {x64.RAX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, } for _, pattern := range usagePatterns { t.Logf("mov %s, %x", pattern.Register, pattern.Number) - code := x64.MoveRegisterNumber32(nil, pattern.Register, uint32(pattern.Number)) + code := x64.MoveRegisterNumber(nil, pattern.Register, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } @@ -65,7 +82,7 @@ func TestMoveRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("mov %s, %s", pattern.Left, pattern.Right) - code := x64.MoveRegisterRegister64(nil, pattern.Left, pattern.Right) + code := x64.MoveRegisterRegister(nil, pattern.Left, pattern.Right) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/x64_test.go b/src/build/arch/x64/x64_test.go index bcbf45b..cdde6cf 100644 --- a/src/build/arch/x64/x64_test.go +++ b/src/build/arch/x64/x64_test.go @@ -9,8 +9,8 @@ import ( func TestX64(t *testing.T) { assert.DeepEqual(t, x64.Call(nil, 1), []byte{0xe8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegisterNumber32(nil, 0, 1), []byte{0xb8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegisterNumber32(nil, 1, 1), []byte{0xb9, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 0, 1), []byte{0xb8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 1, 1), []byte{0xb9, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x64.Return(nil), []byte{0xc3}) assert.DeepEqual(t, x64.Syscall(nil), []byte{0x0f, 0x05}) assert.DeepEqual(t, x64.ExtendRAXToRDX(nil), []byte{0x48, 0x99}) diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index e2adf3f..df8e4ac 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -133,14 +133,14 @@ func (a Assembler) Finalize() ([]byte, []byte) { case MOVE: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) + code = x64.MoveRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.MoveRegisterRegister64(code, operands.Destination, operands.Source) + code = x64.MoveRegisterRegister(code, operands.Destination, operands.Source) case *RegisterLabel: start := len(code) - code = x64.MoveRegisterNumber32(code, operands.Register, 0x00_00_00_00) + code = x64.MoveRegisterNumber(code, operands.Register, 0x00_00_00_00) size := 4 opSize := len(code) - size - start regLabel := x.Data.(*RegisterLabel) diff --git a/src/build/asm/divide.go b/src/build/asm/divide.go index 9c42a08..a916cad 100644 --- a/src/build/asm/divide.go +++ b/src/build/asm/divide.go @@ -7,15 +7,15 @@ func divide(code []byte, data any) []byte { switch operands := data.(type) { case *RegisterNumber: if operands.Register == x64.RAX { - code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number)) + code = x64.MoveRegisterNumber(code, x64.RCX, operands.Number) code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, x64.RCX) } else { - code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) - code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) + code = x64.MoveRegisterRegister(code, x64.RAX, operands.Register) + code = x64.MoveRegisterNumber(code, operands.Register, operands.Number) code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, operands.Register) - code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX) + code = x64.MoveRegisterRegister(code, operands.Register, x64.RAX) } case *RegisterRegister: @@ -23,11 +23,11 @@ func divide(code []byte, data any) []byte { code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, operands.Source) } else { - code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) + code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination) code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, operands.Source) - code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX) + code = x64.MoveRegisterRegister(code, operands.Destination, x64.RAX) } } return code @@ -37,17 +37,17 @@ func divide(code []byte, data any) []byte { func modulo(code []byte, data any) []byte { switch operands := data.(type) { case *RegisterNumber: - code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) - code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) + code = x64.MoveRegisterRegister(code, x64.RAX, operands.Register) + code = x64.MoveRegisterNumber(code, operands.Register, operands.Number) code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, operands.Register) - code = x64.MoveRegisterRegister64(code, operands.Register, x64.RDX) + code = x64.MoveRegisterRegister(code, operands.Register, x64.RDX) case *RegisterRegister: - code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) + code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination) code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, operands.Source) - code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RDX) + code = x64.MoveRegisterRegister(code, operands.Destination, x64.RDX) } return code diff --git a/tests/examples_test.go b/tests/examples_test.go index 94c223a..2a5b41b 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -18,7 +18,7 @@ var examples = []struct { {"factorial", "", "", 120}, {"fibonacci", "", "", 55}, {"array", "", "Hello", 0}, - {"itoa", "", "2147483647", 0}, + {"itoa", "", "9223372036854775807", 0}, } func TestExamples(t *testing.T) { From bf80551a15b8a04f09f5c390cd2c1293265c82af Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 26 Jul 2024 16:19:13 +0200 Subject: [PATCH 0386/1012] Implemented 64-bit move --- examples/itoa/itoa.q | 4 ++-- lib/{io/io.q => log/number.q} | 2 +- src/build/arch/x64/Move.go | 30 +++++++++++++++++++++++------- src/build/arch/x64/Move_test.go | 21 +++++++++++++++++++-- src/build/arch/x64/x64_test.go | 4 ++-- src/build/asm/Finalize.go | 6 +++--- src/build/asm/divide.go | 22 +++++++++++----------- tests/examples_test.go | 2 +- 8 files changed, 62 insertions(+), 29 deletions(-) rename lib/{io/io.q => log/number.q} (95%) diff --git a/examples/itoa/itoa.q b/examples/itoa/itoa.q index 6bfac19..ee4d224 100644 --- a/examples/itoa/itoa.q +++ b/examples/itoa/itoa.q @@ -1,5 +1,5 @@ -import io +import log main() { - io.printNum(2147483647) + log.number(9223372036854775807) } \ No newline at end of file diff --git a/lib/io/io.q b/lib/log/number.q similarity index 95% rename from lib/io/io.q rename to lib/log/number.q index 1f6e8fe..58456ad 100644 --- a/lib/io/io.q +++ b/lib/log/number.q @@ -1,7 +1,7 @@ import mem import sys -printNum(x) { +number(x) { length := 20 buffer := mem.alloc(length) end := buffer + length diff --git a/src/build/arch/x64/Move.go b/src/build/arch/x64/Move.go index 3ebf470..ad6dbda 100644 --- a/src/build/arch/x64/Move.go +++ b/src/build/arch/x64/Move.go @@ -6,18 +6,34 @@ import ( "git.akyoto.dev/cli/q/src/build/cpu" ) -// MoveRegisterNumber32 moves a 32 bit integer into the given register. -func MoveRegisterNumber32(code []byte, destination cpu.Register, number uint32) []byte { +// MoveRegisterNumber moves an integer into the given register. +func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + w := byte(0) + b := byte(0) + + if SizeOf(int64(number)) == 8 { + w = 1 + } + if destination > 0b111 { - code = append(code, REX(0, 0, 0, 1)) + b = 1 destination &= 0b111 } - code = append(code, 0xb8+byte(destination)) - return binary.LittleEndian.AppendUint32(code, number) + if w != 0 || b != 0 { + code = append(code, REX(w, 0, 0, b)) + } + + code = append(code, 0xB8+byte(destination)) + + if w == 1 { + return binary.LittleEndian.AppendUint64(code, uint64(number)) + } else { + return binary.LittleEndian.AppendUint32(code, uint32(number)) + } } -// MoveRegisterRegister64 moves a register value into another register. -func MoveRegisterRegister64(code []byte, destination cpu.Register, source cpu.Register) []byte { +// MoveRegisterRegister moves a register value into another register. +func MoveRegisterRegister(code []byte, destination cpu.Register, source cpu.Register) []byte { return encode(code, AddressDirect, source, destination, 8, 0x89) } diff --git a/src/build/arch/x64/Move_test.go b/src/build/arch/x64/Move_test.go index 7d5251e..cbbe134 100644 --- a/src/build/arch/x64/Move_test.go +++ b/src/build/arch/x64/Move_test.go @@ -30,11 +30,28 @@ func TestMoveRegisterNumber(t *testing.T) { {x64.R13, 0x7FFFFFFF, []byte{0x41, 0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.R14, 0x7FFFFFFF, []byte{0x41, 0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.R15, 0x7FFFFFFF, []byte{0x41, 0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, + + {x64.RAX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, } for _, pattern := range usagePatterns { t.Logf("mov %s, %x", pattern.Register, pattern.Number) - code := x64.MoveRegisterNumber32(nil, pattern.Register, uint32(pattern.Number)) + code := x64.MoveRegisterNumber(nil, pattern.Register, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } @@ -65,7 +82,7 @@ func TestMoveRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("mov %s, %s", pattern.Left, pattern.Right) - code := x64.MoveRegisterRegister64(nil, pattern.Left, pattern.Right) + code := x64.MoveRegisterRegister(nil, pattern.Left, pattern.Right) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/build/arch/x64/x64_test.go b/src/build/arch/x64/x64_test.go index bcbf45b..cdde6cf 100644 --- a/src/build/arch/x64/x64_test.go +++ b/src/build/arch/x64/x64_test.go @@ -9,8 +9,8 @@ import ( func TestX64(t *testing.T) { assert.DeepEqual(t, x64.Call(nil, 1), []byte{0xe8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegisterNumber32(nil, 0, 1), []byte{0xb8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegisterNumber32(nil, 1, 1), []byte{0xb9, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 0, 1), []byte{0xb8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 1, 1), []byte{0xb9, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x64.Return(nil), []byte{0xc3}) assert.DeepEqual(t, x64.Syscall(nil), []byte{0x0f, 0x05}) assert.DeepEqual(t, x64.ExtendRAXToRDX(nil), []byte{0x48, 0x99}) diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index e2adf3f..df8e4ac 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -133,14 +133,14 @@ func (a Assembler) Finalize() ([]byte, []byte) { case MOVE: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) + code = x64.MoveRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.MoveRegisterRegister64(code, operands.Destination, operands.Source) + code = x64.MoveRegisterRegister(code, operands.Destination, operands.Source) case *RegisterLabel: start := len(code) - code = x64.MoveRegisterNumber32(code, operands.Register, 0x00_00_00_00) + code = x64.MoveRegisterNumber(code, operands.Register, 0x00_00_00_00) size := 4 opSize := len(code) - size - start regLabel := x.Data.(*RegisterLabel) diff --git a/src/build/asm/divide.go b/src/build/asm/divide.go index 9c42a08..a916cad 100644 --- a/src/build/asm/divide.go +++ b/src/build/asm/divide.go @@ -7,15 +7,15 @@ func divide(code []byte, data any) []byte { switch operands := data.(type) { case *RegisterNumber: if operands.Register == x64.RAX { - code = x64.MoveRegisterNumber32(code, x64.RCX, uint32(operands.Number)) + code = x64.MoveRegisterNumber(code, x64.RCX, operands.Number) code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, x64.RCX) } else { - code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) - code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) + code = x64.MoveRegisterRegister(code, x64.RAX, operands.Register) + code = x64.MoveRegisterNumber(code, operands.Register, operands.Number) code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, operands.Register) - code = x64.MoveRegisterRegister64(code, operands.Register, x64.RAX) + code = x64.MoveRegisterRegister(code, operands.Register, x64.RAX) } case *RegisterRegister: @@ -23,11 +23,11 @@ func divide(code []byte, data any) []byte { code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, operands.Source) } else { - code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) + code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination) code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, operands.Source) - code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RAX) + code = x64.MoveRegisterRegister(code, operands.Destination, x64.RAX) } } return code @@ -37,17 +37,17 @@ func divide(code []byte, data any) []byte { func modulo(code []byte, data any) []byte { switch operands := data.(type) { case *RegisterNumber: - code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Register) - code = x64.MoveRegisterNumber32(code, operands.Register, uint32(operands.Number)) + code = x64.MoveRegisterRegister(code, x64.RAX, operands.Register) + code = x64.MoveRegisterNumber(code, operands.Register, operands.Number) code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, operands.Register) - code = x64.MoveRegisterRegister64(code, operands.Register, x64.RDX) + code = x64.MoveRegisterRegister(code, operands.Register, x64.RDX) case *RegisterRegister: - code = x64.MoveRegisterRegister64(code, x64.RAX, operands.Destination) + code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination) code = x64.ExtendRAXToRDX(code) code = x64.DivRegister(code, operands.Source) - code = x64.MoveRegisterRegister64(code, operands.Destination, x64.RDX) + code = x64.MoveRegisterRegister(code, operands.Destination, x64.RDX) } return code diff --git a/tests/examples_test.go b/tests/examples_test.go index 94c223a..2a5b41b 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -18,7 +18,7 @@ var examples = []struct { {"factorial", "", "", 120}, {"fibonacci", "", "", 55}, {"array", "", "Hello", 0}, - {"itoa", "", "2147483647", 0}, + {"itoa", "", "9223372036854775807", 0}, } func TestExamples(t *testing.T) { From 86e7ade0512017232903ef809f5a6b246d5421ec Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 26 Jul 2024 19:33:51 +0200 Subject: [PATCH 0387/1012] Fixed jump address calculation --- src/build/asm/Finalize.go | 60 +++++++++++++++++++++--------------- tests/programs/param-order.q | 16 ++++++++++ tests/programs_test.go | 1 + 3 files changed, 53 insertions(+), 24 deletions(-) create mode 100644 tests/programs/param-order.q diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index df8e4ac..5c3397d 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -57,23 +57,25 @@ func (a Assembler) Finalize() ([]byte, []byte) { code = x64.Call(code, 0x00_00_00_00) size := 4 label := x.Data.(*Label) - nextInstructionAddress := Address(len(code)) - pointers = append(pointers, &Pointer{ + pointer := &Pointer{ Position: Address(len(code) - size), OpSize: 1, Size: uint8(size), - Resolve: func() Address { - destination, exists := labels[label.Name] + } - if !exists { - panic("unknown call label") - } + pointer.Resolve = func() Address { + destination, exists := labels[label.Name] - distance := destination - nextInstructionAddress - return Address(distance) - }, - }) + if !exists { + panic("unknown jump label") + } + + distance := destination - (pointer.Position + Address(pointer.Size)) + return Address(distance) + } + + pointers = append(pointers, pointer) case COMMENT: continue @@ -106,23 +108,25 @@ func (a Assembler) Finalize() ([]byte, []byte) { size := 1 label := x.Data.(*Label) - nextInstructionAddress := Address(len(code)) - pointers = append(pointers, &Pointer{ + pointer := &Pointer{ Position: Address(len(code) - size), OpSize: 1, Size: uint8(size), - Resolve: func() Address { - destination, exists := labels[label.Name] + } - if !exists { - panic("unknown jump label") - } + pointer.Resolve = func() Address { + destination, exists := labels[label.Name] - distance := destination - nextInstructionAddress - return Address(distance) - }, - }) + if !exists { + panic("unknown jump label") + } + + distance := destination - (pointer.Position + Address(pointer.Size)) + return Address(distance) + } + + pointers = append(pointers, pointer) case LABEL: labels[x.Data.(*Label).Name] = Address(len(code)) @@ -269,8 +273,16 @@ restart: following.Position += offset } - code = append(left, jump...) - code = append(code, right...) + for key, address := range labels { + if address > pointer.Position { + labels[key] += offset + } + } + + code = make([]byte, len(left)+len(jump)+len(right)) + copy(code, left) + copy(code[len(left):], jump) + copy(code[len(left)+len(jump):], right) goto restart } diff --git a/tests/programs/param-order.q b/tests/programs/param-order.q new file mode 100644 index 0000000..a36d6e8 --- /dev/null +++ b/tests/programs/param-order.q @@ -0,0 +1,16 @@ +main() { + f(1, 2, 3, 4, 5, 6) +} + +f(a, b, c, d, e, f) { + return g(f, e, d, c, b, a) +} + +g(a, b, c, d, e, f) { + assert a == 6 + assert b == 5 + assert c == 4 + assert d == 3 + assert e == 2 + assert f == 1 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index ee99875..1282e2e 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -25,6 +25,7 @@ var programs = []struct { {"nested-calls", "", "", 4}, {"param", "", "", 3}, {"param-multi", "", "", 21}, + {"param-order", "", "", 0}, {"reuse", "", "", 3}, {"return", "", "", 6}, {"reassign", "", "", 2}, From d001e4e55f302b5604c604cda266b56bf3fc3af1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 26 Jul 2024 19:33:51 +0200 Subject: [PATCH 0388/1012] Fixed jump address calculation --- src/build/asm/Finalize.go | 60 +++++++++++++++++++++--------------- tests/programs/param-order.q | 16 ++++++++++ tests/programs_test.go | 1 + 3 files changed, 53 insertions(+), 24 deletions(-) create mode 100644 tests/programs/param-order.q diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index df8e4ac..5c3397d 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -57,23 +57,25 @@ func (a Assembler) Finalize() ([]byte, []byte) { code = x64.Call(code, 0x00_00_00_00) size := 4 label := x.Data.(*Label) - nextInstructionAddress := Address(len(code)) - pointers = append(pointers, &Pointer{ + pointer := &Pointer{ Position: Address(len(code) - size), OpSize: 1, Size: uint8(size), - Resolve: func() Address { - destination, exists := labels[label.Name] + } - if !exists { - panic("unknown call label") - } + pointer.Resolve = func() Address { + destination, exists := labels[label.Name] - distance := destination - nextInstructionAddress - return Address(distance) - }, - }) + if !exists { + panic("unknown jump label") + } + + distance := destination - (pointer.Position + Address(pointer.Size)) + return Address(distance) + } + + pointers = append(pointers, pointer) case COMMENT: continue @@ -106,23 +108,25 @@ func (a Assembler) Finalize() ([]byte, []byte) { size := 1 label := x.Data.(*Label) - nextInstructionAddress := Address(len(code)) - pointers = append(pointers, &Pointer{ + pointer := &Pointer{ Position: Address(len(code) - size), OpSize: 1, Size: uint8(size), - Resolve: func() Address { - destination, exists := labels[label.Name] + } - if !exists { - panic("unknown jump label") - } + pointer.Resolve = func() Address { + destination, exists := labels[label.Name] - distance := destination - nextInstructionAddress - return Address(distance) - }, - }) + if !exists { + panic("unknown jump label") + } + + distance := destination - (pointer.Position + Address(pointer.Size)) + return Address(distance) + } + + pointers = append(pointers, pointer) case LABEL: labels[x.Data.(*Label).Name] = Address(len(code)) @@ -269,8 +273,16 @@ restart: following.Position += offset } - code = append(left, jump...) - code = append(code, right...) + for key, address := range labels { + if address > pointer.Position { + labels[key] += offset + } + } + + code = make([]byte, len(left)+len(jump)+len(right)) + copy(code, left) + copy(code[len(left):], jump) + copy(code[len(left)+len(jump):], right) goto restart } diff --git a/tests/programs/param-order.q b/tests/programs/param-order.q new file mode 100644 index 0000000..a36d6e8 --- /dev/null +++ b/tests/programs/param-order.q @@ -0,0 +1,16 @@ +main() { + f(1, 2, 3, 4, 5, 6) +} + +f(a, b, c, d, e, f) { + return g(f, e, d, c, b, a) +} + +g(a, b, c, d, e, f) { + assert a == 6 + assert b == 5 + assert c == 4 + assert d == 3 + assert e == 2 + assert f == 1 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index ee99875..1282e2e 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -25,6 +25,7 @@ var programs = []struct { {"nested-calls", "", "", 4}, {"param", "", "", 3}, {"param-multi", "", "", 21}, + {"param-order", "", "", 0}, {"reuse", "", "", 3}, {"return", "", "", 6}, {"reassign", "", "", 2}, From 093bd79c828c2fcd9465d0dcfb99f0b2cf3640cb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 27 Jul 2024 12:49:39 +0200 Subject: [PATCH 0389/1012] Implemented unary operator parsing --- README.md | 1 + src/build/expression/Expression_test.go | 26 +++++++- src/build/expression/Operator.go | 5 +- src/build/expression/Parse.go | 6 +- src/build/token/Kind.go | 3 +- src/build/token/Token.go | 5 ++ src/build/token/Tokenize.go | 30 ++++++--- src/build/token/Tokenize_test.go | 82 ++++++++++++++++++++++++- 8 files changed, 142 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index d636314..e79d420 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,7 @@ This is what generates expressions from tokens. - [x] `<<=`, `>>=` - [x] `==`, `!=`, `<`, `<=`, `>`, `>=` - [x] `&&`, `||` +- [ ] `!`, `-` ### Architecture diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index a313cf3..ebcc895 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -17,17 +17,22 @@ func TestParse(t *testing.T) { }{ {"Identity", "1", "1"}, {"Basic calculation", "1+2", "(+ 1 2)"}, + {"Same operator", "1+2+3", "(+ (+ 1 2) 3)"}, {"Same operator 2", "1+2+3+4", "(+ (+ (+ 1 2) 3) 4)"}, + {"Different operator", "1+2-3", "(- (+ 1 2) 3)"}, {"Different operator 2", "1+2-3+4", "(+ (- (+ 1 2) 3) 4)"}, {"Different operator 3", "1+2-3+4-5", "(- (+ (- (+ 1 2) 3) 4) 5)"}, + {"Grouped identity", "(1)", "1"}, {"Grouped identity 2", "((1))", "1"}, {"Grouped identity 3", "(((1)))", "1"}, + {"Adding identity", "(1)+(2)", "(+ 1 2)"}, {"Adding identity 2", "(1)+(2)+(3)", "(+ (+ 1 2) 3)"}, {"Adding identity 3", "(1)+(2)+(3)+(4)", "(+ (+ (+ 1 2) 3) 4)"}, + {"Grouping", "(1+2)", "(+ 1 2)"}, {"Grouping 2", "(1+2+3)", "(+ (+ 1 2) 3)"}, {"Grouping 3", "((1)+(2)+(3))", "(+ (+ 1 2) 3)"}, @@ -35,9 +40,11 @@ func TestParse(t *testing.T) { {"Grouping right", "1*(2+3)", "(* 1 (+ 2 3))"}, {"Grouping same operator", "1+(2+3)", "(+ 1 (+ 2 3))"}, {"Grouping same operator 2", "1+(2+3)+(4+5)", "(+ (+ 1 (+ 2 3)) (+ 4 5))"}, + {"Two groups", "(1+2)*(3+4)", "(* (+ 1 2) (+ 3 4))"}, {"Two groups 2", "(1+2-3)*(3+4-5)", "(* (- (+ 1 2) 3) (- (+ 3 4) 5))"}, {"Two groups 3", "(1+2)*(3+4-5)", "(* (+ 1 2) (- (+ 3 4) 5))"}, + {"Operator priority", "1+2*3", "(+ 1 (* 2 3))"}, {"Operator priority 2", "1*2+3", "(+ (* 1 2) 3)"}, {"Operator priority 3", "1+2*3+4", "(+ (+ 1 (* 2 3)) 4)"}, @@ -46,10 +53,25 @@ func TestParse(t *testing.T) { {"Operator priority 6", "1+2*3+4*5", "(+ (+ 1 (* 2 3)) (* 4 5))"}, {"Operator priority 7", "1+2*3*4*5*6", "(+ 1 (* (* (* (* 2 3) 4) 5) 6))"}, {"Operator priority 8", "1*2*3+4*5*6", "(+ (* (* 1 2) 3) (* (* 4 5) 6))"}, + {"Complex", "(1+2-3*4)*(5+6-7*8)", "(* (- (+ 1 2) (* 3 4)) (- (+ 5 6) (* 7 8)))"}, {"Complex 2", "(1+2*3-4)*(5+6*7-8)", "(* (- (+ 1 (* 2 3)) 4) (- (+ 5 (* 6 7)) 8))"}, {"Complex 3", "(1+2*3-4)*(5+6*7-8)+9-10*11", "(- (+ (* (- (+ 1 (* 2 3)) 4) (- (+ 5 (* 6 7)) 8)) 9) (* 10 11))"}, - {"Unary", "!", "!"}, + + {"Unary not", "!", "!"}, + {"Unary not 2", "!a", "(! a)"}, + {"Unary not 3", "!(!a)", "(! (! a))"}, + {"Unary not 4", "!(a||b)", "(! (|| a b))"}, + {"Unary not 5", "a || !b", "(|| a (! b))"}, + + {"Unary minus", "-", "-"}, + {"Unary minus 2", "-a", "(- a)"}, + {"Unary minus 3", "-(-a)", "(- (- a))"}, + {"Unary minus 4", "-a+b", "(+ (- a) b)"}, + {"Unary minus 5", "-(a+b)", "(- (+ a b))"}, + {"Unary minus 6", "a + -b", "(+ a (- b))"}, + {"Unary minus 7", "-a + -b", "(+ (- a) (- b))"}, + {"Function calls", "a()", "(λ a)"}, {"Function calls 2", "a(1)", "(λ a 1)"}, {"Function calls 3", "a(1)+1", "(+ (λ a 1) 1)"}, @@ -77,8 +99,10 @@ func TestParse(t *testing.T) { {"Function calls 25", "a(1-2*3)", "(λ a (- 1 (* 2 3)))"}, {"Function calls 26", "1+2*a()+4", "(+ (+ 1 (* 2 (λ a))) 4)"}, {"Function calls 27", "sum(a,b)*2+15*4", "(+ (* (λ sum a b) 2) (* 15 4))"}, + {"Package function calls", "math.sum(a,b)", "(λ (. math sum) a b)"}, {"Package function calls 2", "generic.math.sum(a,b)", "(λ (. (. generic math) sum) a b)"}, + {"Array access", "a[0]", "(@ a 0)"}, {"Array access 2", "a[b+c]", "(@ a (+ b c))"}, {"Array access 3", "a.b[c]", "(@ (. a b) c)"}, diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index 1d9e18b..2a4ecdf 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -10,7 +10,7 @@ import ( type Operator struct { Symbol string Precedence int8 - Operands int + Operands int8 } // Operators defines the operators used in the language. @@ -19,6 +19,7 @@ var Operators = map[token.Kind]*Operator{ token.Period: {".", 13, 2}, token.Call: {"λ", 12, 1}, token.Array: {"@", 12, 2}, + token.Negate: {"-", 11, 1}, token.Not: {"!", 11, 1}, token.Mul: {"*", 10, 2}, token.Div: {"/", 10, 2}, @@ -73,7 +74,7 @@ func numOperands(symbol token.Kind) int { return -1 } - return operator.Operands + return int(operator.Operands) } func precedence(symbol token.Kind) int8 { diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index 7fbd6ad..22dbd39 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -117,7 +117,11 @@ func Parse(tokens []token.Token) *Expression { newPrecedence := node.Precedence if newPrecedence > oldPrecedence { - cursor.LastChild().Replace(node) + if len(cursor.Children) == numOperands(cursor.Token.Kind) { + cursor.LastChild().Replace(node) + } else { + cursor.AddChild(node) + } } else { start := cursor diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 33df902..871e49f 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -38,10 +38,11 @@ const ( Shr // >> LogicalAnd // && LogicalOr // || + Not // ! (unary) + Negate // - (unary) Equal // == Less // < Greater // > - Not // ! NotEqual // != LessEqual // <= GreaterEqual // >= diff --git a/src/build/token/Token.go b/src/build/token/Token.go index 134f054..99cbe97 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -28,6 +28,11 @@ func (t Token) IsAssignment() bool { return t.Kind > _assignments && t.Kind < _assignmentsEnd } +// IsExpressionStart returns true if the token starts an expression. +func (t Token) IsExpressionStart() bool { + return t.Kind == GroupStart || t.Kind == ArrayStart || t.Kind == BlockStart +} + // IsKeyword returns true if the token is a keyword. func (t Token) IsKeyword() bool { return t.Kind > _keywords && t.Kind < _keywordsEnd diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 610a36f..63ad4f7 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -26,8 +26,28 @@ func Tokenize(buffer []byte) List { tokens = append(tokens, Token{Kind: ArrayEnd, Position: i, Length: 1}) case '\n': tokens = append(tokens, Token{Kind: NewLine, Position: i, Length: 1}) + case '-': + if len(tokens) == 0 || tokens[len(tokens)-1].IsOperator() || tokens[len(tokens)-1].IsExpressionStart() { + tokens = append(tokens, Token{Kind: Negate, Position: i, Length: 1}) + } else { + if i+1 < Position(len(buffer)) && buffer[i+1] == '=' { + tokens = append(tokens, Token{Kind: SubAssign, Position: i, Length: 2}) + i++ + } else { + tokens = append(tokens, Token{Kind: Sub, Position: i, Length: 1}) + } + } + case '/': - if i+1 >= Position(len(buffer)) || buffer[i+1] != '/' { + if i+1 < Position(len(buffer)) && buffer[i+1] == '/' { + position := i + + for i < Position(len(buffer)) && buffer[i] != '\n' { + i++ + } + + tokens = append(tokens, Token{Kind: Comment, Position: position, Length: Length(i - position)}) + } else { position := i i++ @@ -45,14 +65,6 @@ func Tokenize(buffer []byte) List { } tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(i - position)}) - } else { - position := i - - for i < Position(len(buffer)) && buffer[i] != '\n' { - i++ - } - - tokens = append(tokens, Token{Kind: Comment, Position: position, Length: Length(i - position)}) } continue diff --git a/src/build/token/Tokenize_test.go b/src/build/token/Tokenize_test.go index 19f57e5..de67fbd 100644 --- a/src/build/token/Tokenize_test.go +++ b/src/build/token/Tokenize_test.go @@ -83,13 +83,81 @@ func TestNumber(t *testing.T) { } func TestOperator(t *testing.T) { - tokens := token.Tokenize([]byte(`+ - * /`)) + tokens := token.Tokenize([]byte(`a + b - c * d / e`)) expected := []token.Kind{ + token.Identifier, token.Add, + token.Identifier, token.Sub, + token.Identifier, token.Mul, + token.Identifier, token.Div, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestNegateFirstToken(t *testing.T) { + tokens := token.Tokenize([]byte(`-a`)) + + expected := []token.Kind{ + token.Negate, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestNegateAfterGroupStart(t *testing.T) { + tokens := token.Tokenize([]byte(`(-a)`)) + + expected := []token.Kind{ + token.GroupStart, + token.Negate, + token.Identifier, + token.GroupEnd, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestNegateSub(t *testing.T) { + tokens := token.Tokenize([]byte(`-a-b`)) + + expected := []token.Kind{ + token.Negate, + token.Identifier, + token.Sub, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestNegateAfterOperator(t *testing.T) { + tokens := token.Tokenize([]byte(`-a + -b`)) + + expected := []token.Kind{ + token.Negate, + token.Identifier, + token.Add, + token.Negate, + token.Identifier, token.EOF, } @@ -99,18 +167,28 @@ func TestOperator(t *testing.T) { } func TestOperatorAssign(t *testing.T) { - tokens := token.Tokenize([]byte(`+= -= *= /= &= |= ^= <<= >>=`)) + tokens := token.Tokenize([]byte(`a += b -= c *= d /= e &= f |= g ^= h <<= i >>= j`)) expected := []token.Kind{ + token.Identifier, token.AddAssign, + token.Identifier, token.SubAssign, + token.Identifier, token.MulAssign, + token.Identifier, token.DivAssign, + token.Identifier, token.AndAssign, + token.Identifier, token.OrAssign, + token.Identifier, token.XorAssign, + token.Identifier, token.ShlAssign, + token.Identifier, token.ShrAssign, + token.Identifier, token.EOF, } From 944bacf4e1e418787de06107ef1af4a0f6929de3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 27 Jul 2024 12:49:39 +0200 Subject: [PATCH 0390/1012] Implemented unary operator parsing --- README.md | 1 + src/build/expression/Expression_test.go | 26 +++++++- src/build/expression/Operator.go | 5 +- src/build/expression/Parse.go | 6 +- src/build/token/Kind.go | 3 +- src/build/token/Token.go | 5 ++ src/build/token/Tokenize.go | 30 ++++++--- src/build/token/Tokenize_test.go | 82 ++++++++++++++++++++++++- 8 files changed, 142 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index d636314..e79d420 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,7 @@ This is what generates expressions from tokens. - [x] `<<=`, `>>=` - [x] `==`, `!=`, `<`, `<=`, `>`, `>=` - [x] `&&`, `||` +- [ ] `!`, `-` ### Architecture diff --git a/src/build/expression/Expression_test.go b/src/build/expression/Expression_test.go index a313cf3..ebcc895 100644 --- a/src/build/expression/Expression_test.go +++ b/src/build/expression/Expression_test.go @@ -17,17 +17,22 @@ func TestParse(t *testing.T) { }{ {"Identity", "1", "1"}, {"Basic calculation", "1+2", "(+ 1 2)"}, + {"Same operator", "1+2+3", "(+ (+ 1 2) 3)"}, {"Same operator 2", "1+2+3+4", "(+ (+ (+ 1 2) 3) 4)"}, + {"Different operator", "1+2-3", "(- (+ 1 2) 3)"}, {"Different operator 2", "1+2-3+4", "(+ (- (+ 1 2) 3) 4)"}, {"Different operator 3", "1+2-3+4-5", "(- (+ (- (+ 1 2) 3) 4) 5)"}, + {"Grouped identity", "(1)", "1"}, {"Grouped identity 2", "((1))", "1"}, {"Grouped identity 3", "(((1)))", "1"}, + {"Adding identity", "(1)+(2)", "(+ 1 2)"}, {"Adding identity 2", "(1)+(2)+(3)", "(+ (+ 1 2) 3)"}, {"Adding identity 3", "(1)+(2)+(3)+(4)", "(+ (+ (+ 1 2) 3) 4)"}, + {"Grouping", "(1+2)", "(+ 1 2)"}, {"Grouping 2", "(1+2+3)", "(+ (+ 1 2) 3)"}, {"Grouping 3", "((1)+(2)+(3))", "(+ (+ 1 2) 3)"}, @@ -35,9 +40,11 @@ func TestParse(t *testing.T) { {"Grouping right", "1*(2+3)", "(* 1 (+ 2 3))"}, {"Grouping same operator", "1+(2+3)", "(+ 1 (+ 2 3))"}, {"Grouping same operator 2", "1+(2+3)+(4+5)", "(+ (+ 1 (+ 2 3)) (+ 4 5))"}, + {"Two groups", "(1+2)*(3+4)", "(* (+ 1 2) (+ 3 4))"}, {"Two groups 2", "(1+2-3)*(3+4-5)", "(* (- (+ 1 2) 3) (- (+ 3 4) 5))"}, {"Two groups 3", "(1+2)*(3+4-5)", "(* (+ 1 2) (- (+ 3 4) 5))"}, + {"Operator priority", "1+2*3", "(+ 1 (* 2 3))"}, {"Operator priority 2", "1*2+3", "(+ (* 1 2) 3)"}, {"Operator priority 3", "1+2*3+4", "(+ (+ 1 (* 2 3)) 4)"}, @@ -46,10 +53,25 @@ func TestParse(t *testing.T) { {"Operator priority 6", "1+2*3+4*5", "(+ (+ 1 (* 2 3)) (* 4 5))"}, {"Operator priority 7", "1+2*3*4*5*6", "(+ 1 (* (* (* (* 2 3) 4) 5) 6))"}, {"Operator priority 8", "1*2*3+4*5*6", "(+ (* (* 1 2) 3) (* (* 4 5) 6))"}, + {"Complex", "(1+2-3*4)*(5+6-7*8)", "(* (- (+ 1 2) (* 3 4)) (- (+ 5 6) (* 7 8)))"}, {"Complex 2", "(1+2*3-4)*(5+6*7-8)", "(* (- (+ 1 (* 2 3)) 4) (- (+ 5 (* 6 7)) 8))"}, {"Complex 3", "(1+2*3-4)*(5+6*7-8)+9-10*11", "(- (+ (* (- (+ 1 (* 2 3)) 4) (- (+ 5 (* 6 7)) 8)) 9) (* 10 11))"}, - {"Unary", "!", "!"}, + + {"Unary not", "!", "!"}, + {"Unary not 2", "!a", "(! a)"}, + {"Unary not 3", "!(!a)", "(! (! a))"}, + {"Unary not 4", "!(a||b)", "(! (|| a b))"}, + {"Unary not 5", "a || !b", "(|| a (! b))"}, + + {"Unary minus", "-", "-"}, + {"Unary minus 2", "-a", "(- a)"}, + {"Unary minus 3", "-(-a)", "(- (- a))"}, + {"Unary minus 4", "-a+b", "(+ (- a) b)"}, + {"Unary minus 5", "-(a+b)", "(- (+ a b))"}, + {"Unary minus 6", "a + -b", "(+ a (- b))"}, + {"Unary minus 7", "-a + -b", "(+ (- a) (- b))"}, + {"Function calls", "a()", "(λ a)"}, {"Function calls 2", "a(1)", "(λ a 1)"}, {"Function calls 3", "a(1)+1", "(+ (λ a 1) 1)"}, @@ -77,8 +99,10 @@ func TestParse(t *testing.T) { {"Function calls 25", "a(1-2*3)", "(λ a (- 1 (* 2 3)))"}, {"Function calls 26", "1+2*a()+4", "(+ (+ 1 (* 2 (λ a))) 4)"}, {"Function calls 27", "sum(a,b)*2+15*4", "(+ (* (λ sum a b) 2) (* 15 4))"}, + {"Package function calls", "math.sum(a,b)", "(λ (. math sum) a b)"}, {"Package function calls 2", "generic.math.sum(a,b)", "(λ (. (. generic math) sum) a b)"}, + {"Array access", "a[0]", "(@ a 0)"}, {"Array access 2", "a[b+c]", "(@ a (+ b c))"}, {"Array access 3", "a.b[c]", "(@ (. a b) c)"}, diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index 1d9e18b..2a4ecdf 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -10,7 +10,7 @@ import ( type Operator struct { Symbol string Precedence int8 - Operands int + Operands int8 } // Operators defines the operators used in the language. @@ -19,6 +19,7 @@ var Operators = map[token.Kind]*Operator{ token.Period: {".", 13, 2}, token.Call: {"λ", 12, 1}, token.Array: {"@", 12, 2}, + token.Negate: {"-", 11, 1}, token.Not: {"!", 11, 1}, token.Mul: {"*", 10, 2}, token.Div: {"/", 10, 2}, @@ -73,7 +74,7 @@ func numOperands(symbol token.Kind) int { return -1 } - return operator.Operands + return int(operator.Operands) } func precedence(symbol token.Kind) int8 { diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index 7fbd6ad..22dbd39 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -117,7 +117,11 @@ func Parse(tokens []token.Token) *Expression { newPrecedence := node.Precedence if newPrecedence > oldPrecedence { - cursor.LastChild().Replace(node) + if len(cursor.Children) == numOperands(cursor.Token.Kind) { + cursor.LastChild().Replace(node) + } else { + cursor.AddChild(node) + } } else { start := cursor diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 33df902..871e49f 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -38,10 +38,11 @@ const ( Shr // >> LogicalAnd // && LogicalOr // || + Not // ! (unary) + Negate // - (unary) Equal // == Less // < Greater // > - Not // ! NotEqual // != LessEqual // <= GreaterEqual // >= diff --git a/src/build/token/Token.go b/src/build/token/Token.go index 134f054..99cbe97 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -28,6 +28,11 @@ func (t Token) IsAssignment() bool { return t.Kind > _assignments && t.Kind < _assignmentsEnd } +// IsExpressionStart returns true if the token starts an expression. +func (t Token) IsExpressionStart() bool { + return t.Kind == GroupStart || t.Kind == ArrayStart || t.Kind == BlockStart +} + // IsKeyword returns true if the token is a keyword. func (t Token) IsKeyword() bool { return t.Kind > _keywords && t.Kind < _keywordsEnd diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 610a36f..63ad4f7 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -26,8 +26,28 @@ func Tokenize(buffer []byte) List { tokens = append(tokens, Token{Kind: ArrayEnd, Position: i, Length: 1}) case '\n': tokens = append(tokens, Token{Kind: NewLine, Position: i, Length: 1}) + case '-': + if len(tokens) == 0 || tokens[len(tokens)-1].IsOperator() || tokens[len(tokens)-1].IsExpressionStart() { + tokens = append(tokens, Token{Kind: Negate, Position: i, Length: 1}) + } else { + if i+1 < Position(len(buffer)) && buffer[i+1] == '=' { + tokens = append(tokens, Token{Kind: SubAssign, Position: i, Length: 2}) + i++ + } else { + tokens = append(tokens, Token{Kind: Sub, Position: i, Length: 1}) + } + } + case '/': - if i+1 >= Position(len(buffer)) || buffer[i+1] != '/' { + if i+1 < Position(len(buffer)) && buffer[i+1] == '/' { + position := i + + for i < Position(len(buffer)) && buffer[i] != '\n' { + i++ + } + + tokens = append(tokens, Token{Kind: Comment, Position: position, Length: Length(i - position)}) + } else { position := i i++ @@ -45,14 +65,6 @@ func Tokenize(buffer []byte) List { } tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(i - position)}) - } else { - position := i - - for i < Position(len(buffer)) && buffer[i] != '\n' { - i++ - } - - tokens = append(tokens, Token{Kind: Comment, Position: position, Length: Length(i - position)}) } continue diff --git a/src/build/token/Tokenize_test.go b/src/build/token/Tokenize_test.go index 19f57e5..de67fbd 100644 --- a/src/build/token/Tokenize_test.go +++ b/src/build/token/Tokenize_test.go @@ -83,13 +83,81 @@ func TestNumber(t *testing.T) { } func TestOperator(t *testing.T) { - tokens := token.Tokenize([]byte(`+ - * /`)) + tokens := token.Tokenize([]byte(`a + b - c * d / e`)) expected := []token.Kind{ + token.Identifier, token.Add, + token.Identifier, token.Sub, + token.Identifier, token.Mul, + token.Identifier, token.Div, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestNegateFirstToken(t *testing.T) { + tokens := token.Tokenize([]byte(`-a`)) + + expected := []token.Kind{ + token.Negate, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestNegateAfterGroupStart(t *testing.T) { + tokens := token.Tokenize([]byte(`(-a)`)) + + expected := []token.Kind{ + token.GroupStart, + token.Negate, + token.Identifier, + token.GroupEnd, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestNegateSub(t *testing.T) { + tokens := token.Tokenize([]byte(`-a-b`)) + + expected := []token.Kind{ + token.Negate, + token.Identifier, + token.Sub, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestNegateAfterOperator(t *testing.T) { + tokens := token.Tokenize([]byte(`-a + -b`)) + + expected := []token.Kind{ + token.Negate, + token.Identifier, + token.Add, + token.Negate, + token.Identifier, token.EOF, } @@ -99,18 +167,28 @@ func TestOperator(t *testing.T) { } func TestOperatorAssign(t *testing.T) { - tokens := token.Tokenize([]byte(`+= -= *= /= &= |= ^= <<= >>=`)) + tokens := token.Tokenize([]byte(`a += b -= c *= d /= e &= f |= g ^= h <<= i >>= j`)) expected := []token.Kind{ + token.Identifier, token.AddAssign, + token.Identifier, token.SubAssign, + token.Identifier, token.MulAssign, + token.Identifier, token.DivAssign, + token.Identifier, token.AndAssign, + token.Identifier, token.OrAssign, + token.Identifier, token.XorAssign, + token.Identifier, token.ShlAssign, + token.Identifier, token.ShrAssign, + token.Identifier, token.EOF, } From c2f6aa1a081e7807aacabdedad263c1c0a1f1973 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 27 Jul 2024 17:48:03 +0200 Subject: [PATCH 0391/1012] Implemented negative numbers --- src/build/token/Tokenize.go | 10 +++++++++- src/build/token/Tokenize_test.go | 13 +++++++++++++ tests/programs/negative.q | 5 +++++ tests/programs_test.go | 1 + 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tests/programs/negative.q diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 63ad4f7..52b3122 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -131,7 +131,15 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: Number, Position: position, Length: Length(i - position)}) + last := len(tokens) - 1 + + if len(tokens) > 0 && tokens[last].Kind == Negate { + tokens[last].Kind = Number + tokens[last].Length = Length(i-position) + 1 + } else { + tokens = append(tokens, Token{Kind: Number, Position: position, Length: Length(i - position)}) + } + continue } diff --git a/src/build/token/Tokenize_test.go b/src/build/token/Tokenize_test.go index de67fbd..86ad7be 100644 --- a/src/build/token/Tokenize_test.go +++ b/src/build/token/Tokenize_test.go @@ -166,6 +166,19 @@ func TestNegateAfterOperator(t *testing.T) { } } +func TestNegateNumber(t *testing.T) { + tokens := token.Tokenize([]byte(`-1`)) + + expected := []token.Kind{ + token.Number, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + func TestOperatorAssign(t *testing.T) { tokens := token.Tokenize([]byte(`a += b -= c *= d /= e &= f |= g ^= h <<= i >>= j`)) diff --git a/tests/programs/negative.q b/tests/programs/negative.q new file mode 100644 index 0000000..2bddfb7 --- /dev/null +++ b/tests/programs/negative.q @@ -0,0 +1,5 @@ +main() { + a := -32 + b := 64 + syscall(60, a + b) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 1282e2e..82a9d42 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -42,6 +42,7 @@ var programs = []struct { {"loop", "", "", 0}, {"loop-lifetime", "", "", 0}, {"assert", "", "", 1}, + {"negative", "", "", 32}, } func TestPrograms(t *testing.T) { From 6861ae9a9089714770ef507654234df24399a350 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 27 Jul 2024 17:48:03 +0200 Subject: [PATCH 0392/1012] Implemented negative numbers --- src/build/token/Tokenize.go | 10 +++++++++- src/build/token/Tokenize_test.go | 13 +++++++++++++ tests/programs/negative.q | 5 +++++ tests/programs_test.go | 1 + 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tests/programs/negative.q diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 63ad4f7..52b3122 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -131,7 +131,15 @@ func Tokenize(buffer []byte) List { i++ } - tokens = append(tokens, Token{Kind: Number, Position: position, Length: Length(i - position)}) + last := len(tokens) - 1 + + if len(tokens) > 0 && tokens[last].Kind == Negate { + tokens[last].Kind = Number + tokens[last].Length = Length(i-position) + 1 + } else { + tokens = append(tokens, Token{Kind: Number, Position: position, Length: Length(i - position)}) + } + continue } diff --git a/src/build/token/Tokenize_test.go b/src/build/token/Tokenize_test.go index de67fbd..86ad7be 100644 --- a/src/build/token/Tokenize_test.go +++ b/src/build/token/Tokenize_test.go @@ -166,6 +166,19 @@ func TestNegateAfterOperator(t *testing.T) { } } +func TestNegateNumber(t *testing.T) { + tokens := token.Tokenize([]byte(`-1`)) + + expected := []token.Kind{ + token.Number, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + func TestOperatorAssign(t *testing.T) { tokens := token.Tokenize([]byte(`a += b -= c *= d /= e &= f |= g ^= h <<= i >>= j`)) diff --git a/tests/programs/negative.q b/tests/programs/negative.q new file mode 100644 index 0000000..2bddfb7 --- /dev/null +++ b/tests/programs/negative.q @@ -0,0 +1,5 @@ +main() { + a := -32 + b := 64 + syscall(60, a + b) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 1282e2e..82a9d42 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -42,6 +42,7 @@ var programs = []struct { {"loop", "", "", 0}, {"loop-lifetime", "", "", 0}, {"assert", "", "", 1}, + {"negative", "", "", 32}, } func TestPrograms(t *testing.T) { From 4ded8260b37b4bc22b425a92398e3ccf735011de Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 28 Jul 2024 15:42:51 +0200 Subject: [PATCH 0393/1012] Implemented negation --- src/build/arch/x64/Negate.go | 8 +++++ src/build/arch/x64/Negate_test.go | 39 ++++++++++++++++++++++ src/build/asm/Finalize.go | 6 ++++ src/build/asm/Mnemonic.go | 45 +++++++++++++++++--------- src/build/core/ExecuteRegister.go | 21 ++++++++++++ src/build/core/ExpressionToRegister.go | 17 ++++++++-- src/build/token/Kind.go | 6 ++-- src/build/token/Token.go | 5 +++ src/build/token/Tokenize.go | 2 +- tests/programs/negation.q | 7 ++++ tests/programs_test.go | 1 + 11 files changed, 136 insertions(+), 21 deletions(-) create mode 100644 src/build/arch/x64/Negate.go create mode 100644 src/build/arch/x64/Negate_test.go create mode 100644 src/build/core/ExecuteRegister.go create mode 100644 tests/programs/negation.q diff --git a/src/build/arch/x64/Negate.go b/src/build/arch/x64/Negate.go new file mode 100644 index 0000000..00ae530 --- /dev/null +++ b/src/build/arch/x64/Negate.go @@ -0,0 +1,8 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// NegateRegister negates the value in the register. +func NegateRegister(code []byte, register cpu.Register) []byte { + return encode(code, AddressDirect, 0b011, register, 8, 0xF7) +} diff --git a/src/build/arch/x64/Negate_test.go b/src/build/arch/x64/Negate_test.go new file mode 100644 index 0000000..dd64518 --- /dev/null +++ b/src/build/arch/x64/Negate_test.go @@ -0,0 +1,39 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestNegateRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x64.RAX, []byte{0x48, 0xF7, 0xD8}}, + {x64.RCX, []byte{0x48, 0xF7, 0xD9}}, + {x64.RDX, []byte{0x48, 0xF7, 0xDA}}, + {x64.RBX, []byte{0x48, 0xF7, 0xDB}}, + {x64.RSP, []byte{0x48, 0xF7, 0xDC}}, + {x64.RBP, []byte{0x48, 0xF7, 0xDD}}, + {x64.RSI, []byte{0x48, 0xF7, 0xDE}}, + {x64.RDI, []byte{0x48, 0xF7, 0xDF}}, + {x64.R8, []byte{0x49, 0xF7, 0xD8}}, + {x64.R9, []byte{0x49, 0xF7, 0xD9}}, + {x64.R10, []byte{0x49, 0xF7, 0xDA}}, + {x64.R11, []byte{0x49, 0xF7, 0xDB}}, + {x64.R12, []byte{0x49, 0xF7, 0xDC}}, + {x64.R13, []byte{0x49, 0xF7, 0xDD}}, + {x64.R14, []byte{0x49, 0xF7, 0xDE}}, + {x64.R15, []byte{0x49, 0xF7, 0xDF}}, + } + + for _, pattern := range usagePatterns { + t.Logf("neg %s", pattern.Register) + code := x64.NegateRegister(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index 5c3397d..e38a83f 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -165,6 +165,12 @@ func (a Assembler) Finalize() ([]byte, []byte) { }) } + case NEGATE: + switch operands := x.Data.(type) { + case *Register: + code = x64.NegateRegister(code, operands.Register) + } + case OR: switch operands := x.Data.(type) { case *RegisterNumber: diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 78070f8..e8c778b 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -4,12 +4,31 @@ type Mnemonic uint8 const ( NONE Mnemonic = iota + + // Arithmetic ADD - AND - CALL - COMMENT - COMPARE DIV + MODULO + MUL + NEGATE + SUB + + // Bitwise operations + AND + OR + SHIFTL + SHIFTRS + XOR + + // Data movement + MOVE + LOAD + POP + PUSH + STORE + + // Control flow + CALL JE JNE JG @@ -17,21 +36,13 @@ const ( JL JLE JUMP - MUL LABEL - LOAD - MODULO - MOVE - OR - POP - PUSH RETURN - SHIFTL - SHIFTRS - STORE - SUB SYSCALL - XOR + + // Others + COMMENT + COMPARE ) // String returns a human readable version. @@ -73,6 +84,8 @@ func (m Mnemonic) String() string { return "move" case MUL: return "mul" + case NEGATE: + return "negate" case OR: return "or" case POP: diff --git a/src/build/core/ExecuteRegister.go b/src/build/core/ExecuteRegister.go new file mode 100644 index 0000000..6627acf --- /dev/null +++ b/src/build/core/ExecuteRegister.go @@ -0,0 +1,21 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" +) + +// ExecuteRegister performs an operation on a single register. +func (f *Function) ExecuteRegister(operation token.Token, register cpu.Register) error { + switch operation.Kind { + case token.Negate: + f.Register(asm.NEGATE, register) + + default: + return errors.New(&errors.InvalidOperator{Operator: operation.Text(f.File.Bytes)}, f.File, operation.Position) + } + + return nil +} diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 212b7ed..a0ab61f 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -1,6 +1,8 @@ package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/cpu" @@ -24,8 +26,19 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return err } - if len(node.Children) < 2 { - return errors.New(errors.MissingOperand, f.File, node.Token.End()) + if len(node.Children) == 1 { + if !node.Token.IsUnaryOperator() { + fmt.Println(node.String(f.File.Bytes), node.Token.Kind) + return errors.New(errors.MissingOperand, f.File, node.Token.End()) + } + + err := f.ExpressionToRegister(node.Children[0], register) + + if err != nil { + return err + } + + return f.ExecuteRegister(node.Token, register) } left := node.Children[0] diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 871e49f..44f0132 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -38,8 +38,6 @@ const ( Shr // >> LogicalAnd // && LogicalOr // || - Not // ! (unary) - Negate // - (unary) Equal // == Less // < Greater // > @@ -51,6 +49,10 @@ const ( Call // x() Array // [x] Separator // , + _unary // + Not // ! (unary) + Negate // - (unary) + _unaryEnd // _assignments // Assign // = AddAssign // += diff --git a/src/build/token/Token.go b/src/build/token/Token.go index 99cbe97..caac815 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -43,6 +43,11 @@ func (t Token) IsOperator() bool { return t.Kind > _operators && t.Kind < _operatorsEnd } +// IsUnaryOperator returns true if the token is a unary operator. +func (t Token) IsUnaryOperator() bool { + return t.Kind > _unary && t.Kind < _unaryEnd +} + // Reset resets the token to default values. func (t *Token) Reset() { t.Position = 0 diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 52b3122..9e0f4fa 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -27,7 +27,7 @@ func Tokenize(buffer []byte) List { case '\n': tokens = append(tokens, Token{Kind: NewLine, Position: i, Length: 1}) case '-': - if len(tokens) == 0 || tokens[len(tokens)-1].IsOperator() || tokens[len(tokens)-1].IsExpressionStart() { + if len(tokens) == 0 || tokens[len(tokens)-1].IsOperator() || tokens[len(tokens)-1].IsExpressionStart() || tokens[len(tokens)-1].IsKeyword() { tokens = append(tokens, Token{Kind: Negate, Position: i, Length: 1}) } else { if i+1 < Position(len(buffer)) && buffer[i+1] == '=' { diff --git a/tests/programs/negation.q b/tests/programs/negation.q new file mode 100644 index 0000000..aea1133 --- /dev/null +++ b/tests/programs/negation.q @@ -0,0 +1,7 @@ +main() { + syscall(60, f(-32)) +} + +f(x) { + return -x +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 82a9d42..204f314 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -43,6 +43,7 @@ var programs = []struct { {"loop-lifetime", "", "", 0}, {"assert", "", "", 1}, {"negative", "", "", 32}, + {"negation", "", "", 32}, } func TestPrograms(t *testing.T) { From bb74c0cf50b5a42c37c1018d08bdb4293caae3b7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 28 Jul 2024 15:42:51 +0200 Subject: [PATCH 0394/1012] Implemented negation --- src/build/arch/x64/Negate.go | 8 +++++ src/build/arch/x64/Negate_test.go | 39 ++++++++++++++++++++++ src/build/asm/Finalize.go | 6 ++++ src/build/asm/Mnemonic.go | 45 +++++++++++++++++--------- src/build/core/ExecuteRegister.go | 21 ++++++++++++ src/build/core/ExpressionToRegister.go | 17 ++++++++-- src/build/token/Kind.go | 6 ++-- src/build/token/Token.go | 5 +++ src/build/token/Tokenize.go | 2 +- tests/programs/negation.q | 7 ++++ tests/programs_test.go | 1 + 11 files changed, 136 insertions(+), 21 deletions(-) create mode 100644 src/build/arch/x64/Negate.go create mode 100644 src/build/arch/x64/Negate_test.go create mode 100644 src/build/core/ExecuteRegister.go create mode 100644 tests/programs/negation.q diff --git a/src/build/arch/x64/Negate.go b/src/build/arch/x64/Negate.go new file mode 100644 index 0000000..00ae530 --- /dev/null +++ b/src/build/arch/x64/Negate.go @@ -0,0 +1,8 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// NegateRegister negates the value in the register. +func NegateRegister(code []byte, register cpu.Register) []byte { + return encode(code, AddressDirect, 0b011, register, 8, 0xF7) +} diff --git a/src/build/arch/x64/Negate_test.go b/src/build/arch/x64/Negate_test.go new file mode 100644 index 0000000..dd64518 --- /dev/null +++ b/src/build/arch/x64/Negate_test.go @@ -0,0 +1,39 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestNegateRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x64.RAX, []byte{0x48, 0xF7, 0xD8}}, + {x64.RCX, []byte{0x48, 0xF7, 0xD9}}, + {x64.RDX, []byte{0x48, 0xF7, 0xDA}}, + {x64.RBX, []byte{0x48, 0xF7, 0xDB}}, + {x64.RSP, []byte{0x48, 0xF7, 0xDC}}, + {x64.RBP, []byte{0x48, 0xF7, 0xDD}}, + {x64.RSI, []byte{0x48, 0xF7, 0xDE}}, + {x64.RDI, []byte{0x48, 0xF7, 0xDF}}, + {x64.R8, []byte{0x49, 0xF7, 0xD8}}, + {x64.R9, []byte{0x49, 0xF7, 0xD9}}, + {x64.R10, []byte{0x49, 0xF7, 0xDA}}, + {x64.R11, []byte{0x49, 0xF7, 0xDB}}, + {x64.R12, []byte{0x49, 0xF7, 0xDC}}, + {x64.R13, []byte{0x49, 0xF7, 0xDD}}, + {x64.R14, []byte{0x49, 0xF7, 0xDE}}, + {x64.R15, []byte{0x49, 0xF7, 0xDF}}, + } + + for _, pattern := range usagePatterns { + t.Logf("neg %s", pattern.Register) + code := x64.NegateRegister(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index 5c3397d..e38a83f 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -165,6 +165,12 @@ func (a Assembler) Finalize() ([]byte, []byte) { }) } + case NEGATE: + switch operands := x.Data.(type) { + case *Register: + code = x64.NegateRegister(code, operands.Register) + } + case OR: switch operands := x.Data.(type) { case *RegisterNumber: diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 78070f8..e8c778b 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -4,12 +4,31 @@ type Mnemonic uint8 const ( NONE Mnemonic = iota + + // Arithmetic ADD - AND - CALL - COMMENT - COMPARE DIV + MODULO + MUL + NEGATE + SUB + + // Bitwise operations + AND + OR + SHIFTL + SHIFTRS + XOR + + // Data movement + MOVE + LOAD + POP + PUSH + STORE + + // Control flow + CALL JE JNE JG @@ -17,21 +36,13 @@ const ( JL JLE JUMP - MUL LABEL - LOAD - MODULO - MOVE - OR - POP - PUSH RETURN - SHIFTL - SHIFTRS - STORE - SUB SYSCALL - XOR + + // Others + COMMENT + COMPARE ) // String returns a human readable version. @@ -73,6 +84,8 @@ func (m Mnemonic) String() string { return "move" case MUL: return "mul" + case NEGATE: + return "negate" case OR: return "or" case POP: diff --git a/src/build/core/ExecuteRegister.go b/src/build/core/ExecuteRegister.go new file mode 100644 index 0000000..6627acf --- /dev/null +++ b/src/build/core/ExecuteRegister.go @@ -0,0 +1,21 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" +) + +// ExecuteRegister performs an operation on a single register. +func (f *Function) ExecuteRegister(operation token.Token, register cpu.Register) error { + switch operation.Kind { + case token.Negate: + f.Register(asm.NEGATE, register) + + default: + return errors.New(&errors.InvalidOperator{Operator: operation.Text(f.File.Bytes)}, f.File, operation.Position) + } + + return nil +} diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 212b7ed..a0ab61f 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -1,6 +1,8 @@ package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/cpu" @@ -24,8 +26,19 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return err } - if len(node.Children) < 2 { - return errors.New(errors.MissingOperand, f.File, node.Token.End()) + if len(node.Children) == 1 { + if !node.Token.IsUnaryOperator() { + fmt.Println(node.String(f.File.Bytes), node.Token.Kind) + return errors.New(errors.MissingOperand, f.File, node.Token.End()) + } + + err := f.ExpressionToRegister(node.Children[0], register) + + if err != nil { + return err + } + + return f.ExecuteRegister(node.Token, register) } left := node.Children[0] diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 871e49f..44f0132 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -38,8 +38,6 @@ const ( Shr // >> LogicalAnd // && LogicalOr // || - Not // ! (unary) - Negate // - (unary) Equal // == Less // < Greater // > @@ -51,6 +49,10 @@ const ( Call // x() Array // [x] Separator // , + _unary // + Not // ! (unary) + Negate // - (unary) + _unaryEnd // _assignments // Assign // = AddAssign // += diff --git a/src/build/token/Token.go b/src/build/token/Token.go index 99cbe97..caac815 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -43,6 +43,11 @@ func (t Token) IsOperator() bool { return t.Kind > _operators && t.Kind < _operatorsEnd } +// IsUnaryOperator returns true if the token is a unary operator. +func (t Token) IsUnaryOperator() bool { + return t.Kind > _unary && t.Kind < _unaryEnd +} + // Reset resets the token to default values. func (t *Token) Reset() { t.Position = 0 diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 52b3122..9e0f4fa 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -27,7 +27,7 @@ func Tokenize(buffer []byte) List { case '\n': tokens = append(tokens, Token{Kind: NewLine, Position: i, Length: 1}) case '-': - if len(tokens) == 0 || tokens[len(tokens)-1].IsOperator() || tokens[len(tokens)-1].IsExpressionStart() { + if len(tokens) == 0 || tokens[len(tokens)-1].IsOperator() || tokens[len(tokens)-1].IsExpressionStart() || tokens[len(tokens)-1].IsKeyword() { tokens = append(tokens, Token{Kind: Negate, Position: i, Length: 1}) } else { if i+1 < Position(len(buffer)) && buffer[i+1] == '=' { diff --git a/tests/programs/negation.q b/tests/programs/negation.q new file mode 100644 index 0000000..aea1133 --- /dev/null +++ b/tests/programs/negation.q @@ -0,0 +1,7 @@ +main() { + syscall(60, f(-32)) +} + +f(x) { + return -x +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 82a9d42..204f314 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -43,6 +43,7 @@ var programs = []struct { {"loop-lifetime", "", "", 0}, {"assert", "", "", 1}, {"negative", "", "", 32}, + {"negation", "", "", 32}, } func TestPrograms(t *testing.T) { From 8d629dda7276976f06d1d9e4d787e9834ecce5bd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 28 Jul 2024 18:12:42 +0200 Subject: [PATCH 0395/1012] Improved division split --- src/build/ast/Count.go | 2 +- src/build/ast/Define.go | 4 +-- src/build/ast/Parse.go | 4 +-- src/build/core/CompileAssignDivision.go | 9 +++-- src/build/core/CompileDefinition.go | 10 +++--- src/build/core/Evaluate.go | 24 +++++++++++++ tests/programs/division-split.q | 22 ++++++++++++ tests/programs/modulo-assign.q | 8 +++++ tests/programs/modulo.q | 9 +++++ tests/programs/remainder.q | 46 ------------------------- tests/programs_test.go | 10 +++--- 11 files changed, 84 insertions(+), 64 deletions(-) create mode 100644 src/build/core/Evaluate.go create mode 100644 tests/programs/division-split.q create mode 100644 tests/programs/modulo-assign.q create mode 100644 tests/programs/modulo.q delete mode 100644 tests/programs/remainder.q diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index 0c0c4a8..031a5fc 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -15,7 +15,7 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { count += node.Expression.Count(buffer, kind, name) case *Define: - count += node.Value.Count(buffer, kind, name) + count += node.Expression.Count(buffer, kind, name) case *Return: if node.Value != nil { diff --git a/src/build/ast/Define.go b/src/build/ast/Define.go index 64f0c15..551c9b7 100644 --- a/src/build/ast/Define.go +++ b/src/build/ast/Define.go @@ -2,11 +2,9 @@ package ast import ( "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" ) // Define represents a variable definition. type Define struct { - Value *expression.Expression - Name token.Token + Expression *expression.Expression } diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index a7c0e0e..4db7cca 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -83,9 +83,7 @@ func toASTNode(tokens token.List, buffer []byte) (Node, error) { return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) } - name := expr.Children[0].Token - value := expr.Children[1] - return &Define{Name: name, Value: value}, nil + return &Define{Expression: expr}, nil case IsAssignment(expr): if len(expr.Children) < 2 { diff --git a/src/build/core/CompileAssignDivision.go b/src/build/core/CompileAssignDivision.go index de383c9..177b008 100644 --- a/src/build/core/CompileAssignDivision.go +++ b/src/build/core/CompileAssignDivision.go @@ -29,11 +29,14 @@ func (f *Function) CompileAssignDivision(node *ast.Assign) error { } dividend := right.Children[0] - name = dividend.Token.Text(f.File.Bytes) - dividendVariable := f.VariableByName(name) + dividendRegister, err := f.Evaluate(dividend) + + if err != nil { + return err + } divisor := right.Children[1] - err := f.Execute(right.Token, dividendVariable.Register, divisor) + err = f.Execute(right.Token, dividendRegister, divisor) f.RegisterRegister(asm.MOVE, quotientVariable.Register, x64.RAX) f.RegisterRegister(asm.MOVE, remainderVariable.Register, x64.RDX) return err diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 6e1943f..75241ba 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -9,20 +9,22 @@ import ( // CompileDefinition compiles a variable definition. func (f *Function) CompileDefinition(node *ast.Define) error { - name := node.Name.Text(f.File.Bytes) + left := node.Expression.Children[0] + right := node.Expression.Children[1] + name := left.Token.Text(f.File.Bytes) if f.IdentifierExists(name) { - return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, node.Name.Position) + return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, left.Token.Position) } uses := token.Count(f.Body, f.File.Bytes, token.Identifier, name) - 1 if uses == 0 { - return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) + return errors.New(&errors.UnusedVariable{Name: name}, f.File, left.Token.Position) } register := f.NewRegister() - err := f.ExpressionToRegister(node.Value, register) + err := f.ExpressionToRegister(right, register) f.AddVariable(&scope.Variable{ Name: name, diff --git a/src/build/core/Evaluate.go b/src/build/core/Evaluate.go new file mode 100644 index 0000000..f7d601a --- /dev/null +++ b/src/build/core/Evaluate.go @@ -0,0 +1,24 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Evaluate evaluates an expression and returns a register that contains the value of the expression. +func (f *Function) Evaluate(expr *expression.Expression) (cpu.Register, error) { + if expr.Token.Kind == token.Identifier { + name := expr.Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + + if variable.Alive == 1 { + f.UseVariable(variable) + return variable.Register, nil + } + } + + tmp := f.NewRegister() + err := f.ExpressionToRegister(expr, tmp) + return tmp, err +} diff --git a/tests/programs/division-split.q b/tests/programs/division-split.q new file mode 100644 index 0000000..e44e409 --- /dev/null +++ b/tests/programs/division-split.q @@ -0,0 +1,22 @@ +main() { + quotient := 0 + remainder := 0 + dividend := 256 + divisor := 100 + + quotient, remainder = 256 / 100 + assert quotient == 2 + assert remainder == 56 + + quotient, remainder = dividend / 100 + assert quotient == 2 + assert remainder == 56 + + quotient, remainder = 256 / divisor + assert quotient == 2 + assert remainder == 56 + + quotient, remainder = dividend / divisor + assert quotient == 2 + assert remainder == 56 +} \ No newline at end of file diff --git a/tests/programs/modulo-assign.q b/tests/programs/modulo-assign.q new file mode 100644 index 0000000..8fa9671 --- /dev/null +++ b/tests/programs/modulo-assign.q @@ -0,0 +1,8 @@ +main() { + x := 256 + x %= 100 + assert x == 56 + + x %= 10 + assert x == 6 +} \ No newline at end of file diff --git a/tests/programs/modulo.q b/tests/programs/modulo.q new file mode 100644 index 0000000..c6e0a59 --- /dev/null +++ b/tests/programs/modulo.q @@ -0,0 +1,9 @@ +main() { + assert 0 % 1 == 0 + assert 1 % 1 == 0 + assert 2 % 1 == 0 + assert 0 % 2 == 0 + assert 1 % 2 == 1 + assert 2 % 2 == 0 + assert 3 % 2 == 1 +} \ No newline at end of file diff --git a/tests/programs/remainder.q b/tests/programs/remainder.q deleted file mode 100644 index 2bbe4f5..0000000 --- a/tests/programs/remainder.q +++ /dev/null @@ -1,46 +0,0 @@ -import sys - -main() { - if 0 % 1 != 0 { - sys.exit(1) - } - - if 1 % 1 != 0 { - sys.exit(1) - } - - if 2 % 1 != 0 { - sys.exit(1) - } - - if 0 % 2 != 0 { - sys.exit(1) - } - - if 1 % 2 != 1 { - sys.exit(1) - } - - if 2 % 2 != 0 { - sys.exit(1) - } - - if 3 % 2 != 1 { - sys.exit(1) - } - - x := 256 - x %= 100 - - if x != 56 { - sys.exit(1) - } - - x %= 10 - - if x != 6 { - sys.exit(1) - } - - sys.exit(0) -} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 204f314..46e2a1b 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -33,14 +33,16 @@ var programs = []struct { {"branch-and", "", "", 0}, {"branch-or", "", "", 0}, {"branch-both", "", "", 0}, + {"jump-near", "", "", 0}, + {"loop", "", "", 0}, + {"loop-lifetime", "", "", 0}, {"bitwise-and", "", "", 0}, {"bitwise-or", "", "", 0}, {"bitwise-xor", "", "", 0}, {"shift", "", "", 0}, - {"remainder", "", "", 0}, - {"jump-near", "", "", 0}, - {"loop", "", "", 0}, - {"loop-lifetime", "", "", 0}, + {"modulo", "", "", 0}, + {"modulo-assign", "", "", 0}, + {"division-split", "", "", 0}, {"assert", "", "", 1}, {"negative", "", "", 32}, {"negation", "", "", 32}, From c2c147f1b43d12bb5da960e5cb1a8ba861b32b7b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 28 Jul 2024 18:12:42 +0200 Subject: [PATCH 0396/1012] Improved division split --- src/build/ast/Count.go | 2 +- src/build/ast/Define.go | 4 +-- src/build/ast/Parse.go | 4 +-- src/build/core/CompileAssignDivision.go | 9 +++-- src/build/core/CompileDefinition.go | 10 +++--- src/build/core/Evaluate.go | 24 +++++++++++++ tests/programs/division-split.q | 22 ++++++++++++ tests/programs/modulo-assign.q | 8 +++++ tests/programs/modulo.q | 9 +++++ tests/programs/remainder.q | 46 ------------------------- tests/programs_test.go | 10 +++--- 11 files changed, 84 insertions(+), 64 deletions(-) create mode 100644 src/build/core/Evaluate.go create mode 100644 tests/programs/division-split.q create mode 100644 tests/programs/modulo-assign.q create mode 100644 tests/programs/modulo.q delete mode 100644 tests/programs/remainder.q diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index 0c0c4a8..031a5fc 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -15,7 +15,7 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { count += node.Expression.Count(buffer, kind, name) case *Define: - count += node.Value.Count(buffer, kind, name) + count += node.Expression.Count(buffer, kind, name) case *Return: if node.Value != nil { diff --git a/src/build/ast/Define.go b/src/build/ast/Define.go index 64f0c15..551c9b7 100644 --- a/src/build/ast/Define.go +++ b/src/build/ast/Define.go @@ -2,11 +2,9 @@ package ast import ( "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" ) // Define represents a variable definition. type Define struct { - Value *expression.Expression - Name token.Token + Expression *expression.Expression } diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index a7c0e0e..4db7cca 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -83,9 +83,7 @@ func toASTNode(tokens token.List, buffer []byte) (Node, error) { return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) } - name := expr.Children[0].Token - value := expr.Children[1] - return &Define{Name: name, Value: value}, nil + return &Define{Expression: expr}, nil case IsAssignment(expr): if len(expr.Children) < 2 { diff --git a/src/build/core/CompileAssignDivision.go b/src/build/core/CompileAssignDivision.go index de383c9..177b008 100644 --- a/src/build/core/CompileAssignDivision.go +++ b/src/build/core/CompileAssignDivision.go @@ -29,11 +29,14 @@ func (f *Function) CompileAssignDivision(node *ast.Assign) error { } dividend := right.Children[0] - name = dividend.Token.Text(f.File.Bytes) - dividendVariable := f.VariableByName(name) + dividendRegister, err := f.Evaluate(dividend) + + if err != nil { + return err + } divisor := right.Children[1] - err := f.Execute(right.Token, dividendVariable.Register, divisor) + err = f.Execute(right.Token, dividendRegister, divisor) f.RegisterRegister(asm.MOVE, quotientVariable.Register, x64.RAX) f.RegisterRegister(asm.MOVE, remainderVariable.Register, x64.RDX) return err diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 6e1943f..75241ba 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -9,20 +9,22 @@ import ( // CompileDefinition compiles a variable definition. func (f *Function) CompileDefinition(node *ast.Define) error { - name := node.Name.Text(f.File.Bytes) + left := node.Expression.Children[0] + right := node.Expression.Children[1] + name := left.Token.Text(f.File.Bytes) if f.IdentifierExists(name) { - return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, node.Name.Position) + return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, left.Token.Position) } uses := token.Count(f.Body, f.File.Bytes, token.Identifier, name) - 1 if uses == 0 { - return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) + return errors.New(&errors.UnusedVariable{Name: name}, f.File, left.Token.Position) } register := f.NewRegister() - err := f.ExpressionToRegister(node.Value, register) + err := f.ExpressionToRegister(right, register) f.AddVariable(&scope.Variable{ Name: name, diff --git a/src/build/core/Evaluate.go b/src/build/core/Evaluate.go new file mode 100644 index 0000000..f7d601a --- /dev/null +++ b/src/build/core/Evaluate.go @@ -0,0 +1,24 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Evaluate evaluates an expression and returns a register that contains the value of the expression. +func (f *Function) Evaluate(expr *expression.Expression) (cpu.Register, error) { + if expr.Token.Kind == token.Identifier { + name := expr.Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + + if variable.Alive == 1 { + f.UseVariable(variable) + return variable.Register, nil + } + } + + tmp := f.NewRegister() + err := f.ExpressionToRegister(expr, tmp) + return tmp, err +} diff --git a/tests/programs/division-split.q b/tests/programs/division-split.q new file mode 100644 index 0000000..e44e409 --- /dev/null +++ b/tests/programs/division-split.q @@ -0,0 +1,22 @@ +main() { + quotient := 0 + remainder := 0 + dividend := 256 + divisor := 100 + + quotient, remainder = 256 / 100 + assert quotient == 2 + assert remainder == 56 + + quotient, remainder = dividend / 100 + assert quotient == 2 + assert remainder == 56 + + quotient, remainder = 256 / divisor + assert quotient == 2 + assert remainder == 56 + + quotient, remainder = dividend / divisor + assert quotient == 2 + assert remainder == 56 +} \ No newline at end of file diff --git a/tests/programs/modulo-assign.q b/tests/programs/modulo-assign.q new file mode 100644 index 0000000..8fa9671 --- /dev/null +++ b/tests/programs/modulo-assign.q @@ -0,0 +1,8 @@ +main() { + x := 256 + x %= 100 + assert x == 56 + + x %= 10 + assert x == 6 +} \ No newline at end of file diff --git a/tests/programs/modulo.q b/tests/programs/modulo.q new file mode 100644 index 0000000..c6e0a59 --- /dev/null +++ b/tests/programs/modulo.q @@ -0,0 +1,9 @@ +main() { + assert 0 % 1 == 0 + assert 1 % 1 == 0 + assert 2 % 1 == 0 + assert 0 % 2 == 0 + assert 1 % 2 == 1 + assert 2 % 2 == 0 + assert 3 % 2 == 1 +} \ No newline at end of file diff --git a/tests/programs/remainder.q b/tests/programs/remainder.q deleted file mode 100644 index 2bbe4f5..0000000 --- a/tests/programs/remainder.q +++ /dev/null @@ -1,46 +0,0 @@ -import sys - -main() { - if 0 % 1 != 0 { - sys.exit(1) - } - - if 1 % 1 != 0 { - sys.exit(1) - } - - if 2 % 1 != 0 { - sys.exit(1) - } - - if 0 % 2 != 0 { - sys.exit(1) - } - - if 1 % 2 != 1 { - sys.exit(1) - } - - if 2 % 2 != 0 { - sys.exit(1) - } - - if 3 % 2 != 1 { - sys.exit(1) - } - - x := 256 - x %= 100 - - if x != 56 { - sys.exit(1) - } - - x %= 10 - - if x != 6 { - sys.exit(1) - } - - sys.exit(0) -} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 204f314..46e2a1b 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -33,14 +33,16 @@ var programs = []struct { {"branch-and", "", "", 0}, {"branch-or", "", "", 0}, {"branch-both", "", "", 0}, + {"jump-near", "", "", 0}, + {"loop", "", "", 0}, + {"loop-lifetime", "", "", 0}, {"bitwise-and", "", "", 0}, {"bitwise-or", "", "", 0}, {"bitwise-xor", "", "", 0}, {"shift", "", "", 0}, - {"remainder", "", "", 0}, - {"jump-near", "", "", 0}, - {"loop", "", "", 0}, - {"loop-lifetime", "", "", 0}, + {"modulo", "", "", 0}, + {"modulo-assign", "", "", 0}, + {"division-split", "", "", 0}, {"assert", "", "", 1}, {"negative", "", "", 32}, {"negation", "", "", 32}, From 8a4e623c9c4d7b0c2a091b133ecf0b33e8f48ae7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 28 Jul 2024 18:47:13 +0200 Subject: [PATCH 0397/1012] Added assertions to most tests --- tests/programs/bitwise-and.q | 73 +++++++--------------------------- tests/programs/bitwise-or.q | 73 +++++++--------------------------- tests/programs/bitwise-xor.q | 73 +++++++--------------------------- tests/programs/chained-calls.q | 4 +- tests/programs/math.q | 3 +- tests/programs/nested-calls.q | 4 +- tests/programs/param-multi.q | 2 +- tests/programs/param.q | 2 +- tests/programs/precedence.q | 3 +- tests/programs/reassign.q | 2 +- tests/programs/return.q | 2 +- tests/programs/reuse.q | 2 +- tests/programs/shift.q | 68 ++++++------------------------- tests/programs/square-sum.q | 3 +- tests/programs_test.go | 38 +++++++++--------- 15 files changed, 86 insertions(+), 266 deletions(-) diff --git a/tests/programs/bitwise-and.q b/tests/programs/bitwise-and.q index fda2dcc..34df541 100644 --- a/tests/programs/bitwise-and.q +++ b/tests/programs/bitwise-and.q @@ -1,61 +1,16 @@ -import sys - main() { - if 0 & 0 != 0 { - sys.exit(1) - } - - if 0 & 1 != 0 { - sys.exit(2) - } - - if 1 & 0 != 0 { - sys.exit(3) - } - - if 1 & 1 != 1 { - sys.exit(4) - } - - if 1 & 2 != 0 { - sys.exit(5) - } - - if 1 & 3 != 1 { - sys.exit(6) - } - - if 2 & 0 != 0 { - sys.exit(7) - } - - if 2 & 1 != 0 { - sys.exit(8) - } - - if 2 & 2 != 2 { - sys.exit(9) - } - - if 2 & 3 != 2 { - sys.exit(10) - } - - if 3 & 0 != 0 { - sys.exit(11) - } - - if 3 & 1 != 1 { - sys.exit(12) - } - - if 3 & 2 != 2 { - sys.exit(13) - } - - if 3 & 3 != 3 { - sys.exit(14) - } - - sys.exit(0) + assert 0 & 0 == 0 + assert 0 & 1 == 0 + assert 1 & 0 == 0 + assert 1 & 1 == 1 + assert 1 & 2 == 0 + assert 1 & 3 == 1 + assert 2 & 0 == 0 + assert 2 & 1 == 0 + assert 2 & 2 == 2 + assert 2 & 3 == 2 + assert 3 & 0 == 0 + assert 3 & 1 == 1 + assert 3 & 2 == 2 + assert 3 & 3 == 3 } \ No newline at end of file diff --git a/tests/programs/bitwise-or.q b/tests/programs/bitwise-or.q index 6ea338f..12de2d2 100644 --- a/tests/programs/bitwise-or.q +++ b/tests/programs/bitwise-or.q @@ -1,61 +1,16 @@ -import sys - main() { - if 0 | 0 != 0 { - sys.exit(1) - } - - if 0 | 1 != 1 { - sys.exit(2) - } - - if 1 | 0 != 1 { - sys.exit(3) - } - - if 1 | 1 != 1 { - sys.exit(4) - } - - if 1 | 2 != 3 { - sys.exit(5) - } - - if 1 | 3 != 3 { - sys.exit(6) - } - - if 2 | 0 != 2 { - sys.exit(7) - } - - if 2 | 1 != 3 { - sys.exit(8) - } - - if 2 | 2 != 2 { - sys.exit(9) - } - - if 2 | 3 != 3 { - sys.exit(10) - } - - if 3 | 0 != 3 { - sys.exit(11) - } - - if 3 | 1 != 3 { - sys.exit(12) - } - - if 3 | 2 != 3 { - sys.exit(13) - } - - if 3 | 3 != 3 { - sys.exit(14) - } - - sys.exit(0) + assert 0 | 0 == 0 + assert 0 | 1 == 1 + assert 1 | 0 == 1 + assert 1 | 1 == 1 + assert 1 | 2 == 3 + assert 1 | 3 == 3 + assert 2 | 0 == 2 + assert 2 | 1 == 3 + assert 2 | 2 == 2 + assert 2 | 3 == 3 + assert 3 | 0 == 3 + assert 3 | 1 == 3 + assert 3 | 2 == 3 + assert 3 | 3 == 3 } \ No newline at end of file diff --git a/tests/programs/bitwise-xor.q b/tests/programs/bitwise-xor.q index f648051..e18f5e8 100644 --- a/tests/programs/bitwise-xor.q +++ b/tests/programs/bitwise-xor.q @@ -1,61 +1,16 @@ -import sys - main() { - if 0 ^ 0 != 0 { - sys.exit(1) - } - - if 0 ^ 1 != 1 { - sys.exit(2) - } - - if 1 ^ 0 != 1 { - sys.exit(3) - } - - if 1 ^ 1 != 0 { - sys.exit(4) - } - - if 1 ^ 2 != 3 { - sys.exit(5) - } - - if 1 ^ 3 != 2 { - sys.exit(6) - } - - if 2 ^ 0 != 2 { - sys.exit(7) - } - - if 2 ^ 1 != 3 { - sys.exit(8) - } - - if 2 ^ 2 != 0 { - sys.exit(9) - } - - if 2 ^ 3 != 1 { - sys.exit(10) - } - - if 3 ^ 0 != 3 { - sys.exit(11) - } - - if 3 ^ 1 != 2 { - sys.exit(12) - } - - if 3 ^ 2 != 1 { - sys.exit(13) - } - - if 3 ^ 3 != 0 { - sys.exit(14) - } - - sys.exit(0) + assert 0 ^ 0 == 0 + assert 0 ^ 1 == 1 + assert 1 ^ 0 == 1 + assert 1 ^ 1 == 0 + assert 1 ^ 2 == 3 + assert 1 ^ 3 == 2 + assert 2 ^ 0 == 2 + assert 2 ^ 1 == 3 + assert 2 ^ 2 == 0 + assert 2 ^ 3 == 1 + assert 3 ^ 0 == 3 + assert 3 ^ 1 == 2 + assert 3 ^ 2 == 1 + assert 3 ^ 3 == 0 } \ No newline at end of file diff --git a/tests/programs/chained-calls.q b/tests/programs/chained-calls.q index 49ddb84..e62a02e 100644 --- a/tests/programs/chained-calls.q +++ b/tests/programs/chained-calls.q @@ -1,7 +1,5 @@ -import sys - main() { - sys.exit(f(1) + f(2) + f(3)) + assert f(1) + f(2) + f(3) == 9 } f(x) { diff --git a/tests/programs/math.q b/tests/programs/math.q index b3bb8ee..6fd1712 100644 --- a/tests/programs/math.q +++ b/tests/programs/math.q @@ -1,6 +1,7 @@ main() { x := 1000 - syscall(60, div10(x) / 10 + div(x, 100) * 4 - 40 - x + x) + result := div10(x) / 10 + div(x, 100) * 4 - 40 - x + x + assert result == 10 } div(x, y) { diff --git a/tests/programs/nested-calls.q b/tests/programs/nested-calls.q index f226bcb..a23e15c 100644 --- a/tests/programs/nested-calls.q +++ b/tests/programs/nested-calls.q @@ -1,7 +1,5 @@ -import sys - main() { - sys.exit(f(f(f(1)))) + assert f(f(f(1))) == 4 } f(x) { diff --git a/tests/programs/param-multi.q b/tests/programs/param-multi.q index 97d7ec7..1267e94 100644 --- a/tests/programs/param-multi.q +++ b/tests/programs/param-multi.q @@ -1,5 +1,5 @@ main() { - syscall(60, f(1, 2, 3)) + assert f(1, 2, 3) == 21 } f(x, y, z) { diff --git a/tests/programs/param.q b/tests/programs/param.q index bac5204..11ee501 100644 --- a/tests/programs/param.q +++ b/tests/programs/param.q @@ -1,5 +1,5 @@ main() { - syscall(60, f(1)) + assert f(1) == 3 } f(x) { diff --git a/tests/programs/precedence.q b/tests/programs/precedence.q index bfb4fd2..c8ea775 100644 --- a/tests/programs/precedence.q +++ b/tests/programs/precedence.q @@ -1,5 +1,6 @@ main() { x := 2 y := 3 - syscall(60, (x + y + (x + y) * (x + y)) / x / y + x + y) + result := (x + y + (x + y) * (x + y)) / x / y + x + y + assert result == 10 } \ No newline at end of file diff --git a/tests/programs/reassign.q b/tests/programs/reassign.q index df940d8..26aa3e8 100644 --- a/tests/programs/reassign.q +++ b/tests/programs/reassign.q @@ -2,5 +2,5 @@ main() { x := 1 y := x + 1 x = 2 - syscall(60, y) + assert y == 2 } \ No newline at end of file diff --git a/tests/programs/return.q b/tests/programs/return.q index 13f1e6a..f5e13d5 100644 --- a/tests/programs/return.q +++ b/tests/programs/return.q @@ -1,5 +1,5 @@ main() { - syscall(60, f(2)) + assert f(2) == 6 } f(x) { diff --git a/tests/programs/reuse.q b/tests/programs/reuse.q index 073fd49..010f747 100644 --- a/tests/programs/reuse.q +++ b/tests/programs/reuse.q @@ -1,5 +1,5 @@ main() { - syscall(60, f(1)) + assert f(1) == 3 } f(x) { diff --git a/tests/programs/shift.q b/tests/programs/shift.q index 1fd1ddf..2bb90b8 100644 --- a/tests/programs/shift.q +++ b/tests/programs/shift.q @@ -1,57 +1,15 @@ -import sys - main() { - if 0 << 0 != 0 { - sys.exit(1) - } - - if 0 >> 0 != 0 { - sys.exit(2) - } - - if 1 << 0 != 1 { - sys.exit(3) - } - - if 1 >> 0 != 1 { - sys.exit(4) - } - - if 1 >> 1 != 0 { - sys.exit(5) - } - - if 1 << 1 != 2 { - sys.exit(6) - } - - if 1 << 2 != 4 { - sys.exit(7) - } - - if 1 << 3 != 8 { - sys.exit(8) - } - - if 1 << 4 != 16 { - sys.exit(9) - } - - if 16 >> 1 != 8 { - sys.exit(10) - } - - if 16 >> 2 != 4 { - sys.exit(11) - } - - if 16 >> 3 != 2 { - sys.exit(12) - } - - if 16 >> 4 != 1 { - sys.exit(13) - } - - sys.exit(0) + assert 0 << 0 == 0 + assert 0 >> 0 == 0 + assert 1 << 0 == 1 + assert 1 >> 0 == 1 + assert 1 >> 1 == 0 + assert 1 << 1 == 2 + assert 1 << 2 == 4 + assert 1 << 3 == 8 + assert 1 << 4 == 16 + assert 16 >> 1 == 8 + assert 16 >> 2 == 4 + assert 16 >> 3 == 2 + assert 16 >> 4 == 1 } \ No newline at end of file diff --git a/tests/programs/square-sum.q b/tests/programs/square-sum.q index aace245..04b1237 100644 --- a/tests/programs/square-sum.q +++ b/tests/programs/square-sum.q @@ -1,6 +1,5 @@ main() { - x := f(2, 3) - syscall(60, x) + assert f(2, 3) == 25 } f(x, y) { diff --git a/tests/programs_test.go b/tests/programs_test.go index 46e2a1b..3a2d801 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -18,24 +18,12 @@ var programs = []struct { ExitCode int }{ {"empty", "", "", 0}, - {"math", "", "", 10}, - {"precedence", "", "", 10}, - {"square-sum", "", "", 25}, - {"chained-calls", "", "", 9}, - {"nested-calls", "", "", 4}, - {"param", "", "", 3}, - {"param-multi", "", "", 21}, - {"param-order", "", "", 0}, - {"reuse", "", "", 3}, - {"return", "", "", 6}, - {"reassign", "", "", 2}, - {"branch", "", "", 0}, - {"branch-and", "", "", 0}, - {"branch-or", "", "", 0}, - {"branch-both", "", "", 0}, - {"jump-near", "", "", 0}, - {"loop", "", "", 0}, - {"loop-lifetime", "", "", 0}, + {"assert", "", "", 1}, + {"reuse", "", "", 0}, + {"reassign", "", "", 0}, + {"return", "", "", 0}, + {"math", "", "", 0}, + {"precedence", "", "", 0}, {"bitwise-and", "", "", 0}, {"bitwise-or", "", "", 0}, {"bitwise-xor", "", "", 0}, @@ -43,9 +31,21 @@ var programs = []struct { {"modulo", "", "", 0}, {"modulo-assign", "", "", 0}, {"division-split", "", "", 0}, - {"assert", "", "", 1}, {"negative", "", "", 32}, {"negation", "", "", 32}, + {"square-sum", "", "", 0}, + {"chained-calls", "", "", 0}, + {"nested-calls", "", "", 0}, + {"param", "", "", 0}, + {"param-multi", "", "", 0}, + {"param-order", "", "", 0}, + {"branch", "", "", 0}, + {"branch-and", "", "", 0}, + {"branch-or", "", "", 0}, + {"branch-both", "", "", 0}, + {"jump-near", "", "", 0}, + {"loop", "", "", 0}, + {"loop-lifetime", "", "", 0}, } func TestPrograms(t *testing.T) { From 32d7455c38c77a5aa4f83e8fc8e10d92793704d2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 28 Jul 2024 18:47:13 +0200 Subject: [PATCH 0398/1012] Added assertions to most tests --- tests/programs/bitwise-and.q | 73 +++++++--------------------------- tests/programs/bitwise-or.q | 73 +++++++--------------------------- tests/programs/bitwise-xor.q | 73 +++++++--------------------------- tests/programs/chained-calls.q | 4 +- tests/programs/math.q | 3 +- tests/programs/nested-calls.q | 4 +- tests/programs/param-multi.q | 2 +- tests/programs/param.q | 2 +- tests/programs/precedence.q | 3 +- tests/programs/reassign.q | 2 +- tests/programs/return.q | 2 +- tests/programs/reuse.q | 2 +- tests/programs/shift.q | 68 ++++++------------------------- tests/programs/square-sum.q | 3 +- tests/programs_test.go | 38 +++++++++--------- 15 files changed, 86 insertions(+), 266 deletions(-) diff --git a/tests/programs/bitwise-and.q b/tests/programs/bitwise-and.q index fda2dcc..34df541 100644 --- a/tests/programs/bitwise-and.q +++ b/tests/programs/bitwise-and.q @@ -1,61 +1,16 @@ -import sys - main() { - if 0 & 0 != 0 { - sys.exit(1) - } - - if 0 & 1 != 0 { - sys.exit(2) - } - - if 1 & 0 != 0 { - sys.exit(3) - } - - if 1 & 1 != 1 { - sys.exit(4) - } - - if 1 & 2 != 0 { - sys.exit(5) - } - - if 1 & 3 != 1 { - sys.exit(6) - } - - if 2 & 0 != 0 { - sys.exit(7) - } - - if 2 & 1 != 0 { - sys.exit(8) - } - - if 2 & 2 != 2 { - sys.exit(9) - } - - if 2 & 3 != 2 { - sys.exit(10) - } - - if 3 & 0 != 0 { - sys.exit(11) - } - - if 3 & 1 != 1 { - sys.exit(12) - } - - if 3 & 2 != 2 { - sys.exit(13) - } - - if 3 & 3 != 3 { - sys.exit(14) - } - - sys.exit(0) + assert 0 & 0 == 0 + assert 0 & 1 == 0 + assert 1 & 0 == 0 + assert 1 & 1 == 1 + assert 1 & 2 == 0 + assert 1 & 3 == 1 + assert 2 & 0 == 0 + assert 2 & 1 == 0 + assert 2 & 2 == 2 + assert 2 & 3 == 2 + assert 3 & 0 == 0 + assert 3 & 1 == 1 + assert 3 & 2 == 2 + assert 3 & 3 == 3 } \ No newline at end of file diff --git a/tests/programs/bitwise-or.q b/tests/programs/bitwise-or.q index 6ea338f..12de2d2 100644 --- a/tests/programs/bitwise-or.q +++ b/tests/programs/bitwise-or.q @@ -1,61 +1,16 @@ -import sys - main() { - if 0 | 0 != 0 { - sys.exit(1) - } - - if 0 | 1 != 1 { - sys.exit(2) - } - - if 1 | 0 != 1 { - sys.exit(3) - } - - if 1 | 1 != 1 { - sys.exit(4) - } - - if 1 | 2 != 3 { - sys.exit(5) - } - - if 1 | 3 != 3 { - sys.exit(6) - } - - if 2 | 0 != 2 { - sys.exit(7) - } - - if 2 | 1 != 3 { - sys.exit(8) - } - - if 2 | 2 != 2 { - sys.exit(9) - } - - if 2 | 3 != 3 { - sys.exit(10) - } - - if 3 | 0 != 3 { - sys.exit(11) - } - - if 3 | 1 != 3 { - sys.exit(12) - } - - if 3 | 2 != 3 { - sys.exit(13) - } - - if 3 | 3 != 3 { - sys.exit(14) - } - - sys.exit(0) + assert 0 | 0 == 0 + assert 0 | 1 == 1 + assert 1 | 0 == 1 + assert 1 | 1 == 1 + assert 1 | 2 == 3 + assert 1 | 3 == 3 + assert 2 | 0 == 2 + assert 2 | 1 == 3 + assert 2 | 2 == 2 + assert 2 | 3 == 3 + assert 3 | 0 == 3 + assert 3 | 1 == 3 + assert 3 | 2 == 3 + assert 3 | 3 == 3 } \ No newline at end of file diff --git a/tests/programs/bitwise-xor.q b/tests/programs/bitwise-xor.q index f648051..e18f5e8 100644 --- a/tests/programs/bitwise-xor.q +++ b/tests/programs/bitwise-xor.q @@ -1,61 +1,16 @@ -import sys - main() { - if 0 ^ 0 != 0 { - sys.exit(1) - } - - if 0 ^ 1 != 1 { - sys.exit(2) - } - - if 1 ^ 0 != 1 { - sys.exit(3) - } - - if 1 ^ 1 != 0 { - sys.exit(4) - } - - if 1 ^ 2 != 3 { - sys.exit(5) - } - - if 1 ^ 3 != 2 { - sys.exit(6) - } - - if 2 ^ 0 != 2 { - sys.exit(7) - } - - if 2 ^ 1 != 3 { - sys.exit(8) - } - - if 2 ^ 2 != 0 { - sys.exit(9) - } - - if 2 ^ 3 != 1 { - sys.exit(10) - } - - if 3 ^ 0 != 3 { - sys.exit(11) - } - - if 3 ^ 1 != 2 { - sys.exit(12) - } - - if 3 ^ 2 != 1 { - sys.exit(13) - } - - if 3 ^ 3 != 0 { - sys.exit(14) - } - - sys.exit(0) + assert 0 ^ 0 == 0 + assert 0 ^ 1 == 1 + assert 1 ^ 0 == 1 + assert 1 ^ 1 == 0 + assert 1 ^ 2 == 3 + assert 1 ^ 3 == 2 + assert 2 ^ 0 == 2 + assert 2 ^ 1 == 3 + assert 2 ^ 2 == 0 + assert 2 ^ 3 == 1 + assert 3 ^ 0 == 3 + assert 3 ^ 1 == 2 + assert 3 ^ 2 == 1 + assert 3 ^ 3 == 0 } \ No newline at end of file diff --git a/tests/programs/chained-calls.q b/tests/programs/chained-calls.q index 49ddb84..e62a02e 100644 --- a/tests/programs/chained-calls.q +++ b/tests/programs/chained-calls.q @@ -1,7 +1,5 @@ -import sys - main() { - sys.exit(f(1) + f(2) + f(3)) + assert f(1) + f(2) + f(3) == 9 } f(x) { diff --git a/tests/programs/math.q b/tests/programs/math.q index b3bb8ee..6fd1712 100644 --- a/tests/programs/math.q +++ b/tests/programs/math.q @@ -1,6 +1,7 @@ main() { x := 1000 - syscall(60, div10(x) / 10 + div(x, 100) * 4 - 40 - x + x) + result := div10(x) / 10 + div(x, 100) * 4 - 40 - x + x + assert result == 10 } div(x, y) { diff --git a/tests/programs/nested-calls.q b/tests/programs/nested-calls.q index f226bcb..a23e15c 100644 --- a/tests/programs/nested-calls.q +++ b/tests/programs/nested-calls.q @@ -1,7 +1,5 @@ -import sys - main() { - sys.exit(f(f(f(1)))) + assert f(f(f(1))) == 4 } f(x) { diff --git a/tests/programs/param-multi.q b/tests/programs/param-multi.q index 97d7ec7..1267e94 100644 --- a/tests/programs/param-multi.q +++ b/tests/programs/param-multi.q @@ -1,5 +1,5 @@ main() { - syscall(60, f(1, 2, 3)) + assert f(1, 2, 3) == 21 } f(x, y, z) { diff --git a/tests/programs/param.q b/tests/programs/param.q index bac5204..11ee501 100644 --- a/tests/programs/param.q +++ b/tests/programs/param.q @@ -1,5 +1,5 @@ main() { - syscall(60, f(1)) + assert f(1) == 3 } f(x) { diff --git a/tests/programs/precedence.q b/tests/programs/precedence.q index bfb4fd2..c8ea775 100644 --- a/tests/programs/precedence.q +++ b/tests/programs/precedence.q @@ -1,5 +1,6 @@ main() { x := 2 y := 3 - syscall(60, (x + y + (x + y) * (x + y)) / x / y + x + y) + result := (x + y + (x + y) * (x + y)) / x / y + x + y + assert result == 10 } \ No newline at end of file diff --git a/tests/programs/reassign.q b/tests/programs/reassign.q index df940d8..26aa3e8 100644 --- a/tests/programs/reassign.q +++ b/tests/programs/reassign.q @@ -2,5 +2,5 @@ main() { x := 1 y := x + 1 x = 2 - syscall(60, y) + assert y == 2 } \ No newline at end of file diff --git a/tests/programs/return.q b/tests/programs/return.q index 13f1e6a..f5e13d5 100644 --- a/tests/programs/return.q +++ b/tests/programs/return.q @@ -1,5 +1,5 @@ main() { - syscall(60, f(2)) + assert f(2) == 6 } f(x) { diff --git a/tests/programs/reuse.q b/tests/programs/reuse.q index 073fd49..010f747 100644 --- a/tests/programs/reuse.q +++ b/tests/programs/reuse.q @@ -1,5 +1,5 @@ main() { - syscall(60, f(1)) + assert f(1) == 3 } f(x) { diff --git a/tests/programs/shift.q b/tests/programs/shift.q index 1fd1ddf..2bb90b8 100644 --- a/tests/programs/shift.q +++ b/tests/programs/shift.q @@ -1,57 +1,15 @@ -import sys - main() { - if 0 << 0 != 0 { - sys.exit(1) - } - - if 0 >> 0 != 0 { - sys.exit(2) - } - - if 1 << 0 != 1 { - sys.exit(3) - } - - if 1 >> 0 != 1 { - sys.exit(4) - } - - if 1 >> 1 != 0 { - sys.exit(5) - } - - if 1 << 1 != 2 { - sys.exit(6) - } - - if 1 << 2 != 4 { - sys.exit(7) - } - - if 1 << 3 != 8 { - sys.exit(8) - } - - if 1 << 4 != 16 { - sys.exit(9) - } - - if 16 >> 1 != 8 { - sys.exit(10) - } - - if 16 >> 2 != 4 { - sys.exit(11) - } - - if 16 >> 3 != 2 { - sys.exit(12) - } - - if 16 >> 4 != 1 { - sys.exit(13) - } - - sys.exit(0) + assert 0 << 0 == 0 + assert 0 >> 0 == 0 + assert 1 << 0 == 1 + assert 1 >> 0 == 1 + assert 1 >> 1 == 0 + assert 1 << 1 == 2 + assert 1 << 2 == 4 + assert 1 << 3 == 8 + assert 1 << 4 == 16 + assert 16 >> 1 == 8 + assert 16 >> 2 == 4 + assert 16 >> 3 == 2 + assert 16 >> 4 == 1 } \ No newline at end of file diff --git a/tests/programs/square-sum.q b/tests/programs/square-sum.q index aace245..04b1237 100644 --- a/tests/programs/square-sum.q +++ b/tests/programs/square-sum.q @@ -1,6 +1,5 @@ main() { - x := f(2, 3) - syscall(60, x) + assert f(2, 3) == 25 } f(x, y) { diff --git a/tests/programs_test.go b/tests/programs_test.go index 46e2a1b..3a2d801 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -18,24 +18,12 @@ var programs = []struct { ExitCode int }{ {"empty", "", "", 0}, - {"math", "", "", 10}, - {"precedence", "", "", 10}, - {"square-sum", "", "", 25}, - {"chained-calls", "", "", 9}, - {"nested-calls", "", "", 4}, - {"param", "", "", 3}, - {"param-multi", "", "", 21}, - {"param-order", "", "", 0}, - {"reuse", "", "", 3}, - {"return", "", "", 6}, - {"reassign", "", "", 2}, - {"branch", "", "", 0}, - {"branch-and", "", "", 0}, - {"branch-or", "", "", 0}, - {"branch-both", "", "", 0}, - {"jump-near", "", "", 0}, - {"loop", "", "", 0}, - {"loop-lifetime", "", "", 0}, + {"assert", "", "", 1}, + {"reuse", "", "", 0}, + {"reassign", "", "", 0}, + {"return", "", "", 0}, + {"math", "", "", 0}, + {"precedence", "", "", 0}, {"bitwise-and", "", "", 0}, {"bitwise-or", "", "", 0}, {"bitwise-xor", "", "", 0}, @@ -43,9 +31,21 @@ var programs = []struct { {"modulo", "", "", 0}, {"modulo-assign", "", "", 0}, {"division-split", "", "", 0}, - {"assert", "", "", 1}, {"negative", "", "", 32}, {"negation", "", "", 32}, + {"square-sum", "", "", 0}, + {"chained-calls", "", "", 0}, + {"nested-calls", "", "", 0}, + {"param", "", "", 0}, + {"param-multi", "", "", 0}, + {"param-order", "", "", 0}, + {"branch", "", "", 0}, + {"branch-and", "", "", 0}, + {"branch-or", "", "", 0}, + {"branch-both", "", "", 0}, + {"jump-near", "", "", 0}, + {"loop", "", "", 0}, + {"loop-lifetime", "", "", 0}, } func TestPrograms(t *testing.T) { From 0e999cae92f9c1ccc0d7429db6c22a02fc305baa Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 28 Jul 2024 21:09:42 +0200 Subject: [PATCH 0399/1012] Implemented move with sign extension --- src/build/arch/x64/Move.go | 22 +++++++++++++++++++++- src/build/arch/x64/Move_test.go | 20 ++++++++++++++++++++ tests/programs/negation.q | 2 +- tests/programs/negative.q | 2 +- tests/programs_test.go | 4 ++-- 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/build/arch/x64/Move.go b/src/build/arch/x64/Move.go index ad6dbda..82f5869 100644 --- a/src/build/arch/x64/Move.go +++ b/src/build/arch/x64/Move.go @@ -9,12 +9,17 @@ import ( // MoveRegisterNumber moves an integer into the given register. func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte { w := byte(0) - b := byte(0) if SizeOf(int64(number)) == 8 { w = 1 } + if w == 0 && number < 0 { + return MoveRegisterNumber32(code, destination, number) + } + + b := byte(0) + if destination > 0b111 { b = 1 destination &= 0b111 @@ -33,6 +38,21 @@ func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byt } } +// MoveRegisterNumber32 moves an integer into the given register and sign-extends the register. +func MoveRegisterNumber32(code []byte, destination cpu.Register, number int) []byte { + b := byte(0) + + if destination > 0b111 { + b = 1 + destination &= 0b111 + } + + code = append(code, REX(1, 0, 0, b)) + code = append(code, 0xC7) + code = append(code, ModRM(AddressDirect, 0, byte(destination))) + return binary.LittleEndian.AppendUint32(code, uint32(number)) +} + // MoveRegisterRegister moves a register value into another register. func MoveRegisterRegister(code []byte, destination cpu.Register, source cpu.Register) []byte { return encode(code, AddressDirect, source, destination, 8, 0x89) diff --git a/src/build/arch/x64/Move_test.go b/src/build/arch/x64/Move_test.go index cbbe134..66cfb4a 100644 --- a/src/build/arch/x64/Move_test.go +++ b/src/build/arch/x64/Move_test.go @@ -14,6 +14,7 @@ func TestMoveRegisterNumber(t *testing.T) { Number int Code []byte }{ + // 32 bits {x64.RAX, 0x7FFFFFFF, []byte{0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.RCX, 0x7FFFFFFF, []byte{0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.RDX, 0x7FFFFFFF, []byte{0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -31,6 +32,7 @@ func TestMoveRegisterNumber(t *testing.T) { {x64.R14, 0x7FFFFFFF, []byte{0x41, 0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.R15, 0x7FFFFFFF, []byte{0x41, 0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, + // 64 bits {x64.RAX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.RCX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.RDX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -47,6 +49,24 @@ func TestMoveRegisterNumber(t *testing.T) { {x64.R13, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.R14, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.R15, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + + // Negative numbers + {x64.RAX, -1, []byte{0x48, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.RCX, -1, []byte{0x48, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.RDX, -1, []byte{0x48, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.RBX, -1, []byte{0x48, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.RSP, -1, []byte{0x48, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.RBP, -1, []byte{0x48, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.RSI, -1, []byte{0x48, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.RDI, -1, []byte{0x48, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.R8, -1, []byte{0x49, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.R9, -1, []byte{0x49, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.R10, -1, []byte{0x49, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.R11, -1, []byte{0x49, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.R12, -1, []byte{0x49, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.R13, -1, []byte{0x49, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.R14, -1, []byte{0x49, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.R15, -1, []byte{0x49, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}}, } for _, pattern := range usagePatterns { diff --git a/tests/programs/negation.q b/tests/programs/negation.q index aea1133..94e2826 100644 --- a/tests/programs/negation.q +++ b/tests/programs/negation.q @@ -1,5 +1,5 @@ main() { - syscall(60, f(-32)) + assert f(-32) == 32 } f(x) { diff --git a/tests/programs/negative.q b/tests/programs/negative.q index 2bddfb7..f300e48 100644 --- a/tests/programs/negative.q +++ b/tests/programs/negative.q @@ -1,5 +1,5 @@ main() { a := -32 b := 64 - syscall(60, a + b) + assert a + b == 32 } \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 3a2d801..932c085 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -31,8 +31,8 @@ var programs = []struct { {"modulo", "", "", 0}, {"modulo-assign", "", "", 0}, {"division-split", "", "", 0}, - {"negative", "", "", 32}, - {"negation", "", "", 32}, + {"negative", "", "", 0}, + {"negation", "", "", 0}, {"square-sum", "", "", 0}, {"chained-calls", "", "", 0}, {"nested-calls", "", "", 0}, From f1d4e65c1be2ce8eb6499ce3eefc59a01bf4d8e1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 28 Jul 2024 21:09:42 +0200 Subject: [PATCH 0400/1012] Implemented move with sign extension --- src/build/arch/x64/Move.go | 22 +++++++++++++++++++++- src/build/arch/x64/Move_test.go | 20 ++++++++++++++++++++ tests/programs/negation.q | 2 +- tests/programs/negative.q | 2 +- tests/programs_test.go | 4 ++-- 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/build/arch/x64/Move.go b/src/build/arch/x64/Move.go index ad6dbda..82f5869 100644 --- a/src/build/arch/x64/Move.go +++ b/src/build/arch/x64/Move.go @@ -9,12 +9,17 @@ import ( // MoveRegisterNumber moves an integer into the given register. func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte { w := byte(0) - b := byte(0) if SizeOf(int64(number)) == 8 { w = 1 } + if w == 0 && number < 0 { + return MoveRegisterNumber32(code, destination, number) + } + + b := byte(0) + if destination > 0b111 { b = 1 destination &= 0b111 @@ -33,6 +38,21 @@ func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byt } } +// MoveRegisterNumber32 moves an integer into the given register and sign-extends the register. +func MoveRegisterNumber32(code []byte, destination cpu.Register, number int) []byte { + b := byte(0) + + if destination > 0b111 { + b = 1 + destination &= 0b111 + } + + code = append(code, REX(1, 0, 0, b)) + code = append(code, 0xC7) + code = append(code, ModRM(AddressDirect, 0, byte(destination))) + return binary.LittleEndian.AppendUint32(code, uint32(number)) +} + // MoveRegisterRegister moves a register value into another register. func MoveRegisterRegister(code []byte, destination cpu.Register, source cpu.Register) []byte { return encode(code, AddressDirect, source, destination, 8, 0x89) diff --git a/src/build/arch/x64/Move_test.go b/src/build/arch/x64/Move_test.go index cbbe134..66cfb4a 100644 --- a/src/build/arch/x64/Move_test.go +++ b/src/build/arch/x64/Move_test.go @@ -14,6 +14,7 @@ func TestMoveRegisterNumber(t *testing.T) { Number int Code []byte }{ + // 32 bits {x64.RAX, 0x7FFFFFFF, []byte{0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.RCX, 0x7FFFFFFF, []byte{0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.RDX, 0x7FFFFFFF, []byte{0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -31,6 +32,7 @@ func TestMoveRegisterNumber(t *testing.T) { {x64.R14, 0x7FFFFFFF, []byte{0x41, 0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.R15, 0x7FFFFFFF, []byte{0x41, 0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, + // 64 bits {x64.RAX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.RCX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.RDX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -47,6 +49,24 @@ func TestMoveRegisterNumber(t *testing.T) { {x64.R13, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.R14, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x64.R15, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + + // Negative numbers + {x64.RAX, -1, []byte{0x48, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.RCX, -1, []byte{0x48, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.RDX, -1, []byte{0x48, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.RBX, -1, []byte{0x48, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.RSP, -1, []byte{0x48, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.RBP, -1, []byte{0x48, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.RSI, -1, []byte{0x48, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.RDI, -1, []byte{0x48, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.R8, -1, []byte{0x49, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.R9, -1, []byte{0x49, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.R10, -1, []byte{0x49, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.R11, -1, []byte{0x49, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.R12, -1, []byte{0x49, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.R13, -1, []byte{0x49, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.R14, -1, []byte{0x49, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x64.R15, -1, []byte{0x49, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}}, } for _, pattern := range usagePatterns { diff --git a/tests/programs/negation.q b/tests/programs/negation.q index aea1133..94e2826 100644 --- a/tests/programs/negation.q +++ b/tests/programs/negation.q @@ -1,5 +1,5 @@ main() { - syscall(60, f(-32)) + assert f(-32) == 32 } f(x) { diff --git a/tests/programs/negative.q b/tests/programs/negative.q index 2bddfb7..f300e48 100644 --- a/tests/programs/negative.q +++ b/tests/programs/negative.q @@ -1,5 +1,5 @@ main() { a := -32 b := 64 - syscall(60, a + b) + assert a + b == 32 } \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 3a2d801..932c085 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -31,8 +31,8 @@ var programs = []struct { {"modulo", "", "", 0}, {"modulo-assign", "", "", 0}, {"division-split", "", "", 0}, - {"negative", "", "", 32}, - {"negation", "", "", 32}, + {"negative", "", "", 0}, + {"negation", "", "", 0}, {"square-sum", "", "", 0}, {"chained-calls", "", "", 0}, {"nested-calls", "", "", 0}, From 6d77a8a120375e9a03607d0a7029977f2281b7e0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 29 Jul 2024 00:30:26 +0200 Subject: [PATCH 0401/1012] Implemented numbers with different bases --- README.md | 2 +- lib/mem/alloc.q | 2 +- src/build/core/ExpressionToRegister.go | 3 - src/build/core/Number.go | 20 +++++- src/build/token/Tokenize.go | 54 +++++++++++++-- src/build/token/Tokenize_test.go | 65 +++++++++++++++++++ tests/programs/binary.q | 10 +++ .../{division-split.q => div-split.q} | 0 tests/programs/hexadecimal.q | 8 +++ tests/programs/octal.q | 8 +++ tests/programs_test.go | 5 +- 11 files changed, 165 insertions(+), 12 deletions(-) create mode 100644 tests/programs/binary.q rename tests/programs/{division-split.q => div-split.q} (100%) create mode 100644 tests/programs/hexadecimal.q create mode 100644 tests/programs/octal.q diff --git a/README.md b/README.md index e79d420..f03c58a 100644 --- a/README.md +++ b/README.md @@ -112,10 +112,10 @@ This is what generates expressions from tokens. - [x] Variable lifetimes - [x] Branches - [x] Loops +- [x] Hexadecimal, octal and binary literals - [ ] Data structures - [ ] Type system - [ ] Type operator: `|` (`User | Error`) -- [ ] Hexadecimal, octal and binary literals - [ ] Error handling - [ ] Multiple return values - [ ] Threading library diff --git a/lib/mem/alloc.q b/lib/mem/alloc.q index e03ece3..3a6347c 100644 --- a/lib/mem/alloc.q +++ b/lib/mem/alloc.q @@ -1,7 +1,7 @@ import sys alloc(length) { - return sys.mmap(0, length, 3, 290) + return sys.mmap(0, length, 0x1|0x2, 0x02|0x20|0x100) } free(address, length) { diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index a0ab61f..4c1d69c 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/cpu" @@ -28,7 +26,6 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if len(node.Children) == 1 { if !node.Token.IsUnaryOperator() { - fmt.Println(node.String(f.File.Bytes), node.Token.Kind) return errors.New(errors.MissingOperand, f.File, node.Token.End()) } diff --git a/src/build/core/Number.go b/src/build/core/Number.go index a3d2f47..2cc05a1 100644 --- a/src/build/core/Number.go +++ b/src/build/core/Number.go @@ -2,6 +2,7 @@ package core import ( "strconv" + "strings" "unicode/utf8" "git.akyoto.dev/cli/q/src/build/errors" @@ -12,7 +13,24 @@ import ( func (f *Function) Number(t token.Token) (int, byte, error) { switch t.Kind { case token.Number: - number, err := strconv.Atoi(t.Text(f.File.Bytes)) + digits := t.Text(f.File.Bytes) + + if strings.HasPrefix(digits, "0x") { + number, err := strconv.ParseInt(digits[2:], 16, 64) + return int(number), 8, err + } + + if strings.HasPrefix(digits, "0o") { + number, err := strconv.ParseInt(digits[2:], 8, 64) + return int(number), 8, err + } + + if strings.HasPrefix(digits, "0b") { + number, err := strconv.ParseInt(digits[2:], 2, 64) + return int(number), 8, err + } + + number, err := strconv.Atoi(digits) return number, 8, err case token.Rune: diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 9e0f4fa..929eff3 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -94,6 +94,38 @@ func Tokenize(buffer []byte) List { tokens = append(tokens, Token{Kind: kind, Position: start, Length: Length(end - start)}) continue + case '0': + position := i + i++ + + if i >= Position(len(buffer)) { + tokens = append(tokens, Token{Kind: Number, Position: position, Length: 1}) + break + } + + filter := isDigit + + switch buffer[i] { + case 'x': + i++ + filter = isHexDigit + + case 'b': + i++ + filter = isBinaryDigit + + case 'o': + i++ + filter = isOctalDigit + } + + for i < Position(len(buffer)) && filter(buffer[i]) { + i++ + } + + tokens = append(tokens, Token{Kind: Number, Position: position, Length: Length(i - position)}) + continue + default: if isIdentifierStart(buffer[i]) { position := i @@ -123,11 +155,11 @@ func Tokenize(buffer []byte) List { continue } - if isNumber(buffer[i]) { + if isDigit(buffer[i]) { position := i i++ - for i < Position(len(buffer)) && isNumber(buffer[i]) { + for i < Position(len(buffer)) && isDigit(buffer[i]) { i++ } @@ -235,7 +267,7 @@ func Tokenize(buffer []byte) List { } func isIdentifier(c byte) bool { - return isLetter(c) || isNumber(c) || c == '_' + return isLetter(c) || isDigit(c) || c == '_' } func isIdentifierStart(c byte) bool { @@ -246,8 +278,20 @@ func isLetter(c byte) bool { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') } -func isNumber(c byte) bool { - return (c >= '0' && c <= '9') +func isDigit(c byte) bool { + return c >= '0' && c <= '9' +} + +func isHexDigit(c byte) bool { + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') +} + +func isBinaryDigit(c byte) bool { + return c == '0' || c == '1' +} + +func isOctalDigit(c byte) bool { + return c >= '0' && c <= '7' } func isOperator(c byte) bool { diff --git a/src/build/token/Tokenize_test.go b/src/build/token/Tokenize_test.go index 86ad7be..efc743c 100644 --- a/src/build/token/Tokenize_test.go +++ b/src/build/token/Tokenize_test.go @@ -179,6 +179,71 @@ func TestNegateNumber(t *testing.T) { } } +func TestBinaryNumber(t *testing.T) { + tokens := token.Tokenize([]byte(`0b1010`)) + + expected := []token.Kind{ + token.Number, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestOctalNumber(t *testing.T) { + tokens := token.Tokenize([]byte(`0o755`)) + + expected := []token.Kind{ + token.Number, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestHexadecimalNumber(t *testing.T) { + tokens := token.Tokenize([]byte(`0xCAFE`)) + + expected := []token.Kind{ + token.Number, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestStandaloneZero(t *testing.T) { + tokens := token.Tokenize([]byte(`0`)) + + expected := []token.Kind{ + token.Number, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestLeadingZero(t *testing.T) { + tokens := token.Tokenize([]byte(`0123`)) + + expected := []token.Kind{ + token.Number, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + func TestOperatorAssign(t *testing.T) { tokens := token.Tokenize([]byte(`a += b -= c *= d /= e &= f |= g ^= h <<= i >>= j`)) diff --git a/tests/programs/binary.q b/tests/programs/binary.q new file mode 100644 index 0000000..0c35196 --- /dev/null +++ b/tests/programs/binary.q @@ -0,0 +1,10 @@ +main() { + assert 0b0 == 0 + assert 0b1 == 1 + assert 0b10 == 2 + assert 0b11 == 3 + assert 0b100 == 4 + assert 0b101 == 5 + assert 0b110 == 6 + assert 0b111 == 7 +} \ No newline at end of file diff --git a/tests/programs/division-split.q b/tests/programs/div-split.q similarity index 100% rename from tests/programs/division-split.q rename to tests/programs/div-split.q diff --git a/tests/programs/hexadecimal.q b/tests/programs/hexadecimal.q new file mode 100644 index 0000000..78de4d5 --- /dev/null +++ b/tests/programs/hexadecimal.q @@ -0,0 +1,8 @@ +main() { + assert 0x0 == 0 + assert 0x1 == 1 + assert 0xA == 10 + assert 0x10 == 16 + assert 0xFF == 255 + assert 0x1000 == 4096 +} \ No newline at end of file diff --git a/tests/programs/octal.q b/tests/programs/octal.q new file mode 100644 index 0000000..b58fa8e --- /dev/null +++ b/tests/programs/octal.q @@ -0,0 +1,8 @@ +main() { + assert 0o0 == 0 + assert 0o1 == 1 + assert 0o7 == 7 + assert 0o10 == 8 + assert 0o100 == 64 + assert 0o755 == 493 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 932c085..2bead24 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -22,6 +22,9 @@ var programs = []struct { {"reuse", "", "", 0}, {"reassign", "", "", 0}, {"return", "", "", 0}, + {"binary", "", "", 0}, + {"octal", "", "", 0}, + {"hexadecimal", "", "", 0}, {"math", "", "", 0}, {"precedence", "", "", 0}, {"bitwise-and", "", "", 0}, @@ -30,7 +33,7 @@ var programs = []struct { {"shift", "", "", 0}, {"modulo", "", "", 0}, {"modulo-assign", "", "", 0}, - {"division-split", "", "", 0}, + {"div-split", "", "", 0}, {"negative", "", "", 0}, {"negation", "", "", 0}, {"square-sum", "", "", 0}, From d5953649d9fc9983a5bdef1d3a07c810ceec11a1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 29 Jul 2024 00:30:26 +0200 Subject: [PATCH 0402/1012] Implemented numbers with different bases --- README.md | 2 +- lib/mem/alloc.q | 2 +- src/build/core/ExpressionToRegister.go | 3 - src/build/core/Number.go | 20 +++++- src/build/token/Tokenize.go | 54 +++++++++++++-- src/build/token/Tokenize_test.go | 65 +++++++++++++++++++ tests/programs/binary.q | 10 +++ .../{division-split.q => div-split.q} | 0 tests/programs/hexadecimal.q | 8 +++ tests/programs/octal.q | 8 +++ tests/programs_test.go | 5 +- 11 files changed, 165 insertions(+), 12 deletions(-) create mode 100644 tests/programs/binary.q rename tests/programs/{division-split.q => div-split.q} (100%) create mode 100644 tests/programs/hexadecimal.q create mode 100644 tests/programs/octal.q diff --git a/README.md b/README.md index e79d420..f03c58a 100644 --- a/README.md +++ b/README.md @@ -112,10 +112,10 @@ This is what generates expressions from tokens. - [x] Variable lifetimes - [x] Branches - [x] Loops +- [x] Hexadecimal, octal and binary literals - [ ] Data structures - [ ] Type system - [ ] Type operator: `|` (`User | Error`) -- [ ] Hexadecimal, octal and binary literals - [ ] Error handling - [ ] Multiple return values - [ ] Threading library diff --git a/lib/mem/alloc.q b/lib/mem/alloc.q index e03ece3..3a6347c 100644 --- a/lib/mem/alloc.q +++ b/lib/mem/alloc.q @@ -1,7 +1,7 @@ import sys alloc(length) { - return sys.mmap(0, length, 3, 290) + return sys.mmap(0, length, 0x1|0x2, 0x02|0x20|0x100) } free(address, length) { diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index a0ab61f..4c1d69c 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/cpu" @@ -28,7 +26,6 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if len(node.Children) == 1 { if !node.Token.IsUnaryOperator() { - fmt.Println(node.String(f.File.Bytes), node.Token.Kind) return errors.New(errors.MissingOperand, f.File, node.Token.End()) } diff --git a/src/build/core/Number.go b/src/build/core/Number.go index a3d2f47..2cc05a1 100644 --- a/src/build/core/Number.go +++ b/src/build/core/Number.go @@ -2,6 +2,7 @@ package core import ( "strconv" + "strings" "unicode/utf8" "git.akyoto.dev/cli/q/src/build/errors" @@ -12,7 +13,24 @@ import ( func (f *Function) Number(t token.Token) (int, byte, error) { switch t.Kind { case token.Number: - number, err := strconv.Atoi(t.Text(f.File.Bytes)) + digits := t.Text(f.File.Bytes) + + if strings.HasPrefix(digits, "0x") { + number, err := strconv.ParseInt(digits[2:], 16, 64) + return int(number), 8, err + } + + if strings.HasPrefix(digits, "0o") { + number, err := strconv.ParseInt(digits[2:], 8, 64) + return int(number), 8, err + } + + if strings.HasPrefix(digits, "0b") { + number, err := strconv.ParseInt(digits[2:], 2, 64) + return int(number), 8, err + } + + number, err := strconv.Atoi(digits) return number, 8, err case token.Rune: diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 9e0f4fa..929eff3 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -94,6 +94,38 @@ func Tokenize(buffer []byte) List { tokens = append(tokens, Token{Kind: kind, Position: start, Length: Length(end - start)}) continue + case '0': + position := i + i++ + + if i >= Position(len(buffer)) { + tokens = append(tokens, Token{Kind: Number, Position: position, Length: 1}) + break + } + + filter := isDigit + + switch buffer[i] { + case 'x': + i++ + filter = isHexDigit + + case 'b': + i++ + filter = isBinaryDigit + + case 'o': + i++ + filter = isOctalDigit + } + + for i < Position(len(buffer)) && filter(buffer[i]) { + i++ + } + + tokens = append(tokens, Token{Kind: Number, Position: position, Length: Length(i - position)}) + continue + default: if isIdentifierStart(buffer[i]) { position := i @@ -123,11 +155,11 @@ func Tokenize(buffer []byte) List { continue } - if isNumber(buffer[i]) { + if isDigit(buffer[i]) { position := i i++ - for i < Position(len(buffer)) && isNumber(buffer[i]) { + for i < Position(len(buffer)) && isDigit(buffer[i]) { i++ } @@ -235,7 +267,7 @@ func Tokenize(buffer []byte) List { } func isIdentifier(c byte) bool { - return isLetter(c) || isNumber(c) || c == '_' + return isLetter(c) || isDigit(c) || c == '_' } func isIdentifierStart(c byte) bool { @@ -246,8 +278,20 @@ func isLetter(c byte) bool { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') } -func isNumber(c byte) bool { - return (c >= '0' && c <= '9') +func isDigit(c byte) bool { + return c >= '0' && c <= '9' +} + +func isHexDigit(c byte) bool { + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') +} + +func isBinaryDigit(c byte) bool { + return c == '0' || c == '1' +} + +func isOctalDigit(c byte) bool { + return c >= '0' && c <= '7' } func isOperator(c byte) bool { diff --git a/src/build/token/Tokenize_test.go b/src/build/token/Tokenize_test.go index 86ad7be..efc743c 100644 --- a/src/build/token/Tokenize_test.go +++ b/src/build/token/Tokenize_test.go @@ -179,6 +179,71 @@ func TestNegateNumber(t *testing.T) { } } +func TestBinaryNumber(t *testing.T) { + tokens := token.Tokenize([]byte(`0b1010`)) + + expected := []token.Kind{ + token.Number, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestOctalNumber(t *testing.T) { + tokens := token.Tokenize([]byte(`0o755`)) + + expected := []token.Kind{ + token.Number, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestHexadecimalNumber(t *testing.T) { + tokens := token.Tokenize([]byte(`0xCAFE`)) + + expected := []token.Kind{ + token.Number, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestStandaloneZero(t *testing.T) { + tokens := token.Tokenize([]byte(`0`)) + + expected := []token.Kind{ + token.Number, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestLeadingZero(t *testing.T) { + tokens := token.Tokenize([]byte(`0123`)) + + expected := []token.Kind{ + token.Number, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + func TestOperatorAssign(t *testing.T) { tokens := token.Tokenize([]byte(`a += b -= c *= d /= e &= f |= g ^= h <<= i >>= j`)) diff --git a/tests/programs/binary.q b/tests/programs/binary.q new file mode 100644 index 0000000..0c35196 --- /dev/null +++ b/tests/programs/binary.q @@ -0,0 +1,10 @@ +main() { + assert 0b0 == 0 + assert 0b1 == 1 + assert 0b10 == 2 + assert 0b11 == 3 + assert 0b100 == 4 + assert 0b101 == 5 + assert 0b110 == 6 + assert 0b111 == 7 +} \ No newline at end of file diff --git a/tests/programs/division-split.q b/tests/programs/div-split.q similarity index 100% rename from tests/programs/division-split.q rename to tests/programs/div-split.q diff --git a/tests/programs/hexadecimal.q b/tests/programs/hexadecimal.q new file mode 100644 index 0000000..78de4d5 --- /dev/null +++ b/tests/programs/hexadecimal.q @@ -0,0 +1,8 @@ +main() { + assert 0x0 == 0 + assert 0x1 == 1 + assert 0xA == 10 + assert 0x10 == 16 + assert 0xFF == 255 + assert 0x1000 == 4096 +} \ No newline at end of file diff --git a/tests/programs/octal.q b/tests/programs/octal.q new file mode 100644 index 0000000..b58fa8e --- /dev/null +++ b/tests/programs/octal.q @@ -0,0 +1,8 @@ +main() { + assert 0o0 == 0 + assert 0o1 == 1 + assert 0o7 == 7 + assert 0o10 == 8 + assert 0o100 == 64 + assert 0o755 == 493 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 932c085..2bead24 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -22,6 +22,9 @@ var programs = []struct { {"reuse", "", "", 0}, {"reassign", "", "", 0}, {"return", "", "", 0}, + {"binary", "", "", 0}, + {"octal", "", "", 0}, + {"hexadecimal", "", "", 0}, {"math", "", "", 0}, {"precedence", "", "", 0}, {"bitwise-and", "", "", 0}, @@ -30,7 +33,7 @@ var programs = []struct { {"shift", "", "", 0}, {"modulo", "", "", 0}, {"modulo-assign", "", "", 0}, - {"division-split", "", "", 0}, + {"div-split", "", "", 0}, {"negative", "", "", 0}, {"negation", "", "", 0}, {"square-sum", "", "", 0}, From 315ad23e31e7d9cf928c14eb2139e8e73e3f466b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 29 Jul 2024 12:33:28 +0200 Subject: [PATCH 0403/1012] Moved sizeof functions to a separate package --- src/build/arch/x64/Move.go | 3 ++- src/build/arch/x64/SizeOf_test.go | 21 ------------------- src/build/arch/x64/encodeNum.go | 3 ++- src/build/asm/Finalize.go | 3 ++- src/build/core/CompileAssignArray.go | 2 +- src/build/core/ExecuteLeaf.go | 2 +- src/build/core/ExpressionToMemory.go | 5 ++++- src/build/core/Number.go | 18 ++++++++-------- src/build/core/TokenToRegister.go | 2 +- .../{arch/x64/SizeOf.go => sizeof/Signed.go} | 6 +++--- src/build/sizeof/Signed_test.go | 21 +++++++++++++++++++ src/build/sizeof/Unsigned.go | 20 ++++++++++++++++++ src/build/sizeof/Unsigned_test.go | 17 +++++++++++++++ 13 files changed, 83 insertions(+), 40 deletions(-) delete mode 100644 src/build/arch/x64/SizeOf_test.go rename src/build/{arch/x64/SizeOf.go => sizeof/Signed.go} (67%) create mode 100644 src/build/sizeof/Signed_test.go create mode 100644 src/build/sizeof/Unsigned.go create mode 100644 src/build/sizeof/Unsigned_test.go diff --git a/src/build/arch/x64/Move.go b/src/build/arch/x64/Move.go index 82f5869..cac1d09 100644 --- a/src/build/arch/x64/Move.go +++ b/src/build/arch/x64/Move.go @@ -4,13 +4,14 @@ import ( "encoding/binary" "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/sizeof" ) // MoveRegisterNumber moves an integer into the given register. func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte { w := byte(0) - if SizeOf(int64(number)) == 8 { + if sizeof.Signed(int64(number)) == 8 { w = 1 } diff --git a/src/build/arch/x64/SizeOf_test.go b/src/build/arch/x64/SizeOf_test.go deleted file mode 100644 index eb84a59..0000000 --- a/src/build/arch/x64/SizeOf_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package x64_test - -import ( - "math" - "testing" - - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/go/assert" -) - -func TestSizeOf(t *testing.T) { - assert.Equal(t, x64.SizeOf(0), 1) - assert.Equal(t, x64.SizeOf(math.MinInt8), 1) - assert.Equal(t, x64.SizeOf(math.MaxInt8), 1) - assert.Equal(t, x64.SizeOf(math.MinInt16), 2) - assert.Equal(t, x64.SizeOf(math.MaxInt16), 2) - assert.Equal(t, x64.SizeOf(math.MinInt32), 4) - assert.Equal(t, x64.SizeOf(math.MaxInt32), 4) - assert.Equal(t, x64.SizeOf(math.MinInt64), 8) - assert.Equal(t, x64.SizeOf(math.MaxInt64), 8) -} diff --git a/src/build/arch/x64/encodeNum.go b/src/build/arch/x64/encodeNum.go index 2053dfb..a9a3313 100644 --- a/src/build/arch/x64/encodeNum.go +++ b/src/build/arch/x64/encodeNum.go @@ -4,11 +4,12 @@ import ( "encoding/binary" "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/sizeof" ) // encodeNum encodes an instruction with up to two registers and a number parameter. func encodeNum(code []byte, mod AddressMode, reg cpu.Register, rm cpu.Register, number int, opCode8 byte, opCode32 byte) []byte { - if SizeOf(int64(number)) == 1 { + if sizeof.Signed(int64(number)) == 1 { code = encode(code, mod, reg, rm, 8, opCode8) return append(code, byte(number)) } diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index e38a83f..7094fe2 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -7,6 +7,7 @@ import ( "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/elf" + "git.akyoto.dev/cli/q/src/build/sizeof" ) // Finalize generates the final machine code. @@ -242,7 +243,7 @@ restart: for i, pointer := range pointers { address := pointer.Resolve() - if x64.SizeOf(int64(address)) > int(pointer.Size) { + if sizeof.Signed(int64(address)) > int(pointer.Size) { left := code[:pointer.Position-Address(pointer.OpSize)] right := code[pointer.Position+Address(pointer.Size):] size := pointer.Size + pointer.OpSize diff --git a/src/build/core/CompileAssignArray.go b/src/build/core/CompileAssignArray.go index f519ea0..167d3e3 100644 --- a/src/build/core/CompileAssignArray.go +++ b/src/build/core/CompileAssignArray.go @@ -21,7 +21,7 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { defer f.UseVariable(variable) index := left.Children[1] - offset, _, err := f.Number(index.Token) + offset, err := f.Number(index.Token) if err != nil { return err diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index 6dd7645..fe78ef6 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -21,7 +21,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope return f.ExecuteRegisterRegister(operation, register, variable.Register) case token.Number, token.Rune: - number, _, err := f.Number(operand) + number, err := f.Number(operand) if err != nil { return err diff --git a/src/build/core/ExpressionToMemory.go b/src/build/core/ExpressionToMemory.go index af7ec37..d876c7b 100644 --- a/src/build/core/ExpressionToMemory.go +++ b/src/build/core/ExpressionToMemory.go @@ -4,18 +4,21 @@ import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/sizeof" "git.akyoto.dev/cli/q/src/build/token" ) // ExpressionToMemory puts the result of an expression into the specified memory address. func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) error { if node.IsLeaf() && (node.Token.Kind == token.Number || node.Token.Kind == token.Rune) { - number, size, err := f.Number(node.Token) + number, err := f.Number(node.Token) if err != nil { return err } + size := byte(sizeof.Signed(int64(number))) + if size != memory.Length { return errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) } diff --git a/src/build/core/Number.go b/src/build/core/Number.go index 2cc05a1..f6c14cd 100644 --- a/src/build/core/Number.go +++ b/src/build/core/Number.go @@ -10,45 +10,45 @@ import ( ) // Number tries to convert the token into a numeric value. -func (f *Function) Number(t token.Token) (int, byte, error) { +func (f *Function) Number(t token.Token) (int, error) { switch t.Kind { case token.Number: digits := t.Text(f.File.Bytes) if strings.HasPrefix(digits, "0x") { number, err := strconv.ParseInt(digits[2:], 16, 64) - return int(number), 8, err + return int(number), err } if strings.HasPrefix(digits, "0o") { number, err := strconv.ParseInt(digits[2:], 8, 64) - return int(number), 8, err + return int(number), err } if strings.HasPrefix(digits, "0b") { number, err := strconv.ParseInt(digits[2:], 2, 64) - return int(number), 8, err + return int(number), err } number, err := strconv.Atoi(digits) - return number, 8, err + return number, err case token.Rune: r := t.Bytes(f.File.Bytes) r = r[1 : len(r)-1] if len(r) == 0 { - return 0, 0, errors.New(errors.InvalidRune, f.File, t.Position+1) + return 0, errors.New(errors.InvalidRune, f.File, t.Position+1) } number, size := utf8.DecodeRune(r) if len(r) > size { - return 0, 0, errors.New(errors.InvalidRune, f.File, t.Position+1) + return 0, errors.New(errors.InvalidRune, f.File, t.Position+1) } - return int(number), byte(size), nil + return int(number), nil } - return 0, 0, errors.New(errors.InvalidNumber, f.File, t.Position) + return 0, errors.New(errors.InvalidNumber, f.File, t.Position) } diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 50e2b77..e95d87d 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -26,7 +26,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return nil case token.Number, token.Rune: - number, _, err := f.Number(t) + number, err := f.Number(t) if err != nil { return err diff --git a/src/build/arch/x64/SizeOf.go b/src/build/sizeof/Signed.go similarity index 67% rename from src/build/arch/x64/SizeOf.go rename to src/build/sizeof/Signed.go index 6c7293a..a0fee4a 100644 --- a/src/build/arch/x64/SizeOf.go +++ b/src/build/sizeof/Signed.go @@ -1,9 +1,9 @@ -package x64 +package sizeof import "math" -// SizeOf tells you how many bytes are needed to encode this number. -func SizeOf(number int64) int { +// Signed tells you how many bytes are needed to encode this signed number. +func Signed(number int64) int { switch { case number >= math.MinInt8 && number <= math.MaxInt8: return 1 diff --git a/src/build/sizeof/Signed_test.go b/src/build/sizeof/Signed_test.go new file mode 100644 index 0000000..6ddc15f --- /dev/null +++ b/src/build/sizeof/Signed_test.go @@ -0,0 +1,21 @@ +package sizeof_test + +import ( + "math" + "testing" + + "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/go/assert" +) + +func TestSigned(t *testing.T) { + assert.Equal(t, sizeof.Signed(0), 1) + assert.Equal(t, sizeof.Signed(math.MinInt8), 1) + assert.Equal(t, sizeof.Signed(math.MaxInt8), 1) + assert.Equal(t, sizeof.Signed(math.MinInt16), 2) + assert.Equal(t, sizeof.Signed(math.MaxInt16), 2) + assert.Equal(t, sizeof.Signed(math.MinInt32), 4) + assert.Equal(t, sizeof.Signed(math.MaxInt32), 4) + assert.Equal(t, sizeof.Signed(math.MinInt64), 8) + assert.Equal(t, sizeof.Signed(math.MaxInt64), 8) +} diff --git a/src/build/sizeof/Unsigned.go b/src/build/sizeof/Unsigned.go new file mode 100644 index 0000000..949b77a --- /dev/null +++ b/src/build/sizeof/Unsigned.go @@ -0,0 +1,20 @@ +package sizeof + +import "math" + +// Unsigned tells you how many bytes are needed to encode this unsigned number. +func Unsigned(number uint64) int { + switch { + case number <= math.MaxUint8: + return 1 + + case number <= math.MaxUint16: + return 2 + + case number <= math.MaxUint32: + return 4 + + default: + return 8 + } +} diff --git a/src/build/sizeof/Unsigned_test.go b/src/build/sizeof/Unsigned_test.go new file mode 100644 index 0000000..e9a5dc8 --- /dev/null +++ b/src/build/sizeof/Unsigned_test.go @@ -0,0 +1,17 @@ +package sizeof_test + +import ( + "math" + "testing" + + "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/go/assert" +) + +func TestUnsigned(t *testing.T) { + assert.Equal(t, sizeof.Unsigned(0), 1) + assert.Equal(t, sizeof.Unsigned(math.MaxUint8), 1) + assert.Equal(t, sizeof.Unsigned(math.MaxUint16), 2) + assert.Equal(t, sizeof.Unsigned(math.MaxUint32), 4) + assert.Equal(t, sizeof.Unsigned(math.MaxUint64), 8) +} From 8d4eb9935d006a69661e8e632c4e7ba2cb2e238d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 29 Jul 2024 12:33:28 +0200 Subject: [PATCH 0404/1012] Moved sizeof functions to a separate package --- src/build/arch/x64/Move.go | 3 ++- src/build/arch/x64/SizeOf_test.go | 21 ------------------- src/build/arch/x64/encodeNum.go | 3 ++- src/build/asm/Finalize.go | 3 ++- src/build/core/CompileAssignArray.go | 2 +- src/build/core/ExecuteLeaf.go | 2 +- src/build/core/ExpressionToMemory.go | 5 ++++- src/build/core/Number.go | 18 ++++++++-------- src/build/core/TokenToRegister.go | 2 +- .../{arch/x64/SizeOf.go => sizeof/Signed.go} | 6 +++--- src/build/sizeof/Signed_test.go | 21 +++++++++++++++++++ src/build/sizeof/Unsigned.go | 20 ++++++++++++++++++ src/build/sizeof/Unsigned_test.go | 17 +++++++++++++++ 13 files changed, 83 insertions(+), 40 deletions(-) delete mode 100644 src/build/arch/x64/SizeOf_test.go rename src/build/{arch/x64/SizeOf.go => sizeof/Signed.go} (67%) create mode 100644 src/build/sizeof/Signed_test.go create mode 100644 src/build/sizeof/Unsigned.go create mode 100644 src/build/sizeof/Unsigned_test.go diff --git a/src/build/arch/x64/Move.go b/src/build/arch/x64/Move.go index 82f5869..cac1d09 100644 --- a/src/build/arch/x64/Move.go +++ b/src/build/arch/x64/Move.go @@ -4,13 +4,14 @@ import ( "encoding/binary" "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/sizeof" ) // MoveRegisterNumber moves an integer into the given register. func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte { w := byte(0) - if SizeOf(int64(number)) == 8 { + if sizeof.Signed(int64(number)) == 8 { w = 1 } diff --git a/src/build/arch/x64/SizeOf_test.go b/src/build/arch/x64/SizeOf_test.go deleted file mode 100644 index eb84a59..0000000 --- a/src/build/arch/x64/SizeOf_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package x64_test - -import ( - "math" - "testing" - - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/go/assert" -) - -func TestSizeOf(t *testing.T) { - assert.Equal(t, x64.SizeOf(0), 1) - assert.Equal(t, x64.SizeOf(math.MinInt8), 1) - assert.Equal(t, x64.SizeOf(math.MaxInt8), 1) - assert.Equal(t, x64.SizeOf(math.MinInt16), 2) - assert.Equal(t, x64.SizeOf(math.MaxInt16), 2) - assert.Equal(t, x64.SizeOf(math.MinInt32), 4) - assert.Equal(t, x64.SizeOf(math.MaxInt32), 4) - assert.Equal(t, x64.SizeOf(math.MinInt64), 8) - assert.Equal(t, x64.SizeOf(math.MaxInt64), 8) -} diff --git a/src/build/arch/x64/encodeNum.go b/src/build/arch/x64/encodeNum.go index 2053dfb..a9a3313 100644 --- a/src/build/arch/x64/encodeNum.go +++ b/src/build/arch/x64/encodeNum.go @@ -4,11 +4,12 @@ import ( "encoding/binary" "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/sizeof" ) // encodeNum encodes an instruction with up to two registers and a number parameter. func encodeNum(code []byte, mod AddressMode, reg cpu.Register, rm cpu.Register, number int, opCode8 byte, opCode32 byte) []byte { - if SizeOf(int64(number)) == 1 { + if sizeof.Signed(int64(number)) == 1 { code = encode(code, mod, reg, rm, 8, opCode8) return append(code, byte(number)) } diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index e38a83f..7094fe2 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -7,6 +7,7 @@ import ( "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/elf" + "git.akyoto.dev/cli/q/src/build/sizeof" ) // Finalize generates the final machine code. @@ -242,7 +243,7 @@ restart: for i, pointer := range pointers { address := pointer.Resolve() - if x64.SizeOf(int64(address)) > int(pointer.Size) { + if sizeof.Signed(int64(address)) > int(pointer.Size) { left := code[:pointer.Position-Address(pointer.OpSize)] right := code[pointer.Position+Address(pointer.Size):] size := pointer.Size + pointer.OpSize diff --git a/src/build/core/CompileAssignArray.go b/src/build/core/CompileAssignArray.go index f519ea0..167d3e3 100644 --- a/src/build/core/CompileAssignArray.go +++ b/src/build/core/CompileAssignArray.go @@ -21,7 +21,7 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { defer f.UseVariable(variable) index := left.Children[1] - offset, _, err := f.Number(index.Token) + offset, err := f.Number(index.Token) if err != nil { return err diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index 6dd7645..fe78ef6 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -21,7 +21,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope return f.ExecuteRegisterRegister(operation, register, variable.Register) case token.Number, token.Rune: - number, _, err := f.Number(operand) + number, err := f.Number(operand) if err != nil { return err diff --git a/src/build/core/ExpressionToMemory.go b/src/build/core/ExpressionToMemory.go index af7ec37..d876c7b 100644 --- a/src/build/core/ExpressionToMemory.go +++ b/src/build/core/ExpressionToMemory.go @@ -4,18 +4,21 @@ import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/sizeof" "git.akyoto.dev/cli/q/src/build/token" ) // ExpressionToMemory puts the result of an expression into the specified memory address. func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) error { if node.IsLeaf() && (node.Token.Kind == token.Number || node.Token.Kind == token.Rune) { - number, size, err := f.Number(node.Token) + number, err := f.Number(node.Token) if err != nil { return err } + size := byte(sizeof.Signed(int64(number))) + if size != memory.Length { return errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) } diff --git a/src/build/core/Number.go b/src/build/core/Number.go index 2cc05a1..f6c14cd 100644 --- a/src/build/core/Number.go +++ b/src/build/core/Number.go @@ -10,45 +10,45 @@ import ( ) // Number tries to convert the token into a numeric value. -func (f *Function) Number(t token.Token) (int, byte, error) { +func (f *Function) Number(t token.Token) (int, error) { switch t.Kind { case token.Number: digits := t.Text(f.File.Bytes) if strings.HasPrefix(digits, "0x") { number, err := strconv.ParseInt(digits[2:], 16, 64) - return int(number), 8, err + return int(number), err } if strings.HasPrefix(digits, "0o") { number, err := strconv.ParseInt(digits[2:], 8, 64) - return int(number), 8, err + return int(number), err } if strings.HasPrefix(digits, "0b") { number, err := strconv.ParseInt(digits[2:], 2, 64) - return int(number), 8, err + return int(number), err } number, err := strconv.Atoi(digits) - return number, 8, err + return number, err case token.Rune: r := t.Bytes(f.File.Bytes) r = r[1 : len(r)-1] if len(r) == 0 { - return 0, 0, errors.New(errors.InvalidRune, f.File, t.Position+1) + return 0, errors.New(errors.InvalidRune, f.File, t.Position+1) } number, size := utf8.DecodeRune(r) if len(r) > size { - return 0, 0, errors.New(errors.InvalidRune, f.File, t.Position+1) + return 0, errors.New(errors.InvalidRune, f.File, t.Position+1) } - return int(number), byte(size), nil + return int(number), nil } - return 0, 0, errors.New(errors.InvalidNumber, f.File, t.Position) + return 0, errors.New(errors.InvalidNumber, f.File, t.Position) } diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 50e2b77..e95d87d 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -26,7 +26,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return nil case token.Number, token.Rune: - number, _, err := f.Number(t) + number, err := f.Number(t) if err != nil { return err diff --git a/src/build/arch/x64/SizeOf.go b/src/build/sizeof/Signed.go similarity index 67% rename from src/build/arch/x64/SizeOf.go rename to src/build/sizeof/Signed.go index 6c7293a..a0fee4a 100644 --- a/src/build/arch/x64/SizeOf.go +++ b/src/build/sizeof/Signed.go @@ -1,9 +1,9 @@ -package x64 +package sizeof import "math" -// SizeOf tells you how many bytes are needed to encode this number. -func SizeOf(number int64) int { +// Signed tells you how many bytes are needed to encode this signed number. +func Signed(number int64) int { switch { case number >= math.MinInt8 && number <= math.MaxInt8: return 1 diff --git a/src/build/sizeof/Signed_test.go b/src/build/sizeof/Signed_test.go new file mode 100644 index 0000000..6ddc15f --- /dev/null +++ b/src/build/sizeof/Signed_test.go @@ -0,0 +1,21 @@ +package sizeof_test + +import ( + "math" + "testing" + + "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/go/assert" +) + +func TestSigned(t *testing.T) { + assert.Equal(t, sizeof.Signed(0), 1) + assert.Equal(t, sizeof.Signed(math.MinInt8), 1) + assert.Equal(t, sizeof.Signed(math.MaxInt8), 1) + assert.Equal(t, sizeof.Signed(math.MinInt16), 2) + assert.Equal(t, sizeof.Signed(math.MaxInt16), 2) + assert.Equal(t, sizeof.Signed(math.MinInt32), 4) + assert.Equal(t, sizeof.Signed(math.MaxInt32), 4) + assert.Equal(t, sizeof.Signed(math.MinInt64), 8) + assert.Equal(t, sizeof.Signed(math.MaxInt64), 8) +} diff --git a/src/build/sizeof/Unsigned.go b/src/build/sizeof/Unsigned.go new file mode 100644 index 0000000..949b77a --- /dev/null +++ b/src/build/sizeof/Unsigned.go @@ -0,0 +1,20 @@ +package sizeof + +import "math" + +// Unsigned tells you how many bytes are needed to encode this unsigned number. +func Unsigned(number uint64) int { + switch { + case number <= math.MaxUint8: + return 1 + + case number <= math.MaxUint16: + return 2 + + case number <= math.MaxUint32: + return 4 + + default: + return 8 + } +} diff --git a/src/build/sizeof/Unsigned_test.go b/src/build/sizeof/Unsigned_test.go new file mode 100644 index 0000000..e9a5dc8 --- /dev/null +++ b/src/build/sizeof/Unsigned_test.go @@ -0,0 +1,17 @@ +package sizeof_test + +import ( + "math" + "testing" + + "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/go/assert" +) + +func TestUnsigned(t *testing.T) { + assert.Equal(t, sizeof.Unsigned(0), 1) + assert.Equal(t, sizeof.Unsigned(math.MaxUint8), 1) + assert.Equal(t, sizeof.Unsigned(math.MaxUint16), 2) + assert.Equal(t, sizeof.Unsigned(math.MaxUint32), 4) + assert.Equal(t, sizeof.Unsigned(math.MaxUint64), 8) +} From 67c7d1ec991819e7f27fc34828a9b77b4a97cb80 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 29 Jul 2024 14:44:16 +0200 Subject: [PATCH 0405/1012] Implemented constant folding --- src/build/config/config.go | 11 +++- src/build/core/CompileASTNode.go | 6 +++ src/build/core/CompileAssert.go | 5 ++ src/build/core/ExpressionToRegister.go | 5 ++ src/build/core/Fold.go | 74 ++++++++++++++++++++++++++ src/build/expression/Expression.go | 2 + src/build/token/Token.go | 5 ++ tests/programs_test.go | 11 ++++ 8 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/build/core/Fold.go diff --git a/src/build/config/config.go b/src/build/config/config.go index 99791d2..7ff0e87 100644 --- a/src/build/config/config.go +++ b/src/build/config/config.go @@ -15,6 +15,15 @@ const ( ) var ( + // Shows the assembly instructions at the end of the compilation. Assembler = false - Dry = false + + // Calculates the result of operations on constants at compile time. + ConstantFold = true + + // Skips writing the executable to disk. + Dry = false + + // Skips compiling assert statements. + SkipAsserts = false ) diff --git a/src/build/core/CompileASTNode.go b/src/build/core/CompileASTNode.go index 16cb0bd..eac1e31 100644 --- a/src/build/core/CompileASTNode.go +++ b/src/build/core/CompileASTNode.go @@ -8,21 +8,27 @@ import ( func (f *Function) CompileASTNode(node ast.Node) error { switch node := node.(type) { case *ast.Assert: + f.Fold(node.Condition) return f.CompileAssert(node) case *ast.Assign: + f.Fold(node.Expression) return f.CompileAssign(node) case *ast.Call: + f.Fold(node.Expression) return f.CompileCall(node.Expression) case *ast.Define: + f.Fold(node.Expression) return f.CompileDefinition(node) case *ast.Return: + f.Fold(node.Value) return f.CompileReturn(node) case *ast.If: + f.Fold(node.Condition) return f.CompileIf(node) case *ast.Loop: diff --git a/src/build/core/CompileAssert.go b/src/build/core/CompileAssert.go index 2715e44..05cc38c 100644 --- a/src/build/core/CompileAssert.go +++ b/src/build/core/CompileAssert.go @@ -5,10 +5,15 @@ import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/config" ) // CompileAssert compiles an assertion. func (f *Function) CompileAssert(assert *ast.Assert) error { + if config.SkipAsserts { + return nil + } + f.count.assert++ success := fmt.Sprintf("%s_assert_%d_true", f.Name, f.count.assert) fail := fmt.Sprintf("%s_assert_%d_false", f.Name, f.count.assert) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 4c1d69c..a49264f 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -10,6 +10,11 @@ import ( // ExpressionToRegister puts the result of an expression into the specified register. func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) error { + if node.IsFolded { + f.RegisterNumber(asm.MOVE, register, node.Value) + return nil + } + if node.IsLeaf() { return f.TokenToRegister(node.Token, register) } diff --git a/src/build/core/Fold.go b/src/build/core/Fold.go new file mode 100644 index 0000000..11b6d59 --- /dev/null +++ b/src/build/core/Fold.go @@ -0,0 +1,74 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Fold will try to precalculate the results of operations with constants. +func (f *Function) Fold(expr *expression.Expression) error { + if !config.ConstantFold { + return nil + } + + if expr == nil { + return nil + } + + if expr.IsLeaf() { + if expr.Token.IsNumeric() { + value, err := f.Number(expr.Token) + expr.Value = value + expr.IsFolded = true + return err + } + + return nil + } + + canFold := true + + for _, child := range expr.Children { + f.Fold(child) + + if !child.IsFolded { + canFold = false + } + } + + if !canFold || len(expr.Children) != 2 { + return nil + } + + a := expr.Children[0].Value + b := expr.Children[1].Value + + switch expr.Token.Kind { + case token.Or: + expr.Value = a | b + case token.And: + expr.Value = a & b + case token.Xor: + expr.Value = a ^ b + case token.Add: + expr.Value = a + b + case token.Sub: + expr.Value = a - b + case token.Mul: + expr.Value = a * b + case token.Div: + expr.Value = a / b + case token.Mod: + expr.Value = a % b + case token.Shl: + expr.Value = a << b + case token.Shr: + expr.Value = a >> b + default: + return nil + } + + expr.IsFolded = true + return nil +} diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 7b39b12..057476b 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -12,6 +12,8 @@ type Expression struct { Children []*Expression Token token.Token Precedence int8 + Value int + IsFolded bool } // New creates a new expression. diff --git a/src/build/token/Token.go b/src/build/token/Token.go index caac815..3885218 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -38,6 +38,11 @@ func (t Token) IsKeyword() bool { return t.Kind > _keywords && t.Kind < _keywordsEnd } +// IsNumeric returns true if the token is a number or rune. +func (t Token) IsNumeric() bool { + return t.Kind == Number || t.Kind == Rune +} + // IsOperator returns true if the token is an operator. func (t Token) IsOperator() bool { return t.Kind > _operators && t.Kind < _operatorsEnd diff --git a/tests/programs_test.go b/tests/programs_test.go index 2bead24..429695f 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -8,6 +8,7 @@ import ( "testing" "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/go/assert" ) @@ -52,11 +53,21 @@ var programs = []struct { } func TestPrograms(t *testing.T) { + config.ConstantFold = false + for _, test := range programs { t.Run(test.Name, func(t *testing.T) { run(t, filepath.Join("programs", test.Name+".q"), test.Input, test.Output, test.ExitCode) }) } + + config.ConstantFold = true + + for _, test := range programs { + t.Run(test.Name+" (optimized)", func(t *testing.T) { + run(t, filepath.Join("programs", test.Name+".q"), test.Input, test.Output, test.ExitCode) + }) + } } func BenchmarkPrograms(b *testing.B) { From 5b1d45672051e9d54d8b9ec92581f66b46abd9a6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 29 Jul 2024 14:44:16 +0200 Subject: [PATCH 0406/1012] Implemented constant folding --- src/build/config/config.go | 11 +++- src/build/core/CompileASTNode.go | 6 +++ src/build/core/CompileAssert.go | 5 ++ src/build/core/ExpressionToRegister.go | 5 ++ src/build/core/Fold.go | 74 ++++++++++++++++++++++++++ src/build/expression/Expression.go | 2 + src/build/token/Token.go | 5 ++ tests/programs_test.go | 11 ++++ 8 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/build/core/Fold.go diff --git a/src/build/config/config.go b/src/build/config/config.go index 99791d2..7ff0e87 100644 --- a/src/build/config/config.go +++ b/src/build/config/config.go @@ -15,6 +15,15 @@ const ( ) var ( + // Shows the assembly instructions at the end of the compilation. Assembler = false - Dry = false + + // Calculates the result of operations on constants at compile time. + ConstantFold = true + + // Skips writing the executable to disk. + Dry = false + + // Skips compiling assert statements. + SkipAsserts = false ) diff --git a/src/build/core/CompileASTNode.go b/src/build/core/CompileASTNode.go index 16cb0bd..eac1e31 100644 --- a/src/build/core/CompileASTNode.go +++ b/src/build/core/CompileASTNode.go @@ -8,21 +8,27 @@ import ( func (f *Function) CompileASTNode(node ast.Node) error { switch node := node.(type) { case *ast.Assert: + f.Fold(node.Condition) return f.CompileAssert(node) case *ast.Assign: + f.Fold(node.Expression) return f.CompileAssign(node) case *ast.Call: + f.Fold(node.Expression) return f.CompileCall(node.Expression) case *ast.Define: + f.Fold(node.Expression) return f.CompileDefinition(node) case *ast.Return: + f.Fold(node.Value) return f.CompileReturn(node) case *ast.If: + f.Fold(node.Condition) return f.CompileIf(node) case *ast.Loop: diff --git a/src/build/core/CompileAssert.go b/src/build/core/CompileAssert.go index 2715e44..05cc38c 100644 --- a/src/build/core/CompileAssert.go +++ b/src/build/core/CompileAssert.go @@ -5,10 +5,15 @@ import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/config" ) // CompileAssert compiles an assertion. func (f *Function) CompileAssert(assert *ast.Assert) error { + if config.SkipAsserts { + return nil + } + f.count.assert++ success := fmt.Sprintf("%s_assert_%d_true", f.Name, f.count.assert) fail := fmt.Sprintf("%s_assert_%d_false", f.Name, f.count.assert) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 4c1d69c..a49264f 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -10,6 +10,11 @@ import ( // ExpressionToRegister puts the result of an expression into the specified register. func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) error { + if node.IsFolded { + f.RegisterNumber(asm.MOVE, register, node.Value) + return nil + } + if node.IsLeaf() { return f.TokenToRegister(node.Token, register) } diff --git a/src/build/core/Fold.go b/src/build/core/Fold.go new file mode 100644 index 0000000..11b6d59 --- /dev/null +++ b/src/build/core/Fold.go @@ -0,0 +1,74 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Fold will try to precalculate the results of operations with constants. +func (f *Function) Fold(expr *expression.Expression) error { + if !config.ConstantFold { + return nil + } + + if expr == nil { + return nil + } + + if expr.IsLeaf() { + if expr.Token.IsNumeric() { + value, err := f.Number(expr.Token) + expr.Value = value + expr.IsFolded = true + return err + } + + return nil + } + + canFold := true + + for _, child := range expr.Children { + f.Fold(child) + + if !child.IsFolded { + canFold = false + } + } + + if !canFold || len(expr.Children) != 2 { + return nil + } + + a := expr.Children[0].Value + b := expr.Children[1].Value + + switch expr.Token.Kind { + case token.Or: + expr.Value = a | b + case token.And: + expr.Value = a & b + case token.Xor: + expr.Value = a ^ b + case token.Add: + expr.Value = a + b + case token.Sub: + expr.Value = a - b + case token.Mul: + expr.Value = a * b + case token.Div: + expr.Value = a / b + case token.Mod: + expr.Value = a % b + case token.Shl: + expr.Value = a << b + case token.Shr: + expr.Value = a >> b + default: + return nil + } + + expr.IsFolded = true + return nil +} diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 7b39b12..057476b 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -12,6 +12,8 @@ type Expression struct { Children []*Expression Token token.Token Precedence int8 + Value int + IsFolded bool } // New creates a new expression. diff --git a/src/build/token/Token.go b/src/build/token/Token.go index caac815..3885218 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -38,6 +38,11 @@ func (t Token) IsKeyword() bool { return t.Kind > _keywords && t.Kind < _keywordsEnd } +// IsNumeric returns true if the token is a number or rune. +func (t Token) IsNumeric() bool { + return t.Kind == Number || t.Kind == Rune +} + // IsOperator returns true if the token is an operator. func (t Token) IsOperator() bool { return t.Kind > _operators && t.Kind < _operatorsEnd diff --git a/tests/programs_test.go b/tests/programs_test.go index 2bead24..429695f 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -8,6 +8,7 @@ import ( "testing" "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/go/assert" ) @@ -52,11 +53,21 @@ var programs = []struct { } func TestPrograms(t *testing.T) { + config.ConstantFold = false + for _, test := range programs { t.Run(test.Name, func(t *testing.T) { run(t, filepath.Join("programs", test.Name+".q"), test.Input, test.Output, test.ExitCode) }) } + + config.ConstantFold = true + + for _, test := range programs { + t.Run(test.Name+" (optimized)", func(t *testing.T) { + run(t, filepath.Join("programs", test.Name+".q"), test.Input, test.Output, test.ExitCode) + }) + } } func BenchmarkPrograms(b *testing.B) { From 21f40859cbe45f743f803488716ede29254da31d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 29 Jul 2024 14:45:53 +0200 Subject: [PATCH 0407/1012] Updated todo list --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f03c58a..8598efb 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ This is what generates expressions from tokens. ### Optimizations - [x] Exclude unused functions -- [ ] Expression folding +- [x] Constant folding - [ ] Function call inlining - [ ] Loop unrolls From ce7468523173fad8ceabd87ddeab186735db4651 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 29 Jul 2024 14:45:53 +0200 Subject: [PATCH 0408/1012] Updated todo list --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f03c58a..8598efb 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ This is what generates expressions from tokens. ### Optimizations - [x] Exclude unused functions -- [ ] Expression folding +- [x] Constant folding - [ ] Function call inlining - [ ] Loop unrolls From e6c201d9cb5b38e49ed924b286c3ada5a29edcb8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 29 Jul 2024 18:03:15 +0200 Subject: [PATCH 0409/1012] Improved tests --- src/build/compiler/Result.go | 30 +++++++++++++++++++++--------- src/cli/Build.go | 2 +- tests/examples_test.go | 14 ++++++++++++-- tests/programs_test.go | 35 ++++++++++++++++++----------------- 4 files changed, 52 insertions(+), 29 deletions(-) diff --git a/src/build/compiler/Result.go b/src/build/compiler/Result.go index ee7c8c1..68c37ca 100644 --- a/src/build/compiler/Result.go +++ b/src/build/compiler/Result.go @@ -2,6 +2,7 @@ package compiler import ( "bufio" + "io" "os" "git.akyoto.dev/cli/q/src/build/arch/x64" @@ -83,24 +84,27 @@ func (r *Result) PrintInstructions() { }) } -// Write writes an executable file to disk. -func (r *Result) Write(path string) error { +// Write writes the executable to the given writer. +func (r *Result) Write(writer io.Writer) error { code, data := r.finalize() - return write(path, code, data) + return write(writer, code, data) } -// write writes an executable file to disk. -func write(path string, code []byte, data []byte) error { +// Write writes an executable file to disk. +func (r *Result) WriteFile(path string) error { file, err := os.Create(path) if err != nil { return err } - buffer := bufio.NewWriter(file) - executable := elf.New(code, data) - executable.Write(buffer) - buffer.Flush() + err = r.Write(file) + + if err != nil { + file.Close() + return err + } + err = file.Close() if err != nil { @@ -109,3 +113,11 @@ func write(path string, code []byte, data []byte) error { return os.Chmod(path, 0755) } + +// write writes an executable file to the given writer. +func write(writer io.Writer, code []byte, data []byte) error { + buffer := bufio.NewWriter(writer) + executable := elf.New(code, data) + executable.Write(buffer) + return buffer.Flush() +} diff --git a/src/cli/Build.go b/src/cli/Build.go index 9f54a16..ba79ec6 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -74,5 +74,5 @@ func makeExecutable(b *build.Build) error { return nil } - return result.Write(b.Executable()) + return result.WriteFile(b.Executable()) } diff --git a/tests/examples_test.go b/tests/examples_test.go index 2a5b41b..5c2ee12 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -5,6 +5,7 @@ import ( "testing" "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/go/assert" ) @@ -18,13 +19,22 @@ var examples = []struct { {"factorial", "", "", 120}, {"fibonacci", "", "", 55}, {"array", "", "Hello", 0}, + {"echo", "Echo", "Echo", 0}, {"itoa", "", "9223372036854775807", 0}, } func TestExamples(t *testing.T) { for _, test := range examples { - t.Run(test.Name, func(t *testing.T) { - run(t, filepath.Join("..", "examples", test.Name), test.Input, test.Output, test.ExitCode) + directory := filepath.Join("..", "examples", test.Name) + + t.Run(test.Name+"/debug", func(t *testing.T) { + config.ConstantFold = false + run(t, directory, "debug", test.Name, test.Input, test.Output, test.ExitCode) + }) + + t.Run(test.Name+"/release", func(t *testing.T) { + config.ConstantFold = true + run(t, directory, "release", test.Name, test.Input, test.Output, test.ExitCode) }) } } diff --git a/tests/programs_test.go b/tests/programs_test.go index 429695f..5dfce4e 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -53,19 +53,17 @@ var programs = []struct { } func TestPrograms(t *testing.T) { - config.ConstantFold = false - for _, test := range programs { - t.Run(test.Name, func(t *testing.T) { - run(t, filepath.Join("programs", test.Name+".q"), test.Input, test.Output, test.ExitCode) + file := filepath.Join("programs", test.Name+".q") + + t.Run(test.Name+"/debug", func(t *testing.T) { + config.ConstantFold = false + run(t, file, "debug", test.Name, test.Input, test.Output, test.ExitCode) }) - } - config.ConstantFold = true - - for _, test := range programs { - t.Run(test.Name+" (optimized)", func(t *testing.T) { - run(t, filepath.Join("programs", test.Name+".q"), test.Input, test.Output, test.ExitCode) + t.Run(test.Name+"/release", func(t *testing.T) { + config.ConstantFold = true + run(t, file, "release", test.Name, test.Input, test.Output, test.ExitCode) }) } } @@ -84,21 +82,24 @@ func BenchmarkPrograms(b *testing.B) { } // run builds and runs the file to check if the output matches the expected output. -func run(t *testing.T, name string, input string, expectedOutput string, expectedExitCode int) { - b := build.New(name) - assert.True(t, len(b.Executable()) > 0) - +func run(t *testing.T, path string, version string, name string, input string, expectedOutput string, expectedExitCode int) { + b := build.New(path) result, err := b.Run() assert.Nil(t, err) - err = result.Write(b.Executable()) + directory := filepath.Join(os.TempDir(), "q", version) + err = os.MkdirAll(directory, 0755) assert.Nil(t, err) - stat, err := os.Stat(b.Executable()) + executable := filepath.Join(directory, name) + err = result.WriteFile(executable) + assert.Nil(t, err) + + stat, err := os.Stat(executable) assert.Nil(t, err) assert.True(t, stat.Size() > 0) - cmd := exec.Command(b.Executable()) + cmd := exec.Command(executable) cmd.Stdin = strings.NewReader(input) output, err := cmd.Output() exitCode := 0 From ae8e46de4db81b566d98cf9170aa822e22c7b1ad Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 29 Jul 2024 18:03:15 +0200 Subject: [PATCH 0410/1012] Improved tests --- src/build/compiler/Result.go | 30 +++++++++++++++++++++--------- src/cli/Build.go | 2 +- tests/examples_test.go | 14 ++++++++++++-- tests/programs_test.go | 35 ++++++++++++++++++----------------- 4 files changed, 52 insertions(+), 29 deletions(-) diff --git a/src/build/compiler/Result.go b/src/build/compiler/Result.go index ee7c8c1..68c37ca 100644 --- a/src/build/compiler/Result.go +++ b/src/build/compiler/Result.go @@ -2,6 +2,7 @@ package compiler import ( "bufio" + "io" "os" "git.akyoto.dev/cli/q/src/build/arch/x64" @@ -83,24 +84,27 @@ func (r *Result) PrintInstructions() { }) } -// Write writes an executable file to disk. -func (r *Result) Write(path string) error { +// Write writes the executable to the given writer. +func (r *Result) Write(writer io.Writer) error { code, data := r.finalize() - return write(path, code, data) + return write(writer, code, data) } -// write writes an executable file to disk. -func write(path string, code []byte, data []byte) error { +// Write writes an executable file to disk. +func (r *Result) WriteFile(path string) error { file, err := os.Create(path) if err != nil { return err } - buffer := bufio.NewWriter(file) - executable := elf.New(code, data) - executable.Write(buffer) - buffer.Flush() + err = r.Write(file) + + if err != nil { + file.Close() + return err + } + err = file.Close() if err != nil { @@ -109,3 +113,11 @@ func write(path string, code []byte, data []byte) error { return os.Chmod(path, 0755) } + +// write writes an executable file to the given writer. +func write(writer io.Writer, code []byte, data []byte) error { + buffer := bufio.NewWriter(writer) + executable := elf.New(code, data) + executable.Write(buffer) + return buffer.Flush() +} diff --git a/src/cli/Build.go b/src/cli/Build.go index 9f54a16..ba79ec6 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -74,5 +74,5 @@ func makeExecutable(b *build.Build) error { return nil } - return result.Write(b.Executable()) + return result.WriteFile(b.Executable()) } diff --git a/tests/examples_test.go b/tests/examples_test.go index 2a5b41b..5c2ee12 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -5,6 +5,7 @@ import ( "testing" "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/go/assert" ) @@ -18,13 +19,22 @@ var examples = []struct { {"factorial", "", "", 120}, {"fibonacci", "", "", 55}, {"array", "", "Hello", 0}, + {"echo", "Echo", "Echo", 0}, {"itoa", "", "9223372036854775807", 0}, } func TestExamples(t *testing.T) { for _, test := range examples { - t.Run(test.Name, func(t *testing.T) { - run(t, filepath.Join("..", "examples", test.Name), test.Input, test.Output, test.ExitCode) + directory := filepath.Join("..", "examples", test.Name) + + t.Run(test.Name+"/debug", func(t *testing.T) { + config.ConstantFold = false + run(t, directory, "debug", test.Name, test.Input, test.Output, test.ExitCode) + }) + + t.Run(test.Name+"/release", func(t *testing.T) { + config.ConstantFold = true + run(t, directory, "release", test.Name, test.Input, test.Output, test.ExitCode) }) } } diff --git a/tests/programs_test.go b/tests/programs_test.go index 429695f..5dfce4e 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -53,19 +53,17 @@ var programs = []struct { } func TestPrograms(t *testing.T) { - config.ConstantFold = false - for _, test := range programs { - t.Run(test.Name, func(t *testing.T) { - run(t, filepath.Join("programs", test.Name+".q"), test.Input, test.Output, test.ExitCode) + file := filepath.Join("programs", test.Name+".q") + + t.Run(test.Name+"/debug", func(t *testing.T) { + config.ConstantFold = false + run(t, file, "debug", test.Name, test.Input, test.Output, test.ExitCode) }) - } - config.ConstantFold = true - - for _, test := range programs { - t.Run(test.Name+" (optimized)", func(t *testing.T) { - run(t, filepath.Join("programs", test.Name+".q"), test.Input, test.Output, test.ExitCode) + t.Run(test.Name+"/release", func(t *testing.T) { + config.ConstantFold = true + run(t, file, "release", test.Name, test.Input, test.Output, test.ExitCode) }) } } @@ -84,21 +82,24 @@ func BenchmarkPrograms(b *testing.B) { } // run builds and runs the file to check if the output matches the expected output. -func run(t *testing.T, name string, input string, expectedOutput string, expectedExitCode int) { - b := build.New(name) - assert.True(t, len(b.Executable()) > 0) - +func run(t *testing.T, path string, version string, name string, input string, expectedOutput string, expectedExitCode int) { + b := build.New(path) result, err := b.Run() assert.Nil(t, err) - err = result.Write(b.Executable()) + directory := filepath.Join(os.TempDir(), "q", version) + err = os.MkdirAll(directory, 0755) assert.Nil(t, err) - stat, err := os.Stat(b.Executable()) + executable := filepath.Join(directory, name) + err = result.WriteFile(executable) + assert.Nil(t, err) + + stat, err := os.Stat(executable) assert.Nil(t, err) assert.True(t, stat.Size() > 0) - cmd := exec.Command(b.Executable()) + cmd := exec.Command(executable) cmd.Stdin = strings.NewReader(input) output, err := cmd.Output() exitCode := 0 From 9e312a347762086e225fcc9e4ba6898906fad41f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 30 Jul 2024 12:20:33 +0200 Subject: [PATCH 0411/1012] Simplified code --- src/build/core/AddBytes.go | 13 +++++++++++++ src/build/core/ExecuteRegisterRegister.go | 22 +++++++++++----------- src/build/core/TokenToRegister.go | 8 ++------ 3 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 src/build/core/AddBytes.go diff --git a/src/build/core/AddBytes.go b/src/build/core/AddBytes.go new file mode 100644 index 0000000..2e793ea --- /dev/null +++ b/src/build/core/AddBytes.go @@ -0,0 +1,13 @@ +package core + +import ( + "fmt" +) + +// AddBytes adds a sequence of bytes and returns its address as a label. +func (f *Function) AddBytes(value []byte) string { + f.count.data++ + label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data) + f.Assembler.SetData(label, value) + return label +} diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index bea03e9..aa8006f 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -8,37 +8,37 @@ import ( ) // ExecuteRegisterRegister performs an operation on two registers. -func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { +func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.Register, operand cpu.Register) error { switch operation.Kind { case token.Add, token.AddAssign: - f.RegisterRegister(asm.ADD, destination, source) + f.RegisterRegister(asm.ADD, register, operand) case token.Sub, token.SubAssign: - f.RegisterRegister(asm.SUB, destination, source) + f.RegisterRegister(asm.SUB, register, operand) case token.Mul, token.MulAssign: - f.RegisterRegister(asm.MUL, destination, source) + f.RegisterRegister(asm.MUL, register, operand) case token.Div, token.DivAssign: - f.RegisterRegister(asm.DIV, destination, source) + f.RegisterRegister(asm.DIV, register, operand) case token.Mod, token.ModAssign: - f.RegisterRegister(asm.MODULO, destination, source) + f.RegisterRegister(asm.MODULO, register, operand) case token.And, token.AndAssign: - f.RegisterRegister(asm.AND, destination, source) + f.RegisterRegister(asm.AND, register, operand) case token.Or, token.OrAssign: - f.RegisterRegister(asm.OR, destination, source) + f.RegisterRegister(asm.OR, register, operand) case token.Xor, token.XorAssign: - f.RegisterRegister(asm.XOR, destination, source) + f.RegisterRegister(asm.XOR, register, operand) case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: - f.RegisterRegister(asm.COMPARE, destination, source) + f.RegisterRegister(asm.COMPARE, register, operand) case token.Assign: - f.RegisterRegister(asm.MOVE, destination, source) + f.RegisterRegister(asm.MOVE, register, operand) default: return errors.New(&errors.InvalidOperator{Operator: operation.Text(f.File.Bytes)}, f.File, operation.Position) diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index e95d87d..0ed4e0a 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" @@ -36,10 +34,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return nil case token.String: - f.count.data++ - label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data) - value := t.Bytes(f.File.Bytes)[1 : t.Length-1] - f.Assembler.SetData(label, value) + data := t.Bytes(f.File.Bytes)[1 : t.Length-1] + label := f.AddBytes(data) f.RegisterLabel(asm.MOVE, register, label) return nil From 265ab988d952285b82783f455e52114f6bad573e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 30 Jul 2024 12:20:33 +0200 Subject: [PATCH 0412/1012] Simplified code --- src/build/core/AddBytes.go | 13 +++++++++++++ src/build/core/ExecuteRegisterRegister.go | 22 +++++++++++----------- src/build/core/TokenToRegister.go | 8 ++------ 3 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 src/build/core/AddBytes.go diff --git a/src/build/core/AddBytes.go b/src/build/core/AddBytes.go new file mode 100644 index 0000000..2e793ea --- /dev/null +++ b/src/build/core/AddBytes.go @@ -0,0 +1,13 @@ +package core + +import ( + "fmt" +) + +// AddBytes adds a sequence of bytes and returns its address as a label. +func (f *Function) AddBytes(value []byte) string { + f.count.data++ + label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data) + f.Assembler.SetData(label, value) + return label +} diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index bea03e9..aa8006f 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -8,37 +8,37 @@ import ( ) // ExecuteRegisterRegister performs an operation on two registers. -func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { +func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.Register, operand cpu.Register) error { switch operation.Kind { case token.Add, token.AddAssign: - f.RegisterRegister(asm.ADD, destination, source) + f.RegisterRegister(asm.ADD, register, operand) case token.Sub, token.SubAssign: - f.RegisterRegister(asm.SUB, destination, source) + f.RegisterRegister(asm.SUB, register, operand) case token.Mul, token.MulAssign: - f.RegisterRegister(asm.MUL, destination, source) + f.RegisterRegister(asm.MUL, register, operand) case token.Div, token.DivAssign: - f.RegisterRegister(asm.DIV, destination, source) + f.RegisterRegister(asm.DIV, register, operand) case token.Mod, token.ModAssign: - f.RegisterRegister(asm.MODULO, destination, source) + f.RegisterRegister(asm.MODULO, register, operand) case token.And, token.AndAssign: - f.RegisterRegister(asm.AND, destination, source) + f.RegisterRegister(asm.AND, register, operand) case token.Or, token.OrAssign: - f.RegisterRegister(asm.OR, destination, source) + f.RegisterRegister(asm.OR, register, operand) case token.Xor, token.XorAssign: - f.RegisterRegister(asm.XOR, destination, source) + f.RegisterRegister(asm.XOR, register, operand) case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: - f.RegisterRegister(asm.COMPARE, destination, source) + f.RegisterRegister(asm.COMPARE, register, operand) case token.Assign: - f.RegisterRegister(asm.MOVE, destination, source) + f.RegisterRegister(asm.MOVE, register, operand) default: return errors.New(&errors.InvalidOperator{Operator: operation.Text(f.File.Bytes)}, f.File, operation.Position) diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index e95d87d..0ed4e0a 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" @@ -36,10 +34,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return nil case token.String: - f.count.data++ - label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data) - value := t.Bytes(f.File.Bytes)[1 : t.Length-1] - f.Assembler.SetData(label, value) + data := t.Bytes(f.File.Bytes)[1 : t.Length-1] + label := f.AddBytes(data) f.RegisterLabel(asm.MOVE, register, label) return nil From fe9585e03f4084f4aef087e21de25a16fdefff0c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 30 Jul 2024 12:48:48 +0200 Subject: [PATCH 0413/1012] Improved AST parser --- src/build/ast/Parse.go | 87 +----------------------------- src/build/ast/parseKeyword.go | 59 ++++++++++++++++++++ src/build/ast/parseNode.go | 42 +++++++++++++++ src/build/expression/Expression.go | 12 ++--- 4 files changed, 109 insertions(+), 91 deletions(-) create mode 100644 src/build/ast/parseKeyword.go create mode 100644 src/build/ast/parseNode.go diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index 4db7cca..2c6fd94 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -1,17 +1,16 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" ) // Parse generates an AST from a list of tokens. -func Parse(tokens []token.Token, buffer []byte) (AST, error) { +func Parse(tokens []token.Token, source []byte) (AST, error) { tree := make(AST, 0, len(tokens)/64) err := EachInstruction(tokens, func(instruction token.List) error { - node, err := toASTNode(instruction, buffer) + node, err := parseNode(instruction, source) if err == nil && node != nil { tree = append(tree, node) @@ -23,83 +22,6 @@ func Parse(tokens []token.Token, buffer []byte) (AST, error) { return tree, err } -// toASTNode generates an AST node from an instruction. -func toASTNode(tokens token.List, buffer []byte) (Node, error) { - if tokens[0].IsKeyword() { - if tokens[0].Kind == token.Return { - if len(tokens) == 1 { - return &Return{}, nil - } - - value := expression.Parse(tokens[1:]) - return &Return{Value: value}, nil - } - - if tokens[0].Kind == token.Assert { - if len(tokens) == 1 { - return nil, errors.New(errors.MissingExpression, nil, tokens[0].End()) - } - - condition := expression.Parse(tokens[1:]) - return &Assert{Condition: condition}, nil - } - - if keywordHasBlock(tokens[0].Kind) { - blockStart := tokens.IndexKind(token.BlockStart) - blockEnd := tokens.LastIndexKind(token.BlockEnd) - - if blockStart == -1 { - return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) - } - - if blockEnd == -1 { - return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) - } - - body, err := Parse(tokens[blockStart+1:blockEnd], buffer) - - switch tokens[0].Kind { - case token.If: - condition := expression.Parse(tokens[1:blockStart]) - return &If{Condition: condition, Body: body}, err - - case token.Loop: - return &Loop{Body: body}, err - } - } - - return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text(buffer)}, nil, tokens[0].Position) - } - - expr := expression.Parse(tokens) - - if expr == nil { - return nil, nil - } - - switch { - case IsVariableDefinition(expr): - if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) - } - - return &Define{Expression: expr}, nil - - case IsAssignment(expr): - if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) - } - - return &Assign{Expression: expr}, nil - - case IsFunctionCall(expr): - return &Call{Expression: expr}, nil - - default: - return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text(buffer)}, nil, expr.Token.Position) - } -} - // IsAssignment returns true if the expression is an assignment. func IsAssignment(expr *expression.Expression) bool { return expr.Token.IsAssignment() @@ -114,8 +36,3 @@ func IsFunctionCall(expr *expression.Expression) bool { func IsVariableDefinition(expr *expression.Expression) bool { return expr.Token.Kind == token.Define } - -// keywordHasBlock returns true if the keyword requires a block. -func keywordHasBlock(kind token.Kind) bool { - return kind == token.If || kind == token.Loop -} diff --git a/src/build/ast/parseKeyword.go b/src/build/ast/parseKeyword.go new file mode 100644 index 0000000..f8aa676 --- /dev/null +++ b/src/build/ast/parseKeyword.go @@ -0,0 +1,59 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// parseKeyword generates a keyword node from an instruction. +func parseKeyword(tokens token.List, source []byte) (Node, error) { + switch tokens[0].Kind { + case token.Assert: + if len(tokens) == 1 { + return nil, errors.New(errors.MissingExpression, nil, tokens[0].End()) + } + + condition := expression.Parse(tokens[1:]) + return &Assert{Condition: condition}, nil + + case token.If: + blockStart, _, body, err := block(tokens, source) + condition := expression.Parse(tokens[1:blockStart]) + return &If{Condition: condition, Body: body}, err + + case token.Loop: + _, _, body, err := block(tokens, source) + return &Loop{Body: body}, err + + case token.Return: + if len(tokens) == 1 { + return &Return{}, nil + } + + value := expression.Parse(tokens[1:]) + return &Return{Value: value}, nil + + default: + return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text(source)}, nil, tokens[0].Position) + } +} + +// block retrieves the start and end position of a block. +func block(tokens token.List, source []byte) (blockStart int, blockEnd int, body AST, err error) { + blockStart = tokens.IndexKind(token.BlockStart) + blockEnd = tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + err = errors.New(errors.MissingBlockStart, nil, tokens[0].End()) + return + } + + if blockEnd == -1 { + err = errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) + return + } + + body, err = Parse(tokens[blockStart+1:blockEnd], source) + return +} diff --git a/src/build/ast/parseNode.go b/src/build/ast/parseNode.go new file mode 100644 index 0000000..9ea7174 --- /dev/null +++ b/src/build/ast/parseNode.go @@ -0,0 +1,42 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// parseNode generates an AST node from an instruction. +func parseNode(tokens token.List, source []byte) (Node, error) { + if tokens[0].IsKeyword() { + return parseKeyword(tokens, source) + } + + expr := expression.Parse(tokens) + + if expr == nil { + return nil, nil + } + + switch { + case IsVariableDefinition(expr): + if len(expr.Children) < 2 { + return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) + } + + return &Define{Expression: expr}, nil + + case IsAssignment(expr): + if len(expr.Children) < 2 { + return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) + } + + return &Assign{Expression: expr}, nil + + case IsFunctionCall(expr): + return &Call{Expression: expr}, nil + + default: + return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text(source)}, nil, expr.Token.Position) + } +} diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 057476b..070b4fa 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -114,16 +114,16 @@ func (expr *Expression) LastChild() *Expression { } // String generates a textual representation of the expression. -func (expr *Expression) String(data []byte) string { +func (expr *Expression) String(source []byte) string { builder := strings.Builder{} - expr.write(&builder, data) + expr.write(&builder, source) return builder.String() } // write generates a textual representation of the expression. -func (expr *Expression) write(builder *strings.Builder, data []byte) { +func (expr *Expression) write(builder *strings.Builder, source []byte) { if expr.IsLeaf() { - builder.WriteString(expr.Token.Text(data)) + builder.WriteString(expr.Token.Text(source)) return } @@ -135,12 +135,12 @@ func (expr *Expression) write(builder *strings.Builder, data []byte) { case token.Array: builder.WriteString("@") default: - builder.WriteString(expr.Token.Text(data)) + builder.WriteString(expr.Token.Text(source)) } for _, child := range expr.Children { builder.WriteByte(' ') - child.write(builder, data) + child.write(builder, source) } builder.WriteByte(')') From 1e7a1399d33254fc384e3dd1a77e6d252a7c2797 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 30 Jul 2024 12:48:48 +0200 Subject: [PATCH 0414/1012] Improved AST parser --- src/build/ast/Parse.go | 87 +----------------------------- src/build/ast/parseKeyword.go | 59 ++++++++++++++++++++ src/build/ast/parseNode.go | 42 +++++++++++++++ src/build/expression/Expression.go | 12 ++--- 4 files changed, 109 insertions(+), 91 deletions(-) create mode 100644 src/build/ast/parseKeyword.go create mode 100644 src/build/ast/parseNode.go diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index 4db7cca..2c6fd94 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -1,17 +1,16 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" ) // Parse generates an AST from a list of tokens. -func Parse(tokens []token.Token, buffer []byte) (AST, error) { +func Parse(tokens []token.Token, source []byte) (AST, error) { tree := make(AST, 0, len(tokens)/64) err := EachInstruction(tokens, func(instruction token.List) error { - node, err := toASTNode(instruction, buffer) + node, err := parseNode(instruction, source) if err == nil && node != nil { tree = append(tree, node) @@ -23,83 +22,6 @@ func Parse(tokens []token.Token, buffer []byte) (AST, error) { return tree, err } -// toASTNode generates an AST node from an instruction. -func toASTNode(tokens token.List, buffer []byte) (Node, error) { - if tokens[0].IsKeyword() { - if tokens[0].Kind == token.Return { - if len(tokens) == 1 { - return &Return{}, nil - } - - value := expression.Parse(tokens[1:]) - return &Return{Value: value}, nil - } - - if tokens[0].Kind == token.Assert { - if len(tokens) == 1 { - return nil, errors.New(errors.MissingExpression, nil, tokens[0].End()) - } - - condition := expression.Parse(tokens[1:]) - return &Assert{Condition: condition}, nil - } - - if keywordHasBlock(tokens[0].Kind) { - blockStart := tokens.IndexKind(token.BlockStart) - blockEnd := tokens.LastIndexKind(token.BlockEnd) - - if blockStart == -1 { - return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) - } - - if blockEnd == -1 { - return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) - } - - body, err := Parse(tokens[blockStart+1:blockEnd], buffer) - - switch tokens[0].Kind { - case token.If: - condition := expression.Parse(tokens[1:blockStart]) - return &If{Condition: condition, Body: body}, err - - case token.Loop: - return &Loop{Body: body}, err - } - } - - return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text(buffer)}, nil, tokens[0].Position) - } - - expr := expression.Parse(tokens) - - if expr == nil { - return nil, nil - } - - switch { - case IsVariableDefinition(expr): - if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) - } - - return &Define{Expression: expr}, nil - - case IsAssignment(expr): - if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) - } - - return &Assign{Expression: expr}, nil - - case IsFunctionCall(expr): - return &Call{Expression: expr}, nil - - default: - return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text(buffer)}, nil, expr.Token.Position) - } -} - // IsAssignment returns true if the expression is an assignment. func IsAssignment(expr *expression.Expression) bool { return expr.Token.IsAssignment() @@ -114,8 +36,3 @@ func IsFunctionCall(expr *expression.Expression) bool { func IsVariableDefinition(expr *expression.Expression) bool { return expr.Token.Kind == token.Define } - -// keywordHasBlock returns true if the keyword requires a block. -func keywordHasBlock(kind token.Kind) bool { - return kind == token.If || kind == token.Loop -} diff --git a/src/build/ast/parseKeyword.go b/src/build/ast/parseKeyword.go new file mode 100644 index 0000000..f8aa676 --- /dev/null +++ b/src/build/ast/parseKeyword.go @@ -0,0 +1,59 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// parseKeyword generates a keyword node from an instruction. +func parseKeyword(tokens token.List, source []byte) (Node, error) { + switch tokens[0].Kind { + case token.Assert: + if len(tokens) == 1 { + return nil, errors.New(errors.MissingExpression, nil, tokens[0].End()) + } + + condition := expression.Parse(tokens[1:]) + return &Assert{Condition: condition}, nil + + case token.If: + blockStart, _, body, err := block(tokens, source) + condition := expression.Parse(tokens[1:blockStart]) + return &If{Condition: condition, Body: body}, err + + case token.Loop: + _, _, body, err := block(tokens, source) + return &Loop{Body: body}, err + + case token.Return: + if len(tokens) == 1 { + return &Return{}, nil + } + + value := expression.Parse(tokens[1:]) + return &Return{Value: value}, nil + + default: + return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text(source)}, nil, tokens[0].Position) + } +} + +// block retrieves the start and end position of a block. +func block(tokens token.List, source []byte) (blockStart int, blockEnd int, body AST, err error) { + blockStart = tokens.IndexKind(token.BlockStart) + blockEnd = tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + err = errors.New(errors.MissingBlockStart, nil, tokens[0].End()) + return + } + + if blockEnd == -1 { + err = errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) + return + } + + body, err = Parse(tokens[blockStart+1:blockEnd], source) + return +} diff --git a/src/build/ast/parseNode.go b/src/build/ast/parseNode.go new file mode 100644 index 0000000..9ea7174 --- /dev/null +++ b/src/build/ast/parseNode.go @@ -0,0 +1,42 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// parseNode generates an AST node from an instruction. +func parseNode(tokens token.List, source []byte) (Node, error) { + if tokens[0].IsKeyword() { + return parseKeyword(tokens, source) + } + + expr := expression.Parse(tokens) + + if expr == nil { + return nil, nil + } + + switch { + case IsVariableDefinition(expr): + if len(expr.Children) < 2 { + return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) + } + + return &Define{Expression: expr}, nil + + case IsAssignment(expr): + if len(expr.Children) < 2 { + return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) + } + + return &Assign{Expression: expr}, nil + + case IsFunctionCall(expr): + return &Call{Expression: expr}, nil + + default: + return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text(source)}, nil, expr.Token.Position) + } +} diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 057476b..070b4fa 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -114,16 +114,16 @@ func (expr *Expression) LastChild() *Expression { } // String generates a textual representation of the expression. -func (expr *Expression) String(data []byte) string { +func (expr *Expression) String(source []byte) string { builder := strings.Builder{} - expr.write(&builder, data) + expr.write(&builder, source) return builder.String() } // write generates a textual representation of the expression. -func (expr *Expression) write(builder *strings.Builder, data []byte) { +func (expr *Expression) write(builder *strings.Builder, source []byte) { if expr.IsLeaf() { - builder.WriteString(expr.Token.Text(data)) + builder.WriteString(expr.Token.Text(source)) return } @@ -135,12 +135,12 @@ func (expr *Expression) write(builder *strings.Builder, data []byte) { case token.Array: builder.WriteString("@") default: - builder.WriteString(expr.Token.Text(data)) + builder.WriteString(expr.Token.Text(source)) } for _, child := range expr.Children { builder.WriteByte(' ') - child.write(builder, data) + child.write(builder, source) } builder.WriteByte(')') From ff86dfe590f0b4d71ab6782471f79721cbd39917 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 30 Jul 2024 15:46:47 +0200 Subject: [PATCH 0415/1012] Added more tests --- src/build/core/ExpressionToMemory.go | 3 +-- tests/programs/reassign.q | 8 ++++---- tests/programs/variables.q | 15 +++++++++++++++ tests/programs_test.go | 3 ++- 4 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 tests/programs/variables.q diff --git a/src/build/core/ExpressionToMemory.go b/src/build/core/ExpressionToMemory.go index d876c7b..781b378 100644 --- a/src/build/core/ExpressionToMemory.go +++ b/src/build/core/ExpressionToMemory.go @@ -5,12 +5,11 @@ import ( "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/sizeof" - "git.akyoto.dev/cli/q/src/build/token" ) // ExpressionToMemory puts the result of an expression into the specified memory address. func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) error { - if node.IsLeaf() && (node.Token.Kind == token.Number || node.Token.Kind == token.Rune) { + if node.IsLeaf() && node.Token.IsNumeric() { number, err := f.Number(node.Token) if err != nil { diff --git a/tests/programs/reassign.q b/tests/programs/reassign.q index 26aa3e8..4c6fa45 100644 --- a/tests/programs/reassign.q +++ b/tests/programs/reassign.q @@ -1,6 +1,6 @@ main() { - x := 1 - y := x + 1 - x = 2 - assert y == 2 + a := 1 + b := a + 1 + a = 2 + assert b == 2 } \ No newline at end of file diff --git a/tests/programs/variables.q b/tests/programs/variables.q new file mode 100644 index 0000000..aa461ab --- /dev/null +++ b/tests/programs/variables.q @@ -0,0 +1,15 @@ +main() { + a := 1 + b := 2 + c := 3 + d := 4 + e := 5 + f := 6 + g := 7 + h := 8 + + assert a != b + assert c != d + assert e != f + assert g != h +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 5dfce4e..6fbc5fe 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -20,8 +20,9 @@ var programs = []struct { }{ {"empty", "", "", 0}, {"assert", "", "", 1}, - {"reuse", "", "", 0}, + {"variables", "", "", 0}, {"reassign", "", "", 0}, + {"reuse", "", "", 0}, {"return", "", "", 0}, {"binary", "", "", 0}, {"octal", "", "", 0}, From 5abe8acc7044b3c42aa35b2e7771a14a0936432d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 30 Jul 2024 15:46:47 +0200 Subject: [PATCH 0416/1012] Added more tests --- src/build/core/ExpressionToMemory.go | 3 +-- tests/programs/reassign.q | 8 ++++---- tests/programs/variables.q | 15 +++++++++++++++ tests/programs_test.go | 3 ++- 4 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 tests/programs/variables.q diff --git a/src/build/core/ExpressionToMemory.go b/src/build/core/ExpressionToMemory.go index d876c7b..781b378 100644 --- a/src/build/core/ExpressionToMemory.go +++ b/src/build/core/ExpressionToMemory.go @@ -5,12 +5,11 @@ import ( "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/sizeof" - "git.akyoto.dev/cli/q/src/build/token" ) // ExpressionToMemory puts the result of an expression into the specified memory address. func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) error { - if node.IsLeaf() && (node.Token.Kind == token.Number || node.Token.Kind == token.Rune) { + if node.IsLeaf() && node.Token.IsNumeric() { number, err := f.Number(node.Token) if err != nil { diff --git a/tests/programs/reassign.q b/tests/programs/reassign.q index 26aa3e8..4c6fa45 100644 --- a/tests/programs/reassign.q +++ b/tests/programs/reassign.q @@ -1,6 +1,6 @@ main() { - x := 1 - y := x + 1 - x = 2 - assert y == 2 + a := 1 + b := a + 1 + a = 2 + assert b == 2 } \ No newline at end of file diff --git a/tests/programs/variables.q b/tests/programs/variables.q new file mode 100644 index 0000000..aa461ab --- /dev/null +++ b/tests/programs/variables.q @@ -0,0 +1,15 @@ +main() { + a := 1 + b := 2 + c := 3 + d := 4 + e := 5 + f := 6 + g := 7 + h := 8 + + assert a != b + assert c != d + assert e != f + assert g != h +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 5dfce4e..6fbc5fe 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -20,8 +20,9 @@ var programs = []struct { }{ {"empty", "", "", 0}, {"assert", "", "", 1}, - {"reuse", "", "", 0}, + {"variables", "", "", 0}, {"reassign", "", "", 0}, + {"reuse", "", "", 0}, {"return", "", "", 0}, {"binary", "", "", 0}, {"octal", "", "", 0}, From e537e543ccc8bb4b42aa4da29f07334e9ec67f85 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 30 Jul 2024 16:36:33 +0200 Subject: [PATCH 0417/1012] Implemented else blocks --- README.md | 1 + examples/factorial/factorial.q | 4 +-- examples/fibonacci/fibonacci.q | 4 +-- examples/gcd/gcd.q | 19 ++++++++++++++ src/build/ast/EachInstruction.go | 12 +++++++++ src/build/ast/If.go | 1 + src/build/ast/Parse.go | 8 +++--- src/build/ast/parseKeyword.go | 19 +++++++++++++- src/build/ast/parseNode.go | 4 +-- src/build/core/CompileIf.go | 37 +++++++++++++++++++++++++--- src/build/errors/CompileErrors.go | 17 +++++++------ src/build/token/Kind.go | 1 + src/build/token/Tokenize.go | 2 ++ tests/errors/ExpectedIfBeforeElse.q | 3 +++ tests/errors/ExpectedIfBeforeElse2.q | 4 +++ tests/errors_test.go | 2 ++ tests/examples_test.go | 5 ++-- 17 files changed, 118 insertions(+), 25 deletions(-) create mode 100644 examples/gcd/gcd.q create mode 100644 tests/errors/ExpectedIfBeforeElse.q create mode 100644 tests/errors/ExpectedIfBeforeElse2.q diff --git a/README.md b/README.md index 8598efb..70e1ea5 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ This is what generates expressions from tokens. ### Keywords - [x] `assert` +- [x] `else` - [ ] `for` - [x] `if` - [x] `import` diff --git a/examples/factorial/factorial.q b/examples/factorial/factorial.q index 30315ec..dab665c 100644 --- a/examples/factorial/factorial.q +++ b/examples/factorial/factorial.q @@ -1,7 +1,7 @@ -import sys +import log main() { - sys.exit(factorial(5)) + log.number(factorial(5)) } factorial(x) { diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q index 505bf26..acc3e78 100644 --- a/examples/fibonacci/fibonacci.q +++ b/examples/fibonacci/fibonacci.q @@ -1,7 +1,7 @@ -import sys +import log main() { - sys.exit(fibonacci(10)) + log.number(fibonacci(10)) } fibonacci(x) { diff --git a/examples/gcd/gcd.q b/examples/gcd/gcd.q new file mode 100644 index 0000000..9e9b5e0 --- /dev/null +++ b/examples/gcd/gcd.q @@ -0,0 +1,19 @@ +import log + +main() { + log.number(gcd(1071, 462)) +} + +gcd(a, b) { + loop { + if a == b { + return a + } + + if a > b { + a -= b + } else { + b -= a + } + } +} \ No newline at end of file diff --git a/src/build/ast/EachInstruction.go b/src/build/ast/EachInstruction.go index f11bedc..b4d9d8a 100644 --- a/src/build/ast/EachInstruction.go +++ b/src/build/ast/EachInstruction.go @@ -39,6 +39,18 @@ func EachInstruction(body token.List, call func(token.List) error) error { case token.BlockEnd: blockLevel-- + + if groupLevel > 0 || blockLevel > 0 { + continue + } + + err := call(body[start : i+1]) + + if err != nil { + return err + } + + start = i + 1 } } diff --git a/src/build/ast/If.go b/src/build/ast/If.go index 4275f34..f99307f 100644 --- a/src/build/ast/If.go +++ b/src/build/ast/If.go @@ -8,4 +8,5 @@ import ( type If struct { Condition *expression.Expression Body AST + Else AST } diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index 2c6fd94..582f497 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -7,19 +7,19 @@ import ( // Parse generates an AST from a list of tokens. func Parse(tokens []token.Token, source []byte) (AST, error) { - tree := make(AST, 0, len(tokens)/64) + nodes := make(AST, 0, len(tokens)/64) err := EachInstruction(tokens, func(instruction token.List) error { - node, err := parseNode(instruction, source) + node, err := parseNode(instruction, source, nodes) if err == nil && node != nil { - tree = append(tree, node) + nodes = append(nodes, node) } return err }) - return tree, err + return nodes, err } // IsAssignment returns true if the expression is an assignment. diff --git a/src/build/ast/parseKeyword.go b/src/build/ast/parseKeyword.go index f8aa676..a123570 100644 --- a/src/build/ast/parseKeyword.go +++ b/src/build/ast/parseKeyword.go @@ -7,7 +7,7 @@ import ( ) // parseKeyword generates a keyword node from an instruction. -func parseKeyword(tokens token.List, source []byte) (Node, error) { +func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { switch tokens[0].Kind { case token.Assert: if len(tokens) == 1 { @@ -22,6 +22,23 @@ func parseKeyword(tokens token.List, source []byte) (Node, error) { condition := expression.Parse(tokens[1:blockStart]) return &If{Condition: condition, Body: body}, err + case token.Else: + _, _, body, err := block(tokens, source) + + if len(nodes) == 0 { + return nil, errors.New(errors.ExpectedIfBeforeElse, nil, tokens[0].Position) + } + + last := nodes[len(nodes)-1] + ifNode, exists := last.(*If) + + if !exists { + return nil, errors.New(errors.ExpectedIfBeforeElse, nil, tokens[0].Position) + } + + ifNode.Else = body + return nil, err + case token.Loop: _, _, body, err := block(tokens, source) return &Loop{Body: body}, err diff --git a/src/build/ast/parseNode.go b/src/build/ast/parseNode.go index 9ea7174..8473c29 100644 --- a/src/build/ast/parseNode.go +++ b/src/build/ast/parseNode.go @@ -7,9 +7,9 @@ import ( ) // parseNode generates an AST node from an instruction. -func parseNode(tokens token.List, source []byte) (Node, error) { +func parseNode(tokens token.List, source []byte, nodes AST) (Node, error) { if tokens[0].IsKeyword() { - return parseKeyword(tokens, source) + return parseKeyword(tokens, source, nodes) } expr := expression.Parse(tokens) diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 3eeb4ae..6b1a869 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -3,15 +3,20 @@ package core import ( "fmt" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" ) // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { f.count.branch++ - success := fmt.Sprintf("%s_if_%d_true", f.Name, f.count.branch) - fail := fmt.Sprintf("%s_if_%d_false", f.Name, f.count.branch) - err := f.CompileCondition(branch.Condition, success, fail) + + var ( + end string + success = fmt.Sprintf("%s_if_%d_true", f.Name, f.count.branch) + fail = fmt.Sprintf("%s_if_%d_false", f.Name, f.count.branch) + err = f.CompileCondition(branch.Condition, success, fail) + ) if err != nil { return err @@ -20,7 +25,31 @@ func (f *Function) CompileIf(branch *ast.If) error { f.AddLabel(success) f.PushScope(branch.Body, f.File.Bytes) err = f.CompileAST(branch.Body) + + if err != nil { + return err + } + + if branch.Else != nil { + end = fmt.Sprintf("%s_if_%d_end", f.Name, f.count.branch) + f.Jump(asm.JUMP, end) + } + f.PopScope() f.AddLabel(fail) - return err + + if branch.Else != nil { + f.PushScope(branch.Else, f.File.Bytes) + err = f.CompileAST(branch.Else) + + if err != nil { + return err + } + + f.PopScope() + f.AddLabel(end) + return nil + } + + return nil } diff --git a/src/build/errors/CompileErrors.go b/src/build/errors/CompileErrors.go index 5f65804..a214de3 100644 --- a/src/build/errors/CompileErrors.go +++ b/src/build/errors/CompileErrors.go @@ -1,12 +1,13 @@ package errors var ( - InvalidNumber = &Base{"Invalid number"} - InvalidExpression = &Base{"Invalid expression"} - InvalidRune = &Base{"Invalid rune"} - InvalidStatement = &Base{"Invalid statement"} - MissingExpression = &Base{"Missing expression"} - MissingMainFunction = &Base{"Missing main function"} - MissingOperand = &Base{"Missing operand"} - NotImplemented = &Base{"Not implemented"} + ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} + InvalidNumber = &Base{"Invalid number"} + InvalidExpression = &Base{"Invalid expression"} + InvalidRune = &Base{"Invalid rune"} + InvalidStatement = &Base{"Invalid statement"} + MissingExpression = &Base{"Missing expression"} + MissingMainFunction = &Base{"Missing main function"} + MissingOperand = &Base{"Missing operand"} + NotImplemented = &Base{"Not implemented"} ) diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 44f0132..2c12464 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -20,6 +20,7 @@ const ( ArrayEnd // ] _keywords // Assert // assert + Else // else If // if Import // import Loop // loop diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 929eff3..c955e9a 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -143,6 +143,8 @@ func Tokenize(buffer []byte) List { kind = Assert case "if": kind = If + case "else": + kind = Else case "import": kind = Import case "loop": diff --git a/tests/errors/ExpectedIfBeforeElse.q b/tests/errors/ExpectedIfBeforeElse.q new file mode 100644 index 0000000..a4a7018 --- /dev/null +++ b/tests/errors/ExpectedIfBeforeElse.q @@ -0,0 +1,3 @@ +main() { + else {} +} \ No newline at end of file diff --git a/tests/errors/ExpectedIfBeforeElse2.q b/tests/errors/ExpectedIfBeforeElse2.q new file mode 100644 index 0000000..a2b575f --- /dev/null +++ b/tests/errors/ExpectedIfBeforeElse2.q @@ -0,0 +1,4 @@ +main() { + loop {} + else {} +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 5b28ee2..88f6b3f 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -17,6 +17,8 @@ var errs = []struct { {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, + {"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse}, + {"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}}, diff --git a/tests/examples_test.go b/tests/examples_test.go index 5c2ee12..94d5f80 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -16,8 +16,9 @@ var examples = []struct { ExitCode int }{ {"hello", "", "Hello", 0}, - {"factorial", "", "", 120}, - {"fibonacci", "", "", 55}, + {"factorial", "", "120", 0}, + {"fibonacci", "", "55", 0}, + {"gcd", "", "21", 0}, {"array", "", "Hello", 0}, {"echo", "Echo", "Echo", 0}, {"itoa", "", "9223372036854775807", 0}, From 323952f4bc9e1ac54b6f95c36ff43bc7388e9847 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 30 Jul 2024 16:36:33 +0200 Subject: [PATCH 0418/1012] Implemented else blocks --- README.md | 1 + examples/factorial/factorial.q | 4 +-- examples/fibonacci/fibonacci.q | 4 +-- examples/gcd/gcd.q | 19 ++++++++++++++ src/build/ast/EachInstruction.go | 12 +++++++++ src/build/ast/If.go | 1 + src/build/ast/Parse.go | 8 +++--- src/build/ast/parseKeyword.go | 19 +++++++++++++- src/build/ast/parseNode.go | 4 +-- src/build/core/CompileIf.go | 37 +++++++++++++++++++++++++--- src/build/errors/CompileErrors.go | 17 +++++++------ src/build/token/Kind.go | 1 + src/build/token/Tokenize.go | 2 ++ tests/errors/ExpectedIfBeforeElse.q | 3 +++ tests/errors/ExpectedIfBeforeElse2.q | 4 +++ tests/errors_test.go | 2 ++ tests/examples_test.go | 5 ++-- 17 files changed, 118 insertions(+), 25 deletions(-) create mode 100644 examples/gcd/gcd.q create mode 100644 tests/errors/ExpectedIfBeforeElse.q create mode 100644 tests/errors/ExpectedIfBeforeElse2.q diff --git a/README.md b/README.md index 8598efb..70e1ea5 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ This is what generates expressions from tokens. ### Keywords - [x] `assert` +- [x] `else` - [ ] `for` - [x] `if` - [x] `import` diff --git a/examples/factorial/factorial.q b/examples/factorial/factorial.q index 30315ec..dab665c 100644 --- a/examples/factorial/factorial.q +++ b/examples/factorial/factorial.q @@ -1,7 +1,7 @@ -import sys +import log main() { - sys.exit(factorial(5)) + log.number(factorial(5)) } factorial(x) { diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q index 505bf26..acc3e78 100644 --- a/examples/fibonacci/fibonacci.q +++ b/examples/fibonacci/fibonacci.q @@ -1,7 +1,7 @@ -import sys +import log main() { - sys.exit(fibonacci(10)) + log.number(fibonacci(10)) } fibonacci(x) { diff --git a/examples/gcd/gcd.q b/examples/gcd/gcd.q new file mode 100644 index 0000000..9e9b5e0 --- /dev/null +++ b/examples/gcd/gcd.q @@ -0,0 +1,19 @@ +import log + +main() { + log.number(gcd(1071, 462)) +} + +gcd(a, b) { + loop { + if a == b { + return a + } + + if a > b { + a -= b + } else { + b -= a + } + } +} \ No newline at end of file diff --git a/src/build/ast/EachInstruction.go b/src/build/ast/EachInstruction.go index f11bedc..b4d9d8a 100644 --- a/src/build/ast/EachInstruction.go +++ b/src/build/ast/EachInstruction.go @@ -39,6 +39,18 @@ func EachInstruction(body token.List, call func(token.List) error) error { case token.BlockEnd: blockLevel-- + + if groupLevel > 0 || blockLevel > 0 { + continue + } + + err := call(body[start : i+1]) + + if err != nil { + return err + } + + start = i + 1 } } diff --git a/src/build/ast/If.go b/src/build/ast/If.go index 4275f34..f99307f 100644 --- a/src/build/ast/If.go +++ b/src/build/ast/If.go @@ -8,4 +8,5 @@ import ( type If struct { Condition *expression.Expression Body AST + Else AST } diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index 2c6fd94..582f497 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -7,19 +7,19 @@ import ( // Parse generates an AST from a list of tokens. func Parse(tokens []token.Token, source []byte) (AST, error) { - tree := make(AST, 0, len(tokens)/64) + nodes := make(AST, 0, len(tokens)/64) err := EachInstruction(tokens, func(instruction token.List) error { - node, err := parseNode(instruction, source) + node, err := parseNode(instruction, source, nodes) if err == nil && node != nil { - tree = append(tree, node) + nodes = append(nodes, node) } return err }) - return tree, err + return nodes, err } // IsAssignment returns true if the expression is an assignment. diff --git a/src/build/ast/parseKeyword.go b/src/build/ast/parseKeyword.go index f8aa676..a123570 100644 --- a/src/build/ast/parseKeyword.go +++ b/src/build/ast/parseKeyword.go @@ -7,7 +7,7 @@ import ( ) // parseKeyword generates a keyword node from an instruction. -func parseKeyword(tokens token.List, source []byte) (Node, error) { +func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { switch tokens[0].Kind { case token.Assert: if len(tokens) == 1 { @@ -22,6 +22,23 @@ func parseKeyword(tokens token.List, source []byte) (Node, error) { condition := expression.Parse(tokens[1:blockStart]) return &If{Condition: condition, Body: body}, err + case token.Else: + _, _, body, err := block(tokens, source) + + if len(nodes) == 0 { + return nil, errors.New(errors.ExpectedIfBeforeElse, nil, tokens[0].Position) + } + + last := nodes[len(nodes)-1] + ifNode, exists := last.(*If) + + if !exists { + return nil, errors.New(errors.ExpectedIfBeforeElse, nil, tokens[0].Position) + } + + ifNode.Else = body + return nil, err + case token.Loop: _, _, body, err := block(tokens, source) return &Loop{Body: body}, err diff --git a/src/build/ast/parseNode.go b/src/build/ast/parseNode.go index 9ea7174..8473c29 100644 --- a/src/build/ast/parseNode.go +++ b/src/build/ast/parseNode.go @@ -7,9 +7,9 @@ import ( ) // parseNode generates an AST node from an instruction. -func parseNode(tokens token.List, source []byte) (Node, error) { +func parseNode(tokens token.List, source []byte, nodes AST) (Node, error) { if tokens[0].IsKeyword() { - return parseKeyword(tokens, source) + return parseKeyword(tokens, source, nodes) } expr := expression.Parse(tokens) diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 3eeb4ae..6b1a869 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -3,15 +3,20 @@ package core import ( "fmt" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" ) // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { f.count.branch++ - success := fmt.Sprintf("%s_if_%d_true", f.Name, f.count.branch) - fail := fmt.Sprintf("%s_if_%d_false", f.Name, f.count.branch) - err := f.CompileCondition(branch.Condition, success, fail) + + var ( + end string + success = fmt.Sprintf("%s_if_%d_true", f.Name, f.count.branch) + fail = fmt.Sprintf("%s_if_%d_false", f.Name, f.count.branch) + err = f.CompileCondition(branch.Condition, success, fail) + ) if err != nil { return err @@ -20,7 +25,31 @@ func (f *Function) CompileIf(branch *ast.If) error { f.AddLabel(success) f.PushScope(branch.Body, f.File.Bytes) err = f.CompileAST(branch.Body) + + if err != nil { + return err + } + + if branch.Else != nil { + end = fmt.Sprintf("%s_if_%d_end", f.Name, f.count.branch) + f.Jump(asm.JUMP, end) + } + f.PopScope() f.AddLabel(fail) - return err + + if branch.Else != nil { + f.PushScope(branch.Else, f.File.Bytes) + err = f.CompileAST(branch.Else) + + if err != nil { + return err + } + + f.PopScope() + f.AddLabel(end) + return nil + } + + return nil } diff --git a/src/build/errors/CompileErrors.go b/src/build/errors/CompileErrors.go index 5f65804..a214de3 100644 --- a/src/build/errors/CompileErrors.go +++ b/src/build/errors/CompileErrors.go @@ -1,12 +1,13 @@ package errors var ( - InvalidNumber = &Base{"Invalid number"} - InvalidExpression = &Base{"Invalid expression"} - InvalidRune = &Base{"Invalid rune"} - InvalidStatement = &Base{"Invalid statement"} - MissingExpression = &Base{"Missing expression"} - MissingMainFunction = &Base{"Missing main function"} - MissingOperand = &Base{"Missing operand"} - NotImplemented = &Base{"Not implemented"} + ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} + InvalidNumber = &Base{"Invalid number"} + InvalidExpression = &Base{"Invalid expression"} + InvalidRune = &Base{"Invalid rune"} + InvalidStatement = &Base{"Invalid statement"} + MissingExpression = &Base{"Missing expression"} + MissingMainFunction = &Base{"Missing main function"} + MissingOperand = &Base{"Missing operand"} + NotImplemented = &Base{"Not implemented"} ) diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 44f0132..2c12464 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -20,6 +20,7 @@ const ( ArrayEnd // ] _keywords // Assert // assert + Else // else If // if Import // import Loop // loop diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 929eff3..c955e9a 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -143,6 +143,8 @@ func Tokenize(buffer []byte) List { kind = Assert case "if": kind = If + case "else": + kind = Else case "import": kind = Import case "loop": diff --git a/tests/errors/ExpectedIfBeforeElse.q b/tests/errors/ExpectedIfBeforeElse.q new file mode 100644 index 0000000..a4a7018 --- /dev/null +++ b/tests/errors/ExpectedIfBeforeElse.q @@ -0,0 +1,3 @@ +main() { + else {} +} \ No newline at end of file diff --git a/tests/errors/ExpectedIfBeforeElse2.q b/tests/errors/ExpectedIfBeforeElse2.q new file mode 100644 index 0000000..a2b575f --- /dev/null +++ b/tests/errors/ExpectedIfBeforeElse2.q @@ -0,0 +1,4 @@ +main() { + loop {} + else {} +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 5b28ee2..88f6b3f 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -17,6 +17,8 @@ var errs = []struct { {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, + {"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse}, + {"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}}, diff --git a/tests/examples_test.go b/tests/examples_test.go index 5c2ee12..94d5f80 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -16,8 +16,9 @@ var examples = []struct { ExitCode int }{ {"hello", "", "Hello", 0}, - {"factorial", "", "", 120}, - {"fibonacci", "", "", 55}, + {"factorial", "", "120", 0}, + {"fibonacci", "", "55", 0}, + {"gcd", "", "21", 0}, {"array", "", "Hello", 0}, {"echo", "Echo", "Echo", 0}, {"itoa", "", "9223372036854775807", 0}, From 6510166ae0afa43e5f91d15f736c2c87559d7f8c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 30 Jul 2024 20:02:55 +0200 Subject: [PATCH 0419/1012] Added main prefix --- lib/log/number.q | 6 +++++- src/build/compiler/Compile.go | 2 +- src/build/compiler/Result.go | 2 +- src/build/core/CompileCall.go | 26 +++++++++++++++++--------- src/build/core/Function.go | 1 + src/build/core/NewFunction.go | 9 +++++---- src/build/expression/Expression.go | 2 +- src/build/scanner/queue.go | 4 ++-- src/build/scanner/scanFile.go | 2 +- 9 files changed, 34 insertions(+), 20 deletions(-) diff --git a/lib/log/number.q b/lib/log/number.q index 58456ad..c8de482 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -4,6 +4,11 @@ import sys number(x) { length := 20 buffer := mem.alloc(length) + itoa(x, buffer, length) + mem.free(buffer, length) +} + +itoa(x, buffer, length) { end := buffer + length tmp := end digit := 0 @@ -15,7 +20,6 @@ number(x) { if x == 0 { sys.write(1, tmp, end - tmp) - mem.free(buffer, length) return } } diff --git a/src/build/compiler/Compile.go b/src/build/compiler/Compile.go index 565fe28..e25de67 100644 --- a/src/build/compiler/Compile.go +++ b/src/build/compiler/Compile.go @@ -47,7 +47,7 @@ func Compile(functions <-chan *core.Function, errs <-chan error) (Result, error) } // Check for existence of `main` - main, exists := all["main"] + main, exists := all["main.main"] if !exists { return result, errors.MissingMainFunction diff --git a/src/build/compiler/Result.go b/src/build/compiler/Result.go index 68c37ca..db1f814 100644 --- a/src/build/compiler/Result.go +++ b/src/build/compiler/Result.go @@ -31,7 +31,7 @@ func (r *Result) finalize() ([]byte, []byte) { Data: make(map[string][]byte, r.DataCount), } - final.Call("main") + final.Call("main.main") final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit) final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0) final.Syscall() diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 1441ee9..9251510 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -1,6 +1,8 @@ package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" @@ -11,22 +13,28 @@ import ( // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. func (f *Function) CompileCall(root *expression.Expression) error { - funcName := "" - funcNameRoot := root.Children[0] + var ( + pkg = f.Package + nameRoot = root.Children[0] + name string + fullName string + ) - if funcNameRoot.IsLeaf() { - funcName = funcNameRoot.Token.Text(f.File.Bytes) + if nameRoot.IsLeaf() { + name = nameRoot.Token.Text(f.File.Bytes) } else { - funcName = funcNameRoot.Children[0].Token.Text(f.File.Bytes) + funcNameRoot.Token.Text(f.File.Bytes) + funcNameRoot.Children[1].Token.Text(f.File.Bytes) + pkg = nameRoot.Children[0].Token.Text(f.File.Bytes) + name = nameRoot.Children[1].Token.Text(f.File.Bytes) } - isSyscall := funcName == "syscall" + isSyscall := name == "syscall" if !isSyscall { - _, exists := f.Functions[funcName] + fullName = fmt.Sprintf("%s.%s", pkg, name) + _, exists := f.Functions[fullName] if !exists { - return errors.New(&errors.UnknownFunction{Name: funcName}, f.File, root.Children[0].Token.Position) + return errors.New(&errors.UnknownFunction{Name: name}, f.File, root.Children[0].Token.Position) } } @@ -56,7 +64,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { if isSyscall { f.Syscall() } else { - f.Call(funcName) + f.Call(fullName) } // Free parameter registers diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 29fe007..7a4b4c6 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -9,6 +9,7 @@ import ( // Function represents the smallest unit of code. type Function struct { register.Machine + Package string Name string File *fs.File Body []token.Token diff --git a/src/build/core/NewFunction.go b/src/build/core/NewFunction.go index d4bc8f3..bd4f23b 100644 --- a/src/build/core/NewFunction.go +++ b/src/build/core/NewFunction.go @@ -11,11 +11,12 @@ import ( ) // NewFunction creates a new function. -func NewFunction(name string, file *fs.File, body []token.Token) *Function { +func NewFunction(pkg string, name string, file *fs.File, body []token.Token) *Function { return &Function{ - Name: name, - File: file, - Body: body, + Package: pkg, + Name: name, + File: file, + Body: body, Machine: register.Machine{ Assembler: asm.Assembler{ Instructions: make([]asm.Instruction, 0, 8), diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 070b4fa..3a55506 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -11,8 +11,8 @@ type Expression struct { Parent *Expression Children []*Expression Token token.Token - Precedence int8 Value int + Precedence int8 IsFolded bool } diff --git a/src/build/scanner/queue.go b/src/build/scanner/queue.go index 6ccfc15..0bc628b 100644 --- a/src/build/scanner/queue.go +++ b/src/build/scanner/queue.go @@ -13,9 +13,9 @@ func (s *Scanner) queue(files ...string) { } if stat.IsDir() { - s.queueDirectory(file, "") + s.queueDirectory(file, "main") } else { - s.queueFile(file, "") + s.queueFile(file, "main") } } } diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index e724767..4d7b620 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -198,7 +198,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { name = fmt.Sprintf("%s.%s", pkg, name) } - function := core.NewFunction(name, file, body) + function := core.NewFunction(pkg, name, file, body) parameters := tokens[paramsStart:paramsEnd] count := 0 From 0c1b57b4e41d6190cdfd1a7f5e52312e70b91741 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 30 Jul 2024 20:02:55 +0200 Subject: [PATCH 0420/1012] Added main prefix --- lib/log/number.q | 6 +++++- src/build/compiler/Compile.go | 2 +- src/build/compiler/Result.go | 2 +- src/build/core/CompileCall.go | 26 +++++++++++++++++--------- src/build/core/Function.go | 1 + src/build/core/NewFunction.go | 9 +++++---- src/build/expression/Expression.go | 2 +- src/build/scanner/queue.go | 4 ++-- src/build/scanner/scanFile.go | 2 +- 9 files changed, 34 insertions(+), 20 deletions(-) diff --git a/lib/log/number.q b/lib/log/number.q index 58456ad..c8de482 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -4,6 +4,11 @@ import sys number(x) { length := 20 buffer := mem.alloc(length) + itoa(x, buffer, length) + mem.free(buffer, length) +} + +itoa(x, buffer, length) { end := buffer + length tmp := end digit := 0 @@ -15,7 +20,6 @@ number(x) { if x == 0 { sys.write(1, tmp, end - tmp) - mem.free(buffer, length) return } } diff --git a/src/build/compiler/Compile.go b/src/build/compiler/Compile.go index 565fe28..e25de67 100644 --- a/src/build/compiler/Compile.go +++ b/src/build/compiler/Compile.go @@ -47,7 +47,7 @@ func Compile(functions <-chan *core.Function, errs <-chan error) (Result, error) } // Check for existence of `main` - main, exists := all["main"] + main, exists := all["main.main"] if !exists { return result, errors.MissingMainFunction diff --git a/src/build/compiler/Result.go b/src/build/compiler/Result.go index 68c37ca..db1f814 100644 --- a/src/build/compiler/Result.go +++ b/src/build/compiler/Result.go @@ -31,7 +31,7 @@ func (r *Result) finalize() ([]byte, []byte) { Data: make(map[string][]byte, r.DataCount), } - final.Call("main") + final.Call("main.main") final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit) final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0) final.Syscall() diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 1441ee9..9251510 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -1,6 +1,8 @@ package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" @@ -11,22 +13,28 @@ import ( // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. func (f *Function) CompileCall(root *expression.Expression) error { - funcName := "" - funcNameRoot := root.Children[0] + var ( + pkg = f.Package + nameRoot = root.Children[0] + name string + fullName string + ) - if funcNameRoot.IsLeaf() { - funcName = funcNameRoot.Token.Text(f.File.Bytes) + if nameRoot.IsLeaf() { + name = nameRoot.Token.Text(f.File.Bytes) } else { - funcName = funcNameRoot.Children[0].Token.Text(f.File.Bytes) + funcNameRoot.Token.Text(f.File.Bytes) + funcNameRoot.Children[1].Token.Text(f.File.Bytes) + pkg = nameRoot.Children[0].Token.Text(f.File.Bytes) + name = nameRoot.Children[1].Token.Text(f.File.Bytes) } - isSyscall := funcName == "syscall" + isSyscall := name == "syscall" if !isSyscall { - _, exists := f.Functions[funcName] + fullName = fmt.Sprintf("%s.%s", pkg, name) + _, exists := f.Functions[fullName] if !exists { - return errors.New(&errors.UnknownFunction{Name: funcName}, f.File, root.Children[0].Token.Position) + return errors.New(&errors.UnknownFunction{Name: name}, f.File, root.Children[0].Token.Position) } } @@ -56,7 +64,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { if isSyscall { f.Syscall() } else { - f.Call(funcName) + f.Call(fullName) } // Free parameter registers diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 29fe007..7a4b4c6 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -9,6 +9,7 @@ import ( // Function represents the smallest unit of code. type Function struct { register.Machine + Package string Name string File *fs.File Body []token.Token diff --git a/src/build/core/NewFunction.go b/src/build/core/NewFunction.go index d4bc8f3..bd4f23b 100644 --- a/src/build/core/NewFunction.go +++ b/src/build/core/NewFunction.go @@ -11,11 +11,12 @@ import ( ) // NewFunction creates a new function. -func NewFunction(name string, file *fs.File, body []token.Token) *Function { +func NewFunction(pkg string, name string, file *fs.File, body []token.Token) *Function { return &Function{ - Name: name, - File: file, - Body: body, + Package: pkg, + Name: name, + File: file, + Body: body, Machine: register.Machine{ Assembler: asm.Assembler{ Instructions: make([]asm.Instruction, 0, 8), diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 070b4fa..3a55506 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -11,8 +11,8 @@ type Expression struct { Parent *Expression Children []*Expression Token token.Token - Precedence int8 Value int + Precedence int8 IsFolded bool } diff --git a/src/build/scanner/queue.go b/src/build/scanner/queue.go index 6ccfc15..0bc628b 100644 --- a/src/build/scanner/queue.go +++ b/src/build/scanner/queue.go @@ -13,9 +13,9 @@ func (s *Scanner) queue(files ...string) { } if stat.IsDir() { - s.queueDirectory(file, "") + s.queueDirectory(file, "main") } else { - s.queueFile(file, "") + s.queueFile(file, "main") } } } diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index e724767..4d7b620 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -198,7 +198,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { name = fmt.Sprintf("%s.%s", pkg, name) } - function := core.NewFunction(name, file, body) + function := core.NewFunction(pkg, name, file, body) parameters := tokens[paramsStart:paramsEnd] count := 0 From 2a0137c2c296fe54c421308c5839d6ac7a7796cb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 30 Jul 2024 21:26:15 +0200 Subject: [PATCH 0421/1012] Reduced memory usage --- src/build/core/CompileCall.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 9251510..7ac3d10 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -1,7 +1,7 @@ package core import ( - "fmt" + "strings" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/errors" @@ -30,7 +30,11 @@ func (f *Function) CompileCall(root *expression.Expression) error { isSyscall := name == "syscall" if !isSyscall { - fullName = fmt.Sprintf("%s.%s", pkg, name) + tmp := strings.Builder{} + tmp.WriteString(pkg) + tmp.WriteString(".") + tmp.WriteString(name) + fullName = tmp.String() _, exists := f.Functions[fullName] if !exists { From 45e15cdb52ee6f694b5cd8be4db3e64e518c3de8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 30 Jul 2024 21:26:15 +0200 Subject: [PATCH 0422/1012] Reduced memory usage --- src/build/core/CompileCall.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 9251510..7ac3d10 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -1,7 +1,7 @@ package core import ( - "fmt" + "strings" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/errors" @@ -30,7 +30,11 @@ func (f *Function) CompileCall(root *expression.Expression) error { isSyscall := name == "syscall" if !isSyscall { - fullName = fmt.Sprintf("%s.%s", pkg, name) + tmp := strings.Builder{} + tmp.WriteString(pkg) + tmp.WriteString(".") + tmp.WriteString(name) + fullName = tmp.String() _, exists := f.Functions[fullName] if !exists { From 9ea99c97c4ae496c840b0c7fe08fad3e565eb9c5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 31 Jul 2024 00:03:16 +0200 Subject: [PATCH 0423/1012] Added more examples --- examples/collatz/collatz.q | 22 ++++++++++++++++++++++ src/build/expression/Expression.go | 10 ++++++---- tests/examples_test.go | 1 + 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 examples/collatz/collatz.q diff --git a/examples/collatz/collatz.q b/examples/collatz/collatz.q new file mode 100644 index 0000000..3093312 --- /dev/null +++ b/examples/collatz/collatz.q @@ -0,0 +1,22 @@ +import log +import sys + +main() { + x := 12 + + loop { + if x & 1 == 0 { + x /= 2 + } else { + x = 3 * x + 1 + } + + log.number(x) + + if x == 1 { + return + } + + sys.write(1, " ", 1) + } +} \ No newline at end of file diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 3a55506..55abb19 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -55,14 +55,16 @@ func (expr *Expression) Count(buffer []byte, kind token.Kind, name string) uint8 // Reset resets all values to the default. func (expr *Expression) Reset() { - for _, child := range expr.Children { - child.Reset() + expr.Parent = nil + + if expr.Children != nil { + expr.Children = expr.Children[:0] } expr.Token.Reset() - expr.Parent = nil - expr.Children = expr.Children[:0] + expr.Value = 0 expr.Precedence = 0 + expr.IsFolded = false } // EachLeaf iterates through all leaves in the tree. diff --git a/tests/examples_test.go b/tests/examples_test.go index 94d5f80..0ff6142 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -22,6 +22,7 @@ var examples = []struct { {"array", "", "Hello", 0}, {"echo", "Echo", "Echo", 0}, {"itoa", "", "9223372036854775807", 0}, + {"collatz", "", "6 3 10 5 16 8 4 2 1", 0}, } func TestExamples(t *testing.T) { From 2293603923f875f2814ee10dafde77e90b8d985f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 31 Jul 2024 00:03:16 +0200 Subject: [PATCH 0424/1012] Added more examples --- examples/collatz/collatz.q | 22 ++++++++++++++++++++++ src/build/expression/Expression.go | 10 ++++++---- tests/examples_test.go | 1 + 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 examples/collatz/collatz.q diff --git a/examples/collatz/collatz.q b/examples/collatz/collatz.q new file mode 100644 index 0000000..3093312 --- /dev/null +++ b/examples/collatz/collatz.q @@ -0,0 +1,22 @@ +import log +import sys + +main() { + x := 12 + + loop { + if x & 1 == 0 { + x /= 2 + } else { + x = 3 * x + 1 + } + + log.number(x) + + if x == 1 { + return + } + + sys.write(1, " ", 1) + } +} \ No newline at end of file diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 3a55506..55abb19 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -55,14 +55,16 @@ func (expr *Expression) Count(buffer []byte, kind token.Kind, name string) uint8 // Reset resets all values to the default. func (expr *Expression) Reset() { - for _, child := range expr.Children { - child.Reset() + expr.Parent = nil + + if expr.Children != nil { + expr.Children = expr.Children[:0] } expr.Token.Reset() - expr.Parent = nil - expr.Children = expr.Children[:0] + expr.Value = 0 expr.Precedence = 0 + expr.IsFolded = false } // EachLeaf iterates through all leaves in the tree. diff --git a/tests/examples_test.go b/tests/examples_test.go index 94d5f80..0ff6142 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -22,6 +22,7 @@ var examples = []struct { {"array", "", "Hello", 0}, {"echo", "Echo", "Echo", 0}, {"itoa", "", "9223372036854775807", 0}, + {"collatz", "", "6 3 10 5 16 8 4 2 1", 0}, } func TestExamples(t *testing.T) { From 2d905c1ac06adece7c96ad6ef3328d859291826f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 31 Jul 2024 11:55:21 +0200 Subject: [PATCH 0425/1012] Added a check for unused imports --- README.md | 2 +- src/build/Build.go | 6 ++-- src/build/compiler/Compile.go | 51 ++++++++++++++++++++---------- src/build/core/CompileCall.go | 16 +++++++++- src/build/errors/UnknownPackage.go | 18 +++++++++++ src/build/errors/UnusedImport.go | 13 ++++++++ src/build/fs/File.go | 8 +++-- src/build/fs/Import.go | 11 +++++++ src/build/scanner/Scan.go | 11 +++++-- src/build/scanner/Scanner.go | 2 ++ src/build/scanner/scanFile.go | 23 +++++++++++--- tests/errors/UnknownPackage.q | 3 ++ tests/errors/UnusedImport.q | 3 ++ tests/errors_test.go | 2 ++ 14 files changed, 138 insertions(+), 31 deletions(-) create mode 100644 src/build/errors/UnknownPackage.go create mode 100644 src/build/errors/UnusedImport.go create mode 100644 src/build/fs/Import.go create mode 100644 tests/errors/UnknownPackage.q create mode 100644 tests/errors/UnusedImport.q diff --git a/README.md b/README.md index 70e1ea5..98ec0c9 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ This is what generates expressions from tokens. - [x] Unused variables - [x] Unused parameters -- [ ] Unused imports +- [x] Unused imports - [ ] Unnecessary newlines - [ ] Ineffective assignments diff --git a/src/build/Build.go b/src/build/Build.go index b292f4b..7f407ce 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -20,10 +20,10 @@ func New(files ...string) *Build { } } -// Run parses the input files and generates an executable file. +// Run compiles the input files. func (build *Build) Run() (compiler.Result, error) { - functions, errors := scanner.Scan(build.Files) - return compiler.Compile(functions, errors) + files, functions, errors := scanner.Scan(build.Files) + return compiler.Compile(files, functions, errors) } // Executable returns the path to the executable. diff --git a/src/build/compiler/Compile.go b/src/build/compiler/Compile.go index e25de67..a10224d 100644 --- a/src/build/compiler/Compile.go +++ b/src/build/compiler/Compile.go @@ -5,15 +5,34 @@ import ( "git.akyoto.dev/cli/q/src/build/core" "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/fs" ) // Compile waits for the scan to finish and compiles all functions. -func Compile(functions <-chan *core.Function, errs <-chan error) (Result, error) { +func Compile(files <-chan *fs.File, functions <-chan *core.Function, errs <-chan error) (Result, error) { result := Result{} - all := map[string]*core.Function{} + allFunctions := map[string]*core.Function{} + allFiles := map[string]*fs.File{} - for functions != nil || errs != nil { + for functions != nil || files != nil || errs != nil { select { + case function, ok := <-functions: + if !ok { + functions = nil + continue + } + + function.Functions = allFunctions + allFunctions[function.Name] = function + + case file, ok := <-files: + if !ok { + files = nil + continue + } + + allFiles[file.Path] = file + case err, ok := <-errs: if !ok { errs = nil @@ -21,23 +40,14 @@ func Compile(functions <-chan *core.Function, errs <-chan error) (Result, error) } return result, err - - case function, ok := <-functions: - if !ok { - functions = nil - continue - } - - function.Functions = all - all[function.Name] = function } } // Start parallel compilation - CompileFunctions(all) + CompileFunctions(allFunctions) // Report errors if any occurred - for _, function := range all { + for _, function := range allFunctions { if function.Err != nil { return result, function.Err } @@ -46,15 +56,24 @@ func Compile(functions <-chan *core.Function, errs <-chan error) (Result, error) result.DataCount += len(function.Assembler.Data) } + // Check for unused imports in all files + for _, file := range allFiles { + for _, pkg := range file.Imports { + if !pkg.Used { + return result, errors.New(&errors.UnusedImport{Package: pkg.Path}, file, pkg.Position) + } + } + } + // Check for existence of `main` - main, exists := all["main.main"] + main, exists := allFunctions["main.main"] if !exists { return result, errors.MissingMainFunction } result.Main = main - result.Functions = all + result.Functions = allFunctions return result, nil } diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 7ac3d10..68e915a 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -30,6 +30,20 @@ func (f *Function) CompileCall(root *expression.Expression) error { isSyscall := name == "syscall" if !isSyscall { + if pkg != f.File.Package { + if f.File.Imports == nil { + return errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) + } + + imp, exists := f.File.Imports[pkg] + + if !exists { + return errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) + } + + imp.Used = true + } + tmp := strings.Builder{} tmp.WriteString(pkg) tmp.WriteString(".") @@ -38,7 +52,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { _, exists := f.Functions[fullName] if !exists { - return errors.New(&errors.UnknownFunction{Name: name}, f.File, root.Children[0].Token.Position) + return errors.New(&errors.UnknownFunction{Name: name}, f.File, nameRoot.Token.Position) } } diff --git a/src/build/errors/UnknownPackage.go b/src/build/errors/UnknownPackage.go new file mode 100644 index 0000000..5cdc965 --- /dev/null +++ b/src/build/errors/UnknownPackage.go @@ -0,0 +1,18 @@ +package errors + +import "fmt" + +// UnknownPackage represents unknown package errors. +type UnknownPackage struct { + Name string + CorrectName string +} + +// Error generates the string representation. +func (err *UnknownPackage) Error() string { + if err.CorrectName != "" { + return fmt.Sprintf("Unknown package '%s', did you mean '%s'?", err.Name, err.CorrectName) + } + + return fmt.Sprintf("Unknown package '%s'", err.Name) +} diff --git a/src/build/errors/UnusedImport.go b/src/build/errors/UnusedImport.go new file mode 100644 index 0000000..441e9d9 --- /dev/null +++ b/src/build/errors/UnusedImport.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// UnusedImport error is created when an import is never used. +type UnusedImport struct { + Package string +} + +// Error generates the string representation. +func (err *UnusedImport) Error() string { + return fmt.Sprintf("Unused import '%s'", err.Package) +} diff --git a/src/build/fs/File.go b/src/build/fs/File.go index 1a10a1d..205daa1 100644 --- a/src/build/fs/File.go +++ b/src/build/fs/File.go @@ -4,7 +4,9 @@ import "git.akyoto.dev/cli/q/src/build/token" // File represents a single source file. type File struct { - Path string - Bytes []byte - Tokens token.List + Path string + Package string + Bytes []byte + Imports map[string]*Import + Tokens token.List } diff --git a/src/build/fs/Import.go b/src/build/fs/Import.go new file mode 100644 index 0000000..6690425 --- /dev/null +++ b/src/build/fs/Import.go @@ -0,0 +1,11 @@ +package fs + +import "git.akyoto.dev/cli/q/src/build/token" + +// Import represents an import statement in a file. +type Import struct { + Path string + FullPath string + Position token.Position + Used bool +} diff --git a/src/build/scanner/Scan.go b/src/build/scanner/Scan.go index b79d04f..4b25a3d 100644 --- a/src/build/scanner/Scan.go +++ b/src/build/scanner/Scan.go @@ -1,10 +1,14 @@ package scanner -import "git.akyoto.dev/cli/q/src/build/core" +import ( + "git.akyoto.dev/cli/q/src/build/core" + "git.akyoto.dev/cli/q/src/build/fs" +) // Scan scans the list of files. -func Scan(files []string) (<-chan *core.Function, <-chan error) { +func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan error) { scanner := Scanner{ + files: make(chan *fs.File), functions: make(chan *core.Function), errors: make(chan error), } @@ -12,9 +16,10 @@ func Scan(files []string) (<-chan *core.Function, <-chan error) { go func() { scanner.queue(files...) scanner.group.Wait() + close(scanner.files) close(scanner.functions) close(scanner.errors) }() - return scanner.functions, scanner.errors + return scanner.files, scanner.functions, scanner.errors } diff --git a/src/build/scanner/Scanner.go b/src/build/scanner/Scanner.go index 0c829ac..5e6b0c2 100644 --- a/src/build/scanner/Scanner.go +++ b/src/build/scanner/Scanner.go @@ -4,10 +4,12 @@ import ( "sync" "git.akyoto.dev/cli/q/src/build/core" + "git.akyoto.dev/cli/q/src/build/fs" ) // Scanner is used to scan files before the actual compilation step. type Scanner struct { + files chan *fs.File functions chan *core.Function errors chan error queued sync.Map diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index 4d7b620..757cbaf 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -26,11 +26,14 @@ func (s *Scanner) scanFile(path string, pkg string) error { tokens := token.Tokenize(contents) file := &fs.File{ - Path: path, - Bytes: contents, - Tokens: tokens, + Path: path, + Bytes: contents, + Tokens: tokens, + Package: pkg, } + s.files <- file + var ( i = 0 groupLevel = 0 @@ -50,8 +53,20 @@ func (s *Scanner) scanFile(path string, pkg string) error { } packageName := tokens[i].Text(contents) - s.queueDirectory(filepath.Join(config.Library, packageName), packageName) + if file.Imports == nil { + file.Imports = map[string]*fs.Import{} + } + + fullPath := filepath.Join(config.Library, packageName) + + file.Imports[packageName] = &fs.Import{ + Path: packageName, + FullPath: fullPath, + Position: tokens[i].Position, + } + + s.queueDirectory(fullPath, packageName) i++ if tokens[i].Kind != token.NewLine && tokens[i].Kind != token.EOF { diff --git a/tests/errors/UnknownPackage.q b/tests/errors/UnknownPackage.q new file mode 100644 index 0000000..a159f13 --- /dev/null +++ b/tests/errors/UnknownPackage.q @@ -0,0 +1,3 @@ +main() { + sys.read() +} \ No newline at end of file diff --git a/tests/errors/UnusedImport.q b/tests/errors/UnusedImport.q new file mode 100644 index 0000000..e5a5498 --- /dev/null +++ b/tests/errors/UnusedImport.q @@ -0,0 +1,3 @@ +import sys + +main(){} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 88f6b3f..2b9ce40 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -40,6 +40,8 @@ var errs = []struct { {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}}, + {"UnknownPackage.q", &errors.UnknownPackage{Name: "sys"}}, + {"UnusedImport.q", &errors.UnusedImport{Package: "sys"}}, {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, } From 87ae78c86ad502f382b98bd181d09cb159674e3f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 31 Jul 2024 11:55:21 +0200 Subject: [PATCH 0426/1012] Added a check for unused imports --- README.md | 2 +- src/build/Build.go | 6 ++-- src/build/compiler/Compile.go | 51 ++++++++++++++++++++---------- src/build/core/CompileCall.go | 16 +++++++++- src/build/errors/UnknownPackage.go | 18 +++++++++++ src/build/errors/UnusedImport.go | 13 ++++++++ src/build/fs/File.go | 8 +++-- src/build/fs/Import.go | 11 +++++++ src/build/scanner/Scan.go | 11 +++++-- src/build/scanner/Scanner.go | 2 ++ src/build/scanner/scanFile.go | 23 +++++++++++--- tests/errors/UnknownPackage.q | 3 ++ tests/errors/UnusedImport.q | 3 ++ tests/errors_test.go | 2 ++ 14 files changed, 138 insertions(+), 31 deletions(-) create mode 100644 src/build/errors/UnknownPackage.go create mode 100644 src/build/errors/UnusedImport.go create mode 100644 src/build/fs/Import.go create mode 100644 tests/errors/UnknownPackage.q create mode 100644 tests/errors/UnusedImport.q diff --git a/README.md b/README.md index 70e1ea5..98ec0c9 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ This is what generates expressions from tokens. - [x] Unused variables - [x] Unused parameters -- [ ] Unused imports +- [x] Unused imports - [ ] Unnecessary newlines - [ ] Ineffective assignments diff --git a/src/build/Build.go b/src/build/Build.go index b292f4b..7f407ce 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -20,10 +20,10 @@ func New(files ...string) *Build { } } -// Run parses the input files and generates an executable file. +// Run compiles the input files. func (build *Build) Run() (compiler.Result, error) { - functions, errors := scanner.Scan(build.Files) - return compiler.Compile(functions, errors) + files, functions, errors := scanner.Scan(build.Files) + return compiler.Compile(files, functions, errors) } // Executable returns the path to the executable. diff --git a/src/build/compiler/Compile.go b/src/build/compiler/Compile.go index e25de67..a10224d 100644 --- a/src/build/compiler/Compile.go +++ b/src/build/compiler/Compile.go @@ -5,15 +5,34 @@ import ( "git.akyoto.dev/cli/q/src/build/core" "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/fs" ) // Compile waits for the scan to finish and compiles all functions. -func Compile(functions <-chan *core.Function, errs <-chan error) (Result, error) { +func Compile(files <-chan *fs.File, functions <-chan *core.Function, errs <-chan error) (Result, error) { result := Result{} - all := map[string]*core.Function{} + allFunctions := map[string]*core.Function{} + allFiles := map[string]*fs.File{} - for functions != nil || errs != nil { + for functions != nil || files != nil || errs != nil { select { + case function, ok := <-functions: + if !ok { + functions = nil + continue + } + + function.Functions = allFunctions + allFunctions[function.Name] = function + + case file, ok := <-files: + if !ok { + files = nil + continue + } + + allFiles[file.Path] = file + case err, ok := <-errs: if !ok { errs = nil @@ -21,23 +40,14 @@ func Compile(functions <-chan *core.Function, errs <-chan error) (Result, error) } return result, err - - case function, ok := <-functions: - if !ok { - functions = nil - continue - } - - function.Functions = all - all[function.Name] = function } } // Start parallel compilation - CompileFunctions(all) + CompileFunctions(allFunctions) // Report errors if any occurred - for _, function := range all { + for _, function := range allFunctions { if function.Err != nil { return result, function.Err } @@ -46,15 +56,24 @@ func Compile(functions <-chan *core.Function, errs <-chan error) (Result, error) result.DataCount += len(function.Assembler.Data) } + // Check for unused imports in all files + for _, file := range allFiles { + for _, pkg := range file.Imports { + if !pkg.Used { + return result, errors.New(&errors.UnusedImport{Package: pkg.Path}, file, pkg.Position) + } + } + } + // Check for existence of `main` - main, exists := all["main.main"] + main, exists := allFunctions["main.main"] if !exists { return result, errors.MissingMainFunction } result.Main = main - result.Functions = all + result.Functions = allFunctions return result, nil } diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 7ac3d10..68e915a 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -30,6 +30,20 @@ func (f *Function) CompileCall(root *expression.Expression) error { isSyscall := name == "syscall" if !isSyscall { + if pkg != f.File.Package { + if f.File.Imports == nil { + return errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) + } + + imp, exists := f.File.Imports[pkg] + + if !exists { + return errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) + } + + imp.Used = true + } + tmp := strings.Builder{} tmp.WriteString(pkg) tmp.WriteString(".") @@ -38,7 +52,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { _, exists := f.Functions[fullName] if !exists { - return errors.New(&errors.UnknownFunction{Name: name}, f.File, root.Children[0].Token.Position) + return errors.New(&errors.UnknownFunction{Name: name}, f.File, nameRoot.Token.Position) } } diff --git a/src/build/errors/UnknownPackage.go b/src/build/errors/UnknownPackage.go new file mode 100644 index 0000000..5cdc965 --- /dev/null +++ b/src/build/errors/UnknownPackage.go @@ -0,0 +1,18 @@ +package errors + +import "fmt" + +// UnknownPackage represents unknown package errors. +type UnknownPackage struct { + Name string + CorrectName string +} + +// Error generates the string representation. +func (err *UnknownPackage) Error() string { + if err.CorrectName != "" { + return fmt.Sprintf("Unknown package '%s', did you mean '%s'?", err.Name, err.CorrectName) + } + + return fmt.Sprintf("Unknown package '%s'", err.Name) +} diff --git a/src/build/errors/UnusedImport.go b/src/build/errors/UnusedImport.go new file mode 100644 index 0000000..441e9d9 --- /dev/null +++ b/src/build/errors/UnusedImport.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// UnusedImport error is created when an import is never used. +type UnusedImport struct { + Package string +} + +// Error generates the string representation. +func (err *UnusedImport) Error() string { + return fmt.Sprintf("Unused import '%s'", err.Package) +} diff --git a/src/build/fs/File.go b/src/build/fs/File.go index 1a10a1d..205daa1 100644 --- a/src/build/fs/File.go +++ b/src/build/fs/File.go @@ -4,7 +4,9 @@ import "git.akyoto.dev/cli/q/src/build/token" // File represents a single source file. type File struct { - Path string - Bytes []byte - Tokens token.List + Path string + Package string + Bytes []byte + Imports map[string]*Import + Tokens token.List } diff --git a/src/build/fs/Import.go b/src/build/fs/Import.go new file mode 100644 index 0000000..6690425 --- /dev/null +++ b/src/build/fs/Import.go @@ -0,0 +1,11 @@ +package fs + +import "git.akyoto.dev/cli/q/src/build/token" + +// Import represents an import statement in a file. +type Import struct { + Path string + FullPath string + Position token.Position + Used bool +} diff --git a/src/build/scanner/Scan.go b/src/build/scanner/Scan.go index b79d04f..4b25a3d 100644 --- a/src/build/scanner/Scan.go +++ b/src/build/scanner/Scan.go @@ -1,10 +1,14 @@ package scanner -import "git.akyoto.dev/cli/q/src/build/core" +import ( + "git.akyoto.dev/cli/q/src/build/core" + "git.akyoto.dev/cli/q/src/build/fs" +) // Scan scans the list of files. -func Scan(files []string) (<-chan *core.Function, <-chan error) { +func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan error) { scanner := Scanner{ + files: make(chan *fs.File), functions: make(chan *core.Function), errors: make(chan error), } @@ -12,9 +16,10 @@ func Scan(files []string) (<-chan *core.Function, <-chan error) { go func() { scanner.queue(files...) scanner.group.Wait() + close(scanner.files) close(scanner.functions) close(scanner.errors) }() - return scanner.functions, scanner.errors + return scanner.files, scanner.functions, scanner.errors } diff --git a/src/build/scanner/Scanner.go b/src/build/scanner/Scanner.go index 0c829ac..5e6b0c2 100644 --- a/src/build/scanner/Scanner.go +++ b/src/build/scanner/Scanner.go @@ -4,10 +4,12 @@ import ( "sync" "git.akyoto.dev/cli/q/src/build/core" + "git.akyoto.dev/cli/q/src/build/fs" ) // Scanner is used to scan files before the actual compilation step. type Scanner struct { + files chan *fs.File functions chan *core.Function errors chan error queued sync.Map diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index 4d7b620..757cbaf 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -26,11 +26,14 @@ func (s *Scanner) scanFile(path string, pkg string) error { tokens := token.Tokenize(contents) file := &fs.File{ - Path: path, - Bytes: contents, - Tokens: tokens, + Path: path, + Bytes: contents, + Tokens: tokens, + Package: pkg, } + s.files <- file + var ( i = 0 groupLevel = 0 @@ -50,8 +53,20 @@ func (s *Scanner) scanFile(path string, pkg string) error { } packageName := tokens[i].Text(contents) - s.queueDirectory(filepath.Join(config.Library, packageName), packageName) + if file.Imports == nil { + file.Imports = map[string]*fs.Import{} + } + + fullPath := filepath.Join(config.Library, packageName) + + file.Imports[packageName] = &fs.Import{ + Path: packageName, + FullPath: fullPath, + Position: tokens[i].Position, + } + + s.queueDirectory(fullPath, packageName) i++ if tokens[i].Kind != token.NewLine && tokens[i].Kind != token.EOF { diff --git a/tests/errors/UnknownPackage.q b/tests/errors/UnknownPackage.q new file mode 100644 index 0000000..a159f13 --- /dev/null +++ b/tests/errors/UnknownPackage.q @@ -0,0 +1,3 @@ +main() { + sys.read() +} \ No newline at end of file diff --git a/tests/errors/UnusedImport.q b/tests/errors/UnusedImport.q new file mode 100644 index 0000000..e5a5498 --- /dev/null +++ b/tests/errors/UnusedImport.q @@ -0,0 +1,3 @@ +import sys + +main(){} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 88f6b3f..2b9ce40 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -40,6 +40,8 @@ var errs = []struct { {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}}, + {"UnknownPackage.q", &errors.UnknownPackage{Name: "sys"}}, + {"UnusedImport.q", &errors.UnusedImport{Package: "sys"}}, {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, } From c415f0250ad15bc33741dbbf40670355667763c7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 31 Jul 2024 13:37:46 +0200 Subject: [PATCH 0427/1012] Simplified itoa --- README.md | 1 + lib/log/number.q | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 98ec0c9..d28007f 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ A simple programming language. * Fast compilation * Small binaries +* High performance ## Installation diff --git a/lib/log/number.q b/lib/log/number.q index c8de482..6b9c531 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -4,7 +4,8 @@ import sys number(x) { length := 20 buffer := mem.alloc(length) - itoa(x, buffer, length) + tmp := itoa(x, buffer, length) + sys.write(1, tmp, buffer + length - tmp) mem.free(buffer, length) } @@ -19,8 +20,7 @@ itoa(x, buffer, length) { tmp[0] = '0' + digit if x == 0 { - sys.write(1, tmp, end - tmp) - return + return tmp } } } \ No newline at end of file From 76c916018ae8f0d3edb8dd3a1775e2e32c703bc3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 31 Jul 2024 13:37:46 +0200 Subject: [PATCH 0428/1012] Simplified itoa --- README.md | 1 + lib/log/number.q | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 98ec0c9..d28007f 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ A simple programming language. * Fast compilation * Small binaries +* High performance ## Installation diff --git a/lib/log/number.q b/lib/log/number.q index c8de482..6b9c531 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -4,7 +4,8 @@ import sys number(x) { length := 20 buffer := mem.alloc(length) - itoa(x, buffer, length) + tmp := itoa(x, buffer, length) + sys.write(1, tmp, buffer + length - tmp) mem.free(buffer, length) } @@ -19,8 +20,7 @@ itoa(x, buffer, length) { tmp[0] = '0' + digit if x == 0 { - sys.write(1, tmp, end - tmp) - return + return tmp } } } \ No newline at end of file From 553a673b16d94d716f2fcb54d4d930af1e1c473e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 31 Jul 2024 15:24:21 +0200 Subject: [PATCH 0429/1012] Added more tests --- src/build/register/RegisterNumber.go | 16 +++++++++++++--- tests/programs/64-bit.q | 12 ++++++++++++ tests/programs/negation.q | 7 +++++-- tests/programs/negative.q | 18 +++++++++++++++--- tests/programs/out-of-memory.q | 6 ++++++ tests/programs_test.go | 1 + 6 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 tests/programs/64-bit.q create mode 100644 tests/programs/out-of-memory.q diff --git a/src/build/register/RegisterNumber.go b/src/build/register/RegisterNumber.go index ce9a41f..298c4d7 100644 --- a/src/build/register/RegisterNumber.go +++ b/src/build/register/RegisterNumber.go @@ -3,6 +3,7 @@ package register import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/sizeof" ) func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { @@ -10,11 +11,20 @@ func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { f.SaveRegister(a) } - f.Assembler.RegisterNumber(mnemonic, a, b) + if sizeof.Signed(int64(b)) == 8 { + tmp := f.NewRegister() + f.Assembler.RegisterNumber(asm.MOVE, tmp, b) + f.UseRegister(tmp) + f.postInstruction() + f.Assembler.RegisterRegister(mnemonic, a, tmp) + f.postInstruction() + f.FreeRegister(tmp) + } else { + f.Assembler.RegisterNumber(mnemonic, a, b) + f.postInstruction() + } if mnemonic == asm.MOVE { f.UseRegister(a) } - - f.postInstruction() } diff --git a/tests/programs/64-bit.q b/tests/programs/64-bit.q new file mode 100644 index 0000000..37e9d42 --- /dev/null +++ b/tests/programs/64-bit.q @@ -0,0 +1,12 @@ +main() { + x := 20000000000 + assert x == 20000000000 + x -= 10000000000 + assert x == 10000000000 + x -= 10000000000 + assert x == 0 + x -= 10000000000 + assert x == -10000000000 + x -= 10000000000 + assert x == -20000000000 +} \ No newline at end of file diff --git a/tests/programs/negation.q b/tests/programs/negation.q index 94e2826..709f995 100644 --- a/tests/programs/negation.q +++ b/tests/programs/negation.q @@ -1,7 +1,10 @@ main() { - assert f(-32) == 32 + assert neg(-1) == 1 + assert neg(1) == -1 + assert neg(-256) == 256 + assert neg(256) == -256 } -f(x) { +neg(x) { return -x } \ No newline at end of file diff --git a/tests/programs/negative.q b/tests/programs/negative.q index f300e48..dd0ff3f 100644 --- a/tests/programs/negative.q +++ b/tests/programs/negative.q @@ -1,5 +1,17 @@ main() { - a := -32 - b := 64 - assert a + b == 32 + a := -1 + b := -2 + assert a == -1 + assert a != 0xFF + assert a != 0xFFFF + assert a != 0xFFFFFFFF + assert b == -2 + assert b != 0xFE + assert b != 0xFFFE + assert b != 0xFFFFFFFE + assert a + b == -3 + assert a - b == 1 + assert a * b == 2 + assert a / b == 0 + assert a % b == -1 } \ No newline at end of file diff --git a/tests/programs/out-of-memory.q b/tests/programs/out-of-memory.q new file mode 100644 index 0000000..4b7603f --- /dev/null +++ b/tests/programs/out-of-memory.q @@ -0,0 +1,6 @@ +import mem + +main() { + address := mem.alloc(1024 * 1024 * 1024 * 1024) + assert address < 0 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 6fbc5fe..3406331 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -51,6 +51,7 @@ var programs = []struct { {"jump-near", "", "", 0}, {"loop", "", "", 0}, {"loop-lifetime", "", "", 0}, + {"out-of-memory", "", "", 0}, } func TestPrograms(t *testing.T) { From 3d245a15f9ae1761509a3b32bc50b74c4a8aaf8d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 31 Jul 2024 15:24:21 +0200 Subject: [PATCH 0430/1012] Added more tests --- src/build/register/RegisterNumber.go | 16 +++++++++++++--- tests/programs/64-bit.q | 12 ++++++++++++ tests/programs/negation.q | 7 +++++-- tests/programs/negative.q | 18 +++++++++++++++--- tests/programs/out-of-memory.q | 6 ++++++ tests/programs_test.go | 1 + 6 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 tests/programs/64-bit.q create mode 100644 tests/programs/out-of-memory.q diff --git a/src/build/register/RegisterNumber.go b/src/build/register/RegisterNumber.go index ce9a41f..298c4d7 100644 --- a/src/build/register/RegisterNumber.go +++ b/src/build/register/RegisterNumber.go @@ -3,6 +3,7 @@ package register import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/sizeof" ) func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { @@ -10,11 +11,20 @@ func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { f.SaveRegister(a) } - f.Assembler.RegisterNumber(mnemonic, a, b) + if sizeof.Signed(int64(b)) == 8 { + tmp := f.NewRegister() + f.Assembler.RegisterNumber(asm.MOVE, tmp, b) + f.UseRegister(tmp) + f.postInstruction() + f.Assembler.RegisterRegister(mnemonic, a, tmp) + f.postInstruction() + f.FreeRegister(tmp) + } else { + f.Assembler.RegisterNumber(mnemonic, a, b) + f.postInstruction() + } if mnemonic == asm.MOVE { f.UseRegister(a) } - - f.postInstruction() } diff --git a/tests/programs/64-bit.q b/tests/programs/64-bit.q new file mode 100644 index 0000000..37e9d42 --- /dev/null +++ b/tests/programs/64-bit.q @@ -0,0 +1,12 @@ +main() { + x := 20000000000 + assert x == 20000000000 + x -= 10000000000 + assert x == 10000000000 + x -= 10000000000 + assert x == 0 + x -= 10000000000 + assert x == -10000000000 + x -= 10000000000 + assert x == -20000000000 +} \ No newline at end of file diff --git a/tests/programs/negation.q b/tests/programs/negation.q index 94e2826..709f995 100644 --- a/tests/programs/negation.q +++ b/tests/programs/negation.q @@ -1,7 +1,10 @@ main() { - assert f(-32) == 32 + assert neg(-1) == 1 + assert neg(1) == -1 + assert neg(-256) == 256 + assert neg(256) == -256 } -f(x) { +neg(x) { return -x } \ No newline at end of file diff --git a/tests/programs/negative.q b/tests/programs/negative.q index f300e48..dd0ff3f 100644 --- a/tests/programs/negative.q +++ b/tests/programs/negative.q @@ -1,5 +1,17 @@ main() { - a := -32 - b := 64 - assert a + b == 32 + a := -1 + b := -2 + assert a == -1 + assert a != 0xFF + assert a != 0xFFFF + assert a != 0xFFFFFFFF + assert b == -2 + assert b != 0xFE + assert b != 0xFFFE + assert b != 0xFFFFFFFE + assert a + b == -3 + assert a - b == 1 + assert a * b == 2 + assert a / b == 0 + assert a % b == -1 } \ No newline at end of file diff --git a/tests/programs/out-of-memory.q b/tests/programs/out-of-memory.q new file mode 100644 index 0000000..4b7603f --- /dev/null +++ b/tests/programs/out-of-memory.q @@ -0,0 +1,6 @@ +import mem + +main() { + address := mem.alloc(1024 * 1024 * 1024 * 1024) + assert address < 0 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 6fbc5fe..3406331 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -51,6 +51,7 @@ var programs = []struct { {"jump-near", "", "", 0}, {"loop", "", "", 0}, {"loop-lifetime", "", "", 0}, + {"out-of-memory", "", "", 0}, } func TestPrograms(t *testing.T) { From b8aa7ccf434195ab91fd438f0a956a29f70b09b5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 31 Jul 2024 15:27:20 +0200 Subject: [PATCH 0431/1012] Added more tests --- tests/programs_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/programs_test.go b/tests/programs_test.go index 3406331..493ac51 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -36,6 +36,7 @@ var programs = []struct { {"modulo", "", "", 0}, {"modulo-assign", "", "", 0}, {"div-split", "", "", 0}, + {"64-bit", "", "", 0}, {"negative", "", "", 0}, {"negation", "", "", 0}, {"square-sum", "", "", 0}, From 88be002c4528a526d3deb58ad8721cd490d80c20 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 31 Jul 2024 15:27:20 +0200 Subject: [PATCH 0432/1012] Added more tests --- tests/programs_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/programs_test.go b/tests/programs_test.go index 3406331..493ac51 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -36,6 +36,7 @@ var programs = []struct { {"modulo", "", "", 0}, {"modulo-assign", "", "", 0}, {"div-split", "", "", 0}, + {"64-bit", "", "", 0}, {"negative", "", "", 0}, {"negation", "", "", 0}, {"square-sum", "", "", 0}, From 85a6a957aa3af1ed1700d4fcf40ad51f21e046e0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 31 Jul 2024 15:36:44 +0200 Subject: [PATCH 0433/1012] Removed unnecessary instructions --- src/build/register/RegisterNumber.go | 29 +++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/build/register/RegisterNumber.go b/src/build/register/RegisterNumber.go index 298c4d7..a6b5aa6 100644 --- a/src/build/register/RegisterNumber.go +++ b/src/build/register/RegisterNumber.go @@ -11,20 +11,27 @@ func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { f.SaveRegister(a) } - if sizeof.Signed(int64(b)) == 8 { - tmp := f.NewRegister() - f.Assembler.RegisterNumber(asm.MOVE, tmp, b) - f.UseRegister(tmp) - f.postInstruction() - f.Assembler.RegisterRegister(mnemonic, a, tmp) - f.postInstruction() - f.FreeRegister(tmp) - } else { + // The `MOVE` operation is very flexible and works with any type of immediate number. + if mnemonic == asm.MOVE { f.Assembler.RegisterNumber(mnemonic, a, b) + f.UseRegister(a) f.postInstruction() + return } - if mnemonic == asm.MOVE { - f.UseRegister(a) + // If the number only needs 32 bits, we can encode the instruction. + if sizeof.Signed(int64(b)) <= 4 { + f.Assembler.RegisterNumber(mnemonic, a, b) + f.postInstruction() + return } + + // If the number needs 64 bits, we need to use a temporary register. + tmp := f.NewRegister() + f.Assembler.RegisterNumber(asm.MOVE, tmp, b) + f.UseRegister(tmp) + f.postInstruction() + f.Assembler.RegisterRegister(mnemonic, a, tmp) + f.postInstruction() + f.FreeRegister(tmp) } From cdbfa744b7ff6a0716a30c76cf372bb2d8728f4f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 31 Jul 2024 15:36:44 +0200 Subject: [PATCH 0434/1012] Removed unnecessary instructions --- src/build/register/RegisterNumber.go | 29 +++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/build/register/RegisterNumber.go b/src/build/register/RegisterNumber.go index 298c4d7..a6b5aa6 100644 --- a/src/build/register/RegisterNumber.go +++ b/src/build/register/RegisterNumber.go @@ -11,20 +11,27 @@ func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { f.SaveRegister(a) } - if sizeof.Signed(int64(b)) == 8 { - tmp := f.NewRegister() - f.Assembler.RegisterNumber(asm.MOVE, tmp, b) - f.UseRegister(tmp) - f.postInstruction() - f.Assembler.RegisterRegister(mnemonic, a, tmp) - f.postInstruction() - f.FreeRegister(tmp) - } else { + // The `MOVE` operation is very flexible and works with any type of immediate number. + if mnemonic == asm.MOVE { f.Assembler.RegisterNumber(mnemonic, a, b) + f.UseRegister(a) f.postInstruction() + return } - if mnemonic == asm.MOVE { - f.UseRegister(a) + // If the number only needs 32 bits, we can encode the instruction. + if sizeof.Signed(int64(b)) <= 4 { + f.Assembler.RegisterNumber(mnemonic, a, b) + f.postInstruction() + return } + + // If the number needs 64 bits, we need to use a temporary register. + tmp := f.NewRegister() + f.Assembler.RegisterNumber(asm.MOVE, tmp, b) + f.UseRegister(tmp) + f.postInstruction() + f.Assembler.RegisterRegister(mnemonic, a, tmp) + f.postInstruction() + f.FreeRegister(tmp) } From e5adcff1af6ff4735d94cfe575f5f938830feb0d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 31 Jul 2024 17:50:31 +0200 Subject: [PATCH 0435/1012] Added escape sequences --- README.md | 1 + examples/hello/hello.q | 2 +- src/build/asm/Finalize.go | 6 ++++ src/build/core/ExpressionToRegister.go | 8 +++++ src/build/core/Number.go | 2 +- src/build/core/String.go | 43 ++++++++++++++++++++++++++ src/build/core/TokenToRegister.go | 3 +- src/build/token/Tokenize.go | 2 +- tests/examples_test.go | 2 +- tests/programs/escape-rune.q | 9 ++++++ tests/programs/escape-string.q | 10 ++++++ tests/programs_test.go | 6 ++-- 12 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 src/build/core/String.go create mode 100644 tests/programs/escape-rune.q create mode 100644 tests/programs/escape-string.q diff --git a/README.md b/README.md index d28007f..8dcacc6 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ This is what generates expressions from tokens. - [x] Branches - [x] Loops - [x] Hexadecimal, octal and binary literals +- [x] Escape sequences - [ ] Data structures - [ ] Type system - [ ] Type operator: `|` (`User | Error`) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 6adfc54..2371295 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,7 +1,7 @@ import sys main() { - print("Hello", 5) + print("Hello\n", 6) } print(address, length) { diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index 7094fe2..340b6d4 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -132,6 +132,12 @@ func (a Assembler) Finalize() ([]byte, []byte) { case LABEL: labels[x.Data.(*Label).Name] = Address(len(code)) + case LOAD: + switch operands := x.Data.(type) { + case *MemoryRegister: + code = x64.LoadRegister(code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) + } + case MODULO: code = modulo(code, x.Data) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index a49264f..1956932 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -6,6 +6,7 @@ import ( "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" ) // ExpressionToRegister puts the result of an expression into the specified register. @@ -29,6 +30,13 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return err } + if node.Token.Kind == token.Array { + array := f.VariableByName(node.Children[0].Token.Text(f.File.Bytes)) + offset, err := f.Number(node.Children[1].Token) + f.MemoryRegister(asm.LOAD, asm.Memory{Base: array.Register, Offset: byte(offset), Length: 1}, register) + return err + } + if len(node.Children) == 1 { if !node.Token.IsUnaryOperator() { return errors.New(errors.MissingOperand, f.File, node.Token.End()) diff --git a/src/build/core/Number.go b/src/build/core/Number.go index f6c14cd..394666f 100644 --- a/src/build/core/Number.go +++ b/src/build/core/Number.go @@ -35,7 +35,7 @@ func (f *Function) Number(t token.Token) (int, error) { case token.Rune: r := t.Bytes(f.File.Bytes) - r = r[1 : len(r)-1] + r = String(r) if len(r) == 0 { return 0, errors.New(errors.InvalidRune, f.File, t.Position+1) diff --git a/src/build/core/String.go b/src/build/core/String.go new file mode 100644 index 0000000..f614c3b --- /dev/null +++ b/src/build/core/String.go @@ -0,0 +1,43 @@ +package core + +import "bytes" + +// String replaces the escape sequences in the contents of a string token with the respective characters. +func String(data []byte) []byte { + data = data[1 : len(data)-1] + escape := bytes.IndexByte(data, '\\') + + if escape == -1 { + return data + } + + tmp := make([]byte, 0, len(data)) + + for { + tmp = append(tmp, data[:escape]...) + + switch data[escape+1] { + case '0': + tmp = append(tmp, '\000') + case 't': + tmp = append(tmp, '\t') + case 'n': + tmp = append(tmp, '\n') + case 'r': + tmp = append(tmp, '\r') + case '"': + tmp = append(tmp, '"') + case '\'': + tmp = append(tmp, '\'') + case '\\': + tmp = append(tmp, '\\') + } + + data = data[escape+2:] + escape = bytes.IndexByte(data, '\\') + + if escape == -1 { + return tmp + } + } +} diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 0ed4e0a..6dd6c8c 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -34,7 +34,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return nil case token.String: - data := t.Bytes(f.File.Bytes)[1 : t.Length-1] + data := t.Bytes(f.File.Bytes) + data = String(data) label := f.AddBytes(data) f.RegisterLabel(asm.MOVE, register, label) return nil diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index c955e9a..be8a309 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -76,7 +76,7 @@ func Tokenize(buffer []byte) List { i++ for i < Position(len(buffer)) { - if buffer[i] == limiter { + if buffer[i] == limiter && (buffer[i-1] != '\\' || buffer[i-2] == '\\') { end = i + 1 i++ break diff --git a/tests/examples_test.go b/tests/examples_test.go index 0ff6142..116e270 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -15,7 +15,7 @@ var examples = []struct { Output string ExitCode int }{ - {"hello", "", "Hello", 0}, + {"hello", "", "Hello\n", 0}, {"factorial", "", "120", 0}, {"fibonacci", "", "55", 0}, {"gcd", "", "21", 0}, diff --git a/tests/programs/escape-rune.q b/tests/programs/escape-rune.q new file mode 100644 index 0000000..f7e0950 --- /dev/null +++ b/tests/programs/escape-rune.q @@ -0,0 +1,9 @@ +main() { + assert '\0' == 0 + assert '\t' == 9 + assert '\n' == 10 + assert '\r' == 13 + assert '\"' == 34 + assert '\'' == 39 + assert '\\' == 92 +} \ No newline at end of file diff --git a/tests/programs/escape-string.q b/tests/programs/escape-string.q new file mode 100644 index 0000000..1902f17 --- /dev/null +++ b/tests/programs/escape-string.q @@ -0,0 +1,10 @@ +main() { + str := "\0\t\n\r\"\'\\" + assert str[0] == '\0' + assert str[1] == '\t' + assert str[2] == '\n' + assert str[3] == '\r' + assert str[4] == '\"' + assert str[5] == '\'' + assert str[6] == '\\' +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 493ac51..043dbe9 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -24,11 +24,13 @@ var programs = []struct { {"reassign", "", "", 0}, {"reuse", "", "", 0}, {"return", "", "", 0}, + {"math", "", "", 0}, + {"precedence", "", "", 0}, {"binary", "", "", 0}, {"octal", "", "", 0}, {"hexadecimal", "", "", 0}, - {"math", "", "", 0}, - {"precedence", "", "", 0}, + {"escape-rune", "", "", 0}, + {"escape-string", "", "", 0}, {"bitwise-and", "", "", 0}, {"bitwise-or", "", "", 0}, {"bitwise-xor", "", "", 0}, From c8824e699aed04b905867e95b2ea35e544cdf6bd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 31 Jul 2024 17:50:31 +0200 Subject: [PATCH 0436/1012] Added escape sequences --- README.md | 1 + examples/hello/hello.q | 2 +- src/build/asm/Finalize.go | 6 ++++ src/build/core/ExpressionToRegister.go | 8 +++++ src/build/core/Number.go | 2 +- src/build/core/String.go | 43 ++++++++++++++++++++++++++ src/build/core/TokenToRegister.go | 3 +- src/build/token/Tokenize.go | 2 +- tests/examples_test.go | 2 +- tests/programs/escape-rune.q | 9 ++++++ tests/programs/escape-string.q | 10 ++++++ tests/programs_test.go | 6 ++-- 12 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 src/build/core/String.go create mode 100644 tests/programs/escape-rune.q create mode 100644 tests/programs/escape-string.q diff --git a/README.md b/README.md index d28007f..8dcacc6 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ This is what generates expressions from tokens. - [x] Branches - [x] Loops - [x] Hexadecimal, octal and binary literals +- [x] Escape sequences - [ ] Data structures - [ ] Type system - [ ] Type operator: `|` (`User | Error`) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 6adfc54..2371295 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,7 +1,7 @@ import sys main() { - print("Hello", 5) + print("Hello\n", 6) } print(address, length) { diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index 7094fe2..340b6d4 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -132,6 +132,12 @@ func (a Assembler) Finalize() ([]byte, []byte) { case LABEL: labels[x.Data.(*Label).Name] = Address(len(code)) + case LOAD: + switch operands := x.Data.(type) { + case *MemoryRegister: + code = x64.LoadRegister(code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) + } + case MODULO: code = modulo(code, x.Data) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index a49264f..1956932 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -6,6 +6,7 @@ import ( "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" ) // ExpressionToRegister puts the result of an expression into the specified register. @@ -29,6 +30,13 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return err } + if node.Token.Kind == token.Array { + array := f.VariableByName(node.Children[0].Token.Text(f.File.Bytes)) + offset, err := f.Number(node.Children[1].Token) + f.MemoryRegister(asm.LOAD, asm.Memory{Base: array.Register, Offset: byte(offset), Length: 1}, register) + return err + } + if len(node.Children) == 1 { if !node.Token.IsUnaryOperator() { return errors.New(errors.MissingOperand, f.File, node.Token.End()) diff --git a/src/build/core/Number.go b/src/build/core/Number.go index f6c14cd..394666f 100644 --- a/src/build/core/Number.go +++ b/src/build/core/Number.go @@ -35,7 +35,7 @@ func (f *Function) Number(t token.Token) (int, error) { case token.Rune: r := t.Bytes(f.File.Bytes) - r = r[1 : len(r)-1] + r = String(r) if len(r) == 0 { return 0, errors.New(errors.InvalidRune, f.File, t.Position+1) diff --git a/src/build/core/String.go b/src/build/core/String.go new file mode 100644 index 0000000..f614c3b --- /dev/null +++ b/src/build/core/String.go @@ -0,0 +1,43 @@ +package core + +import "bytes" + +// String replaces the escape sequences in the contents of a string token with the respective characters. +func String(data []byte) []byte { + data = data[1 : len(data)-1] + escape := bytes.IndexByte(data, '\\') + + if escape == -1 { + return data + } + + tmp := make([]byte, 0, len(data)) + + for { + tmp = append(tmp, data[:escape]...) + + switch data[escape+1] { + case '0': + tmp = append(tmp, '\000') + case 't': + tmp = append(tmp, '\t') + case 'n': + tmp = append(tmp, '\n') + case 'r': + tmp = append(tmp, '\r') + case '"': + tmp = append(tmp, '"') + case '\'': + tmp = append(tmp, '\'') + case '\\': + tmp = append(tmp, '\\') + } + + data = data[escape+2:] + escape = bytes.IndexByte(data, '\\') + + if escape == -1 { + return tmp + } + } +} diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 0ed4e0a..6dd6c8c 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -34,7 +34,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return nil case token.String: - data := t.Bytes(f.File.Bytes)[1 : t.Length-1] + data := t.Bytes(f.File.Bytes) + data = String(data) label := f.AddBytes(data) f.RegisterLabel(asm.MOVE, register, label) return nil diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index c955e9a..be8a309 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -76,7 +76,7 @@ func Tokenize(buffer []byte) List { i++ for i < Position(len(buffer)) { - if buffer[i] == limiter { + if buffer[i] == limiter && (buffer[i-1] != '\\' || buffer[i-2] == '\\') { end = i + 1 i++ break diff --git a/tests/examples_test.go b/tests/examples_test.go index 0ff6142..116e270 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -15,7 +15,7 @@ var examples = []struct { Output string ExitCode int }{ - {"hello", "", "Hello", 0}, + {"hello", "", "Hello\n", 0}, {"factorial", "", "120", 0}, {"fibonacci", "", "55", 0}, {"gcd", "", "21", 0}, diff --git a/tests/programs/escape-rune.q b/tests/programs/escape-rune.q new file mode 100644 index 0000000..f7e0950 --- /dev/null +++ b/tests/programs/escape-rune.q @@ -0,0 +1,9 @@ +main() { + assert '\0' == 0 + assert '\t' == 9 + assert '\n' == 10 + assert '\r' == 13 + assert '\"' == 34 + assert '\'' == 39 + assert '\\' == 92 +} \ No newline at end of file diff --git a/tests/programs/escape-string.q b/tests/programs/escape-string.q new file mode 100644 index 0000000..1902f17 --- /dev/null +++ b/tests/programs/escape-string.q @@ -0,0 +1,10 @@ +main() { + str := "\0\t\n\r\"\'\\" + assert str[0] == '\0' + assert str[1] == '\t' + assert str[2] == '\n' + assert str[3] == '\r' + assert str[4] == '\"' + assert str[5] == '\'' + assert str[6] == '\\' +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 493ac51..043dbe9 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -24,11 +24,13 @@ var programs = []struct { {"reassign", "", "", 0}, {"reuse", "", "", 0}, {"return", "", "", 0}, + {"math", "", "", 0}, + {"precedence", "", "", 0}, {"binary", "", "", 0}, {"octal", "", "", 0}, {"hexadecimal", "", "", 0}, - {"math", "", "", 0}, - {"precedence", "", "", 0}, + {"escape-rune", "", "", 0}, + {"escape-string", "", "", 0}, {"bitwise-and", "", "", 0}, {"bitwise-or", "", "", 0}, {"bitwise-xor", "", "", 0}, From e3264ba2e7bfa033ffd38cf39c515bbe7a641d2f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 31 Jul 2024 23:47:44 +0200 Subject: [PATCH 0437/1012] Added more destructive mnemonics --- src/build/register/isDestructive.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/build/register/isDestructive.go b/src/build/register/isDestructive.go index 086ceca..9305367 100644 --- a/src/build/register/isDestructive.go +++ b/src/build/register/isDestructive.go @@ -4,7 +4,7 @@ import "git.akyoto.dev/cli/q/src/build/asm" func isDestructive(mnemonic asm.Mnemonic) bool { switch mnemonic { - case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV: + case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV, asm.MODULO, asm.AND, asm.OR, asm.XOR, asm.SHIFTL, asm.SHIFTRS, asm.NEGATE: return true default: return false From 8862dec3e4f717ed7ae9005bfc99151eced39249 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 31 Jul 2024 23:47:44 +0200 Subject: [PATCH 0438/1012] Added more destructive mnemonics --- src/build/register/isDestructive.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/build/register/isDestructive.go b/src/build/register/isDestructive.go index 086ceca..9305367 100644 --- a/src/build/register/isDestructive.go +++ b/src/build/register/isDestructive.go @@ -4,7 +4,7 @@ import "git.akyoto.dev/cli/q/src/build/asm" func isDestructive(mnemonic asm.Mnemonic) bool { switch mnemonic { - case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV: + case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV, asm.MODULO, asm.AND, asm.OR, asm.XOR, asm.SHIFTL, asm.SHIFTRS, asm.NEGATE: return true default: return false From 03aa384b944ba0fe02ea2a2496781bd65066cfe3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 1 Aug 2024 14:40:47 +0200 Subject: [PATCH 0439/1012] Fixed incorrect token count --- src/build/ast/Count.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index 031a5fc..4c5e40e 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -25,6 +25,7 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { case *If: count += node.Condition.Count(buffer, kind, name) count += Count(node.Body, buffer, kind, name) + count += Count(node.Else, buffer, kind, name) case *Loop: count += Count(node.Body, buffer, kind, name) From 3c70529015ab3550ae4a8bd3f797aee1645d0ea7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 1 Aug 2024 14:40:47 +0200 Subject: [PATCH 0440/1012] Fixed incorrect token count --- src/build/ast/Count.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index 031a5fc..4c5e40e 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -25,6 +25,7 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { case *If: count += node.Condition.Count(buffer, kind, name) count += Count(node.Body, buffer, kind, name) + count += Count(node.Else, buffer, kind, name) case *Loop: count += Count(node.Body, buffer, kind, name) From a59bd174af7c9909fed59331c02f029eeb3a8134 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 1 Aug 2024 23:41:39 +0200 Subject: [PATCH 0441/1012] Improved performance --- src/build/compiler/Compile.go | 4 +- src/build/config/config.go | 3 - src/build/core/CompileAssert.go | 5 -- src/build/expression/Operator.go | 21 ++---- src/build/expression/Parse.go | 111 ++++++++++++++++--------------- src/build/scope/Stack.go | 1 + src/build/token/Kind.go | 16 ++--- src/build/token/Tokenize_test.go | 70 ++++++++++--------- src/cli/Run.go | 4 +- src/cli/System.go | 1 - 10 files changed, 112 insertions(+), 124 deletions(-) diff --git a/src/build/compiler/Compile.go b/src/build/compiler/Compile.go index a10224d..facd13b 100644 --- a/src/build/compiler/Compile.go +++ b/src/build/compiler/Compile.go @@ -11,8 +11,8 @@ import ( // Compile waits for the scan to finish and compiles all functions. func Compile(files <-chan *fs.File, functions <-chan *core.Function, errs <-chan error) (Result, error) { result := Result{} + allFiles := make([]*fs.File, 0, 8) allFunctions := map[string]*core.Function{} - allFiles := map[string]*fs.File{} for functions != nil || files != nil || errs != nil { select { @@ -31,7 +31,7 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, errs <-chan continue } - allFiles[file.Path] = file + allFiles = append(allFiles, file) case err, ok := <-errs: if !ok { diff --git a/src/build/config/config.go b/src/build/config/config.go index 7ff0e87..9254e64 100644 --- a/src/build/config/config.go +++ b/src/build/config/config.go @@ -23,7 +23,4 @@ var ( // Skips writing the executable to disk. Dry = false - - // Skips compiling assert statements. - SkipAsserts = false ) diff --git a/src/build/core/CompileAssert.go b/src/build/core/CompileAssert.go index 05cc38c..2715e44 100644 --- a/src/build/core/CompileAssert.go +++ b/src/build/core/CompileAssert.go @@ -5,15 +5,10 @@ import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/config" ) // CompileAssert compiles an assertion. func (f *Function) CompileAssert(assert *ast.Assert) error { - if config.SkipAsserts { - return nil - } - f.count.assert++ success := fmt.Sprintf("%s_assert_%d_true", f.Name, f.count.assert) fail := fmt.Sprintf("%s_assert_%d_false", f.Name, f.count.assert) diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index 2a4ecdf..96431f1 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -15,7 +15,7 @@ type Operator struct { // Operators defines the operators used in the language. // The number corresponds to the operator priority and can not be zero. -var Operators = map[token.Kind]*Operator{ +var Operators = [64]Operator{ token.Period: {".", 13, 2}, token.Call: {"λ", 12, 1}, token.Array: {"@", 12, 2}, @@ -41,12 +41,15 @@ var Operators = map[token.Kind]*Operator{ token.LogicalAnd: {"&&", 2, 2}, token.LogicalOr: {"||", 1, 2}, + token.Separator: {",", 0, 2}, + token.Assign: {"=", math.MinInt8, 2}, token.Define: {":=", math.MinInt8, 2}, token.AddAssign: {"+=", math.MinInt8, 2}, token.SubAssign: {"-=", math.MinInt8, 2}, token.MulAssign: {"*=", math.MinInt8, 2}, token.DivAssign: {"/=", math.MinInt8, 2}, + token.ModAssign: {"%=", math.MinInt8, 2}, token.ShrAssign: {">>=", math.MinInt8, 2}, token.ShlAssign: {"<<=", math.MinInt8, 2}, } @@ -68,21 +71,9 @@ func isComplete(expr *Expression) bool { } func numOperands(symbol token.Kind) int { - operator, exists := Operators[symbol] - - if !exists { - return -1 - } - - return int(operator.Operands) + return int(Operators[symbol].Operands) } func precedence(symbol token.Kind) int8 { - operator, exists := Operators[symbol] - - if !exists { - return -1 - } - - return operator.Precedence + return Operators[symbol].Precedence } diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index 22dbd39..87e9768 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -101,63 +101,64 @@ func Parse(tokens []token.Token) *Expression { continue } - if t.IsOperator() { - if cursor == nil { - cursor = NewLeaf(t) - cursor.Precedence = precedence(t.Kind) - root = cursor - continue - } - - node := NewLeaf(t) - node.Precedence = precedence(t.Kind) - - if cursor.Token.IsOperator() { - oldPrecedence := cursor.Precedence - newPrecedence := node.Precedence - - if newPrecedence > oldPrecedence { - if len(cursor.Children) == numOperands(cursor.Token.Kind) { - cursor.LastChild().Replace(node) - } else { - cursor.AddChild(node) - } - } else { - start := cursor - - for start != nil { - precedence := start.Precedence - - if precedence < newPrecedence { - start.LastChild().Replace(node) - break - } - - if precedence == newPrecedence { - if start == root { - root = node - } - - start.Replace(node) - break - } - - start = start.Parent - } - - if start == nil { - root.Replace(node) - root = node - } - } - } else { - node.AddChild(cursor) - root = node - } - - cursor = node + if !t.IsOperator() { continue } + + if cursor == nil { + cursor = NewLeaf(t) + cursor.Precedence = precedence(t.Kind) + root = cursor + continue + } + + node := NewLeaf(t) + node.Precedence = precedence(t.Kind) + + if cursor.Token.IsOperator() { + oldPrecedence := cursor.Precedence + newPrecedence := node.Precedence + + if newPrecedence > oldPrecedence { + if len(cursor.Children) == numOperands(cursor.Token.Kind) { + cursor.LastChild().Replace(node) + } else { + cursor.AddChild(node) + } + } else { + start := cursor + + for start != nil { + precedence := start.Precedence + + if precedence < newPrecedence { + start.LastChild().Replace(node) + break + } + + if precedence == newPrecedence { + if start == root { + root = node + } + + start.Replace(node) + break + } + + start = start.Parent + } + + if start == nil { + root.Replace(node) + root = node + } + } + } else { + node.AddChild(cursor) + root = node + } + + cursor = node } return root diff --git a/src/build/scope/Stack.go b/src/build/scope/Stack.go index 40ce40a..ceca9e3 100644 --- a/src/build/scope/Stack.go +++ b/src/build/scope/Stack.go @@ -6,6 +6,7 @@ import ( "git.akyoto.dev/cli/q/src/build/token" ) +// Stack is a stack of scopes. type Stack struct { Scopes []*Scope } diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 2c12464..6bbaff8 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -18,14 +18,6 @@ const ( BlockEnd // } ArrayStart // [ ArrayEnd // ] - _keywords // - Assert // assert - Else // else - If // if - Import // import - Loop // loop - Return // return - _keywordsEnd // _operators // Add // + Sub // - @@ -68,4 +60,12 @@ const ( ShrAssign // >>= _assignmentsEnd // _operatorsEnd // + _keywords // + Assert // assert + Else // else + If // if + Import // import + Loop // loop + Return // return + _keywordsEnd // ) diff --git a/src/build/token/Tokenize_test.go b/src/build/token/Tokenize_test.go index efc743c..4d6e36b 100644 --- a/src/build/token/Tokenize_test.go +++ b/src/build/token/Tokenize_test.go @@ -25,11 +25,15 @@ func TestFunction(t *testing.T) { } func TestKeyword(t *testing.T) { - tokens := token.Tokenize([]byte("return x")) + tokens := token.Tokenize([]byte("assert if import else loop return")) expected := []token.Kind{ + token.Assert, + token.If, + token.Import, + token.Else, + token.Loop, token.Return, - token.Identifier, token.EOF, } @@ -103,6 +107,37 @@ func TestOperator(t *testing.T) { } } +func TestOperatorAssign(t *testing.T) { + tokens := token.Tokenize([]byte(`a += b -= c *= d /= e &= f |= g ^= h <<= i >>= j`)) + + expected := []token.Kind{ + token.Identifier, + token.AddAssign, + token.Identifier, + token.SubAssign, + token.Identifier, + token.MulAssign, + token.Identifier, + token.DivAssign, + token.Identifier, + token.AndAssign, + token.Identifier, + token.OrAssign, + token.Identifier, + token.XorAssign, + token.Identifier, + token.ShlAssign, + token.Identifier, + token.ShrAssign, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + func TestNegateFirstToken(t *testing.T) { tokens := token.Tokenize([]byte(`-a`)) @@ -244,37 +279,6 @@ func TestLeadingZero(t *testing.T) { } } -func TestOperatorAssign(t *testing.T) { - tokens := token.Tokenize([]byte(`a += b -= c *= d /= e &= f |= g ^= h <<= i >>= j`)) - - expected := []token.Kind{ - token.Identifier, - token.AddAssign, - token.Identifier, - token.SubAssign, - token.Identifier, - token.MulAssign, - token.Identifier, - token.DivAssign, - token.Identifier, - token.AndAssign, - token.Identifier, - token.OrAssign, - token.Identifier, - token.XorAssign, - token.Identifier, - token.ShlAssign, - token.Identifier, - token.ShrAssign, - token.Identifier, - token.EOF, - } - - for i, kind := range expected { - assert.Equal(t, tokens[i].Kind, kind) - } -} - func TestSeparator(t *testing.T) { tokens := token.Tokenize([]byte("a,b,c")) diff --git a/src/cli/Run.go b/src/cli/Run.go index 9133f36..7de865b 100644 --- a/src/cli/Run.go +++ b/src/cli/Run.go @@ -33,9 +33,9 @@ func Run(args []string) int { if err != nil { fmt.Fprintln(os.Stderr, err) - switch err.(type) { + switch err := err.(type) { case *exec.ExitError: - return 0 + return err.ExitCode() default: return 1 diff --git a/src/cli/System.go b/src/cli/System.go index 5a26b37..b523d7a 100644 --- a/src/cli/System.go +++ b/src/cli/System.go @@ -18,6 +18,5 @@ func System(args []string) int { fmt.Printf(line, "Compiler:", config.Executable) fmt.Printf(line, "Library:", config.Library) fmt.Printf(line, "Threads:", strconv.Itoa(runtime.NumCPU())) - return 0 } From 778c125d195ef2baa49ffe8480cbe8e06dbe8979 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 1 Aug 2024 23:41:39 +0200 Subject: [PATCH 0442/1012] Improved performance --- src/build/compiler/Compile.go | 4 +- src/build/config/config.go | 3 - src/build/core/CompileAssert.go | 5 -- src/build/expression/Operator.go | 21 ++---- src/build/expression/Parse.go | 111 ++++++++++++++++--------------- src/build/scope/Stack.go | 1 + src/build/token/Kind.go | 16 ++--- src/build/token/Tokenize_test.go | 70 ++++++++++--------- src/cli/Run.go | 4 +- src/cli/System.go | 1 - 10 files changed, 112 insertions(+), 124 deletions(-) diff --git a/src/build/compiler/Compile.go b/src/build/compiler/Compile.go index a10224d..facd13b 100644 --- a/src/build/compiler/Compile.go +++ b/src/build/compiler/Compile.go @@ -11,8 +11,8 @@ import ( // Compile waits for the scan to finish and compiles all functions. func Compile(files <-chan *fs.File, functions <-chan *core.Function, errs <-chan error) (Result, error) { result := Result{} + allFiles := make([]*fs.File, 0, 8) allFunctions := map[string]*core.Function{} - allFiles := map[string]*fs.File{} for functions != nil || files != nil || errs != nil { select { @@ -31,7 +31,7 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, errs <-chan continue } - allFiles[file.Path] = file + allFiles = append(allFiles, file) case err, ok := <-errs: if !ok { diff --git a/src/build/config/config.go b/src/build/config/config.go index 7ff0e87..9254e64 100644 --- a/src/build/config/config.go +++ b/src/build/config/config.go @@ -23,7 +23,4 @@ var ( // Skips writing the executable to disk. Dry = false - - // Skips compiling assert statements. - SkipAsserts = false ) diff --git a/src/build/core/CompileAssert.go b/src/build/core/CompileAssert.go index 05cc38c..2715e44 100644 --- a/src/build/core/CompileAssert.go +++ b/src/build/core/CompileAssert.go @@ -5,15 +5,10 @@ import ( "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/config" ) // CompileAssert compiles an assertion. func (f *Function) CompileAssert(assert *ast.Assert) error { - if config.SkipAsserts { - return nil - } - f.count.assert++ success := fmt.Sprintf("%s_assert_%d_true", f.Name, f.count.assert) fail := fmt.Sprintf("%s_assert_%d_false", f.Name, f.count.assert) diff --git a/src/build/expression/Operator.go b/src/build/expression/Operator.go index 2a4ecdf..96431f1 100644 --- a/src/build/expression/Operator.go +++ b/src/build/expression/Operator.go @@ -15,7 +15,7 @@ type Operator struct { // Operators defines the operators used in the language. // The number corresponds to the operator priority and can not be zero. -var Operators = map[token.Kind]*Operator{ +var Operators = [64]Operator{ token.Period: {".", 13, 2}, token.Call: {"λ", 12, 1}, token.Array: {"@", 12, 2}, @@ -41,12 +41,15 @@ var Operators = map[token.Kind]*Operator{ token.LogicalAnd: {"&&", 2, 2}, token.LogicalOr: {"||", 1, 2}, + token.Separator: {",", 0, 2}, + token.Assign: {"=", math.MinInt8, 2}, token.Define: {":=", math.MinInt8, 2}, token.AddAssign: {"+=", math.MinInt8, 2}, token.SubAssign: {"-=", math.MinInt8, 2}, token.MulAssign: {"*=", math.MinInt8, 2}, token.DivAssign: {"/=", math.MinInt8, 2}, + token.ModAssign: {"%=", math.MinInt8, 2}, token.ShrAssign: {">>=", math.MinInt8, 2}, token.ShlAssign: {"<<=", math.MinInt8, 2}, } @@ -68,21 +71,9 @@ func isComplete(expr *Expression) bool { } func numOperands(symbol token.Kind) int { - operator, exists := Operators[symbol] - - if !exists { - return -1 - } - - return int(operator.Operands) + return int(Operators[symbol].Operands) } func precedence(symbol token.Kind) int8 { - operator, exists := Operators[symbol] - - if !exists { - return -1 - } - - return operator.Precedence + return Operators[symbol].Precedence } diff --git a/src/build/expression/Parse.go b/src/build/expression/Parse.go index 22dbd39..87e9768 100644 --- a/src/build/expression/Parse.go +++ b/src/build/expression/Parse.go @@ -101,63 +101,64 @@ func Parse(tokens []token.Token) *Expression { continue } - if t.IsOperator() { - if cursor == nil { - cursor = NewLeaf(t) - cursor.Precedence = precedence(t.Kind) - root = cursor - continue - } - - node := NewLeaf(t) - node.Precedence = precedence(t.Kind) - - if cursor.Token.IsOperator() { - oldPrecedence := cursor.Precedence - newPrecedence := node.Precedence - - if newPrecedence > oldPrecedence { - if len(cursor.Children) == numOperands(cursor.Token.Kind) { - cursor.LastChild().Replace(node) - } else { - cursor.AddChild(node) - } - } else { - start := cursor - - for start != nil { - precedence := start.Precedence - - if precedence < newPrecedence { - start.LastChild().Replace(node) - break - } - - if precedence == newPrecedence { - if start == root { - root = node - } - - start.Replace(node) - break - } - - start = start.Parent - } - - if start == nil { - root.Replace(node) - root = node - } - } - } else { - node.AddChild(cursor) - root = node - } - - cursor = node + if !t.IsOperator() { continue } + + if cursor == nil { + cursor = NewLeaf(t) + cursor.Precedence = precedence(t.Kind) + root = cursor + continue + } + + node := NewLeaf(t) + node.Precedence = precedence(t.Kind) + + if cursor.Token.IsOperator() { + oldPrecedence := cursor.Precedence + newPrecedence := node.Precedence + + if newPrecedence > oldPrecedence { + if len(cursor.Children) == numOperands(cursor.Token.Kind) { + cursor.LastChild().Replace(node) + } else { + cursor.AddChild(node) + } + } else { + start := cursor + + for start != nil { + precedence := start.Precedence + + if precedence < newPrecedence { + start.LastChild().Replace(node) + break + } + + if precedence == newPrecedence { + if start == root { + root = node + } + + start.Replace(node) + break + } + + start = start.Parent + } + + if start == nil { + root.Replace(node) + root = node + } + } + } else { + node.AddChild(cursor) + root = node + } + + cursor = node } return root diff --git a/src/build/scope/Stack.go b/src/build/scope/Stack.go index 40ce40a..ceca9e3 100644 --- a/src/build/scope/Stack.go +++ b/src/build/scope/Stack.go @@ -6,6 +6,7 @@ import ( "git.akyoto.dev/cli/q/src/build/token" ) +// Stack is a stack of scopes. type Stack struct { Scopes []*Scope } diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 2c12464..6bbaff8 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -18,14 +18,6 @@ const ( BlockEnd // } ArrayStart // [ ArrayEnd // ] - _keywords // - Assert // assert - Else // else - If // if - Import // import - Loop // loop - Return // return - _keywordsEnd // _operators // Add // + Sub // - @@ -68,4 +60,12 @@ const ( ShrAssign // >>= _assignmentsEnd // _operatorsEnd // + _keywords // + Assert // assert + Else // else + If // if + Import // import + Loop // loop + Return // return + _keywordsEnd // ) diff --git a/src/build/token/Tokenize_test.go b/src/build/token/Tokenize_test.go index efc743c..4d6e36b 100644 --- a/src/build/token/Tokenize_test.go +++ b/src/build/token/Tokenize_test.go @@ -25,11 +25,15 @@ func TestFunction(t *testing.T) { } func TestKeyword(t *testing.T) { - tokens := token.Tokenize([]byte("return x")) + tokens := token.Tokenize([]byte("assert if import else loop return")) expected := []token.Kind{ + token.Assert, + token.If, + token.Import, + token.Else, + token.Loop, token.Return, - token.Identifier, token.EOF, } @@ -103,6 +107,37 @@ func TestOperator(t *testing.T) { } } +func TestOperatorAssign(t *testing.T) { + tokens := token.Tokenize([]byte(`a += b -= c *= d /= e &= f |= g ^= h <<= i >>= j`)) + + expected := []token.Kind{ + token.Identifier, + token.AddAssign, + token.Identifier, + token.SubAssign, + token.Identifier, + token.MulAssign, + token.Identifier, + token.DivAssign, + token.Identifier, + token.AndAssign, + token.Identifier, + token.OrAssign, + token.Identifier, + token.XorAssign, + token.Identifier, + token.ShlAssign, + token.Identifier, + token.ShrAssign, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + func TestNegateFirstToken(t *testing.T) { tokens := token.Tokenize([]byte(`-a`)) @@ -244,37 +279,6 @@ func TestLeadingZero(t *testing.T) { } } -func TestOperatorAssign(t *testing.T) { - tokens := token.Tokenize([]byte(`a += b -= c *= d /= e &= f |= g ^= h <<= i >>= j`)) - - expected := []token.Kind{ - token.Identifier, - token.AddAssign, - token.Identifier, - token.SubAssign, - token.Identifier, - token.MulAssign, - token.Identifier, - token.DivAssign, - token.Identifier, - token.AndAssign, - token.Identifier, - token.OrAssign, - token.Identifier, - token.XorAssign, - token.Identifier, - token.ShlAssign, - token.Identifier, - token.ShrAssign, - token.Identifier, - token.EOF, - } - - for i, kind := range expected { - assert.Equal(t, tokens[i].Kind, kind) - } -} - func TestSeparator(t *testing.T) { tokens := token.Tokenize([]byte("a,b,c")) diff --git a/src/cli/Run.go b/src/cli/Run.go index 9133f36..7de865b 100644 --- a/src/cli/Run.go +++ b/src/cli/Run.go @@ -33,9 +33,9 @@ func Run(args []string) int { if err != nil { fmt.Fprintln(os.Stderr, err) - switch err.(type) { + switch err := err.(type) { case *exec.ExitError: - return 0 + return err.ExitCode() default: return 1 diff --git a/src/cli/System.go b/src/cli/System.go index 5a26b37..b523d7a 100644 --- a/src/cli/System.go +++ b/src/cli/System.go @@ -18,6 +18,5 @@ func System(args []string) int { fmt.Printf(line, "Compiler:", config.Executable) fmt.Printf(line, "Library:", config.Library) fmt.Printf(line, "Threads:", strconv.Itoa(runtime.NumCPU())) - return 0 } From c67056d40506c97cf13973cc1485b44ea235ff3d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 2 Aug 2024 10:46:39 +0200 Subject: [PATCH 0443/1012] Improved visualization of register usage --- src/build/core/TokenToRegister.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 6dd6c8c..a7e32f3 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -19,8 +19,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - f.RegisterRegister(asm.MOVE, register, variable.Register) f.UseVariable(variable) + f.RegisterRegister(asm.MOVE, register, variable.Register) return nil case token.Number, token.Rune: From 548e3475fb3461446425bc89ee8d266f5893d84a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 2 Aug 2024 10:46:39 +0200 Subject: [PATCH 0444/1012] Improved visualization of register usage --- src/build/core/TokenToRegister.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 6dd6c8c..a7e32f3 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -19,8 +19,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - f.RegisterRegister(asm.MOVE, register, variable.Register) f.UseVariable(variable) + f.RegisterRegister(asm.MOVE, register, variable.Register) return nil case token.Number, token.Rune: From 270e7e27a0fe9caf057c71d3bd929f870158e596 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 2 Aug 2024 11:41:21 +0200 Subject: [PATCH 0445/1012] Improved performance --- src/build/compiler/Compile.go | 2 +- src/build/core/AddBytes.go | 2 +- src/build/core/Compile.go | 2 +- src/build/core/CompileAssert.go | 4 ++-- src/build/core/CompileCondition.go | 4 ++-- src/build/core/CompileIf.go | 6 +++--- src/build/core/CompileLoop.go | 2 +- src/build/core/Function.go | 17 +++++++++-------- src/build/core/NewFunction.go | 9 +++++---- src/build/register/SaveRegister.go | 8 ++++---- src/build/scanner/scanFile.go | 5 ----- 11 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/build/compiler/Compile.go b/src/build/compiler/Compile.go index facd13b..4b31d9a 100644 --- a/src/build/compiler/Compile.go +++ b/src/build/compiler/Compile.go @@ -23,7 +23,7 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, errs <-chan } function.Functions = allFunctions - allFunctions[function.Name] = function + allFunctions[function.UniqueName] = function case file, ok := <-files: if !ok { diff --git a/src/build/core/AddBytes.go b/src/build/core/AddBytes.go index 2e793ea..d75c11d 100644 --- a/src/build/core/AddBytes.go +++ b/src/build/core/AddBytes.go @@ -7,7 +7,7 @@ import ( // AddBytes adds a sequence of bytes and returns its address as a label. func (f *Function) AddBytes(value []byte) string { f.count.data++ - label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data) + label := fmt.Sprintf("%s_data_%d", f.UniqueName, f.count.data) f.Assembler.SetData(label, value) return label } diff --git a/src/build/core/Compile.go b/src/build/core/Compile.go index 4898f69..1a28253 100644 --- a/src/build/core/Compile.go +++ b/src/build/core/Compile.go @@ -2,7 +2,7 @@ package core // Compile turns a function into machine code. func (f *Function) Compile() { - f.AddLabel(f.Name) + f.AddLabel(f.UniqueName) f.Err = f.CompileTokens(f.Body) f.Return() diff --git a/src/build/core/CompileAssert.go b/src/build/core/CompileAssert.go index 2715e44..f601624 100644 --- a/src/build/core/CompileAssert.go +++ b/src/build/core/CompileAssert.go @@ -10,8 +10,8 @@ import ( // CompileAssert compiles an assertion. func (f *Function) CompileAssert(assert *ast.Assert) error { f.count.assert++ - success := fmt.Sprintf("%s_assert_%d_true", f.Name, f.count.assert) - fail := fmt.Sprintf("%s_assert_%d_false", f.Name, f.count.assert) + success := fmt.Sprintf("%s_assert_%d_true", f.UniqueName, f.count.assert) + fail := fmt.Sprintf("%s_assert_%d_false", f.UniqueName, f.count.assert) err := f.CompileCondition(assert.Condition, success, fail) if err != nil { diff --git a/src/build/core/CompileCondition.go b/src/build/core/CompileCondition.go index 54e0ab0..b43a65f 100644 --- a/src/build/core/CompileCondition.go +++ b/src/build/core/CompileCondition.go @@ -12,7 +12,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab switch condition.Token.Kind { case token.LogicalOr: f.count.subBranch++ - leftFailLabel := fmt.Sprintf("%s_false_%d", f.Name, f.count.subBranch) + leftFailLabel := fmt.Sprintf("%s_false_%d", f.UniqueName, f.count.subBranch) // Left left := condition.Children[0] @@ -39,7 +39,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab case token.LogicalAnd: f.count.subBranch++ - leftSuccessLabel := fmt.Sprintf("%s_true_%d", f.Name, f.count.subBranch) + leftSuccessLabel := fmt.Sprintf("%s_true_%d", f.UniqueName, f.count.subBranch) // Left left := condition.Children[0] diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 6b1a869..ec15dcb 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -13,8 +13,8 @@ func (f *Function) CompileIf(branch *ast.If) error { var ( end string - success = fmt.Sprintf("%s_if_%d_true", f.Name, f.count.branch) - fail = fmt.Sprintf("%s_if_%d_false", f.Name, f.count.branch) + success = fmt.Sprintf("%s_if_%d_true", f.UniqueName, f.count.branch) + fail = fmt.Sprintf("%s_if_%d_false", f.UniqueName, f.count.branch) err = f.CompileCondition(branch.Condition, success, fail) ) @@ -31,7 +31,7 @@ func (f *Function) CompileIf(branch *ast.If) error { } if branch.Else != nil { - end = fmt.Sprintf("%s_if_%d_end", f.Name, f.count.branch) + end = fmt.Sprintf("%s_if_%d_end", f.UniqueName, f.count.branch) f.Jump(asm.JUMP, end) } diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index a1b116a..1a28900 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -10,7 +10,7 @@ import ( // CompileLoop compiles a loop instruction. func (f *Function) CompileLoop(loop *ast.Loop) error { f.count.loop++ - label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) + label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) f.AddLabel(label) scope := f.PushScope(loop.Body, f.File.Bytes) scope.InLoop = true diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 7a4b4c6..a4f9234 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -9,14 +9,15 @@ import ( // Function represents the smallest unit of code. type Function struct { register.Machine - Package string - Name string - File *fs.File - Body []token.Token - Functions map[string]*Function - Err error - deferred []func() - count counter + Package string + Name string + UniqueName string + File *fs.File + Body []token.Token + Functions map[string]*Function + Err error + deferred []func() + count counter } // counter stores how often a certain statement appeared so we can generate a unique label from it. diff --git a/src/build/core/NewFunction.go b/src/build/core/NewFunction.go index bd4f23b..2772db2 100644 --- a/src/build/core/NewFunction.go +++ b/src/build/core/NewFunction.go @@ -13,10 +13,11 @@ import ( // NewFunction creates a new function. func NewFunction(pkg string, name string, file *fs.File, body []token.Token) *Function { return &Function{ - Package: pkg, - Name: name, - File: file, - Body: body, + Package: pkg, + Name: name, + UniqueName: pkg + "." + name, + File: file, + Body: body, Machine: register.Machine{ Assembler: asm.Assembler{ Instructions: make([]asm.Instruction, 0, 8), diff --git a/src/build/register/SaveRegister.go b/src/build/register/SaveRegister.go index 1c20818..18dfb76 100644 --- a/src/build/register/SaveRegister.go +++ b/src/build/register/SaveRegister.go @@ -1,6 +1,8 @@ package register import ( + "slices" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" ) @@ -11,10 +13,8 @@ func (f *Machine) SaveRegister(register cpu.Register) { return } - for _, general := range f.CPU.General { - if register == general { - return - } + if slices.Contains(f.CPU.General, register) { + return } variable := f.VariableByRegister(register) diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index 757cbaf..0f21732 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -1,7 +1,6 @@ package scanner import ( - "fmt" "os" "path/filepath" @@ -209,10 +208,6 @@ func (s *Scanner) scanFile(path string, pkg string) error { name := tokens[nameStart].Text(contents) body := tokens[bodyStart:i] - if pkg != "" { - name = fmt.Sprintf("%s.%s", pkg, name) - } - function := core.NewFunction(pkg, name, file, body) parameters := tokens[paramsStart:paramsEnd] count := 0 From b0c568e6167871a0b90967af53137781e6533f11 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 2 Aug 2024 11:41:21 +0200 Subject: [PATCH 0446/1012] Improved performance --- src/build/compiler/Compile.go | 2 +- src/build/core/AddBytes.go | 2 +- src/build/core/Compile.go | 2 +- src/build/core/CompileAssert.go | 4 ++-- src/build/core/CompileCondition.go | 4 ++-- src/build/core/CompileIf.go | 6 +++--- src/build/core/CompileLoop.go | 2 +- src/build/core/Function.go | 17 +++++++++-------- src/build/core/NewFunction.go | 9 +++++---- src/build/register/SaveRegister.go | 8 ++++---- src/build/scanner/scanFile.go | 5 ----- 11 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/build/compiler/Compile.go b/src/build/compiler/Compile.go index facd13b..4b31d9a 100644 --- a/src/build/compiler/Compile.go +++ b/src/build/compiler/Compile.go @@ -23,7 +23,7 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, errs <-chan } function.Functions = allFunctions - allFunctions[function.Name] = function + allFunctions[function.UniqueName] = function case file, ok := <-files: if !ok { diff --git a/src/build/core/AddBytes.go b/src/build/core/AddBytes.go index 2e793ea..d75c11d 100644 --- a/src/build/core/AddBytes.go +++ b/src/build/core/AddBytes.go @@ -7,7 +7,7 @@ import ( // AddBytes adds a sequence of bytes and returns its address as a label. func (f *Function) AddBytes(value []byte) string { f.count.data++ - label := fmt.Sprintf("%s_data_%d", f.Name, f.count.data) + label := fmt.Sprintf("%s_data_%d", f.UniqueName, f.count.data) f.Assembler.SetData(label, value) return label } diff --git a/src/build/core/Compile.go b/src/build/core/Compile.go index 4898f69..1a28253 100644 --- a/src/build/core/Compile.go +++ b/src/build/core/Compile.go @@ -2,7 +2,7 @@ package core // Compile turns a function into machine code. func (f *Function) Compile() { - f.AddLabel(f.Name) + f.AddLabel(f.UniqueName) f.Err = f.CompileTokens(f.Body) f.Return() diff --git a/src/build/core/CompileAssert.go b/src/build/core/CompileAssert.go index 2715e44..f601624 100644 --- a/src/build/core/CompileAssert.go +++ b/src/build/core/CompileAssert.go @@ -10,8 +10,8 @@ import ( // CompileAssert compiles an assertion. func (f *Function) CompileAssert(assert *ast.Assert) error { f.count.assert++ - success := fmt.Sprintf("%s_assert_%d_true", f.Name, f.count.assert) - fail := fmt.Sprintf("%s_assert_%d_false", f.Name, f.count.assert) + success := fmt.Sprintf("%s_assert_%d_true", f.UniqueName, f.count.assert) + fail := fmt.Sprintf("%s_assert_%d_false", f.UniqueName, f.count.assert) err := f.CompileCondition(assert.Condition, success, fail) if err != nil { diff --git a/src/build/core/CompileCondition.go b/src/build/core/CompileCondition.go index 54e0ab0..b43a65f 100644 --- a/src/build/core/CompileCondition.go +++ b/src/build/core/CompileCondition.go @@ -12,7 +12,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab switch condition.Token.Kind { case token.LogicalOr: f.count.subBranch++ - leftFailLabel := fmt.Sprintf("%s_false_%d", f.Name, f.count.subBranch) + leftFailLabel := fmt.Sprintf("%s_false_%d", f.UniqueName, f.count.subBranch) // Left left := condition.Children[0] @@ -39,7 +39,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab case token.LogicalAnd: f.count.subBranch++ - leftSuccessLabel := fmt.Sprintf("%s_true_%d", f.Name, f.count.subBranch) + leftSuccessLabel := fmt.Sprintf("%s_true_%d", f.UniqueName, f.count.subBranch) // Left left := condition.Children[0] diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 6b1a869..ec15dcb 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -13,8 +13,8 @@ func (f *Function) CompileIf(branch *ast.If) error { var ( end string - success = fmt.Sprintf("%s_if_%d_true", f.Name, f.count.branch) - fail = fmt.Sprintf("%s_if_%d_false", f.Name, f.count.branch) + success = fmt.Sprintf("%s_if_%d_true", f.UniqueName, f.count.branch) + fail = fmt.Sprintf("%s_if_%d_false", f.UniqueName, f.count.branch) err = f.CompileCondition(branch.Condition, success, fail) ) @@ -31,7 +31,7 @@ func (f *Function) CompileIf(branch *ast.If) error { } if branch.Else != nil { - end = fmt.Sprintf("%s_if_%d_end", f.Name, f.count.branch) + end = fmt.Sprintf("%s_if_%d_end", f.UniqueName, f.count.branch) f.Jump(asm.JUMP, end) } diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index a1b116a..1a28900 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -10,7 +10,7 @@ import ( // CompileLoop compiles a loop instruction. func (f *Function) CompileLoop(loop *ast.Loop) error { f.count.loop++ - label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) + label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) f.AddLabel(label) scope := f.PushScope(loop.Body, f.File.Bytes) scope.InLoop = true diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 7a4b4c6..a4f9234 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -9,14 +9,15 @@ import ( // Function represents the smallest unit of code. type Function struct { register.Machine - Package string - Name string - File *fs.File - Body []token.Token - Functions map[string]*Function - Err error - deferred []func() - count counter + Package string + Name string + UniqueName string + File *fs.File + Body []token.Token + Functions map[string]*Function + Err error + deferred []func() + count counter } // counter stores how often a certain statement appeared so we can generate a unique label from it. diff --git a/src/build/core/NewFunction.go b/src/build/core/NewFunction.go index bd4f23b..2772db2 100644 --- a/src/build/core/NewFunction.go +++ b/src/build/core/NewFunction.go @@ -13,10 +13,11 @@ import ( // NewFunction creates a new function. func NewFunction(pkg string, name string, file *fs.File, body []token.Token) *Function { return &Function{ - Package: pkg, - Name: name, - File: file, - Body: body, + Package: pkg, + Name: name, + UniqueName: pkg + "." + name, + File: file, + Body: body, Machine: register.Machine{ Assembler: asm.Assembler{ Instructions: make([]asm.Instruction, 0, 8), diff --git a/src/build/register/SaveRegister.go b/src/build/register/SaveRegister.go index 1c20818..18dfb76 100644 --- a/src/build/register/SaveRegister.go +++ b/src/build/register/SaveRegister.go @@ -1,6 +1,8 @@ package register import ( + "slices" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" ) @@ -11,10 +13,8 @@ func (f *Machine) SaveRegister(register cpu.Register) { return } - for _, general := range f.CPU.General { - if register == general { - return - } + if slices.Contains(f.CPU.General, register) { + return } variable := f.VariableByRegister(register) diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index 757cbaf..0f21732 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -1,7 +1,6 @@ package scanner import ( - "fmt" "os" "path/filepath" @@ -209,10 +208,6 @@ func (s *Scanner) scanFile(path string, pkg string) error { name := tokens[nameStart].Text(contents) body := tokens[bodyStart:i] - if pkg != "" { - name = fmt.Sprintf("%s.%s", pkg, name) - } - function := core.NewFunction(pkg, name, file, body) parameters := tokens[paramsStart:paramsEnd] count := 0 From c9850cf678299be8274c08786c92c9515a838ff6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 2 Aug 2024 12:16:35 +0200 Subject: [PATCH 0447/1012] Added missing node --- src/build/ast/Count.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index 4c5e40e..7ef7b18 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -8,6 +8,9 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { for _, node := range body { switch node := node.(type) { + case *Assert: + count += node.Condition.Count(buffer, kind, name) + case *Assign: count += node.Expression.Count(buffer, kind, name) From 34d865986ac62fccbe0168a903c8af066d741fdb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 2 Aug 2024 12:16:35 +0200 Subject: [PATCH 0448/1012] Added missing node --- src/build/ast/Count.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index 4c5e40e..7ef7b18 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -8,6 +8,9 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { for _, node := range body { switch node := node.(type) { + case *Assert: + count += node.Condition.Count(buffer, kind, name) + case *Assign: count += node.Expression.Count(buffer, kind, name) From 8766a4ef1aee9184c663943ea3878cf9cd11f7f9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 2 Aug 2024 12:55:25 +0200 Subject: [PATCH 0449/1012] Fixed incorrect register move --- src/build/core/ExecuteRegisterNumber.go | 10 +++++++--- src/build/core/ExecuteRegisterRegister.go | 10 +++++++--- src/build/core/ExpressionToRegister.go | 3 +++ src/build/core/TokenToRegister.go | 3 +++ src/build/register/RegisterLabel.go | 4 ---- src/build/register/RegisterNumber.go | 4 ---- src/build/register/RegisterRegister.go | 4 ---- src/build/register/isDestructive.go | 12 ------------ src/build/token/Kind.go | 4 +++- src/build/token/Token.go | 5 +++++ tests/programs/op-assign.q | 9 +++++++++ tests/programs_test.go | 1 + 12 files changed, 38 insertions(+), 31 deletions(-) delete mode 100644 src/build/register/isDestructive.go create mode 100644 tests/programs/op-assign.q diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index e2e4171..c78fe66 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -9,6 +9,10 @@ import ( // ExecuteRegisterNumber performs an operation on a register and a number. func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { + if !operation.IsAssignment() && !operation.IsComparison() { + f.SaveRegister(register) + } + switch operation.Kind { case token.Add, token.AddAssign: f.RegisterNumber(asm.ADD, register, number) @@ -40,12 +44,12 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg case token.Shr, token.ShrAssign: f.RegisterNumber(asm.SHIFTRS, register, number) - case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: - f.RegisterNumber(asm.COMPARE, register, number) - case token.Assign: f.RegisterNumber(asm.MOVE, register, number) + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: + f.RegisterNumber(asm.COMPARE, register, number) + default: return errors.New(&errors.InvalidOperator{Operator: operation.Text(f.File.Bytes)}, f.File, operation.Position) } diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index aa8006f..129d452 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -9,6 +9,10 @@ import ( // ExecuteRegisterRegister performs an operation on two registers. func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.Register, operand cpu.Register) error { + if !operation.IsAssignment() && !operation.IsComparison() { + f.SaveRegister(register) + } + switch operation.Kind { case token.Add, token.AddAssign: f.RegisterRegister(asm.ADD, register, operand) @@ -34,12 +38,12 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.R case token.Xor, token.XorAssign: f.RegisterRegister(asm.XOR, register, operand) - case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: - f.RegisterRegister(asm.COMPARE, register, operand) - case token.Assign: f.RegisterRegister(asm.MOVE, register, operand) + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: + f.RegisterRegister(asm.COMPARE, register, operand) + default: return errors.New(&errors.InvalidOperator{Operator: operation.Text(f.File.Bytes)}, f.File, operation.Position) } diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 1956932..e115758 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -12,6 +12,7 @@ import ( // ExpressionToRegister puts the result of an expression into the specified register. func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) error { if node.IsFolded { + f.SaveRegister(register) f.RegisterNumber(asm.MOVE, register, node.Value) return nil } @@ -24,6 +25,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp err := f.CompileCall(node) if register != f.CPU.Output[0] { + f.SaveRegister(register) f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) } @@ -68,6 +70,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp err = f.Execute(node.Token, register, right) if register != final { + f.SaveRegister(final) f.RegisterRegister(asm.MOVE, final, register) f.FreeRegister(register) } diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index a7e32f3..3335daf 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -20,6 +20,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { } f.UseVariable(variable) + f.SaveRegister(register) f.RegisterRegister(asm.MOVE, register, variable.Register) return nil @@ -30,6 +31,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return err } + f.SaveRegister(register) f.RegisterNumber(asm.MOVE, register, number) return nil @@ -37,6 +39,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { data := t.Bytes(f.File.Bytes) data = String(data) label := f.AddBytes(data) + f.SaveRegister(register) f.RegisterLabel(asm.MOVE, register, label) return nil diff --git a/src/build/register/RegisterLabel.go b/src/build/register/RegisterLabel.go index 7778e5a..70690bc 100644 --- a/src/build/register/RegisterLabel.go +++ b/src/build/register/RegisterLabel.go @@ -6,10 +6,6 @@ import ( ) func (f *Machine) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { - if f.RegisterIsUsed(register) && isDestructive(mnemonic) { - f.SaveRegister(register) - } - f.Assembler.RegisterLabel(mnemonic, register, label) if mnemonic == asm.MOVE { diff --git a/src/build/register/RegisterNumber.go b/src/build/register/RegisterNumber.go index a6b5aa6..32b552b 100644 --- a/src/build/register/RegisterNumber.go +++ b/src/build/register/RegisterNumber.go @@ -7,10 +7,6 @@ import ( ) func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { - if f.RegisterIsUsed(a) && isDestructive(mnemonic) { - f.SaveRegister(a) - } - // The `MOVE` operation is very flexible and works with any type of immediate number. if mnemonic == asm.MOVE { f.Assembler.RegisterNumber(mnemonic, a, b) diff --git a/src/build/register/RegisterRegister.go b/src/build/register/RegisterRegister.go index a969ca5..5f87783 100644 --- a/src/build/register/RegisterRegister.go +++ b/src/build/register/RegisterRegister.go @@ -10,10 +10,6 @@ func (f *Machine) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu. return } - if f.RegisterIsUsed(a) && isDestructive(mnemonic) { - f.SaveRegister(a) - } - f.Assembler.RegisterRegister(mnemonic, a, b) if mnemonic == asm.MOVE { diff --git a/src/build/register/isDestructive.go b/src/build/register/isDestructive.go deleted file mode 100644 index 9305367..0000000 --- a/src/build/register/isDestructive.go +++ /dev/null @@ -1,12 +0,0 @@ -package register - -import "git.akyoto.dev/cli/q/src/build/asm" - -func isDestructive(mnemonic asm.Mnemonic) bool { - switch mnemonic { - case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV, asm.MODULO, asm.AND, asm.OR, asm.XOR, asm.SHIFTL, asm.SHIFTRS, asm.NEGATE: - return true - default: - return false - } -} diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 6bbaff8..1b45363 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -31,12 +31,14 @@ const ( Shr // >> LogicalAnd // && LogicalOr // || + _comparisons // Equal // == + NotEqual // != Less // < Greater // > - NotEqual // != LessEqual // <= GreaterEqual // >= + _comparisonsEnd // Define // := Period // . Call // x() diff --git a/src/build/token/Token.go b/src/build/token/Token.go index 3885218..0476cbd 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -28,6 +28,11 @@ func (t Token) IsAssignment() bool { return t.Kind > _assignments && t.Kind < _assignmentsEnd } +// IsComparison returns true if the token is a comparison operator. +func (t Token) IsComparison() bool { + return t.Kind > _comparisons && t.Kind < _comparisonsEnd +} + // IsExpressionStart returns true if the token starts an expression. func (t Token) IsExpressionStart() bool { return t.Kind == GroupStart || t.Kind == ArrayStart || t.Kind == BlockStart diff --git a/tests/programs/op-assign.q b/tests/programs/op-assign.q new file mode 100644 index 0000000..496b3a5 --- /dev/null +++ b/tests/programs/op-assign.q @@ -0,0 +1,9 @@ +main() { + f(10) +} + +f(new) { + old := new + new -= 1 + assert new != old +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 043dbe9..4f2be2d 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -26,6 +26,7 @@ var programs = []struct { {"return", "", "", 0}, {"math", "", "", 0}, {"precedence", "", "", 0}, + {"op-assign", "", "", 0}, {"binary", "", "", 0}, {"octal", "", "", 0}, {"hexadecimal", "", "", 0}, From b1f0d2039479ef51dbeb5aac12f03f926e5fa799 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 2 Aug 2024 12:55:25 +0200 Subject: [PATCH 0450/1012] Fixed incorrect register move --- src/build/core/ExecuteRegisterNumber.go | 10 +++++++--- src/build/core/ExecuteRegisterRegister.go | 10 +++++++--- src/build/core/ExpressionToRegister.go | 3 +++ src/build/core/TokenToRegister.go | 3 +++ src/build/register/RegisterLabel.go | 4 ---- src/build/register/RegisterNumber.go | 4 ---- src/build/register/RegisterRegister.go | 4 ---- src/build/register/isDestructive.go | 12 ------------ src/build/token/Kind.go | 4 +++- src/build/token/Token.go | 5 +++++ tests/programs/op-assign.q | 9 +++++++++ tests/programs_test.go | 1 + 12 files changed, 38 insertions(+), 31 deletions(-) delete mode 100644 src/build/register/isDestructive.go create mode 100644 tests/programs/op-assign.q diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index e2e4171..c78fe66 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -9,6 +9,10 @@ import ( // ExecuteRegisterNumber performs an operation on a register and a number. func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { + if !operation.IsAssignment() && !operation.IsComparison() { + f.SaveRegister(register) + } + switch operation.Kind { case token.Add, token.AddAssign: f.RegisterNumber(asm.ADD, register, number) @@ -40,12 +44,12 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg case token.Shr, token.ShrAssign: f.RegisterNumber(asm.SHIFTRS, register, number) - case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: - f.RegisterNumber(asm.COMPARE, register, number) - case token.Assign: f.RegisterNumber(asm.MOVE, register, number) + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: + f.RegisterNumber(asm.COMPARE, register, number) + default: return errors.New(&errors.InvalidOperator{Operator: operation.Text(f.File.Bytes)}, f.File, operation.Position) } diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index aa8006f..129d452 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -9,6 +9,10 @@ import ( // ExecuteRegisterRegister performs an operation on two registers. func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.Register, operand cpu.Register) error { + if !operation.IsAssignment() && !operation.IsComparison() { + f.SaveRegister(register) + } + switch operation.Kind { case token.Add, token.AddAssign: f.RegisterRegister(asm.ADD, register, operand) @@ -34,12 +38,12 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.R case token.Xor, token.XorAssign: f.RegisterRegister(asm.XOR, register, operand) - case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: - f.RegisterRegister(asm.COMPARE, register, operand) - case token.Assign: f.RegisterRegister(asm.MOVE, register, operand) + case token.Equal, token.NotEqual, token.Less, token.LessEqual, token.Greater, token.GreaterEqual: + f.RegisterRegister(asm.COMPARE, register, operand) + default: return errors.New(&errors.InvalidOperator{Operator: operation.Text(f.File.Bytes)}, f.File, operation.Position) } diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 1956932..e115758 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -12,6 +12,7 @@ import ( // ExpressionToRegister puts the result of an expression into the specified register. func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) error { if node.IsFolded { + f.SaveRegister(register) f.RegisterNumber(asm.MOVE, register, node.Value) return nil } @@ -24,6 +25,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp err := f.CompileCall(node) if register != f.CPU.Output[0] { + f.SaveRegister(register) f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) } @@ -68,6 +70,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp err = f.Execute(node.Token, register, right) if register != final { + f.SaveRegister(final) f.RegisterRegister(asm.MOVE, final, register) f.FreeRegister(register) } diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index a7e32f3..3335daf 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -20,6 +20,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { } f.UseVariable(variable) + f.SaveRegister(register) f.RegisterRegister(asm.MOVE, register, variable.Register) return nil @@ -30,6 +31,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return err } + f.SaveRegister(register) f.RegisterNumber(asm.MOVE, register, number) return nil @@ -37,6 +39,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { data := t.Bytes(f.File.Bytes) data = String(data) label := f.AddBytes(data) + f.SaveRegister(register) f.RegisterLabel(asm.MOVE, register, label) return nil diff --git a/src/build/register/RegisterLabel.go b/src/build/register/RegisterLabel.go index 7778e5a..70690bc 100644 --- a/src/build/register/RegisterLabel.go +++ b/src/build/register/RegisterLabel.go @@ -6,10 +6,6 @@ import ( ) func (f *Machine) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { - if f.RegisterIsUsed(register) && isDestructive(mnemonic) { - f.SaveRegister(register) - } - f.Assembler.RegisterLabel(mnemonic, register, label) if mnemonic == asm.MOVE { diff --git a/src/build/register/RegisterNumber.go b/src/build/register/RegisterNumber.go index a6b5aa6..32b552b 100644 --- a/src/build/register/RegisterNumber.go +++ b/src/build/register/RegisterNumber.go @@ -7,10 +7,6 @@ import ( ) func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { - if f.RegisterIsUsed(a) && isDestructive(mnemonic) { - f.SaveRegister(a) - } - // The `MOVE` operation is very flexible and works with any type of immediate number. if mnemonic == asm.MOVE { f.Assembler.RegisterNumber(mnemonic, a, b) diff --git a/src/build/register/RegisterRegister.go b/src/build/register/RegisterRegister.go index a969ca5..5f87783 100644 --- a/src/build/register/RegisterRegister.go +++ b/src/build/register/RegisterRegister.go @@ -10,10 +10,6 @@ func (f *Machine) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu. return } - if f.RegisterIsUsed(a) && isDestructive(mnemonic) { - f.SaveRegister(a) - } - f.Assembler.RegisterRegister(mnemonic, a, b) if mnemonic == asm.MOVE { diff --git a/src/build/register/isDestructive.go b/src/build/register/isDestructive.go deleted file mode 100644 index 9305367..0000000 --- a/src/build/register/isDestructive.go +++ /dev/null @@ -1,12 +0,0 @@ -package register - -import "git.akyoto.dev/cli/q/src/build/asm" - -func isDestructive(mnemonic asm.Mnemonic) bool { - switch mnemonic { - case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV, asm.MODULO, asm.AND, asm.OR, asm.XOR, asm.SHIFTL, asm.SHIFTRS, asm.NEGATE: - return true - default: - return false - } -} diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 6bbaff8..1b45363 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -31,12 +31,14 @@ const ( Shr // >> LogicalAnd // && LogicalOr // || + _comparisons // Equal // == + NotEqual // != Less // < Greater // > - NotEqual // != LessEqual // <= GreaterEqual // >= + _comparisonsEnd // Define // := Period // . Call // x() diff --git a/src/build/token/Token.go b/src/build/token/Token.go index 3885218..0476cbd 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -28,6 +28,11 @@ func (t Token) IsAssignment() bool { return t.Kind > _assignments && t.Kind < _assignmentsEnd } +// IsComparison returns true if the token is a comparison operator. +func (t Token) IsComparison() bool { + return t.Kind > _comparisons && t.Kind < _comparisonsEnd +} + // IsExpressionStart returns true if the token starts an expression. func (t Token) IsExpressionStart() bool { return t.Kind == GroupStart || t.Kind == ArrayStart || t.Kind == BlockStart diff --git a/tests/programs/op-assign.q b/tests/programs/op-assign.q new file mode 100644 index 0000000..496b3a5 --- /dev/null +++ b/tests/programs/op-assign.q @@ -0,0 +1,9 @@ +main() { + f(10) +} + +f(new) { + old := new + new -= 1 + assert new != old +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 043dbe9..4f2be2d 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -26,6 +26,7 @@ var programs = []struct { {"return", "", "", 0}, {"math", "", "", 0}, {"precedence", "", "", 0}, + {"op-assign", "", "", 0}, {"binary", "", "", 0}, {"octal", "", "", 0}, {"hexadecimal", "", "", 0}, From 08d5f38072b4e58371bc05f1687a66e9e9275f11 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 2 Aug 2024 15:06:32 +0200 Subject: [PATCH 0451/1012] Fixed an incorrect register move --- examples/collatz/collatz.q | 4 +++- src/build/core/CompileLoop.go | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/collatz/collatz.q b/examples/collatz/collatz.q index 3093312..32507a4 100644 --- a/examples/collatz/collatz.q +++ b/examples/collatz/collatz.q @@ -2,8 +2,10 @@ import log import sys main() { - x := 12 + collatz(12) +} +collatz(x) { loop { if x & 1 == 0 { x /= 2 diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index 1a28900..83b8c1a 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -9,6 +9,10 @@ import ( // CompileLoop compiles a loop instruction. func (f *Function) CompileLoop(loop *ast.Loop) error { + for _, register := range f.CPU.Input { + f.SaveRegister(register) + } + f.count.loop++ label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) f.AddLabel(label) From df5813515d0c4e6ff8be91164f5af5c42d52e023 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 2 Aug 2024 15:06:32 +0200 Subject: [PATCH 0452/1012] Fixed an incorrect register move --- examples/collatz/collatz.q | 4 +++- src/build/core/CompileLoop.go | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/collatz/collatz.q b/examples/collatz/collatz.q index 3093312..32507a4 100644 --- a/examples/collatz/collatz.q +++ b/examples/collatz/collatz.q @@ -2,8 +2,10 @@ import log import sys main() { - x := 12 + collatz(12) +} +collatz(x) { loop { if x & 1 == 0 { x /= 2 diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index 1a28900..83b8c1a 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -9,6 +9,10 @@ import ( // CompileLoop compiles a loop instruction. func (f *Function) CompileLoop(loop *ast.Loop) error { + for _, register := range f.CPU.Input { + f.SaveRegister(register) + } + f.count.loop++ label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) f.AddLabel(label) From 178d543f8f821bc0042345e99b21afbb46359be7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 3 Aug 2024 01:11:22 +0200 Subject: [PATCH 0453/1012] Added a rudimentary FizzBuzz example --- examples/fizzbuzz/fizzbuzz.q | 39 ++++++++++++++++++++++++++++++++++++ src/build/asm/Finalize.go | 5 +++++ src/build/core/AddBytes.go | 2 +- src/build/scope/Scope.go | 1 - src/build/scope/Stack.go | 5 ++--- src/build/scope/Variable.go | 1 - tests/examples_test.go | 1 + 7 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 examples/fizzbuzz/fizzbuzz.q diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q new file mode 100644 index 0000000..14724a8 --- /dev/null +++ b/examples/fizzbuzz/fizzbuzz.q @@ -0,0 +1,39 @@ +import log +import sys + +main() { + fizzbuzz(15) +} + +fizzbuzz(n) { + x := 1 + + loop { + // TODO: implement switch statement + if x % 15 == 0 { + print("FizzBuzz", 8) + } else { + if x % 5 == 0 { + print("Buzz", 4) + } else { + if x % 3 == 0 { + print("Fizz", 4) + } else { + log.number(x) + } + } + } + + x += 1 + + if x > n { + return + } else { + print(" ", 1) + } + } +} + +print(address, length) { + sys.write(1, address, length) +} \ No newline at end of file diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index 340b6d4..97aa786 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -3,6 +3,7 @@ package asm import ( "encoding/binary" "fmt" + "strings" "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/config" @@ -287,6 +288,10 @@ restart: } for key, address := range labels { + if strings.HasPrefix(key, "data_") { + continue + } + if address > pointer.Position { labels[key] += offset } diff --git a/src/build/core/AddBytes.go b/src/build/core/AddBytes.go index d75c11d..bd620e0 100644 --- a/src/build/core/AddBytes.go +++ b/src/build/core/AddBytes.go @@ -7,7 +7,7 @@ import ( // AddBytes adds a sequence of bytes and returns its address as a label. func (f *Function) AddBytes(value []byte) string { f.count.data++ - label := fmt.Sprintf("%s_data_%d", f.UniqueName, f.count.data) + label := fmt.Sprintf("data_%s_%d", f.UniqueName, f.count.data) f.Assembler.SetData(label, value) return label } diff --git a/src/build/scope/Scope.go b/src/build/scope/Scope.go index 42021e6..9bbb3db 100644 --- a/src/build/scope/Scope.go +++ b/src/build/scope/Scope.go @@ -14,7 +14,6 @@ type Scope struct { // AddVariable adds a new variable to the current scope. func (s *Scope) AddVariable(variable *Variable) { - variable.Depth = s.Depth s.Variables = append(s.Variables, variable) s.Use(variable.Register) } diff --git a/src/build/scope/Stack.go b/src/build/scope/Stack.go index ceca9e3..94fec99 100644 --- a/src/build/scope/Stack.go +++ b/src/build/scope/Stack.go @@ -47,7 +47,6 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { Name: v.Name, Register: v.Register, Alive: count, - Depth: uint8(len(stack.Scopes)), }) } } @@ -58,8 +57,8 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { // UseVariable reduces the lifetime of the variable in all scopes. func (stack *Stack) UseVariable(variable *Variable) { - for depth, scope := range stack.Scopes { - if scope.InLoop && variable.Depth != uint8(depth) { + for _, scope := range stack.Scopes { + if scope.InLoop { continue } diff --git a/src/build/scope/Variable.go b/src/build/scope/Variable.go index 28e845a..491bb78 100644 --- a/src/build/scope/Variable.go +++ b/src/build/scope/Variable.go @@ -8,7 +8,6 @@ import ( type Variable struct { Name string Alive uint8 - Depth uint8 Register cpu.Register } diff --git a/tests/examples_test.go b/tests/examples_test.go index 116e270..b378045 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -23,6 +23,7 @@ var examples = []struct { {"echo", "Echo", "Echo", 0}, {"itoa", "", "9223372036854775807", 0}, {"collatz", "", "6 3 10 5 16 8 4 2 1", 0}, + {"fizzbuzz", "", "1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz", 0}, } func TestExamples(t *testing.T) { From 6aa1e674dff79c7e91ea3cb453ff379be6795758 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 3 Aug 2024 01:11:22 +0200 Subject: [PATCH 0454/1012] Added a rudimentary FizzBuzz example --- examples/fizzbuzz/fizzbuzz.q | 39 ++++++++++++++++++++++++++++++++++++ src/build/asm/Finalize.go | 5 +++++ src/build/core/AddBytes.go | 2 +- src/build/scope/Scope.go | 1 - src/build/scope/Stack.go | 5 ++--- src/build/scope/Variable.go | 1 - tests/examples_test.go | 1 + 7 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 examples/fizzbuzz/fizzbuzz.q diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q new file mode 100644 index 0000000..14724a8 --- /dev/null +++ b/examples/fizzbuzz/fizzbuzz.q @@ -0,0 +1,39 @@ +import log +import sys + +main() { + fizzbuzz(15) +} + +fizzbuzz(n) { + x := 1 + + loop { + // TODO: implement switch statement + if x % 15 == 0 { + print("FizzBuzz", 8) + } else { + if x % 5 == 0 { + print("Buzz", 4) + } else { + if x % 3 == 0 { + print("Fizz", 4) + } else { + log.number(x) + } + } + } + + x += 1 + + if x > n { + return + } else { + print(" ", 1) + } + } +} + +print(address, length) { + sys.write(1, address, length) +} \ No newline at end of file diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index 340b6d4..97aa786 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -3,6 +3,7 @@ package asm import ( "encoding/binary" "fmt" + "strings" "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/config" @@ -287,6 +288,10 @@ restart: } for key, address := range labels { + if strings.HasPrefix(key, "data_") { + continue + } + if address > pointer.Position { labels[key] += offset } diff --git a/src/build/core/AddBytes.go b/src/build/core/AddBytes.go index d75c11d..bd620e0 100644 --- a/src/build/core/AddBytes.go +++ b/src/build/core/AddBytes.go @@ -7,7 +7,7 @@ import ( // AddBytes adds a sequence of bytes and returns its address as a label. func (f *Function) AddBytes(value []byte) string { f.count.data++ - label := fmt.Sprintf("%s_data_%d", f.UniqueName, f.count.data) + label := fmt.Sprintf("data_%s_%d", f.UniqueName, f.count.data) f.Assembler.SetData(label, value) return label } diff --git a/src/build/scope/Scope.go b/src/build/scope/Scope.go index 42021e6..9bbb3db 100644 --- a/src/build/scope/Scope.go +++ b/src/build/scope/Scope.go @@ -14,7 +14,6 @@ type Scope struct { // AddVariable adds a new variable to the current scope. func (s *Scope) AddVariable(variable *Variable) { - variable.Depth = s.Depth s.Variables = append(s.Variables, variable) s.Use(variable.Register) } diff --git a/src/build/scope/Stack.go b/src/build/scope/Stack.go index ceca9e3..94fec99 100644 --- a/src/build/scope/Stack.go +++ b/src/build/scope/Stack.go @@ -47,7 +47,6 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { Name: v.Name, Register: v.Register, Alive: count, - Depth: uint8(len(stack.Scopes)), }) } } @@ -58,8 +57,8 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { // UseVariable reduces the lifetime of the variable in all scopes. func (stack *Stack) UseVariable(variable *Variable) { - for depth, scope := range stack.Scopes { - if scope.InLoop && variable.Depth != uint8(depth) { + for _, scope := range stack.Scopes { + if scope.InLoop { continue } diff --git a/src/build/scope/Variable.go b/src/build/scope/Variable.go index 28e845a..491bb78 100644 --- a/src/build/scope/Variable.go +++ b/src/build/scope/Variable.go @@ -8,7 +8,6 @@ import ( type Variable struct { Name string Alive uint8 - Depth uint8 Register cpu.Register } diff --git a/tests/examples_test.go b/tests/examples_test.go index 116e270..b378045 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -23,6 +23,7 @@ var examples = []struct { {"echo", "Echo", "Echo", 0}, {"itoa", "", "9223372036854775807", 0}, {"collatz", "", "6 3 10 5 16 8 4 2 1", 0}, + {"fizzbuzz", "", "1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz", 0}, } func TestExamples(t *testing.T) { From 09ec8d84462f6863f832cf84cb18880d02f2c7bf Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 3 Aug 2024 17:09:08 +0200 Subject: [PATCH 0455/1012] Implemented string interning for static data --- src/build/asm/Assembler.go | 14 +++++--- src/build/asm/Finalize.go | 69 +++++++++++++++++++------------------ src/build/data/Data.go | 45 ++++++++++++++++++++++++ src/build/data/Data_test.go | 28 +++++++++++++++ 4 files changed, 117 insertions(+), 39 deletions(-) create mode 100644 src/build/data/Data.go create mode 100644 src/build/data/Data_test.go diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index d539003..4a595cf 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -1,10 +1,14 @@ package asm -import "maps" +import ( + "maps" + + "git.akyoto.dev/cli/q/src/build/data" +) // Assembler contains a list of instructions. type Assembler struct { - Data map[string][]byte + Data data.Data Instructions []Instruction } @@ -15,10 +19,10 @@ func (a *Assembler) Merge(b Assembler) { } // SetData sets the data for the given label. -func (a *Assembler) SetData(label string, data []byte) { +func (a *Assembler) SetData(label string, bytes []byte) { if a.Data == nil { - a.Data = map[string][]byte{} + a.Data = data.Data{} } - a.Data[label] = data + a.Data[label] = bytes } diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index 97aa786..ff0c7b3 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -3,6 +3,7 @@ package asm import ( "encoding/binary" "fmt" + "slices" "strings" "git.akyoto.dev/cli/q/src/build/arch/x64" @@ -13,10 +14,14 @@ import ( // Finalize generates the final machine code. func (a Assembler) Finalize() ([]byte, []byte) { - code := make([]byte, 0, len(a.Instructions)*8) - data := make([]byte, 0, 16) - labels := map[string]Address{} - pointers := []*Pointer{} + var ( + code = make([]byte, 0, len(a.Instructions)*8) + data []byte + codeLabels = map[string]Address{} + dataLabels map[string]Address + codePointers []*Pointer + dataPointers []*Pointer + ) for _, x := range a.Instructions { switch x.Mnemonic { @@ -67,7 +72,7 @@ func (a Assembler) Finalize() ([]byte, []byte) { } pointer.Resolve = func() Address { - destination, exists := labels[label.Name] + destination, exists := codeLabels[label.Name] if !exists { panic("unknown jump label") @@ -77,7 +82,7 @@ func (a Assembler) Finalize() ([]byte, []byte) { return Address(distance) } - pointers = append(pointers, pointer) + codePointers = append(codePointers, pointer) case COMMENT: continue @@ -118,7 +123,7 @@ func (a Assembler) Finalize() ([]byte, []byte) { } pointer.Resolve = func() Address { - destination, exists := labels[label.Name] + destination, exists := codeLabels[label.Name] if !exists { panic("unknown jump label") @@ -128,10 +133,10 @@ func (a Assembler) Finalize() ([]byte, []byte) { return Address(distance) } - pointers = append(pointers, pointer) + codePointers = append(codePointers, pointer) case LABEL: - labels[x.Data.(*Label).Name] = Address(len(code)) + codeLabels[x.Data.(*Label).Name] = Address(len(code)) case LOAD: switch operands := x.Data.(type) { @@ -157,12 +162,16 @@ func (a Assembler) Finalize() ([]byte, []byte) { opSize := len(code) - size - start regLabel := x.Data.(*RegisterLabel) - pointers = append(pointers, &Pointer{ + if !strings.HasPrefix(regLabel.Label, "data_") { + panic("non-data moves not implemented yet") + } + + dataPointers = append(dataPointers, &Pointer{ Position: Address(len(code) - size), OpSize: uint8(opSize), Size: uint8(size), Resolve: func() Address { - destination, exists := labels[regLabel.Label] + destination, exists := dataLabels[regLabel.Label] if !exists { panic("unknown label") @@ -238,16 +247,8 @@ func (a Assembler) Finalize() ([]byte, []byte) { } } - dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) - dataStart += int32(elf.Padding(int64(dataStart), config.Align)) - - for label, slice := range a.Data { - labels[label] = dataStart + Address(len(data)) - data = append(data, slice...) - } - restart: - for i, pointer := range pointers { + for i, pointer := range codePointers { address := pointer.Resolve() if sizeof.Signed(int64(address)) > int(pointer.Size) { @@ -283,24 +284,17 @@ restart: jump = binary.LittleEndian.AppendUint32(jump, uint32(address)) offset := Address(len(jump)) - Address(size) - for _, following := range pointers[i+1:] { + for _, following := range codePointers[i+1:] { following.Position += offset } - for key, address := range labels { - if strings.HasPrefix(key, "data_") { - continue - } - + for key, address := range codeLabels { if address > pointer.Position { - labels[key] += offset + codeLabels[key] += offset } } - code = make([]byte, len(left)+len(jump)+len(right)) - copy(code, left) - copy(code[len(left):], jump) - copy(code[len(left)+len(jump):], right) + code = slices.Concat(left, jump, right) goto restart } @@ -309,17 +303,24 @@ restart: switch pointer.Size { case 1: slice[0] = uint8(address) - case 2: binary.LittleEndian.PutUint16(slice, uint16(address)) - case 4: binary.LittleEndian.PutUint32(slice, uint32(address)) - case 8: binary.LittleEndian.PutUint64(slice, uint64(address)) } } + data, dataLabels = a.Data.Finalize() + dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) + dataStart += int32(elf.Padding(int64(dataStart), config.Align)) + + for _, pointer := range dataPointers { + address := dataStart + pointer.Resolve() + slice := code[pointer.Position : pointer.Position+4] + binary.LittleEndian.PutUint32(slice, uint32(address)) + } + return code, data } diff --git a/src/build/data/Data.go b/src/build/data/Data.go new file mode 100644 index 0000000..16aca87 --- /dev/null +++ b/src/build/data/Data.go @@ -0,0 +1,45 @@ +package data + +import ( + "bytes" + "sort" +) + +// Data saves slices of bytes referenced by labels. +type Data map[string][]byte + +// Finalize returns the final raw data slice and a map of labels with their respective indices. +// It will try to reuse existing data whenever possible. +func (data Data) Finalize() ([]byte, map[string]int32) { + var ( + final []byte + keys = make([]string, 0, len(data)) + positions = make(map[string]int32, len(data)) + ) + + for key := range data { + keys = append(keys, key) + } + + sort.SliceStable(keys, func(i, j int) bool { + return len(data[keys[i]]) > len(data[keys[j]]) + }) + + for _, key := range keys { + raw := data[key] + position := bytes.Index(final, raw) + + if position != -1 { + positions[key] = int32(position) + } else { + positions[key] = int32(len(final)) + final = append(final, raw...) + } + } + + return final, positions +} + +func (data Data) Insert(label string, raw []byte) { + data[label] = raw +} diff --git a/src/build/data/Data_test.go b/src/build/data/Data_test.go new file mode 100644 index 0000000..d81faa8 --- /dev/null +++ b/src/build/data/Data_test.go @@ -0,0 +1,28 @@ +package data_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/data" + "git.akyoto.dev/go/assert" +) + +func TestInterning(t *testing.T) { + d := data.Data{} + d.Insert("label1", []byte("Hello")) + d.Insert("label2", []byte("ello")) + raw, positions := d.Finalize() + assert.DeepEqual(t, raw, []byte("Hello")) + assert.Equal(t, positions["label1"], 0) + assert.Equal(t, positions["label2"], 1) +} + +func TestInterningReverse(t *testing.T) { + d := data.Data{} + d.Insert("label1", []byte("ello")) + d.Insert("label2", []byte("Hello")) + raw, positions := d.Finalize() + assert.DeepEqual(t, raw, []byte("Hello")) + assert.Equal(t, positions["label1"], 1) + assert.Equal(t, positions["label2"], 0) +} From d07b455f671b315a6141ca2c3c6100bb2170d94a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 3 Aug 2024 17:09:08 +0200 Subject: [PATCH 0456/1012] Implemented string interning for static data --- src/build/asm/Assembler.go | 14 +++++--- src/build/asm/Finalize.go | 69 +++++++++++++++++++------------------ src/build/data/Data.go | 45 ++++++++++++++++++++++++ src/build/data/Data_test.go | 28 +++++++++++++++ 4 files changed, 117 insertions(+), 39 deletions(-) create mode 100644 src/build/data/Data.go create mode 100644 src/build/data/Data_test.go diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index d539003..4a595cf 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -1,10 +1,14 @@ package asm -import "maps" +import ( + "maps" + + "git.akyoto.dev/cli/q/src/build/data" +) // Assembler contains a list of instructions. type Assembler struct { - Data map[string][]byte + Data data.Data Instructions []Instruction } @@ -15,10 +19,10 @@ func (a *Assembler) Merge(b Assembler) { } // SetData sets the data for the given label. -func (a *Assembler) SetData(label string, data []byte) { +func (a *Assembler) SetData(label string, bytes []byte) { if a.Data == nil { - a.Data = map[string][]byte{} + a.Data = data.Data{} } - a.Data[label] = data + a.Data[label] = bytes } diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index 97aa786..ff0c7b3 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -3,6 +3,7 @@ package asm import ( "encoding/binary" "fmt" + "slices" "strings" "git.akyoto.dev/cli/q/src/build/arch/x64" @@ -13,10 +14,14 @@ import ( // Finalize generates the final machine code. func (a Assembler) Finalize() ([]byte, []byte) { - code := make([]byte, 0, len(a.Instructions)*8) - data := make([]byte, 0, 16) - labels := map[string]Address{} - pointers := []*Pointer{} + var ( + code = make([]byte, 0, len(a.Instructions)*8) + data []byte + codeLabels = map[string]Address{} + dataLabels map[string]Address + codePointers []*Pointer + dataPointers []*Pointer + ) for _, x := range a.Instructions { switch x.Mnemonic { @@ -67,7 +72,7 @@ func (a Assembler) Finalize() ([]byte, []byte) { } pointer.Resolve = func() Address { - destination, exists := labels[label.Name] + destination, exists := codeLabels[label.Name] if !exists { panic("unknown jump label") @@ -77,7 +82,7 @@ func (a Assembler) Finalize() ([]byte, []byte) { return Address(distance) } - pointers = append(pointers, pointer) + codePointers = append(codePointers, pointer) case COMMENT: continue @@ -118,7 +123,7 @@ func (a Assembler) Finalize() ([]byte, []byte) { } pointer.Resolve = func() Address { - destination, exists := labels[label.Name] + destination, exists := codeLabels[label.Name] if !exists { panic("unknown jump label") @@ -128,10 +133,10 @@ func (a Assembler) Finalize() ([]byte, []byte) { return Address(distance) } - pointers = append(pointers, pointer) + codePointers = append(codePointers, pointer) case LABEL: - labels[x.Data.(*Label).Name] = Address(len(code)) + codeLabels[x.Data.(*Label).Name] = Address(len(code)) case LOAD: switch operands := x.Data.(type) { @@ -157,12 +162,16 @@ func (a Assembler) Finalize() ([]byte, []byte) { opSize := len(code) - size - start regLabel := x.Data.(*RegisterLabel) - pointers = append(pointers, &Pointer{ + if !strings.HasPrefix(regLabel.Label, "data_") { + panic("non-data moves not implemented yet") + } + + dataPointers = append(dataPointers, &Pointer{ Position: Address(len(code) - size), OpSize: uint8(opSize), Size: uint8(size), Resolve: func() Address { - destination, exists := labels[regLabel.Label] + destination, exists := dataLabels[regLabel.Label] if !exists { panic("unknown label") @@ -238,16 +247,8 @@ func (a Assembler) Finalize() ([]byte, []byte) { } } - dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) - dataStart += int32(elf.Padding(int64(dataStart), config.Align)) - - for label, slice := range a.Data { - labels[label] = dataStart + Address(len(data)) - data = append(data, slice...) - } - restart: - for i, pointer := range pointers { + for i, pointer := range codePointers { address := pointer.Resolve() if sizeof.Signed(int64(address)) > int(pointer.Size) { @@ -283,24 +284,17 @@ restart: jump = binary.LittleEndian.AppendUint32(jump, uint32(address)) offset := Address(len(jump)) - Address(size) - for _, following := range pointers[i+1:] { + for _, following := range codePointers[i+1:] { following.Position += offset } - for key, address := range labels { - if strings.HasPrefix(key, "data_") { - continue - } - + for key, address := range codeLabels { if address > pointer.Position { - labels[key] += offset + codeLabels[key] += offset } } - code = make([]byte, len(left)+len(jump)+len(right)) - copy(code, left) - copy(code[len(left):], jump) - copy(code[len(left)+len(jump):], right) + code = slices.Concat(left, jump, right) goto restart } @@ -309,17 +303,24 @@ restart: switch pointer.Size { case 1: slice[0] = uint8(address) - case 2: binary.LittleEndian.PutUint16(slice, uint16(address)) - case 4: binary.LittleEndian.PutUint32(slice, uint32(address)) - case 8: binary.LittleEndian.PutUint64(slice, uint64(address)) } } + data, dataLabels = a.Data.Finalize() + dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) + dataStart += int32(elf.Padding(int64(dataStart), config.Align)) + + for _, pointer := range dataPointers { + address := dataStart + pointer.Resolve() + slice := code[pointer.Position : pointer.Position+4] + binary.LittleEndian.PutUint32(slice, uint32(address)) + } + return code, data } diff --git a/src/build/data/Data.go b/src/build/data/Data.go new file mode 100644 index 0000000..16aca87 --- /dev/null +++ b/src/build/data/Data.go @@ -0,0 +1,45 @@ +package data + +import ( + "bytes" + "sort" +) + +// Data saves slices of bytes referenced by labels. +type Data map[string][]byte + +// Finalize returns the final raw data slice and a map of labels with their respective indices. +// It will try to reuse existing data whenever possible. +func (data Data) Finalize() ([]byte, map[string]int32) { + var ( + final []byte + keys = make([]string, 0, len(data)) + positions = make(map[string]int32, len(data)) + ) + + for key := range data { + keys = append(keys, key) + } + + sort.SliceStable(keys, func(i, j int) bool { + return len(data[keys[i]]) > len(data[keys[j]]) + }) + + for _, key := range keys { + raw := data[key] + position := bytes.Index(final, raw) + + if position != -1 { + positions[key] = int32(position) + } else { + positions[key] = int32(len(final)) + final = append(final, raw...) + } + } + + return final, positions +} + +func (data Data) Insert(label string, raw []byte) { + data[label] = raw +} diff --git a/src/build/data/Data_test.go b/src/build/data/Data_test.go new file mode 100644 index 0000000..d81faa8 --- /dev/null +++ b/src/build/data/Data_test.go @@ -0,0 +1,28 @@ +package data_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/data" + "git.akyoto.dev/go/assert" +) + +func TestInterning(t *testing.T) { + d := data.Data{} + d.Insert("label1", []byte("Hello")) + d.Insert("label2", []byte("ello")) + raw, positions := d.Finalize() + assert.DeepEqual(t, raw, []byte("Hello")) + assert.Equal(t, positions["label1"], 0) + assert.Equal(t, positions["label2"], 1) +} + +func TestInterningReverse(t *testing.T) { + d := data.Data{} + d.Insert("label1", []byte("ello")) + d.Insert("label2", []byte("Hello")) + raw, positions := d.Finalize() + assert.DeepEqual(t, raw, []byte("Hello")) + assert.Equal(t, positions["label1"], 1) + assert.Equal(t, positions["label2"], 0) +} From 52d1de042c38a364c5a188dc46d0367e23a94927 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 3 Aug 2024 22:24:40 +0200 Subject: [PATCH 0457/1012] Implemented switch statements --- examples/fizzbuzz/fizzbuzz.q | 26 +++++++-------- src/build/ast/Count.go | 19 ++++++++--- src/build/ast/Switch.go | 16 ++++++++++ src/build/ast/parseKeyword.go | 15 +++++++++ src/build/ast/parseSwitch.go | 37 +++++++++++++++++++++ src/build/core/CompileASTNode.go | 15 ++++++--- src/build/core/CompileSwitch.go | 55 ++++++++++++++++++++++++++++++++ src/build/core/Function.go | 11 ++++--- src/build/token/Kind.go | 1 + src/build/token/Tokenize.go | 2 ++ 10 files changed, 170 insertions(+), 27 deletions(-) create mode 100644 src/build/ast/Switch.go create mode 100644 src/build/ast/parseSwitch.go create mode 100644 src/build/core/CompileSwitch.go diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q index 14724a8..391c7a2 100644 --- a/examples/fizzbuzz/fizzbuzz.q +++ b/examples/fizzbuzz/fizzbuzz.q @@ -9,18 +9,18 @@ fizzbuzz(n) { x := 1 loop { - // TODO: implement switch statement - if x % 15 == 0 { - print("FizzBuzz", 8) - } else { - if x % 5 == 0 { + switch { + x % 15 == 0 { + print("FizzBuzz", 8) + } + x % 5 == 0 { print("Buzz", 4) - } else { - if x % 3 == 0 { - print("Fizz", 4) - } else { - log.number(x) - } + } + x % 3 == 0 { + print("Fizz", 4) + } + _ { + log.number(x) } } @@ -28,9 +28,9 @@ fizzbuzz(n) { if x > n { return - } else { - print(" ", 1) } + + print(" ", 1) } } diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index 7ef7b18..192a36f 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -20,11 +20,6 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { case *Define: count += node.Expression.Count(buffer, kind, name) - case *Return: - if node.Value != nil { - count += node.Value.Count(buffer, kind, name) - } - case *If: count += node.Condition.Count(buffer, kind, name) count += Count(node.Body, buffer, kind, name) @@ -33,6 +28,20 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { case *Loop: count += Count(node.Body, buffer, kind, name) + case *Return: + if node.Value != nil { + count += node.Value.Count(buffer, kind, name) + } + + case *Switch: + for _, c := range node.Cases { + if c.Condition != nil { + count += c.Condition.Count(buffer, kind, name) + } + + count += Count(c.Body, buffer, kind, name) + } + default: panic("unknown AST type") } diff --git a/src/build/ast/Switch.go b/src/build/ast/Switch.go new file mode 100644 index 0000000..80ff3d3 --- /dev/null +++ b/src/build/ast/Switch.go @@ -0,0 +1,16 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/build/expression" +) + +// Switch represents a switch statement. +type Switch struct { + Cases []Case +} + +// Case represents a case inside a switch. +type Case struct { + Condition *expression.Expression + Body AST +} diff --git a/src/build/ast/parseKeyword.go b/src/build/ast/parseKeyword.go index a123570..a1363f9 100644 --- a/src/build/ast/parseKeyword.go +++ b/src/build/ast/parseKeyword.go @@ -51,6 +51,21 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { value := expression.Parse(tokens[1:]) return &Return{Value: value}, nil + case token.Switch: + blockStart := tokens.IndexKind(token.BlockStart) + blockEnd := tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) + } + + if blockEnd == -1 { + return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) + } + + cases, err := parseSwitch(tokens[blockStart+1:blockEnd], source) + return &Switch{Cases: cases}, err + default: return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text(source)}, nil, tokens[0].Position) } diff --git a/src/build/ast/parseSwitch.go b/src/build/ast/parseSwitch.go new file mode 100644 index 0000000..752405a --- /dev/null +++ b/src/build/ast/parseSwitch.go @@ -0,0 +1,37 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// parseSwitch generates the cases inside a switch statement. +func parseSwitch(tokens token.List, source []byte) ([]Case, error) { + var cases []Case + + err := EachInstruction(tokens, func(caseTokens token.List) error { + blockStart, _, body, err := block(caseTokens, source) + + if err != nil { + return err + } + + conditionTokens := caseTokens[:blockStart] + var condition *expression.Expression + + if len(conditionTokens) == 1 && conditionTokens[0].Kind == token.Identifier && conditionTokens[0].Text(source) == "_" { + condition = nil + } else { + condition = expression.Parse(conditionTokens) + } + + cases = append(cases, Case{ + Condition: condition, + Body: body, + }) + + return nil + }) + + return cases, err +} diff --git a/src/build/core/CompileASTNode.go b/src/build/core/CompileASTNode.go index eac1e31..e3bccaf 100644 --- a/src/build/core/CompileASTNode.go +++ b/src/build/core/CompileASTNode.go @@ -23,10 +23,6 @@ func (f *Function) CompileASTNode(node ast.Node) error { f.Fold(node.Expression) return f.CompileDefinition(node) - case *ast.Return: - f.Fold(node.Value) - return f.CompileReturn(node) - case *ast.If: f.Fold(node.Condition) return f.CompileIf(node) @@ -34,6 +30,17 @@ func (f *Function) CompileASTNode(node ast.Node) error { case *ast.Loop: return f.CompileLoop(node) + case *ast.Return: + f.Fold(node.Value) + return f.CompileReturn(node) + + case *ast.Switch: + for _, c := range node.Cases { + f.Fold(c.Condition) + } + + return f.CompileSwitch(node) + default: panic("unknown AST type") } diff --git a/src/build/core/CompileSwitch.go b/src/build/core/CompileSwitch.go new file mode 100644 index 0000000..7d0e0f3 --- /dev/null +++ b/src/build/core/CompileSwitch.go @@ -0,0 +1,55 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" +) + +// CompileSwitch compiles a multi-branch instruction. +func (f *Function) CompileSwitch(s *ast.Switch) error { + f.count.multiBranch++ + end := fmt.Sprintf("%s_switch_%d_end", f.UniqueName, f.count.multiBranch) + + for _, branch := range s.Cases { + if branch.Condition == nil { + f.PushScope(branch.Body, f.File.Bytes) + err := f.CompileAST(branch.Body) + + if err != nil { + return err + } + + f.PopScope() + break + } + + f.count.branch++ + + var ( + success = fmt.Sprintf("%s_case_%d_true", f.UniqueName, f.count.branch) + fail = fmt.Sprintf("%s_case_%d_false", f.UniqueName, f.count.branch) + err = f.CompileCondition(branch.Condition, success, fail) + ) + + if err != nil { + return err + } + + f.AddLabel(success) + f.PushScope(branch.Body, f.File.Bytes) + err = f.CompileAST(branch.Body) + + if err != nil { + return err + } + + f.Jump(asm.JUMP, end) + f.PopScope() + f.AddLabel(fail) + } + + f.AddLabel(end) + return nil +} diff --git a/src/build/core/Function.go b/src/build/core/Function.go index a4f9234..9aeb10a 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -22,9 +22,10 @@ type Function struct { // counter stores how often a certain statement appeared so we can generate a unique label from it. type counter struct { - assert int - branch int - data int - loop int - subBranch int + assert int + branch int + multiBranch int + data int + loop int + subBranch int } diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 1b45363..fa97224 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -69,5 +69,6 @@ const ( Import // import Loop // loop Return // return + Switch // switch _keywordsEnd // ) diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index be8a309..85d3eba 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -151,6 +151,8 @@ func Tokenize(buffer []byte) List { kind = Loop case "return": kind = Return + case "switch": + kind = Switch } tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(len(identifier))}) From dbf416d45b56d8b0e8fcc02391c47a4822ee9eff Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 3 Aug 2024 22:24:40 +0200 Subject: [PATCH 0458/1012] Implemented switch statements --- examples/fizzbuzz/fizzbuzz.q | 26 +++++++-------- src/build/ast/Count.go | 19 ++++++++--- src/build/ast/Switch.go | 16 ++++++++++ src/build/ast/parseKeyword.go | 15 +++++++++ src/build/ast/parseSwitch.go | 37 +++++++++++++++++++++ src/build/core/CompileASTNode.go | 15 ++++++--- src/build/core/CompileSwitch.go | 55 ++++++++++++++++++++++++++++++++ src/build/core/Function.go | 11 ++++--- src/build/token/Kind.go | 1 + src/build/token/Tokenize.go | 2 ++ 10 files changed, 170 insertions(+), 27 deletions(-) create mode 100644 src/build/ast/Switch.go create mode 100644 src/build/ast/parseSwitch.go create mode 100644 src/build/core/CompileSwitch.go diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q index 14724a8..391c7a2 100644 --- a/examples/fizzbuzz/fizzbuzz.q +++ b/examples/fizzbuzz/fizzbuzz.q @@ -9,18 +9,18 @@ fizzbuzz(n) { x := 1 loop { - // TODO: implement switch statement - if x % 15 == 0 { - print("FizzBuzz", 8) - } else { - if x % 5 == 0 { + switch { + x % 15 == 0 { + print("FizzBuzz", 8) + } + x % 5 == 0 { print("Buzz", 4) - } else { - if x % 3 == 0 { - print("Fizz", 4) - } else { - log.number(x) - } + } + x % 3 == 0 { + print("Fizz", 4) + } + _ { + log.number(x) } } @@ -28,9 +28,9 @@ fizzbuzz(n) { if x > n { return - } else { - print(" ", 1) } + + print(" ", 1) } } diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index 7ef7b18..192a36f 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -20,11 +20,6 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { case *Define: count += node.Expression.Count(buffer, kind, name) - case *Return: - if node.Value != nil { - count += node.Value.Count(buffer, kind, name) - } - case *If: count += node.Condition.Count(buffer, kind, name) count += Count(node.Body, buffer, kind, name) @@ -33,6 +28,20 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { case *Loop: count += Count(node.Body, buffer, kind, name) + case *Return: + if node.Value != nil { + count += node.Value.Count(buffer, kind, name) + } + + case *Switch: + for _, c := range node.Cases { + if c.Condition != nil { + count += c.Condition.Count(buffer, kind, name) + } + + count += Count(c.Body, buffer, kind, name) + } + default: panic("unknown AST type") } diff --git a/src/build/ast/Switch.go b/src/build/ast/Switch.go new file mode 100644 index 0000000..80ff3d3 --- /dev/null +++ b/src/build/ast/Switch.go @@ -0,0 +1,16 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/build/expression" +) + +// Switch represents a switch statement. +type Switch struct { + Cases []Case +} + +// Case represents a case inside a switch. +type Case struct { + Condition *expression.Expression + Body AST +} diff --git a/src/build/ast/parseKeyword.go b/src/build/ast/parseKeyword.go index a123570..a1363f9 100644 --- a/src/build/ast/parseKeyword.go +++ b/src/build/ast/parseKeyword.go @@ -51,6 +51,21 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { value := expression.Parse(tokens[1:]) return &Return{Value: value}, nil + case token.Switch: + blockStart := tokens.IndexKind(token.BlockStart) + blockEnd := tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) + } + + if blockEnd == -1 { + return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) + } + + cases, err := parseSwitch(tokens[blockStart+1:blockEnd], source) + return &Switch{Cases: cases}, err + default: return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text(source)}, nil, tokens[0].Position) } diff --git a/src/build/ast/parseSwitch.go b/src/build/ast/parseSwitch.go new file mode 100644 index 0000000..752405a --- /dev/null +++ b/src/build/ast/parseSwitch.go @@ -0,0 +1,37 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// parseSwitch generates the cases inside a switch statement. +func parseSwitch(tokens token.List, source []byte) ([]Case, error) { + var cases []Case + + err := EachInstruction(tokens, func(caseTokens token.List) error { + blockStart, _, body, err := block(caseTokens, source) + + if err != nil { + return err + } + + conditionTokens := caseTokens[:blockStart] + var condition *expression.Expression + + if len(conditionTokens) == 1 && conditionTokens[0].Kind == token.Identifier && conditionTokens[0].Text(source) == "_" { + condition = nil + } else { + condition = expression.Parse(conditionTokens) + } + + cases = append(cases, Case{ + Condition: condition, + Body: body, + }) + + return nil + }) + + return cases, err +} diff --git a/src/build/core/CompileASTNode.go b/src/build/core/CompileASTNode.go index eac1e31..e3bccaf 100644 --- a/src/build/core/CompileASTNode.go +++ b/src/build/core/CompileASTNode.go @@ -23,10 +23,6 @@ func (f *Function) CompileASTNode(node ast.Node) error { f.Fold(node.Expression) return f.CompileDefinition(node) - case *ast.Return: - f.Fold(node.Value) - return f.CompileReturn(node) - case *ast.If: f.Fold(node.Condition) return f.CompileIf(node) @@ -34,6 +30,17 @@ func (f *Function) CompileASTNode(node ast.Node) error { case *ast.Loop: return f.CompileLoop(node) + case *ast.Return: + f.Fold(node.Value) + return f.CompileReturn(node) + + case *ast.Switch: + for _, c := range node.Cases { + f.Fold(c.Condition) + } + + return f.CompileSwitch(node) + default: panic("unknown AST type") } diff --git a/src/build/core/CompileSwitch.go b/src/build/core/CompileSwitch.go new file mode 100644 index 0000000..7d0e0f3 --- /dev/null +++ b/src/build/core/CompileSwitch.go @@ -0,0 +1,55 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" +) + +// CompileSwitch compiles a multi-branch instruction. +func (f *Function) CompileSwitch(s *ast.Switch) error { + f.count.multiBranch++ + end := fmt.Sprintf("%s_switch_%d_end", f.UniqueName, f.count.multiBranch) + + for _, branch := range s.Cases { + if branch.Condition == nil { + f.PushScope(branch.Body, f.File.Bytes) + err := f.CompileAST(branch.Body) + + if err != nil { + return err + } + + f.PopScope() + break + } + + f.count.branch++ + + var ( + success = fmt.Sprintf("%s_case_%d_true", f.UniqueName, f.count.branch) + fail = fmt.Sprintf("%s_case_%d_false", f.UniqueName, f.count.branch) + err = f.CompileCondition(branch.Condition, success, fail) + ) + + if err != nil { + return err + } + + f.AddLabel(success) + f.PushScope(branch.Body, f.File.Bytes) + err = f.CompileAST(branch.Body) + + if err != nil { + return err + } + + f.Jump(asm.JUMP, end) + f.PopScope() + f.AddLabel(fail) + } + + f.AddLabel(end) + return nil +} diff --git a/src/build/core/Function.go b/src/build/core/Function.go index a4f9234..9aeb10a 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -22,9 +22,10 @@ type Function struct { // counter stores how often a certain statement appeared so we can generate a unique label from it. type counter struct { - assert int - branch int - data int - loop int - subBranch int + assert int + branch int + multiBranch int + data int + loop int + subBranch int } diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 1b45363..fa97224 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -69,5 +69,6 @@ const ( Import // import Loop // loop Return // return + Switch // switch _keywordsEnd // ) diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index be8a309..85d3eba 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -151,6 +151,8 @@ func Tokenize(buffer []byte) List { kind = Loop case "return": kind = Return + case "switch": + kind = Switch } tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(len(identifier))}) From a811da3477bbbf5027bef16d599d1e7594b5ca3d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 3 Aug 2024 23:05:09 +0200 Subject: [PATCH 0459/1012] Added more tests --- README.md | 2 +- src/build/ast/EachInstruction.go | 4 +++ src/build/ast/parseKeyword.go | 8 +++++- src/build/errors/CompileErrors.go | 1 + src/build/token/List.go | 15 +++++++++++ tests/errors/EmptySwitch.q | 3 +++ tests/errors_test.go | 1 + tests/programs/switch.q | 44 +++++++++++++++++++++++++++++++ tests/programs_test.go | 1 + 9 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 tests/errors/EmptySwitch.q create mode 100644 tests/programs/switch.q diff --git a/README.md b/README.md index 8dcacc6..8ba0c76 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ This is what generates expressions from tokens. - [x] `import` - [x] `loop` - [x] `return` -- [ ] `switch` +- [x] `switch` ### Optimizations diff --git a/src/build/ast/EachInstruction.go b/src/build/ast/EachInstruction.go index b4d9d8a..927cca6 100644 --- a/src/build/ast/EachInstruction.go +++ b/src/build/ast/EachInstruction.go @@ -54,5 +54,9 @@ func EachInstruction(body token.List, call func(token.List) error) error { } } + if start != len(body) { + return call(body[start:]) + } + return nil } diff --git a/src/build/ast/parseKeyword.go b/src/build/ast/parseKeyword.go index a1363f9..0b6a423 100644 --- a/src/build/ast/parseKeyword.go +++ b/src/build/ast/parseKeyword.go @@ -63,7 +63,13 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) } - cases, err := parseSwitch(tokens[blockStart+1:blockEnd], source) + body := tokens[blockStart+1 : blockEnd] + + if len(body) == 0 { + return nil, errors.New(errors.EmptySwitch, nil, tokens[0].Position) + } + + cases, err := parseSwitch(body, source) return &Switch{Cases: cases}, err default: diff --git a/src/build/errors/CompileErrors.go b/src/build/errors/CompileErrors.go index a214de3..c0e14cb 100644 --- a/src/build/errors/CompileErrors.go +++ b/src/build/errors/CompileErrors.go @@ -1,6 +1,7 @@ package errors var ( + EmptySwitch = &Base{"Empty switch"} ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} InvalidNumber = &Base{"Invalid number"} InvalidExpression = &Base{"Invalid expression"} diff --git a/src/build/token/List.go b/src/build/token/List.go index 7e57693..37ad0eb 100644 --- a/src/build/token/List.go +++ b/src/build/token/List.go @@ -1,5 +1,9 @@ package token +import ( + "strings" +) + // List is a slice of tokens. type List []Token @@ -24,3 +28,14 @@ func (list List) LastIndexKind(kind Kind) int { return -1 } + +// Text returns the concatenated token text. +func (list List) Text(source []byte) string { + tmp := strings.Builder{} + + for _, t := range list { + tmp.WriteString(t.Text(source)) + } + + return tmp.String() +} diff --git a/tests/errors/EmptySwitch.q b/tests/errors/EmptySwitch.q new file mode 100644 index 0000000..866a186 --- /dev/null +++ b/tests/errors/EmptySwitch.q @@ -0,0 +1,3 @@ +main() { + switch {} +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 2b9ce40..2ec440e 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -14,6 +14,7 @@ var errs = []struct { File string ExpectedError error }{ + {"EmptySwitch.q", errors.EmptySwitch}, {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, diff --git a/tests/programs/switch.q b/tests/programs/switch.q new file mode 100644 index 0000000..7c1e050 --- /dev/null +++ b/tests/programs/switch.q @@ -0,0 +1,44 @@ +import sys + +main() { + correct := 0 + + switch { + 1 == 1 { correct += 1 } + } + + switch { + 1 == 1 { correct += 1 } + _ { correct -= 1 } + } + + switch { + 0 == 1 { correct -= 1 } + _ { correct += 1 } + } + + switch { + 0 == 1 { correct -= 1 } + 0 == 2 { correct -= 1 } + _ { correct += 1 } + } + + switch { + 0 == 1 { correct -= 1 } + 0 == 2 { correct -= 1 } + 2 == 2 { correct += 1 } + _ { correct -= 1 } + } + + switch { + 0 == 1 { correct -= 1 } + 0 == 2 { correct -= 1 } + 0 == 3 { correct -= 1 } + } + + if correct == 5 { + return + } + + sys.exit(1) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 4f2be2d..c381573 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -53,6 +53,7 @@ var programs = []struct { {"branch-or", "", "", 0}, {"branch-both", "", "", 0}, {"jump-near", "", "", 0}, + {"switch", "", "", 0}, {"loop", "", "", 0}, {"loop-lifetime", "", "", 0}, {"out-of-memory", "", "", 0}, From 57ee65abd1ff21da000fc380af22c4c7f757b945 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 3 Aug 2024 23:05:09 +0200 Subject: [PATCH 0460/1012] Added more tests --- README.md | 2 +- src/build/ast/EachInstruction.go | 4 +++ src/build/ast/parseKeyword.go | 8 +++++- src/build/errors/CompileErrors.go | 1 + src/build/token/List.go | 15 +++++++++++ tests/errors/EmptySwitch.q | 3 +++ tests/errors_test.go | 1 + tests/programs/switch.q | 44 +++++++++++++++++++++++++++++++ tests/programs_test.go | 1 + 9 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 tests/errors/EmptySwitch.q create mode 100644 tests/programs/switch.q diff --git a/README.md b/README.md index 8dcacc6..8ba0c76 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ This is what generates expressions from tokens. - [x] `import` - [x] `loop` - [x] `return` -- [ ] `switch` +- [x] `switch` ### Optimizations diff --git a/src/build/ast/EachInstruction.go b/src/build/ast/EachInstruction.go index b4d9d8a..927cca6 100644 --- a/src/build/ast/EachInstruction.go +++ b/src/build/ast/EachInstruction.go @@ -54,5 +54,9 @@ func EachInstruction(body token.List, call func(token.List) error) error { } } + if start != len(body) { + return call(body[start:]) + } + return nil } diff --git a/src/build/ast/parseKeyword.go b/src/build/ast/parseKeyword.go index a1363f9..0b6a423 100644 --- a/src/build/ast/parseKeyword.go +++ b/src/build/ast/parseKeyword.go @@ -63,7 +63,13 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) } - cases, err := parseSwitch(tokens[blockStart+1:blockEnd], source) + body := tokens[blockStart+1 : blockEnd] + + if len(body) == 0 { + return nil, errors.New(errors.EmptySwitch, nil, tokens[0].Position) + } + + cases, err := parseSwitch(body, source) return &Switch{Cases: cases}, err default: diff --git a/src/build/errors/CompileErrors.go b/src/build/errors/CompileErrors.go index a214de3..c0e14cb 100644 --- a/src/build/errors/CompileErrors.go +++ b/src/build/errors/CompileErrors.go @@ -1,6 +1,7 @@ package errors var ( + EmptySwitch = &Base{"Empty switch"} ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} InvalidNumber = &Base{"Invalid number"} InvalidExpression = &Base{"Invalid expression"} diff --git a/src/build/token/List.go b/src/build/token/List.go index 7e57693..37ad0eb 100644 --- a/src/build/token/List.go +++ b/src/build/token/List.go @@ -1,5 +1,9 @@ package token +import ( + "strings" +) + // List is a slice of tokens. type List []Token @@ -24,3 +28,14 @@ func (list List) LastIndexKind(kind Kind) int { return -1 } + +// Text returns the concatenated token text. +func (list List) Text(source []byte) string { + tmp := strings.Builder{} + + for _, t := range list { + tmp.WriteString(t.Text(source)) + } + + return tmp.String() +} diff --git a/tests/errors/EmptySwitch.q b/tests/errors/EmptySwitch.q new file mode 100644 index 0000000..866a186 --- /dev/null +++ b/tests/errors/EmptySwitch.q @@ -0,0 +1,3 @@ +main() { + switch {} +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 2b9ce40..2ec440e 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -14,6 +14,7 @@ var errs = []struct { File string ExpectedError error }{ + {"EmptySwitch.q", errors.EmptySwitch}, {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, diff --git a/tests/programs/switch.q b/tests/programs/switch.q new file mode 100644 index 0000000..7c1e050 --- /dev/null +++ b/tests/programs/switch.q @@ -0,0 +1,44 @@ +import sys + +main() { + correct := 0 + + switch { + 1 == 1 { correct += 1 } + } + + switch { + 1 == 1 { correct += 1 } + _ { correct -= 1 } + } + + switch { + 0 == 1 { correct -= 1 } + _ { correct += 1 } + } + + switch { + 0 == 1 { correct -= 1 } + 0 == 2 { correct -= 1 } + _ { correct += 1 } + } + + switch { + 0 == 1 { correct -= 1 } + 0 == 2 { correct -= 1 } + 2 == 2 { correct += 1 } + _ { correct -= 1 } + } + + switch { + 0 == 1 { correct -= 1 } + 0 == 2 { correct -= 1 } + 0 == 3 { correct -= 1 } + } + + if correct == 5 { + return + } + + sys.exit(1) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 4f2be2d..c381573 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -53,6 +53,7 @@ var programs = []struct { {"branch-or", "", "", 0}, {"branch-both", "", "", 0}, {"jump-near", "", "", 0}, + {"switch", "", "", 0}, {"loop", "", "", 0}, {"loop-lifetime", "", "", 0}, {"out-of-memory", "", "", 0}, From 5736b08786826e8f83c0f774b8f9a24f9e3952ca Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 3 Aug 2024 23:32:47 +0200 Subject: [PATCH 0461/1012] Improved gcd example --- examples/gcd/gcd.q | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/examples/gcd/gcd.q b/examples/gcd/gcd.q index 9e9b5e0..03dead2 100644 --- a/examples/gcd/gcd.q +++ b/examples/gcd/gcd.q @@ -6,14 +6,10 @@ main() { gcd(a, b) { loop { - if a == b { - return a - } - - if a > b { - a -= b - } else { - b -= a + switch { + a == b { return a } + a > b { a -= b } + _ { b -= a } } } } \ No newline at end of file From 3ad95104215dc94c3e6c481a309a2d07152828ff Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 3 Aug 2024 23:32:47 +0200 Subject: [PATCH 0462/1012] Improved gcd example --- examples/gcd/gcd.q | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/examples/gcd/gcd.q b/examples/gcd/gcd.q index 9e9b5e0..03dead2 100644 --- a/examples/gcd/gcd.q +++ b/examples/gcd/gcd.q @@ -6,14 +6,10 @@ main() { gcd(a, b) { loop { - if a == b { - return a - } - - if a > b { - a -= b - } else { - b -= a + switch { + a == b { return a } + a > b { a -= b } + _ { b -= a } } } } \ No newline at end of file From 745df4df2ab15ca22d55c3e436bf622e7b9da625 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 4 Aug 2024 00:00:26 +0200 Subject: [PATCH 0463/1012] Improved fizzbuzz example --- examples/fizzbuzz/fizzbuzz.q | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q index 391c7a2..9d88215 100644 --- a/examples/fizzbuzz/fizzbuzz.q +++ b/examples/fizzbuzz/fizzbuzz.q @@ -10,18 +10,10 @@ fizzbuzz(n) { loop { switch { - x % 15 == 0 { - print("FizzBuzz", 8) - } - x % 5 == 0 { - print("Buzz", 4) - } - x % 3 == 0 { - print("Fizz", 4) - } - _ { - log.number(x) - } + x % 15 == 0 { print("FizzBuzz", 8) } + x % 5 == 0 { print("Buzz", 4) } + x % 3 == 0 { print("Fizz", 4) } + _ { log.number(x) } } x += 1 From f9ff83136ac53f4a7b5339c7c17810f6d3de01d7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 4 Aug 2024 00:00:26 +0200 Subject: [PATCH 0464/1012] Improved fizzbuzz example --- examples/fizzbuzz/fizzbuzz.q | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q index 391c7a2..9d88215 100644 --- a/examples/fizzbuzz/fizzbuzz.q +++ b/examples/fizzbuzz/fizzbuzz.q @@ -10,18 +10,10 @@ fizzbuzz(n) { loop { switch { - x % 15 == 0 { - print("FizzBuzz", 8) - } - x % 5 == 0 { - print("Buzz", 4) - } - x % 3 == 0 { - print("Fizz", 4) - } - _ { - log.number(x) - } + x % 15 == 0 { print("FizzBuzz", 8) } + x % 5 == 0 { print("Buzz", 4) } + x % 3 == 0 { print("Fizz", 4) } + _ { log.number(x) } } x += 1 From f2858b1dc2f7687b00d0e3b75e9469a6290ab272 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 4 Aug 2024 00:35:33 +0200 Subject: [PATCH 0465/1012] Improved division --- src/build/asm/Finalize.go | 32 +++++++++++++-- src/build/asm/divide.go | 54 ------------------------- src/build/core/ExecuteRegisterNumber.go | 10 ++++- 3 files changed, 36 insertions(+), 60 deletions(-) delete mode 100644 src/build/asm/divide.go diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index ff0c7b3..093e060 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -58,7 +58,34 @@ func (a Assembler) Finalize() ([]byte, []byte) { } case DIV: - code = divide(code, x.Data) + switch operands := x.Data.(type) { + case *RegisterRegister: + if operands.Destination != x64.RAX { + code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination) + } + + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Source) + + if operands.Destination != x64.RAX { + code = x64.MoveRegisterRegister(code, operands.Destination, x64.RAX) + } + } + + case MODULO: + switch operands := x.Data.(type) { + case *RegisterRegister: + if operands.Destination != x64.RAX { + code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination) + } + + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Source) + + if operands.Destination != x64.RDX { + code = x64.MoveRegisterRegister(code, operands.Destination, x64.RDX) + } + } case CALL: code = x64.Call(code, 0x00_00_00_00) @@ -144,9 +171,6 @@ func (a Assembler) Finalize() ([]byte, []byte) { code = x64.LoadRegister(code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) } - case MODULO: - code = modulo(code, x.Data) - case MOVE: switch operands := x.Data.(type) { case *RegisterNumber: diff --git a/src/build/asm/divide.go b/src/build/asm/divide.go deleted file mode 100644 index a916cad..0000000 --- a/src/build/asm/divide.go +++ /dev/null @@ -1,54 +0,0 @@ -package asm - -import "git.akyoto.dev/cli/q/src/build/arch/x64" - -// divide implements the division on x64 machines. -func divide(code []byte, data any) []byte { - switch operands := data.(type) { - case *RegisterNumber: - if operands.Register == x64.RAX { - code = x64.MoveRegisterNumber(code, x64.RCX, operands.Number) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, x64.RCX) - } else { - code = x64.MoveRegisterRegister(code, x64.RAX, operands.Register) - code = x64.MoveRegisterNumber(code, operands.Register, operands.Number) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Register) - code = x64.MoveRegisterRegister(code, operands.Register, x64.RAX) - } - - case *RegisterRegister: - if operands.Destination == x64.RAX { - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Source) - } else { - code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination) - code = x64.ExtendRAXToRDX(code) - - code = x64.DivRegister(code, operands.Source) - code = x64.MoveRegisterRegister(code, operands.Destination, x64.RAX) - } - } - return code -} - -// modulo calculates the division remainder on x64 machines. -func modulo(code []byte, data any) []byte { - switch operands := data.(type) { - case *RegisterNumber: - code = x64.MoveRegisterRegister(code, x64.RAX, operands.Register) - code = x64.MoveRegisterNumber(code, operands.Register, operands.Number) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Register) - code = x64.MoveRegisterRegister(code, operands.Register, x64.RDX) - - case *RegisterRegister: - code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Source) - code = x64.MoveRegisterRegister(code, operands.Destination, x64.RDX) - } - - return code -} diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index c78fe66..1884d51 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -24,10 +24,16 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg f.RegisterNumber(asm.MUL, register, number) case token.Div, token.DivAssign: - f.RegisterNumber(asm.DIV, register, number) + tmp := f.NewRegister() + f.RegisterNumber(asm.MOVE, tmp, number) + f.RegisterRegister(asm.DIV, register, tmp) + f.FreeRegister(tmp) case token.Mod, token.ModAssign: - f.RegisterNumber(asm.MODULO, register, number) + tmp := f.NewRegister() + f.RegisterNumber(asm.MOVE, tmp, number) + f.RegisterRegister(asm.MODULO, register, tmp) + f.FreeRegister(tmp) case token.And, token.AndAssign: f.RegisterNumber(asm.AND, register, number) From c8045cb4fb248d47c1f160e03bc0bd40dcc2428d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 4 Aug 2024 00:35:33 +0200 Subject: [PATCH 0466/1012] Improved division --- src/build/asm/Finalize.go | 32 +++++++++++++-- src/build/asm/divide.go | 54 ------------------------- src/build/core/ExecuteRegisterNumber.go | 10 ++++- 3 files changed, 36 insertions(+), 60 deletions(-) delete mode 100644 src/build/asm/divide.go diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index ff0c7b3..093e060 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -58,7 +58,34 @@ func (a Assembler) Finalize() ([]byte, []byte) { } case DIV: - code = divide(code, x.Data) + switch operands := x.Data.(type) { + case *RegisterRegister: + if operands.Destination != x64.RAX { + code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination) + } + + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Source) + + if operands.Destination != x64.RAX { + code = x64.MoveRegisterRegister(code, operands.Destination, x64.RAX) + } + } + + case MODULO: + switch operands := x.Data.(type) { + case *RegisterRegister: + if operands.Destination != x64.RAX { + code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination) + } + + code = x64.ExtendRAXToRDX(code) + code = x64.DivRegister(code, operands.Source) + + if operands.Destination != x64.RDX { + code = x64.MoveRegisterRegister(code, operands.Destination, x64.RDX) + } + } case CALL: code = x64.Call(code, 0x00_00_00_00) @@ -144,9 +171,6 @@ func (a Assembler) Finalize() ([]byte, []byte) { code = x64.LoadRegister(code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) } - case MODULO: - code = modulo(code, x.Data) - case MOVE: switch operands := x.Data.(type) { case *RegisterNumber: diff --git a/src/build/asm/divide.go b/src/build/asm/divide.go deleted file mode 100644 index a916cad..0000000 --- a/src/build/asm/divide.go +++ /dev/null @@ -1,54 +0,0 @@ -package asm - -import "git.akyoto.dev/cli/q/src/build/arch/x64" - -// divide implements the division on x64 machines. -func divide(code []byte, data any) []byte { - switch operands := data.(type) { - case *RegisterNumber: - if operands.Register == x64.RAX { - code = x64.MoveRegisterNumber(code, x64.RCX, operands.Number) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, x64.RCX) - } else { - code = x64.MoveRegisterRegister(code, x64.RAX, operands.Register) - code = x64.MoveRegisterNumber(code, operands.Register, operands.Number) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Register) - code = x64.MoveRegisterRegister(code, operands.Register, x64.RAX) - } - - case *RegisterRegister: - if operands.Destination == x64.RAX { - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Source) - } else { - code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination) - code = x64.ExtendRAXToRDX(code) - - code = x64.DivRegister(code, operands.Source) - code = x64.MoveRegisterRegister(code, operands.Destination, x64.RAX) - } - } - return code -} - -// modulo calculates the division remainder on x64 machines. -func modulo(code []byte, data any) []byte { - switch operands := data.(type) { - case *RegisterNumber: - code = x64.MoveRegisterRegister(code, x64.RAX, operands.Register) - code = x64.MoveRegisterNumber(code, operands.Register, operands.Number) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Register) - code = x64.MoveRegisterRegister(code, operands.Register, x64.RDX) - - case *RegisterRegister: - code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination) - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Source) - code = x64.MoveRegisterRegister(code, operands.Destination, x64.RDX) - } - - return code -} diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index c78fe66..1884d51 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -24,10 +24,16 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg f.RegisterNumber(asm.MUL, register, number) case token.Div, token.DivAssign: - f.RegisterNumber(asm.DIV, register, number) + tmp := f.NewRegister() + f.RegisterNumber(asm.MOVE, tmp, number) + f.RegisterRegister(asm.DIV, register, tmp) + f.FreeRegister(tmp) case token.Mod, token.ModAssign: - f.RegisterNumber(asm.MODULO, register, number) + tmp := f.NewRegister() + f.RegisterNumber(asm.MOVE, tmp, number) + f.RegisterRegister(asm.MODULO, register, tmp) + f.FreeRegister(tmp) case token.And, token.AndAssign: f.RegisterNumber(asm.AND, register, number) From 40d2171c481eeea64b9eb57425a196cff8f5adef Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 4 Aug 2024 16:17:33 +0200 Subject: [PATCH 0467/1012] Added prime numbers example --- examples/prime/prime.q | 47 +++++++++++++++++++++++ src/build/core/ExecuteRegisterNumber.go | 5 +++ src/build/core/ExecuteRegisterRegister.go | 5 +++ tests/examples_test.go | 1 + 4 files changed, 58 insertions(+) create mode 100644 examples/prime/prime.q diff --git a/examples/prime/prime.q b/examples/prime/prime.q new file mode 100644 index 0000000..a117bdc --- /dev/null +++ b/examples/prime/prime.q @@ -0,0 +1,47 @@ +import log +import sys + +main() { + n := 100 + i := 2 + + loop { + if i > n { + return + } + + if isPrime(i) == 1 { + if i != 2 { + sys.write(1, " ", 1) + } + + log.number(i) + } + + i += 1 + } +} + +isPrime(x) { + if x == 2 { + return 1 + } + + if x % 2 == 0 { + return 0 + } + + i := 3 + + loop { + if i * i > x { + return 1 + } + + if x % i == 0 { + return 0 + } + + i += 2 + } +} \ No newline at end of file diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index 1884d51..63a45f6 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -1,6 +1,7 @@ package core import ( + "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" @@ -24,12 +25,16 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg f.RegisterNumber(asm.MUL, register, number) case token.Div, token.DivAssign: + f.SaveRegister(x64.RAX) + f.SaveRegister(x64.RDX) tmp := f.NewRegister() f.RegisterNumber(asm.MOVE, tmp, number) f.RegisterRegister(asm.DIV, register, tmp) f.FreeRegister(tmp) case token.Mod, token.ModAssign: + f.SaveRegister(x64.RAX) + f.SaveRegister(x64.RDX) tmp := f.NewRegister() f.RegisterNumber(asm.MOVE, tmp, number) f.RegisterRegister(asm.MODULO, register, tmp) diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index 129d452..42363ae 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -1,6 +1,7 @@ package core import ( + "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" @@ -24,9 +25,13 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.R f.RegisterRegister(asm.MUL, register, operand) case token.Div, token.DivAssign: + f.SaveRegister(x64.RAX) + f.SaveRegister(x64.RDX) f.RegisterRegister(asm.DIV, register, operand) case token.Mod, token.ModAssign: + f.SaveRegister(x64.RAX) + f.SaveRegister(x64.RDX) f.RegisterRegister(asm.MODULO, register, operand) case token.And, token.AndAssign: diff --git a/tests/examples_test.go b/tests/examples_test.go index b378045..2279740 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -24,6 +24,7 @@ var examples = []struct { {"itoa", "", "9223372036854775807", 0}, {"collatz", "", "6 3 10 5 16 8 4 2 1", 0}, {"fizzbuzz", "", "1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz", 0}, + {"prime", "", "2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97", 0}, } func TestExamples(t *testing.T) { From 3e3567cd3276cd7b9ffdacf7ef2e417ca4da600b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 4 Aug 2024 16:17:33 +0200 Subject: [PATCH 0468/1012] Added prime numbers example --- examples/prime/prime.q | 47 +++++++++++++++++++++++ src/build/core/ExecuteRegisterNumber.go | 5 +++ src/build/core/ExecuteRegisterRegister.go | 5 +++ tests/examples_test.go | 1 + 4 files changed, 58 insertions(+) create mode 100644 examples/prime/prime.q diff --git a/examples/prime/prime.q b/examples/prime/prime.q new file mode 100644 index 0000000..a117bdc --- /dev/null +++ b/examples/prime/prime.q @@ -0,0 +1,47 @@ +import log +import sys + +main() { + n := 100 + i := 2 + + loop { + if i > n { + return + } + + if isPrime(i) == 1 { + if i != 2 { + sys.write(1, " ", 1) + } + + log.number(i) + } + + i += 1 + } +} + +isPrime(x) { + if x == 2 { + return 1 + } + + if x % 2 == 0 { + return 0 + } + + i := 3 + + loop { + if i * i > x { + return 1 + } + + if x % i == 0 { + return 0 + } + + i += 2 + } +} \ No newline at end of file diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/build/core/ExecuteRegisterNumber.go index 1884d51..63a45f6 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/build/core/ExecuteRegisterNumber.go @@ -1,6 +1,7 @@ package core import ( + "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" @@ -24,12 +25,16 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg f.RegisterNumber(asm.MUL, register, number) case token.Div, token.DivAssign: + f.SaveRegister(x64.RAX) + f.SaveRegister(x64.RDX) tmp := f.NewRegister() f.RegisterNumber(asm.MOVE, tmp, number) f.RegisterRegister(asm.DIV, register, tmp) f.FreeRegister(tmp) case token.Mod, token.ModAssign: + f.SaveRegister(x64.RAX) + f.SaveRegister(x64.RDX) tmp := f.NewRegister() f.RegisterNumber(asm.MOVE, tmp, number) f.RegisterRegister(asm.MODULO, register, tmp) diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/build/core/ExecuteRegisterRegister.go index 129d452..42363ae 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/build/core/ExecuteRegisterRegister.go @@ -1,6 +1,7 @@ package core import ( + "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" @@ -24,9 +25,13 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.R f.RegisterRegister(asm.MUL, register, operand) case token.Div, token.DivAssign: + f.SaveRegister(x64.RAX) + f.SaveRegister(x64.RDX) f.RegisterRegister(asm.DIV, register, operand) case token.Mod, token.ModAssign: + f.SaveRegister(x64.RAX) + f.SaveRegister(x64.RDX) f.RegisterRegister(asm.MODULO, register, operand) case token.And, token.AndAssign: diff --git a/tests/examples_test.go b/tests/examples_test.go index b378045..2279740 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -24,6 +24,7 @@ var examples = []struct { {"itoa", "", "9223372036854775807", 0}, {"collatz", "", "6 3 10 5 16 8 4 2 1", 0}, {"fizzbuzz", "", "1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz", 0}, + {"prime", "", "2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97", 0}, } func TestExamples(t *testing.T) { From b12491511977c4363609df2df48997a868dbf39c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 5 Aug 2024 10:54:18 +0200 Subject: [PATCH 0469/1012] Removed unnecessary line --- lib/log/number.q | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/log/number.q b/lib/log/number.q index 6b9c531..01471dc 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -10,8 +10,7 @@ number(x) { } itoa(x, buffer, length) { - end := buffer + length - tmp := end + tmp := buffer + length digit := 0 loop { From d4020da6d96bb8cb2d2512ee183cdd15b1906361 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 5 Aug 2024 10:54:18 +0200 Subject: [PATCH 0470/1012] Removed unnecessary line --- lib/log/number.q | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/log/number.q b/lib/log/number.q index 6b9c531..01471dc 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -10,8 +10,7 @@ number(x) { } itoa(x, buffer, length) { - end := buffer + length - tmp := end + tmp := buffer + length digit := 0 loop { From 14e2aa0588b936aafc192d485032d2c7e2cbfd20 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 5 Aug 2024 12:39:07 +0200 Subject: [PATCH 0471/1012] Implemented multiple return values --- README.md | 2 +- lib/log/number.q | 9 +++-- src/build/arch/x64/Return.go | 2 +- src/build/ast/Count.go | 4 +- src/build/ast/Return.go | 2 +- src/build/ast/parseKeyword.go | 4 +- src/build/core/CompileASTNode.go | 5 ++- src/build/core/CompileCall.go | 1 + src/build/core/CompileDefinition.go | 48 +++++++++++++++--------- src/build/core/CompileReturn.go | 4 +- src/build/core/Define.go | 31 +++++++++++++++ src/build/core/ExpressionToRegister.go | 5 +-- src/build/core/ExpressionsToRegisters.go | 2 +- tests/programs/return-multi.q | 28 ++++++++++++++ tests/programs_test.go | 1 + 15 files changed, 113 insertions(+), 35 deletions(-) create mode 100644 src/build/core/Define.go create mode 100644 tests/programs/return-multi.q diff --git a/README.md b/README.md index 8ba0c76..d48bd05 100644 --- a/README.md +++ b/README.md @@ -115,11 +115,11 @@ This is what generates expressions from tokens. - [x] Loops - [x] Hexadecimal, octal and binary literals - [x] Escape sequences +- [x] Multiple return values - [ ] Data structures - [ ] Type system - [ ] Type operator: `|` (`User | Error`) - [ ] Error handling -- [ ] Multiple return values - [ ] Threading library - [ ] Self-hosted compiler diff --git a/lib/log/number.q b/lib/log/number.q index 01471dc..86be427 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -4,13 +4,14 @@ import sys number(x) { length := 20 buffer := mem.alloc(length) - tmp := itoa(x, buffer, length) - sys.write(1, tmp, buffer + length - tmp) + address, count := itoa(x, buffer, length) + sys.write(1, address, count) mem.free(buffer, length) } itoa(x, buffer, length) { - tmp := buffer + length + end := buffer + length + tmp := end digit := 0 loop { @@ -19,7 +20,7 @@ itoa(x, buffer, length) { tmp[0] = '0' + digit if x == 0 { - return tmp + return tmp, end - tmp } } } \ No newline at end of file diff --git a/src/build/arch/x64/Return.go b/src/build/arch/x64/Return.go index dc185da..a64d39f 100644 --- a/src/build/arch/x64/Return.go +++ b/src/build/arch/x64/Return.go @@ -3,5 +3,5 @@ package x64 // Return transfers program control to a return address located on the top of the stack. // The address is usually placed on the stack by a Call instruction. func Return(code []byte) []byte { - return append(code, 0xc3) + return append(code, 0xC3) } diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index 192a36f..2c61203 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -29,8 +29,8 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { count += Count(node.Body, buffer, kind, name) case *Return: - if node.Value != nil { - count += node.Value.Count(buffer, kind, name) + for _, value := range node.Values { + count += value.Count(buffer, kind, name) } case *Switch: diff --git a/src/build/ast/Return.go b/src/build/ast/Return.go index 37574e2..6c8aa2d 100644 --- a/src/build/ast/Return.go +++ b/src/build/ast/Return.go @@ -6,5 +6,5 @@ import ( // Return represents a return statement. type Return struct { - Value *expression.Expression + Values []*expression.Expression } diff --git a/src/build/ast/parseKeyword.go b/src/build/ast/parseKeyword.go index 0b6a423..f4a948a 100644 --- a/src/build/ast/parseKeyword.go +++ b/src/build/ast/parseKeyword.go @@ -48,8 +48,8 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { return &Return{}, nil } - value := expression.Parse(tokens[1:]) - return &Return{Value: value}, nil + values := expression.NewList(tokens[1:]) + return &Return{Values: values}, nil case token.Switch: blockStart := tokens.IndexKind(token.BlockStart) diff --git a/src/build/core/CompileASTNode.go b/src/build/core/CompileASTNode.go index e3bccaf..7277ccc 100644 --- a/src/build/core/CompileASTNode.go +++ b/src/build/core/CompileASTNode.go @@ -31,7 +31,10 @@ func (f *Function) CompileASTNode(node ast.Node) error { return f.CompileLoop(node) case *ast.Return: - f.Fold(node.Value) + for _, value := range node.Values { + f.Fold(value) + } + return f.CompileReturn(node) case *ast.Switch: diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 68e915a..c04f842 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -69,6 +69,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { return err } + // TODO: Save all return value registers of the function f.SaveRegister(f.CPU.Output[0]) // Push diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 75241ba..689f020 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -1,36 +1,50 @@ package core import ( + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/scope" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/expression" ) // CompileDefinition compiles a variable definition. func (f *Function) CompileDefinition(node *ast.Define) error { left := node.Expression.Children[0] right := node.Expression.Children[1] - name := left.Token.Text(f.File.Bytes) - if f.IdentifierExists(name) { - return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, left.Token.Position) + if left.IsLeaf() { + variable, err := f.Define(left) + + if err != nil { + return err + } + + err = f.ExpressionToRegister(right, variable.Register) + f.AddVariable(variable) + return err } - uses := token.Count(f.Body, f.File.Bytes, token.Identifier, name) - 1 - - if uses == 0 { - return errors.New(&errors.UnusedVariable{Name: name}, f.File, left.Token.Position) + if !ast.IsFunctionCall(right) { + return errors.New(errors.NotImplemented, f.File, node.Expression.Token.Position) } - register := f.NewRegister() - err := f.ExpressionToRegister(right, register) + count := 0 + err := f.CompileCall(right) - f.AddVariable(&scope.Variable{ - Name: name, - Register: register, - Alive: uses, + if err != nil { + return err + } + + return left.EachLeaf(func(leaf *expression.Expression) error { + variable, err := f.Define(leaf) + + if err != nil { + return err + } + + f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) + f.AddVariable(variable) + count++ + return nil }) - - return err } diff --git a/src/build/core/CompileReturn.go b/src/build/core/CompileReturn.go index 26f1cab..ca59d56 100644 --- a/src/build/core/CompileReturn.go +++ b/src/build/core/CompileReturn.go @@ -8,9 +8,9 @@ import ( func (f *Function) CompileReturn(node *ast.Return) error { defer f.Return() - if node.Value == nil { + if len(node.Values) == 0 { return nil } - return f.ExpressionToRegister(node.Value, f.CPU.Output[0]) + return f.ExpressionsToRegisters(node.Values, f.CPU.Output) } diff --git a/src/build/core/Define.go b/src/build/core/Define.go new file mode 100644 index 0000000..0867f1b --- /dev/null +++ b/src/build/core/Define.go @@ -0,0 +1,31 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/scope" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Define defines a new variable. +func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) { + name := leaf.Token.Text(f.File.Bytes) + + if f.IdentifierExists(name) { + return nil, errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, leaf.Token.Position) + } + + uses := token.Count(f.Body, f.File.Bytes, token.Identifier, name) - 1 + + if uses == 0 { + return nil, errors.New(&errors.UnusedVariable{Name: name}, f.File, leaf.Token.Position) + } + + variable := &scope.Variable{ + Name: name, + Register: f.NewRegister(), + Alive: uses, + } + + return variable, nil +} diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index e115758..29a084c 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -11,8 +11,9 @@ import ( // ExpressionToRegister puts the result of an expression into the specified register. func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) error { + f.SaveRegister(register) + if node.IsFolded { - f.SaveRegister(register) f.RegisterNumber(asm.MOVE, register, node.Value) return nil } @@ -25,7 +26,6 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp err := f.CompileCall(node) if register != f.CPU.Output[0] { - f.SaveRegister(register) f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) } @@ -70,7 +70,6 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp err = f.Execute(node.Token, register, right) if register != final { - f.SaveRegister(final) f.RegisterRegister(asm.MOVE, final, register) f.FreeRegister(register) } diff --git a/src/build/core/ExpressionsToRegisters.go b/src/build/core/ExpressionsToRegisters.go index 64d6816..f3a09fe 100644 --- a/src/build/core/ExpressionsToRegisters.go +++ b/src/build/core/ExpressionsToRegisters.go @@ -7,7 +7,7 @@ import ( // ExpressionsToRegisters moves multiple expressions into the specified registers. func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { - for i := len(registers) - 1; i >= 0; i-- { + for i := len(expressions) - 1; i >= 0; i-- { err := f.ExpressionToRegister(expressions[i], registers[i]) if err != nil { diff --git a/tests/programs/return-multi.q b/tests/programs/return-multi.q new file mode 100644 index 0000000..47e72be --- /dev/null +++ b/tests/programs/return-multi.q @@ -0,0 +1,28 @@ +main() { + a, b := reverse2(1, 2) + assert a == 2 + assert b == 1 + + c, d, e := reverse3(1, 2, 3) + assert c == 3 + assert d == 2 + assert e == 1 + + f, g, h, i := mix4(1, 2, 3, 4) + assert f == 4 + 1 + assert g == 3 + 2 + assert h == 2 + 3 + assert i == 1 + 4 +} + +reverse2(a, b) { + return b, a +} + +reverse3(a, b, c) { + return c, b, a +} + +mix4(a, b, c, d) { + return d + a, c + b, b + c, a + d +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index c381573..39c5677 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -24,6 +24,7 @@ var programs = []struct { {"reassign", "", "", 0}, {"reuse", "", "", 0}, {"return", "", "", 0}, + {"return-multi", "", "", 0}, {"math", "", "", 0}, {"precedence", "", "", 0}, {"op-assign", "", "", 0}, From 42f0367a94d709beb23b93e3cd7104f78adb6da4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 5 Aug 2024 12:39:07 +0200 Subject: [PATCH 0472/1012] Implemented multiple return values --- README.md | 2 +- lib/log/number.q | 9 +++-- src/build/arch/x64/Return.go | 2 +- src/build/ast/Count.go | 4 +- src/build/ast/Return.go | 2 +- src/build/ast/parseKeyword.go | 4 +- src/build/core/CompileASTNode.go | 5 ++- src/build/core/CompileCall.go | 1 + src/build/core/CompileDefinition.go | 48 +++++++++++++++--------- src/build/core/CompileReturn.go | 4 +- src/build/core/Define.go | 31 +++++++++++++++ src/build/core/ExpressionToRegister.go | 5 +-- src/build/core/ExpressionsToRegisters.go | 2 +- tests/programs/return-multi.q | 28 ++++++++++++++ tests/programs_test.go | 1 + 15 files changed, 113 insertions(+), 35 deletions(-) create mode 100644 src/build/core/Define.go create mode 100644 tests/programs/return-multi.q diff --git a/README.md b/README.md index 8ba0c76..d48bd05 100644 --- a/README.md +++ b/README.md @@ -115,11 +115,11 @@ This is what generates expressions from tokens. - [x] Loops - [x] Hexadecimal, octal and binary literals - [x] Escape sequences +- [x] Multiple return values - [ ] Data structures - [ ] Type system - [ ] Type operator: `|` (`User | Error`) - [ ] Error handling -- [ ] Multiple return values - [ ] Threading library - [ ] Self-hosted compiler diff --git a/lib/log/number.q b/lib/log/number.q index 01471dc..86be427 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -4,13 +4,14 @@ import sys number(x) { length := 20 buffer := mem.alloc(length) - tmp := itoa(x, buffer, length) - sys.write(1, tmp, buffer + length - tmp) + address, count := itoa(x, buffer, length) + sys.write(1, address, count) mem.free(buffer, length) } itoa(x, buffer, length) { - tmp := buffer + length + end := buffer + length + tmp := end digit := 0 loop { @@ -19,7 +20,7 @@ itoa(x, buffer, length) { tmp[0] = '0' + digit if x == 0 { - return tmp + return tmp, end - tmp } } } \ No newline at end of file diff --git a/src/build/arch/x64/Return.go b/src/build/arch/x64/Return.go index dc185da..a64d39f 100644 --- a/src/build/arch/x64/Return.go +++ b/src/build/arch/x64/Return.go @@ -3,5 +3,5 @@ package x64 // Return transfers program control to a return address located on the top of the stack. // The address is usually placed on the stack by a Call instruction. func Return(code []byte) []byte { - return append(code, 0xc3) + return append(code, 0xC3) } diff --git a/src/build/ast/Count.go b/src/build/ast/Count.go index 192a36f..2c61203 100644 --- a/src/build/ast/Count.go +++ b/src/build/ast/Count.go @@ -29,8 +29,8 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { count += Count(node.Body, buffer, kind, name) case *Return: - if node.Value != nil { - count += node.Value.Count(buffer, kind, name) + for _, value := range node.Values { + count += value.Count(buffer, kind, name) } case *Switch: diff --git a/src/build/ast/Return.go b/src/build/ast/Return.go index 37574e2..6c8aa2d 100644 --- a/src/build/ast/Return.go +++ b/src/build/ast/Return.go @@ -6,5 +6,5 @@ import ( // Return represents a return statement. type Return struct { - Value *expression.Expression + Values []*expression.Expression } diff --git a/src/build/ast/parseKeyword.go b/src/build/ast/parseKeyword.go index 0b6a423..f4a948a 100644 --- a/src/build/ast/parseKeyword.go +++ b/src/build/ast/parseKeyword.go @@ -48,8 +48,8 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { return &Return{}, nil } - value := expression.Parse(tokens[1:]) - return &Return{Value: value}, nil + values := expression.NewList(tokens[1:]) + return &Return{Values: values}, nil case token.Switch: blockStart := tokens.IndexKind(token.BlockStart) diff --git a/src/build/core/CompileASTNode.go b/src/build/core/CompileASTNode.go index e3bccaf..7277ccc 100644 --- a/src/build/core/CompileASTNode.go +++ b/src/build/core/CompileASTNode.go @@ -31,7 +31,10 @@ func (f *Function) CompileASTNode(node ast.Node) error { return f.CompileLoop(node) case *ast.Return: - f.Fold(node.Value) + for _, value := range node.Values { + f.Fold(value) + } + return f.CompileReturn(node) case *ast.Switch: diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 68e915a..c04f842 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -69,6 +69,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { return err } + // TODO: Save all return value registers of the function f.SaveRegister(f.CPU.Output[0]) // Push diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 75241ba..689f020 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -1,36 +1,50 @@ package core import ( + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/scope" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/expression" ) // CompileDefinition compiles a variable definition. func (f *Function) CompileDefinition(node *ast.Define) error { left := node.Expression.Children[0] right := node.Expression.Children[1] - name := left.Token.Text(f.File.Bytes) - if f.IdentifierExists(name) { - return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, left.Token.Position) + if left.IsLeaf() { + variable, err := f.Define(left) + + if err != nil { + return err + } + + err = f.ExpressionToRegister(right, variable.Register) + f.AddVariable(variable) + return err } - uses := token.Count(f.Body, f.File.Bytes, token.Identifier, name) - 1 - - if uses == 0 { - return errors.New(&errors.UnusedVariable{Name: name}, f.File, left.Token.Position) + if !ast.IsFunctionCall(right) { + return errors.New(errors.NotImplemented, f.File, node.Expression.Token.Position) } - register := f.NewRegister() - err := f.ExpressionToRegister(right, register) + count := 0 + err := f.CompileCall(right) - f.AddVariable(&scope.Variable{ - Name: name, - Register: register, - Alive: uses, + if err != nil { + return err + } + + return left.EachLeaf(func(leaf *expression.Expression) error { + variable, err := f.Define(leaf) + + if err != nil { + return err + } + + f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) + f.AddVariable(variable) + count++ + return nil }) - - return err } diff --git a/src/build/core/CompileReturn.go b/src/build/core/CompileReturn.go index 26f1cab..ca59d56 100644 --- a/src/build/core/CompileReturn.go +++ b/src/build/core/CompileReturn.go @@ -8,9 +8,9 @@ import ( func (f *Function) CompileReturn(node *ast.Return) error { defer f.Return() - if node.Value == nil { + if len(node.Values) == 0 { return nil } - return f.ExpressionToRegister(node.Value, f.CPU.Output[0]) + return f.ExpressionsToRegisters(node.Values, f.CPU.Output) } diff --git a/src/build/core/Define.go b/src/build/core/Define.go new file mode 100644 index 0000000..0867f1b --- /dev/null +++ b/src/build/core/Define.go @@ -0,0 +1,31 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/scope" + "git.akyoto.dev/cli/q/src/build/token" +) + +// Define defines a new variable. +func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) { + name := leaf.Token.Text(f.File.Bytes) + + if f.IdentifierExists(name) { + return nil, errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, leaf.Token.Position) + } + + uses := token.Count(f.Body, f.File.Bytes, token.Identifier, name) - 1 + + if uses == 0 { + return nil, errors.New(&errors.UnusedVariable{Name: name}, f.File, leaf.Token.Position) + } + + variable := &scope.Variable{ + Name: name, + Register: f.NewRegister(), + Alive: uses, + } + + return variable, nil +} diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index e115758..29a084c 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -11,8 +11,9 @@ import ( // ExpressionToRegister puts the result of an expression into the specified register. func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) error { + f.SaveRegister(register) + if node.IsFolded { - f.SaveRegister(register) f.RegisterNumber(asm.MOVE, register, node.Value) return nil } @@ -25,7 +26,6 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp err := f.CompileCall(node) if register != f.CPU.Output[0] { - f.SaveRegister(register) f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) } @@ -70,7 +70,6 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp err = f.Execute(node.Token, register, right) if register != final { - f.SaveRegister(final) f.RegisterRegister(asm.MOVE, final, register) f.FreeRegister(register) } diff --git a/src/build/core/ExpressionsToRegisters.go b/src/build/core/ExpressionsToRegisters.go index 64d6816..f3a09fe 100644 --- a/src/build/core/ExpressionsToRegisters.go +++ b/src/build/core/ExpressionsToRegisters.go @@ -7,7 +7,7 @@ import ( // ExpressionsToRegisters moves multiple expressions into the specified registers. func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { - for i := len(registers) - 1; i >= 0; i-- { + for i := len(expressions) - 1; i >= 0; i-- { err := f.ExpressionToRegister(expressions[i], registers[i]) if err != nil { diff --git a/tests/programs/return-multi.q b/tests/programs/return-multi.q new file mode 100644 index 0000000..47e72be --- /dev/null +++ b/tests/programs/return-multi.q @@ -0,0 +1,28 @@ +main() { + a, b := reverse2(1, 2) + assert a == 2 + assert b == 1 + + c, d, e := reverse3(1, 2, 3) + assert c == 3 + assert d == 2 + assert e == 1 + + f, g, h, i := mix4(1, 2, 3, 4) + assert f == 4 + 1 + assert g == 3 + 2 + assert h == 2 + 3 + assert i == 1 + 4 +} + +reverse2(a, b) { + return b, a +} + +reverse3(a, b, c) { + return c, b, a +} + +mix4(a, b, c, d) { + return d + a, c + b, b + c, a + d +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index c381573..39c5677 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -24,6 +24,7 @@ var programs = []struct { {"reassign", "", "", 0}, {"reuse", "", "", 0}, {"return", "", "", 0}, + {"return-multi", "", "", 0}, {"math", "", "", 0}, {"precedence", "", "", 0}, {"op-assign", "", "", 0}, From 60a920734d7f2a52d3323e05a596bca12cdd043d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 5 Aug 2024 17:16:32 +0200 Subject: [PATCH 0473/1012] Added RISC-V to the todo list --- README.md | 4 ++-- src/build/arch/arm64/Registers.go | 2 +- src/build/arch/riscv/Registers.go | 40 +++++++++++++++++++++++++++++++ src/build/arch/x64/encode.go | 1 - 4 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 src/build/arch/riscv/Registers.go diff --git a/README.md b/README.md index d48bd05..2e5e22c 100644 --- a/README.md +++ b/README.md @@ -165,8 +165,8 @@ This is what generates expressions from tokens. ### Architecture - [ ] arm64 -- [ ] wasm -- [x] x86-64 +- [ ] riscv +- [x] x64 ### Platform diff --git a/src/build/arch/arm64/Registers.go b/src/build/arch/arm64/Registers.go index 39c87b6..a00c709 100644 --- a/src/build/arch/arm64/Registers.go +++ b/src/build/arch/arm64/Registers.go @@ -1,4 +1,4 @@ -package register +package arm64 import "git.akyoto.dev/cli/q/src/build/cpu" diff --git a/src/build/arch/riscv/Registers.go b/src/build/arch/riscv/Registers.go new file mode 100644 index 0000000..846b936 --- /dev/null +++ b/src/build/arch/riscv/Registers.go @@ -0,0 +1,40 @@ +package riscv + +import "git.akyoto.dev/cli/q/src/build/cpu" + +const ( + X0 cpu.Register = iota + X1 + X2 + X3 + X4 + X5 + X6 + X7 + X8 + X9 + X10 + X11 + X12 + X13 + X14 + X15 + X16 + X17 + X18 + X19 + X20 + X21 + X22 + X23 + X24 + X25 + X26 + X27 + X28 + X29 + X30 + X31 +) + +var SyscallArgs = []cpu.Register{X10, X11, X12, X13, X14, X15, X16} diff --git a/src/build/arch/x64/encode.go b/src/build/arch/x64/encode.go index 570076d..1f45001 100644 --- a/src/build/arch/x64/encode.go +++ b/src/build/arch/x64/encode.go @@ -29,6 +29,5 @@ func encode(code []byte, mod AddressMode, reg cpu.Register, rm cpu.Register, num code = append(code, opCodes...) code = append(code, ModRM(mod, byte(reg), byte(rm))) - return code } From cd1119add29c58858567a8914dd4c64030578446 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 5 Aug 2024 17:16:32 +0200 Subject: [PATCH 0474/1012] Added RISC-V to the todo list --- README.md | 4 ++-- src/build/arch/arm64/Registers.go | 2 +- src/build/arch/riscv/Registers.go | 40 +++++++++++++++++++++++++++++++ src/build/arch/x64/encode.go | 1 - 4 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 src/build/arch/riscv/Registers.go diff --git a/README.md b/README.md index d48bd05..2e5e22c 100644 --- a/README.md +++ b/README.md @@ -165,8 +165,8 @@ This is what generates expressions from tokens. ### Architecture - [ ] arm64 -- [ ] wasm -- [x] x86-64 +- [ ] riscv +- [x] x64 ### Platform diff --git a/src/build/arch/arm64/Registers.go b/src/build/arch/arm64/Registers.go index 39c87b6..a00c709 100644 --- a/src/build/arch/arm64/Registers.go +++ b/src/build/arch/arm64/Registers.go @@ -1,4 +1,4 @@ -package register +package arm64 import "git.akyoto.dev/cli/q/src/build/cpu" diff --git a/src/build/arch/riscv/Registers.go b/src/build/arch/riscv/Registers.go new file mode 100644 index 0000000..846b936 --- /dev/null +++ b/src/build/arch/riscv/Registers.go @@ -0,0 +1,40 @@ +package riscv + +import "git.akyoto.dev/cli/q/src/build/cpu" + +const ( + X0 cpu.Register = iota + X1 + X2 + X3 + X4 + X5 + X6 + X7 + X8 + X9 + X10 + X11 + X12 + X13 + X14 + X15 + X16 + X17 + X18 + X19 + X20 + X21 + X22 + X23 + X24 + X25 + X26 + X27 + X28 + X29 + X30 + X31 +) + +var SyscallArgs = []cpu.Register{X10, X11, X12, X13, X14, X15, X16} diff --git a/src/build/arch/x64/encode.go b/src/build/arch/x64/encode.go index 570076d..1f45001 100644 --- a/src/build/arch/x64/encode.go +++ b/src/build/arch/x64/encode.go @@ -29,6 +29,5 @@ func encode(code []byte, mod AddressMode, reg cpu.Register, rm cpu.Register, num code = append(code, opCodes...) code = append(code, ModRM(mod, byte(reg), byte(rm))) - return code } From 188b85fdce0d5e2b0ea1c7f413ea94b47f058236 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 5 Aug 2024 18:47:24 +0200 Subject: [PATCH 0475/1012] Added types --- examples/collatz/collatz.q | 2 +- examples/factorial/factorial.q | 2 +- examples/fibonacci/fibonacci.q | 2 +- examples/fizzbuzz/fizzbuzz.q | 4 +-- examples/gcd/gcd.q | 2 +- examples/hello/hello.q | 2 +- examples/prime/prime.q | 2 +- lib/log/number.q | 4 +-- lib/mem/alloc.q | 4 +-- lib/sys/linux.q | 38 ++++++++++++++-------------- src/build/errors/Base.go | 21 +++++++++++++++ src/build/errors/CompileErrors.go | 14 ---------- src/build/errors/ScanErrors.go | 11 -------- src/build/scanner/scanFile.go | 20 ++++++++++++--- src/build/token/Kind.go | 1 + src/build/token/Tokenize.go | 24 +++++++++++++----- tests/errors/UnknownIdentifier3.q | 2 +- tests/programs/branch.q | 6 ++--- tests/programs/chained-calls.q | 2 +- tests/programs/{64-bit.q => int64.q} | 0 tests/programs/loop-lifetime.q | 2 +- tests/programs/math.q | 4 +-- tests/programs/negation.q | 2 +- tests/programs/nested-calls.q | 2 +- tests/programs/op-assign.q | 2 +- tests/programs/param-multi.q | 4 +-- tests/programs/param-order.q | 6 ++--- tests/programs/param.q | 4 +-- tests/programs/return-multi.q | 6 ++--- tests/programs/return.q | 4 +-- tests/programs/reuse.q | 2 +- tests/programs/square-sum.q | 2 +- tests/programs_test.go | 2 +- 33 files changed, 113 insertions(+), 92 deletions(-) delete mode 100644 src/build/errors/CompileErrors.go delete mode 100644 src/build/errors/ScanErrors.go rename tests/programs/{64-bit.q => int64.q} (100%) diff --git a/examples/collatz/collatz.q b/examples/collatz/collatz.q index 32507a4..f7d9037 100644 --- a/examples/collatz/collatz.q +++ b/examples/collatz/collatz.q @@ -5,7 +5,7 @@ main() { collatz(12) } -collatz(x) { +collatz(x Int) { loop { if x & 1 == 0 { x /= 2 diff --git a/examples/factorial/factorial.q b/examples/factorial/factorial.q index dab665c..885fc2f 100644 --- a/examples/factorial/factorial.q +++ b/examples/factorial/factorial.q @@ -4,7 +4,7 @@ main() { log.number(factorial(5)) } -factorial(x) { +factorial(x Int) -> Int { if x <= 1 { return 1 } diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q index acc3e78..a071002 100644 --- a/examples/fibonacci/fibonacci.q +++ b/examples/fibonacci/fibonacci.q @@ -4,7 +4,7 @@ main() { log.number(fibonacci(10)) } -fibonacci(x) { +fibonacci(x Int) -> Int { if x <= 1 { return x } diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q index 9d88215..5cca2d5 100644 --- a/examples/fizzbuzz/fizzbuzz.q +++ b/examples/fizzbuzz/fizzbuzz.q @@ -5,7 +5,7 @@ main() { fizzbuzz(15) } -fizzbuzz(n) { +fizzbuzz(n Int) { x := 1 loop { @@ -26,6 +26,6 @@ fizzbuzz(n) { } } -print(address, length) { +print(address Pointer, length Int) { sys.write(1, address, length) } \ No newline at end of file diff --git a/examples/gcd/gcd.q b/examples/gcd/gcd.q index 03dead2..d952825 100644 --- a/examples/gcd/gcd.q +++ b/examples/gcd/gcd.q @@ -4,7 +4,7 @@ main() { log.number(gcd(1071, 462)) } -gcd(a, b) { +gcd(a Int, b Int) -> Int { loop { switch { a == b { return a } diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 2371295..1d9220d 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -4,6 +4,6 @@ main() { print("Hello\n", 6) } -print(address, length) { +print(address Pointer, length Int) { sys.write(1, address, length) } \ No newline at end of file diff --git a/examples/prime/prime.q b/examples/prime/prime.q index a117bdc..836c831 100644 --- a/examples/prime/prime.q +++ b/examples/prime/prime.q @@ -22,7 +22,7 @@ main() { } } -isPrime(x) { +isPrime(x Int) -> Int { if x == 2 { return 1 } diff --git a/lib/log/number.q b/lib/log/number.q index 86be427..93b7388 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -1,7 +1,7 @@ import mem import sys -number(x) { +number(x Int) { length := 20 buffer := mem.alloc(length) address, count := itoa(x, buffer, length) @@ -9,7 +9,7 @@ number(x) { mem.free(buffer, length) } -itoa(x, buffer, length) { +itoa(x Int, buffer Pointer, length Int) -> (Pointer, Int) { end := buffer + length tmp := end digit := 0 diff --git a/lib/mem/alloc.q b/lib/mem/alloc.q index 3a6347c..747290a 100644 --- a/lib/mem/alloc.q +++ b/lib/mem/alloc.q @@ -1,9 +1,9 @@ import sys -alloc(length) { +alloc(length Int) -> Pointer { return sys.mmap(0, length, 0x1|0x2, 0x02|0x20|0x100) } -free(address, length) { +free(address Pointer, length Int) -> Int { return sys.munmap(address, length) } \ No newline at end of file diff --git a/lib/sys/linux.q b/lib/sys/linux.q index d582d1c..e6d84c2 100644 --- a/lib/sys/linux.q +++ b/lib/sys/linux.q @@ -1,71 +1,71 @@ -read(fd, address, length) { +read(fd Int, address Pointer, length Int) -> Int { return syscall(0, fd, address, length) } -write(fd, address, length) { +write(fd Int, address Pointer, length Int) -> Int { return syscall(1, fd, address, length) } -open(file, flags, mode) { +open(file Pointer, flags Int, mode Int) -> Int { return syscall(2, file, flags, mode) } -close(fd) { +close(fd Int) -> Int { return syscall(3, fd) } -mmap(address, length, protection, flags) { +mmap(address Int, length Int, protection Int, flags Int) -> Int { return syscall(9, address, length, protection, flags) } -munmap(address, length) { +munmap(address Pointer, length Int) -> Int { return syscall(11, address, length) } -clone(flags, stack) { +clone(flags Int, stack Pointer) -> ThreadID { return syscall(56, flags, stack) } -exit(code) { - syscall(60, code) +exit(status Int) { + syscall(60, status) } -socket(family, type, protocol) { +socket(family Int, type Int, protocol Int) -> Int { return syscall(41, family, type, protocol) } -accept(fd, address, length) { +accept(fd Int, address Pointer, length Int) -> Int { return syscall(43, fd, address, length) } -bind(fd, address, length) { +bind(fd Int, address Pointer, length Int) -> Int { return syscall(49, fd, address, length) } -listen(fd, backlog) { +listen(fd Int, backlog Int) -> Int { return syscall(50, fd, backlog) } -getcwd(buffer, length) { +getcwd(buffer Pointer, length Int) -> Int { return syscall(79, buffer, length) } -chdir(path) { +chdir(path Pointer) -> Int { return syscall(80, path) } -rename(old, new) { +rename(old Pointer, new Pointer) -> Int { return syscall(82, old, new) } -mkdir(path, mode) { +mkdir(path Pointer, mode Int) -> Int { return syscall(83, path, mode) } -rmdir(path) { +rmdir(path Pointer) -> Int { return syscall(84, path) } -unlink(file) { +unlink(file Pointer) -> Int { return syscall(87, file) } diff --git a/src/build/errors/Base.go b/src/build/errors/Base.go index a019d58..136c9bf 100644 --- a/src/build/errors/Base.go +++ b/src/build/errors/Base.go @@ -1,5 +1,26 @@ package errors +var ( + EmptySwitch = &Base{"Empty switch"} + ExpectedFunctionName = &Base{"Expected function name"} + ExpectedFunctionParameters = &Base{"Expected function parameters"} + ExpectedFunctionDefinition = &Base{"Expected function definition"} + ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} + InvalidNumber = &Base{"Invalid number"} + InvalidExpression = &Base{"Invalid expression"} + InvalidRune = &Base{"Invalid rune"} + InvalidStatement = &Base{"Invalid statement"} + MissingBlockStart = &Base{"Missing '{'"} + MissingBlockEnd = &Base{"Missing '}'"} + MissingExpression = &Base{"Missing expression"} + MissingGroupStart = &Base{"Missing '('"} + MissingGroupEnd = &Base{"Missing ')'"} + MissingMainFunction = &Base{"Missing main function"} + MissingOperand = &Base{"Missing operand"} + MissingType = &Base{"Missing type"} + NotImplemented = &Base{"Not implemented"} +) + // Base is the base class for errors that have no parameters. type Base struct { Message string diff --git a/src/build/errors/CompileErrors.go b/src/build/errors/CompileErrors.go deleted file mode 100644 index c0e14cb..0000000 --- a/src/build/errors/CompileErrors.go +++ /dev/null @@ -1,14 +0,0 @@ -package errors - -var ( - EmptySwitch = &Base{"Empty switch"} - ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} - InvalidNumber = &Base{"Invalid number"} - InvalidExpression = &Base{"Invalid expression"} - InvalidRune = &Base{"Invalid rune"} - InvalidStatement = &Base{"Invalid statement"} - MissingExpression = &Base{"Missing expression"} - MissingMainFunction = &Base{"Missing main function"} - MissingOperand = &Base{"Missing operand"} - NotImplemented = &Base{"Not implemented"} -) diff --git a/src/build/errors/ScanErrors.go b/src/build/errors/ScanErrors.go deleted file mode 100644 index 7fe07f2..0000000 --- a/src/build/errors/ScanErrors.go +++ /dev/null @@ -1,11 +0,0 @@ -package errors - -var ( - MissingBlockStart = &Base{"Missing '{'"} - MissingBlockEnd = &Base{"Missing '}'"} - MissingGroupStart = &Base{"Missing '('"} - MissingGroupEnd = &Base{"Missing ')'"} - ExpectedFunctionName = &Base{"Expected function name"} - ExpectedFunctionParameters = &Base{"Expected function parameters"} - ExpectedFunctionDefinition = &Base{"Expected function definition"} -) diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index 0f21732..59260bd 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -1,6 +1,7 @@ package scanner import ( + "fmt" "os" "path/filepath" @@ -153,8 +154,20 @@ func (s *Scanner) scanFile(path string, pkg string) error { return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) } + // Return type + if i < len(tokens) && tokens[i].Kind == token.ReturnType { + for i < len(tokens) && tokens[i].Kind != token.BlockStart { + i++ + } + } + // Function definition for i < len(tokens) { + if tokens[i].Kind == token.ReturnType { + i++ + continue + } + if tokens[i].Kind == token.BlockStart { blockLevel++ i++ @@ -207,17 +220,18 @@ func (s *Scanner) scanFile(path string, pkg string) error { name := tokens[nameStart].Text(contents) body := tokens[bodyStart:i] - function := core.NewFunction(pkg, name, file, body) parameters := tokens[paramsStart:paramsEnd] count := 0 err := expression.EachParameter(parameters, func(tokens token.List) error { - if len(tokens) != 1 { - return errors.New(errors.NotImplemented, file, tokens[0].Position) + if len(tokens) < 2 { + return errors.New(errors.MissingType, file, tokens[0].End()) } name := tokens[0].Text(contents) + dataType := tokens[1].Text(contents) + fmt.Println(dataType) register := x64.CallRegisters[count] uses := token.Count(function.Body, contents, token.Identifier, name) diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index fa97224..e0c23f8 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -18,6 +18,7 @@ const ( BlockEnd // } ArrayStart // [ ArrayEnd // ] + ReturnType // -> _operators // Add // + Sub // - diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 85d3eba..2e15e25 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -30,9 +30,17 @@ func Tokenize(buffer []byte) List { if len(tokens) == 0 || tokens[len(tokens)-1].IsOperator() || tokens[len(tokens)-1].IsExpressionStart() || tokens[len(tokens)-1].IsKeyword() { tokens = append(tokens, Token{Kind: Negate, Position: i, Length: 1}) } else { - if i+1 < Position(len(buffer)) && buffer[i+1] == '=' { - tokens = append(tokens, Token{Kind: SubAssign, Position: i, Length: 2}) - i++ + if i+1 < Position(len(buffer)) { + switch buffer[i+1] { + case '=': + tokens = append(tokens, Token{Kind: SubAssign, Position: i, Length: 2}) + i++ + case '>': + tokens = append(tokens, Token{Kind: ReturnType, Position: i, Length: 2}) + i++ + default: + tokens = append(tokens, Token{Kind: Sub, Position: i, Length: 1}) + } } else { tokens = append(tokens, Token{Kind: Sub, Position: i, Length: 1}) } @@ -212,10 +220,12 @@ func Tokenize(buffer []byte) List { kind = Add case "+=": kind = AddAssign - case "-": - kind = Sub - case "-=": - kind = SubAssign + // case "-": + // kind = Sub + // case "-=": + // kind = SubAssign + // case "->": + // kind = ReturnType case ".": kind = Period case "/": diff --git a/tests/errors/UnknownIdentifier3.q b/tests/errors/UnknownIdentifier3.q index a7387e2..4cc4927 100644 --- a/tests/errors/UnknownIdentifier3.q +++ b/tests/errors/UnknownIdentifier3.q @@ -2,6 +2,6 @@ main() { x := 1 + f(x) } -f(x) { +f(x Int) -> Int { return x } \ No newline at end of file diff --git a/tests/programs/branch.q b/tests/programs/branch.q index 9e6f514..171002b 100644 --- a/tests/programs/branch.q +++ b/tests/programs/branch.q @@ -70,14 +70,14 @@ main() { if x == 0 { sys.exit(0) } - + sys.exit(1) } -inc(x) { +inc(x Int) -> Int { return x + 1 } -dec(x) { +dec(x Int) -> Int { return x - 1 } \ No newline at end of file diff --git a/tests/programs/chained-calls.q b/tests/programs/chained-calls.q index e62a02e..cd3c519 100644 --- a/tests/programs/chained-calls.q +++ b/tests/programs/chained-calls.q @@ -2,6 +2,6 @@ main() { assert f(1) + f(2) + f(3) == 9 } -f(x) { +f(x Int) -> Int { return x + 1 } \ No newline at end of file diff --git a/tests/programs/64-bit.q b/tests/programs/int64.q similarity index 100% rename from tests/programs/64-bit.q rename to tests/programs/int64.q diff --git a/tests/programs/loop-lifetime.q b/tests/programs/loop-lifetime.q index 1262b7d..d30489e 100644 --- a/tests/programs/loop-lifetime.q +++ b/tests/programs/loop-lifetime.q @@ -12,6 +12,6 @@ main() { } } -f(x) { +f(x Int) -> Int { return x } \ No newline at end of file diff --git a/tests/programs/math.q b/tests/programs/math.q index 6fd1712..2de1b1b 100644 --- a/tests/programs/math.q +++ b/tests/programs/math.q @@ -4,10 +4,10 @@ main() { assert result == 10 } -div(x, y) { +div(x Int, y Int) -> Int { return x / y } -div10(x) { +div10(x Int) -> Int { return x / 10 } \ No newline at end of file diff --git a/tests/programs/negation.q b/tests/programs/negation.q index 709f995..089d4d7 100644 --- a/tests/programs/negation.q +++ b/tests/programs/negation.q @@ -5,6 +5,6 @@ main() { assert neg(256) == -256 } -neg(x) { +neg(x Int) -> Int { return -x } \ No newline at end of file diff --git a/tests/programs/nested-calls.q b/tests/programs/nested-calls.q index a23e15c..e555b99 100644 --- a/tests/programs/nested-calls.q +++ b/tests/programs/nested-calls.q @@ -2,6 +2,6 @@ main() { assert f(f(f(1))) == 4 } -f(x) { +f(x Int) -> Int { return x + 1 } \ No newline at end of file diff --git a/tests/programs/op-assign.q b/tests/programs/op-assign.q index 496b3a5..257b6ac 100644 --- a/tests/programs/op-assign.q +++ b/tests/programs/op-assign.q @@ -2,7 +2,7 @@ main() { f(10) } -f(new) { +f(new Int) { old := new new -= 1 assert new != old diff --git a/tests/programs/param-multi.q b/tests/programs/param-multi.q index 1267e94..0e19661 100644 --- a/tests/programs/param-multi.q +++ b/tests/programs/param-multi.q @@ -2,11 +2,11 @@ main() { assert f(1, 2, 3) == 21 } -f(x, y, z) { +f(x Int, y Int, z Int) -> Int { w := g(4, 5, 6) return x + y + z + w } -g(x, y, z) { +g(x Int, y Int, z Int) -> Int { return x + y + z } \ No newline at end of file diff --git a/tests/programs/param-order.q b/tests/programs/param-order.q index a36d6e8..2b8ef0f 100644 --- a/tests/programs/param-order.q +++ b/tests/programs/param-order.q @@ -2,11 +2,11 @@ main() { f(1, 2, 3, 4, 5, 6) } -f(a, b, c, d, e, f) { - return g(f, e, d, c, b, a) +f(a Int, b Int, c Int, d Int, e Int, f Int) { + g(f, e, d, c, b, a) } -g(a, b, c, d, e, f) { +g(a Int, b Int, c Int, d Int, e Int, f Int) { assert a == 6 assert b == 5 assert c == 4 diff --git a/tests/programs/param.q b/tests/programs/param.q index 11ee501..5e1628d 100644 --- a/tests/programs/param.q +++ b/tests/programs/param.q @@ -2,11 +2,11 @@ main() { assert f(1) == 3 } -f(x) { +f(x Int) -> Int { y := g() return x + y } -g() { +g() -> Int { return 2 } \ No newline at end of file diff --git a/tests/programs/return-multi.q b/tests/programs/return-multi.q index 47e72be..be81c42 100644 --- a/tests/programs/return-multi.q +++ b/tests/programs/return-multi.q @@ -15,14 +15,14 @@ main() { assert i == 1 + 4 } -reverse2(a, b) { +reverse2(a Int, b Int) -> (Int, Int) { return b, a } -reverse3(a, b, c) { +reverse3(a Int, b Int, c Int) -> (Int, Int, Int) { return c, b, a } -mix4(a, b, c, d) { +mix4(a Int, b Int, c Int, d Int) -> (Int, Int, Int, Int) { return d + a, c + b, b + c, a + d } \ No newline at end of file diff --git a/tests/programs/return.q b/tests/programs/return.q index f5e13d5..36095bc 100644 --- a/tests/programs/return.q +++ b/tests/programs/return.q @@ -2,10 +2,10 @@ main() { assert f(2) == 6 } -f(x) { +f(x Int) -> Int { return x + 1 + g(x) } -g(x) { +g(x Int) -> Int { return x + 1 } \ No newline at end of file diff --git a/tests/programs/reuse.q b/tests/programs/reuse.q index 010f747..4658edb 100644 --- a/tests/programs/reuse.q +++ b/tests/programs/reuse.q @@ -2,6 +2,6 @@ main() { assert f(1) == 3 } -f(x) { +f(x Int) -> Int { return x + 1 + x } \ No newline at end of file diff --git a/tests/programs/square-sum.q b/tests/programs/square-sum.q index 04b1237..a9c0850 100644 --- a/tests/programs/square-sum.q +++ b/tests/programs/square-sum.q @@ -2,6 +2,6 @@ main() { assert f(2, 3) == 25 } -f(x, y) { +f(x Int, y Int) -> Int { return (x + y) * (x + y) } \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 39c5677..7e002a7 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -40,7 +40,7 @@ var programs = []struct { {"modulo", "", "", 0}, {"modulo-assign", "", "", 0}, {"div-split", "", "", 0}, - {"64-bit", "", "", 0}, + {"int64", "", "", 0}, {"negative", "", "", 0}, {"negation", "", "", 0}, {"square-sum", "", "", 0}, From 83661c5e7a400389593a168ac22aed5b1c68eeac Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 5 Aug 2024 18:47:24 +0200 Subject: [PATCH 0476/1012] Added types --- examples/collatz/collatz.q | 2 +- examples/factorial/factorial.q | 2 +- examples/fibonacci/fibonacci.q | 2 +- examples/fizzbuzz/fizzbuzz.q | 4 +-- examples/gcd/gcd.q | 2 +- examples/hello/hello.q | 2 +- examples/prime/prime.q | 2 +- lib/log/number.q | 4 +-- lib/mem/alloc.q | 4 +-- lib/sys/linux.q | 38 ++++++++++++++-------------- src/build/errors/Base.go | 21 +++++++++++++++ src/build/errors/CompileErrors.go | 14 ---------- src/build/errors/ScanErrors.go | 11 -------- src/build/scanner/scanFile.go | 20 ++++++++++++--- src/build/token/Kind.go | 1 + src/build/token/Tokenize.go | 24 +++++++++++++----- tests/errors/UnknownIdentifier3.q | 2 +- tests/programs/branch.q | 6 ++--- tests/programs/chained-calls.q | 2 +- tests/programs/{64-bit.q => int64.q} | 0 tests/programs/loop-lifetime.q | 2 +- tests/programs/math.q | 4 +-- tests/programs/negation.q | 2 +- tests/programs/nested-calls.q | 2 +- tests/programs/op-assign.q | 2 +- tests/programs/param-multi.q | 4 +-- tests/programs/param-order.q | 6 ++--- tests/programs/param.q | 4 +-- tests/programs/return-multi.q | 6 ++--- tests/programs/return.q | 4 +-- tests/programs/reuse.q | 2 +- tests/programs/square-sum.q | 2 +- tests/programs_test.go | 2 +- 33 files changed, 113 insertions(+), 92 deletions(-) delete mode 100644 src/build/errors/CompileErrors.go delete mode 100644 src/build/errors/ScanErrors.go rename tests/programs/{64-bit.q => int64.q} (100%) diff --git a/examples/collatz/collatz.q b/examples/collatz/collatz.q index 32507a4..f7d9037 100644 --- a/examples/collatz/collatz.q +++ b/examples/collatz/collatz.q @@ -5,7 +5,7 @@ main() { collatz(12) } -collatz(x) { +collatz(x Int) { loop { if x & 1 == 0 { x /= 2 diff --git a/examples/factorial/factorial.q b/examples/factorial/factorial.q index dab665c..885fc2f 100644 --- a/examples/factorial/factorial.q +++ b/examples/factorial/factorial.q @@ -4,7 +4,7 @@ main() { log.number(factorial(5)) } -factorial(x) { +factorial(x Int) -> Int { if x <= 1 { return 1 } diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q index acc3e78..a071002 100644 --- a/examples/fibonacci/fibonacci.q +++ b/examples/fibonacci/fibonacci.q @@ -4,7 +4,7 @@ main() { log.number(fibonacci(10)) } -fibonacci(x) { +fibonacci(x Int) -> Int { if x <= 1 { return x } diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q index 9d88215..5cca2d5 100644 --- a/examples/fizzbuzz/fizzbuzz.q +++ b/examples/fizzbuzz/fizzbuzz.q @@ -5,7 +5,7 @@ main() { fizzbuzz(15) } -fizzbuzz(n) { +fizzbuzz(n Int) { x := 1 loop { @@ -26,6 +26,6 @@ fizzbuzz(n) { } } -print(address, length) { +print(address Pointer, length Int) { sys.write(1, address, length) } \ No newline at end of file diff --git a/examples/gcd/gcd.q b/examples/gcd/gcd.q index 03dead2..d952825 100644 --- a/examples/gcd/gcd.q +++ b/examples/gcd/gcd.q @@ -4,7 +4,7 @@ main() { log.number(gcd(1071, 462)) } -gcd(a, b) { +gcd(a Int, b Int) -> Int { loop { switch { a == b { return a } diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 2371295..1d9220d 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -4,6 +4,6 @@ main() { print("Hello\n", 6) } -print(address, length) { +print(address Pointer, length Int) { sys.write(1, address, length) } \ No newline at end of file diff --git a/examples/prime/prime.q b/examples/prime/prime.q index a117bdc..836c831 100644 --- a/examples/prime/prime.q +++ b/examples/prime/prime.q @@ -22,7 +22,7 @@ main() { } } -isPrime(x) { +isPrime(x Int) -> Int { if x == 2 { return 1 } diff --git a/lib/log/number.q b/lib/log/number.q index 86be427..93b7388 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -1,7 +1,7 @@ import mem import sys -number(x) { +number(x Int) { length := 20 buffer := mem.alloc(length) address, count := itoa(x, buffer, length) @@ -9,7 +9,7 @@ number(x) { mem.free(buffer, length) } -itoa(x, buffer, length) { +itoa(x Int, buffer Pointer, length Int) -> (Pointer, Int) { end := buffer + length tmp := end digit := 0 diff --git a/lib/mem/alloc.q b/lib/mem/alloc.q index 3a6347c..747290a 100644 --- a/lib/mem/alloc.q +++ b/lib/mem/alloc.q @@ -1,9 +1,9 @@ import sys -alloc(length) { +alloc(length Int) -> Pointer { return sys.mmap(0, length, 0x1|0x2, 0x02|0x20|0x100) } -free(address, length) { +free(address Pointer, length Int) -> Int { return sys.munmap(address, length) } \ No newline at end of file diff --git a/lib/sys/linux.q b/lib/sys/linux.q index d582d1c..e6d84c2 100644 --- a/lib/sys/linux.q +++ b/lib/sys/linux.q @@ -1,71 +1,71 @@ -read(fd, address, length) { +read(fd Int, address Pointer, length Int) -> Int { return syscall(0, fd, address, length) } -write(fd, address, length) { +write(fd Int, address Pointer, length Int) -> Int { return syscall(1, fd, address, length) } -open(file, flags, mode) { +open(file Pointer, flags Int, mode Int) -> Int { return syscall(2, file, flags, mode) } -close(fd) { +close(fd Int) -> Int { return syscall(3, fd) } -mmap(address, length, protection, flags) { +mmap(address Int, length Int, protection Int, flags Int) -> Int { return syscall(9, address, length, protection, flags) } -munmap(address, length) { +munmap(address Pointer, length Int) -> Int { return syscall(11, address, length) } -clone(flags, stack) { +clone(flags Int, stack Pointer) -> ThreadID { return syscall(56, flags, stack) } -exit(code) { - syscall(60, code) +exit(status Int) { + syscall(60, status) } -socket(family, type, protocol) { +socket(family Int, type Int, protocol Int) -> Int { return syscall(41, family, type, protocol) } -accept(fd, address, length) { +accept(fd Int, address Pointer, length Int) -> Int { return syscall(43, fd, address, length) } -bind(fd, address, length) { +bind(fd Int, address Pointer, length Int) -> Int { return syscall(49, fd, address, length) } -listen(fd, backlog) { +listen(fd Int, backlog Int) -> Int { return syscall(50, fd, backlog) } -getcwd(buffer, length) { +getcwd(buffer Pointer, length Int) -> Int { return syscall(79, buffer, length) } -chdir(path) { +chdir(path Pointer) -> Int { return syscall(80, path) } -rename(old, new) { +rename(old Pointer, new Pointer) -> Int { return syscall(82, old, new) } -mkdir(path, mode) { +mkdir(path Pointer, mode Int) -> Int { return syscall(83, path, mode) } -rmdir(path) { +rmdir(path Pointer) -> Int { return syscall(84, path) } -unlink(file) { +unlink(file Pointer) -> Int { return syscall(87, file) } diff --git a/src/build/errors/Base.go b/src/build/errors/Base.go index a019d58..136c9bf 100644 --- a/src/build/errors/Base.go +++ b/src/build/errors/Base.go @@ -1,5 +1,26 @@ package errors +var ( + EmptySwitch = &Base{"Empty switch"} + ExpectedFunctionName = &Base{"Expected function name"} + ExpectedFunctionParameters = &Base{"Expected function parameters"} + ExpectedFunctionDefinition = &Base{"Expected function definition"} + ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} + InvalidNumber = &Base{"Invalid number"} + InvalidExpression = &Base{"Invalid expression"} + InvalidRune = &Base{"Invalid rune"} + InvalidStatement = &Base{"Invalid statement"} + MissingBlockStart = &Base{"Missing '{'"} + MissingBlockEnd = &Base{"Missing '}'"} + MissingExpression = &Base{"Missing expression"} + MissingGroupStart = &Base{"Missing '('"} + MissingGroupEnd = &Base{"Missing ')'"} + MissingMainFunction = &Base{"Missing main function"} + MissingOperand = &Base{"Missing operand"} + MissingType = &Base{"Missing type"} + NotImplemented = &Base{"Not implemented"} +) + // Base is the base class for errors that have no parameters. type Base struct { Message string diff --git a/src/build/errors/CompileErrors.go b/src/build/errors/CompileErrors.go deleted file mode 100644 index c0e14cb..0000000 --- a/src/build/errors/CompileErrors.go +++ /dev/null @@ -1,14 +0,0 @@ -package errors - -var ( - EmptySwitch = &Base{"Empty switch"} - ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} - InvalidNumber = &Base{"Invalid number"} - InvalidExpression = &Base{"Invalid expression"} - InvalidRune = &Base{"Invalid rune"} - InvalidStatement = &Base{"Invalid statement"} - MissingExpression = &Base{"Missing expression"} - MissingMainFunction = &Base{"Missing main function"} - MissingOperand = &Base{"Missing operand"} - NotImplemented = &Base{"Not implemented"} -) diff --git a/src/build/errors/ScanErrors.go b/src/build/errors/ScanErrors.go deleted file mode 100644 index 7fe07f2..0000000 --- a/src/build/errors/ScanErrors.go +++ /dev/null @@ -1,11 +0,0 @@ -package errors - -var ( - MissingBlockStart = &Base{"Missing '{'"} - MissingBlockEnd = &Base{"Missing '}'"} - MissingGroupStart = &Base{"Missing '('"} - MissingGroupEnd = &Base{"Missing ')'"} - ExpectedFunctionName = &Base{"Expected function name"} - ExpectedFunctionParameters = &Base{"Expected function parameters"} - ExpectedFunctionDefinition = &Base{"Expected function definition"} -) diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index 0f21732..59260bd 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -1,6 +1,7 @@ package scanner import ( + "fmt" "os" "path/filepath" @@ -153,8 +154,20 @@ func (s *Scanner) scanFile(path string, pkg string) error { return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) } + // Return type + if i < len(tokens) && tokens[i].Kind == token.ReturnType { + for i < len(tokens) && tokens[i].Kind != token.BlockStart { + i++ + } + } + // Function definition for i < len(tokens) { + if tokens[i].Kind == token.ReturnType { + i++ + continue + } + if tokens[i].Kind == token.BlockStart { blockLevel++ i++ @@ -207,17 +220,18 @@ func (s *Scanner) scanFile(path string, pkg string) error { name := tokens[nameStart].Text(contents) body := tokens[bodyStart:i] - function := core.NewFunction(pkg, name, file, body) parameters := tokens[paramsStart:paramsEnd] count := 0 err := expression.EachParameter(parameters, func(tokens token.List) error { - if len(tokens) != 1 { - return errors.New(errors.NotImplemented, file, tokens[0].Position) + if len(tokens) < 2 { + return errors.New(errors.MissingType, file, tokens[0].End()) } name := tokens[0].Text(contents) + dataType := tokens[1].Text(contents) + fmt.Println(dataType) register := x64.CallRegisters[count] uses := token.Count(function.Body, contents, token.Identifier, name) diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index fa97224..e0c23f8 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -18,6 +18,7 @@ const ( BlockEnd // } ArrayStart // [ ArrayEnd // ] + ReturnType // -> _operators // Add // + Sub // - diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 85d3eba..2e15e25 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -30,9 +30,17 @@ func Tokenize(buffer []byte) List { if len(tokens) == 0 || tokens[len(tokens)-1].IsOperator() || tokens[len(tokens)-1].IsExpressionStart() || tokens[len(tokens)-1].IsKeyword() { tokens = append(tokens, Token{Kind: Negate, Position: i, Length: 1}) } else { - if i+1 < Position(len(buffer)) && buffer[i+1] == '=' { - tokens = append(tokens, Token{Kind: SubAssign, Position: i, Length: 2}) - i++ + if i+1 < Position(len(buffer)) { + switch buffer[i+1] { + case '=': + tokens = append(tokens, Token{Kind: SubAssign, Position: i, Length: 2}) + i++ + case '>': + tokens = append(tokens, Token{Kind: ReturnType, Position: i, Length: 2}) + i++ + default: + tokens = append(tokens, Token{Kind: Sub, Position: i, Length: 1}) + } } else { tokens = append(tokens, Token{Kind: Sub, Position: i, Length: 1}) } @@ -212,10 +220,12 @@ func Tokenize(buffer []byte) List { kind = Add case "+=": kind = AddAssign - case "-": - kind = Sub - case "-=": - kind = SubAssign + // case "-": + // kind = Sub + // case "-=": + // kind = SubAssign + // case "->": + // kind = ReturnType case ".": kind = Period case "/": diff --git a/tests/errors/UnknownIdentifier3.q b/tests/errors/UnknownIdentifier3.q index a7387e2..4cc4927 100644 --- a/tests/errors/UnknownIdentifier3.q +++ b/tests/errors/UnknownIdentifier3.q @@ -2,6 +2,6 @@ main() { x := 1 + f(x) } -f(x) { +f(x Int) -> Int { return x } \ No newline at end of file diff --git a/tests/programs/branch.q b/tests/programs/branch.q index 9e6f514..171002b 100644 --- a/tests/programs/branch.q +++ b/tests/programs/branch.q @@ -70,14 +70,14 @@ main() { if x == 0 { sys.exit(0) } - + sys.exit(1) } -inc(x) { +inc(x Int) -> Int { return x + 1 } -dec(x) { +dec(x Int) -> Int { return x - 1 } \ No newline at end of file diff --git a/tests/programs/chained-calls.q b/tests/programs/chained-calls.q index e62a02e..cd3c519 100644 --- a/tests/programs/chained-calls.q +++ b/tests/programs/chained-calls.q @@ -2,6 +2,6 @@ main() { assert f(1) + f(2) + f(3) == 9 } -f(x) { +f(x Int) -> Int { return x + 1 } \ No newline at end of file diff --git a/tests/programs/64-bit.q b/tests/programs/int64.q similarity index 100% rename from tests/programs/64-bit.q rename to tests/programs/int64.q diff --git a/tests/programs/loop-lifetime.q b/tests/programs/loop-lifetime.q index 1262b7d..d30489e 100644 --- a/tests/programs/loop-lifetime.q +++ b/tests/programs/loop-lifetime.q @@ -12,6 +12,6 @@ main() { } } -f(x) { +f(x Int) -> Int { return x } \ No newline at end of file diff --git a/tests/programs/math.q b/tests/programs/math.q index 6fd1712..2de1b1b 100644 --- a/tests/programs/math.q +++ b/tests/programs/math.q @@ -4,10 +4,10 @@ main() { assert result == 10 } -div(x, y) { +div(x Int, y Int) -> Int { return x / y } -div10(x) { +div10(x Int) -> Int { return x / 10 } \ No newline at end of file diff --git a/tests/programs/negation.q b/tests/programs/negation.q index 709f995..089d4d7 100644 --- a/tests/programs/negation.q +++ b/tests/programs/negation.q @@ -5,6 +5,6 @@ main() { assert neg(256) == -256 } -neg(x) { +neg(x Int) -> Int { return -x } \ No newline at end of file diff --git a/tests/programs/nested-calls.q b/tests/programs/nested-calls.q index a23e15c..e555b99 100644 --- a/tests/programs/nested-calls.q +++ b/tests/programs/nested-calls.q @@ -2,6 +2,6 @@ main() { assert f(f(f(1))) == 4 } -f(x) { +f(x Int) -> Int { return x + 1 } \ No newline at end of file diff --git a/tests/programs/op-assign.q b/tests/programs/op-assign.q index 496b3a5..257b6ac 100644 --- a/tests/programs/op-assign.q +++ b/tests/programs/op-assign.q @@ -2,7 +2,7 @@ main() { f(10) } -f(new) { +f(new Int) { old := new new -= 1 assert new != old diff --git a/tests/programs/param-multi.q b/tests/programs/param-multi.q index 1267e94..0e19661 100644 --- a/tests/programs/param-multi.q +++ b/tests/programs/param-multi.q @@ -2,11 +2,11 @@ main() { assert f(1, 2, 3) == 21 } -f(x, y, z) { +f(x Int, y Int, z Int) -> Int { w := g(4, 5, 6) return x + y + z + w } -g(x, y, z) { +g(x Int, y Int, z Int) -> Int { return x + y + z } \ No newline at end of file diff --git a/tests/programs/param-order.q b/tests/programs/param-order.q index a36d6e8..2b8ef0f 100644 --- a/tests/programs/param-order.q +++ b/tests/programs/param-order.q @@ -2,11 +2,11 @@ main() { f(1, 2, 3, 4, 5, 6) } -f(a, b, c, d, e, f) { - return g(f, e, d, c, b, a) +f(a Int, b Int, c Int, d Int, e Int, f Int) { + g(f, e, d, c, b, a) } -g(a, b, c, d, e, f) { +g(a Int, b Int, c Int, d Int, e Int, f Int) { assert a == 6 assert b == 5 assert c == 4 diff --git a/tests/programs/param.q b/tests/programs/param.q index 11ee501..5e1628d 100644 --- a/tests/programs/param.q +++ b/tests/programs/param.q @@ -2,11 +2,11 @@ main() { assert f(1) == 3 } -f(x) { +f(x Int) -> Int { y := g() return x + y } -g() { +g() -> Int { return 2 } \ No newline at end of file diff --git a/tests/programs/return-multi.q b/tests/programs/return-multi.q index 47e72be..be81c42 100644 --- a/tests/programs/return-multi.q +++ b/tests/programs/return-multi.q @@ -15,14 +15,14 @@ main() { assert i == 1 + 4 } -reverse2(a, b) { +reverse2(a Int, b Int) -> (Int, Int) { return b, a } -reverse3(a, b, c) { +reverse3(a Int, b Int, c Int) -> (Int, Int, Int) { return c, b, a } -mix4(a, b, c, d) { +mix4(a Int, b Int, c Int, d Int) -> (Int, Int, Int, Int) { return d + a, c + b, b + c, a + d } \ No newline at end of file diff --git a/tests/programs/return.q b/tests/programs/return.q index f5e13d5..36095bc 100644 --- a/tests/programs/return.q +++ b/tests/programs/return.q @@ -2,10 +2,10 @@ main() { assert f(2) == 6 } -f(x) { +f(x Int) -> Int { return x + 1 + g(x) } -g(x) { +g(x Int) -> Int { return x + 1 } \ No newline at end of file diff --git a/tests/programs/reuse.q b/tests/programs/reuse.q index 010f747..4658edb 100644 --- a/tests/programs/reuse.q +++ b/tests/programs/reuse.q @@ -2,6 +2,6 @@ main() { assert f(1) == 3 } -f(x) { +f(x Int) -> Int { return x + 1 + x } \ No newline at end of file diff --git a/tests/programs/square-sum.q b/tests/programs/square-sum.q index 04b1237..a9c0850 100644 --- a/tests/programs/square-sum.q +++ b/tests/programs/square-sum.q @@ -2,6 +2,6 @@ main() { assert f(2, 3) == 25 } -f(x, y) { +f(x Int, y Int) -> Int { return (x + y) * (x + y) } \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 39c5677..7e002a7 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -40,7 +40,7 @@ var programs = []struct { {"modulo", "", "", 0}, {"modulo-assign", "", "", 0}, {"div-split", "", "", 0}, - {"64-bit", "", "", 0}, + {"int64", "", "", 0}, {"negative", "", "", 0}, {"negation", "", "", 0}, {"square-sum", "", "", 0}, From 7c5e0ae3a13944547a7b86c4329fc27d3ff58d84 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 5 Aug 2024 19:33:23 +0200 Subject: [PATCH 0477/1012] Updated todo list --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2e5e22c..1eafca8 100644 --- a/README.md +++ b/README.md @@ -116,9 +116,11 @@ This is what generates expressions from tokens. - [x] Hexadecimal, octal and binary literals - [x] Escape sequences - [x] Multiple return values -- [ ] Data structures - [ ] Type system -- [ ] Type operator: `|` (`User | Error`) +- [ ] Type operator `?` +- [ ] Data structures +- [ ] Slices +- [ ] Floating-point arithmetic - [ ] Error handling - [ ] Threading library - [ ] Self-hosted compiler From 67a37cdb26f86306fa623723ce65d2a46286cd1a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 5 Aug 2024 19:33:23 +0200 Subject: [PATCH 0478/1012] Updated todo list --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2e5e22c..1eafca8 100644 --- a/README.md +++ b/README.md @@ -116,9 +116,11 @@ This is what generates expressions from tokens. - [x] Hexadecimal, octal and binary literals - [x] Escape sequences - [x] Multiple return values -- [ ] Data structures - [ ] Type system -- [ ] Type operator: `|` (`User | Error`) +- [ ] Type operator `?` +- [ ] Data structures +- [ ] Slices +- [ ] Floating-point arithmetic - [ ] Error handling - [ ] Threading library - [ ] Self-hosted compiler From baa2463b4ba21f5ec08beacae1491eb721adc3d4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 6 Aug 2024 15:12:07 +0200 Subject: [PATCH 0479/1012] Added types --- src/build/core/Compare.go | 4 ++-- src/build/core/CompileASTNode.go | 3 ++- src/build/core/CompileAssignArray.go | 3 ++- src/build/core/CompileAssignDivision.go | 2 +- src/build/core/CompileCall.go | 16 +++++++------ src/build/core/CompileDefinition.go | 18 ++++++++++++--- src/build/core/Evaluate.go | 9 ++++---- src/build/core/Execute.go | 4 ++-- src/build/core/ExecuteLeaf.go | 3 ++- src/build/core/ExpressionToMemory.go | 13 ++++++----- src/build/core/ExpressionToRegister.go | 29 ++++++++++++++---------- src/build/core/ExpressionsToRegisters.go | 2 +- src/build/core/Function.go | 22 ++++++++++-------- src/build/core/TokenToRegister.go | 15 ++++++------ src/build/errors/Base.go | 1 + src/build/scanner/scanFile.go | 20 +++++++++++++--- src/build/scope/Variable.go | 2 ++ src/build/types/New.go | 8 +++++++ src/build/types/Type.go | 8 +++++++ 19 files changed, 122 insertions(+), 60 deletions(-) create mode 100644 src/build/types/New.go create mode 100644 src/build/types/Type.go diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 0ccfe3c..c884a82 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -25,7 +25,7 @@ func (f *Function) Compare(comparison *expression.Expression) error { } if ast.IsFunctionCall(left) && right.IsLeaf() { - err := f.CompileCall(left) + _, err := f.CompileCall(left) if err != nil { return err @@ -35,7 +35,7 @@ func (f *Function) Compare(comparison *expression.Expression) error { } tmp := f.NewRegister() - err := f.ExpressionToRegister(left, tmp) + _, err := f.ExpressionToRegister(left, tmp) if err != nil { return err diff --git a/src/build/core/CompileASTNode.go b/src/build/core/CompileASTNode.go index 7277ccc..ddbd05a 100644 --- a/src/build/core/CompileASTNode.go +++ b/src/build/core/CompileASTNode.go @@ -17,7 +17,8 @@ func (f *Function) CompileASTNode(node ast.Node) error { case *ast.Call: f.Fold(node.Expression) - return f.CompileCall(node.Expression) + _, err := f.CompileCall(node.Expression) + return err case *ast.Define: f.Fold(node.Expression) diff --git a/src/build/core/CompileAssignArray.go b/src/build/core/CompileAssignArray.go index 167d3e3..8f812d5 100644 --- a/src/build/core/CompileAssignArray.go +++ b/src/build/core/CompileAssignArray.go @@ -33,5 +33,6 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { Length: byte(1), } - return f.ExpressionToMemory(right, memory) + _, err = f.ExpressionToMemory(right, memory) + return err } diff --git a/src/build/core/CompileAssignDivision.go b/src/build/core/CompileAssignDivision.go index 177b008..9a06c61 100644 --- a/src/build/core/CompileAssignDivision.go +++ b/src/build/core/CompileAssignDivision.go @@ -29,7 +29,7 @@ func (f *Function) CompileAssignDivision(node *ast.Assign) error { } dividend := right.Children[0] - dividendRegister, err := f.Evaluate(dividend) + _, dividendRegister, err := f.Evaluate(dividend) if err != nil { return err diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index c04f842..25466db 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -12,12 +12,14 @@ import ( // All call registers must hold the correct parameter values before the function invocation. // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. -func (f *Function) CompileCall(root *expression.Expression) error { +func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { var ( pkg = f.Package nameRoot = root.Children[0] + fn *Function name string fullName string + exists bool ) if nameRoot.IsLeaf() { @@ -32,13 +34,13 @@ func (f *Function) CompileCall(root *expression.Expression) error { if !isSyscall { if pkg != f.File.Package { if f.File.Imports == nil { - return errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) } imp, exists := f.File.Imports[pkg] if !exists { - return errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) } imp.Used = true @@ -49,10 +51,10 @@ func (f *Function) CompileCall(root *expression.Expression) error { tmp.WriteString(".") tmp.WriteString(name) fullName = tmp.String() - _, exists := f.Functions[fullName] + fn, exists = f.Functions[fullName] if !exists { - return errors.New(&errors.UnknownFunction{Name: name}, f.File, nameRoot.Token.Position) + return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameRoot.Token.Position) } } @@ -66,7 +68,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { err := f.ExpressionsToRegisters(parameters, registers) if err != nil { - return err + return fn, err } // TODO: Save all return value registers of the function @@ -104,5 +106,5 @@ func (f *Function) CompileCall(root *expression.Expression) error { } } - return nil + return fn, nil } diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 689f020..aeed43d 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -5,6 +5,7 @@ import ( "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/types" ) // CompileDefinition compiles a variable definition. @@ -19,9 +20,20 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return err } - err = f.ExpressionToRegister(right, variable.Register) + typ, err := f.ExpressionToRegister(right, variable.Register) + + if err != nil { + return err + } + + variable.Type = typ + + if variable.Type == types.Invalid { + return errors.New(errors.UnknownType, f.File, node.Expression.Token.End()) + } + f.AddVariable(variable) - return err + return nil } if !ast.IsFunctionCall(right) { @@ -29,7 +41,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { } count := 0 - err := f.CompileCall(right) + _, err := f.CompileCall(right) if err != nil { return err diff --git a/src/build/core/Evaluate.go b/src/build/core/Evaluate.go index f7d601a..145f3b1 100644 --- a/src/build/core/Evaluate.go +++ b/src/build/core/Evaluate.go @@ -4,21 +4,22 @@ import ( "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/types" ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. -func (f *Function) Evaluate(expr *expression.Expression) (cpu.Register, error) { +func (f *Function) Evaluate(expr *expression.Expression) (types.Type, cpu.Register, error) { if expr.Token.Kind == token.Identifier { name := expr.Token.Text(f.File.Bytes) variable := f.VariableByName(name) if variable.Alive == 1 { f.UseVariable(variable) - return variable.Register, nil + return variable.Type, variable.Register, nil } } tmp := f.NewRegister() - err := f.ExpressionToRegister(expr, tmp) - return tmp, err + typ, err := f.ExpressionToRegister(expr, tmp) + return typ, tmp, err } diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index 76a54bf..e4b7010 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -14,7 +14,7 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * } if ast.IsFunctionCall(value) { - err := f.CompileCall(value) + _, err := f.CompileCall(value) if err != nil { return err @@ -26,7 +26,7 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * tmp := f.NewRegister() defer f.FreeRegister(tmp) - err := f.ExpressionToRegister(value, tmp) + _, err := f.ExpressionToRegister(value, tmp) if err != nil { return err diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index fe78ef6..2254a8a 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -31,7 +31,8 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope case token.String: if operation.Kind == token.Assign { - return f.TokenToRegister(operand, register) + _, err := f.TokenToRegister(operand, register) + return err } } diff --git a/src/build/core/ExpressionToMemory.go b/src/build/core/ExpressionToMemory.go index 781b378..204e17d 100644 --- a/src/build/core/ExpressionToMemory.go +++ b/src/build/core/ExpressionToMemory.go @@ -5,30 +5,31 @@ import ( "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/cli/q/src/build/types" ) // ExpressionToMemory puts the result of an expression into the specified memory address. -func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) error { +func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (types.Type, error) { if node.IsLeaf() && node.Token.IsNumeric() { number, err := f.Number(node.Token) if err != nil { - return err + return types.Invalid, err } size := byte(sizeof.Signed(int64(number))) if size != memory.Length { - return errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) + return types.Invalid, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) } f.MemoryNumber(asm.STORE, memory, number) - return nil + return types.Int, nil } tmp := f.NewRegister() defer f.FreeRegister(tmp) - err := f.ExpressionToRegister(node, tmp) + typ, err := f.ExpressionToRegister(node, tmp) f.MemoryRegister(asm.STORE, memory, tmp) - return err + return typ, err } diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 29a084c..c084351 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -7,15 +7,16 @@ import ( "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/types" ) // ExpressionToRegister puts the result of an expression into the specified register. -func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) error { +func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { f.SaveRegister(register) if node.IsFolded { f.RegisterNumber(asm.MOVE, register, node.Value) - return nil + return types.Int, nil } if node.IsLeaf() { @@ -23,34 +24,38 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } if ast.IsFunctionCall(node) { - err := f.CompileCall(node) + fn, err := f.CompileCall(node) if register != f.CPU.Output[0] { f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) } - return err + if fn == nil || len(fn.ReturnTypes) == 0 { + return types.Invalid, err + } + + return fn.ReturnTypes[0], err } if node.Token.Kind == token.Array { array := f.VariableByName(node.Children[0].Token.Text(f.File.Bytes)) offset, err := f.Number(node.Children[1].Token) f.MemoryRegister(asm.LOAD, asm.Memory{Base: array.Register, Offset: byte(offset), Length: 1}, register) - return err + return types.Int, err } if len(node.Children) == 1 { if !node.Token.IsUnaryOperator() { - return errors.New(errors.MissingOperand, f.File, node.Token.End()) + return types.Invalid, errors.New(errors.MissingOperand, f.File, node.Token.End()) } - err := f.ExpressionToRegister(node.Children[0], register) + typ, err := f.ExpressionToRegister(node.Children[0], register) if err != nil { - return err + return typ, err } - return f.ExecuteRegister(node.Token, register) + return typ, f.ExecuteRegister(node.Token, register) } left := node.Children[0] @@ -61,10 +66,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp register = f.NewRegister() } - err := f.ExpressionToRegister(left, register) + typ, err := f.ExpressionToRegister(left, register) if err != nil { - return err + return types.Invalid, err } err = f.Execute(node.Token, register, right) @@ -74,5 +79,5 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp f.FreeRegister(register) } - return err + return typ, err } diff --git a/src/build/core/ExpressionsToRegisters.go b/src/build/core/ExpressionsToRegisters.go index f3a09fe..982701a 100644 --- a/src/build/core/ExpressionsToRegisters.go +++ b/src/build/core/ExpressionsToRegisters.go @@ -8,7 +8,7 @@ import ( // ExpressionsToRegisters moves multiple expressions into the specified registers. func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { for i := len(expressions) - 1; i >= 0; i-- { - err := f.ExpressionToRegister(expressions[i], registers[i]) + _, err := f.ExpressionToRegister(expressions[i], registers[i]) if err != nil { return err diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 9aeb10a..5f8c25c 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -3,21 +3,25 @@ package core import ( "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/register" + "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/types" ) // Function represents the smallest unit of code. type Function struct { register.Machine - Package string - Name string - UniqueName string - File *fs.File - Body []token.Token - Functions map[string]*Function - Err error - deferred []func() - count counter + Package string + Name string + UniqueName string + File *fs.File + Body token.List + Parameters []*scope.Variable + ReturnTypes []types.Type + Functions map[string]*Function + Err error + deferred []func() + count counter } // counter stores how often a certain statement appeared so we can generate a unique label from it. diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 3335daf..c1d38ff 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -5,35 +5,36 @@ import ( "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/types" ) // TokenToRegister moves a token into a register. // It only works with identifiers, numbers and strings. -func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { +func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types.Type, error) { switch t.Kind { case token.Identifier: name := t.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) + return types.Invalid, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } f.UseVariable(variable) f.SaveRegister(register) f.RegisterRegister(asm.MOVE, register, variable.Register) - return nil + return variable.Type, nil case token.Number, token.Rune: number, err := f.Number(t) if err != nil { - return err + return types.Invalid, err } f.SaveRegister(register) f.RegisterNumber(asm.MOVE, register, number) - return nil + return types.Int, nil case token.String: data := t.Bytes(f.File.Bytes) @@ -41,9 +42,9 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { label := f.AddBytes(data) f.SaveRegister(register) f.RegisterLabel(asm.MOVE, register, label) - return nil + return types.Int, nil default: - return errors.New(errors.InvalidExpression, f.File, t.Position) + return types.Invalid, errors.New(errors.InvalidExpression, f.File, t.Position) } } diff --git a/src/build/errors/Base.go b/src/build/errors/Base.go index 136c9bf..0933e59 100644 --- a/src/build/errors/Base.go +++ b/src/build/errors/Base.go @@ -19,6 +19,7 @@ var ( MissingOperand = &Base{"Missing operand"} MissingType = &Base{"Missing type"} NotImplemented = &Base{"Not implemented"} + UnknownType = &Base{"Unknown type"} ) // Base is the base class for errors that have no parameters. diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index 59260bd..b7174b0 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -1,7 +1,6 @@ package scanner import ( - "fmt" "os" "path/filepath" @@ -13,6 +12,7 @@ import ( "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/types" ) // scanFile scans a single file. @@ -42,6 +42,8 @@ func (s *Scanner) scanFile(path string, pkg string) error { paramsStart = -1 paramsEnd = -1 bodyStart = -1 + typeStart = -1 + typeEnd = -1 ) for { @@ -156,9 +158,13 @@ func (s *Scanner) scanFile(path string, pkg string) error { // Return type if i < len(tokens) && tokens[i].Kind == token.ReturnType { + typeStart = i + 1 + for i < len(tokens) && tokens[i].Kind != token.BlockStart { i++ } + + typeEnd = i } // Function definition @@ -221,6 +227,11 @@ func (s *Scanner) scanFile(path string, pkg string) error { name := tokens[nameStart].Text(contents) body := tokens[bodyStart:i] function := core.NewFunction(pkg, name, file, body) + + if typeStart != -1 { + function.ReturnTypes = append(function.ReturnTypes, types.New(tokens[typeStart:typeEnd])) + } + parameters := tokens[paramsStart:paramsEnd] count := 0 @@ -230,8 +241,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { } name := tokens[0].Text(contents) - dataType := tokens[1].Text(contents) - fmt.Println(dataType) + dataType := types.New(tokens[1:]) register := x64.CallRegisters[count] uses := token.Count(function.Body, contents, token.Identifier, name) @@ -241,10 +251,12 @@ func (s *Scanner) scanFile(path string, pkg string) error { variable := &scope.Variable{ Name: name, + Type: dataType, Register: register, Alive: uses, } + function.Parameters = append(function.Parameters, variable) function.AddVariable(variable) count++ return nil @@ -258,6 +270,8 @@ func (s *Scanner) scanFile(path string, pkg string) error { nameStart = -1 paramsStart = -1 bodyStart = -1 + typeStart = -1 + typeEnd = -1 i++ } } diff --git a/src/build/scope/Variable.go b/src/build/scope/Variable.go index 491bb78..0346ca1 100644 --- a/src/build/scope/Variable.go +++ b/src/build/scope/Variable.go @@ -2,11 +2,13 @@ package scope import ( "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/types" ) // Variable represents a named register. type Variable struct { Name string + Type types.Type Alive uint8 Register cpu.Register } diff --git a/src/build/types/New.go b/src/build/types/New.go new file mode 100644 index 0000000..73e7d23 --- /dev/null +++ b/src/build/types/New.go @@ -0,0 +1,8 @@ +package types + +import "git.akyoto.dev/cli/q/src/build/token" + +// New creates a new type from a list of tokens. +func New(tokens token.List) Type { + return Int +} diff --git a/src/build/types/Type.go b/src/build/types/Type.go new file mode 100644 index 0000000..07340a0 --- /dev/null +++ b/src/build/types/Type.go @@ -0,0 +1,8 @@ +package types + +type Type int + +const ( + Invalid Type = iota // Invalid is an invalid type. + Int +) From 6e848774ed951fe2d594b2a59dcc1b15831c14fb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 6 Aug 2024 15:12:07 +0200 Subject: [PATCH 0480/1012] Added types --- src/build/core/Compare.go | 4 ++-- src/build/core/CompileASTNode.go | 3 ++- src/build/core/CompileAssignArray.go | 3 ++- src/build/core/CompileAssignDivision.go | 2 +- src/build/core/CompileCall.go | 16 +++++++------ src/build/core/CompileDefinition.go | 18 ++++++++++++--- src/build/core/Evaluate.go | 9 ++++---- src/build/core/Execute.go | 4 ++-- src/build/core/ExecuteLeaf.go | 3 ++- src/build/core/ExpressionToMemory.go | 13 ++++++----- src/build/core/ExpressionToRegister.go | 29 ++++++++++++++---------- src/build/core/ExpressionsToRegisters.go | 2 +- src/build/core/Function.go | 22 ++++++++++-------- src/build/core/TokenToRegister.go | 15 ++++++------ src/build/errors/Base.go | 1 + src/build/scanner/scanFile.go | 20 +++++++++++++--- src/build/scope/Variable.go | 2 ++ src/build/types/New.go | 8 +++++++ src/build/types/Type.go | 8 +++++++ 19 files changed, 122 insertions(+), 60 deletions(-) create mode 100644 src/build/types/New.go create mode 100644 src/build/types/Type.go diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index 0ccfe3c..c884a82 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -25,7 +25,7 @@ func (f *Function) Compare(comparison *expression.Expression) error { } if ast.IsFunctionCall(left) && right.IsLeaf() { - err := f.CompileCall(left) + _, err := f.CompileCall(left) if err != nil { return err @@ -35,7 +35,7 @@ func (f *Function) Compare(comparison *expression.Expression) error { } tmp := f.NewRegister() - err := f.ExpressionToRegister(left, tmp) + _, err := f.ExpressionToRegister(left, tmp) if err != nil { return err diff --git a/src/build/core/CompileASTNode.go b/src/build/core/CompileASTNode.go index 7277ccc..ddbd05a 100644 --- a/src/build/core/CompileASTNode.go +++ b/src/build/core/CompileASTNode.go @@ -17,7 +17,8 @@ func (f *Function) CompileASTNode(node ast.Node) error { case *ast.Call: f.Fold(node.Expression) - return f.CompileCall(node.Expression) + _, err := f.CompileCall(node.Expression) + return err case *ast.Define: f.Fold(node.Expression) diff --git a/src/build/core/CompileAssignArray.go b/src/build/core/CompileAssignArray.go index 167d3e3..8f812d5 100644 --- a/src/build/core/CompileAssignArray.go +++ b/src/build/core/CompileAssignArray.go @@ -33,5 +33,6 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { Length: byte(1), } - return f.ExpressionToMemory(right, memory) + _, err = f.ExpressionToMemory(right, memory) + return err } diff --git a/src/build/core/CompileAssignDivision.go b/src/build/core/CompileAssignDivision.go index 177b008..9a06c61 100644 --- a/src/build/core/CompileAssignDivision.go +++ b/src/build/core/CompileAssignDivision.go @@ -29,7 +29,7 @@ func (f *Function) CompileAssignDivision(node *ast.Assign) error { } dividend := right.Children[0] - dividendRegister, err := f.Evaluate(dividend) + _, dividendRegister, err := f.Evaluate(dividend) if err != nil { return err diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index c04f842..25466db 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -12,12 +12,14 @@ import ( // All call registers must hold the correct parameter values before the function invocation. // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. -func (f *Function) CompileCall(root *expression.Expression) error { +func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { var ( pkg = f.Package nameRoot = root.Children[0] + fn *Function name string fullName string + exists bool ) if nameRoot.IsLeaf() { @@ -32,13 +34,13 @@ func (f *Function) CompileCall(root *expression.Expression) error { if !isSyscall { if pkg != f.File.Package { if f.File.Imports == nil { - return errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) } imp, exists := f.File.Imports[pkg] if !exists { - return errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) } imp.Used = true @@ -49,10 +51,10 @@ func (f *Function) CompileCall(root *expression.Expression) error { tmp.WriteString(".") tmp.WriteString(name) fullName = tmp.String() - _, exists := f.Functions[fullName] + fn, exists = f.Functions[fullName] if !exists { - return errors.New(&errors.UnknownFunction{Name: name}, f.File, nameRoot.Token.Position) + return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameRoot.Token.Position) } } @@ -66,7 +68,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { err := f.ExpressionsToRegisters(parameters, registers) if err != nil { - return err + return fn, err } // TODO: Save all return value registers of the function @@ -104,5 +106,5 @@ func (f *Function) CompileCall(root *expression.Expression) error { } } - return nil + return fn, nil } diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index 689f020..aeed43d 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -5,6 +5,7 @@ import ( "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/types" ) // CompileDefinition compiles a variable definition. @@ -19,9 +20,20 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return err } - err = f.ExpressionToRegister(right, variable.Register) + typ, err := f.ExpressionToRegister(right, variable.Register) + + if err != nil { + return err + } + + variable.Type = typ + + if variable.Type == types.Invalid { + return errors.New(errors.UnknownType, f.File, node.Expression.Token.End()) + } + f.AddVariable(variable) - return err + return nil } if !ast.IsFunctionCall(right) { @@ -29,7 +41,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { } count := 0 - err := f.CompileCall(right) + _, err := f.CompileCall(right) if err != nil { return err diff --git a/src/build/core/Evaluate.go b/src/build/core/Evaluate.go index f7d601a..145f3b1 100644 --- a/src/build/core/Evaluate.go +++ b/src/build/core/Evaluate.go @@ -4,21 +4,22 @@ import ( "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/types" ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. -func (f *Function) Evaluate(expr *expression.Expression) (cpu.Register, error) { +func (f *Function) Evaluate(expr *expression.Expression) (types.Type, cpu.Register, error) { if expr.Token.Kind == token.Identifier { name := expr.Token.Text(f.File.Bytes) variable := f.VariableByName(name) if variable.Alive == 1 { f.UseVariable(variable) - return variable.Register, nil + return variable.Type, variable.Register, nil } } tmp := f.NewRegister() - err := f.ExpressionToRegister(expr, tmp) - return tmp, err + typ, err := f.ExpressionToRegister(expr, tmp) + return typ, tmp, err } diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index 76a54bf..e4b7010 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -14,7 +14,7 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * } if ast.IsFunctionCall(value) { - err := f.CompileCall(value) + _, err := f.CompileCall(value) if err != nil { return err @@ -26,7 +26,7 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * tmp := f.NewRegister() defer f.FreeRegister(tmp) - err := f.ExpressionToRegister(value, tmp) + _, err := f.ExpressionToRegister(value, tmp) if err != nil { return err diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index fe78ef6..2254a8a 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -31,7 +31,8 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope case token.String: if operation.Kind == token.Assign { - return f.TokenToRegister(operand, register) + _, err := f.TokenToRegister(operand, register) + return err } } diff --git a/src/build/core/ExpressionToMemory.go b/src/build/core/ExpressionToMemory.go index 781b378..204e17d 100644 --- a/src/build/core/ExpressionToMemory.go +++ b/src/build/core/ExpressionToMemory.go @@ -5,30 +5,31 @@ import ( "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/cli/q/src/build/types" ) // ExpressionToMemory puts the result of an expression into the specified memory address. -func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) error { +func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (types.Type, error) { if node.IsLeaf() && node.Token.IsNumeric() { number, err := f.Number(node.Token) if err != nil { - return err + return types.Invalid, err } size := byte(sizeof.Signed(int64(number))) if size != memory.Length { - return errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) + return types.Invalid, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) } f.MemoryNumber(asm.STORE, memory, number) - return nil + return types.Int, nil } tmp := f.NewRegister() defer f.FreeRegister(tmp) - err := f.ExpressionToRegister(node, tmp) + typ, err := f.ExpressionToRegister(node, tmp) f.MemoryRegister(asm.STORE, memory, tmp) - return err + return typ, err } diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 29a084c..c084351 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -7,15 +7,16 @@ import ( "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/types" ) // ExpressionToRegister puts the result of an expression into the specified register. -func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) error { +func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { f.SaveRegister(register) if node.IsFolded { f.RegisterNumber(asm.MOVE, register, node.Value) - return nil + return types.Int, nil } if node.IsLeaf() { @@ -23,34 +24,38 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } if ast.IsFunctionCall(node) { - err := f.CompileCall(node) + fn, err := f.CompileCall(node) if register != f.CPU.Output[0] { f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) } - return err + if fn == nil || len(fn.ReturnTypes) == 0 { + return types.Invalid, err + } + + return fn.ReturnTypes[0], err } if node.Token.Kind == token.Array { array := f.VariableByName(node.Children[0].Token.Text(f.File.Bytes)) offset, err := f.Number(node.Children[1].Token) f.MemoryRegister(asm.LOAD, asm.Memory{Base: array.Register, Offset: byte(offset), Length: 1}, register) - return err + return types.Int, err } if len(node.Children) == 1 { if !node.Token.IsUnaryOperator() { - return errors.New(errors.MissingOperand, f.File, node.Token.End()) + return types.Invalid, errors.New(errors.MissingOperand, f.File, node.Token.End()) } - err := f.ExpressionToRegister(node.Children[0], register) + typ, err := f.ExpressionToRegister(node.Children[0], register) if err != nil { - return err + return typ, err } - return f.ExecuteRegister(node.Token, register) + return typ, f.ExecuteRegister(node.Token, register) } left := node.Children[0] @@ -61,10 +66,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp register = f.NewRegister() } - err := f.ExpressionToRegister(left, register) + typ, err := f.ExpressionToRegister(left, register) if err != nil { - return err + return types.Invalid, err } err = f.Execute(node.Token, register, right) @@ -74,5 +79,5 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp f.FreeRegister(register) } - return err + return typ, err } diff --git a/src/build/core/ExpressionsToRegisters.go b/src/build/core/ExpressionsToRegisters.go index f3a09fe..982701a 100644 --- a/src/build/core/ExpressionsToRegisters.go +++ b/src/build/core/ExpressionsToRegisters.go @@ -8,7 +8,7 @@ import ( // ExpressionsToRegisters moves multiple expressions into the specified registers. func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { for i := len(expressions) - 1; i >= 0; i-- { - err := f.ExpressionToRegister(expressions[i], registers[i]) + _, err := f.ExpressionToRegister(expressions[i], registers[i]) if err != nil { return err diff --git a/src/build/core/Function.go b/src/build/core/Function.go index 9aeb10a..5f8c25c 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -3,21 +3,25 @@ package core import ( "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/register" + "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/types" ) // Function represents the smallest unit of code. type Function struct { register.Machine - Package string - Name string - UniqueName string - File *fs.File - Body []token.Token - Functions map[string]*Function - Err error - deferred []func() - count counter + Package string + Name string + UniqueName string + File *fs.File + Body token.List + Parameters []*scope.Variable + ReturnTypes []types.Type + Functions map[string]*Function + Err error + deferred []func() + count counter } // counter stores how often a certain statement appeared so we can generate a unique label from it. diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 3335daf..c1d38ff 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -5,35 +5,36 @@ import ( "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/types" ) // TokenToRegister moves a token into a register. // It only works with identifiers, numbers and strings. -func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { +func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types.Type, error) { switch t.Kind { case token.Identifier: name := t.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) + return types.Invalid, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } f.UseVariable(variable) f.SaveRegister(register) f.RegisterRegister(asm.MOVE, register, variable.Register) - return nil + return variable.Type, nil case token.Number, token.Rune: number, err := f.Number(t) if err != nil { - return err + return types.Invalid, err } f.SaveRegister(register) f.RegisterNumber(asm.MOVE, register, number) - return nil + return types.Int, nil case token.String: data := t.Bytes(f.File.Bytes) @@ -41,9 +42,9 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { label := f.AddBytes(data) f.SaveRegister(register) f.RegisterLabel(asm.MOVE, register, label) - return nil + return types.Int, nil default: - return errors.New(errors.InvalidExpression, f.File, t.Position) + return types.Invalid, errors.New(errors.InvalidExpression, f.File, t.Position) } } diff --git a/src/build/errors/Base.go b/src/build/errors/Base.go index 136c9bf..0933e59 100644 --- a/src/build/errors/Base.go +++ b/src/build/errors/Base.go @@ -19,6 +19,7 @@ var ( MissingOperand = &Base{"Missing operand"} MissingType = &Base{"Missing type"} NotImplemented = &Base{"Not implemented"} + UnknownType = &Base{"Unknown type"} ) // Base is the base class for errors that have no parameters. diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index 59260bd..b7174b0 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -1,7 +1,6 @@ package scanner import ( - "fmt" "os" "path/filepath" @@ -13,6 +12,7 @@ import ( "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/build/types" ) // scanFile scans a single file. @@ -42,6 +42,8 @@ func (s *Scanner) scanFile(path string, pkg string) error { paramsStart = -1 paramsEnd = -1 bodyStart = -1 + typeStart = -1 + typeEnd = -1 ) for { @@ -156,9 +158,13 @@ func (s *Scanner) scanFile(path string, pkg string) error { // Return type if i < len(tokens) && tokens[i].Kind == token.ReturnType { + typeStart = i + 1 + for i < len(tokens) && tokens[i].Kind != token.BlockStart { i++ } + + typeEnd = i } // Function definition @@ -221,6 +227,11 @@ func (s *Scanner) scanFile(path string, pkg string) error { name := tokens[nameStart].Text(contents) body := tokens[bodyStart:i] function := core.NewFunction(pkg, name, file, body) + + if typeStart != -1 { + function.ReturnTypes = append(function.ReturnTypes, types.New(tokens[typeStart:typeEnd])) + } + parameters := tokens[paramsStart:paramsEnd] count := 0 @@ -230,8 +241,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { } name := tokens[0].Text(contents) - dataType := tokens[1].Text(contents) - fmt.Println(dataType) + dataType := types.New(tokens[1:]) register := x64.CallRegisters[count] uses := token.Count(function.Body, contents, token.Identifier, name) @@ -241,10 +251,12 @@ func (s *Scanner) scanFile(path string, pkg string) error { variable := &scope.Variable{ Name: name, + Type: dataType, Register: register, Alive: uses, } + function.Parameters = append(function.Parameters, variable) function.AddVariable(variable) count++ return nil @@ -258,6 +270,8 @@ func (s *Scanner) scanFile(path string, pkg string) error { nameStart = -1 paramsStart = -1 bodyStart = -1 + typeStart = -1 + typeEnd = -1 i++ } } diff --git a/src/build/scope/Variable.go b/src/build/scope/Variable.go index 491bb78..0346ca1 100644 --- a/src/build/scope/Variable.go +++ b/src/build/scope/Variable.go @@ -2,11 +2,13 @@ package scope import ( "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/types" ) // Variable represents a named register. type Variable struct { Name string + Type types.Type Alive uint8 Register cpu.Register } diff --git a/src/build/types/New.go b/src/build/types/New.go new file mode 100644 index 0000000..73e7d23 --- /dev/null +++ b/src/build/types/New.go @@ -0,0 +1,8 @@ +package types + +import "git.akyoto.dev/cli/q/src/build/token" + +// New creates a new type from a list of tokens. +func New(tokens token.List) Type { + return Int +} diff --git a/src/build/types/Type.go b/src/build/types/Type.go new file mode 100644 index 0000000..07340a0 --- /dev/null +++ b/src/build/types/Type.go @@ -0,0 +1,8 @@ +package types + +type Type int + +const ( + Invalid Type = iota // Invalid is an invalid type. + Int +) From cacee7260afdb6a4b6a01eed4ad1b35dfa14c5ab Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 7 Aug 2024 16:20:03 +0200 Subject: [PATCH 0481/1012] Implemented type checks --- lib/sys/linux.q | 2 +- src/build/arch/x64/Registers.go | 11 ++-- src/build/compiler/Result.go | 8 +-- src/build/core/CompileCall.go | 89 +++++++++++++------------- src/build/core/CompileDefinition.go | 6 +- src/build/core/CompileReturn.go | 21 +++++- src/build/core/CompileSyscall.go | 32 +++++++++ src/build/core/ExpressionToRegister.go | 6 +- src/build/core/NewFunction.go | 11 ++-- src/build/core/TokenToRegister.go | 2 +- src/build/cpu/CPU.go | 11 ++-- src/build/errors/TypeMismatch.go | 26 ++++++++ src/build/scanner/scanFile.go | 11 +++- src/build/scope/Stack.go | 1 + src/build/types/New.go | 6 +- src/build/types/NewList.go | 19 ++++++ src/build/types/Type.go | 8 ++- tests/errors/TypeMismatch.q | 7 ++ tests/errors_test.go | 1 + 19 files changed, 199 insertions(+), 79 deletions(-) create mode 100644 src/build/core/CompileSyscall.go create mode 100644 src/build/errors/TypeMismatch.go create mode 100644 src/build/types/NewList.go create mode 100644 tests/errors/TypeMismatch.q diff --git a/lib/sys/linux.q b/lib/sys/linux.q index e6d84c2..e70ffee 100644 --- a/lib/sys/linux.q +++ b/lib/sys/linux.q @@ -14,7 +14,7 @@ close(fd Int) -> Int { return syscall(3, fd) } -mmap(address Int, length Int, protection Int, flags Int) -> Int { +mmap(address Int, length Int, protection Int, flags Int) -> Pointer { return syscall(9, address, length, protection, flags) } diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go index 2d9bd1c..e8ecbc8 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/build/arch/x64/Registers.go @@ -22,9 +22,10 @@ const ( ) var ( - AllRegisters = []cpu.Register{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15} - SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} - GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} - CallRegisters = SyscallRegisters - ReturnValueRegisters = SyscallRegisters + AllRegisters = []cpu.Register{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15} + SyscallInputRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} + SyscallOutputRegisters = []cpu.Register{RAX, RCX, R11} + GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} + InputRegisters = SyscallInputRegisters + OutputRegisters = SyscallInputRegisters ) diff --git a/src/build/compiler/Result.go b/src/build/compiler/Result.go index db1f814..d9e699a 100644 --- a/src/build/compiler/Result.go +++ b/src/build/compiler/Result.go @@ -32,13 +32,13 @@ func (r *Result) finalize() ([]byte, []byte) { } final.Call("main.main") - final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit) - final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() final.Label(asm.LABEL, "_crash") - final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit) - final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 1) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() // This will place the main function immediately after the entry point diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 25466db..16c14f2 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -15,80 +15,80 @@ import ( func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { var ( pkg = f.Package - nameRoot = root.Children[0] + nameNode = root.Children[0] fn *Function name string fullName string exists bool ) - if nameRoot.IsLeaf() { - name = nameRoot.Token.Text(f.File.Bytes) + if nameNode.IsLeaf() { + name = nameNode.Token.Text(f.File.Bytes) + + if name == "syscall" { + return nil, f.CompileSyscall(root) + } } else { - pkg = nameRoot.Children[0].Token.Text(f.File.Bytes) - name = nameRoot.Children[1].Token.Text(f.File.Bytes) + pkg = nameNode.Children[0].Token.Text(f.File.Bytes) + name = nameNode.Children[1].Token.Text(f.File.Bytes) } - isSyscall := name == "syscall" - - if !isSyscall { - if pkg != f.File.Package { - if f.File.Imports == nil { - return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) - } - - imp, exists := f.File.Imports[pkg] - - if !exists { - return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) - } - - imp.Used = true + if pkg != f.File.Package { + if f.File.Imports == nil { + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position) } - tmp := strings.Builder{} - tmp.WriteString(pkg) - tmp.WriteString(".") - tmp.WriteString(name) - fullName = tmp.String() - fn, exists = f.Functions[fullName] + imp, exists := f.File.Imports[pkg] if !exists { - return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameRoot.Token.Position) + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position) } + + imp.Used = true + } + + tmp := strings.Builder{} + tmp.WriteString(pkg) + tmp.WriteString(".") + tmp.WriteString(name) + fullName = tmp.String() + fn, exists = f.Functions[fullName] + + if !exists { + return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) } parameters := root.Children[1:] registers := f.CPU.Input[:len(parameters)] - if isSyscall { - registers = f.CPU.Syscall[:len(parameters)] + for i := len(parameters) - 1; i >= 0; i-- { + typ, err := f.ExpressionToRegister(parameters[i], registers[i]) + + if err != nil { + return nil, err + } + + if typ != fn.Parameters[i].Type { + return nil, errors.New(&errors.TypeMismatch{ + Encountered: string(typ), + Expected: string(fn.Parameters[i].Type), + ParameterName: fn.Parameters[i].Name, + }, f.File, parameters[i].Token.Position) + } } - err := f.ExpressionsToRegisters(parameters, registers) - - if err != nil { - return fn, err + for _, register := range f.CPU.Output[:len(fn.ReturnTypes)] { + f.SaveRegister(register) } - // TODO: Save all return value registers of the function - f.SaveRegister(f.CPU.Output[0]) - - // Push for _, register := range f.CPU.General { if f.RegisterIsUsed(register) { f.Register(asm.PUSH, register) } } - // Call - if isSyscall { - f.Syscall() - } else { - f.Call(fullName) - } + f.Call(fullName) - // Free parameter registers for _, register := range registers { if register == f.CPU.Output[0] && root.Parent != nil { continue @@ -97,7 +97,6 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { f.FreeRegister(register) } - // Pop for i := len(f.CPU.General) - 1; i >= 0; i-- { register := f.CPU.General[i] diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index aeed43d..81ea095 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -41,7 +41,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { } count := 0 - _, err := f.CompileCall(right) + called, err := f.CompileCall(right) if err != nil { return err @@ -54,6 +54,10 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return err } + if called != nil { + variable.Type = called.ReturnTypes[count] + } + f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) f.AddVariable(variable) count++ diff --git a/src/build/core/CompileReturn.go b/src/build/core/CompileReturn.go index ca59d56..e15bc22 100644 --- a/src/build/core/CompileReturn.go +++ b/src/build/core/CompileReturn.go @@ -2,6 +2,8 @@ package core import ( "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/types" ) // CompileReturn compiles a return instruction. @@ -12,5 +14,22 @@ func (f *Function) CompileReturn(node *ast.Return) error { return nil } - return f.ExpressionsToRegisters(node.Values, f.CPU.Output) + for i := len(node.Values) - 1; i >= 0; i-- { + typ, err := f.ExpressionToRegister(node.Values[i], f.CPU.Output[i]) + + if err != nil { + return err + } + + if typ != types.Any && typ != f.ReturnTypes[i] { + return errors.New(&errors.TypeMismatch{ + Encountered: string(typ), + Expected: string(f.ReturnTypes[i]), + ParameterName: "", + IsReturn: true, + }, f.File, node.Values[i].Token.Position) + } + } + + return nil } diff --git a/src/build/core/CompileSyscall.go b/src/build/core/CompileSyscall.go new file mode 100644 index 0000000..63b3a23 --- /dev/null +++ b/src/build/core/CompileSyscall.go @@ -0,0 +1,32 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/expression" +) + +// CompileSyscall executes a kernel syscall. +func (f *Function) CompileSyscall(root *expression.Expression) error { + parameters := root.Children[1:] + registers := f.CPU.SyscallInput[:len(parameters)] + err := f.ExpressionsToRegisters(parameters, registers) + + if err != nil { + return err + } + + for _, register := range f.CPU.SyscallOutput { + f.SaveRegister(register) + } + + f.Syscall() + + for _, register := range registers { + if register == f.CPU.SyscallOutput[0] && root.Parent != nil { + continue + } + + f.FreeRegister(register) + } + + return nil +} diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index c084351..7d7d834 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -31,7 +31,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } if fn == nil || len(fn.ReturnTypes) == 0 { - return types.Invalid, err + return types.Any, err } return fn.ReturnTypes[0], err @@ -72,6 +72,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return types.Invalid, err } + if typ == types.Pointer && (node.Token.Kind == token.Add || node.Token.Kind == token.Sub) && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.Pointer { + typ = types.Int + } + err = f.Execute(node.Token, register, right) if register != final { diff --git a/src/build/core/NewFunction.go b/src/build/core/NewFunction.go index 2772db2..736bf56 100644 --- a/src/build/core/NewFunction.go +++ b/src/build/core/NewFunction.go @@ -26,11 +26,12 @@ func NewFunction(pkg string, name string, file *fs.File, body []token.Token) *Fu Scopes: []*scope.Scope{{}}, }, CPU: cpu.CPU{ - All: x64.AllRegisters, - Input: x64.CallRegisters, - General: x64.GeneralRegisters, - Syscall: x64.SyscallRegisters, - Output: x64.ReturnValueRegisters, + All: x64.AllRegisters, + General: x64.GeneralRegisters, + Input: x64.InputRegisters, + Output: x64.OutputRegisters, + SyscallInput: x64.SyscallInputRegisters, + SyscallOutput: x64.SyscallOutputRegisters, }, }, } diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index c1d38ff..1d3df22 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -42,7 +42,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. label := f.AddBytes(data) f.SaveRegister(register) f.RegisterLabel(asm.MOVE, register, label) - return types.Int, nil + return types.Pointer, nil default: return types.Invalid, errors.New(errors.InvalidExpression, f.File, t.Position) diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go index 9edbc06..4fe0571 100644 --- a/src/build/cpu/CPU.go +++ b/src/build/cpu/CPU.go @@ -2,9 +2,10 @@ package cpu // CPU represents the processor. type CPU struct { - All []Register - General []Register - Syscall []Register - Input []Register - Output []Register + All []Register + General []Register + Input []Register + Output []Register + SyscallInput []Register + SyscallOutput []Register } diff --git a/src/build/errors/TypeMismatch.go b/src/build/errors/TypeMismatch.go new file mode 100644 index 0000000..cb8aae4 --- /dev/null +++ b/src/build/errors/TypeMismatch.go @@ -0,0 +1,26 @@ +package errors + +import "fmt" + +// TypeMismatch represents an error where a type requirement was not met. +type TypeMismatch struct { + Encountered string + Expected string + ParameterName string + IsReturn bool +} + +// Error generates the string representation. +func (err *TypeMismatch) Error() string { + subject := "type" + + if err.IsReturn { + subject = "return type" + } + + if err.ParameterName != "" { + return fmt.Sprintf("Expected parameter '%s' of %s '%s' (encountered '%s')", err.ParameterName, subject, err.Expected, err.Encountered) + } + + return fmt.Sprintf("Expected %s '%s' instead of '%s'", subject, err.Expected, err.Encountered) +} diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index b7174b0..714ba72 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -229,7 +229,12 @@ func (s *Scanner) scanFile(path string, pkg string) error { function := core.NewFunction(pkg, name, file, body) if typeStart != -1 { - function.ReturnTypes = append(function.ReturnTypes, types.New(tokens[typeStart:typeEnd])) + if tokens[typeStart].Kind == token.GroupStart && tokens[typeEnd-1].Kind == token.GroupEnd { + typeStart++ + typeEnd-- + } + + function.ReturnTypes = types.NewList(tokens[typeStart:typeEnd], contents) } parameters := tokens[paramsStart:paramsEnd] @@ -241,8 +246,8 @@ func (s *Scanner) scanFile(path string, pkg string) error { } name := tokens[0].Text(contents) - dataType := types.New(tokens[1:]) - register := x64.CallRegisters[count] + dataType := types.New(tokens[1:].Text(contents)) + register := x64.InputRegisters[count] uses := token.Count(function.Body, contents, token.Identifier, name) if uses == 0 { diff --git a/src/build/scope/Stack.go b/src/build/scope/Stack.go index 94fec99..750bc4c 100644 --- a/src/build/scope/Stack.go +++ b/src/build/scope/Stack.go @@ -47,6 +47,7 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { Name: v.Name, Register: v.Register, Alive: count, + Type: v.Type, }) } } diff --git a/src/build/types/New.go b/src/build/types/New.go index 73e7d23..ae20817 100644 --- a/src/build/types/New.go +++ b/src/build/types/New.go @@ -1,8 +1,6 @@ package types -import "git.akyoto.dev/cli/q/src/build/token" - // New creates a new type from a list of tokens. -func New(tokens token.List) Type { - return Int +func New(name string) Type { + return Type(name) } diff --git a/src/build/types/NewList.go b/src/build/types/NewList.go new file mode 100644 index 0000000..91bb4ca --- /dev/null +++ b/src/build/types/NewList.go @@ -0,0 +1,19 @@ +package types + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// NewList generates a list of types from comma separated tokens. +func NewList(tokens token.List, source []byte) []Type { + var list []Type + + expression.EachParameter(tokens, func(parameter token.List) error { + typ := New(parameter.Text(source)) + list = append(list, typ) + return nil + }) + + return list +} diff --git a/src/build/types/Type.go b/src/build/types/Type.go index 07340a0..76c04a9 100644 --- a/src/build/types/Type.go +++ b/src/build/types/Type.go @@ -1,8 +1,10 @@ package types -type Type int +type Type string const ( - Invalid Type = iota // Invalid is an invalid type. - Int + Invalid = "" + Any = "Any" + Int = "Int" + Pointer = "Pointer" ) diff --git a/tests/errors/TypeMismatch.q b/tests/errors/TypeMismatch.q new file mode 100644 index 0000000..d86c306 --- /dev/null +++ b/tests/errors/TypeMismatch.q @@ -0,0 +1,7 @@ +main() { + writeToMemory(0) +} + +writeToMemory(p Pointer) { + p[0] = 'A' +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 2ec440e..61eab3d 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -36,6 +36,7 @@ var errs = []struct { {"MissingOperand.q", errors.MissingOperand}, {"MissingOperand2.q", errors.MissingOperand}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, + {"TypeMismatch.q", &errors.TypeMismatch{Expected: "Pointer", Encountered: "Int", ParameterName: "p"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, From 1b13539b22d6e73574b110fab2acdfbe3b520921 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 7 Aug 2024 16:20:03 +0200 Subject: [PATCH 0482/1012] Implemented type checks --- lib/sys/linux.q | 2 +- src/build/arch/x64/Registers.go | 11 ++-- src/build/compiler/Result.go | 8 +-- src/build/core/CompileCall.go | 89 +++++++++++++------------- src/build/core/CompileDefinition.go | 6 +- src/build/core/CompileReturn.go | 21 +++++- src/build/core/CompileSyscall.go | 32 +++++++++ src/build/core/ExpressionToRegister.go | 6 +- src/build/core/NewFunction.go | 11 ++-- src/build/core/TokenToRegister.go | 2 +- src/build/cpu/CPU.go | 11 ++-- src/build/errors/TypeMismatch.go | 26 ++++++++ src/build/scanner/scanFile.go | 11 +++- src/build/scope/Stack.go | 1 + src/build/types/New.go | 6 +- src/build/types/NewList.go | 19 ++++++ src/build/types/Type.go | 8 ++- tests/errors/TypeMismatch.q | 7 ++ tests/errors_test.go | 1 + 19 files changed, 199 insertions(+), 79 deletions(-) create mode 100644 src/build/core/CompileSyscall.go create mode 100644 src/build/errors/TypeMismatch.go create mode 100644 src/build/types/NewList.go create mode 100644 tests/errors/TypeMismatch.q diff --git a/lib/sys/linux.q b/lib/sys/linux.q index e6d84c2..e70ffee 100644 --- a/lib/sys/linux.q +++ b/lib/sys/linux.q @@ -14,7 +14,7 @@ close(fd Int) -> Int { return syscall(3, fd) } -mmap(address Int, length Int, protection Int, flags Int) -> Int { +mmap(address Int, length Int, protection Int, flags Int) -> Pointer { return syscall(9, address, length, protection, flags) } diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go index 2d9bd1c..e8ecbc8 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/build/arch/x64/Registers.go @@ -22,9 +22,10 @@ const ( ) var ( - AllRegisters = []cpu.Register{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15} - SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} - GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} - CallRegisters = SyscallRegisters - ReturnValueRegisters = SyscallRegisters + AllRegisters = []cpu.Register{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15} + SyscallInputRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} + SyscallOutputRegisters = []cpu.Register{RAX, RCX, R11} + GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} + InputRegisters = SyscallInputRegisters + OutputRegisters = SyscallInputRegisters ) diff --git a/src/build/compiler/Result.go b/src/build/compiler/Result.go index db1f814..d9e699a 100644 --- a/src/build/compiler/Result.go +++ b/src/build/compiler/Result.go @@ -32,13 +32,13 @@ func (r *Result) finalize() ([]byte, []byte) { } final.Call("main.main") - final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit) - final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() final.Label(asm.LABEL, "_crash") - final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit) - final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 1) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() // This will place the main function immediately after the entry point diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 25466db..16c14f2 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -15,80 +15,80 @@ import ( func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { var ( pkg = f.Package - nameRoot = root.Children[0] + nameNode = root.Children[0] fn *Function name string fullName string exists bool ) - if nameRoot.IsLeaf() { - name = nameRoot.Token.Text(f.File.Bytes) + if nameNode.IsLeaf() { + name = nameNode.Token.Text(f.File.Bytes) + + if name == "syscall" { + return nil, f.CompileSyscall(root) + } } else { - pkg = nameRoot.Children[0].Token.Text(f.File.Bytes) - name = nameRoot.Children[1].Token.Text(f.File.Bytes) + pkg = nameNode.Children[0].Token.Text(f.File.Bytes) + name = nameNode.Children[1].Token.Text(f.File.Bytes) } - isSyscall := name == "syscall" - - if !isSyscall { - if pkg != f.File.Package { - if f.File.Imports == nil { - return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) - } - - imp, exists := f.File.Imports[pkg] - - if !exists { - return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position) - } - - imp.Used = true + if pkg != f.File.Package { + if f.File.Imports == nil { + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position) } - tmp := strings.Builder{} - tmp.WriteString(pkg) - tmp.WriteString(".") - tmp.WriteString(name) - fullName = tmp.String() - fn, exists = f.Functions[fullName] + imp, exists := f.File.Imports[pkg] if !exists { - return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameRoot.Token.Position) + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position) } + + imp.Used = true + } + + tmp := strings.Builder{} + tmp.WriteString(pkg) + tmp.WriteString(".") + tmp.WriteString(name) + fullName = tmp.String() + fn, exists = f.Functions[fullName] + + if !exists { + return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) } parameters := root.Children[1:] registers := f.CPU.Input[:len(parameters)] - if isSyscall { - registers = f.CPU.Syscall[:len(parameters)] + for i := len(parameters) - 1; i >= 0; i-- { + typ, err := f.ExpressionToRegister(parameters[i], registers[i]) + + if err != nil { + return nil, err + } + + if typ != fn.Parameters[i].Type { + return nil, errors.New(&errors.TypeMismatch{ + Encountered: string(typ), + Expected: string(fn.Parameters[i].Type), + ParameterName: fn.Parameters[i].Name, + }, f.File, parameters[i].Token.Position) + } } - err := f.ExpressionsToRegisters(parameters, registers) - - if err != nil { - return fn, err + for _, register := range f.CPU.Output[:len(fn.ReturnTypes)] { + f.SaveRegister(register) } - // TODO: Save all return value registers of the function - f.SaveRegister(f.CPU.Output[0]) - - // Push for _, register := range f.CPU.General { if f.RegisterIsUsed(register) { f.Register(asm.PUSH, register) } } - // Call - if isSyscall { - f.Syscall() - } else { - f.Call(fullName) - } + f.Call(fullName) - // Free parameter registers for _, register := range registers { if register == f.CPU.Output[0] && root.Parent != nil { continue @@ -97,7 +97,6 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { f.FreeRegister(register) } - // Pop for i := len(f.CPU.General) - 1; i >= 0; i-- { register := f.CPU.General[i] diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index aeed43d..81ea095 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -41,7 +41,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { } count := 0 - _, err := f.CompileCall(right) + called, err := f.CompileCall(right) if err != nil { return err @@ -54,6 +54,10 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return err } + if called != nil { + variable.Type = called.ReturnTypes[count] + } + f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) f.AddVariable(variable) count++ diff --git a/src/build/core/CompileReturn.go b/src/build/core/CompileReturn.go index ca59d56..e15bc22 100644 --- a/src/build/core/CompileReturn.go +++ b/src/build/core/CompileReturn.go @@ -2,6 +2,8 @@ package core import ( "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/types" ) // CompileReturn compiles a return instruction. @@ -12,5 +14,22 @@ func (f *Function) CompileReturn(node *ast.Return) error { return nil } - return f.ExpressionsToRegisters(node.Values, f.CPU.Output) + for i := len(node.Values) - 1; i >= 0; i-- { + typ, err := f.ExpressionToRegister(node.Values[i], f.CPU.Output[i]) + + if err != nil { + return err + } + + if typ != types.Any && typ != f.ReturnTypes[i] { + return errors.New(&errors.TypeMismatch{ + Encountered: string(typ), + Expected: string(f.ReturnTypes[i]), + ParameterName: "", + IsReturn: true, + }, f.File, node.Values[i].Token.Position) + } + } + + return nil } diff --git a/src/build/core/CompileSyscall.go b/src/build/core/CompileSyscall.go new file mode 100644 index 0000000..63b3a23 --- /dev/null +++ b/src/build/core/CompileSyscall.go @@ -0,0 +1,32 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/expression" +) + +// CompileSyscall executes a kernel syscall. +func (f *Function) CompileSyscall(root *expression.Expression) error { + parameters := root.Children[1:] + registers := f.CPU.SyscallInput[:len(parameters)] + err := f.ExpressionsToRegisters(parameters, registers) + + if err != nil { + return err + } + + for _, register := range f.CPU.SyscallOutput { + f.SaveRegister(register) + } + + f.Syscall() + + for _, register := range registers { + if register == f.CPU.SyscallOutput[0] && root.Parent != nil { + continue + } + + f.FreeRegister(register) + } + + return nil +} diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index c084351..7d7d834 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -31,7 +31,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } if fn == nil || len(fn.ReturnTypes) == 0 { - return types.Invalid, err + return types.Any, err } return fn.ReturnTypes[0], err @@ -72,6 +72,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return types.Invalid, err } + if typ == types.Pointer && (node.Token.Kind == token.Add || node.Token.Kind == token.Sub) && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.Pointer { + typ = types.Int + } + err = f.Execute(node.Token, register, right) if register != final { diff --git a/src/build/core/NewFunction.go b/src/build/core/NewFunction.go index 2772db2..736bf56 100644 --- a/src/build/core/NewFunction.go +++ b/src/build/core/NewFunction.go @@ -26,11 +26,12 @@ func NewFunction(pkg string, name string, file *fs.File, body []token.Token) *Fu Scopes: []*scope.Scope{{}}, }, CPU: cpu.CPU{ - All: x64.AllRegisters, - Input: x64.CallRegisters, - General: x64.GeneralRegisters, - Syscall: x64.SyscallRegisters, - Output: x64.ReturnValueRegisters, + All: x64.AllRegisters, + General: x64.GeneralRegisters, + Input: x64.InputRegisters, + Output: x64.OutputRegisters, + SyscallInput: x64.SyscallInputRegisters, + SyscallOutput: x64.SyscallOutputRegisters, }, }, } diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index c1d38ff..1d3df22 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -42,7 +42,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. label := f.AddBytes(data) f.SaveRegister(register) f.RegisterLabel(asm.MOVE, register, label) - return types.Int, nil + return types.Pointer, nil default: return types.Invalid, errors.New(errors.InvalidExpression, f.File, t.Position) diff --git a/src/build/cpu/CPU.go b/src/build/cpu/CPU.go index 9edbc06..4fe0571 100644 --- a/src/build/cpu/CPU.go +++ b/src/build/cpu/CPU.go @@ -2,9 +2,10 @@ package cpu // CPU represents the processor. type CPU struct { - All []Register - General []Register - Syscall []Register - Input []Register - Output []Register + All []Register + General []Register + Input []Register + Output []Register + SyscallInput []Register + SyscallOutput []Register } diff --git a/src/build/errors/TypeMismatch.go b/src/build/errors/TypeMismatch.go new file mode 100644 index 0000000..cb8aae4 --- /dev/null +++ b/src/build/errors/TypeMismatch.go @@ -0,0 +1,26 @@ +package errors + +import "fmt" + +// TypeMismatch represents an error where a type requirement was not met. +type TypeMismatch struct { + Encountered string + Expected string + ParameterName string + IsReturn bool +} + +// Error generates the string representation. +func (err *TypeMismatch) Error() string { + subject := "type" + + if err.IsReturn { + subject = "return type" + } + + if err.ParameterName != "" { + return fmt.Sprintf("Expected parameter '%s' of %s '%s' (encountered '%s')", err.ParameterName, subject, err.Expected, err.Encountered) + } + + return fmt.Sprintf("Expected %s '%s' instead of '%s'", subject, err.Expected, err.Encountered) +} diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index b7174b0..714ba72 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -229,7 +229,12 @@ func (s *Scanner) scanFile(path string, pkg string) error { function := core.NewFunction(pkg, name, file, body) if typeStart != -1 { - function.ReturnTypes = append(function.ReturnTypes, types.New(tokens[typeStart:typeEnd])) + if tokens[typeStart].Kind == token.GroupStart && tokens[typeEnd-1].Kind == token.GroupEnd { + typeStart++ + typeEnd-- + } + + function.ReturnTypes = types.NewList(tokens[typeStart:typeEnd], contents) } parameters := tokens[paramsStart:paramsEnd] @@ -241,8 +246,8 @@ func (s *Scanner) scanFile(path string, pkg string) error { } name := tokens[0].Text(contents) - dataType := types.New(tokens[1:]) - register := x64.CallRegisters[count] + dataType := types.New(tokens[1:].Text(contents)) + register := x64.InputRegisters[count] uses := token.Count(function.Body, contents, token.Identifier, name) if uses == 0 { diff --git a/src/build/scope/Stack.go b/src/build/scope/Stack.go index 94fec99..750bc4c 100644 --- a/src/build/scope/Stack.go +++ b/src/build/scope/Stack.go @@ -47,6 +47,7 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { Name: v.Name, Register: v.Register, Alive: count, + Type: v.Type, }) } } diff --git a/src/build/types/New.go b/src/build/types/New.go index 73e7d23..ae20817 100644 --- a/src/build/types/New.go +++ b/src/build/types/New.go @@ -1,8 +1,6 @@ package types -import "git.akyoto.dev/cli/q/src/build/token" - // New creates a new type from a list of tokens. -func New(tokens token.List) Type { - return Int +func New(name string) Type { + return Type(name) } diff --git a/src/build/types/NewList.go b/src/build/types/NewList.go new file mode 100644 index 0000000..91bb4ca --- /dev/null +++ b/src/build/types/NewList.go @@ -0,0 +1,19 @@ +package types + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" +) + +// NewList generates a list of types from comma separated tokens. +func NewList(tokens token.List, source []byte) []Type { + var list []Type + + expression.EachParameter(tokens, func(parameter token.List) error { + typ := New(parameter.Text(source)) + list = append(list, typ) + return nil + }) + + return list +} diff --git a/src/build/types/Type.go b/src/build/types/Type.go index 07340a0..76c04a9 100644 --- a/src/build/types/Type.go +++ b/src/build/types/Type.go @@ -1,8 +1,10 @@ package types -type Type int +type Type string const ( - Invalid Type = iota // Invalid is an invalid type. - Int + Invalid = "" + Any = "Any" + Int = "Int" + Pointer = "Pointer" ) diff --git a/tests/errors/TypeMismatch.q b/tests/errors/TypeMismatch.q new file mode 100644 index 0000000..d86c306 --- /dev/null +++ b/tests/errors/TypeMismatch.q @@ -0,0 +1,7 @@ +main() { + writeToMemory(0) +} + +writeToMemory(p Pointer) { + p[0] = 'A' +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 2ec440e..61eab3d 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -36,6 +36,7 @@ var errs = []struct { {"MissingOperand.q", errors.MissingOperand}, {"MissingOperand2.q", errors.MissingOperand}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, + {"TypeMismatch.q", &errors.TypeMismatch{Expected: "Pointer", Encountered: "Int", ParameterName: "p"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, From a4662813075a1eb692dcaf8fbd51ebe6bcbb3fb7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 7 Aug 2024 19:39:10 +0200 Subject: [PATCH 0483/1012] Simplified file structure --- README.md | 146 +++++++++--------- go.mod | 4 +- go.sum | 4 +- src/{build => }/arch/arm64/Registers.go | 2 +- src/{build => }/arch/riscv/Registers.go | 2 +- src/{build => }/arch/x64/Add.go | 2 +- src/{build => }/arch/x64/Add_test.go | 4 +- src/{build => }/arch/x64/And.go | 2 +- src/{build => }/arch/x64/Call.go | 0 src/{build => }/arch/x64/Compare.go | 2 +- src/{build => }/arch/x64/Compare_test.go | 4 +- src/{build => }/arch/x64/Div.go | 2 +- src/{build => }/arch/x64/Div_test.go | 4 +- src/{build => }/arch/x64/ExtendRAXToRDX.go | 0 src/{build => }/arch/x64/Jump.go | 0 src/{build => }/arch/x64/Jump_test.go | 2 +- src/{build => }/arch/x64/Load.go | 2 +- src/{build => }/arch/x64/Load_test.go | 4 +- src/{build => }/arch/x64/ModRM.go | 0 src/{build => }/arch/x64/ModRM_test.go | 2 +- src/{build => }/arch/x64/Move.go | 4 +- src/{build => }/arch/x64/Move_test.go | 4 +- src/{build => }/arch/x64/Mul.go | 2 +- src/{build => }/arch/x64/Mul_test.go | 4 +- src/{build => }/arch/x64/Negate.go | 2 +- src/{build => }/arch/x64/Negate_test.go | 4 +- src/{build => }/arch/x64/Or.go | 2 +- src/{build => }/arch/x64/Pop.go | 2 +- src/{build => }/arch/x64/Pop_test.go | 4 +- src/{build => }/arch/x64/Push.go | 2 +- src/{build => }/arch/x64/Push_test.go | 4 +- src/{build => }/arch/x64/REX.go | 0 src/{build => }/arch/x64/REX_test.go | 2 +- src/{build => }/arch/x64/Registers.go | 2 +- src/{build => }/arch/x64/Return.go | 0 src/{build => }/arch/x64/SIB.go | 0 src/{build => }/arch/x64/SIB_test.go | 2 +- src/{build => }/arch/x64/Shift.go | 2 +- src/{build => }/arch/x64/Store.go | 2 +- src/{build => }/arch/x64/Store_test.go | 4 +- src/{build => }/arch/x64/Sub.go | 2 +- src/{build => }/arch/x64/Sub_test.go | 4 +- src/{build => }/arch/x64/Syscall.go | 0 src/{build => }/arch/x64/Xor.go | 2 +- src/{build => }/arch/x64/encode.go | 2 +- src/{build => }/arch/x64/encodeNum.go | 4 +- src/{build => }/arch/x64/memoryAccess.go | 2 +- src/{build => }/arch/x64/x64_test.go | 2 +- src/{build => }/asm/Assembler.go | 2 +- src/{build => }/asm/Finalize.go | 8 +- src/{build => }/asm/Instruction.go | 0 src/{build => }/asm/Instructions.go | 0 src/{build => }/asm/Label.go | 0 src/{build => }/asm/Memory.go | 2 +- src/{build => }/asm/MemoryNumber.go | 0 src/{build => }/asm/MemoryRegister.go | 2 +- src/{build => }/asm/Mnemonic.go | 0 src/{build => }/asm/Optimizer.go | 2 +- src/{build => }/asm/Pointer.go | 0 src/{build => }/asm/Register.go | 2 +- src/{build => }/asm/RegisterLabel.go | 2 +- src/{build => }/asm/RegisterNumber.go | 2 +- src/{build => }/asm/RegisterRegister.go | 2 +- src/{build => }/ast/AST.go | 0 src/{build => }/ast/Assert.go | 2 +- src/{build => }/ast/Assign.go | 2 +- src/{build => }/ast/Call.go | 2 +- src/{build => }/ast/Count.go | 2 +- src/{build => }/ast/Define.go | 2 +- src/{build => }/ast/EachInstruction.go | 2 +- src/{build => }/ast/If.go | 2 +- src/{build => }/ast/Loop.go | 0 src/{build => }/ast/Parse.go | 4 +- src/{build => }/ast/Return.go | 2 +- src/{build => }/ast/Switch.go | 2 +- src/{build => }/ast/parseKeyword.go | 6 +- src/{build => }/ast/parseNode.go | 6 +- src/{build => }/ast/parseSwitch.go | 4 +- src/build/Build.go | 4 +- src/build/expression/List.go | 55 ------- src/build/token/List_test.go | 18 --- src/cli/Build.go | 4 +- src/cli/Run.go | 2 +- src/cli/System.go | 2 +- src/{build => }/compiler/Compile.go | 6 +- src/{build => }/compiler/Result.go | 10 +- src/{build => }/config/config.go | 0 src/{build => }/config/init.go | 0 src/{build => }/core/AddBytes.go | 0 src/{build => }/core/Compare.go | 8 +- src/{build => }/core/Compile.go | 0 src/{build => }/core/CompileAST.go | 2 +- src/{build => }/core/CompileASTNode.go | 2 +- src/{build => }/core/CompileAssert.go | 4 +- src/{build => }/core/CompileAssign.go | 6 +- src/{build => }/core/CompileAssignArray.go | 6 +- src/{build => }/core/CompileAssignDivision.go | 8 +- src/{build => }/core/CompileCall.go | 6 +- src/{build => }/core/CompileCondition.go | 4 +- src/{build => }/core/CompileDefinition.go | 10 +- src/{build => }/core/CompileIf.go | 4 +- src/{build => }/core/CompileLoop.go | 4 +- src/{build => }/core/CompileReturn.go | 6 +- src/{build => }/core/CompileSwitch.go | 4 +- src/{build => }/core/CompileSyscall.go | 2 +- src/{build => }/core/CompileTokens.go | 6 +- src/{build => }/core/Defer.go | 0 src/{build => }/core/Define.go | 8 +- src/{build => }/core/Evaluate.go | 8 +- src/{build => }/core/Execute.go | 8 +- src/{build => }/core/ExecuteLeaf.go | 6 +- src/{build => }/core/ExecuteRegister.go | 8 +- src/{build => }/core/ExecuteRegisterNumber.go | 10 +- .../core/ExecuteRegisterRegister.go | 10 +- src/{build => }/core/ExpressionToMemory.go | 10 +- src/{build => }/core/ExpressionToRegister.go | 14 +- .../core/ExpressionsToRegisters.go | 4 +- src/{build => }/core/Fold.go | 6 +- src/{build => }/core/Function.go | 10 +- src/{build => }/core/IdentifierExists.go | 0 src/{build => }/core/JumpIfFalse.go | 4 +- src/{build => }/core/JumpIfTrue.go | 4 +- src/{build => }/core/NewFunction.go | 14 +- src/{build => }/core/Number.go | 4 +- src/{build => }/core/PrintInstructions.go | 2 +- src/{build => }/core/String.go | 0 src/{build => }/core/TokenToRegister.go | 10 +- src/{build => }/core/UsesRegister.go | 6 +- src/{build => }/cpu/CPU.go | 0 src/{build => }/cpu/Register.go | 0 src/{build => }/cpu/Register_test.go | 2 +- src/{build => }/cpu/State.go | 0 src/{build => }/cpu/State_test.go | 2 +- src/{build => }/data/Data.go | 0 src/{build => }/data/Data_test.go | 2 +- src/{build => }/elf/ELF.go | 2 +- src/{build => }/elf/ELF_test.go | 2 +- src/{build => }/elf/Header.go | 0 src/{build => }/elf/Padding.go | 0 src/{build => }/elf/ProgramHeader.go | 0 src/{build => }/elf/SectionHeader.go | 0 src/{build => }/elf/elf.md | 0 src/{build => }/errors/Base.go | 0 src/{build => }/errors/Error.go | 4 +- src/{build => }/errors/InvalidCharacter.go | 0 src/{build => }/errors/InvalidInstruction.go | 0 src/{build => }/errors/InvalidOperator.go | 0 .../errors/KeywordNotImplemented.go | 0 src/{build => }/errors/NumberExceedsBounds.go | 0 src/{build => }/errors/Stack.go | 0 src/{build => }/errors/TypeMismatch.go | 0 src/{build => }/errors/UnknownCLIParameter.go | 0 src/{build => }/errors/UnknownFunction.go | 0 src/{build => }/errors/UnknownIdentifier.go | 0 src/{build => }/errors/UnknownPackage.go | 0 src/{build => }/errors/UnusedImport.go | 0 src/{build => }/errors/UnusedVariable.go | 0 .../errors/VariableAlreadyExists.go | 0 src/{build => }/expression/Expression.go | 2 +- src/{build => }/expression/Expression_test.go | 26 +--- src/expression/List.go | 18 +++ src/{build => }/expression/Operator.go | 2 +- src/{build => }/expression/Parse.go | 2 +- src/{build => }/expression/bench_test.go | 4 +- src/{build => }/fs/File.go | 2 +- src/{build => }/fs/Import.go | 2 +- src/{build => }/fs/Walk.go | 0 src/{build => }/fs/Walk_test.go | 2 +- src/{build => }/os/linux/Syscall.go | 0 src/{build => }/register/AddLabel.go | 2 +- src/{build => }/register/Call.go | 0 src/{build => }/register/Comment.go | 0 src/{build => }/register/FreeRegister.go | 2 +- src/{build => }/register/Jump.go | 2 +- src/{build => }/register/Machine.go | 6 +- src/{build => }/register/MemoryNumber.go | 2 +- src/{build => }/register/MemoryRegister.go | 4 +- src/{build => }/register/NewRegister.go | 2 +- src/{build => }/register/Register.go | 4 +- src/{build => }/register/RegisterIsUsed.go | 2 +- src/{build => }/register/RegisterLabel.go | 4 +- src/{build => }/register/RegisterNumber.go | 6 +- src/{build => }/register/RegisterRegister.go | 4 +- src/{build => }/register/Return.go | 0 src/{build => }/register/SaveRegister.go | 4 +- src/{build => }/register/Syscall.go | 0 src/{build => }/register/UseRegister.go | 2 +- src/{build => }/register/postInstruction.go | 2 +- src/{build => }/scanner/Scan.go | 4 +- src/{build => }/scanner/Scanner.go | 4 +- src/{build => }/scanner/queue.go | 0 src/{build => }/scanner/queueDirectory.go | 2 +- src/{build => }/scanner/queueFile.go | 0 src/{build => }/scanner/scanFile.go | 19 ++- src/{build => }/scope/Scope.go | 2 +- src/{build => }/scope/Stack.go | 6 +- src/{build => }/scope/Variable.go | 4 +- src/{build => }/sizeof/Signed.go | 0 src/{build => }/sizeof/Signed_test.go | 2 +- src/{build => }/sizeof/Unsigned.go | 0 src/{build => }/sizeof/Unsigned_test.go | 2 +- src/{build => }/token/Count.go | 0 src/{build => }/token/Count_test.go | 2 +- src/{build => }/token/Kind.go | 0 src/{build => }/token/Length.go | 0 src/{build => }/token/List.go | 37 +++++ src/token/List_test.go | 40 +++++ src/{build => }/token/Position.go | 0 src/{build => }/token/Token.go | 0 src/{build => }/token/Token_test.go | 2 +- src/{build => }/token/Tokenize.go | 0 src/{build => }/token/Tokenize_test.go | 2 +- src/{build => }/token/bench_test.go | 2 +- src/{build => }/types/New.go | 0 src/{build => }/types/NewList.go | 7 +- src/{build => }/types/Type.go | 0 tests/errors_test.go | 2 +- tests/examples_test.go | 2 +- tests/programs_test.go | 2 +- 219 files changed, 453 insertions(+), 457 deletions(-) rename src/{build => }/arch/arm64/Registers.go (85%) rename src/{build => }/arch/riscv/Registers.go (85%) rename src/{build => }/arch/x64/Add.go (92%) rename src/{build => }/arch/x64/Add_test.go (97%) rename src/{build => }/arch/x64/And.go (92%) rename src/{build => }/arch/x64/Call.go (100%) rename src/{build => }/arch/x64/Compare.go (92%) rename src/{build => }/arch/x64/Compare_test.go (97%) rename src/{build => }/arch/x64/Div.go (85%) rename src/{build => }/arch/x64/Div_test.go (92%) rename src/{build => }/arch/x64/ExtendRAXToRDX.go (100%) rename src/{build => }/arch/x64/Jump.go (100%) rename src/{build => }/arch/x64/Jump_test.go (96%) rename src/{build => }/arch/x64/Load.go (85%) rename src/{build => }/arch/x64/Load_test.go (99%) rename src/{build => }/arch/x64/ModRM.go (100%) rename src/{build => }/arch/x64/ModRM_test.go (95%) rename src/{build => }/arch/x64/Move.go (94%) rename src/{build => }/arch/x64/Move_test.go (98%) rename src/{build => }/arch/x64/Mul.go (91%) rename src/{build => }/arch/x64/Mul_test.go (97%) rename src/{build => }/arch/x64/Negate.go (81%) rename src/{build => }/arch/x64/Negate_test.go (92%) rename src/{build => }/arch/x64/Or.go (92%) rename src/{build => }/arch/x64/Pop.go (86%) rename src/{build => }/arch/x64/Pop_test.go (91%) rename src/{build => }/arch/x64/Push.go (86%) rename src/{build => }/arch/x64/Push_test.go (91%) rename src/{build => }/arch/x64/REX.go (100%) rename src/{build => }/arch/x64/REX_test.go (94%) rename src/{build => }/arch/x64/Registers.go (92%) rename src/{build => }/arch/x64/Return.go (100%) rename src/{build => }/arch/x64/SIB.go (100%) rename src/{build => }/arch/x64/SIB_test.go (95%) rename src/{build => }/arch/x64/Shift.go (93%) rename src/{build => }/arch/x64/Store.go (95%) rename src/{build => }/arch/x64/Store_test.go (99%) rename src/{build => }/arch/x64/Sub.go (92%) rename src/{build => }/arch/x64/Sub_test.go (97%) rename src/{build => }/arch/x64/Syscall.go (100%) rename src/{build => }/arch/x64/Xor.go (92%) rename src/{build => }/arch/x64/encode.go (94%) rename src/{build => }/arch/x64/encodeNum.go (86%) rename src/{build => }/arch/x64/memoryAccess.go (93%) rename src/{build => }/arch/x64/x64_test.go (92%) rename src/{build => }/asm/Assembler.go (93%) rename src/{build => }/asm/Finalize.go (98%) rename src/{build => }/asm/Instruction.go (100%) rename src/{build => }/asm/Instructions.go (100%) rename src/{build => }/asm/Label.go (100%) rename src/{build => }/asm/Memory.go (65%) rename src/{build => }/asm/MemoryNumber.go (100%) rename src/{build => }/asm/MemoryRegister.go (94%) rename src/{build => }/asm/Mnemonic.go (100%) rename src/{build => }/asm/Optimizer.go (93%) rename src/{build => }/asm/Pointer.go (100%) rename src/{build => }/asm/Register.go (92%) rename src/{build => }/asm/RegisterLabel.go (94%) rename src/{build => }/asm/RegisterNumber.go (94%) rename src/{build => }/asm/RegisterRegister.go (94%) rename src/{build => }/ast/AST.go (100%) rename src/{build => }/ast/Assert.go (78%) rename src/{build => }/ast/Assign.go (78%) rename src/{build => }/ast/Call.go (67%) rename src/{build => }/ast/Count.go (95%) rename src/{build => }/ast/Define.go (73%) rename src/{build => }/ast/EachInstruction.go (95%) rename src/{build => }/ast/If.go (75%) rename src/{build => }/ast/Loop.go (100%) rename src/{build => }/ast/Parse.go (91%) rename src/{build => }/ast/Return.go (73%) rename src/{build => }/ast/Switch.go (82%) rename src/{build => }/ast/parseKeyword.go (95%) rename src/{build => }/ast/parseNode.go (87%) rename src/{build => }/ast/parseSwitch.go (89%) delete mode 100644 src/build/expression/List.go delete mode 100644 src/build/token/List_test.go rename src/{build => }/compiler/Compile.go (94%) rename src/{build => }/compiler/Result.go (93%) rename src/{build => }/config/config.go (100%) rename src/{build => }/config/init.go (100%) rename src/{build => }/core/AddBytes.go (100%) rename src/{build => }/core/Compare.go (85%) rename src/{build => }/core/Compile.go (100%) rename src/{build => }/core/CompileAST.go (86%) rename src/{build => }/core/CompileASTNode.go (95%) rename src/{build => }/core/CompileAssert.go (86%) rename src/{build => }/core/CompileAssign.go (87%) rename src/{build => }/core/CompileAssignArray.go (86%) rename src/{build => }/core/CompileAssignDivision.go (87%) rename src/{build => }/core/CompileCall.go (95%) rename src/{build => }/core/CompileCondition.go (95%) rename src/{build => }/core/CompileDefinition.go (84%) rename src/{build => }/core/CompileIf.go (92%) rename src/{build => }/core/CompileLoop.go (85%) rename src/{build => }/core/CompileReturn.go (84%) rename src/{build => }/core/CompileSwitch.go (92%) rename src/{build => }/core/CompileSyscall.go (92%) rename src/{build => }/core/CompileTokens.go (69%) rename src/{build => }/core/Defer.go (100%) rename src/{build => }/core/Define.go (78%) rename src/{build => }/core/Evaluate.go (77%) rename src/{build => }/core/Execute.go (80%) rename src/{build => }/core/ExecuteLeaf.go (88%) rename src/{build => }/core/ExecuteRegister.go (71%) rename src/{build => }/core/ExecuteRegisterNumber.go (89%) rename src/{build => }/core/ExecuteRegisterRegister.go (88%) rename src/{build => }/core/ExpressionToMemory.go (80%) rename src/{build => }/core/ExpressionToRegister.go (87%) rename src/{build => }/core/ExpressionsToRegisters.go (82%) rename src/{build => }/core/Fold.go (89%) rename src/{build => }/core/Function.go (75%) rename src/{build => }/core/IdentifierExists.go (100%) rename src/{build => }/core/JumpIfFalse.go (85%) rename src/{build => }/core/JumpIfTrue.go (85%) rename src/{build => }/core/NewFunction.go (72%) rename src/{build => }/core/Number.go (92%) rename src/{build => }/core/PrintInstructions.go (97%) rename src/{build => }/core/String.go (100%) rename src/{build => }/core/TokenToRegister.go (84%) rename src/{build => }/core/UsesRegister.go (84%) rename src/{build => }/cpu/CPU.go (100%) rename src/{build => }/cpu/Register.go (100%) rename src/{build => }/cpu/Register_test.go (82%) rename src/{build => }/cpu/State.go (100%) rename src/{build => }/cpu/State_test.go (95%) rename src/{build => }/data/Data.go (100%) rename src/{build => }/data/Data_test.go (94%) rename src/{build => }/elf/ELF.go (98%) rename src/{build => }/elf/ELF_test.go (77%) rename src/{build => }/elf/Header.go (100%) rename src/{build => }/elf/Padding.go (100%) rename src/{build => }/elf/ProgramHeader.go (100%) rename src/{build => }/elf/SectionHeader.go (100%) rename src/{build => }/elf/elf.md (100%) rename src/{build => }/errors/Base.go (100%) rename src/{build => }/errors/Error.go (94%) rename src/{build => }/errors/InvalidCharacter.go (100%) rename src/{build => }/errors/InvalidInstruction.go (100%) rename src/{build => }/errors/InvalidOperator.go (100%) rename src/{build => }/errors/KeywordNotImplemented.go (100%) rename src/{build => }/errors/NumberExceedsBounds.go (100%) rename src/{build => }/errors/Stack.go (100%) rename src/{build => }/errors/TypeMismatch.go (100%) rename src/{build => }/errors/UnknownCLIParameter.go (100%) rename src/{build => }/errors/UnknownFunction.go (100%) rename src/{build => }/errors/UnknownIdentifier.go (100%) rename src/{build => }/errors/UnknownPackage.go (100%) rename src/{build => }/errors/UnusedImport.go (100%) rename src/{build => }/errors/UnusedVariable.go (100%) rename src/{build => }/errors/VariableAlreadyExists.go (100%) rename src/{build => }/expression/Expression.go (98%) rename src/{build => }/expression/Expression_test.go (91%) create mode 100644 src/expression/List.go rename src/{build => }/expression/Operator.go (98%) rename src/{build => }/expression/Parse.go (98%) rename src/{build => }/expression/bench_test.go (72%) rename src/{build => }/fs/File.go (78%) rename src/{build => }/fs/Import.go (77%) rename src/{build => }/fs/Walk.go (100%) rename src/{build => }/fs/Walk_test.go (93%) rename src/{build => }/os/linux/Syscall.go (100%) rename src/{build => }/register/AddLabel.go (73%) rename src/{build => }/register/Call.go (100%) rename src/{build => }/register/Comment.go (100%) rename src/{build => }/register/FreeRegister.go (76%) rename src/{build => }/register/Jump.go (76%) rename src/{build => }/register/Machine.go (62%) rename src/{build => }/register/MemoryNumber.go (78%) rename src/{build => }/register/MemoryRegister.go (70%) rename src/{build => }/register/NewRegister.go (83%) rename src/{build => }/register/Register.go (73%) rename src/{build => }/register/RegisterIsUsed.go (78%) rename src/{build => }/register/RegisterLabel.go (77%) rename src/{build => }/register/RegisterNumber.go (86%) rename src/{build => }/register/RegisterRegister.go (79%) rename src/{build => }/register/Return.go (100%) rename src/{build => }/register/SaveRegister.go (87%) rename src/{build => }/register/Syscall.go (100%) rename src/{build => }/register/UseRegister.go (78%) rename src/{build => }/register/postInstruction.go (77%) rename src/{build => }/scanner/Scan.go (86%) rename src/{build => }/scanner/Scanner.go (77%) rename src/{build => }/scanner/queue.go (100%) rename src/{build => }/scanner/queueDirectory.go (92%) rename src/{build => }/scanner/queueFile.go (100%) rename src/{build => }/scanner/scanFile.go (92%) rename src/{build => }/scope/Scope.go (93%) rename src/{build => }/scope/Stack.go (94%) rename src/{build => }/scope/Variable.go (85%) rename src/{build => }/sizeof/Signed.go (100%) rename src/{build => }/sizeof/Signed_test.go (93%) rename src/{build => }/sizeof/Unsigned.go (100%) rename src/{build => }/sizeof/Unsigned_test.go (89%) rename src/{build => }/token/Count.go (100%) rename src/{build => }/token/Count_test.go (92%) rename src/{build => }/token/Kind.go (100%) rename src/{build => }/token/Length.go (100%) rename src/{build => }/token/List.go (54%) create mode 100644 src/token/List_test.go rename src/{build => }/token/Position.go (100%) rename src/{build => }/token/Token.go (100%) rename src/{build => }/token/Token_test.go (96%) rename src/{build => }/token/Tokenize.go (100%) rename src/{build => }/token/Tokenize_test.go (99%) rename src/{build => }/token/bench_test.go (90%) rename src/{build => }/types/New.go (100%) rename src/{build => }/types/NewList.go (60%) rename src/{build => }/types/Type.go (100%) diff --git a/README.md b/README.md index 1eafca8..695e702 100644 --- a/README.md +++ b/README.md @@ -24,79 +24,6 @@ Build a Linux x86-64 ELF executable from `examples/hello` and run it: ./q run examples/hello ``` -## Documentation - -### [main.go](main.go) - -Entry point. It simply calls `cli.Main` which we can use for testing. - -### [src/cli/Main.go](src/cli/Main.go) - -The command line interface expects a command like `build` as the first argument. -Commands are implemented as functions in the [src/cli](src/cli) directory. -Each command has its own set of parameters. - -### [src/cli/Build.go](src/cli/Build.go) - -The build command creates a new `Build` instance with the given directory and calls the `Run` method. - -If no directory is specified, it will use the current directory. - -If the `--dry` flag is specified, it will perform all tasks except the final write to disk. -This flag should be used in most tests and benchmarks to avoid needless disk writes. - -```shell -q build -q build examples/hello -q build examples/hello --dry -``` - -Adding the `-a` or `--assembler` flag shows the generated assembly instructions: - -```shell -q build examples/hello -a -``` - -Adding the `-v` or `--verbose` flag shows verbose compiler information: - -```shell -q build examples/hello -v -``` - -### [src/build/Build.go](src/build/Build.go) - -The `Build` type defines all the information needed to start building an executable file. -The name of the executable will be equal to the name of the build directory. - -`Run` starts the build which will scan all `.q` source files in the build directory. -Every source file is scanned in its own goroutine for performance reasons. -Parallelization here is possible because the order of files in a directory is not significant. - -The main thread is meanwhile waiting for new function objects to arrive from the scanners. -Once a function has arrived, it will be stored for compilation later. -We need to wait with the compilation step until we have enough information about all identifiers from the scan. - -Then all the functions that were scanned will be compiled in parallel. -We create a separate goroutine for each function compilation. -Each function will then be translated to generic assembler instructions. - -All the functions that are required to run the program will be added to the final assembler. -The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. - -### [src/build/core/Function.go](src/build/core/Function.go) - -This is the "heart" of the compiler. -Each function runs `f.Compile` which organizes the source code into an abstract syntax tree that is then compiled via `f.CompileAST`. -You can think of AST nodes as the individual statements in your source code. - -### [src/build/ast/Parse.go](src/build/ast/Parse.go) - -This is what generates the AST from tokens. - -### [src/build/expression/Parse.go](src/build/expression/Parse.go) - -This is what generates expressions from tokens. - ## Todo ### Compiler @@ -176,6 +103,79 @@ This is what generates expressions from tokens. - [ ] Mac - [ ] Windows +## Documentation + +### [main.go](main.go) + +Entry point. It simply calls `cli.Main` which we can use for testing. + +### [src/cli/Main.go](src/cli/Main.go) + +The command line interface expects a command like `build` as the first argument. +Commands are implemented as functions in the [src/cli](src/cli) directory. +Each command has its own set of parameters. + +### [src/cli/Build.go](src/cli/Build.go) + +The build command creates a new `Build` instance with the given directory and calls the `Run` method. + +If no directory is specified, it will use the current directory. + +If the `--dry` flag is specified, it will perform all tasks except the final write to disk. +This flag should be used in most tests and benchmarks to avoid needless disk writes. + +```shell +q build +q build examples/hello +q build examples/hello --dry +``` + +Adding the `-a` or `--assembler` flag shows the generated assembly instructions: + +```shell +q build examples/hello -a +``` + +Adding the `-v` or `--verbose` flag shows verbose compiler information: + +```shell +q build examples/hello -v +``` + +### [src/build/Build.go](src/build/Build.go) + +The `Build` type defines all the information needed to start building an executable file. +The name of the executable will be equal to the name of the build directory. + +`Run` starts the build which will scan all `.q` source files in the build directory. +Every source file is scanned in its own goroutine for performance reasons. +Parallelization here is possible because the order of files in a directory is not significant. + +The main thread is meanwhile waiting for new function objects to arrive from the scanners. +Once a function has arrived, it will be stored for compilation later. +We need to wait with the compilation step until we have enough information about all identifiers from the scan. + +Then all the functions that were scanned will be compiled in parallel. +We create a separate goroutine for each function compilation. +Each function will then be translated to generic assembler instructions. + +All the functions that are required to run the program will be added to the final assembler. +The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. + +### [src/core/Function.go](src/core/Function.go) + +This is the "heart" of the compiler. +Each function runs `f.Compile` which organizes the source code into an abstract syntax tree that is then compiled via `f.CompileAST`. +You can think of AST nodes as the individual statements in your source code. + +### [src/ast/Parse.go](src/ast/Parse.go) + +This is what generates the AST from tokens. + +### [src/expression/Parse.go](src/expression/Parse.go) + +This is what generates expressions from tokens. + ## Tests ```shell diff --git a/go.mod b/go.mod index 52bbab9..d8c9380 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module git.akyoto.dev/cli/q -go 1.22.4 +go 1.22.6 require ( git.akyoto.dev/go/assert v0.1.3 git.akyoto.dev/go/color v0.1.1 ) -require golang.org/x/sys v0.22.0 // indirect +require golang.org/x/sys v0.23.0 // indirect diff --git a/go.sum b/go.sum index 5e65b25..bf24815 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,5 @@ git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= git.akyoto.dev/go/color v0.1.1 h1:mMAoMIwLBPNy7ocRSxdsCFs7onPC3GfDEiJErCneqRE= git.akyoto.dev/go/color v0.1.1/go.mod h1:ywOjoD0O0sk6bIn92uAJf7mErlEFCuQInL84y4Lqi3Q= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/src/build/arch/arm64/Registers.go b/src/arch/arm64/Registers.go similarity index 85% rename from src/build/arch/arm64/Registers.go rename to src/arch/arm64/Registers.go index a00c709..3fd4f57 100644 --- a/src/build/arch/arm64/Registers.go +++ b/src/arch/arm64/Registers.go @@ -1,6 +1,6 @@ package arm64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" const ( X0 cpu.Register = iota diff --git a/src/build/arch/riscv/Registers.go b/src/arch/riscv/Registers.go similarity index 85% rename from src/build/arch/riscv/Registers.go rename to src/arch/riscv/Registers.go index 846b936..e9436d2 100644 --- a/src/build/arch/riscv/Registers.go +++ b/src/arch/riscv/Registers.go @@ -1,6 +1,6 @@ package riscv -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" const ( X0 cpu.Register = iota diff --git a/src/build/arch/x64/Add.go b/src/arch/x64/Add.go similarity index 92% rename from src/build/arch/x64/Add.go rename to src/arch/x64/Add.go index 2e55c7d..a487a3b 100644 --- a/src/build/arch/x64/Add.go +++ b/src/arch/x64/Add.go @@ -1,7 +1,7 @@ package x64 import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // AddRegisterNumber adds a number to the given register. diff --git a/src/build/arch/x64/Add_test.go b/src/arch/x64/Add_test.go similarity index 97% rename from src/build/arch/x64/Add_test.go rename to src/arch/x64/Add_test.go index 607b467..52d6645 100644 --- a/src/build/arch/x64/Add_test.go +++ b/src/arch/x64/Add_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/And.go b/src/arch/x64/And.go similarity index 92% rename from src/build/arch/x64/And.go rename to src/arch/x64/And.go index e5d8fcb..a20c3c3 100644 --- a/src/build/arch/x64/And.go +++ b/src/arch/x64/And.go @@ -1,7 +1,7 @@ package x64 import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // AndRegisterNumber performs a bitwise AND using a register and a number. diff --git a/src/build/arch/x64/Call.go b/src/arch/x64/Call.go similarity index 100% rename from src/build/arch/x64/Call.go rename to src/arch/x64/Call.go diff --git a/src/build/arch/x64/Compare.go b/src/arch/x64/Compare.go similarity index 92% rename from src/build/arch/x64/Compare.go rename to src/arch/x64/Compare.go index 49b5ba6..3c48dcf 100644 --- a/src/build/arch/x64/Compare.go +++ b/src/arch/x64/Compare.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // Compares the register with the number and sets the status flags in the EFLAGS register. func CompareRegisterNumber(code []byte, register cpu.Register, number int) []byte { diff --git a/src/build/arch/x64/Compare_test.go b/src/arch/x64/Compare_test.go similarity index 97% rename from src/build/arch/x64/Compare_test.go rename to src/arch/x64/Compare_test.go index cef49b3..6372665 100644 --- a/src/build/arch/x64/Compare_test.go +++ b/src/arch/x64/Compare_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Div.go b/src/arch/x64/Div.go similarity index 85% rename from src/build/arch/x64/Div.go rename to src/arch/x64/Div.go index 5c567bd..26439d1 100644 --- a/src/build/arch/x64/Div.go +++ b/src/arch/x64/Div.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // DivRegister divides RDX:RAX by the value in the register. func DivRegister(code []byte, divisor cpu.Register) []byte { diff --git a/src/build/arch/x64/Div_test.go b/src/arch/x64/Div_test.go similarity index 92% rename from src/build/arch/x64/Div_test.go rename to src/arch/x64/Div_test.go index 267250e..3598dbf 100644 --- a/src/build/arch/x64/Div_test.go +++ b/src/arch/x64/Div_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/ExtendRAXToRDX.go b/src/arch/x64/ExtendRAXToRDX.go similarity index 100% rename from src/build/arch/x64/ExtendRAXToRDX.go rename to src/arch/x64/ExtendRAXToRDX.go diff --git a/src/build/arch/x64/Jump.go b/src/arch/x64/Jump.go similarity index 100% rename from src/build/arch/x64/Jump.go rename to src/arch/x64/Jump.go diff --git a/src/build/arch/x64/Jump_test.go b/src/arch/x64/Jump_test.go similarity index 96% rename from src/build/arch/x64/Jump_test.go rename to src/arch/x64/Jump_test.go index 5c12133..fe40c50 100644 --- a/src/build/arch/x64/Jump_test.go +++ b/src/arch/x64/Jump_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Load.go b/src/arch/x64/Load.go similarity index 85% rename from src/build/arch/x64/Load.go rename to src/arch/x64/Load.go index 18e36d2..eb5ac51 100644 --- a/src/build/arch/x64/Load.go +++ b/src/arch/x64/Load.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // LoadRegister loads from memory into a register. func LoadRegister(code []byte, destination cpu.Register, offset byte, length byte, source cpu.Register) []byte { diff --git a/src/build/arch/x64/Load_test.go b/src/arch/x64/Load_test.go similarity index 99% rename from src/build/arch/x64/Load_test.go rename to src/arch/x64/Load_test.go index f906a28..31330c6 100644 --- a/src/build/arch/x64/Load_test.go +++ b/src/arch/x64/Load_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/ModRM.go b/src/arch/x64/ModRM.go similarity index 100% rename from src/build/arch/x64/ModRM.go rename to src/arch/x64/ModRM.go diff --git a/src/build/arch/x64/ModRM_test.go b/src/arch/x64/ModRM_test.go similarity index 95% rename from src/build/arch/x64/ModRM_test.go rename to src/arch/x64/ModRM_test.go index e9d470f..edcaffe 100644 --- a/src/build/arch/x64/ModRM_test.go +++ b/src/arch/x64/ModRM_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Move.go b/src/arch/x64/Move.go similarity index 94% rename from src/build/arch/x64/Move.go rename to src/arch/x64/Move.go index cac1d09..48edc79 100644 --- a/src/build/arch/x64/Move.go +++ b/src/arch/x64/Move.go @@ -3,8 +3,8 @@ package x64 import ( "encoding/binary" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/sizeof" ) // MoveRegisterNumber moves an integer into the given register. diff --git a/src/build/arch/x64/Move_test.go b/src/arch/x64/Move_test.go similarity index 98% rename from src/build/arch/x64/Move_test.go rename to src/arch/x64/Move_test.go index 66cfb4a..faf9fcc 100644 --- a/src/build/arch/x64/Move_test.go +++ b/src/arch/x64/Move_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Mul.go b/src/arch/x64/Mul.go similarity index 91% rename from src/build/arch/x64/Mul.go rename to src/arch/x64/Mul.go index b6d7883..6c0cbea 100644 --- a/src/build/arch/x64/Mul.go +++ b/src/arch/x64/Mul.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // MulRegisterNumber multiplies a register with a number. func MulRegisterNumber(code []byte, destination cpu.Register, number int) []byte { diff --git a/src/build/arch/x64/Mul_test.go b/src/arch/x64/Mul_test.go similarity index 97% rename from src/build/arch/x64/Mul_test.go rename to src/arch/x64/Mul_test.go index 5c029dd..cd8da70 100644 --- a/src/build/arch/x64/Mul_test.go +++ b/src/arch/x64/Mul_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Negate.go b/src/arch/x64/Negate.go similarity index 81% rename from src/build/arch/x64/Negate.go rename to src/arch/x64/Negate.go index 00ae530..479c143 100644 --- a/src/build/arch/x64/Negate.go +++ b/src/arch/x64/Negate.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // NegateRegister negates the value in the register. func NegateRegister(code []byte, register cpu.Register) []byte { diff --git a/src/build/arch/x64/Negate_test.go b/src/arch/x64/Negate_test.go similarity index 92% rename from src/build/arch/x64/Negate_test.go rename to src/arch/x64/Negate_test.go index dd64518..98aa13e 100644 --- a/src/build/arch/x64/Negate_test.go +++ b/src/arch/x64/Negate_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Or.go b/src/arch/x64/Or.go similarity index 92% rename from src/build/arch/x64/Or.go rename to src/arch/x64/Or.go index 2252925..fec37da 100644 --- a/src/build/arch/x64/Or.go +++ b/src/arch/x64/Or.go @@ -1,7 +1,7 @@ package x64 import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // OrRegisterNumber performs a bitwise OR using a register and a number. diff --git a/src/build/arch/x64/Pop.go b/src/arch/x64/Pop.go similarity index 86% rename from src/build/arch/x64/Pop.go rename to src/arch/x64/Pop.go index 4ce82ec..b97182f 100644 --- a/src/build/arch/x64/Pop.go +++ b/src/arch/x64/Pop.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // PopRegister pops a value from the stack and saves it into the register. func PopRegister(code []byte, register cpu.Register) []byte { diff --git a/src/build/arch/x64/Pop_test.go b/src/arch/x64/Pop_test.go similarity index 91% rename from src/build/arch/x64/Pop_test.go rename to src/arch/x64/Pop_test.go index 5375bde..0a4ce98 100644 --- a/src/build/arch/x64/Pop_test.go +++ b/src/arch/x64/Pop_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Push.go b/src/arch/x64/Push.go similarity index 86% rename from src/build/arch/x64/Push.go rename to src/arch/x64/Push.go index 944d1e4..bdae334 100644 --- a/src/build/arch/x64/Push.go +++ b/src/arch/x64/Push.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // PushRegister pushes the value inside the register onto the stack. func PushRegister(code []byte, register cpu.Register) []byte { diff --git a/src/build/arch/x64/Push_test.go b/src/arch/x64/Push_test.go similarity index 91% rename from src/build/arch/x64/Push_test.go rename to src/arch/x64/Push_test.go index 9177532..0dc092b 100644 --- a/src/build/arch/x64/Push_test.go +++ b/src/arch/x64/Push_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/REX.go b/src/arch/x64/REX.go similarity index 100% rename from src/build/arch/x64/REX.go rename to src/arch/x64/REX.go diff --git a/src/build/arch/x64/REX_test.go b/src/arch/x64/REX_test.go similarity index 94% rename from src/build/arch/x64/REX_test.go rename to src/arch/x64/REX_test.go index e48e3c5..b212b24 100644 --- a/src/build/arch/x64/REX_test.go +++ b/src/arch/x64/REX_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Registers.go b/src/arch/x64/Registers.go similarity index 92% rename from src/build/arch/x64/Registers.go rename to src/arch/x64/Registers.go index e8ecbc8..9162141 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/arch/x64/Registers.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" const ( RAX cpu.Register = iota diff --git a/src/build/arch/x64/Return.go b/src/arch/x64/Return.go similarity index 100% rename from src/build/arch/x64/Return.go rename to src/arch/x64/Return.go diff --git a/src/build/arch/x64/SIB.go b/src/arch/x64/SIB.go similarity index 100% rename from src/build/arch/x64/SIB.go rename to src/arch/x64/SIB.go diff --git a/src/build/arch/x64/SIB_test.go b/src/arch/x64/SIB_test.go similarity index 95% rename from src/build/arch/x64/SIB_test.go rename to src/arch/x64/SIB_test.go index 690f04b..7dedf6e 100644 --- a/src/build/arch/x64/SIB_test.go +++ b/src/arch/x64/SIB_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Shift.go b/src/arch/x64/Shift.go similarity index 93% rename from src/build/arch/x64/Shift.go rename to src/arch/x64/Shift.go index 2f88cb1..6fa2251 100644 --- a/src/build/arch/x64/Shift.go +++ b/src/arch/x64/Shift.go @@ -1,7 +1,7 @@ package x64 import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // ShiftLeftNumber shifts the register value by `bitCount` bits to the left. diff --git a/src/build/arch/x64/Store.go b/src/arch/x64/Store.go similarity index 95% rename from src/build/arch/x64/Store.go rename to src/arch/x64/Store.go index f7c8f5b..01bdfc6 100644 --- a/src/build/arch/x64/Store.go +++ b/src/arch/x64/Store.go @@ -3,7 +3,7 @@ package x64 import ( "encoding/binary" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // StoreNumber stores a number into the memory address included in the given register. diff --git a/src/build/arch/x64/Store_test.go b/src/arch/x64/Store_test.go similarity index 99% rename from src/build/arch/x64/Store_test.go rename to src/arch/x64/Store_test.go index 81758d6..1d9d9a9 100644 --- a/src/build/arch/x64/Store_test.go +++ b/src/arch/x64/Store_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Sub.go b/src/arch/x64/Sub.go similarity index 92% rename from src/build/arch/x64/Sub.go rename to src/arch/x64/Sub.go index 268fcc6..fb9a649 100644 --- a/src/build/arch/x64/Sub.go +++ b/src/arch/x64/Sub.go @@ -1,7 +1,7 @@ package x64 import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // SubRegisterNumber subtracts a number from the given register. diff --git a/src/build/arch/x64/Sub_test.go b/src/arch/x64/Sub_test.go similarity index 97% rename from src/build/arch/x64/Sub_test.go rename to src/arch/x64/Sub_test.go index 69199e9..d6898e9 100644 --- a/src/build/arch/x64/Sub_test.go +++ b/src/arch/x64/Sub_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Syscall.go b/src/arch/x64/Syscall.go similarity index 100% rename from src/build/arch/x64/Syscall.go rename to src/arch/x64/Syscall.go diff --git a/src/build/arch/x64/Xor.go b/src/arch/x64/Xor.go similarity index 92% rename from src/build/arch/x64/Xor.go rename to src/arch/x64/Xor.go index 305dce2..8a469eb 100644 --- a/src/build/arch/x64/Xor.go +++ b/src/arch/x64/Xor.go @@ -1,7 +1,7 @@ package x64 import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // XorRegisterNumber performs a bitwise XOR using a register and a number. diff --git a/src/build/arch/x64/encode.go b/src/arch/x64/encode.go similarity index 94% rename from src/build/arch/x64/encode.go rename to src/arch/x64/encode.go index 1f45001..6e872b5 100644 --- a/src/build/arch/x64/encode.go +++ b/src/arch/x64/encode.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // encode is the core function that encodes an instruction. func encode(code []byte, mod AddressMode, reg cpu.Register, rm cpu.Register, numBytes byte, opCodes ...byte) []byte { diff --git a/src/build/arch/x64/encodeNum.go b/src/arch/x64/encodeNum.go similarity index 86% rename from src/build/arch/x64/encodeNum.go rename to src/arch/x64/encodeNum.go index a9a3313..c6db324 100644 --- a/src/build/arch/x64/encodeNum.go +++ b/src/arch/x64/encodeNum.go @@ -3,8 +3,8 @@ package x64 import ( "encoding/binary" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/sizeof" ) // encodeNum encodes an instruction with up to two registers and a number parameter. diff --git a/src/build/arch/x64/memoryAccess.go b/src/arch/x64/memoryAccess.go similarity index 93% rename from src/build/arch/x64/memoryAccess.go rename to src/arch/x64/memoryAccess.go index b8cafa5..1ff2bca 100644 --- a/src/build/arch/x64/memoryAccess.go +++ b/src/arch/x64/memoryAccess.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // memoryAccess encodes a memory access. func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { diff --git a/src/build/arch/x64/x64_test.go b/src/arch/x64/x64_test.go similarity index 92% rename from src/build/arch/x64/x64_test.go rename to src/arch/x64/x64_test.go index cdde6cf..0ed00d5 100644 --- a/src/build/arch/x64/x64_test.go +++ b/src/arch/x64/x64_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/build/asm/Assembler.go b/src/asm/Assembler.go similarity index 93% rename from src/build/asm/Assembler.go rename to src/asm/Assembler.go index 4a595cf..c5c9b8d 100644 --- a/src/build/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -3,7 +3,7 @@ package asm import ( "maps" - "git.akyoto.dev/cli/q/src/build/data" + "git.akyoto.dev/cli/q/src/data" ) // Assembler contains a list of instructions. diff --git a/src/build/asm/Finalize.go b/src/asm/Finalize.go similarity index 98% rename from src/build/asm/Finalize.go rename to src/asm/Finalize.go index 093e060..446048a 100644 --- a/src/build/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -6,10 +6,10 @@ import ( "slices" "strings" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/elf" - "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/sizeof" ) // Finalize generates the final machine code. diff --git a/src/build/asm/Instruction.go b/src/asm/Instruction.go similarity index 100% rename from src/build/asm/Instruction.go rename to src/asm/Instruction.go diff --git a/src/build/asm/Instructions.go b/src/asm/Instructions.go similarity index 100% rename from src/build/asm/Instructions.go rename to src/asm/Instructions.go diff --git a/src/build/asm/Label.go b/src/asm/Label.go similarity index 100% rename from src/build/asm/Label.go rename to src/asm/Label.go diff --git a/src/build/asm/Memory.go b/src/asm/Memory.go similarity index 65% rename from src/build/asm/Memory.go rename to src/asm/Memory.go index ef209ec..3e98a71 100644 --- a/src/build/asm/Memory.go +++ b/src/asm/Memory.go @@ -1,6 +1,6 @@ package asm -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" type Memory struct { Base cpu.Register diff --git a/src/build/asm/MemoryNumber.go b/src/asm/MemoryNumber.go similarity index 100% rename from src/build/asm/MemoryNumber.go rename to src/asm/MemoryNumber.go diff --git a/src/build/asm/MemoryRegister.go b/src/asm/MemoryRegister.go similarity index 94% rename from src/build/asm/MemoryRegister.go rename to src/asm/MemoryRegister.go index 927a97c..21232ce 100644 --- a/src/build/asm/MemoryRegister.go +++ b/src/asm/MemoryRegister.go @@ -3,7 +3,7 @@ package asm import ( "fmt" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // MemoryRegister operates with a memory address and a number. diff --git a/src/build/asm/Mnemonic.go b/src/asm/Mnemonic.go similarity index 100% rename from src/build/asm/Mnemonic.go rename to src/asm/Mnemonic.go diff --git a/src/build/asm/Optimizer.go b/src/asm/Optimizer.go similarity index 93% rename from src/build/asm/Optimizer.go rename to src/asm/Optimizer.go index 2ef72ae..7bf6b99 100644 --- a/src/build/asm/Optimizer.go +++ b/src/asm/Optimizer.go @@ -1,6 +1,6 @@ package asm -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // unnecessary returns true if the register/register operation can be skipped. func (a *Assembler) unnecessary(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool { diff --git a/src/build/asm/Pointer.go b/src/asm/Pointer.go similarity index 100% rename from src/build/asm/Pointer.go rename to src/asm/Pointer.go diff --git a/src/build/asm/Register.go b/src/asm/Register.go similarity index 92% rename from src/build/asm/Register.go rename to src/asm/Register.go index 0f737c9..7188752 100644 --- a/src/build/asm/Register.go +++ b/src/asm/Register.go @@ -1,7 +1,7 @@ package asm import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // Register operates with a single register. diff --git a/src/build/asm/RegisterLabel.go b/src/asm/RegisterLabel.go similarity index 94% rename from src/build/asm/RegisterLabel.go rename to src/asm/RegisterLabel.go index f51bf77..8092d9f 100644 --- a/src/build/asm/RegisterLabel.go +++ b/src/asm/RegisterLabel.go @@ -3,7 +3,7 @@ package asm import ( "fmt" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // RegisterLabel operates with a register and a label. diff --git a/src/build/asm/RegisterNumber.go b/src/asm/RegisterNumber.go similarity index 94% rename from src/build/asm/RegisterNumber.go rename to src/asm/RegisterNumber.go index b3bcb9e..6addf74 100644 --- a/src/build/asm/RegisterNumber.go +++ b/src/asm/RegisterNumber.go @@ -3,7 +3,7 @@ package asm import ( "fmt" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // RegisterNumber operates with a register and a number. diff --git a/src/build/asm/RegisterRegister.go b/src/asm/RegisterRegister.go similarity index 94% rename from src/build/asm/RegisterRegister.go rename to src/asm/RegisterRegister.go index 584058d..8631fd6 100644 --- a/src/build/asm/RegisterRegister.go +++ b/src/asm/RegisterRegister.go @@ -3,7 +3,7 @@ package asm import ( "fmt" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // RegisterRegister operates with two registers. diff --git a/src/build/ast/AST.go b/src/ast/AST.go similarity index 100% rename from src/build/ast/AST.go rename to src/ast/AST.go diff --git a/src/build/ast/Assert.go b/src/ast/Assert.go similarity index 78% rename from src/build/ast/Assert.go rename to src/ast/Assert.go index 0f537db..6da47fb 100644 --- a/src/build/ast/Assert.go +++ b/src/ast/Assert.go @@ -1,7 +1,7 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/expression" ) // Assert represents a condition that must be true, otherwise the program stops. diff --git a/src/build/ast/Assign.go b/src/ast/Assign.go similarity index 78% rename from src/build/ast/Assign.go rename to src/ast/Assign.go index 161f968..8bdbe99 100644 --- a/src/build/ast/Assign.go +++ b/src/ast/Assign.go @@ -1,7 +1,7 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/expression" ) // Assign represents an assignment to an existing variable or memory location. diff --git a/src/build/ast/Call.go b/src/ast/Call.go similarity index 67% rename from src/build/ast/Call.go rename to src/ast/Call.go index 6bd7dec..8aa683e 100644 --- a/src/build/ast/Call.go +++ b/src/ast/Call.go @@ -1,6 +1,6 @@ package ast -import "git.akyoto.dev/cli/q/src/build/expression" +import "git.akyoto.dev/cli/q/src/expression" // Call represents a function call. type Call struct { diff --git a/src/build/ast/Count.go b/src/ast/Count.go similarity index 95% rename from src/build/ast/Count.go rename to src/ast/Count.go index 2c61203..2c9f1f7 100644 --- a/src/build/ast/Count.go +++ b/src/ast/Count.go @@ -1,6 +1,6 @@ package ast -import "git.akyoto.dev/cli/q/src/build/token" +import "git.akyoto.dev/cli/q/src/token" // Count counts how often the given token appears in the AST. func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { diff --git a/src/build/ast/Define.go b/src/ast/Define.go similarity index 73% rename from src/build/ast/Define.go rename to src/ast/Define.go index 551c9b7..0b8030c 100644 --- a/src/build/ast/Define.go +++ b/src/ast/Define.go @@ -1,7 +1,7 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/expression" ) // Define represents a variable definition. diff --git a/src/build/ast/EachInstruction.go b/src/ast/EachInstruction.go similarity index 95% rename from src/build/ast/EachInstruction.go rename to src/ast/EachInstruction.go index 927cca6..b1a354f 100644 --- a/src/build/ast/EachInstruction.go +++ b/src/ast/EachInstruction.go @@ -1,6 +1,6 @@ package ast -import "git.akyoto.dev/cli/q/src/build/token" +import "git.akyoto.dev/cli/q/src/token" // EachInstruction calls the function on each instruction. func EachInstruction(body token.List, call func(token.List) error) error { diff --git a/src/build/ast/If.go b/src/ast/If.go similarity index 75% rename from src/build/ast/If.go rename to src/ast/If.go index f99307f..079640d 100644 --- a/src/build/ast/If.go +++ b/src/ast/If.go @@ -1,7 +1,7 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/expression" ) // If represents an if statement. diff --git a/src/build/ast/Loop.go b/src/ast/Loop.go similarity index 100% rename from src/build/ast/Loop.go rename to src/ast/Loop.go diff --git a/src/build/ast/Parse.go b/src/ast/Parse.go similarity index 91% rename from src/build/ast/Parse.go rename to src/ast/Parse.go index 582f497..8b9083e 100644 --- a/src/build/ast/Parse.go +++ b/src/ast/Parse.go @@ -1,8 +1,8 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // Parse generates an AST from a list of tokens. diff --git a/src/build/ast/Return.go b/src/ast/Return.go similarity index 73% rename from src/build/ast/Return.go rename to src/ast/Return.go index 6c8aa2d..8815638 100644 --- a/src/build/ast/Return.go +++ b/src/ast/Return.go @@ -1,7 +1,7 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/expression" ) // Return represents a return statement. diff --git a/src/build/ast/Switch.go b/src/ast/Switch.go similarity index 82% rename from src/build/ast/Switch.go rename to src/ast/Switch.go index 80ff3d3..a5fc6a0 100644 --- a/src/build/ast/Switch.go +++ b/src/ast/Switch.go @@ -1,7 +1,7 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/expression" ) // Switch represents a switch statement. diff --git a/src/build/ast/parseKeyword.go b/src/ast/parseKeyword.go similarity index 95% rename from src/build/ast/parseKeyword.go rename to src/ast/parseKeyword.go index f4a948a..32c2bbd 100644 --- a/src/build/ast/parseKeyword.go +++ b/src/ast/parseKeyword.go @@ -1,9 +1,9 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // parseKeyword generates a keyword node from an instruction. diff --git a/src/build/ast/parseNode.go b/src/ast/parseNode.go similarity index 87% rename from src/build/ast/parseNode.go rename to src/ast/parseNode.go index 8473c29..93fada4 100644 --- a/src/build/ast/parseNode.go +++ b/src/ast/parseNode.go @@ -1,9 +1,9 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // parseNode generates an AST node from an instruction. diff --git a/src/build/ast/parseSwitch.go b/src/ast/parseSwitch.go similarity index 89% rename from src/build/ast/parseSwitch.go rename to src/ast/parseSwitch.go index 752405a..65eb712 100644 --- a/src/build/ast/parseSwitch.go +++ b/src/ast/parseSwitch.go @@ -1,8 +1,8 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // parseSwitch generates the cases inside a switch statement. diff --git a/src/build/Build.go b/src/build/Build.go index 7f407ce..2e740c6 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -4,8 +4,8 @@ import ( "path/filepath" "strings" - "git.akyoto.dev/cli/q/src/build/compiler" - "git.akyoto.dev/cli/q/src/build/scanner" + "git.akyoto.dev/cli/q/src/compiler" + "git.akyoto.dev/cli/q/src/scanner" ) // Build describes a compiler build. diff --git a/src/build/expression/List.go b/src/build/expression/List.go deleted file mode 100644 index 75c44d0..0000000 --- a/src/build/expression/List.go +++ /dev/null @@ -1,55 +0,0 @@ -package expression - -import ( - "git.akyoto.dev/cli/q/src/build/token" -) - -// NewList generates a list of expressions from comma separated parameters. -func NewList(tokens token.List) []*Expression { - var list []*Expression - - EachParameter(tokens, func(parameter token.List) error { - expression := Parse(parameter) - list = append(list, expression) - return nil - }) - - return list -} - -// EachParameter calls the callback function on each parameter in a comma separated list. -func EachParameter(tokens token.List, call func(token.List) error) error { - start := 0 - groupLevel := 0 - - for i, t := range tokens { - switch t.Kind { - case token.GroupStart, token.ArrayStart, token.BlockStart: - groupLevel++ - - case token.GroupEnd, token.ArrayEnd, token.BlockEnd: - groupLevel-- - - case token.Separator: - if groupLevel > 0 { - continue - } - - parameter := tokens[start:i] - err := call(parameter) - - if err != nil { - return err - } - - start = i + 1 - } - } - - if start != len(tokens) { - parameter := tokens[start:] - return call(parameter) - } - - return nil -} diff --git a/src/build/token/List_test.go b/src/build/token/List_test.go deleted file mode 100644 index b4529f1..0000000 --- a/src/build/token/List_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package token_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/go/assert" -) - -func TestIndexKind(t *testing.T) { - tokens := token.Tokenize([]byte("a{{}}")) - assert.Equal(t, tokens.IndexKind(token.NewLine), -1) - assert.Equal(t, tokens.LastIndexKind(token.NewLine), -1) - assert.Equal(t, tokens.IndexKind(token.BlockStart), 1) - assert.Equal(t, tokens.LastIndexKind(token.BlockStart), 2) - assert.Equal(t, tokens.IndexKind(token.BlockEnd), 3) - assert.Equal(t, tokens.LastIndexKind(token.BlockEnd), 4) -} diff --git a/src/cli/Build.go b/src/cli/Build.go index ba79ec6..ea5926e 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -6,8 +6,8 @@ import ( "strings" "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/errors" ) // Build parses the arguments and creates a build. diff --git a/src/cli/Run.go b/src/cli/Run.go index 7de865b..20b49f6 100644 --- a/src/cli/Run.go +++ b/src/cli/Run.go @@ -5,7 +5,7 @@ import ( "os" "os/exec" - "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/errors" ) // Run builds and runs the executable. diff --git a/src/cli/System.go b/src/cli/System.go index b523d7a..a2a6801 100644 --- a/src/cli/System.go +++ b/src/cli/System.go @@ -5,7 +5,7 @@ import ( "runtime" "strconv" - "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/config" ) // System shows system information. diff --git a/src/build/compiler/Compile.go b/src/compiler/Compile.go similarity index 94% rename from src/build/compiler/Compile.go rename to src/compiler/Compile.go index 4b31d9a..5d17cd5 100644 --- a/src/build/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -3,9 +3,9 @@ package compiler import ( "sync" - "git.akyoto.dev/cli/q/src/build/core" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" ) // Compile waits for the scan to finish and compiles all functions. diff --git a/src/build/compiler/Result.go b/src/compiler/Result.go similarity index 93% rename from src/build/compiler/Result.go rename to src/compiler/Result.go index d9e699a..ca51ba1 100644 --- a/src/build/compiler/Result.go +++ b/src/compiler/Result.go @@ -5,11 +5,11 @@ import ( "io" "os" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/core" - "git.akyoto.dev/cli/q/src/build/elf" - "git.akyoto.dev/cli/q/src/build/os/linux" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/os/linux" ) // Result contains all the compiled functions in a build. diff --git a/src/build/config/config.go b/src/config/config.go similarity index 100% rename from src/build/config/config.go rename to src/config/config.go diff --git a/src/build/config/init.go b/src/config/init.go similarity index 100% rename from src/build/config/init.go rename to src/config/init.go diff --git a/src/build/core/AddBytes.go b/src/core/AddBytes.go similarity index 100% rename from src/build/core/AddBytes.go rename to src/core/AddBytes.go diff --git a/src/build/core/Compare.go b/src/core/Compare.go similarity index 85% rename from src/build/core/Compare.go rename to src/core/Compare.go index c884a82..0244335 100644 --- a/src/build/core/Compare.go +++ b/src/core/Compare.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // Compare evaluates a boolean expression. diff --git a/src/build/core/Compile.go b/src/core/Compile.go similarity index 100% rename from src/build/core/Compile.go rename to src/core/Compile.go diff --git a/src/build/core/CompileAST.go b/src/core/CompileAST.go similarity index 86% rename from src/build/core/CompileAST.go rename to src/core/CompileAST.go index a2faba8..a9d54f5 100644 --- a/src/build/core/CompileAST.go +++ b/src/core/CompileAST.go @@ -1,7 +1,7 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/ast" ) // CompileAST compiles an abstract syntax tree. diff --git a/src/build/core/CompileASTNode.go b/src/core/CompileASTNode.go similarity index 95% rename from src/build/core/CompileASTNode.go rename to src/core/CompileASTNode.go index ddbd05a..e5daa67 100644 --- a/src/build/core/CompileASTNode.go +++ b/src/core/CompileASTNode.go @@ -1,7 +1,7 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/ast" ) // CompileASTNode compiles a node in the AST. diff --git a/src/build/core/CompileAssert.go b/src/core/CompileAssert.go similarity index 86% rename from src/build/core/CompileAssert.go rename to src/core/CompileAssert.go index f601624..f8608e0 100644 --- a/src/build/core/CompileAssert.go +++ b/src/core/CompileAssert.go @@ -3,8 +3,8 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" ) // CompileAssert compiles an assertion. diff --git a/src/build/core/CompileAssign.go b/src/core/CompileAssign.go similarity index 87% rename from src/build/core/CompileAssign.go rename to src/core/CompileAssign.go index d157792..d048b80 100644 --- a/src/build/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" ) // CompileAssign compiles an assign statement. diff --git a/src/build/core/CompileAssignArray.go b/src/core/CompileAssignArray.go similarity index 86% rename from src/build/core/CompileAssignArray.go rename to src/core/CompileAssignArray.go index 8f812d5..8fb040b 100644 --- a/src/build/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/errors" ) // CompileAssignArray compiles an assign statement for array elements. diff --git a/src/build/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go similarity index 87% rename from src/build/core/CompileAssignDivision.go rename to src/core/CompileAssignDivision.go index 9a06c61..7531001 100644 --- a/src/build/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/errors" ) // CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right. diff --git a/src/build/core/CompileCall.go b/src/core/CompileCall.go similarity index 95% rename from src/build/core/CompileCall.go rename to src/core/CompileCall.go index 16c14f2..5036f1f 100644 --- a/src/build/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -3,9 +3,9 @@ package core import ( "strings" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" ) // CompileCall executes a function call. diff --git a/src/build/core/CompileCondition.go b/src/core/CompileCondition.go similarity index 95% rename from src/build/core/CompileCondition.go rename to src/core/CompileCondition.go index b43a65f..28b16b9 100644 --- a/src/build/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -3,8 +3,8 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // CompileCondition inserts code to jump to the start label or end label depending on the truth of the condition. diff --git a/src/build/core/CompileDefinition.go b/src/core/CompileDefinition.go similarity index 84% rename from src/build/core/CompileDefinition.go rename to src/core/CompileDefinition.go index 81ea095..90993d9 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" ) // CompileDefinition compiles a variable definition. diff --git a/src/build/core/CompileIf.go b/src/core/CompileIf.go similarity index 92% rename from src/build/core/CompileIf.go rename to src/core/CompileIf.go index ec15dcb..e7ce92c 100644 --- a/src/build/core/CompileIf.go +++ b/src/core/CompileIf.go @@ -3,8 +3,8 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" ) // CompileIf compiles a branch instruction. diff --git a/src/build/core/CompileLoop.go b/src/core/CompileLoop.go similarity index 85% rename from src/build/core/CompileLoop.go rename to src/core/CompileLoop.go index 83b8c1a..f2ef623 100644 --- a/src/build/core/CompileLoop.go +++ b/src/core/CompileLoop.go @@ -3,8 +3,8 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" ) // CompileLoop compiles a loop instruction. diff --git a/src/build/core/CompileReturn.go b/src/core/CompileReturn.go similarity index 84% rename from src/build/core/CompileReturn.go rename to src/core/CompileReturn.go index e15bc22..754b4b8 100644 --- a/src/build/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/types" ) // CompileReturn compiles a return instruction. diff --git a/src/build/core/CompileSwitch.go b/src/core/CompileSwitch.go similarity index 92% rename from src/build/core/CompileSwitch.go rename to src/core/CompileSwitch.go index 7d0e0f3..7a08153 100644 --- a/src/build/core/CompileSwitch.go +++ b/src/core/CompileSwitch.go @@ -3,8 +3,8 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" ) // CompileSwitch compiles a multi-branch instruction. diff --git a/src/build/core/CompileSyscall.go b/src/core/CompileSyscall.go similarity index 92% rename from src/build/core/CompileSyscall.go rename to src/core/CompileSyscall.go index 63b3a23..cadce36 100644 --- a/src/build/core/CompileSyscall.go +++ b/src/core/CompileSyscall.go @@ -1,7 +1,7 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/expression" ) // CompileSyscall executes a kernel syscall. diff --git a/src/build/core/CompileTokens.go b/src/core/CompileTokens.go similarity index 69% rename from src/build/core/CompileTokens.go rename to src/core/CompileTokens.go index 634c5d7..06bff0f 100644 --- a/src/build/core/CompileTokens.go +++ b/src/core/CompileTokens.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" ) // CompileTokens compiles a token list. diff --git a/src/build/core/Defer.go b/src/core/Defer.go similarity index 100% rename from src/build/core/Defer.go rename to src/core/Defer.go diff --git a/src/build/core/Define.go b/src/core/Define.go similarity index 78% rename from src/build/core/Define.go rename to src/core/Define.go index 0867f1b..ecd4178 100644 --- a/src/build/core/Define.go +++ b/src/core/Define.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/scope" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/scope" + "git.akyoto.dev/cli/q/src/token" ) // Define defines a new variable. diff --git a/src/build/core/Evaluate.go b/src/core/Evaluate.go similarity index 77% rename from src/build/core/Evaluate.go rename to src/core/Evaluate.go index 145f3b1..ed23b7d 100644 --- a/src/build/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. diff --git a/src/build/core/Execute.go b/src/core/Execute.go similarity index 80% rename from src/build/core/Execute.go rename to src/core/Execute.go index e4b7010..74132b8 100644 --- a/src/build/core/Execute.go +++ b/src/core/Execute.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // Execute executes an operation on a register with a value operand. diff --git a/src/build/core/ExecuteLeaf.go b/src/core/ExecuteLeaf.go similarity index 88% rename from src/build/core/ExecuteLeaf.go rename to src/core/ExecuteLeaf.go index 2254a8a..45cbe23 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/core/ExecuteLeaf.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" ) // ExecuteLeaf performs an operation on a register with the given leaf operand. diff --git a/src/build/core/ExecuteRegister.go b/src/core/ExecuteRegister.go similarity index 71% rename from src/build/core/ExecuteRegister.go rename to src/core/ExecuteRegister.go index 6627acf..8a48bd5 100644 --- a/src/build/core/ExecuteRegister.go +++ b/src/core/ExecuteRegister.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" ) // ExecuteRegister performs an operation on a single register. diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/core/ExecuteRegisterNumber.go similarity index 89% rename from src/build/core/ExecuteRegisterNumber.go rename to src/core/ExecuteRegisterNumber.go index 63a45f6..e3cfda4 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/core/ExecuteRegisterNumber.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" ) // ExecuteRegisterNumber performs an operation on a register and a number. diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/core/ExecuteRegisterRegister.go similarity index 88% rename from src/build/core/ExecuteRegisterRegister.go rename to src/core/ExecuteRegisterRegister.go index 42363ae..738ed9c 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/core/ExecuteRegisterRegister.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" ) // ExecuteRegisterRegister performs an operation on two registers. diff --git a/src/build/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go similarity index 80% rename from src/build/core/ExpressionToMemory.go rename to src/core/ExpressionToMemory.go index 204e17d..f39e734 100644 --- a/src/build/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/sizeof" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/sizeof" + "git.akyoto.dev/cli/q/src/types" ) // ExpressionToMemory puts the result of an expression into the specified memory address. diff --git a/src/build/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go similarity index 87% rename from src/build/core/ExpressionToRegister.go rename to src/core/ExpressionToRegister.go index 7d7d834..8b4fa20 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -1,13 +1,13 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" ) // ExpressionToRegister puts the result of an expression into the specified register. diff --git a/src/build/core/ExpressionsToRegisters.go b/src/core/ExpressionsToRegisters.go similarity index 82% rename from src/build/core/ExpressionsToRegisters.go rename to src/core/ExpressionsToRegisters.go index 982701a..4f2dbed 100644 --- a/src/build/core/ExpressionsToRegisters.go +++ b/src/core/ExpressionsToRegisters.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/expression" ) // ExpressionsToRegisters moves multiple expressions into the specified registers. diff --git a/src/build/core/Fold.go b/src/core/Fold.go similarity index 89% rename from src/build/core/Fold.go rename to src/core/Fold.go index 11b6d59..983d42f 100644 --- a/src/build/core/Fold.go +++ b/src/core/Fold.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // Fold will try to precalculate the results of operations with constants. diff --git a/src/build/core/Function.go b/src/core/Function.go similarity index 75% rename from src/build/core/Function.go rename to src/core/Function.go index 5f8c25c..4ff2c4d 100644 --- a/src/build/core/Function.go +++ b/src/core/Function.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/fs" - "git.akyoto.dev/cli/q/src/build/register" - "git.akyoto.dev/cli/q/src/build/scope" - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/cli/q/src/scope" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" ) // Function represents the smallest unit of code. diff --git a/src/build/core/IdentifierExists.go b/src/core/IdentifierExists.go similarity index 100% rename from src/build/core/IdentifierExists.go rename to src/core/IdentifierExists.go diff --git a/src/build/core/JumpIfFalse.go b/src/core/JumpIfFalse.go similarity index 85% rename from src/build/core/JumpIfFalse.go rename to src/core/JumpIfFalse.go index 042a676..44eb1ba 100644 --- a/src/build/core/JumpIfFalse.go +++ b/src/core/JumpIfFalse.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/token" ) // JumpIfFalse jumps to the label if the previous comparison was false. diff --git a/src/build/core/JumpIfTrue.go b/src/core/JumpIfTrue.go similarity index 85% rename from src/build/core/JumpIfTrue.go rename to src/core/JumpIfTrue.go index 059618e..c8dc487 100644 --- a/src/build/core/JumpIfTrue.go +++ b/src/core/JumpIfTrue.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/token" ) // JumpIfTrue jumps to the label if the previous comparison was true. diff --git a/src/build/core/NewFunction.go b/src/core/NewFunction.go similarity index 72% rename from src/build/core/NewFunction.go rename to src/core/NewFunction.go index 736bf56..0f7bccc 100644 --- a/src/build/core/NewFunction.go +++ b/src/core/NewFunction.go @@ -1,13 +1,13 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/fs" - "git.akyoto.dev/cli/q/src/build/register" - "git.akyoto.dev/cli/q/src/build/scope" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/cli/q/src/scope" + "git.akyoto.dev/cli/q/src/token" ) // NewFunction creates a new function. diff --git a/src/build/core/Number.go b/src/core/Number.go similarity index 92% rename from src/build/core/Number.go rename to src/core/Number.go index 394666f..da39481 100644 --- a/src/build/core/Number.go +++ b/src/core/Number.go @@ -5,8 +5,8 @@ import ( "strings" "unicode/utf8" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" ) // Number tries to convert the token into a numeric value. diff --git a/src/build/core/PrintInstructions.go b/src/core/PrintInstructions.go similarity index 97% rename from src/build/core/PrintInstructions.go rename to src/core/PrintInstructions.go index a824f3a..fe47d3a 100644 --- a/src/build/core/PrintInstructions.go +++ b/src/core/PrintInstructions.go @@ -4,7 +4,7 @@ import ( "bytes" "fmt" - "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/go/color/ansi" ) diff --git a/src/build/core/String.go b/src/core/String.go similarity index 100% rename from src/build/core/String.go rename to src/core/String.go diff --git a/src/build/core/TokenToRegister.go b/src/core/TokenToRegister.go similarity index 84% rename from src/build/core/TokenToRegister.go rename to src/core/TokenToRegister.go index 1d3df22..d33c1a7 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" ) // TokenToRegister moves a token into a register. diff --git a/src/build/core/UsesRegister.go b/src/core/UsesRegister.go similarity index 84% rename from src/build/core/UsesRegister.go rename to src/core/UsesRegister.go index 8ae4076..0ad2d90 100644 --- a/src/build/core/UsesRegister.go +++ b/src/core/UsesRegister.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/expression" ) // UsesRegister returns true if evaluating the expression would write or read the given register. diff --git a/src/build/cpu/CPU.go b/src/cpu/CPU.go similarity index 100% rename from src/build/cpu/CPU.go rename to src/cpu/CPU.go diff --git a/src/build/cpu/Register.go b/src/cpu/Register.go similarity index 100% rename from src/build/cpu/Register.go rename to src/cpu/Register.go diff --git a/src/build/cpu/Register_test.go b/src/cpu/Register_test.go similarity index 82% rename from src/build/cpu/Register_test.go rename to src/cpu/Register_test.go index 8c4c4ce..e14f6f0 100644 --- a/src/build/cpu/Register_test.go +++ b/src/cpu/Register_test.go @@ -3,7 +3,7 @@ package cpu_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/cpu/State.go b/src/cpu/State.go similarity index 100% rename from src/build/cpu/State.go rename to src/cpu/State.go diff --git a/src/build/cpu/State_test.go b/src/cpu/State_test.go similarity index 95% rename from src/build/cpu/State_test.go rename to src/cpu/State_test.go index 90cdab7..52b1fa9 100644 --- a/src/build/cpu/State_test.go +++ b/src/cpu/State_test.go @@ -3,7 +3,7 @@ package cpu_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/data/Data.go b/src/data/Data.go similarity index 100% rename from src/build/data/Data.go rename to src/data/Data.go diff --git a/src/build/data/Data_test.go b/src/data/Data_test.go similarity index 94% rename from src/build/data/Data_test.go rename to src/data/Data_test.go index d81faa8..b8d0c60 100644 --- a/src/build/data/Data_test.go +++ b/src/data/Data_test.go @@ -3,7 +3,7 @@ package data_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/data" + "git.akyoto.dev/cli/q/src/data" "git.akyoto.dev/go/assert" ) diff --git a/src/build/elf/ELF.go b/src/elf/ELF.go similarity index 98% rename from src/build/elf/ELF.go rename to src/elf/ELF.go index 1cc8b57..2556d1e 100644 --- a/src/build/elf/ELF.go +++ b/src/elf/ELF.go @@ -5,7 +5,7 @@ import ( "encoding/binary" "io" - "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/config" ) // ELF represents an ELF file. diff --git a/src/build/elf/ELF_test.go b/src/elf/ELF_test.go similarity index 77% rename from src/build/elf/ELF_test.go rename to src/elf/ELF_test.go index b609d60..45a9aa2 100644 --- a/src/build/elf/ELF_test.go +++ b/src/elf/ELF_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/build/elf" + "git.akyoto.dev/cli/q/src/elf" ) func TestELF(t *testing.T) { diff --git a/src/build/elf/Header.go b/src/elf/Header.go similarity index 100% rename from src/build/elf/Header.go rename to src/elf/Header.go diff --git a/src/build/elf/Padding.go b/src/elf/Padding.go similarity index 100% rename from src/build/elf/Padding.go rename to src/elf/Padding.go diff --git a/src/build/elf/ProgramHeader.go b/src/elf/ProgramHeader.go similarity index 100% rename from src/build/elf/ProgramHeader.go rename to src/elf/ProgramHeader.go diff --git a/src/build/elf/SectionHeader.go b/src/elf/SectionHeader.go similarity index 100% rename from src/build/elf/SectionHeader.go rename to src/elf/SectionHeader.go diff --git a/src/build/elf/elf.md b/src/elf/elf.md similarity index 100% rename from src/build/elf/elf.md rename to src/elf/elf.md diff --git a/src/build/errors/Base.go b/src/errors/Base.go similarity index 100% rename from src/build/errors/Base.go rename to src/errors/Base.go diff --git a/src/build/errors/Error.go b/src/errors/Error.go similarity index 94% rename from src/build/errors/Error.go rename to src/errors/Error.go index f0c54ef..d23d7a0 100644 --- a/src/build/errors/Error.go +++ b/src/errors/Error.go @@ -5,8 +5,8 @@ import ( "os" "path/filepath" - "git.akyoto.dev/cli/q/src/build/fs" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" ) // Error is a compiler error at a given line and column. diff --git a/src/build/errors/InvalidCharacter.go b/src/errors/InvalidCharacter.go similarity index 100% rename from src/build/errors/InvalidCharacter.go rename to src/errors/InvalidCharacter.go diff --git a/src/build/errors/InvalidInstruction.go b/src/errors/InvalidInstruction.go similarity index 100% rename from src/build/errors/InvalidInstruction.go rename to src/errors/InvalidInstruction.go diff --git a/src/build/errors/InvalidOperator.go b/src/errors/InvalidOperator.go similarity index 100% rename from src/build/errors/InvalidOperator.go rename to src/errors/InvalidOperator.go diff --git a/src/build/errors/KeywordNotImplemented.go b/src/errors/KeywordNotImplemented.go similarity index 100% rename from src/build/errors/KeywordNotImplemented.go rename to src/errors/KeywordNotImplemented.go diff --git a/src/build/errors/NumberExceedsBounds.go b/src/errors/NumberExceedsBounds.go similarity index 100% rename from src/build/errors/NumberExceedsBounds.go rename to src/errors/NumberExceedsBounds.go diff --git a/src/build/errors/Stack.go b/src/errors/Stack.go similarity index 100% rename from src/build/errors/Stack.go rename to src/errors/Stack.go diff --git a/src/build/errors/TypeMismatch.go b/src/errors/TypeMismatch.go similarity index 100% rename from src/build/errors/TypeMismatch.go rename to src/errors/TypeMismatch.go diff --git a/src/build/errors/UnknownCLIParameter.go b/src/errors/UnknownCLIParameter.go similarity index 100% rename from src/build/errors/UnknownCLIParameter.go rename to src/errors/UnknownCLIParameter.go diff --git a/src/build/errors/UnknownFunction.go b/src/errors/UnknownFunction.go similarity index 100% rename from src/build/errors/UnknownFunction.go rename to src/errors/UnknownFunction.go diff --git a/src/build/errors/UnknownIdentifier.go b/src/errors/UnknownIdentifier.go similarity index 100% rename from src/build/errors/UnknownIdentifier.go rename to src/errors/UnknownIdentifier.go diff --git a/src/build/errors/UnknownPackage.go b/src/errors/UnknownPackage.go similarity index 100% rename from src/build/errors/UnknownPackage.go rename to src/errors/UnknownPackage.go diff --git a/src/build/errors/UnusedImport.go b/src/errors/UnusedImport.go similarity index 100% rename from src/build/errors/UnusedImport.go rename to src/errors/UnusedImport.go diff --git a/src/build/errors/UnusedVariable.go b/src/errors/UnusedVariable.go similarity index 100% rename from src/build/errors/UnusedVariable.go rename to src/errors/UnusedVariable.go diff --git a/src/build/errors/VariableAlreadyExists.go b/src/errors/VariableAlreadyExists.go similarity index 100% rename from src/build/errors/VariableAlreadyExists.go rename to src/errors/VariableAlreadyExists.go diff --git a/src/build/expression/Expression.go b/src/expression/Expression.go similarity index 98% rename from src/build/expression/Expression.go rename to src/expression/Expression.go index 55abb19..2344a37 100644 --- a/src/build/expression/Expression.go +++ b/src/expression/Expression.go @@ -3,7 +3,7 @@ package expression import ( "strings" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/token" ) // Expression is a binary tree with an operator on each node. diff --git a/src/build/expression/Expression_test.go b/src/expression/Expression_test.go similarity index 91% rename from src/build/expression/Expression_test.go rename to src/expression/Expression_test.go index ebcc895..1e03ba8 100644 --- a/src/build/expression/Expression_test.go +++ b/src/expression/Expression_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/go/assert" ) @@ -157,28 +157,6 @@ func TestEachLeaf(t *testing.T) { assert.Equal(t, err.Error(), "error") } -func TestEachParameter(t *testing.T) { - src := []byte("1+2,3*4,5*6,7+8") - tokens := token.Tokenize(src) - parameters := []string{} - - err := expression.EachParameter(tokens, func(parameter token.List) error { - expr := expression.Parse(parameter) - parameters = append(parameters, expr.String(src)) - return nil - }) - - assert.Nil(t, err) - assert.DeepEqual(t, parameters, []string{"(+ 1 2)", "(* 3 4)", "(* 5 6)", "(+ 7 8)"}) - - err = expression.EachParameter(tokens, func(parameter token.List) error { - return fmt.Errorf("error") - }) - - assert.NotNil(t, err) - assert.Equal(t, err.Error(), "error") -} - func TestRemoveChild(t *testing.T) { src := []byte("(1+2-3*4)+(5*6-7+8)") tokens := token.Tokenize(src) diff --git a/src/expression/List.go b/src/expression/List.go new file mode 100644 index 0000000..6431451 --- /dev/null +++ b/src/expression/List.go @@ -0,0 +1,18 @@ +package expression + +import ( + "git.akyoto.dev/cli/q/src/token" +) + +// NewList generates a list of expressions from comma separated parameters. +func NewList(tokens token.List) []*Expression { + var list []*Expression + + tokens.Split(func(parameter token.List) error { + expression := Parse(parameter) + list = append(list, expression) + return nil + }) + + return list +} diff --git a/src/build/expression/Operator.go b/src/expression/Operator.go similarity index 98% rename from src/build/expression/Operator.go rename to src/expression/Operator.go index 96431f1..57468da 100644 --- a/src/build/expression/Operator.go +++ b/src/expression/Operator.go @@ -3,7 +3,7 @@ package expression import ( "math" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/token" ) // Operator represents an operator for mathematical expressions. diff --git a/src/build/expression/Parse.go b/src/expression/Parse.go similarity index 98% rename from src/build/expression/Parse.go rename to src/expression/Parse.go index 87e9768..3ed513e 100644 --- a/src/build/expression/Parse.go +++ b/src/expression/Parse.go @@ -3,7 +3,7 @@ package expression import ( "math" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/token" ) // Parse generates an expression tree from tokens. diff --git a/src/build/expression/bench_test.go b/src/expression/bench_test.go similarity index 72% rename from src/build/expression/bench_test.go rename to src/expression/bench_test.go index c447910..c6266c3 100644 --- a/src/build/expression/bench_test.go +++ b/src/expression/bench_test.go @@ -3,8 +3,8 @@ package expression_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) func BenchmarkExpression(b *testing.B) { diff --git a/src/build/fs/File.go b/src/fs/File.go similarity index 78% rename from src/build/fs/File.go rename to src/fs/File.go index 205daa1..76a43bb 100644 --- a/src/build/fs/File.go +++ b/src/fs/File.go @@ -1,6 +1,6 @@ package fs -import "git.akyoto.dev/cli/q/src/build/token" +import "git.akyoto.dev/cli/q/src/token" // File represents a single source file. type File struct { diff --git a/src/build/fs/Import.go b/src/fs/Import.go similarity index 77% rename from src/build/fs/Import.go rename to src/fs/Import.go index 6690425..ce9032e 100644 --- a/src/build/fs/Import.go +++ b/src/fs/Import.go @@ -1,6 +1,6 @@ package fs -import "git.akyoto.dev/cli/q/src/build/token" +import "git.akyoto.dev/cli/q/src/token" // Import represents an import statement in a file. type Import struct { diff --git a/src/build/fs/Walk.go b/src/fs/Walk.go similarity index 100% rename from src/build/fs/Walk.go rename to src/fs/Walk.go diff --git a/src/build/fs/Walk_test.go b/src/fs/Walk_test.go similarity index 93% rename from src/build/fs/Walk_test.go rename to src/fs/Walk_test.go index 82651ce..09a0830 100644 --- a/src/build/fs/Walk_test.go +++ b/src/fs/Walk_test.go @@ -3,7 +3,7 @@ package fs_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/go/assert" ) diff --git a/src/build/os/linux/Syscall.go b/src/os/linux/Syscall.go similarity index 100% rename from src/build/os/linux/Syscall.go rename to src/os/linux/Syscall.go diff --git a/src/build/register/AddLabel.go b/src/register/AddLabel.go similarity index 73% rename from src/build/register/AddLabel.go rename to src/register/AddLabel.go index 3189ee0..4667275 100644 --- a/src/build/register/AddLabel.go +++ b/src/register/AddLabel.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/build/asm" +import "git.akyoto.dev/cli/q/src/asm" func (f *Machine) AddLabel(label string) { f.Assembler.Label(asm.LABEL, label) diff --git a/src/build/register/Call.go b/src/register/Call.go similarity index 100% rename from src/build/register/Call.go rename to src/register/Call.go diff --git a/src/build/register/Comment.go b/src/register/Comment.go similarity index 100% rename from src/build/register/Comment.go rename to src/register/Comment.go diff --git a/src/build/register/FreeRegister.go b/src/register/FreeRegister.go similarity index 76% rename from src/build/register/FreeRegister.go rename to src/register/FreeRegister.go index 5c459b7..4fd016e 100644 --- a/src/build/register/FreeRegister.go +++ b/src/register/FreeRegister.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // FreeRegister frees a register. func (f *Machine) FreeRegister(register cpu.Register) { diff --git a/src/build/register/Jump.go b/src/register/Jump.go similarity index 76% rename from src/build/register/Jump.go rename to src/register/Jump.go index ff77d01..98045fd 100644 --- a/src/build/register/Jump.go +++ b/src/register/Jump.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/build/asm" +import "git.akyoto.dev/cli/q/src/asm" func (f *Machine) Jump(mnemonic asm.Mnemonic, label string) { f.Assembler.Label(mnemonic, label) diff --git a/src/build/register/Machine.go b/src/register/Machine.go similarity index 62% rename from src/build/register/Machine.go rename to src/register/Machine.go index 50a22b7..07f64b2 100644 --- a/src/build/register/Machine.go +++ b/src/register/Machine.go @@ -1,9 +1,9 @@ package register import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/scope" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/scope" ) // Machine is a register usage aware assembler. diff --git a/src/build/register/MemoryNumber.go b/src/register/MemoryNumber.go similarity index 78% rename from src/build/register/MemoryNumber.go rename to src/register/MemoryNumber.go index 6e99fda..2c1177f 100644 --- a/src/build/register/MemoryNumber.go +++ b/src/register/MemoryNumber.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/build/asm" +import "git.akyoto.dev/cli/q/src/asm" func (f *Machine) MemoryNumber(mnemonic asm.Mnemonic, a asm.Memory, b int) { f.Assembler.MemoryNumber(mnemonic, a, b) diff --git a/src/build/register/MemoryRegister.go b/src/register/MemoryRegister.go similarity index 70% rename from src/build/register/MemoryRegister.go rename to src/register/MemoryRegister.go index 6ad3bc0..1a3c770 100644 --- a/src/build/register/MemoryRegister.go +++ b/src/register/MemoryRegister.go @@ -1,8 +1,8 @@ package register import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" ) func (f *Machine) MemoryRegister(mnemonic asm.Mnemonic, a asm.Memory, b cpu.Register) { diff --git a/src/build/register/NewRegister.go b/src/register/NewRegister.go similarity index 83% rename from src/build/register/NewRegister.go rename to src/register/NewRegister.go index fb2300c..dbe3cc0 100644 --- a/src/build/register/NewRegister.go +++ b/src/register/NewRegister.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // NewRegister reserves a register. func (f *Machine) NewRegister() cpu.Register { diff --git a/src/build/register/Register.go b/src/register/Register.go similarity index 73% rename from src/build/register/Register.go rename to src/register/Register.go index 89f0ca1..fa972a1 100644 --- a/src/build/register/Register.go +++ b/src/register/Register.go @@ -1,8 +1,8 @@ package register import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" ) func (f *Machine) Register(mnemonic asm.Mnemonic, a cpu.Register) { diff --git a/src/build/register/RegisterIsUsed.go b/src/register/RegisterIsUsed.go similarity index 78% rename from src/build/register/RegisterIsUsed.go rename to src/register/RegisterIsUsed.go index 0ba6d0e..fedf149 100644 --- a/src/build/register/RegisterIsUsed.go +++ b/src/register/RegisterIsUsed.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // RegisterIsUsed reserves a register. func (f *Machine) RegisterIsUsed(register cpu.Register) bool { diff --git a/src/build/register/RegisterLabel.go b/src/register/RegisterLabel.go similarity index 77% rename from src/build/register/RegisterLabel.go rename to src/register/RegisterLabel.go index 70690bc..a97de41 100644 --- a/src/build/register/RegisterLabel.go +++ b/src/register/RegisterLabel.go @@ -1,8 +1,8 @@ package register import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" ) func (f *Machine) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { diff --git a/src/build/register/RegisterNumber.go b/src/register/RegisterNumber.go similarity index 86% rename from src/build/register/RegisterNumber.go rename to src/register/RegisterNumber.go index 32b552b..a97dd8b 100644 --- a/src/build/register/RegisterNumber.go +++ b/src/register/RegisterNumber.go @@ -1,9 +1,9 @@ package register import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/sizeof" ) func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { diff --git a/src/build/register/RegisterRegister.go b/src/register/RegisterRegister.go similarity index 79% rename from src/build/register/RegisterRegister.go rename to src/register/RegisterRegister.go index 5f87783..98ac5ea 100644 --- a/src/build/register/RegisterRegister.go +++ b/src/register/RegisterRegister.go @@ -1,8 +1,8 @@ package register import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" ) func (f *Machine) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) { diff --git a/src/build/register/Return.go b/src/register/Return.go similarity index 100% rename from src/build/register/Return.go rename to src/register/Return.go diff --git a/src/build/register/SaveRegister.go b/src/register/SaveRegister.go similarity index 87% rename from src/build/register/SaveRegister.go rename to src/register/SaveRegister.go index 18dfb76..c5a56c9 100644 --- a/src/build/register/SaveRegister.go +++ b/src/register/SaveRegister.go @@ -3,8 +3,8 @@ package register import ( "slices" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" ) // SaveRegister attempts to move a variable occupying this register to another register. diff --git a/src/build/register/Syscall.go b/src/register/Syscall.go similarity index 100% rename from src/build/register/Syscall.go rename to src/register/Syscall.go diff --git a/src/build/register/UseRegister.go b/src/register/UseRegister.go similarity index 78% rename from src/build/register/UseRegister.go rename to src/register/UseRegister.go index ce45d81..996181f 100644 --- a/src/build/register/UseRegister.go +++ b/src/register/UseRegister.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // Use marks a register to be currently in use. func (f *Machine) UseRegister(register cpu.Register) { diff --git a/src/build/register/postInstruction.go b/src/register/postInstruction.go similarity index 77% rename from src/build/register/postInstruction.go rename to src/register/postInstruction.go index d3e2b5d..18f9120 100644 --- a/src/build/register/postInstruction.go +++ b/src/register/postInstruction.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/build/config" +import "git.akyoto.dev/cli/q/src/config" func (f *Machine) postInstruction() { if !config.Assembler { diff --git a/src/build/scanner/Scan.go b/src/scanner/Scan.go similarity index 86% rename from src/build/scanner/Scan.go rename to src/scanner/Scan.go index 4b25a3d..f18cbff 100644 --- a/src/build/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -1,8 +1,8 @@ package scanner import ( - "git.akyoto.dev/cli/q/src/build/core" - "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/fs" ) // Scan scans the list of files. diff --git a/src/build/scanner/Scanner.go b/src/scanner/Scanner.go similarity index 77% rename from src/build/scanner/Scanner.go rename to src/scanner/Scanner.go index 5e6b0c2..434fe30 100644 --- a/src/build/scanner/Scanner.go +++ b/src/scanner/Scanner.go @@ -3,8 +3,8 @@ package scanner import ( "sync" - "git.akyoto.dev/cli/q/src/build/core" - "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/fs" ) // Scanner is used to scan files before the actual compilation step. diff --git a/src/build/scanner/queue.go b/src/scanner/queue.go similarity index 100% rename from src/build/scanner/queue.go rename to src/scanner/queue.go diff --git a/src/build/scanner/queueDirectory.go b/src/scanner/queueDirectory.go similarity index 92% rename from src/build/scanner/queueDirectory.go rename to src/scanner/queueDirectory.go index d21c8ce..167905b 100644 --- a/src/build/scanner/queueDirectory.go +++ b/src/scanner/queueDirectory.go @@ -4,7 +4,7 @@ import ( "path/filepath" "strings" - "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/fs" ) // queueDirectory queues an entire directory to be scanned. diff --git a/src/build/scanner/queueFile.go b/src/scanner/queueFile.go similarity index 100% rename from src/build/scanner/queueFile.go rename to src/scanner/queueFile.go diff --git a/src/build/scanner/scanFile.go b/src/scanner/scanFile.go similarity index 92% rename from src/build/scanner/scanFile.go rename to src/scanner/scanFile.go index 714ba72..46f25ff 100644 --- a/src/build/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -4,15 +4,14 @@ import ( "os" "path/filepath" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/core" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/fs" - "git.akyoto.dev/cli/q/src/build/scope" - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/scope" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" ) // scanFile scans a single file. @@ -240,7 +239,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { parameters := tokens[paramsStart:paramsEnd] count := 0 - err := expression.EachParameter(parameters, func(tokens token.List) error { + err := parameters.Split(func(tokens token.List) error { if len(tokens) < 2 { return errors.New(errors.MissingType, file, tokens[0].End()) } diff --git a/src/build/scope/Scope.go b/src/scope/Scope.go similarity index 93% rename from src/build/scope/Scope.go rename to src/scope/Scope.go index 9bbb3db..fd1f750 100644 --- a/src/build/scope/Scope.go +++ b/src/scope/Scope.go @@ -1,7 +1,7 @@ package scope import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // Scope represents an independent code block. diff --git a/src/build/scope/Stack.go b/src/scope/Stack.go similarity index 94% rename from src/build/scope/Stack.go rename to src/scope/Stack.go index 750bc4c..dbfe206 100644 --- a/src/build/scope/Stack.go +++ b/src/scope/Stack.go @@ -1,9 +1,9 @@ package scope import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/token" ) // Stack is a stack of scopes. diff --git a/src/build/scope/Variable.go b/src/scope/Variable.go similarity index 85% rename from src/build/scope/Variable.go rename to src/scope/Variable.go index 0346ca1..7c6081e 100644 --- a/src/build/scope/Variable.go +++ b/src/scope/Variable.go @@ -1,8 +1,8 @@ package scope import ( - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/types" ) // Variable represents a named register. diff --git a/src/build/sizeof/Signed.go b/src/sizeof/Signed.go similarity index 100% rename from src/build/sizeof/Signed.go rename to src/sizeof/Signed.go diff --git a/src/build/sizeof/Signed_test.go b/src/sizeof/Signed_test.go similarity index 93% rename from src/build/sizeof/Signed_test.go rename to src/sizeof/Signed_test.go index 6ddc15f..bd4d049 100644 --- a/src/build/sizeof/Signed_test.go +++ b/src/sizeof/Signed_test.go @@ -4,7 +4,7 @@ import ( "math" "testing" - "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/cli/q/src/sizeof" "git.akyoto.dev/go/assert" ) diff --git a/src/build/sizeof/Unsigned.go b/src/sizeof/Unsigned.go similarity index 100% rename from src/build/sizeof/Unsigned.go rename to src/sizeof/Unsigned.go diff --git a/src/build/sizeof/Unsigned_test.go b/src/sizeof/Unsigned_test.go similarity index 89% rename from src/build/sizeof/Unsigned_test.go rename to src/sizeof/Unsigned_test.go index e9a5dc8..b4e78a2 100644 --- a/src/build/sizeof/Unsigned_test.go +++ b/src/sizeof/Unsigned_test.go @@ -4,7 +4,7 @@ import ( "math" "testing" - "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/cli/q/src/sizeof" "git.akyoto.dev/go/assert" ) diff --git a/src/build/token/Count.go b/src/token/Count.go similarity index 100% rename from src/build/token/Count.go rename to src/token/Count.go diff --git a/src/build/token/Count_test.go b/src/token/Count_test.go similarity index 92% rename from src/build/token/Count_test.go rename to src/token/Count_test.go index 8742d13..9c8a228 100644 --- a/src/build/token/Count_test.go +++ b/src/token/Count_test.go @@ -3,7 +3,7 @@ package token_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/go/assert" ) diff --git a/src/build/token/Kind.go b/src/token/Kind.go similarity index 100% rename from src/build/token/Kind.go rename to src/token/Kind.go diff --git a/src/build/token/Length.go b/src/token/Length.go similarity index 100% rename from src/build/token/Length.go rename to src/token/Length.go diff --git a/src/build/token/List.go b/src/token/List.go similarity index 54% rename from src/build/token/List.go rename to src/token/List.go index 37ad0eb..c66dc27 100644 --- a/src/build/token/List.go +++ b/src/token/List.go @@ -29,6 +29,43 @@ func (list List) LastIndexKind(kind Kind) int { return -1 } +// Split calls the callback function on each set of tokens in a comma separated list. +func (list List) Split(call func(List) error) error { + start := 0 + groupLevel := 0 + + for i, t := range list { + switch t.Kind { + case GroupStart, ArrayStart, BlockStart: + groupLevel++ + + case GroupEnd, ArrayEnd, BlockEnd: + groupLevel-- + + case Separator: + if groupLevel > 0 { + continue + } + + parameter := list[start:i] + err := call(parameter) + + if err != nil { + return err + } + + start = i + 1 + } + } + + if start != len(list) { + parameter := list[start:] + return call(parameter) + } + + return nil +} + // Text returns the concatenated token text. func (list List) Text(source []byte) string { tmp := strings.Builder{} diff --git a/src/token/List_test.go b/src/token/List_test.go new file mode 100644 index 0000000..71fbf84 --- /dev/null +++ b/src/token/List_test.go @@ -0,0 +1,40 @@ +package token_test + +import ( + "fmt" + "testing" + + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/go/assert" +) + +func TestIndexKind(t *testing.T) { + tokens := token.Tokenize([]byte("a{{}}")) + assert.Equal(t, tokens.IndexKind(token.NewLine), -1) + assert.Equal(t, tokens.LastIndexKind(token.NewLine), -1) + assert.Equal(t, tokens.IndexKind(token.BlockStart), 1) + assert.Equal(t, tokens.LastIndexKind(token.BlockStart), 2) + assert.Equal(t, tokens.IndexKind(token.BlockEnd), 3) + assert.Equal(t, tokens.LastIndexKind(token.BlockEnd), 4) +} + +func TestSplit(t *testing.T) { + src := []byte("1+2,3*4,5*6,7+8") + tokens := token.Tokenize(src) + parameters := []string{} + + err := tokens.Split(func(parameter token.List) error { + parameters = append(parameters, parameter.Text(src)) + return nil + }) + + assert.Nil(t, err) + assert.DeepEqual(t, parameters, []string{"1+2", "3*4", "5*6", "7+8"}) + + err = tokens.Split(func(parameter token.List) error { + return fmt.Errorf("error") + }) + + assert.NotNil(t, err) + assert.Equal(t, err.Error(), "error") +} diff --git a/src/build/token/Position.go b/src/token/Position.go similarity index 100% rename from src/build/token/Position.go rename to src/token/Position.go diff --git a/src/build/token/Token.go b/src/token/Token.go similarity index 100% rename from src/build/token/Token.go rename to src/token/Token.go diff --git a/src/build/token/Token_test.go b/src/token/Token_test.go similarity index 96% rename from src/build/token/Token_test.go rename to src/token/Token_test.go index ba825cc..9396480 100644 --- a/src/build/token/Token_test.go +++ b/src/token/Token_test.go @@ -3,7 +3,7 @@ package token_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/go/assert" ) diff --git a/src/build/token/Tokenize.go b/src/token/Tokenize.go similarity index 100% rename from src/build/token/Tokenize.go rename to src/token/Tokenize.go diff --git a/src/build/token/Tokenize_test.go b/src/token/Tokenize_test.go similarity index 99% rename from src/build/token/Tokenize_test.go rename to src/token/Tokenize_test.go index 4d6e36b..e00f978 100644 --- a/src/build/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -3,7 +3,7 @@ package token_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/go/assert" ) diff --git a/src/build/token/bench_test.go b/src/token/bench_test.go similarity index 90% rename from src/build/token/bench_test.go rename to src/token/bench_test.go index 3ec6007..acc6359 100644 --- a/src/build/token/bench_test.go +++ b/src/token/bench_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/token" ) func BenchmarkLines(b *testing.B) { diff --git a/src/build/types/New.go b/src/types/New.go similarity index 100% rename from src/build/types/New.go rename to src/types/New.go diff --git a/src/build/types/NewList.go b/src/types/NewList.go similarity index 60% rename from src/build/types/NewList.go rename to src/types/NewList.go index 91bb4ca..c315c8d 100644 --- a/src/build/types/NewList.go +++ b/src/types/NewList.go @@ -1,15 +1,12 @@ package types -import ( - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" -) +import "git.akyoto.dev/cli/q/src/token" // NewList generates a list of types from comma separated tokens. func NewList(tokens token.List, source []byte) []Type { var list []Type - expression.EachParameter(tokens, func(parameter token.List) error { + tokens.Split(func(parameter token.List) error { typ := New(parameter.Text(source)) list = append(list, typ) return nil diff --git a/src/build/types/Type.go b/src/types/Type.go similarity index 100% rename from src/build/types/Type.go rename to src/types/Type.go diff --git a/tests/errors_test.go b/tests/errors_test.go index 61eab3d..484cf94 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -6,7 +6,7 @@ import ( "testing" "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/go/assert" ) diff --git a/tests/examples_test.go b/tests/examples_test.go index 2279740..63dd7f3 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -5,7 +5,7 @@ import ( "testing" "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/go/assert" ) diff --git a/tests/programs_test.go b/tests/programs_test.go index 7e002a7..a6b117f 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -8,7 +8,7 @@ import ( "testing" "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/go/assert" ) From 66569446b13bae2e6f26e31102fadf7792297659 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 7 Aug 2024 19:39:10 +0200 Subject: [PATCH 0484/1012] Simplified file structure --- README.md | 146 +++++++++--------- go.mod | 4 +- go.sum | 4 +- src/{build => }/arch/arm64/Registers.go | 2 +- src/{build => }/arch/riscv/Registers.go | 2 +- src/{build => }/arch/x64/Add.go | 2 +- src/{build => }/arch/x64/Add_test.go | 4 +- src/{build => }/arch/x64/And.go | 2 +- src/{build => }/arch/x64/Call.go | 0 src/{build => }/arch/x64/Compare.go | 2 +- src/{build => }/arch/x64/Compare_test.go | 4 +- src/{build => }/arch/x64/Div.go | 2 +- src/{build => }/arch/x64/Div_test.go | 4 +- src/{build => }/arch/x64/ExtendRAXToRDX.go | 0 src/{build => }/arch/x64/Jump.go | 0 src/{build => }/arch/x64/Jump_test.go | 2 +- src/{build => }/arch/x64/Load.go | 2 +- src/{build => }/arch/x64/Load_test.go | 4 +- src/{build => }/arch/x64/ModRM.go | 0 src/{build => }/arch/x64/ModRM_test.go | 2 +- src/{build => }/arch/x64/Move.go | 4 +- src/{build => }/arch/x64/Move_test.go | 4 +- src/{build => }/arch/x64/Mul.go | 2 +- src/{build => }/arch/x64/Mul_test.go | 4 +- src/{build => }/arch/x64/Negate.go | 2 +- src/{build => }/arch/x64/Negate_test.go | 4 +- src/{build => }/arch/x64/Or.go | 2 +- src/{build => }/arch/x64/Pop.go | 2 +- src/{build => }/arch/x64/Pop_test.go | 4 +- src/{build => }/arch/x64/Push.go | 2 +- src/{build => }/arch/x64/Push_test.go | 4 +- src/{build => }/arch/x64/REX.go | 0 src/{build => }/arch/x64/REX_test.go | 2 +- src/{build => }/arch/x64/Registers.go | 2 +- src/{build => }/arch/x64/Return.go | 0 src/{build => }/arch/x64/SIB.go | 0 src/{build => }/arch/x64/SIB_test.go | 2 +- src/{build => }/arch/x64/Shift.go | 2 +- src/{build => }/arch/x64/Store.go | 2 +- src/{build => }/arch/x64/Store_test.go | 4 +- src/{build => }/arch/x64/Sub.go | 2 +- src/{build => }/arch/x64/Sub_test.go | 4 +- src/{build => }/arch/x64/Syscall.go | 0 src/{build => }/arch/x64/Xor.go | 2 +- src/{build => }/arch/x64/encode.go | 2 +- src/{build => }/arch/x64/encodeNum.go | 4 +- src/{build => }/arch/x64/memoryAccess.go | 2 +- src/{build => }/arch/x64/x64_test.go | 2 +- src/{build => }/asm/Assembler.go | 2 +- src/{build => }/asm/Finalize.go | 8 +- src/{build => }/asm/Instruction.go | 0 src/{build => }/asm/Instructions.go | 0 src/{build => }/asm/Label.go | 0 src/{build => }/asm/Memory.go | 2 +- src/{build => }/asm/MemoryNumber.go | 0 src/{build => }/asm/MemoryRegister.go | 2 +- src/{build => }/asm/Mnemonic.go | 0 src/{build => }/asm/Optimizer.go | 2 +- src/{build => }/asm/Pointer.go | 0 src/{build => }/asm/Register.go | 2 +- src/{build => }/asm/RegisterLabel.go | 2 +- src/{build => }/asm/RegisterNumber.go | 2 +- src/{build => }/asm/RegisterRegister.go | 2 +- src/{build => }/ast/AST.go | 0 src/{build => }/ast/Assert.go | 2 +- src/{build => }/ast/Assign.go | 2 +- src/{build => }/ast/Call.go | 2 +- src/{build => }/ast/Count.go | 2 +- src/{build => }/ast/Define.go | 2 +- src/{build => }/ast/EachInstruction.go | 2 +- src/{build => }/ast/If.go | 2 +- src/{build => }/ast/Loop.go | 0 src/{build => }/ast/Parse.go | 4 +- src/{build => }/ast/Return.go | 2 +- src/{build => }/ast/Switch.go | 2 +- src/{build => }/ast/parseKeyword.go | 6 +- src/{build => }/ast/parseNode.go | 6 +- src/{build => }/ast/parseSwitch.go | 4 +- src/build/Build.go | 4 +- src/build/expression/List.go | 55 ------- src/build/token/List_test.go | 18 --- src/cli/Build.go | 4 +- src/cli/Run.go | 2 +- src/cli/System.go | 2 +- src/{build => }/compiler/Compile.go | 6 +- src/{build => }/compiler/Result.go | 10 +- src/{build => }/config/config.go | 0 src/{build => }/config/init.go | 0 src/{build => }/core/AddBytes.go | 0 src/{build => }/core/Compare.go | 8 +- src/{build => }/core/Compile.go | 0 src/{build => }/core/CompileAST.go | 2 +- src/{build => }/core/CompileASTNode.go | 2 +- src/{build => }/core/CompileAssert.go | 4 +- src/{build => }/core/CompileAssign.go | 6 +- src/{build => }/core/CompileAssignArray.go | 6 +- src/{build => }/core/CompileAssignDivision.go | 8 +- src/{build => }/core/CompileCall.go | 6 +- src/{build => }/core/CompileCondition.go | 4 +- src/{build => }/core/CompileDefinition.go | 10 +- src/{build => }/core/CompileIf.go | 4 +- src/{build => }/core/CompileLoop.go | 4 +- src/{build => }/core/CompileReturn.go | 6 +- src/{build => }/core/CompileSwitch.go | 4 +- src/{build => }/core/CompileSyscall.go | 2 +- src/{build => }/core/CompileTokens.go | 6 +- src/{build => }/core/Defer.go | 0 src/{build => }/core/Define.go | 8 +- src/{build => }/core/Evaluate.go | 8 +- src/{build => }/core/Execute.go | 8 +- src/{build => }/core/ExecuteLeaf.go | 6 +- src/{build => }/core/ExecuteRegister.go | 8 +- src/{build => }/core/ExecuteRegisterNumber.go | 10 +- .../core/ExecuteRegisterRegister.go | 10 +- src/{build => }/core/ExpressionToMemory.go | 10 +- src/{build => }/core/ExpressionToRegister.go | 14 +- .../core/ExpressionsToRegisters.go | 4 +- src/{build => }/core/Fold.go | 6 +- src/{build => }/core/Function.go | 10 +- src/{build => }/core/IdentifierExists.go | 0 src/{build => }/core/JumpIfFalse.go | 4 +- src/{build => }/core/JumpIfTrue.go | 4 +- src/{build => }/core/NewFunction.go | 14 +- src/{build => }/core/Number.go | 4 +- src/{build => }/core/PrintInstructions.go | 2 +- src/{build => }/core/String.go | 0 src/{build => }/core/TokenToRegister.go | 10 +- src/{build => }/core/UsesRegister.go | 6 +- src/{build => }/cpu/CPU.go | 0 src/{build => }/cpu/Register.go | 0 src/{build => }/cpu/Register_test.go | 2 +- src/{build => }/cpu/State.go | 0 src/{build => }/cpu/State_test.go | 2 +- src/{build => }/data/Data.go | 0 src/{build => }/data/Data_test.go | 2 +- src/{build => }/elf/ELF.go | 2 +- src/{build => }/elf/ELF_test.go | 2 +- src/{build => }/elf/Header.go | 0 src/{build => }/elf/Padding.go | 0 src/{build => }/elf/ProgramHeader.go | 0 src/{build => }/elf/SectionHeader.go | 0 src/{build => }/elf/elf.md | 0 src/{build => }/errors/Base.go | 0 src/{build => }/errors/Error.go | 4 +- src/{build => }/errors/InvalidCharacter.go | 0 src/{build => }/errors/InvalidInstruction.go | 0 src/{build => }/errors/InvalidOperator.go | 0 .../errors/KeywordNotImplemented.go | 0 src/{build => }/errors/NumberExceedsBounds.go | 0 src/{build => }/errors/Stack.go | 0 src/{build => }/errors/TypeMismatch.go | 0 src/{build => }/errors/UnknownCLIParameter.go | 0 src/{build => }/errors/UnknownFunction.go | 0 src/{build => }/errors/UnknownIdentifier.go | 0 src/{build => }/errors/UnknownPackage.go | 0 src/{build => }/errors/UnusedImport.go | 0 src/{build => }/errors/UnusedVariable.go | 0 .../errors/VariableAlreadyExists.go | 0 src/{build => }/expression/Expression.go | 2 +- src/{build => }/expression/Expression_test.go | 26 +--- src/expression/List.go | 18 +++ src/{build => }/expression/Operator.go | 2 +- src/{build => }/expression/Parse.go | 2 +- src/{build => }/expression/bench_test.go | 4 +- src/{build => }/fs/File.go | 2 +- src/{build => }/fs/Import.go | 2 +- src/{build => }/fs/Walk.go | 0 src/{build => }/fs/Walk_test.go | 2 +- src/{build => }/os/linux/Syscall.go | 0 src/{build => }/register/AddLabel.go | 2 +- src/{build => }/register/Call.go | 0 src/{build => }/register/Comment.go | 0 src/{build => }/register/FreeRegister.go | 2 +- src/{build => }/register/Jump.go | 2 +- src/{build => }/register/Machine.go | 6 +- src/{build => }/register/MemoryNumber.go | 2 +- src/{build => }/register/MemoryRegister.go | 4 +- src/{build => }/register/NewRegister.go | 2 +- src/{build => }/register/Register.go | 4 +- src/{build => }/register/RegisterIsUsed.go | 2 +- src/{build => }/register/RegisterLabel.go | 4 +- src/{build => }/register/RegisterNumber.go | 6 +- src/{build => }/register/RegisterRegister.go | 4 +- src/{build => }/register/Return.go | 0 src/{build => }/register/SaveRegister.go | 4 +- src/{build => }/register/Syscall.go | 0 src/{build => }/register/UseRegister.go | 2 +- src/{build => }/register/postInstruction.go | 2 +- src/{build => }/scanner/Scan.go | 4 +- src/{build => }/scanner/Scanner.go | 4 +- src/{build => }/scanner/queue.go | 0 src/{build => }/scanner/queueDirectory.go | 2 +- src/{build => }/scanner/queueFile.go | 0 src/{build => }/scanner/scanFile.go | 19 ++- src/{build => }/scope/Scope.go | 2 +- src/{build => }/scope/Stack.go | 6 +- src/{build => }/scope/Variable.go | 4 +- src/{build => }/sizeof/Signed.go | 0 src/{build => }/sizeof/Signed_test.go | 2 +- src/{build => }/sizeof/Unsigned.go | 0 src/{build => }/sizeof/Unsigned_test.go | 2 +- src/{build => }/token/Count.go | 0 src/{build => }/token/Count_test.go | 2 +- src/{build => }/token/Kind.go | 0 src/{build => }/token/Length.go | 0 src/{build => }/token/List.go | 37 +++++ src/token/List_test.go | 40 +++++ src/{build => }/token/Position.go | 0 src/{build => }/token/Token.go | 0 src/{build => }/token/Token_test.go | 2 +- src/{build => }/token/Tokenize.go | 0 src/{build => }/token/Tokenize_test.go | 2 +- src/{build => }/token/bench_test.go | 2 +- src/{build => }/types/New.go | 0 src/{build => }/types/NewList.go | 7 +- src/{build => }/types/Type.go | 0 tests/errors_test.go | 2 +- tests/examples_test.go | 2 +- tests/programs_test.go | 2 +- 219 files changed, 453 insertions(+), 457 deletions(-) rename src/{build => }/arch/arm64/Registers.go (85%) rename src/{build => }/arch/riscv/Registers.go (85%) rename src/{build => }/arch/x64/Add.go (92%) rename src/{build => }/arch/x64/Add_test.go (97%) rename src/{build => }/arch/x64/And.go (92%) rename src/{build => }/arch/x64/Call.go (100%) rename src/{build => }/arch/x64/Compare.go (92%) rename src/{build => }/arch/x64/Compare_test.go (97%) rename src/{build => }/arch/x64/Div.go (85%) rename src/{build => }/arch/x64/Div_test.go (92%) rename src/{build => }/arch/x64/ExtendRAXToRDX.go (100%) rename src/{build => }/arch/x64/Jump.go (100%) rename src/{build => }/arch/x64/Jump_test.go (96%) rename src/{build => }/arch/x64/Load.go (85%) rename src/{build => }/arch/x64/Load_test.go (99%) rename src/{build => }/arch/x64/ModRM.go (100%) rename src/{build => }/arch/x64/ModRM_test.go (95%) rename src/{build => }/arch/x64/Move.go (94%) rename src/{build => }/arch/x64/Move_test.go (98%) rename src/{build => }/arch/x64/Mul.go (91%) rename src/{build => }/arch/x64/Mul_test.go (97%) rename src/{build => }/arch/x64/Negate.go (81%) rename src/{build => }/arch/x64/Negate_test.go (92%) rename src/{build => }/arch/x64/Or.go (92%) rename src/{build => }/arch/x64/Pop.go (86%) rename src/{build => }/arch/x64/Pop_test.go (91%) rename src/{build => }/arch/x64/Push.go (86%) rename src/{build => }/arch/x64/Push_test.go (91%) rename src/{build => }/arch/x64/REX.go (100%) rename src/{build => }/arch/x64/REX_test.go (94%) rename src/{build => }/arch/x64/Registers.go (92%) rename src/{build => }/arch/x64/Return.go (100%) rename src/{build => }/arch/x64/SIB.go (100%) rename src/{build => }/arch/x64/SIB_test.go (95%) rename src/{build => }/arch/x64/Shift.go (93%) rename src/{build => }/arch/x64/Store.go (95%) rename src/{build => }/arch/x64/Store_test.go (99%) rename src/{build => }/arch/x64/Sub.go (92%) rename src/{build => }/arch/x64/Sub_test.go (97%) rename src/{build => }/arch/x64/Syscall.go (100%) rename src/{build => }/arch/x64/Xor.go (92%) rename src/{build => }/arch/x64/encode.go (94%) rename src/{build => }/arch/x64/encodeNum.go (86%) rename src/{build => }/arch/x64/memoryAccess.go (93%) rename src/{build => }/arch/x64/x64_test.go (92%) rename src/{build => }/asm/Assembler.go (93%) rename src/{build => }/asm/Finalize.go (98%) rename src/{build => }/asm/Instruction.go (100%) rename src/{build => }/asm/Instructions.go (100%) rename src/{build => }/asm/Label.go (100%) rename src/{build => }/asm/Memory.go (65%) rename src/{build => }/asm/MemoryNumber.go (100%) rename src/{build => }/asm/MemoryRegister.go (94%) rename src/{build => }/asm/Mnemonic.go (100%) rename src/{build => }/asm/Optimizer.go (93%) rename src/{build => }/asm/Pointer.go (100%) rename src/{build => }/asm/Register.go (92%) rename src/{build => }/asm/RegisterLabel.go (94%) rename src/{build => }/asm/RegisterNumber.go (94%) rename src/{build => }/asm/RegisterRegister.go (94%) rename src/{build => }/ast/AST.go (100%) rename src/{build => }/ast/Assert.go (78%) rename src/{build => }/ast/Assign.go (78%) rename src/{build => }/ast/Call.go (67%) rename src/{build => }/ast/Count.go (95%) rename src/{build => }/ast/Define.go (73%) rename src/{build => }/ast/EachInstruction.go (95%) rename src/{build => }/ast/If.go (75%) rename src/{build => }/ast/Loop.go (100%) rename src/{build => }/ast/Parse.go (91%) rename src/{build => }/ast/Return.go (73%) rename src/{build => }/ast/Switch.go (82%) rename src/{build => }/ast/parseKeyword.go (95%) rename src/{build => }/ast/parseNode.go (87%) rename src/{build => }/ast/parseSwitch.go (89%) delete mode 100644 src/build/expression/List.go delete mode 100644 src/build/token/List_test.go rename src/{build => }/compiler/Compile.go (94%) rename src/{build => }/compiler/Result.go (93%) rename src/{build => }/config/config.go (100%) rename src/{build => }/config/init.go (100%) rename src/{build => }/core/AddBytes.go (100%) rename src/{build => }/core/Compare.go (85%) rename src/{build => }/core/Compile.go (100%) rename src/{build => }/core/CompileAST.go (86%) rename src/{build => }/core/CompileASTNode.go (95%) rename src/{build => }/core/CompileAssert.go (86%) rename src/{build => }/core/CompileAssign.go (87%) rename src/{build => }/core/CompileAssignArray.go (86%) rename src/{build => }/core/CompileAssignDivision.go (87%) rename src/{build => }/core/CompileCall.go (95%) rename src/{build => }/core/CompileCondition.go (95%) rename src/{build => }/core/CompileDefinition.go (84%) rename src/{build => }/core/CompileIf.go (92%) rename src/{build => }/core/CompileLoop.go (85%) rename src/{build => }/core/CompileReturn.go (84%) rename src/{build => }/core/CompileSwitch.go (92%) rename src/{build => }/core/CompileSyscall.go (92%) rename src/{build => }/core/CompileTokens.go (69%) rename src/{build => }/core/Defer.go (100%) rename src/{build => }/core/Define.go (78%) rename src/{build => }/core/Evaluate.go (77%) rename src/{build => }/core/Execute.go (80%) rename src/{build => }/core/ExecuteLeaf.go (88%) rename src/{build => }/core/ExecuteRegister.go (71%) rename src/{build => }/core/ExecuteRegisterNumber.go (89%) rename src/{build => }/core/ExecuteRegisterRegister.go (88%) rename src/{build => }/core/ExpressionToMemory.go (80%) rename src/{build => }/core/ExpressionToRegister.go (87%) rename src/{build => }/core/ExpressionsToRegisters.go (82%) rename src/{build => }/core/Fold.go (89%) rename src/{build => }/core/Function.go (75%) rename src/{build => }/core/IdentifierExists.go (100%) rename src/{build => }/core/JumpIfFalse.go (85%) rename src/{build => }/core/JumpIfTrue.go (85%) rename src/{build => }/core/NewFunction.go (72%) rename src/{build => }/core/Number.go (92%) rename src/{build => }/core/PrintInstructions.go (97%) rename src/{build => }/core/String.go (100%) rename src/{build => }/core/TokenToRegister.go (84%) rename src/{build => }/core/UsesRegister.go (84%) rename src/{build => }/cpu/CPU.go (100%) rename src/{build => }/cpu/Register.go (100%) rename src/{build => }/cpu/Register_test.go (82%) rename src/{build => }/cpu/State.go (100%) rename src/{build => }/cpu/State_test.go (95%) rename src/{build => }/data/Data.go (100%) rename src/{build => }/data/Data_test.go (94%) rename src/{build => }/elf/ELF.go (98%) rename src/{build => }/elf/ELF_test.go (77%) rename src/{build => }/elf/Header.go (100%) rename src/{build => }/elf/Padding.go (100%) rename src/{build => }/elf/ProgramHeader.go (100%) rename src/{build => }/elf/SectionHeader.go (100%) rename src/{build => }/elf/elf.md (100%) rename src/{build => }/errors/Base.go (100%) rename src/{build => }/errors/Error.go (94%) rename src/{build => }/errors/InvalidCharacter.go (100%) rename src/{build => }/errors/InvalidInstruction.go (100%) rename src/{build => }/errors/InvalidOperator.go (100%) rename src/{build => }/errors/KeywordNotImplemented.go (100%) rename src/{build => }/errors/NumberExceedsBounds.go (100%) rename src/{build => }/errors/Stack.go (100%) rename src/{build => }/errors/TypeMismatch.go (100%) rename src/{build => }/errors/UnknownCLIParameter.go (100%) rename src/{build => }/errors/UnknownFunction.go (100%) rename src/{build => }/errors/UnknownIdentifier.go (100%) rename src/{build => }/errors/UnknownPackage.go (100%) rename src/{build => }/errors/UnusedImport.go (100%) rename src/{build => }/errors/UnusedVariable.go (100%) rename src/{build => }/errors/VariableAlreadyExists.go (100%) rename src/{build => }/expression/Expression.go (98%) rename src/{build => }/expression/Expression_test.go (91%) create mode 100644 src/expression/List.go rename src/{build => }/expression/Operator.go (98%) rename src/{build => }/expression/Parse.go (98%) rename src/{build => }/expression/bench_test.go (72%) rename src/{build => }/fs/File.go (78%) rename src/{build => }/fs/Import.go (77%) rename src/{build => }/fs/Walk.go (100%) rename src/{build => }/fs/Walk_test.go (93%) rename src/{build => }/os/linux/Syscall.go (100%) rename src/{build => }/register/AddLabel.go (73%) rename src/{build => }/register/Call.go (100%) rename src/{build => }/register/Comment.go (100%) rename src/{build => }/register/FreeRegister.go (76%) rename src/{build => }/register/Jump.go (76%) rename src/{build => }/register/Machine.go (62%) rename src/{build => }/register/MemoryNumber.go (78%) rename src/{build => }/register/MemoryRegister.go (70%) rename src/{build => }/register/NewRegister.go (83%) rename src/{build => }/register/Register.go (73%) rename src/{build => }/register/RegisterIsUsed.go (78%) rename src/{build => }/register/RegisterLabel.go (77%) rename src/{build => }/register/RegisterNumber.go (86%) rename src/{build => }/register/RegisterRegister.go (79%) rename src/{build => }/register/Return.go (100%) rename src/{build => }/register/SaveRegister.go (87%) rename src/{build => }/register/Syscall.go (100%) rename src/{build => }/register/UseRegister.go (78%) rename src/{build => }/register/postInstruction.go (77%) rename src/{build => }/scanner/Scan.go (86%) rename src/{build => }/scanner/Scanner.go (77%) rename src/{build => }/scanner/queue.go (100%) rename src/{build => }/scanner/queueDirectory.go (92%) rename src/{build => }/scanner/queueFile.go (100%) rename src/{build => }/scanner/scanFile.go (92%) rename src/{build => }/scope/Scope.go (93%) rename src/{build => }/scope/Stack.go (94%) rename src/{build => }/scope/Variable.go (85%) rename src/{build => }/sizeof/Signed.go (100%) rename src/{build => }/sizeof/Signed_test.go (93%) rename src/{build => }/sizeof/Unsigned.go (100%) rename src/{build => }/sizeof/Unsigned_test.go (89%) rename src/{build => }/token/Count.go (100%) rename src/{build => }/token/Count_test.go (92%) rename src/{build => }/token/Kind.go (100%) rename src/{build => }/token/Length.go (100%) rename src/{build => }/token/List.go (54%) create mode 100644 src/token/List_test.go rename src/{build => }/token/Position.go (100%) rename src/{build => }/token/Token.go (100%) rename src/{build => }/token/Token_test.go (96%) rename src/{build => }/token/Tokenize.go (100%) rename src/{build => }/token/Tokenize_test.go (99%) rename src/{build => }/token/bench_test.go (90%) rename src/{build => }/types/New.go (100%) rename src/{build => }/types/NewList.go (60%) rename src/{build => }/types/Type.go (100%) diff --git a/README.md b/README.md index 1eafca8..695e702 100644 --- a/README.md +++ b/README.md @@ -24,79 +24,6 @@ Build a Linux x86-64 ELF executable from `examples/hello` and run it: ./q run examples/hello ``` -## Documentation - -### [main.go](main.go) - -Entry point. It simply calls `cli.Main` which we can use for testing. - -### [src/cli/Main.go](src/cli/Main.go) - -The command line interface expects a command like `build` as the first argument. -Commands are implemented as functions in the [src/cli](src/cli) directory. -Each command has its own set of parameters. - -### [src/cli/Build.go](src/cli/Build.go) - -The build command creates a new `Build` instance with the given directory and calls the `Run` method. - -If no directory is specified, it will use the current directory. - -If the `--dry` flag is specified, it will perform all tasks except the final write to disk. -This flag should be used in most tests and benchmarks to avoid needless disk writes. - -```shell -q build -q build examples/hello -q build examples/hello --dry -``` - -Adding the `-a` or `--assembler` flag shows the generated assembly instructions: - -```shell -q build examples/hello -a -``` - -Adding the `-v` or `--verbose` flag shows verbose compiler information: - -```shell -q build examples/hello -v -``` - -### [src/build/Build.go](src/build/Build.go) - -The `Build` type defines all the information needed to start building an executable file. -The name of the executable will be equal to the name of the build directory. - -`Run` starts the build which will scan all `.q` source files in the build directory. -Every source file is scanned in its own goroutine for performance reasons. -Parallelization here is possible because the order of files in a directory is not significant. - -The main thread is meanwhile waiting for new function objects to arrive from the scanners. -Once a function has arrived, it will be stored for compilation later. -We need to wait with the compilation step until we have enough information about all identifiers from the scan. - -Then all the functions that were scanned will be compiled in parallel. -We create a separate goroutine for each function compilation. -Each function will then be translated to generic assembler instructions. - -All the functions that are required to run the program will be added to the final assembler. -The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. - -### [src/build/core/Function.go](src/build/core/Function.go) - -This is the "heart" of the compiler. -Each function runs `f.Compile` which organizes the source code into an abstract syntax tree that is then compiled via `f.CompileAST`. -You can think of AST nodes as the individual statements in your source code. - -### [src/build/ast/Parse.go](src/build/ast/Parse.go) - -This is what generates the AST from tokens. - -### [src/build/expression/Parse.go](src/build/expression/Parse.go) - -This is what generates expressions from tokens. - ## Todo ### Compiler @@ -176,6 +103,79 @@ This is what generates expressions from tokens. - [ ] Mac - [ ] Windows +## Documentation + +### [main.go](main.go) + +Entry point. It simply calls `cli.Main` which we can use for testing. + +### [src/cli/Main.go](src/cli/Main.go) + +The command line interface expects a command like `build` as the first argument. +Commands are implemented as functions in the [src/cli](src/cli) directory. +Each command has its own set of parameters. + +### [src/cli/Build.go](src/cli/Build.go) + +The build command creates a new `Build` instance with the given directory and calls the `Run` method. + +If no directory is specified, it will use the current directory. + +If the `--dry` flag is specified, it will perform all tasks except the final write to disk. +This flag should be used in most tests and benchmarks to avoid needless disk writes. + +```shell +q build +q build examples/hello +q build examples/hello --dry +``` + +Adding the `-a` or `--assembler` flag shows the generated assembly instructions: + +```shell +q build examples/hello -a +``` + +Adding the `-v` or `--verbose` flag shows verbose compiler information: + +```shell +q build examples/hello -v +``` + +### [src/build/Build.go](src/build/Build.go) + +The `Build` type defines all the information needed to start building an executable file. +The name of the executable will be equal to the name of the build directory. + +`Run` starts the build which will scan all `.q` source files in the build directory. +Every source file is scanned in its own goroutine for performance reasons. +Parallelization here is possible because the order of files in a directory is not significant. + +The main thread is meanwhile waiting for new function objects to arrive from the scanners. +Once a function has arrived, it will be stored for compilation later. +We need to wait with the compilation step until we have enough information about all identifiers from the scan. + +Then all the functions that were scanned will be compiled in parallel. +We create a separate goroutine for each function compilation. +Each function will then be translated to generic assembler instructions. + +All the functions that are required to run the program will be added to the final assembler. +The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. + +### [src/core/Function.go](src/core/Function.go) + +This is the "heart" of the compiler. +Each function runs `f.Compile` which organizes the source code into an abstract syntax tree that is then compiled via `f.CompileAST`. +You can think of AST nodes as the individual statements in your source code. + +### [src/ast/Parse.go](src/ast/Parse.go) + +This is what generates the AST from tokens. + +### [src/expression/Parse.go](src/expression/Parse.go) + +This is what generates expressions from tokens. + ## Tests ```shell diff --git a/go.mod b/go.mod index 52bbab9..d8c9380 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module git.akyoto.dev/cli/q -go 1.22.4 +go 1.22.6 require ( git.akyoto.dev/go/assert v0.1.3 git.akyoto.dev/go/color v0.1.1 ) -require golang.org/x/sys v0.22.0 // indirect +require golang.org/x/sys v0.23.0 // indirect diff --git a/go.sum b/go.sum index 5e65b25..bf24815 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,5 @@ git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= git.akyoto.dev/go/color v0.1.1 h1:mMAoMIwLBPNy7ocRSxdsCFs7onPC3GfDEiJErCneqRE= git.akyoto.dev/go/color v0.1.1/go.mod h1:ywOjoD0O0sk6bIn92uAJf7mErlEFCuQInL84y4Lqi3Q= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/src/build/arch/arm64/Registers.go b/src/arch/arm64/Registers.go similarity index 85% rename from src/build/arch/arm64/Registers.go rename to src/arch/arm64/Registers.go index a00c709..3fd4f57 100644 --- a/src/build/arch/arm64/Registers.go +++ b/src/arch/arm64/Registers.go @@ -1,6 +1,6 @@ package arm64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" const ( X0 cpu.Register = iota diff --git a/src/build/arch/riscv/Registers.go b/src/arch/riscv/Registers.go similarity index 85% rename from src/build/arch/riscv/Registers.go rename to src/arch/riscv/Registers.go index 846b936..e9436d2 100644 --- a/src/build/arch/riscv/Registers.go +++ b/src/arch/riscv/Registers.go @@ -1,6 +1,6 @@ package riscv -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" const ( X0 cpu.Register = iota diff --git a/src/build/arch/x64/Add.go b/src/arch/x64/Add.go similarity index 92% rename from src/build/arch/x64/Add.go rename to src/arch/x64/Add.go index 2e55c7d..a487a3b 100644 --- a/src/build/arch/x64/Add.go +++ b/src/arch/x64/Add.go @@ -1,7 +1,7 @@ package x64 import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // AddRegisterNumber adds a number to the given register. diff --git a/src/build/arch/x64/Add_test.go b/src/arch/x64/Add_test.go similarity index 97% rename from src/build/arch/x64/Add_test.go rename to src/arch/x64/Add_test.go index 607b467..52d6645 100644 --- a/src/build/arch/x64/Add_test.go +++ b/src/arch/x64/Add_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/And.go b/src/arch/x64/And.go similarity index 92% rename from src/build/arch/x64/And.go rename to src/arch/x64/And.go index e5d8fcb..a20c3c3 100644 --- a/src/build/arch/x64/And.go +++ b/src/arch/x64/And.go @@ -1,7 +1,7 @@ package x64 import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // AndRegisterNumber performs a bitwise AND using a register and a number. diff --git a/src/build/arch/x64/Call.go b/src/arch/x64/Call.go similarity index 100% rename from src/build/arch/x64/Call.go rename to src/arch/x64/Call.go diff --git a/src/build/arch/x64/Compare.go b/src/arch/x64/Compare.go similarity index 92% rename from src/build/arch/x64/Compare.go rename to src/arch/x64/Compare.go index 49b5ba6..3c48dcf 100644 --- a/src/build/arch/x64/Compare.go +++ b/src/arch/x64/Compare.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // Compares the register with the number and sets the status flags in the EFLAGS register. func CompareRegisterNumber(code []byte, register cpu.Register, number int) []byte { diff --git a/src/build/arch/x64/Compare_test.go b/src/arch/x64/Compare_test.go similarity index 97% rename from src/build/arch/x64/Compare_test.go rename to src/arch/x64/Compare_test.go index cef49b3..6372665 100644 --- a/src/build/arch/x64/Compare_test.go +++ b/src/arch/x64/Compare_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Div.go b/src/arch/x64/Div.go similarity index 85% rename from src/build/arch/x64/Div.go rename to src/arch/x64/Div.go index 5c567bd..26439d1 100644 --- a/src/build/arch/x64/Div.go +++ b/src/arch/x64/Div.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // DivRegister divides RDX:RAX by the value in the register. func DivRegister(code []byte, divisor cpu.Register) []byte { diff --git a/src/build/arch/x64/Div_test.go b/src/arch/x64/Div_test.go similarity index 92% rename from src/build/arch/x64/Div_test.go rename to src/arch/x64/Div_test.go index 267250e..3598dbf 100644 --- a/src/build/arch/x64/Div_test.go +++ b/src/arch/x64/Div_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/ExtendRAXToRDX.go b/src/arch/x64/ExtendRAXToRDX.go similarity index 100% rename from src/build/arch/x64/ExtendRAXToRDX.go rename to src/arch/x64/ExtendRAXToRDX.go diff --git a/src/build/arch/x64/Jump.go b/src/arch/x64/Jump.go similarity index 100% rename from src/build/arch/x64/Jump.go rename to src/arch/x64/Jump.go diff --git a/src/build/arch/x64/Jump_test.go b/src/arch/x64/Jump_test.go similarity index 96% rename from src/build/arch/x64/Jump_test.go rename to src/arch/x64/Jump_test.go index 5c12133..fe40c50 100644 --- a/src/build/arch/x64/Jump_test.go +++ b/src/arch/x64/Jump_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Load.go b/src/arch/x64/Load.go similarity index 85% rename from src/build/arch/x64/Load.go rename to src/arch/x64/Load.go index 18e36d2..eb5ac51 100644 --- a/src/build/arch/x64/Load.go +++ b/src/arch/x64/Load.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // LoadRegister loads from memory into a register. func LoadRegister(code []byte, destination cpu.Register, offset byte, length byte, source cpu.Register) []byte { diff --git a/src/build/arch/x64/Load_test.go b/src/arch/x64/Load_test.go similarity index 99% rename from src/build/arch/x64/Load_test.go rename to src/arch/x64/Load_test.go index f906a28..31330c6 100644 --- a/src/build/arch/x64/Load_test.go +++ b/src/arch/x64/Load_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/ModRM.go b/src/arch/x64/ModRM.go similarity index 100% rename from src/build/arch/x64/ModRM.go rename to src/arch/x64/ModRM.go diff --git a/src/build/arch/x64/ModRM_test.go b/src/arch/x64/ModRM_test.go similarity index 95% rename from src/build/arch/x64/ModRM_test.go rename to src/arch/x64/ModRM_test.go index e9d470f..edcaffe 100644 --- a/src/build/arch/x64/ModRM_test.go +++ b/src/arch/x64/ModRM_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Move.go b/src/arch/x64/Move.go similarity index 94% rename from src/build/arch/x64/Move.go rename to src/arch/x64/Move.go index cac1d09..48edc79 100644 --- a/src/build/arch/x64/Move.go +++ b/src/arch/x64/Move.go @@ -3,8 +3,8 @@ package x64 import ( "encoding/binary" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/sizeof" ) // MoveRegisterNumber moves an integer into the given register. diff --git a/src/build/arch/x64/Move_test.go b/src/arch/x64/Move_test.go similarity index 98% rename from src/build/arch/x64/Move_test.go rename to src/arch/x64/Move_test.go index 66cfb4a..faf9fcc 100644 --- a/src/build/arch/x64/Move_test.go +++ b/src/arch/x64/Move_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Mul.go b/src/arch/x64/Mul.go similarity index 91% rename from src/build/arch/x64/Mul.go rename to src/arch/x64/Mul.go index b6d7883..6c0cbea 100644 --- a/src/build/arch/x64/Mul.go +++ b/src/arch/x64/Mul.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // MulRegisterNumber multiplies a register with a number. func MulRegisterNumber(code []byte, destination cpu.Register, number int) []byte { diff --git a/src/build/arch/x64/Mul_test.go b/src/arch/x64/Mul_test.go similarity index 97% rename from src/build/arch/x64/Mul_test.go rename to src/arch/x64/Mul_test.go index 5c029dd..cd8da70 100644 --- a/src/build/arch/x64/Mul_test.go +++ b/src/arch/x64/Mul_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Negate.go b/src/arch/x64/Negate.go similarity index 81% rename from src/build/arch/x64/Negate.go rename to src/arch/x64/Negate.go index 00ae530..479c143 100644 --- a/src/build/arch/x64/Negate.go +++ b/src/arch/x64/Negate.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // NegateRegister negates the value in the register. func NegateRegister(code []byte, register cpu.Register) []byte { diff --git a/src/build/arch/x64/Negate_test.go b/src/arch/x64/Negate_test.go similarity index 92% rename from src/build/arch/x64/Negate_test.go rename to src/arch/x64/Negate_test.go index dd64518..98aa13e 100644 --- a/src/build/arch/x64/Negate_test.go +++ b/src/arch/x64/Negate_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Or.go b/src/arch/x64/Or.go similarity index 92% rename from src/build/arch/x64/Or.go rename to src/arch/x64/Or.go index 2252925..fec37da 100644 --- a/src/build/arch/x64/Or.go +++ b/src/arch/x64/Or.go @@ -1,7 +1,7 @@ package x64 import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // OrRegisterNumber performs a bitwise OR using a register and a number. diff --git a/src/build/arch/x64/Pop.go b/src/arch/x64/Pop.go similarity index 86% rename from src/build/arch/x64/Pop.go rename to src/arch/x64/Pop.go index 4ce82ec..b97182f 100644 --- a/src/build/arch/x64/Pop.go +++ b/src/arch/x64/Pop.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // PopRegister pops a value from the stack and saves it into the register. func PopRegister(code []byte, register cpu.Register) []byte { diff --git a/src/build/arch/x64/Pop_test.go b/src/arch/x64/Pop_test.go similarity index 91% rename from src/build/arch/x64/Pop_test.go rename to src/arch/x64/Pop_test.go index 5375bde..0a4ce98 100644 --- a/src/build/arch/x64/Pop_test.go +++ b/src/arch/x64/Pop_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Push.go b/src/arch/x64/Push.go similarity index 86% rename from src/build/arch/x64/Push.go rename to src/arch/x64/Push.go index 944d1e4..bdae334 100644 --- a/src/build/arch/x64/Push.go +++ b/src/arch/x64/Push.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // PushRegister pushes the value inside the register onto the stack. func PushRegister(code []byte, register cpu.Register) []byte { diff --git a/src/build/arch/x64/Push_test.go b/src/arch/x64/Push_test.go similarity index 91% rename from src/build/arch/x64/Push_test.go rename to src/arch/x64/Push_test.go index 9177532..0dc092b 100644 --- a/src/build/arch/x64/Push_test.go +++ b/src/arch/x64/Push_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/REX.go b/src/arch/x64/REX.go similarity index 100% rename from src/build/arch/x64/REX.go rename to src/arch/x64/REX.go diff --git a/src/build/arch/x64/REX_test.go b/src/arch/x64/REX_test.go similarity index 94% rename from src/build/arch/x64/REX_test.go rename to src/arch/x64/REX_test.go index e48e3c5..b212b24 100644 --- a/src/build/arch/x64/REX_test.go +++ b/src/arch/x64/REX_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Registers.go b/src/arch/x64/Registers.go similarity index 92% rename from src/build/arch/x64/Registers.go rename to src/arch/x64/Registers.go index e8ecbc8..9162141 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/arch/x64/Registers.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" const ( RAX cpu.Register = iota diff --git a/src/build/arch/x64/Return.go b/src/arch/x64/Return.go similarity index 100% rename from src/build/arch/x64/Return.go rename to src/arch/x64/Return.go diff --git a/src/build/arch/x64/SIB.go b/src/arch/x64/SIB.go similarity index 100% rename from src/build/arch/x64/SIB.go rename to src/arch/x64/SIB.go diff --git a/src/build/arch/x64/SIB_test.go b/src/arch/x64/SIB_test.go similarity index 95% rename from src/build/arch/x64/SIB_test.go rename to src/arch/x64/SIB_test.go index 690f04b..7dedf6e 100644 --- a/src/build/arch/x64/SIB_test.go +++ b/src/arch/x64/SIB_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Shift.go b/src/arch/x64/Shift.go similarity index 93% rename from src/build/arch/x64/Shift.go rename to src/arch/x64/Shift.go index 2f88cb1..6fa2251 100644 --- a/src/build/arch/x64/Shift.go +++ b/src/arch/x64/Shift.go @@ -1,7 +1,7 @@ package x64 import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // ShiftLeftNumber shifts the register value by `bitCount` bits to the left. diff --git a/src/build/arch/x64/Store.go b/src/arch/x64/Store.go similarity index 95% rename from src/build/arch/x64/Store.go rename to src/arch/x64/Store.go index f7c8f5b..01bdfc6 100644 --- a/src/build/arch/x64/Store.go +++ b/src/arch/x64/Store.go @@ -3,7 +3,7 @@ package x64 import ( "encoding/binary" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // StoreNumber stores a number into the memory address included in the given register. diff --git a/src/build/arch/x64/Store_test.go b/src/arch/x64/Store_test.go similarity index 99% rename from src/build/arch/x64/Store_test.go rename to src/arch/x64/Store_test.go index 81758d6..1d9d9a9 100644 --- a/src/build/arch/x64/Store_test.go +++ b/src/arch/x64/Store_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Sub.go b/src/arch/x64/Sub.go similarity index 92% rename from src/build/arch/x64/Sub.go rename to src/arch/x64/Sub.go index 268fcc6..fb9a649 100644 --- a/src/build/arch/x64/Sub.go +++ b/src/arch/x64/Sub.go @@ -1,7 +1,7 @@ package x64 import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // SubRegisterNumber subtracts a number from the given register. diff --git a/src/build/arch/x64/Sub_test.go b/src/arch/x64/Sub_test.go similarity index 97% rename from src/build/arch/x64/Sub_test.go rename to src/arch/x64/Sub_test.go index 69199e9..d6898e9 100644 --- a/src/build/arch/x64/Sub_test.go +++ b/src/arch/x64/Sub_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/arch/x64/Syscall.go b/src/arch/x64/Syscall.go similarity index 100% rename from src/build/arch/x64/Syscall.go rename to src/arch/x64/Syscall.go diff --git a/src/build/arch/x64/Xor.go b/src/arch/x64/Xor.go similarity index 92% rename from src/build/arch/x64/Xor.go rename to src/arch/x64/Xor.go index 305dce2..8a469eb 100644 --- a/src/build/arch/x64/Xor.go +++ b/src/arch/x64/Xor.go @@ -1,7 +1,7 @@ package x64 import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // XorRegisterNumber performs a bitwise XOR using a register and a number. diff --git a/src/build/arch/x64/encode.go b/src/arch/x64/encode.go similarity index 94% rename from src/build/arch/x64/encode.go rename to src/arch/x64/encode.go index 1f45001..6e872b5 100644 --- a/src/build/arch/x64/encode.go +++ b/src/arch/x64/encode.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // encode is the core function that encodes an instruction. func encode(code []byte, mod AddressMode, reg cpu.Register, rm cpu.Register, numBytes byte, opCodes ...byte) []byte { diff --git a/src/build/arch/x64/encodeNum.go b/src/arch/x64/encodeNum.go similarity index 86% rename from src/build/arch/x64/encodeNum.go rename to src/arch/x64/encodeNum.go index a9a3313..c6db324 100644 --- a/src/build/arch/x64/encodeNum.go +++ b/src/arch/x64/encodeNum.go @@ -3,8 +3,8 @@ package x64 import ( "encoding/binary" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/sizeof" ) // encodeNum encodes an instruction with up to two registers and a number parameter. diff --git a/src/build/arch/x64/memoryAccess.go b/src/arch/x64/memoryAccess.go similarity index 93% rename from src/build/arch/x64/memoryAccess.go rename to src/arch/x64/memoryAccess.go index b8cafa5..1ff2bca 100644 --- a/src/build/arch/x64/memoryAccess.go +++ b/src/arch/x64/memoryAccess.go @@ -1,6 +1,6 @@ package x64 -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // memoryAccess encodes a memory access. func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { diff --git a/src/build/arch/x64/x64_test.go b/src/arch/x64/x64_test.go similarity index 92% rename from src/build/arch/x64/x64_test.go rename to src/arch/x64/x64_test.go index cdde6cf..0ed00d5 100644 --- a/src/build/arch/x64/x64_test.go +++ b/src/arch/x64/x64_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/build/asm/Assembler.go b/src/asm/Assembler.go similarity index 93% rename from src/build/asm/Assembler.go rename to src/asm/Assembler.go index 4a595cf..c5c9b8d 100644 --- a/src/build/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -3,7 +3,7 @@ package asm import ( "maps" - "git.akyoto.dev/cli/q/src/build/data" + "git.akyoto.dev/cli/q/src/data" ) // Assembler contains a list of instructions. diff --git a/src/build/asm/Finalize.go b/src/asm/Finalize.go similarity index 98% rename from src/build/asm/Finalize.go rename to src/asm/Finalize.go index 093e060..446048a 100644 --- a/src/build/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -6,10 +6,10 @@ import ( "slices" "strings" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/elf" - "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/sizeof" ) // Finalize generates the final machine code. diff --git a/src/build/asm/Instruction.go b/src/asm/Instruction.go similarity index 100% rename from src/build/asm/Instruction.go rename to src/asm/Instruction.go diff --git a/src/build/asm/Instructions.go b/src/asm/Instructions.go similarity index 100% rename from src/build/asm/Instructions.go rename to src/asm/Instructions.go diff --git a/src/build/asm/Label.go b/src/asm/Label.go similarity index 100% rename from src/build/asm/Label.go rename to src/asm/Label.go diff --git a/src/build/asm/Memory.go b/src/asm/Memory.go similarity index 65% rename from src/build/asm/Memory.go rename to src/asm/Memory.go index ef209ec..3e98a71 100644 --- a/src/build/asm/Memory.go +++ b/src/asm/Memory.go @@ -1,6 +1,6 @@ package asm -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" type Memory struct { Base cpu.Register diff --git a/src/build/asm/MemoryNumber.go b/src/asm/MemoryNumber.go similarity index 100% rename from src/build/asm/MemoryNumber.go rename to src/asm/MemoryNumber.go diff --git a/src/build/asm/MemoryRegister.go b/src/asm/MemoryRegister.go similarity index 94% rename from src/build/asm/MemoryRegister.go rename to src/asm/MemoryRegister.go index 927a97c..21232ce 100644 --- a/src/build/asm/MemoryRegister.go +++ b/src/asm/MemoryRegister.go @@ -3,7 +3,7 @@ package asm import ( "fmt" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // MemoryRegister operates with a memory address and a number. diff --git a/src/build/asm/Mnemonic.go b/src/asm/Mnemonic.go similarity index 100% rename from src/build/asm/Mnemonic.go rename to src/asm/Mnemonic.go diff --git a/src/build/asm/Optimizer.go b/src/asm/Optimizer.go similarity index 93% rename from src/build/asm/Optimizer.go rename to src/asm/Optimizer.go index 2ef72ae..7bf6b99 100644 --- a/src/build/asm/Optimizer.go +++ b/src/asm/Optimizer.go @@ -1,6 +1,6 @@ package asm -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // unnecessary returns true if the register/register operation can be skipped. func (a *Assembler) unnecessary(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool { diff --git a/src/build/asm/Pointer.go b/src/asm/Pointer.go similarity index 100% rename from src/build/asm/Pointer.go rename to src/asm/Pointer.go diff --git a/src/build/asm/Register.go b/src/asm/Register.go similarity index 92% rename from src/build/asm/Register.go rename to src/asm/Register.go index 0f737c9..7188752 100644 --- a/src/build/asm/Register.go +++ b/src/asm/Register.go @@ -1,7 +1,7 @@ package asm import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // Register operates with a single register. diff --git a/src/build/asm/RegisterLabel.go b/src/asm/RegisterLabel.go similarity index 94% rename from src/build/asm/RegisterLabel.go rename to src/asm/RegisterLabel.go index f51bf77..8092d9f 100644 --- a/src/build/asm/RegisterLabel.go +++ b/src/asm/RegisterLabel.go @@ -3,7 +3,7 @@ package asm import ( "fmt" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // RegisterLabel operates with a register and a label. diff --git a/src/build/asm/RegisterNumber.go b/src/asm/RegisterNumber.go similarity index 94% rename from src/build/asm/RegisterNumber.go rename to src/asm/RegisterNumber.go index b3bcb9e..6addf74 100644 --- a/src/build/asm/RegisterNumber.go +++ b/src/asm/RegisterNumber.go @@ -3,7 +3,7 @@ package asm import ( "fmt" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // RegisterNumber operates with a register and a number. diff --git a/src/build/asm/RegisterRegister.go b/src/asm/RegisterRegister.go similarity index 94% rename from src/build/asm/RegisterRegister.go rename to src/asm/RegisterRegister.go index 584058d..8631fd6 100644 --- a/src/build/asm/RegisterRegister.go +++ b/src/asm/RegisterRegister.go @@ -3,7 +3,7 @@ package asm import ( "fmt" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // RegisterRegister operates with two registers. diff --git a/src/build/ast/AST.go b/src/ast/AST.go similarity index 100% rename from src/build/ast/AST.go rename to src/ast/AST.go diff --git a/src/build/ast/Assert.go b/src/ast/Assert.go similarity index 78% rename from src/build/ast/Assert.go rename to src/ast/Assert.go index 0f537db..6da47fb 100644 --- a/src/build/ast/Assert.go +++ b/src/ast/Assert.go @@ -1,7 +1,7 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/expression" ) // Assert represents a condition that must be true, otherwise the program stops. diff --git a/src/build/ast/Assign.go b/src/ast/Assign.go similarity index 78% rename from src/build/ast/Assign.go rename to src/ast/Assign.go index 161f968..8bdbe99 100644 --- a/src/build/ast/Assign.go +++ b/src/ast/Assign.go @@ -1,7 +1,7 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/expression" ) // Assign represents an assignment to an existing variable or memory location. diff --git a/src/build/ast/Call.go b/src/ast/Call.go similarity index 67% rename from src/build/ast/Call.go rename to src/ast/Call.go index 6bd7dec..8aa683e 100644 --- a/src/build/ast/Call.go +++ b/src/ast/Call.go @@ -1,6 +1,6 @@ package ast -import "git.akyoto.dev/cli/q/src/build/expression" +import "git.akyoto.dev/cli/q/src/expression" // Call represents a function call. type Call struct { diff --git a/src/build/ast/Count.go b/src/ast/Count.go similarity index 95% rename from src/build/ast/Count.go rename to src/ast/Count.go index 2c61203..2c9f1f7 100644 --- a/src/build/ast/Count.go +++ b/src/ast/Count.go @@ -1,6 +1,6 @@ package ast -import "git.akyoto.dev/cli/q/src/build/token" +import "git.akyoto.dev/cli/q/src/token" // Count counts how often the given token appears in the AST. func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { diff --git a/src/build/ast/Define.go b/src/ast/Define.go similarity index 73% rename from src/build/ast/Define.go rename to src/ast/Define.go index 551c9b7..0b8030c 100644 --- a/src/build/ast/Define.go +++ b/src/ast/Define.go @@ -1,7 +1,7 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/expression" ) // Define represents a variable definition. diff --git a/src/build/ast/EachInstruction.go b/src/ast/EachInstruction.go similarity index 95% rename from src/build/ast/EachInstruction.go rename to src/ast/EachInstruction.go index 927cca6..b1a354f 100644 --- a/src/build/ast/EachInstruction.go +++ b/src/ast/EachInstruction.go @@ -1,6 +1,6 @@ package ast -import "git.akyoto.dev/cli/q/src/build/token" +import "git.akyoto.dev/cli/q/src/token" // EachInstruction calls the function on each instruction. func EachInstruction(body token.List, call func(token.List) error) error { diff --git a/src/build/ast/If.go b/src/ast/If.go similarity index 75% rename from src/build/ast/If.go rename to src/ast/If.go index f99307f..079640d 100644 --- a/src/build/ast/If.go +++ b/src/ast/If.go @@ -1,7 +1,7 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/expression" ) // If represents an if statement. diff --git a/src/build/ast/Loop.go b/src/ast/Loop.go similarity index 100% rename from src/build/ast/Loop.go rename to src/ast/Loop.go diff --git a/src/build/ast/Parse.go b/src/ast/Parse.go similarity index 91% rename from src/build/ast/Parse.go rename to src/ast/Parse.go index 582f497..8b9083e 100644 --- a/src/build/ast/Parse.go +++ b/src/ast/Parse.go @@ -1,8 +1,8 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // Parse generates an AST from a list of tokens. diff --git a/src/build/ast/Return.go b/src/ast/Return.go similarity index 73% rename from src/build/ast/Return.go rename to src/ast/Return.go index 6c8aa2d..8815638 100644 --- a/src/build/ast/Return.go +++ b/src/ast/Return.go @@ -1,7 +1,7 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/expression" ) // Return represents a return statement. diff --git a/src/build/ast/Switch.go b/src/ast/Switch.go similarity index 82% rename from src/build/ast/Switch.go rename to src/ast/Switch.go index 80ff3d3..a5fc6a0 100644 --- a/src/build/ast/Switch.go +++ b/src/ast/Switch.go @@ -1,7 +1,7 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/expression" ) // Switch represents a switch statement. diff --git a/src/build/ast/parseKeyword.go b/src/ast/parseKeyword.go similarity index 95% rename from src/build/ast/parseKeyword.go rename to src/ast/parseKeyword.go index f4a948a..32c2bbd 100644 --- a/src/build/ast/parseKeyword.go +++ b/src/ast/parseKeyword.go @@ -1,9 +1,9 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // parseKeyword generates a keyword node from an instruction. diff --git a/src/build/ast/parseNode.go b/src/ast/parseNode.go similarity index 87% rename from src/build/ast/parseNode.go rename to src/ast/parseNode.go index 8473c29..93fada4 100644 --- a/src/build/ast/parseNode.go +++ b/src/ast/parseNode.go @@ -1,9 +1,9 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // parseNode generates an AST node from an instruction. diff --git a/src/build/ast/parseSwitch.go b/src/ast/parseSwitch.go similarity index 89% rename from src/build/ast/parseSwitch.go rename to src/ast/parseSwitch.go index 752405a..65eb712 100644 --- a/src/build/ast/parseSwitch.go +++ b/src/ast/parseSwitch.go @@ -1,8 +1,8 @@ package ast import ( - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // parseSwitch generates the cases inside a switch statement. diff --git a/src/build/Build.go b/src/build/Build.go index 7f407ce..2e740c6 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -4,8 +4,8 @@ import ( "path/filepath" "strings" - "git.akyoto.dev/cli/q/src/build/compiler" - "git.akyoto.dev/cli/q/src/build/scanner" + "git.akyoto.dev/cli/q/src/compiler" + "git.akyoto.dev/cli/q/src/scanner" ) // Build describes a compiler build. diff --git a/src/build/expression/List.go b/src/build/expression/List.go deleted file mode 100644 index 75c44d0..0000000 --- a/src/build/expression/List.go +++ /dev/null @@ -1,55 +0,0 @@ -package expression - -import ( - "git.akyoto.dev/cli/q/src/build/token" -) - -// NewList generates a list of expressions from comma separated parameters. -func NewList(tokens token.List) []*Expression { - var list []*Expression - - EachParameter(tokens, func(parameter token.List) error { - expression := Parse(parameter) - list = append(list, expression) - return nil - }) - - return list -} - -// EachParameter calls the callback function on each parameter in a comma separated list. -func EachParameter(tokens token.List, call func(token.List) error) error { - start := 0 - groupLevel := 0 - - for i, t := range tokens { - switch t.Kind { - case token.GroupStart, token.ArrayStart, token.BlockStart: - groupLevel++ - - case token.GroupEnd, token.ArrayEnd, token.BlockEnd: - groupLevel-- - - case token.Separator: - if groupLevel > 0 { - continue - } - - parameter := tokens[start:i] - err := call(parameter) - - if err != nil { - return err - } - - start = i + 1 - } - } - - if start != len(tokens) { - parameter := tokens[start:] - return call(parameter) - } - - return nil -} diff --git a/src/build/token/List_test.go b/src/build/token/List_test.go deleted file mode 100644 index b4529f1..0000000 --- a/src/build/token/List_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package token_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/go/assert" -) - -func TestIndexKind(t *testing.T) { - tokens := token.Tokenize([]byte("a{{}}")) - assert.Equal(t, tokens.IndexKind(token.NewLine), -1) - assert.Equal(t, tokens.LastIndexKind(token.NewLine), -1) - assert.Equal(t, tokens.IndexKind(token.BlockStart), 1) - assert.Equal(t, tokens.LastIndexKind(token.BlockStart), 2) - assert.Equal(t, tokens.IndexKind(token.BlockEnd), 3) - assert.Equal(t, tokens.LastIndexKind(token.BlockEnd), 4) -} diff --git a/src/cli/Build.go b/src/cli/Build.go index ba79ec6..ea5926e 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -6,8 +6,8 @@ import ( "strings" "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/errors" ) // Build parses the arguments and creates a build. diff --git a/src/cli/Run.go b/src/cli/Run.go index 7de865b..20b49f6 100644 --- a/src/cli/Run.go +++ b/src/cli/Run.go @@ -5,7 +5,7 @@ import ( "os" "os/exec" - "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/errors" ) // Run builds and runs the executable. diff --git a/src/cli/System.go b/src/cli/System.go index b523d7a..a2a6801 100644 --- a/src/cli/System.go +++ b/src/cli/System.go @@ -5,7 +5,7 @@ import ( "runtime" "strconv" - "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/config" ) // System shows system information. diff --git a/src/build/compiler/Compile.go b/src/compiler/Compile.go similarity index 94% rename from src/build/compiler/Compile.go rename to src/compiler/Compile.go index 4b31d9a..5d17cd5 100644 --- a/src/build/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -3,9 +3,9 @@ package compiler import ( "sync" - "git.akyoto.dev/cli/q/src/build/core" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" ) // Compile waits for the scan to finish and compiles all functions. diff --git a/src/build/compiler/Result.go b/src/compiler/Result.go similarity index 93% rename from src/build/compiler/Result.go rename to src/compiler/Result.go index d9e699a..ca51ba1 100644 --- a/src/build/compiler/Result.go +++ b/src/compiler/Result.go @@ -5,11 +5,11 @@ import ( "io" "os" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/core" - "git.akyoto.dev/cli/q/src/build/elf" - "git.akyoto.dev/cli/q/src/build/os/linux" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/os/linux" ) // Result contains all the compiled functions in a build. diff --git a/src/build/config/config.go b/src/config/config.go similarity index 100% rename from src/build/config/config.go rename to src/config/config.go diff --git a/src/build/config/init.go b/src/config/init.go similarity index 100% rename from src/build/config/init.go rename to src/config/init.go diff --git a/src/build/core/AddBytes.go b/src/core/AddBytes.go similarity index 100% rename from src/build/core/AddBytes.go rename to src/core/AddBytes.go diff --git a/src/build/core/Compare.go b/src/core/Compare.go similarity index 85% rename from src/build/core/Compare.go rename to src/core/Compare.go index c884a82..0244335 100644 --- a/src/build/core/Compare.go +++ b/src/core/Compare.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // Compare evaluates a boolean expression. diff --git a/src/build/core/Compile.go b/src/core/Compile.go similarity index 100% rename from src/build/core/Compile.go rename to src/core/Compile.go diff --git a/src/build/core/CompileAST.go b/src/core/CompileAST.go similarity index 86% rename from src/build/core/CompileAST.go rename to src/core/CompileAST.go index a2faba8..a9d54f5 100644 --- a/src/build/core/CompileAST.go +++ b/src/core/CompileAST.go @@ -1,7 +1,7 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/ast" ) // CompileAST compiles an abstract syntax tree. diff --git a/src/build/core/CompileASTNode.go b/src/core/CompileASTNode.go similarity index 95% rename from src/build/core/CompileASTNode.go rename to src/core/CompileASTNode.go index ddbd05a..e5daa67 100644 --- a/src/build/core/CompileASTNode.go +++ b/src/core/CompileASTNode.go @@ -1,7 +1,7 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/ast" ) // CompileASTNode compiles a node in the AST. diff --git a/src/build/core/CompileAssert.go b/src/core/CompileAssert.go similarity index 86% rename from src/build/core/CompileAssert.go rename to src/core/CompileAssert.go index f601624..f8608e0 100644 --- a/src/build/core/CompileAssert.go +++ b/src/core/CompileAssert.go @@ -3,8 +3,8 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" ) // CompileAssert compiles an assertion. diff --git a/src/build/core/CompileAssign.go b/src/core/CompileAssign.go similarity index 87% rename from src/build/core/CompileAssign.go rename to src/core/CompileAssign.go index d157792..d048b80 100644 --- a/src/build/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" ) // CompileAssign compiles an assign statement. diff --git a/src/build/core/CompileAssignArray.go b/src/core/CompileAssignArray.go similarity index 86% rename from src/build/core/CompileAssignArray.go rename to src/core/CompileAssignArray.go index 8f812d5..8fb040b 100644 --- a/src/build/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/errors" ) // CompileAssignArray compiles an assign statement for array elements. diff --git a/src/build/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go similarity index 87% rename from src/build/core/CompileAssignDivision.go rename to src/core/CompileAssignDivision.go index 9a06c61..7531001 100644 --- a/src/build/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/errors" ) // CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right. diff --git a/src/build/core/CompileCall.go b/src/core/CompileCall.go similarity index 95% rename from src/build/core/CompileCall.go rename to src/core/CompileCall.go index 16c14f2..5036f1f 100644 --- a/src/build/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -3,9 +3,9 @@ package core import ( "strings" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" ) // CompileCall executes a function call. diff --git a/src/build/core/CompileCondition.go b/src/core/CompileCondition.go similarity index 95% rename from src/build/core/CompileCondition.go rename to src/core/CompileCondition.go index b43a65f..28b16b9 100644 --- a/src/build/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -3,8 +3,8 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // CompileCondition inserts code to jump to the start label or end label depending on the truth of the condition. diff --git a/src/build/core/CompileDefinition.go b/src/core/CompileDefinition.go similarity index 84% rename from src/build/core/CompileDefinition.go rename to src/core/CompileDefinition.go index 81ea095..90993d9 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" ) // CompileDefinition compiles a variable definition. diff --git a/src/build/core/CompileIf.go b/src/core/CompileIf.go similarity index 92% rename from src/build/core/CompileIf.go rename to src/core/CompileIf.go index ec15dcb..e7ce92c 100644 --- a/src/build/core/CompileIf.go +++ b/src/core/CompileIf.go @@ -3,8 +3,8 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" ) // CompileIf compiles a branch instruction. diff --git a/src/build/core/CompileLoop.go b/src/core/CompileLoop.go similarity index 85% rename from src/build/core/CompileLoop.go rename to src/core/CompileLoop.go index 83b8c1a..f2ef623 100644 --- a/src/build/core/CompileLoop.go +++ b/src/core/CompileLoop.go @@ -3,8 +3,8 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" ) // CompileLoop compiles a loop instruction. diff --git a/src/build/core/CompileReturn.go b/src/core/CompileReturn.go similarity index 84% rename from src/build/core/CompileReturn.go rename to src/core/CompileReturn.go index e15bc22..754b4b8 100644 --- a/src/build/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/types" ) // CompileReturn compiles a return instruction. diff --git a/src/build/core/CompileSwitch.go b/src/core/CompileSwitch.go similarity index 92% rename from src/build/core/CompileSwitch.go rename to src/core/CompileSwitch.go index 7d0e0f3..7a08153 100644 --- a/src/build/core/CompileSwitch.go +++ b/src/core/CompileSwitch.go @@ -3,8 +3,8 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" ) // CompileSwitch compiles a multi-branch instruction. diff --git a/src/build/core/CompileSyscall.go b/src/core/CompileSyscall.go similarity index 92% rename from src/build/core/CompileSyscall.go rename to src/core/CompileSyscall.go index 63b3a23..cadce36 100644 --- a/src/build/core/CompileSyscall.go +++ b/src/core/CompileSyscall.go @@ -1,7 +1,7 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/expression" ) // CompileSyscall executes a kernel syscall. diff --git a/src/build/core/CompileTokens.go b/src/core/CompileTokens.go similarity index 69% rename from src/build/core/CompileTokens.go rename to src/core/CompileTokens.go index 634c5d7..06bff0f 100644 --- a/src/build/core/CompileTokens.go +++ b/src/core/CompileTokens.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" ) // CompileTokens compiles a token list. diff --git a/src/build/core/Defer.go b/src/core/Defer.go similarity index 100% rename from src/build/core/Defer.go rename to src/core/Defer.go diff --git a/src/build/core/Define.go b/src/core/Define.go similarity index 78% rename from src/build/core/Define.go rename to src/core/Define.go index 0867f1b..ecd4178 100644 --- a/src/build/core/Define.go +++ b/src/core/Define.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/scope" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/scope" + "git.akyoto.dev/cli/q/src/token" ) // Define defines a new variable. diff --git a/src/build/core/Evaluate.go b/src/core/Evaluate.go similarity index 77% rename from src/build/core/Evaluate.go rename to src/core/Evaluate.go index 145f3b1..ed23b7d 100644 --- a/src/build/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. diff --git a/src/build/core/Execute.go b/src/core/Execute.go similarity index 80% rename from src/build/core/Execute.go rename to src/core/Execute.go index e4b7010..74132b8 100644 --- a/src/build/core/Execute.go +++ b/src/core/Execute.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // Execute executes an operation on a register with a value operand. diff --git a/src/build/core/ExecuteLeaf.go b/src/core/ExecuteLeaf.go similarity index 88% rename from src/build/core/ExecuteLeaf.go rename to src/core/ExecuteLeaf.go index 2254a8a..45cbe23 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/core/ExecuteLeaf.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" ) // ExecuteLeaf performs an operation on a register with the given leaf operand. diff --git a/src/build/core/ExecuteRegister.go b/src/core/ExecuteRegister.go similarity index 71% rename from src/build/core/ExecuteRegister.go rename to src/core/ExecuteRegister.go index 6627acf..8a48bd5 100644 --- a/src/build/core/ExecuteRegister.go +++ b/src/core/ExecuteRegister.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" ) // ExecuteRegister performs an operation on a single register. diff --git a/src/build/core/ExecuteRegisterNumber.go b/src/core/ExecuteRegisterNumber.go similarity index 89% rename from src/build/core/ExecuteRegisterNumber.go rename to src/core/ExecuteRegisterNumber.go index 63a45f6..e3cfda4 100644 --- a/src/build/core/ExecuteRegisterNumber.go +++ b/src/core/ExecuteRegisterNumber.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" ) // ExecuteRegisterNumber performs an operation on a register and a number. diff --git a/src/build/core/ExecuteRegisterRegister.go b/src/core/ExecuteRegisterRegister.go similarity index 88% rename from src/build/core/ExecuteRegisterRegister.go rename to src/core/ExecuteRegisterRegister.go index 42363ae..738ed9c 100644 --- a/src/build/core/ExecuteRegisterRegister.go +++ b/src/core/ExecuteRegisterRegister.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" ) // ExecuteRegisterRegister performs an operation on two registers. diff --git a/src/build/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go similarity index 80% rename from src/build/core/ExpressionToMemory.go rename to src/core/ExpressionToMemory.go index 204e17d..f39e734 100644 --- a/src/build/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/sizeof" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/sizeof" + "git.akyoto.dev/cli/q/src/types" ) // ExpressionToMemory puts the result of an expression into the specified memory address. diff --git a/src/build/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go similarity index 87% rename from src/build/core/ExpressionToRegister.go rename to src/core/ExpressionToRegister.go index 7d7d834..8b4fa20 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -1,13 +1,13 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" ) // ExpressionToRegister puts the result of an expression into the specified register. diff --git a/src/build/core/ExpressionsToRegisters.go b/src/core/ExpressionsToRegisters.go similarity index 82% rename from src/build/core/ExpressionsToRegisters.go rename to src/core/ExpressionsToRegisters.go index 982701a..4f2dbed 100644 --- a/src/build/core/ExpressionsToRegisters.go +++ b/src/core/ExpressionsToRegisters.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/expression" ) // ExpressionsToRegisters moves multiple expressions into the specified registers. diff --git a/src/build/core/Fold.go b/src/core/Fold.go similarity index 89% rename from src/build/core/Fold.go rename to src/core/Fold.go index 11b6d59..983d42f 100644 --- a/src/build/core/Fold.go +++ b/src/core/Fold.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // Fold will try to precalculate the results of operations with constants. diff --git a/src/build/core/Function.go b/src/core/Function.go similarity index 75% rename from src/build/core/Function.go rename to src/core/Function.go index 5f8c25c..4ff2c4d 100644 --- a/src/build/core/Function.go +++ b/src/core/Function.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/fs" - "git.akyoto.dev/cli/q/src/build/register" - "git.akyoto.dev/cli/q/src/build/scope" - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/cli/q/src/scope" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" ) // Function represents the smallest unit of code. diff --git a/src/build/core/IdentifierExists.go b/src/core/IdentifierExists.go similarity index 100% rename from src/build/core/IdentifierExists.go rename to src/core/IdentifierExists.go diff --git a/src/build/core/JumpIfFalse.go b/src/core/JumpIfFalse.go similarity index 85% rename from src/build/core/JumpIfFalse.go rename to src/core/JumpIfFalse.go index 042a676..44eb1ba 100644 --- a/src/build/core/JumpIfFalse.go +++ b/src/core/JumpIfFalse.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/token" ) // JumpIfFalse jumps to the label if the previous comparison was false. diff --git a/src/build/core/JumpIfTrue.go b/src/core/JumpIfTrue.go similarity index 85% rename from src/build/core/JumpIfTrue.go rename to src/core/JumpIfTrue.go index 059618e..c8dc487 100644 --- a/src/build/core/JumpIfTrue.go +++ b/src/core/JumpIfTrue.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/token" ) // JumpIfTrue jumps to the label if the previous comparison was true. diff --git a/src/build/core/NewFunction.go b/src/core/NewFunction.go similarity index 72% rename from src/build/core/NewFunction.go rename to src/core/NewFunction.go index 736bf56..0f7bccc 100644 --- a/src/build/core/NewFunction.go +++ b/src/core/NewFunction.go @@ -1,13 +1,13 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/fs" - "git.akyoto.dev/cli/q/src/build/register" - "git.akyoto.dev/cli/q/src/build/scope" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/register" + "git.akyoto.dev/cli/q/src/scope" + "git.akyoto.dev/cli/q/src/token" ) // NewFunction creates a new function. diff --git a/src/build/core/Number.go b/src/core/Number.go similarity index 92% rename from src/build/core/Number.go rename to src/core/Number.go index 394666f..da39481 100644 --- a/src/build/core/Number.go +++ b/src/core/Number.go @@ -5,8 +5,8 @@ import ( "strings" "unicode/utf8" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" ) // Number tries to convert the token into a numeric value. diff --git a/src/build/core/PrintInstructions.go b/src/core/PrintInstructions.go similarity index 97% rename from src/build/core/PrintInstructions.go rename to src/core/PrintInstructions.go index a824f3a..fe47d3a 100644 --- a/src/build/core/PrintInstructions.go +++ b/src/core/PrintInstructions.go @@ -4,7 +4,7 @@ import ( "bytes" "fmt" - "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/go/color/ansi" ) diff --git a/src/build/core/String.go b/src/core/String.go similarity index 100% rename from src/build/core/String.go rename to src/core/String.go diff --git a/src/build/core/TokenToRegister.go b/src/core/TokenToRegister.go similarity index 84% rename from src/build/core/TokenToRegister.go rename to src/core/TokenToRegister.go index 1d3df22..d33c1a7 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" ) // TokenToRegister moves a token into a register. diff --git a/src/build/core/UsesRegister.go b/src/core/UsesRegister.go similarity index 84% rename from src/build/core/UsesRegister.go rename to src/core/UsesRegister.go index 8ae4076..0ad2d90 100644 --- a/src/build/core/UsesRegister.go +++ b/src/core/UsesRegister.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/expression" ) // UsesRegister returns true if evaluating the expression would write or read the given register. diff --git a/src/build/cpu/CPU.go b/src/cpu/CPU.go similarity index 100% rename from src/build/cpu/CPU.go rename to src/cpu/CPU.go diff --git a/src/build/cpu/Register.go b/src/cpu/Register.go similarity index 100% rename from src/build/cpu/Register.go rename to src/cpu/Register.go diff --git a/src/build/cpu/Register_test.go b/src/cpu/Register_test.go similarity index 82% rename from src/build/cpu/Register_test.go rename to src/cpu/Register_test.go index 8c4c4ce..e14f6f0 100644 --- a/src/build/cpu/Register_test.go +++ b/src/cpu/Register_test.go @@ -3,7 +3,7 @@ package cpu_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/cpu/State.go b/src/cpu/State.go similarity index 100% rename from src/build/cpu/State.go rename to src/cpu/State.go diff --git a/src/build/cpu/State_test.go b/src/cpu/State_test.go similarity index 95% rename from src/build/cpu/State_test.go rename to src/cpu/State_test.go index 90cdab7..52b1fa9 100644 --- a/src/build/cpu/State_test.go +++ b/src/cpu/State_test.go @@ -3,7 +3,7 @@ package cpu_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/go/assert" ) diff --git a/src/build/data/Data.go b/src/data/Data.go similarity index 100% rename from src/build/data/Data.go rename to src/data/Data.go diff --git a/src/build/data/Data_test.go b/src/data/Data_test.go similarity index 94% rename from src/build/data/Data_test.go rename to src/data/Data_test.go index d81faa8..b8d0c60 100644 --- a/src/build/data/Data_test.go +++ b/src/data/Data_test.go @@ -3,7 +3,7 @@ package data_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/data" + "git.akyoto.dev/cli/q/src/data" "git.akyoto.dev/go/assert" ) diff --git a/src/build/elf/ELF.go b/src/elf/ELF.go similarity index 98% rename from src/build/elf/ELF.go rename to src/elf/ELF.go index 1cc8b57..2556d1e 100644 --- a/src/build/elf/ELF.go +++ b/src/elf/ELF.go @@ -5,7 +5,7 @@ import ( "encoding/binary" "io" - "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/config" ) // ELF represents an ELF file. diff --git a/src/build/elf/ELF_test.go b/src/elf/ELF_test.go similarity index 77% rename from src/build/elf/ELF_test.go rename to src/elf/ELF_test.go index b609d60..45a9aa2 100644 --- a/src/build/elf/ELF_test.go +++ b/src/elf/ELF_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/build/elf" + "git.akyoto.dev/cli/q/src/elf" ) func TestELF(t *testing.T) { diff --git a/src/build/elf/Header.go b/src/elf/Header.go similarity index 100% rename from src/build/elf/Header.go rename to src/elf/Header.go diff --git a/src/build/elf/Padding.go b/src/elf/Padding.go similarity index 100% rename from src/build/elf/Padding.go rename to src/elf/Padding.go diff --git a/src/build/elf/ProgramHeader.go b/src/elf/ProgramHeader.go similarity index 100% rename from src/build/elf/ProgramHeader.go rename to src/elf/ProgramHeader.go diff --git a/src/build/elf/SectionHeader.go b/src/elf/SectionHeader.go similarity index 100% rename from src/build/elf/SectionHeader.go rename to src/elf/SectionHeader.go diff --git a/src/build/elf/elf.md b/src/elf/elf.md similarity index 100% rename from src/build/elf/elf.md rename to src/elf/elf.md diff --git a/src/build/errors/Base.go b/src/errors/Base.go similarity index 100% rename from src/build/errors/Base.go rename to src/errors/Base.go diff --git a/src/build/errors/Error.go b/src/errors/Error.go similarity index 94% rename from src/build/errors/Error.go rename to src/errors/Error.go index f0c54ef..d23d7a0 100644 --- a/src/build/errors/Error.go +++ b/src/errors/Error.go @@ -5,8 +5,8 @@ import ( "os" "path/filepath" - "git.akyoto.dev/cli/q/src/build/fs" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" ) // Error is a compiler error at a given line and column. diff --git a/src/build/errors/InvalidCharacter.go b/src/errors/InvalidCharacter.go similarity index 100% rename from src/build/errors/InvalidCharacter.go rename to src/errors/InvalidCharacter.go diff --git a/src/build/errors/InvalidInstruction.go b/src/errors/InvalidInstruction.go similarity index 100% rename from src/build/errors/InvalidInstruction.go rename to src/errors/InvalidInstruction.go diff --git a/src/build/errors/InvalidOperator.go b/src/errors/InvalidOperator.go similarity index 100% rename from src/build/errors/InvalidOperator.go rename to src/errors/InvalidOperator.go diff --git a/src/build/errors/KeywordNotImplemented.go b/src/errors/KeywordNotImplemented.go similarity index 100% rename from src/build/errors/KeywordNotImplemented.go rename to src/errors/KeywordNotImplemented.go diff --git a/src/build/errors/NumberExceedsBounds.go b/src/errors/NumberExceedsBounds.go similarity index 100% rename from src/build/errors/NumberExceedsBounds.go rename to src/errors/NumberExceedsBounds.go diff --git a/src/build/errors/Stack.go b/src/errors/Stack.go similarity index 100% rename from src/build/errors/Stack.go rename to src/errors/Stack.go diff --git a/src/build/errors/TypeMismatch.go b/src/errors/TypeMismatch.go similarity index 100% rename from src/build/errors/TypeMismatch.go rename to src/errors/TypeMismatch.go diff --git a/src/build/errors/UnknownCLIParameter.go b/src/errors/UnknownCLIParameter.go similarity index 100% rename from src/build/errors/UnknownCLIParameter.go rename to src/errors/UnknownCLIParameter.go diff --git a/src/build/errors/UnknownFunction.go b/src/errors/UnknownFunction.go similarity index 100% rename from src/build/errors/UnknownFunction.go rename to src/errors/UnknownFunction.go diff --git a/src/build/errors/UnknownIdentifier.go b/src/errors/UnknownIdentifier.go similarity index 100% rename from src/build/errors/UnknownIdentifier.go rename to src/errors/UnknownIdentifier.go diff --git a/src/build/errors/UnknownPackage.go b/src/errors/UnknownPackage.go similarity index 100% rename from src/build/errors/UnknownPackage.go rename to src/errors/UnknownPackage.go diff --git a/src/build/errors/UnusedImport.go b/src/errors/UnusedImport.go similarity index 100% rename from src/build/errors/UnusedImport.go rename to src/errors/UnusedImport.go diff --git a/src/build/errors/UnusedVariable.go b/src/errors/UnusedVariable.go similarity index 100% rename from src/build/errors/UnusedVariable.go rename to src/errors/UnusedVariable.go diff --git a/src/build/errors/VariableAlreadyExists.go b/src/errors/VariableAlreadyExists.go similarity index 100% rename from src/build/errors/VariableAlreadyExists.go rename to src/errors/VariableAlreadyExists.go diff --git a/src/build/expression/Expression.go b/src/expression/Expression.go similarity index 98% rename from src/build/expression/Expression.go rename to src/expression/Expression.go index 55abb19..2344a37 100644 --- a/src/build/expression/Expression.go +++ b/src/expression/Expression.go @@ -3,7 +3,7 @@ package expression import ( "strings" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/token" ) // Expression is a binary tree with an operator on each node. diff --git a/src/build/expression/Expression_test.go b/src/expression/Expression_test.go similarity index 91% rename from src/build/expression/Expression_test.go rename to src/expression/Expression_test.go index ebcc895..1e03ba8 100644 --- a/src/build/expression/Expression_test.go +++ b/src/expression/Expression_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/go/assert" ) @@ -157,28 +157,6 @@ func TestEachLeaf(t *testing.T) { assert.Equal(t, err.Error(), "error") } -func TestEachParameter(t *testing.T) { - src := []byte("1+2,3*4,5*6,7+8") - tokens := token.Tokenize(src) - parameters := []string{} - - err := expression.EachParameter(tokens, func(parameter token.List) error { - expr := expression.Parse(parameter) - parameters = append(parameters, expr.String(src)) - return nil - }) - - assert.Nil(t, err) - assert.DeepEqual(t, parameters, []string{"(+ 1 2)", "(* 3 4)", "(* 5 6)", "(+ 7 8)"}) - - err = expression.EachParameter(tokens, func(parameter token.List) error { - return fmt.Errorf("error") - }) - - assert.NotNil(t, err) - assert.Equal(t, err.Error(), "error") -} - func TestRemoveChild(t *testing.T) { src := []byte("(1+2-3*4)+(5*6-7+8)") tokens := token.Tokenize(src) diff --git a/src/expression/List.go b/src/expression/List.go new file mode 100644 index 0000000..6431451 --- /dev/null +++ b/src/expression/List.go @@ -0,0 +1,18 @@ +package expression + +import ( + "git.akyoto.dev/cli/q/src/token" +) + +// NewList generates a list of expressions from comma separated parameters. +func NewList(tokens token.List) []*Expression { + var list []*Expression + + tokens.Split(func(parameter token.List) error { + expression := Parse(parameter) + list = append(list, expression) + return nil + }) + + return list +} diff --git a/src/build/expression/Operator.go b/src/expression/Operator.go similarity index 98% rename from src/build/expression/Operator.go rename to src/expression/Operator.go index 96431f1..57468da 100644 --- a/src/build/expression/Operator.go +++ b/src/expression/Operator.go @@ -3,7 +3,7 @@ package expression import ( "math" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/token" ) // Operator represents an operator for mathematical expressions. diff --git a/src/build/expression/Parse.go b/src/expression/Parse.go similarity index 98% rename from src/build/expression/Parse.go rename to src/expression/Parse.go index 87e9768..3ed513e 100644 --- a/src/build/expression/Parse.go +++ b/src/expression/Parse.go @@ -3,7 +3,7 @@ package expression import ( "math" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/token" ) // Parse generates an expression tree from tokens. diff --git a/src/build/expression/bench_test.go b/src/expression/bench_test.go similarity index 72% rename from src/build/expression/bench_test.go rename to src/expression/bench_test.go index c447910..c6266c3 100644 --- a/src/build/expression/bench_test.go +++ b/src/expression/bench_test.go @@ -3,8 +3,8 @@ package expression_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) func BenchmarkExpression(b *testing.B) { diff --git a/src/build/fs/File.go b/src/fs/File.go similarity index 78% rename from src/build/fs/File.go rename to src/fs/File.go index 205daa1..76a43bb 100644 --- a/src/build/fs/File.go +++ b/src/fs/File.go @@ -1,6 +1,6 @@ package fs -import "git.akyoto.dev/cli/q/src/build/token" +import "git.akyoto.dev/cli/q/src/token" // File represents a single source file. type File struct { diff --git a/src/build/fs/Import.go b/src/fs/Import.go similarity index 77% rename from src/build/fs/Import.go rename to src/fs/Import.go index 6690425..ce9032e 100644 --- a/src/build/fs/Import.go +++ b/src/fs/Import.go @@ -1,6 +1,6 @@ package fs -import "git.akyoto.dev/cli/q/src/build/token" +import "git.akyoto.dev/cli/q/src/token" // Import represents an import statement in a file. type Import struct { diff --git a/src/build/fs/Walk.go b/src/fs/Walk.go similarity index 100% rename from src/build/fs/Walk.go rename to src/fs/Walk.go diff --git a/src/build/fs/Walk_test.go b/src/fs/Walk_test.go similarity index 93% rename from src/build/fs/Walk_test.go rename to src/fs/Walk_test.go index 82651ce..09a0830 100644 --- a/src/build/fs/Walk_test.go +++ b/src/fs/Walk_test.go @@ -3,7 +3,7 @@ package fs_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/go/assert" ) diff --git a/src/build/os/linux/Syscall.go b/src/os/linux/Syscall.go similarity index 100% rename from src/build/os/linux/Syscall.go rename to src/os/linux/Syscall.go diff --git a/src/build/register/AddLabel.go b/src/register/AddLabel.go similarity index 73% rename from src/build/register/AddLabel.go rename to src/register/AddLabel.go index 3189ee0..4667275 100644 --- a/src/build/register/AddLabel.go +++ b/src/register/AddLabel.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/build/asm" +import "git.akyoto.dev/cli/q/src/asm" func (f *Machine) AddLabel(label string) { f.Assembler.Label(asm.LABEL, label) diff --git a/src/build/register/Call.go b/src/register/Call.go similarity index 100% rename from src/build/register/Call.go rename to src/register/Call.go diff --git a/src/build/register/Comment.go b/src/register/Comment.go similarity index 100% rename from src/build/register/Comment.go rename to src/register/Comment.go diff --git a/src/build/register/FreeRegister.go b/src/register/FreeRegister.go similarity index 76% rename from src/build/register/FreeRegister.go rename to src/register/FreeRegister.go index 5c459b7..4fd016e 100644 --- a/src/build/register/FreeRegister.go +++ b/src/register/FreeRegister.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // FreeRegister frees a register. func (f *Machine) FreeRegister(register cpu.Register) { diff --git a/src/build/register/Jump.go b/src/register/Jump.go similarity index 76% rename from src/build/register/Jump.go rename to src/register/Jump.go index ff77d01..98045fd 100644 --- a/src/build/register/Jump.go +++ b/src/register/Jump.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/build/asm" +import "git.akyoto.dev/cli/q/src/asm" func (f *Machine) Jump(mnemonic asm.Mnemonic, label string) { f.Assembler.Label(mnemonic, label) diff --git a/src/build/register/Machine.go b/src/register/Machine.go similarity index 62% rename from src/build/register/Machine.go rename to src/register/Machine.go index 50a22b7..07f64b2 100644 --- a/src/build/register/Machine.go +++ b/src/register/Machine.go @@ -1,9 +1,9 @@ package register import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/scope" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/scope" ) // Machine is a register usage aware assembler. diff --git a/src/build/register/MemoryNumber.go b/src/register/MemoryNumber.go similarity index 78% rename from src/build/register/MemoryNumber.go rename to src/register/MemoryNumber.go index 6e99fda..2c1177f 100644 --- a/src/build/register/MemoryNumber.go +++ b/src/register/MemoryNumber.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/build/asm" +import "git.akyoto.dev/cli/q/src/asm" func (f *Machine) MemoryNumber(mnemonic asm.Mnemonic, a asm.Memory, b int) { f.Assembler.MemoryNumber(mnemonic, a, b) diff --git a/src/build/register/MemoryRegister.go b/src/register/MemoryRegister.go similarity index 70% rename from src/build/register/MemoryRegister.go rename to src/register/MemoryRegister.go index 6ad3bc0..1a3c770 100644 --- a/src/build/register/MemoryRegister.go +++ b/src/register/MemoryRegister.go @@ -1,8 +1,8 @@ package register import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" ) func (f *Machine) MemoryRegister(mnemonic asm.Mnemonic, a asm.Memory, b cpu.Register) { diff --git a/src/build/register/NewRegister.go b/src/register/NewRegister.go similarity index 83% rename from src/build/register/NewRegister.go rename to src/register/NewRegister.go index fb2300c..dbe3cc0 100644 --- a/src/build/register/NewRegister.go +++ b/src/register/NewRegister.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // NewRegister reserves a register. func (f *Machine) NewRegister() cpu.Register { diff --git a/src/build/register/Register.go b/src/register/Register.go similarity index 73% rename from src/build/register/Register.go rename to src/register/Register.go index 89f0ca1..fa972a1 100644 --- a/src/build/register/Register.go +++ b/src/register/Register.go @@ -1,8 +1,8 @@ package register import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" ) func (f *Machine) Register(mnemonic asm.Mnemonic, a cpu.Register) { diff --git a/src/build/register/RegisterIsUsed.go b/src/register/RegisterIsUsed.go similarity index 78% rename from src/build/register/RegisterIsUsed.go rename to src/register/RegisterIsUsed.go index 0ba6d0e..fedf149 100644 --- a/src/build/register/RegisterIsUsed.go +++ b/src/register/RegisterIsUsed.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // RegisterIsUsed reserves a register. func (f *Machine) RegisterIsUsed(register cpu.Register) bool { diff --git a/src/build/register/RegisterLabel.go b/src/register/RegisterLabel.go similarity index 77% rename from src/build/register/RegisterLabel.go rename to src/register/RegisterLabel.go index 70690bc..a97de41 100644 --- a/src/build/register/RegisterLabel.go +++ b/src/register/RegisterLabel.go @@ -1,8 +1,8 @@ package register import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" ) func (f *Machine) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { diff --git a/src/build/register/RegisterNumber.go b/src/register/RegisterNumber.go similarity index 86% rename from src/build/register/RegisterNumber.go rename to src/register/RegisterNumber.go index 32b552b..a97dd8b 100644 --- a/src/build/register/RegisterNumber.go +++ b/src/register/RegisterNumber.go @@ -1,9 +1,9 @@ package register import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/sizeof" ) func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { diff --git a/src/build/register/RegisterRegister.go b/src/register/RegisterRegister.go similarity index 79% rename from src/build/register/RegisterRegister.go rename to src/register/RegisterRegister.go index 5f87783..98ac5ea 100644 --- a/src/build/register/RegisterRegister.go +++ b/src/register/RegisterRegister.go @@ -1,8 +1,8 @@ package register import ( - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" ) func (f *Machine) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) { diff --git a/src/build/register/Return.go b/src/register/Return.go similarity index 100% rename from src/build/register/Return.go rename to src/register/Return.go diff --git a/src/build/register/SaveRegister.go b/src/register/SaveRegister.go similarity index 87% rename from src/build/register/SaveRegister.go rename to src/register/SaveRegister.go index 18dfb76..c5a56c9 100644 --- a/src/build/register/SaveRegister.go +++ b/src/register/SaveRegister.go @@ -3,8 +3,8 @@ package register import ( "slices" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" ) // SaveRegister attempts to move a variable occupying this register to another register. diff --git a/src/build/register/Syscall.go b/src/register/Syscall.go similarity index 100% rename from src/build/register/Syscall.go rename to src/register/Syscall.go diff --git a/src/build/register/UseRegister.go b/src/register/UseRegister.go similarity index 78% rename from src/build/register/UseRegister.go rename to src/register/UseRegister.go index ce45d81..996181f 100644 --- a/src/build/register/UseRegister.go +++ b/src/register/UseRegister.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/build/cpu" +import "git.akyoto.dev/cli/q/src/cpu" // Use marks a register to be currently in use. func (f *Machine) UseRegister(register cpu.Register) { diff --git a/src/build/register/postInstruction.go b/src/register/postInstruction.go similarity index 77% rename from src/build/register/postInstruction.go rename to src/register/postInstruction.go index d3e2b5d..18f9120 100644 --- a/src/build/register/postInstruction.go +++ b/src/register/postInstruction.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/build/config" +import "git.akyoto.dev/cli/q/src/config" func (f *Machine) postInstruction() { if !config.Assembler { diff --git a/src/build/scanner/Scan.go b/src/scanner/Scan.go similarity index 86% rename from src/build/scanner/Scan.go rename to src/scanner/Scan.go index 4b25a3d..f18cbff 100644 --- a/src/build/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -1,8 +1,8 @@ package scanner import ( - "git.akyoto.dev/cli/q/src/build/core" - "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/fs" ) // Scan scans the list of files. diff --git a/src/build/scanner/Scanner.go b/src/scanner/Scanner.go similarity index 77% rename from src/build/scanner/Scanner.go rename to src/scanner/Scanner.go index 5e6b0c2..434fe30 100644 --- a/src/build/scanner/Scanner.go +++ b/src/scanner/Scanner.go @@ -3,8 +3,8 @@ package scanner import ( "sync" - "git.akyoto.dev/cli/q/src/build/core" - "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/fs" ) // Scanner is used to scan files before the actual compilation step. diff --git a/src/build/scanner/queue.go b/src/scanner/queue.go similarity index 100% rename from src/build/scanner/queue.go rename to src/scanner/queue.go diff --git a/src/build/scanner/queueDirectory.go b/src/scanner/queueDirectory.go similarity index 92% rename from src/build/scanner/queueDirectory.go rename to src/scanner/queueDirectory.go index d21c8ce..167905b 100644 --- a/src/build/scanner/queueDirectory.go +++ b/src/scanner/queueDirectory.go @@ -4,7 +4,7 @@ import ( "path/filepath" "strings" - "git.akyoto.dev/cli/q/src/build/fs" + "git.akyoto.dev/cli/q/src/fs" ) // queueDirectory queues an entire directory to be scanned. diff --git a/src/build/scanner/queueFile.go b/src/scanner/queueFile.go similarity index 100% rename from src/build/scanner/queueFile.go rename to src/scanner/queueFile.go diff --git a/src/build/scanner/scanFile.go b/src/scanner/scanFile.go similarity index 92% rename from src/build/scanner/scanFile.go rename to src/scanner/scanFile.go index 714ba72..46f25ff 100644 --- a/src/build/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -4,15 +4,14 @@ import ( "os" "path/filepath" - "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/config" - "git.akyoto.dev/cli/q/src/build/core" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/fs" - "git.akyoto.dev/cli/q/src/build/scope" - "git.akyoto.dev/cli/q/src/build/token" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/scope" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" ) // scanFile scans a single file. @@ -240,7 +239,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { parameters := tokens[paramsStart:paramsEnd] count := 0 - err := expression.EachParameter(parameters, func(tokens token.List) error { + err := parameters.Split(func(tokens token.List) error { if len(tokens) < 2 { return errors.New(errors.MissingType, file, tokens[0].End()) } diff --git a/src/build/scope/Scope.go b/src/scope/Scope.go similarity index 93% rename from src/build/scope/Scope.go rename to src/scope/Scope.go index 9bbb3db..fd1f750 100644 --- a/src/build/scope/Scope.go +++ b/src/scope/Scope.go @@ -1,7 +1,7 @@ package scope import ( - "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/cpu" ) // Scope represents an independent code block. diff --git a/src/build/scope/Stack.go b/src/scope/Stack.go similarity index 94% rename from src/build/scope/Stack.go rename to src/scope/Stack.go index 750bc4c..dbfe206 100644 --- a/src/build/scope/Stack.go +++ b/src/scope/Stack.go @@ -1,9 +1,9 @@ package scope import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/token" ) // Stack is a stack of scopes. diff --git a/src/build/scope/Variable.go b/src/scope/Variable.go similarity index 85% rename from src/build/scope/Variable.go rename to src/scope/Variable.go index 0346ca1..7c6081e 100644 --- a/src/build/scope/Variable.go +++ b/src/scope/Variable.go @@ -1,8 +1,8 @@ package scope import ( - "git.akyoto.dev/cli/q/src/build/cpu" - "git.akyoto.dev/cli/q/src/build/types" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/types" ) // Variable represents a named register. diff --git a/src/build/sizeof/Signed.go b/src/sizeof/Signed.go similarity index 100% rename from src/build/sizeof/Signed.go rename to src/sizeof/Signed.go diff --git a/src/build/sizeof/Signed_test.go b/src/sizeof/Signed_test.go similarity index 93% rename from src/build/sizeof/Signed_test.go rename to src/sizeof/Signed_test.go index 6ddc15f..bd4d049 100644 --- a/src/build/sizeof/Signed_test.go +++ b/src/sizeof/Signed_test.go @@ -4,7 +4,7 @@ import ( "math" "testing" - "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/cli/q/src/sizeof" "git.akyoto.dev/go/assert" ) diff --git a/src/build/sizeof/Unsigned.go b/src/sizeof/Unsigned.go similarity index 100% rename from src/build/sizeof/Unsigned.go rename to src/sizeof/Unsigned.go diff --git a/src/build/sizeof/Unsigned_test.go b/src/sizeof/Unsigned_test.go similarity index 89% rename from src/build/sizeof/Unsigned_test.go rename to src/sizeof/Unsigned_test.go index e9a5dc8..b4e78a2 100644 --- a/src/build/sizeof/Unsigned_test.go +++ b/src/sizeof/Unsigned_test.go @@ -4,7 +4,7 @@ import ( "math" "testing" - "git.akyoto.dev/cli/q/src/build/sizeof" + "git.akyoto.dev/cli/q/src/sizeof" "git.akyoto.dev/go/assert" ) diff --git a/src/build/token/Count.go b/src/token/Count.go similarity index 100% rename from src/build/token/Count.go rename to src/token/Count.go diff --git a/src/build/token/Count_test.go b/src/token/Count_test.go similarity index 92% rename from src/build/token/Count_test.go rename to src/token/Count_test.go index 8742d13..9c8a228 100644 --- a/src/build/token/Count_test.go +++ b/src/token/Count_test.go @@ -3,7 +3,7 @@ package token_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/go/assert" ) diff --git a/src/build/token/Kind.go b/src/token/Kind.go similarity index 100% rename from src/build/token/Kind.go rename to src/token/Kind.go diff --git a/src/build/token/Length.go b/src/token/Length.go similarity index 100% rename from src/build/token/Length.go rename to src/token/Length.go diff --git a/src/build/token/List.go b/src/token/List.go similarity index 54% rename from src/build/token/List.go rename to src/token/List.go index 37ad0eb..c66dc27 100644 --- a/src/build/token/List.go +++ b/src/token/List.go @@ -29,6 +29,43 @@ func (list List) LastIndexKind(kind Kind) int { return -1 } +// Split calls the callback function on each set of tokens in a comma separated list. +func (list List) Split(call func(List) error) error { + start := 0 + groupLevel := 0 + + for i, t := range list { + switch t.Kind { + case GroupStart, ArrayStart, BlockStart: + groupLevel++ + + case GroupEnd, ArrayEnd, BlockEnd: + groupLevel-- + + case Separator: + if groupLevel > 0 { + continue + } + + parameter := list[start:i] + err := call(parameter) + + if err != nil { + return err + } + + start = i + 1 + } + } + + if start != len(list) { + parameter := list[start:] + return call(parameter) + } + + return nil +} + // Text returns the concatenated token text. func (list List) Text(source []byte) string { tmp := strings.Builder{} diff --git a/src/token/List_test.go b/src/token/List_test.go new file mode 100644 index 0000000..71fbf84 --- /dev/null +++ b/src/token/List_test.go @@ -0,0 +1,40 @@ +package token_test + +import ( + "fmt" + "testing" + + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/go/assert" +) + +func TestIndexKind(t *testing.T) { + tokens := token.Tokenize([]byte("a{{}}")) + assert.Equal(t, tokens.IndexKind(token.NewLine), -1) + assert.Equal(t, tokens.LastIndexKind(token.NewLine), -1) + assert.Equal(t, tokens.IndexKind(token.BlockStart), 1) + assert.Equal(t, tokens.LastIndexKind(token.BlockStart), 2) + assert.Equal(t, tokens.IndexKind(token.BlockEnd), 3) + assert.Equal(t, tokens.LastIndexKind(token.BlockEnd), 4) +} + +func TestSplit(t *testing.T) { + src := []byte("1+2,3*4,5*6,7+8") + tokens := token.Tokenize(src) + parameters := []string{} + + err := tokens.Split(func(parameter token.List) error { + parameters = append(parameters, parameter.Text(src)) + return nil + }) + + assert.Nil(t, err) + assert.DeepEqual(t, parameters, []string{"1+2", "3*4", "5*6", "7+8"}) + + err = tokens.Split(func(parameter token.List) error { + return fmt.Errorf("error") + }) + + assert.NotNil(t, err) + assert.Equal(t, err.Error(), "error") +} diff --git a/src/build/token/Position.go b/src/token/Position.go similarity index 100% rename from src/build/token/Position.go rename to src/token/Position.go diff --git a/src/build/token/Token.go b/src/token/Token.go similarity index 100% rename from src/build/token/Token.go rename to src/token/Token.go diff --git a/src/build/token/Token_test.go b/src/token/Token_test.go similarity index 96% rename from src/build/token/Token_test.go rename to src/token/Token_test.go index ba825cc..9396480 100644 --- a/src/build/token/Token_test.go +++ b/src/token/Token_test.go @@ -3,7 +3,7 @@ package token_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/go/assert" ) diff --git a/src/build/token/Tokenize.go b/src/token/Tokenize.go similarity index 100% rename from src/build/token/Tokenize.go rename to src/token/Tokenize.go diff --git a/src/build/token/Tokenize_test.go b/src/token/Tokenize_test.go similarity index 99% rename from src/build/token/Tokenize_test.go rename to src/token/Tokenize_test.go index 4d6e36b..e00f978 100644 --- a/src/build/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -3,7 +3,7 @@ package token_test import ( "testing" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/go/assert" ) diff --git a/src/build/token/bench_test.go b/src/token/bench_test.go similarity index 90% rename from src/build/token/bench_test.go rename to src/token/bench_test.go index 3ec6007..acc6359 100644 --- a/src/build/token/bench_test.go +++ b/src/token/bench_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "git.akyoto.dev/cli/q/src/build/token" + "git.akyoto.dev/cli/q/src/token" ) func BenchmarkLines(b *testing.B) { diff --git a/src/build/types/New.go b/src/types/New.go similarity index 100% rename from src/build/types/New.go rename to src/types/New.go diff --git a/src/build/types/NewList.go b/src/types/NewList.go similarity index 60% rename from src/build/types/NewList.go rename to src/types/NewList.go index 91bb4ca..c315c8d 100644 --- a/src/build/types/NewList.go +++ b/src/types/NewList.go @@ -1,15 +1,12 @@ package types -import ( - "git.akyoto.dev/cli/q/src/build/expression" - "git.akyoto.dev/cli/q/src/build/token" -) +import "git.akyoto.dev/cli/q/src/token" // NewList generates a list of types from comma separated tokens. func NewList(tokens token.List, source []byte) []Type { var list []Type - expression.EachParameter(tokens, func(parameter token.List) error { + tokens.Split(func(parameter token.List) error { typ := New(parameter.Text(source)) list = append(list, typ) return nil diff --git a/src/build/types/Type.go b/src/types/Type.go similarity index 100% rename from src/build/types/Type.go rename to src/types/Type.go diff --git a/tests/errors_test.go b/tests/errors_test.go index 61eab3d..484cf94 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -6,7 +6,7 @@ import ( "testing" "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/go/assert" ) diff --git a/tests/examples_test.go b/tests/examples_test.go index 2279740..63dd7f3 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -5,7 +5,7 @@ import ( "testing" "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/go/assert" ) diff --git a/tests/programs_test.go b/tests/programs_test.go index 7e002a7..a6b117f 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -8,7 +8,7 @@ import ( "testing" "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/go/assert" ) From 8eabcf258ddda9e2886a2b67ddc8b1362657c939 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 7 Aug 2024 23:30:53 +0200 Subject: [PATCH 0485/1012] Added more tests --- README.md | 2 + src/arch/arm64/Registers.go | 2 +- src/arch/arm64/Registers_test.go | 12 +++++ src/arch/riscv/Registers.go | 2 +- src/arch/riscv/Registers_test.go | 12 +++++ src/arch/x64/Add.go | 8 +-- src/arch/x64/And.go | 8 +-- src/arch/x64/And_test.go | 88 ++++++++++++++++++++++++++++++++ src/arch/x64/Mul.go | 8 +-- src/arch/x64/Or.go | 8 +-- src/arch/x64/Or_test.go | 88 ++++++++++++++++++++++++++++++++ src/arch/x64/Registers_test.go | 12 +++++ src/arch/x64/Shift_test.go | 71 ++++++++++++++++++++++++++ src/arch/x64/Sub.go | 8 +-- src/arch/x64/Xor.go | 8 +-- src/arch/x64/Xor_test.go | 88 ++++++++++++++++++++++++++++++++ 16 files changed, 399 insertions(+), 26 deletions(-) create mode 100644 src/arch/arm64/Registers_test.go create mode 100644 src/arch/riscv/Registers_test.go create mode 100644 src/arch/x64/And_test.go create mode 100644 src/arch/x64/Or_test.go create mode 100644 src/arch/x64/Registers_test.go create mode 100644 src/arch/x64/Shift_test.go create mode 100644 src/arch/x64/Xor_test.go diff --git a/README.md b/README.md index 695e702..0a34d68 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,10 @@ Build a Linux x86-64 ELF executable from `examples/hello` and run it: - [x] Exclude unused functions - [x] Constant folding +- [ ] Constant propagation - [ ] Function call inlining - [ ] Loop unrolls +- [ ] Assembler optimization backend ### Linter diff --git a/src/arch/arm64/Registers.go b/src/arch/arm64/Registers.go index 3fd4f57..cf8c5db 100644 --- a/src/arch/arm64/Registers.go +++ b/src/arch/arm64/Registers.go @@ -36,4 +36,4 @@ const ( X30 ) -var SyscallArgs = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} +var SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} diff --git a/src/arch/arm64/Registers_test.go b/src/arch/arm64/Registers_test.go new file mode 100644 index 0000000..75f5b7c --- /dev/null +++ b/src/arch/arm64/Registers_test.go @@ -0,0 +1,12 @@ +package arm64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arch/arm64" + "git.akyoto.dev/go/assert" +) + +func TestRegisters(t *testing.T) { + assert.NotNil(t, arm64.SyscallInputRegisters) +} diff --git a/src/arch/riscv/Registers.go b/src/arch/riscv/Registers.go index e9436d2..5267be3 100644 --- a/src/arch/riscv/Registers.go +++ b/src/arch/riscv/Registers.go @@ -37,4 +37,4 @@ const ( X31 ) -var SyscallArgs = []cpu.Register{X10, X11, X12, X13, X14, X15, X16} +var SyscallInputRegisters = []cpu.Register{X10, X11, X12, X13, X14, X15, X16} diff --git a/src/arch/riscv/Registers_test.go b/src/arch/riscv/Registers_test.go new file mode 100644 index 0000000..bc74568 --- /dev/null +++ b/src/arch/riscv/Registers_test.go @@ -0,0 +1,12 @@ +package riscv_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arch/riscv" + "git.akyoto.dev/go/assert" +) + +func TestRegisters(t *testing.T) { + assert.NotNil(t, riscv.SyscallInputRegisters) +} diff --git a/src/arch/x64/Add.go b/src/arch/x64/Add.go index a487a3b..fb70735 100644 --- a/src/arch/x64/Add.go +++ b/src/arch/x64/Add.go @@ -5,11 +5,11 @@ import ( ) // AddRegisterNumber adds a number to the given register. -func AddRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, AddressDirect, 0, destination, number, 0x83, 0x81) +func AddRegisterNumber(code []byte, register cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, 0, register, number, 0x83, 0x81) } // AddRegisterRegister adds a register value into another register. -func AddRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, AddressDirect, operand, destination, 8, 0x01) +func AddRegisterRegister(code []byte, register cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, operand, register, 8, 0x01) } diff --git a/src/arch/x64/And.go b/src/arch/x64/And.go index a20c3c3..8556507 100644 --- a/src/arch/x64/And.go +++ b/src/arch/x64/And.go @@ -5,11 +5,11 @@ import ( ) // AndRegisterNumber performs a bitwise AND using a register and a number. -func AndRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, AddressDirect, 0b100, destination, number, 0x83, 0x81) +func AndRegisterNumber(code []byte, register cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, 0b100, register, number, 0x83, 0x81) } // AndRegisterRegister performs a bitwise AND using two registers. -func AndRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, AddressDirect, operand, destination, 8, 0x21) +func AndRegisterRegister(code []byte, register cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, operand, register, 8, 0x21) } diff --git a/src/arch/x64/And_test.go b/src/arch/x64/And_test.go new file mode 100644 index 0000000..e0e60c8 --- /dev/null +++ b/src/arch/x64/And_test.go @@ -0,0 +1,88 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/go/assert" +) + +func TestAndRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0x83, 0xE0, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0x83, 0xE1, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0x83, 0xE2, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0x83, 0xE3, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0x83, 0xE4, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0x83, 0xE5, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0x83, 0xE6, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0x83, 0xE7, 0x01}}, + {x64.R8, 1, []byte{0x49, 0x83, 0xE0, 0x01}}, + {x64.R9, 1, []byte{0x49, 0x83, 0xE1, 0x01}}, + {x64.R10, 1, []byte{0x49, 0x83, 0xE2, 0x01}}, + {x64.R11, 1, []byte{0x49, 0x83, 0xE3, 0x01}}, + {x64.R12, 1, []byte{0x49, 0x83, 0xE4, 0x01}}, + {x64.R13, 1, []byte{0x49, 0x83, 0xE5, 0x01}}, + {x64.R14, 1, []byte{0x49, 0x83, 0xE6, 0x01}}, + {x64.R15, 1, []byte{0x49, 0x83, 0xE7, 0x01}}, + + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE7, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("and %s, %x", pattern.Register, pattern.Number) + code := x64.AndRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestAndRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x4C, 0x21, 0xF8}}, + {x64.RCX, x64.R14, []byte{0x4C, 0x21, 0xF1}}, + {x64.RDX, x64.R13, []byte{0x4C, 0x21, 0xEA}}, + {x64.RBX, x64.R12, []byte{0x4C, 0x21, 0xE3}}, + {x64.RSP, x64.R11, []byte{0x4C, 0x21, 0xDC}}, + {x64.RBP, x64.R10, []byte{0x4C, 0x21, 0xD5}}, + {x64.RSI, x64.R9, []byte{0x4C, 0x21, 0xCE}}, + {x64.RDI, x64.R8, []byte{0x4C, 0x21, 0xC7}}, + {x64.R8, x64.RDI, []byte{0x49, 0x21, 0xF8}}, + {x64.R9, x64.RSI, []byte{0x49, 0x21, 0xF1}}, + {x64.R10, x64.RBP, []byte{0x49, 0x21, 0xEA}}, + {x64.R11, x64.RSP, []byte{0x49, 0x21, 0xE3}}, + {x64.R12, x64.RBX, []byte{0x49, 0x21, 0xDC}}, + {x64.R13, x64.RDX, []byte{0x49, 0x21, 0xD5}}, + {x64.R14, x64.RCX, []byte{0x49, 0x21, 0xCE}}, + {x64.R15, x64.RAX, []byte{0x49, 0x21, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("and %s, %s", pattern.Left, pattern.Right) + code := x64.AndRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arch/x64/Mul.go b/src/arch/x64/Mul.go index 6c0cbea..1d11da3 100644 --- a/src/arch/x64/Mul.go +++ b/src/arch/x64/Mul.go @@ -3,11 +3,11 @@ package x64 import "git.akyoto.dev/cli/q/src/cpu" // MulRegisterNumber multiplies a register with a number. -func MulRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, AddressDirect, destination, destination, number, 0x6B, 0x69) +func MulRegisterNumber(code []byte, register cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, register, register, number, 0x6B, 0x69) } // MulRegisterRegister multiplies a register with another register. -func MulRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, AddressDirect, destination, operand, 8, 0x0F, 0xAF) +func MulRegisterRegister(code []byte, register cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, register, operand, 8, 0x0F, 0xAF) } diff --git a/src/arch/x64/Or.go b/src/arch/x64/Or.go index fec37da..fa5b440 100644 --- a/src/arch/x64/Or.go +++ b/src/arch/x64/Or.go @@ -5,11 +5,11 @@ import ( ) // OrRegisterNumber performs a bitwise OR using a register and a number. -func OrRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, AddressDirect, 0b001, destination, number, 0x83, 0x81) +func OrRegisterNumber(code []byte, register cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, 0b001, register, number, 0x83, 0x81) } // OrRegisterRegister performs a bitwise OR using two registers. -func OrRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, AddressDirect, operand, destination, 8, 0x09) +func OrRegisterRegister(code []byte, register cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, operand, register, 8, 0x09) } diff --git a/src/arch/x64/Or_test.go b/src/arch/x64/Or_test.go new file mode 100644 index 0000000..89bdc5d --- /dev/null +++ b/src/arch/x64/Or_test.go @@ -0,0 +1,88 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/go/assert" +) + +func TestOrRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0x83, 0xC8, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0x83, 0xC9, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0x83, 0xCA, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0x83, 0xCB, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0x83, 0xCC, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0x83, 0xCD, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0x83, 0xCE, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0x83, 0xCF, 0x01}}, + {x64.R8, 1, []byte{0x49, 0x83, 0xC8, 0x01}}, + {x64.R9, 1, []byte{0x49, 0x83, 0xC9, 0x01}}, + {x64.R10, 1, []byte{0x49, 0x83, 0xCA, 0x01}}, + {x64.R11, 1, []byte{0x49, 0x83, 0xCB, 0x01}}, + {x64.R12, 1, []byte{0x49, 0x83, 0xCC, 0x01}}, + {x64.R13, 1, []byte{0x49, 0x83, 0xCD, 0x01}}, + {x64.R14, 1, []byte{0x49, 0x83, 0xCE, 0x01}}, + {x64.R15, 1, []byte{0x49, 0x83, 0xCF, 0x01}}, + + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("or %s, %x", pattern.Register, pattern.Number) + code := x64.OrRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestOrRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x4C, 0x09, 0xF8}}, + {x64.RCX, x64.R14, []byte{0x4C, 0x09, 0xF1}}, + {x64.RDX, x64.R13, []byte{0x4C, 0x09, 0xEA}}, + {x64.RBX, x64.R12, []byte{0x4C, 0x09, 0xE3}}, + {x64.RSP, x64.R11, []byte{0x4C, 0x09, 0xDC}}, + {x64.RBP, x64.R10, []byte{0x4C, 0x09, 0xD5}}, + {x64.RSI, x64.R9, []byte{0x4C, 0x09, 0xCE}}, + {x64.RDI, x64.R8, []byte{0x4C, 0x09, 0xC7}}, + {x64.R8, x64.RDI, []byte{0x49, 0x09, 0xF8}}, + {x64.R9, x64.RSI, []byte{0x49, 0x09, 0xF1}}, + {x64.R10, x64.RBP, []byte{0x49, 0x09, 0xEA}}, + {x64.R11, x64.RSP, []byte{0x49, 0x09, 0xE3}}, + {x64.R12, x64.RBX, []byte{0x49, 0x09, 0xDC}}, + {x64.R13, x64.RDX, []byte{0x49, 0x09, 0xD5}}, + {x64.R14, x64.RCX, []byte{0x49, 0x09, 0xCE}}, + {x64.R15, x64.RAX, []byte{0x49, 0x09, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("or %s, %s", pattern.Left, pattern.Right) + code := x64.OrRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arch/x64/Registers_test.go b/src/arch/x64/Registers_test.go new file mode 100644 index 0000000..bb8ff19 --- /dev/null +++ b/src/arch/x64/Registers_test.go @@ -0,0 +1,12 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/go/assert" +) + +func TestRegisters(t *testing.T) { + assert.NotContains(t, x64.GeneralRegisters, x64.RSP) +} diff --git a/src/arch/x64/Shift_test.go b/src/arch/x64/Shift_test.go new file mode 100644 index 0000000..926c98d --- /dev/null +++ b/src/arch/x64/Shift_test.go @@ -0,0 +1,71 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/go/assert" +) + +func TestShiftLeftNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0xC1, 0xE0, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0xC1, 0xE1, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0xC1, 0xE2, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0xC1, 0xE3, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0xC1, 0xE4, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0xC1, 0xE5, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0xC1, 0xE6, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0xC1, 0xE7, 0x01}}, + {x64.R8, 1, []byte{0x49, 0xC1, 0xE0, 0x01}}, + {x64.R9, 1, []byte{0x49, 0xC1, 0xE1, 0x01}}, + {x64.R10, 1, []byte{0x49, 0xC1, 0xE2, 0x01}}, + {x64.R11, 1, []byte{0x49, 0xC1, 0xE3, 0x01}}, + {x64.R12, 1, []byte{0x49, 0xC1, 0xE4, 0x01}}, + {x64.R13, 1, []byte{0x49, 0xC1, 0xE5, 0x01}}, + {x64.R14, 1, []byte{0x49, 0xC1, 0xE6, 0x01}}, + {x64.R15, 1, []byte{0x49, 0xC1, 0xE7, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("shl %s, %x", pattern.Register, pattern.Number) + code := x64.ShiftLeftNumber(nil, pattern.Register, byte(pattern.Number)) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestShiftRightSignedNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0xC1, 0xF8, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0xC1, 0xF9, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0xC1, 0xFA, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0xC1, 0xFB, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0xC1, 0xFC, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0xC1, 0xFD, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0xC1, 0xFE, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0xC1, 0xFF, 0x01}}, + {x64.R8, 1, []byte{0x49, 0xC1, 0xF8, 0x01}}, + {x64.R9, 1, []byte{0x49, 0xC1, 0xF9, 0x01}}, + {x64.R10, 1, []byte{0x49, 0xC1, 0xFA, 0x01}}, + {x64.R11, 1, []byte{0x49, 0xC1, 0xFB, 0x01}}, + {x64.R12, 1, []byte{0x49, 0xC1, 0xFC, 0x01}}, + {x64.R13, 1, []byte{0x49, 0xC1, 0xFD, 0x01}}, + {x64.R14, 1, []byte{0x49, 0xC1, 0xFE, 0x01}}, + {x64.R15, 1, []byte{0x49, 0xC1, 0xFF, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("sar %s, %x", pattern.Register, pattern.Number) + code := x64.ShiftRightSignedNumber(nil, pattern.Register, byte(pattern.Number)) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arch/x64/Sub.go b/src/arch/x64/Sub.go index fb9a649..57332fd 100644 --- a/src/arch/x64/Sub.go +++ b/src/arch/x64/Sub.go @@ -5,11 +5,11 @@ import ( ) // SubRegisterNumber subtracts a number from the given register. -func SubRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, AddressDirect, 0b101, destination, number, 0x83, 0x81) +func SubRegisterNumber(code []byte, register cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, 0b101, register, number, 0x83, 0x81) } // SubRegisterRegister subtracts a register value from another register. -func SubRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, AddressDirect, operand, destination, 8, 0x29) +func SubRegisterRegister(code []byte, register cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, operand, register, 8, 0x29) } diff --git a/src/arch/x64/Xor.go b/src/arch/x64/Xor.go index 8a469eb..c36a805 100644 --- a/src/arch/x64/Xor.go +++ b/src/arch/x64/Xor.go @@ -5,11 +5,11 @@ import ( ) // XorRegisterNumber performs a bitwise XOR using a register and a number. -func XorRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, AddressDirect, 0b110, destination, number, 0x83, 0x81) +func XorRegisterNumber(code []byte, register cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, 0b110, register, number, 0x83, 0x81) } // XorRegisterRegister performs a bitwise XOR using two registers. -func XorRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, AddressDirect, operand, destination, 8, 0x31) +func XorRegisterRegister(code []byte, register cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, operand, register, 8, 0x31) } diff --git a/src/arch/x64/Xor_test.go b/src/arch/x64/Xor_test.go new file mode 100644 index 0000000..c9e0744 --- /dev/null +++ b/src/arch/x64/Xor_test.go @@ -0,0 +1,88 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/go/assert" +) + +func TestXorRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0x83, 0xF0, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0x83, 0xF1, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0x83, 0xF2, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0x83, 0xF3, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0x83, 0xF4, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0x83, 0xF5, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0x83, 0xF6, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0x83, 0xF7, 0x01}}, + {x64.R8, 1, []byte{0x49, 0x83, 0xF0, 0x01}}, + {x64.R9, 1, []byte{0x49, 0x83, 0xF1, 0x01}}, + {x64.R10, 1, []byte{0x49, 0x83, 0xF2, 0x01}}, + {x64.R11, 1, []byte{0x49, 0x83, 0xF3, 0x01}}, + {x64.R12, 1, []byte{0x49, 0x83, 0xF4, 0x01}}, + {x64.R13, 1, []byte{0x49, 0x83, 0xF5, 0x01}}, + {x64.R14, 1, []byte{0x49, 0x83, 0xF6, 0x01}}, + {x64.R15, 1, []byte{0x49, 0x83, 0xF7, 0x01}}, + + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF7, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("xor %s, %x", pattern.Register, pattern.Number) + code := x64.XorRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestXorRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x4C, 0x31, 0xF8}}, + {x64.RCX, x64.R14, []byte{0x4C, 0x31, 0xF1}}, + {x64.RDX, x64.R13, []byte{0x4C, 0x31, 0xEA}}, + {x64.RBX, x64.R12, []byte{0x4C, 0x31, 0xE3}}, + {x64.RSP, x64.R11, []byte{0x4C, 0x31, 0xDC}}, + {x64.RBP, x64.R10, []byte{0x4C, 0x31, 0xD5}}, + {x64.RSI, x64.R9, []byte{0x4C, 0x31, 0xCE}}, + {x64.RDI, x64.R8, []byte{0x4C, 0x31, 0xC7}}, + {x64.R8, x64.RDI, []byte{0x49, 0x31, 0xF8}}, + {x64.R9, x64.RSI, []byte{0x49, 0x31, 0xF1}}, + {x64.R10, x64.RBP, []byte{0x49, 0x31, 0xEA}}, + {x64.R11, x64.RSP, []byte{0x49, 0x31, 0xE3}}, + {x64.R12, x64.RBX, []byte{0x49, 0x31, 0xDC}}, + {x64.R13, x64.RDX, []byte{0x49, 0x31, 0xD5}}, + {x64.R14, x64.RCX, []byte{0x49, 0x31, 0xCE}}, + {x64.R15, x64.RAX, []byte{0x49, 0x31, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("xor %s, %s", pattern.Left, pattern.Right) + code := x64.XorRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} From d624a5f895a918a3825dacb242288b0f4317a5f7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 7 Aug 2024 23:30:53 +0200 Subject: [PATCH 0486/1012] Added more tests --- README.md | 2 + src/arch/arm64/Registers.go | 2 +- src/arch/arm64/Registers_test.go | 12 +++++ src/arch/riscv/Registers.go | 2 +- src/arch/riscv/Registers_test.go | 12 +++++ src/arch/x64/Add.go | 8 +-- src/arch/x64/And.go | 8 +-- src/arch/x64/And_test.go | 88 ++++++++++++++++++++++++++++++++ src/arch/x64/Mul.go | 8 +-- src/arch/x64/Or.go | 8 +-- src/arch/x64/Or_test.go | 88 ++++++++++++++++++++++++++++++++ src/arch/x64/Registers_test.go | 12 +++++ src/arch/x64/Shift_test.go | 71 ++++++++++++++++++++++++++ src/arch/x64/Sub.go | 8 +-- src/arch/x64/Xor.go | 8 +-- src/arch/x64/Xor_test.go | 88 ++++++++++++++++++++++++++++++++ 16 files changed, 399 insertions(+), 26 deletions(-) create mode 100644 src/arch/arm64/Registers_test.go create mode 100644 src/arch/riscv/Registers_test.go create mode 100644 src/arch/x64/And_test.go create mode 100644 src/arch/x64/Or_test.go create mode 100644 src/arch/x64/Registers_test.go create mode 100644 src/arch/x64/Shift_test.go create mode 100644 src/arch/x64/Xor_test.go diff --git a/README.md b/README.md index 695e702..0a34d68 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,10 @@ Build a Linux x86-64 ELF executable from `examples/hello` and run it: - [x] Exclude unused functions - [x] Constant folding +- [ ] Constant propagation - [ ] Function call inlining - [ ] Loop unrolls +- [ ] Assembler optimization backend ### Linter diff --git a/src/arch/arm64/Registers.go b/src/arch/arm64/Registers.go index 3fd4f57..cf8c5db 100644 --- a/src/arch/arm64/Registers.go +++ b/src/arch/arm64/Registers.go @@ -36,4 +36,4 @@ const ( X30 ) -var SyscallArgs = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} +var SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} diff --git a/src/arch/arm64/Registers_test.go b/src/arch/arm64/Registers_test.go new file mode 100644 index 0000000..75f5b7c --- /dev/null +++ b/src/arch/arm64/Registers_test.go @@ -0,0 +1,12 @@ +package arm64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arch/arm64" + "git.akyoto.dev/go/assert" +) + +func TestRegisters(t *testing.T) { + assert.NotNil(t, arm64.SyscallInputRegisters) +} diff --git a/src/arch/riscv/Registers.go b/src/arch/riscv/Registers.go index e9436d2..5267be3 100644 --- a/src/arch/riscv/Registers.go +++ b/src/arch/riscv/Registers.go @@ -37,4 +37,4 @@ const ( X31 ) -var SyscallArgs = []cpu.Register{X10, X11, X12, X13, X14, X15, X16} +var SyscallInputRegisters = []cpu.Register{X10, X11, X12, X13, X14, X15, X16} diff --git a/src/arch/riscv/Registers_test.go b/src/arch/riscv/Registers_test.go new file mode 100644 index 0000000..bc74568 --- /dev/null +++ b/src/arch/riscv/Registers_test.go @@ -0,0 +1,12 @@ +package riscv_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arch/riscv" + "git.akyoto.dev/go/assert" +) + +func TestRegisters(t *testing.T) { + assert.NotNil(t, riscv.SyscallInputRegisters) +} diff --git a/src/arch/x64/Add.go b/src/arch/x64/Add.go index a487a3b..fb70735 100644 --- a/src/arch/x64/Add.go +++ b/src/arch/x64/Add.go @@ -5,11 +5,11 @@ import ( ) // AddRegisterNumber adds a number to the given register. -func AddRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, AddressDirect, 0, destination, number, 0x83, 0x81) +func AddRegisterNumber(code []byte, register cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, 0, register, number, 0x83, 0x81) } // AddRegisterRegister adds a register value into another register. -func AddRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, AddressDirect, operand, destination, 8, 0x01) +func AddRegisterRegister(code []byte, register cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, operand, register, 8, 0x01) } diff --git a/src/arch/x64/And.go b/src/arch/x64/And.go index a20c3c3..8556507 100644 --- a/src/arch/x64/And.go +++ b/src/arch/x64/And.go @@ -5,11 +5,11 @@ import ( ) // AndRegisterNumber performs a bitwise AND using a register and a number. -func AndRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, AddressDirect, 0b100, destination, number, 0x83, 0x81) +func AndRegisterNumber(code []byte, register cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, 0b100, register, number, 0x83, 0x81) } // AndRegisterRegister performs a bitwise AND using two registers. -func AndRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, AddressDirect, operand, destination, 8, 0x21) +func AndRegisterRegister(code []byte, register cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, operand, register, 8, 0x21) } diff --git a/src/arch/x64/And_test.go b/src/arch/x64/And_test.go new file mode 100644 index 0000000..e0e60c8 --- /dev/null +++ b/src/arch/x64/And_test.go @@ -0,0 +1,88 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/go/assert" +) + +func TestAndRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0x83, 0xE0, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0x83, 0xE1, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0x83, 0xE2, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0x83, 0xE3, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0x83, 0xE4, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0x83, 0xE5, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0x83, 0xE6, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0x83, 0xE7, 0x01}}, + {x64.R8, 1, []byte{0x49, 0x83, 0xE0, 0x01}}, + {x64.R9, 1, []byte{0x49, 0x83, 0xE1, 0x01}}, + {x64.R10, 1, []byte{0x49, 0x83, 0xE2, 0x01}}, + {x64.R11, 1, []byte{0x49, 0x83, 0xE3, 0x01}}, + {x64.R12, 1, []byte{0x49, 0x83, 0xE4, 0x01}}, + {x64.R13, 1, []byte{0x49, 0x83, 0xE5, 0x01}}, + {x64.R14, 1, []byte{0x49, 0x83, 0xE6, 0x01}}, + {x64.R15, 1, []byte{0x49, 0x83, 0xE7, 0x01}}, + + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE7, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("and %s, %x", pattern.Register, pattern.Number) + code := x64.AndRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestAndRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x4C, 0x21, 0xF8}}, + {x64.RCX, x64.R14, []byte{0x4C, 0x21, 0xF1}}, + {x64.RDX, x64.R13, []byte{0x4C, 0x21, 0xEA}}, + {x64.RBX, x64.R12, []byte{0x4C, 0x21, 0xE3}}, + {x64.RSP, x64.R11, []byte{0x4C, 0x21, 0xDC}}, + {x64.RBP, x64.R10, []byte{0x4C, 0x21, 0xD5}}, + {x64.RSI, x64.R9, []byte{0x4C, 0x21, 0xCE}}, + {x64.RDI, x64.R8, []byte{0x4C, 0x21, 0xC7}}, + {x64.R8, x64.RDI, []byte{0x49, 0x21, 0xF8}}, + {x64.R9, x64.RSI, []byte{0x49, 0x21, 0xF1}}, + {x64.R10, x64.RBP, []byte{0x49, 0x21, 0xEA}}, + {x64.R11, x64.RSP, []byte{0x49, 0x21, 0xE3}}, + {x64.R12, x64.RBX, []byte{0x49, 0x21, 0xDC}}, + {x64.R13, x64.RDX, []byte{0x49, 0x21, 0xD5}}, + {x64.R14, x64.RCX, []byte{0x49, 0x21, 0xCE}}, + {x64.R15, x64.RAX, []byte{0x49, 0x21, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("and %s, %s", pattern.Left, pattern.Right) + code := x64.AndRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arch/x64/Mul.go b/src/arch/x64/Mul.go index 6c0cbea..1d11da3 100644 --- a/src/arch/x64/Mul.go +++ b/src/arch/x64/Mul.go @@ -3,11 +3,11 @@ package x64 import "git.akyoto.dev/cli/q/src/cpu" // MulRegisterNumber multiplies a register with a number. -func MulRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, AddressDirect, destination, destination, number, 0x6B, 0x69) +func MulRegisterNumber(code []byte, register cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, register, register, number, 0x6B, 0x69) } // MulRegisterRegister multiplies a register with another register. -func MulRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, AddressDirect, destination, operand, 8, 0x0F, 0xAF) +func MulRegisterRegister(code []byte, register cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, register, operand, 8, 0x0F, 0xAF) } diff --git a/src/arch/x64/Or.go b/src/arch/x64/Or.go index fec37da..fa5b440 100644 --- a/src/arch/x64/Or.go +++ b/src/arch/x64/Or.go @@ -5,11 +5,11 @@ import ( ) // OrRegisterNumber performs a bitwise OR using a register and a number. -func OrRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, AddressDirect, 0b001, destination, number, 0x83, 0x81) +func OrRegisterNumber(code []byte, register cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, 0b001, register, number, 0x83, 0x81) } // OrRegisterRegister performs a bitwise OR using two registers. -func OrRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, AddressDirect, operand, destination, 8, 0x09) +func OrRegisterRegister(code []byte, register cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, operand, register, 8, 0x09) } diff --git a/src/arch/x64/Or_test.go b/src/arch/x64/Or_test.go new file mode 100644 index 0000000..89bdc5d --- /dev/null +++ b/src/arch/x64/Or_test.go @@ -0,0 +1,88 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/go/assert" +) + +func TestOrRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0x83, 0xC8, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0x83, 0xC9, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0x83, 0xCA, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0x83, 0xCB, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0x83, 0xCC, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0x83, 0xCD, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0x83, 0xCE, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0x83, 0xCF, 0x01}}, + {x64.R8, 1, []byte{0x49, 0x83, 0xC8, 0x01}}, + {x64.R9, 1, []byte{0x49, 0x83, 0xC9, 0x01}}, + {x64.R10, 1, []byte{0x49, 0x83, 0xCA, 0x01}}, + {x64.R11, 1, []byte{0x49, 0x83, 0xCB, 0x01}}, + {x64.R12, 1, []byte{0x49, 0x83, 0xCC, 0x01}}, + {x64.R13, 1, []byte{0x49, 0x83, 0xCD, 0x01}}, + {x64.R14, 1, []byte{0x49, 0x83, 0xCE, 0x01}}, + {x64.R15, 1, []byte{0x49, 0x83, 0xCF, 0x01}}, + + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("or %s, %x", pattern.Register, pattern.Number) + code := x64.OrRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestOrRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x4C, 0x09, 0xF8}}, + {x64.RCX, x64.R14, []byte{0x4C, 0x09, 0xF1}}, + {x64.RDX, x64.R13, []byte{0x4C, 0x09, 0xEA}}, + {x64.RBX, x64.R12, []byte{0x4C, 0x09, 0xE3}}, + {x64.RSP, x64.R11, []byte{0x4C, 0x09, 0xDC}}, + {x64.RBP, x64.R10, []byte{0x4C, 0x09, 0xD5}}, + {x64.RSI, x64.R9, []byte{0x4C, 0x09, 0xCE}}, + {x64.RDI, x64.R8, []byte{0x4C, 0x09, 0xC7}}, + {x64.R8, x64.RDI, []byte{0x49, 0x09, 0xF8}}, + {x64.R9, x64.RSI, []byte{0x49, 0x09, 0xF1}}, + {x64.R10, x64.RBP, []byte{0x49, 0x09, 0xEA}}, + {x64.R11, x64.RSP, []byte{0x49, 0x09, 0xE3}}, + {x64.R12, x64.RBX, []byte{0x49, 0x09, 0xDC}}, + {x64.R13, x64.RDX, []byte{0x49, 0x09, 0xD5}}, + {x64.R14, x64.RCX, []byte{0x49, 0x09, 0xCE}}, + {x64.R15, x64.RAX, []byte{0x49, 0x09, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("or %s, %s", pattern.Left, pattern.Right) + code := x64.OrRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arch/x64/Registers_test.go b/src/arch/x64/Registers_test.go new file mode 100644 index 0000000..bb8ff19 --- /dev/null +++ b/src/arch/x64/Registers_test.go @@ -0,0 +1,12 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/go/assert" +) + +func TestRegisters(t *testing.T) { + assert.NotContains(t, x64.GeneralRegisters, x64.RSP) +} diff --git a/src/arch/x64/Shift_test.go b/src/arch/x64/Shift_test.go new file mode 100644 index 0000000..926c98d --- /dev/null +++ b/src/arch/x64/Shift_test.go @@ -0,0 +1,71 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/go/assert" +) + +func TestShiftLeftNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0xC1, 0xE0, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0xC1, 0xE1, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0xC1, 0xE2, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0xC1, 0xE3, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0xC1, 0xE4, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0xC1, 0xE5, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0xC1, 0xE6, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0xC1, 0xE7, 0x01}}, + {x64.R8, 1, []byte{0x49, 0xC1, 0xE0, 0x01}}, + {x64.R9, 1, []byte{0x49, 0xC1, 0xE1, 0x01}}, + {x64.R10, 1, []byte{0x49, 0xC1, 0xE2, 0x01}}, + {x64.R11, 1, []byte{0x49, 0xC1, 0xE3, 0x01}}, + {x64.R12, 1, []byte{0x49, 0xC1, 0xE4, 0x01}}, + {x64.R13, 1, []byte{0x49, 0xC1, 0xE5, 0x01}}, + {x64.R14, 1, []byte{0x49, 0xC1, 0xE6, 0x01}}, + {x64.R15, 1, []byte{0x49, 0xC1, 0xE7, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("shl %s, %x", pattern.Register, pattern.Number) + code := x64.ShiftLeftNumber(nil, pattern.Register, byte(pattern.Number)) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestShiftRightSignedNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0xC1, 0xF8, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0xC1, 0xF9, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0xC1, 0xFA, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0xC1, 0xFB, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0xC1, 0xFC, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0xC1, 0xFD, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0xC1, 0xFE, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0xC1, 0xFF, 0x01}}, + {x64.R8, 1, []byte{0x49, 0xC1, 0xF8, 0x01}}, + {x64.R9, 1, []byte{0x49, 0xC1, 0xF9, 0x01}}, + {x64.R10, 1, []byte{0x49, 0xC1, 0xFA, 0x01}}, + {x64.R11, 1, []byte{0x49, 0xC1, 0xFB, 0x01}}, + {x64.R12, 1, []byte{0x49, 0xC1, 0xFC, 0x01}}, + {x64.R13, 1, []byte{0x49, 0xC1, 0xFD, 0x01}}, + {x64.R14, 1, []byte{0x49, 0xC1, 0xFE, 0x01}}, + {x64.R15, 1, []byte{0x49, 0xC1, 0xFF, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("sar %s, %x", pattern.Register, pattern.Number) + code := x64.ShiftRightSignedNumber(nil, pattern.Register, byte(pattern.Number)) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arch/x64/Sub.go b/src/arch/x64/Sub.go index fb9a649..57332fd 100644 --- a/src/arch/x64/Sub.go +++ b/src/arch/x64/Sub.go @@ -5,11 +5,11 @@ import ( ) // SubRegisterNumber subtracts a number from the given register. -func SubRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, AddressDirect, 0b101, destination, number, 0x83, 0x81) +func SubRegisterNumber(code []byte, register cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, 0b101, register, number, 0x83, 0x81) } // SubRegisterRegister subtracts a register value from another register. -func SubRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, AddressDirect, operand, destination, 8, 0x29) +func SubRegisterRegister(code []byte, register cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, operand, register, 8, 0x29) } diff --git a/src/arch/x64/Xor.go b/src/arch/x64/Xor.go index 8a469eb..c36a805 100644 --- a/src/arch/x64/Xor.go +++ b/src/arch/x64/Xor.go @@ -5,11 +5,11 @@ import ( ) // XorRegisterNumber performs a bitwise XOR using a register and a number. -func XorRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return encodeNum(code, AddressDirect, 0b110, destination, number, 0x83, 0x81) +func XorRegisterNumber(code []byte, register cpu.Register, number int) []byte { + return encodeNum(code, AddressDirect, 0b110, register, number, 0x83, 0x81) } // XorRegisterRegister performs a bitwise XOR using two registers. -func XorRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte { - return encode(code, AddressDirect, operand, destination, 8, 0x31) +func XorRegisterRegister(code []byte, register cpu.Register, operand cpu.Register) []byte { + return encode(code, AddressDirect, operand, register, 8, 0x31) } diff --git a/src/arch/x64/Xor_test.go b/src/arch/x64/Xor_test.go new file mode 100644 index 0000000..c9e0744 --- /dev/null +++ b/src/arch/x64/Xor_test.go @@ -0,0 +1,88 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/go/assert" +) + +func TestXorRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0x83, 0xF0, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0x83, 0xF1, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0x83, 0xF2, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0x83, 0xF3, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0x83, 0xF4, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0x83, 0xF5, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0x83, 0xF6, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0x83, 0xF7, 0x01}}, + {x64.R8, 1, []byte{0x49, 0x83, 0xF0, 0x01}}, + {x64.R9, 1, []byte{0x49, 0x83, 0xF1, 0x01}}, + {x64.R10, 1, []byte{0x49, 0x83, 0xF2, 0x01}}, + {x64.R11, 1, []byte{0x49, 0x83, 0xF3, 0x01}}, + {x64.R12, 1, []byte{0x49, 0x83, 0xF4, 0x01}}, + {x64.R13, 1, []byte{0x49, 0x83, 0xF5, 0x01}}, + {x64.R14, 1, []byte{0x49, 0x83, 0xF6, 0x01}}, + {x64.R15, 1, []byte{0x49, 0x83, 0xF7, 0x01}}, + + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF7, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("xor %s, %x", pattern.Register, pattern.Number) + code := x64.XorRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestXorRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x4C, 0x31, 0xF8}}, + {x64.RCX, x64.R14, []byte{0x4C, 0x31, 0xF1}}, + {x64.RDX, x64.R13, []byte{0x4C, 0x31, 0xEA}}, + {x64.RBX, x64.R12, []byte{0x4C, 0x31, 0xE3}}, + {x64.RSP, x64.R11, []byte{0x4C, 0x31, 0xDC}}, + {x64.RBP, x64.R10, []byte{0x4C, 0x31, 0xD5}}, + {x64.RSI, x64.R9, []byte{0x4C, 0x31, 0xCE}}, + {x64.RDI, x64.R8, []byte{0x4C, 0x31, 0xC7}}, + {x64.R8, x64.RDI, []byte{0x49, 0x31, 0xF8}}, + {x64.R9, x64.RSI, []byte{0x49, 0x31, 0xF1}}, + {x64.R10, x64.RBP, []byte{0x49, 0x31, 0xEA}}, + {x64.R11, x64.RSP, []byte{0x49, 0x31, 0xE3}}, + {x64.R12, x64.RBX, []byte{0x49, 0x31, 0xDC}}, + {x64.R13, x64.RDX, []byte{0x49, 0x31, 0xD5}}, + {x64.R14, x64.RCX, []byte{0x49, 0x31, 0xCE}}, + {x64.R15, x64.RAX, []byte{0x49, 0x31, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("xor %s, %s", pattern.Left, pattern.Right) + code := x64.XorRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} From 69a5fdc70368e829c02b8874caa5cdfd2a68badf Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 8 Aug 2024 12:55:25 +0200 Subject: [PATCH 0487/1012] Improved type system --- lib/sys/linux.q | 2 +- src/core/CompileCall.go | 19 ++++++------------- src/core/CompileDefinition.go | 3 +-- src/core/CompileReturn.go | 6 +++--- src/core/Evaluate.go | 2 +- src/core/ExpressionToMemory.go | 6 +++--- src/core/ExpressionToRegister.go | 10 +++++----- src/core/Function.go | 2 +- src/core/TokenToRegister.go | 8 ++++---- src/scanner/scanFile.go | 4 ++-- src/scope/Variable.go | 2 +- src/types/Base.go | 16 ++++++++++++++++ src/types/Check.go | 6 ++++++ src/types/Field.go | 11 +++++++++++ src/types/New.go | 6 ------ src/types/NewList.go | 16 ---------------- src/types/Parse.go | 27 +++++++++++++++++++++++++++ src/types/ParseList.go | 16 ++++++++++++++++ src/types/Type.go | 14 ++++++-------- tests/errors_test.go | 2 +- 20 files changed, 111 insertions(+), 67 deletions(-) create mode 100644 src/types/Base.go create mode 100644 src/types/Check.go create mode 100644 src/types/Field.go delete mode 100644 src/types/New.go delete mode 100644 src/types/NewList.go create mode 100644 src/types/Parse.go create mode 100644 src/types/ParseList.go diff --git a/lib/sys/linux.q b/lib/sys/linux.q index e70ffee..c28f77d 100644 --- a/lib/sys/linux.q +++ b/lib/sys/linux.q @@ -22,7 +22,7 @@ munmap(address Pointer, length Int) -> Int { return syscall(11, address, length) } -clone(flags Int, stack Pointer) -> ThreadID { +clone(flags Int, stack Pointer) -> Int { return syscall(56, flags, stack) } diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 5036f1f..14ae685 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -1,11 +1,10 @@ package core import ( - "strings" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" ) // CompileCall executes a function call. @@ -18,7 +17,6 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { nameNode = root.Children[0] fn *Function name string - fullName string exists bool ) @@ -47,12 +45,7 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { imp.Used = true } - tmp := strings.Builder{} - tmp.WriteString(pkg) - tmp.WriteString(".") - tmp.WriteString(name) - fullName = tmp.String() - fn, exists = f.Functions[fullName] + fn, exists = f.Functions[pkg+"."+name] if !exists { return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) @@ -68,10 +61,10 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { return nil, err } - if typ != fn.Parameters[i].Type { + if !types.Check(typ, fn.Parameters[i].Type) { return nil, errors.New(&errors.TypeMismatch{ - Encountered: string(typ), - Expected: string(fn.Parameters[i].Type), + Encountered: typ.Name, + Expected: fn.Parameters[i].Type.Name, ParameterName: fn.Parameters[i].Name, }, f.File, parameters[i].Token.Position) } @@ -87,7 +80,7 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { } } - f.Call(fullName) + f.Call(fn.UniqueName) for _, register := range registers { if register == f.CPU.Output[0] && root.Parent != nil { diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index 90993d9..5782978 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -5,7 +5,6 @@ import ( "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/types" ) // CompileDefinition compiles a variable definition. @@ -28,7 +27,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { variable.Type = typ - if variable.Type == types.Invalid { + if variable.Type == nil { return errors.New(errors.UnknownType, f.File, node.Expression.Token.End()) } diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 754b4b8..ab901f2 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -21,10 +21,10 @@ func (f *Function) CompileReturn(node *ast.Return) error { return err } - if typ != types.Any && typ != f.ReturnTypes[i] { + if !types.Check(typ, f.ReturnTypes[i]) { return errors.New(&errors.TypeMismatch{ - Encountered: string(typ), - Expected: string(f.ReturnTypes[i]), + Encountered: typ.Name, + Expected: f.ReturnTypes[i].Name, ParameterName: "", IsReturn: true, }, f.File, node.Values[i].Token.Position) diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index ed23b7d..235f917 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -8,7 +8,7 @@ import ( ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. -func (f *Function) Evaluate(expr *expression.Expression) (types.Type, cpu.Register, error) { +func (f *Function) Evaluate(expr *expression.Expression) (*types.Type, cpu.Register, error) { if expr.Token.Kind == token.Identifier { name := expr.Token.Text(f.File.Bytes) variable := f.VariableByName(name) diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index f39e734..9077a1e 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -9,18 +9,18 @@ import ( ) // ExpressionToMemory puts the result of an expression into the specified memory address. -func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (types.Type, error) { +func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (*types.Type, error) { if node.IsLeaf() && node.Token.IsNumeric() { number, err := f.Number(node.Token) if err != nil { - return types.Invalid, err + return nil, err } size := byte(sizeof.Signed(int64(number))) if size != memory.Length { - return types.Invalid, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) + return nil, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) } f.MemoryNumber(asm.STORE, memory, number) diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 8b4fa20..7c5b31f 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -11,7 +11,7 @@ import ( ) // ExpressionToRegister puts the result of an expression into the specified register. -func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { +func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (*types.Type, error) { f.SaveRegister(register) if node.IsFolded { @@ -31,7 +31,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } if fn == nil || len(fn.ReturnTypes) == 0 { - return types.Any, err + return nil, err } return fn.ReturnTypes[0], err @@ -46,7 +46,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if len(node.Children) == 1 { if !node.Token.IsUnaryOperator() { - return types.Invalid, errors.New(errors.MissingOperand, f.File, node.Token.End()) + return nil, errors.New(errors.MissingOperand, f.File, node.Token.End()) } typ, err := f.ExpressionToRegister(node.Children[0], register) @@ -69,10 +69,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp typ, err := f.ExpressionToRegister(left, register) if err != nil { - return types.Invalid, err + return nil, err } - if typ == types.Pointer && (node.Token.Kind == token.Add || node.Token.Kind == token.Sub) && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.Pointer { + if typ == types.Pointer && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.Pointer { typ = types.Int } diff --git a/src/core/Function.go b/src/core/Function.go index 4ff2c4d..121ae8c 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -17,7 +17,7 @@ type Function struct { File *fs.File Body token.List Parameters []*scope.Variable - ReturnTypes []types.Type + ReturnTypes []*types.Type Functions map[string]*Function Err error deferred []func() diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index d33c1a7..117d8db 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -10,14 +10,14 @@ import ( // TokenToRegister moves a token into a register. // It only works with identifiers, numbers and strings. -func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types.Type, error) { +func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (*types.Type, error) { switch t.Kind { case token.Identifier: name := t.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { - return types.Invalid, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } f.UseVariable(variable) @@ -29,7 +29,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. number, err := f.Number(t) if err != nil { - return types.Invalid, err + return nil, err } f.SaveRegister(register) @@ -45,6 +45,6 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. return types.Pointer, nil default: - return types.Invalid, errors.New(errors.InvalidExpression, f.File, t.Position) + return nil, errors.New(errors.InvalidExpression, f.File, t.Position) } } diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 46f25ff..1b9c795 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -233,7 +233,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { typeEnd-- } - function.ReturnTypes = types.NewList(tokens[typeStart:typeEnd], contents) + function.ReturnTypes = types.ParseList(tokens[typeStart:typeEnd], contents) } parameters := tokens[paramsStart:paramsEnd] @@ -245,7 +245,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { } name := tokens[0].Text(contents) - dataType := types.New(tokens[1:].Text(contents)) + dataType := types.Parse(tokens[1:].Text(contents)) register := x64.InputRegisters[count] uses := token.Count(function.Body, contents, token.Identifier, name) diff --git a/src/scope/Variable.go b/src/scope/Variable.go index 7c6081e..d64ddca 100644 --- a/src/scope/Variable.go +++ b/src/scope/Variable.go @@ -7,8 +7,8 @@ import ( // Variable represents a named register. type Variable struct { + Type *types.Type Name string - Type types.Type Alive uint8 Register cpu.Register } diff --git a/src/types/Base.go b/src/types/Base.go new file mode 100644 index 0000000..f28b5dd --- /dev/null +++ b/src/types/Base.go @@ -0,0 +1,16 @@ +package types + +var ( + Float64 = &Type{Name: "Float64", Size: 8} + Float32 = &Type{Name: "Float32", Size: 4} + Int64 = &Type{Name: "Int64", Size: 8} + Int32 = &Type{Name: "Int32", Size: 4} + Int16 = &Type{Name: "Int16", Size: 2} + Int8 = &Type{Name: "Int8", Size: 1} + Pointer = &Type{Name: "Pointer", Size: 8} +) + +var ( + Float = Float64 + Int = Int64 +) diff --git a/src/types/Check.go b/src/types/Check.go new file mode 100644 index 0000000..dbf9808 --- /dev/null +++ b/src/types/Check.go @@ -0,0 +1,6 @@ +package types + +// Check returns true if the first type can be converted into the second type. +func Check(a *Type, b *Type) bool { + return a == nil || a == b +} diff --git a/src/types/Field.go b/src/types/Field.go new file mode 100644 index 0000000..aa0d554 --- /dev/null +++ b/src/types/Field.go @@ -0,0 +1,11 @@ +package types + +import "git.akyoto.dev/cli/q/src/token" + +// Field is a field in a data structure. +type Field struct { + Type *Type + Name string + Position token.Position + Offset uint8 +} diff --git a/src/types/New.go b/src/types/New.go deleted file mode 100644 index ae20817..0000000 --- a/src/types/New.go +++ /dev/null @@ -1,6 +0,0 @@ -package types - -// New creates a new type from a list of tokens. -func New(name string) Type { - return Type(name) -} diff --git a/src/types/NewList.go b/src/types/NewList.go deleted file mode 100644 index c315c8d..0000000 --- a/src/types/NewList.go +++ /dev/null @@ -1,16 +0,0 @@ -package types - -import "git.akyoto.dev/cli/q/src/token" - -// NewList generates a list of types from comma separated tokens. -func NewList(tokens token.List, source []byte) []Type { - var list []Type - - tokens.Split(func(parameter token.List) error { - typ := New(parameter.Text(source)) - list = append(list, typ) - return nil - }) - - return list -} diff --git a/src/types/Parse.go b/src/types/Parse.go new file mode 100644 index 0000000..0cfc305 --- /dev/null +++ b/src/types/Parse.go @@ -0,0 +1,27 @@ +package types + +// Parse creates a new type from a list of tokens. +func Parse(name string) *Type { + switch name { + case "Int": + return Int + case "Int64": + return Int64 + case "Int32": + return Int32 + case "Int16": + return Int16 + case "Int8": + return Int8 + case "Float": + return Float + case "Float64": + return Float64 + case "Float32": + return Float32 + case "Pointer": + return Pointer + default: + panic("Unknown type " + name) + } +} diff --git a/src/types/ParseList.go b/src/types/ParseList.go new file mode 100644 index 0000000..56a8592 --- /dev/null +++ b/src/types/ParseList.go @@ -0,0 +1,16 @@ +package types + +import "git.akyoto.dev/cli/q/src/token" + +// ParseList generates a list of types from comma separated tokens. +func ParseList(tokens token.List, source []byte) []*Type { + var list []*Type + + tokens.Split(func(parameter token.List) error { + typ := Parse(parameter.Text(source)) + list = append(list, typ) + return nil + }) + + return list +} diff --git a/src/types/Type.go b/src/types/Type.go index 76c04a9..d9ce347 100644 --- a/src/types/Type.go +++ b/src/types/Type.go @@ -1,10 +1,8 @@ package types -type Type string - -const ( - Invalid = "" - Any = "Any" - Int = "Int" - Pointer = "Pointer" -) +// Type represents a type in the type system. +type Type struct { + Name string + Fields []*Field + Size uint8 +} diff --git a/tests/errors_test.go b/tests/errors_test.go index 484cf94..71dbdf9 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -36,7 +36,7 @@ var errs = []struct { {"MissingOperand.q", errors.MissingOperand}, {"MissingOperand2.q", errors.MissingOperand}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, - {"TypeMismatch.q", &errors.TypeMismatch{Expected: "Pointer", Encountered: "Int", ParameterName: "p"}}, + {"TypeMismatch.q", &errors.TypeMismatch{Expected: "Pointer", Encountered: "Int64", ParameterName: "p"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, From 5d9be01a855a97ce30b74c512585d8078b03f504 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 8 Aug 2024 12:55:25 +0200 Subject: [PATCH 0488/1012] Improved type system --- lib/sys/linux.q | 2 +- src/core/CompileCall.go | 19 ++++++------------- src/core/CompileDefinition.go | 3 +-- src/core/CompileReturn.go | 6 +++--- src/core/Evaluate.go | 2 +- src/core/ExpressionToMemory.go | 6 +++--- src/core/ExpressionToRegister.go | 10 +++++----- src/core/Function.go | 2 +- src/core/TokenToRegister.go | 8 ++++---- src/scanner/scanFile.go | 4 ++-- src/scope/Variable.go | 2 +- src/types/Base.go | 16 ++++++++++++++++ src/types/Check.go | 6 ++++++ src/types/Field.go | 11 +++++++++++ src/types/New.go | 6 ------ src/types/NewList.go | 16 ---------------- src/types/Parse.go | 27 +++++++++++++++++++++++++++ src/types/ParseList.go | 16 ++++++++++++++++ src/types/Type.go | 14 ++++++-------- tests/errors_test.go | 2 +- 20 files changed, 111 insertions(+), 67 deletions(-) create mode 100644 src/types/Base.go create mode 100644 src/types/Check.go create mode 100644 src/types/Field.go delete mode 100644 src/types/New.go delete mode 100644 src/types/NewList.go create mode 100644 src/types/Parse.go create mode 100644 src/types/ParseList.go diff --git a/lib/sys/linux.q b/lib/sys/linux.q index e70ffee..c28f77d 100644 --- a/lib/sys/linux.q +++ b/lib/sys/linux.q @@ -22,7 +22,7 @@ munmap(address Pointer, length Int) -> Int { return syscall(11, address, length) } -clone(flags Int, stack Pointer) -> ThreadID { +clone(flags Int, stack Pointer) -> Int { return syscall(56, flags, stack) } diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 5036f1f..14ae685 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -1,11 +1,10 @@ package core import ( - "strings" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" ) // CompileCall executes a function call. @@ -18,7 +17,6 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { nameNode = root.Children[0] fn *Function name string - fullName string exists bool ) @@ -47,12 +45,7 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { imp.Used = true } - tmp := strings.Builder{} - tmp.WriteString(pkg) - tmp.WriteString(".") - tmp.WriteString(name) - fullName = tmp.String() - fn, exists = f.Functions[fullName] + fn, exists = f.Functions[pkg+"."+name] if !exists { return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) @@ -68,10 +61,10 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { return nil, err } - if typ != fn.Parameters[i].Type { + if !types.Check(typ, fn.Parameters[i].Type) { return nil, errors.New(&errors.TypeMismatch{ - Encountered: string(typ), - Expected: string(fn.Parameters[i].Type), + Encountered: typ.Name, + Expected: fn.Parameters[i].Type.Name, ParameterName: fn.Parameters[i].Name, }, f.File, parameters[i].Token.Position) } @@ -87,7 +80,7 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { } } - f.Call(fullName) + f.Call(fn.UniqueName) for _, register := range registers { if register == f.CPU.Output[0] && root.Parent != nil { diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index 90993d9..5782978 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -5,7 +5,6 @@ import ( "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/types" ) // CompileDefinition compiles a variable definition. @@ -28,7 +27,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { variable.Type = typ - if variable.Type == types.Invalid { + if variable.Type == nil { return errors.New(errors.UnknownType, f.File, node.Expression.Token.End()) } diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 754b4b8..ab901f2 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -21,10 +21,10 @@ func (f *Function) CompileReturn(node *ast.Return) error { return err } - if typ != types.Any && typ != f.ReturnTypes[i] { + if !types.Check(typ, f.ReturnTypes[i]) { return errors.New(&errors.TypeMismatch{ - Encountered: string(typ), - Expected: string(f.ReturnTypes[i]), + Encountered: typ.Name, + Expected: f.ReturnTypes[i].Name, ParameterName: "", IsReturn: true, }, f.File, node.Values[i].Token.Position) diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index ed23b7d..235f917 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -8,7 +8,7 @@ import ( ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. -func (f *Function) Evaluate(expr *expression.Expression) (types.Type, cpu.Register, error) { +func (f *Function) Evaluate(expr *expression.Expression) (*types.Type, cpu.Register, error) { if expr.Token.Kind == token.Identifier { name := expr.Token.Text(f.File.Bytes) variable := f.VariableByName(name) diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index f39e734..9077a1e 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -9,18 +9,18 @@ import ( ) // ExpressionToMemory puts the result of an expression into the specified memory address. -func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (types.Type, error) { +func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (*types.Type, error) { if node.IsLeaf() && node.Token.IsNumeric() { number, err := f.Number(node.Token) if err != nil { - return types.Invalid, err + return nil, err } size := byte(sizeof.Signed(int64(number))) if size != memory.Length { - return types.Invalid, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) + return nil, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) } f.MemoryNumber(asm.STORE, memory, number) diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 8b4fa20..7c5b31f 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -11,7 +11,7 @@ import ( ) // ExpressionToRegister puts the result of an expression into the specified register. -func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { +func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (*types.Type, error) { f.SaveRegister(register) if node.IsFolded { @@ -31,7 +31,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } if fn == nil || len(fn.ReturnTypes) == 0 { - return types.Any, err + return nil, err } return fn.ReturnTypes[0], err @@ -46,7 +46,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if len(node.Children) == 1 { if !node.Token.IsUnaryOperator() { - return types.Invalid, errors.New(errors.MissingOperand, f.File, node.Token.End()) + return nil, errors.New(errors.MissingOperand, f.File, node.Token.End()) } typ, err := f.ExpressionToRegister(node.Children[0], register) @@ -69,10 +69,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp typ, err := f.ExpressionToRegister(left, register) if err != nil { - return types.Invalid, err + return nil, err } - if typ == types.Pointer && (node.Token.Kind == token.Add || node.Token.Kind == token.Sub) && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.Pointer { + if typ == types.Pointer && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.Pointer { typ = types.Int } diff --git a/src/core/Function.go b/src/core/Function.go index 4ff2c4d..121ae8c 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -17,7 +17,7 @@ type Function struct { File *fs.File Body token.List Parameters []*scope.Variable - ReturnTypes []types.Type + ReturnTypes []*types.Type Functions map[string]*Function Err error deferred []func() diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index d33c1a7..117d8db 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -10,14 +10,14 @@ import ( // TokenToRegister moves a token into a register. // It only works with identifiers, numbers and strings. -func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types.Type, error) { +func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (*types.Type, error) { switch t.Kind { case token.Identifier: name := t.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { - return types.Invalid, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } f.UseVariable(variable) @@ -29,7 +29,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. number, err := f.Number(t) if err != nil { - return types.Invalid, err + return nil, err } f.SaveRegister(register) @@ -45,6 +45,6 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. return types.Pointer, nil default: - return types.Invalid, errors.New(errors.InvalidExpression, f.File, t.Position) + return nil, errors.New(errors.InvalidExpression, f.File, t.Position) } } diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 46f25ff..1b9c795 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -233,7 +233,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { typeEnd-- } - function.ReturnTypes = types.NewList(tokens[typeStart:typeEnd], contents) + function.ReturnTypes = types.ParseList(tokens[typeStart:typeEnd], contents) } parameters := tokens[paramsStart:paramsEnd] @@ -245,7 +245,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { } name := tokens[0].Text(contents) - dataType := types.New(tokens[1:].Text(contents)) + dataType := types.Parse(tokens[1:].Text(contents)) register := x64.InputRegisters[count] uses := token.Count(function.Body, contents, token.Identifier, name) diff --git a/src/scope/Variable.go b/src/scope/Variable.go index 7c6081e..d64ddca 100644 --- a/src/scope/Variable.go +++ b/src/scope/Variable.go @@ -7,8 +7,8 @@ import ( // Variable represents a named register. type Variable struct { + Type *types.Type Name string - Type types.Type Alive uint8 Register cpu.Register } diff --git a/src/types/Base.go b/src/types/Base.go new file mode 100644 index 0000000..f28b5dd --- /dev/null +++ b/src/types/Base.go @@ -0,0 +1,16 @@ +package types + +var ( + Float64 = &Type{Name: "Float64", Size: 8} + Float32 = &Type{Name: "Float32", Size: 4} + Int64 = &Type{Name: "Int64", Size: 8} + Int32 = &Type{Name: "Int32", Size: 4} + Int16 = &Type{Name: "Int16", Size: 2} + Int8 = &Type{Name: "Int8", Size: 1} + Pointer = &Type{Name: "Pointer", Size: 8} +) + +var ( + Float = Float64 + Int = Int64 +) diff --git a/src/types/Check.go b/src/types/Check.go new file mode 100644 index 0000000..dbf9808 --- /dev/null +++ b/src/types/Check.go @@ -0,0 +1,6 @@ +package types + +// Check returns true if the first type can be converted into the second type. +func Check(a *Type, b *Type) bool { + return a == nil || a == b +} diff --git a/src/types/Field.go b/src/types/Field.go new file mode 100644 index 0000000..aa0d554 --- /dev/null +++ b/src/types/Field.go @@ -0,0 +1,11 @@ +package types + +import "git.akyoto.dev/cli/q/src/token" + +// Field is a field in a data structure. +type Field struct { + Type *Type + Name string + Position token.Position + Offset uint8 +} diff --git a/src/types/New.go b/src/types/New.go deleted file mode 100644 index ae20817..0000000 --- a/src/types/New.go +++ /dev/null @@ -1,6 +0,0 @@ -package types - -// New creates a new type from a list of tokens. -func New(name string) Type { - return Type(name) -} diff --git a/src/types/NewList.go b/src/types/NewList.go deleted file mode 100644 index c315c8d..0000000 --- a/src/types/NewList.go +++ /dev/null @@ -1,16 +0,0 @@ -package types - -import "git.akyoto.dev/cli/q/src/token" - -// NewList generates a list of types from comma separated tokens. -func NewList(tokens token.List, source []byte) []Type { - var list []Type - - tokens.Split(func(parameter token.List) error { - typ := New(parameter.Text(source)) - list = append(list, typ) - return nil - }) - - return list -} diff --git a/src/types/Parse.go b/src/types/Parse.go new file mode 100644 index 0000000..0cfc305 --- /dev/null +++ b/src/types/Parse.go @@ -0,0 +1,27 @@ +package types + +// Parse creates a new type from a list of tokens. +func Parse(name string) *Type { + switch name { + case "Int": + return Int + case "Int64": + return Int64 + case "Int32": + return Int32 + case "Int16": + return Int16 + case "Int8": + return Int8 + case "Float": + return Float + case "Float64": + return Float64 + case "Float32": + return Float32 + case "Pointer": + return Pointer + default: + panic("Unknown type " + name) + } +} diff --git a/src/types/ParseList.go b/src/types/ParseList.go new file mode 100644 index 0000000..56a8592 --- /dev/null +++ b/src/types/ParseList.go @@ -0,0 +1,16 @@ +package types + +import "git.akyoto.dev/cli/q/src/token" + +// ParseList generates a list of types from comma separated tokens. +func ParseList(tokens token.List, source []byte) []*Type { + var list []*Type + + tokens.Split(func(parameter token.List) error { + typ := Parse(parameter.Text(source)) + list = append(list, typ) + return nil + }) + + return list +} diff --git a/src/types/Type.go b/src/types/Type.go index 76c04a9..d9ce347 100644 --- a/src/types/Type.go +++ b/src/types/Type.go @@ -1,10 +1,8 @@ package types -type Type string - -const ( - Invalid = "" - Any = "Any" - Int = "Int" - Pointer = "Pointer" -) +// Type represents a type in the type system. +type Type struct { + Name string + Fields []*Field + Size uint8 +} diff --git a/tests/errors_test.go b/tests/errors_test.go index 484cf94..71dbdf9 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -36,7 +36,7 @@ var errs = []struct { {"MissingOperand.q", errors.MissingOperand}, {"MissingOperand2.q", errors.MissingOperand}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, - {"TypeMismatch.q", &errors.TypeMismatch{Expected: "Pointer", Encountered: "Int", ParameterName: "p"}}, + {"TypeMismatch.q", &errors.TypeMismatch{Expected: "Pointer", Encountered: "Int64", ParameterName: "p"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, From e3a9ece68cd50270d72fc34a00bb2263cf08abba Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 9 Aug 2024 12:48:13 +0200 Subject: [PATCH 0489/1012] Updated dependencies --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d8c9380..b143677 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,4 @@ require ( git.akyoto.dev/go/color v0.1.1 ) -require golang.org/x/sys v0.23.0 // indirect +require golang.org/x/sys v0.24.0 // indirect diff --git a/go.sum b/go.sum index bf24815..929bcce 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,5 @@ git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= git.akyoto.dev/go/color v0.1.1 h1:mMAoMIwLBPNy7ocRSxdsCFs7onPC3GfDEiJErCneqRE= git.akyoto.dev/go/color v0.1.1/go.mod h1:ywOjoD0O0sk6bIn92uAJf7mErlEFCuQInL84y4Lqi3Q= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= From 53318589d6ab4b046e0fc5bff856a3c172ea659a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 9 Aug 2024 12:48:13 +0200 Subject: [PATCH 0490/1012] Updated dependencies --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d8c9380..b143677 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,4 @@ require ( git.akyoto.dev/go/color v0.1.1 ) -require golang.org/x/sys v0.23.0 // indirect +require golang.org/x/sys v0.24.0 // indirect diff --git a/go.sum b/go.sum index bf24815..929bcce 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,5 @@ git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= git.akyoto.dev/go/color v0.1.1 h1:mMAoMIwLBPNy7ocRSxdsCFs7onPC3GfDEiJErCneqRE= git.akyoto.dev/go/color v0.1.1/go.mod h1:ywOjoD0O0sk6bIn92uAJf7mErlEFCuQInL84y4Lqi3Q= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= From 7c81c3451f32bbcf8306b6cf7089a97f69a2880c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 11 Aug 2024 14:00:51 +0200 Subject: [PATCH 0491/1012] Added more tests --- tests/errors/ExpectedFunctionName2.q | 1 + tests/errors/MissingBlockEnd2.q | 3 +++ tests/errors_test.go | 2 ++ 3 files changed, 6 insertions(+) create mode 100644 tests/errors/ExpectedFunctionName2.q create mode 100644 tests/errors/MissingBlockEnd2.q diff --git a/tests/errors/ExpectedFunctionName2.q b/tests/errors/ExpectedFunctionName2.q new file mode 100644 index 0000000..5638d8b --- /dev/null +++ b/tests/errors/ExpectedFunctionName2.q @@ -0,0 +1 @@ +"Hello" \ No newline at end of file diff --git a/tests/errors/MissingBlockEnd2.q b/tests/errors/MissingBlockEnd2.q new file mode 100644 index 0000000..5562918 --- /dev/null +++ b/tests/errors/MissingBlockEnd2.q @@ -0,0 +1,3 @@ +main() { + loop { +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 71dbdf9..7ecdddc 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -17,6 +17,7 @@ var errs = []struct { {"EmptySwitch.q", errors.EmptySwitch}, {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, + {"ExpectedFunctionName2.q", errors.ExpectedFunctionName}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse}, {"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse}, @@ -29,6 +30,7 @@ var errs = []struct { {"InvalidCharacter3.q", &errors.InvalidCharacter{Character: "@"}}, {"InvalidCharacter4.q", &errors.InvalidCharacter{Character: "+++"}}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, + {"MissingBlockEnd2.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, {"MissingGroupStart.q", errors.MissingGroupStart}, From cc215a27c7e6b50d744d8af448c1a9ae2966dfee Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 11 Aug 2024 14:00:51 +0200 Subject: [PATCH 0492/1012] Added more tests --- tests/errors/ExpectedFunctionName2.q | 1 + tests/errors/MissingBlockEnd2.q | 3 +++ tests/errors_test.go | 2 ++ 3 files changed, 6 insertions(+) create mode 100644 tests/errors/ExpectedFunctionName2.q create mode 100644 tests/errors/MissingBlockEnd2.q diff --git a/tests/errors/ExpectedFunctionName2.q b/tests/errors/ExpectedFunctionName2.q new file mode 100644 index 0000000..5638d8b --- /dev/null +++ b/tests/errors/ExpectedFunctionName2.q @@ -0,0 +1 @@ +"Hello" \ No newline at end of file diff --git a/tests/errors/MissingBlockEnd2.q b/tests/errors/MissingBlockEnd2.q new file mode 100644 index 0000000..5562918 --- /dev/null +++ b/tests/errors/MissingBlockEnd2.q @@ -0,0 +1,3 @@ +main() { + loop { +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 71dbdf9..7ecdddc 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -17,6 +17,7 @@ var errs = []struct { {"EmptySwitch.q", errors.EmptySwitch}, {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, + {"ExpectedFunctionName2.q", errors.ExpectedFunctionName}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse}, {"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse}, @@ -29,6 +30,7 @@ var errs = []struct { {"InvalidCharacter3.q", &errors.InvalidCharacter{Character: "@"}}, {"InvalidCharacter4.q", &errors.InvalidCharacter{Character: "+++"}}, {"MissingBlockEnd.q", errors.MissingBlockEnd}, + {"MissingBlockEnd2.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, {"MissingGroupStart.q", errors.MissingGroupStart}, From af259b364c8e9d4af0b262771c1afd7f5c5a4905 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 11 Aug 2024 21:15:47 +0200 Subject: [PATCH 0493/1012] Added Mach-O file format --- src/compiler/Result.go | 29 +++++++++-- src/macho/CPU.go | 10 ++++ src/macho/Header.go | 13 +++++ src/macho/HeaderFlags.go | 32 ++++++++++++ src/macho/HeaderType.go | 12 +++++ src/macho/LoadCommand.go | 34 +++++++++++++ src/macho/MachO.go | 107 +++++++++++++++++++++++++++++++++++++++ src/macho/Prot.go | 9 ++++ src/macho/Segment64.go | 16 ++++++ src/macho/Thread.go | 8 +++ 10 files changed, 266 insertions(+), 4 deletions(-) create mode 100644 src/macho/CPU.go create mode 100644 src/macho/Header.go create mode 100644 src/macho/HeaderFlags.go create mode 100644 src/macho/HeaderType.go create mode 100644 src/macho/LoadCommand.go create mode 100644 src/macho/MachO.go create mode 100644 src/macho/Prot.go create mode 100644 src/macho/Segment64.go create mode 100644 src/macho/Thread.go diff --git a/src/compiler/Result.go b/src/compiler/Result.go index ca51ba1..8551388 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -4,11 +4,13 @@ import ( "bufio" "io" "os" + "runtime" "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/macho" "git.akyoto.dev/cli/q/src/os/linux" ) @@ -31,13 +33,22 @@ func (r *Result) finalize() ([]byte, []byte) { Data: make(map[string][]byte, r.DataCount), } + sysExit := 0 + + switch runtime.GOOS { + case "linux": + sysExit = linux.Exit + case "darwin": + sysExit = 1 | 0x2000000 + } + final.Call("main.main") - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() final.Label(asm.LABEL, "_crash") - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() @@ -117,7 +128,17 @@ func (r *Result) WriteFile(path string) error { // write writes an executable file to the given writer. func write(writer io.Writer, code []byte, data []byte) error { buffer := bufio.NewWriter(writer) - executable := elf.New(code, data) - executable.Write(buffer) + + switch runtime.GOOS { + case "darwin": + exe := macho.New(code, data) + exe.Write(buffer) + case "linux": + exe := elf.New(code, data) + exe.Write(buffer) + default: + panic("unsupported platform") + } + return buffer.Flush() } diff --git a/src/macho/CPU.go b/src/macho/CPU.go new file mode 100644 index 0000000..3888aaa --- /dev/null +++ b/src/macho/CPU.go @@ -0,0 +1,10 @@ +package macho + +type CPU uint32 + +const ( + CPU_X86 CPU = 7 + CPU_X86_64 CPU = CPU_X86 | 0x01000000 + CPU_ARM CPU = 12 + CPU_ARM_64 CPU = CPU_ARM | 0x01000000 +) diff --git a/src/macho/Header.go b/src/macho/Header.go new file mode 100644 index 0000000..f7798be --- /dev/null +++ b/src/macho/Header.go @@ -0,0 +1,13 @@ +package macho + +// Header contains general information. +type Header struct { + Magic uint32 + Architecture CPU + MicroArchitecture uint32 + Type HeaderType + NumCommands uint32 + SizeCommands uint32 + Flags HeaderFlags + Reserved uint32 +} diff --git a/src/macho/HeaderFlags.go b/src/macho/HeaderFlags.go new file mode 100644 index 0000000..4b17f50 --- /dev/null +++ b/src/macho/HeaderFlags.go @@ -0,0 +1,32 @@ +package macho + +type HeaderFlags uint32 + +const ( + FlagNoUndefs HeaderFlags = 0x1 + FlagIncrLink HeaderFlags = 0x2 + FlagDyldLink HeaderFlags = 0x4 + FlagBindAtLoad HeaderFlags = 0x8 + FlagPrebound HeaderFlags = 0x10 + FlagSplitSegs HeaderFlags = 0x20 + FlagLazyInit HeaderFlags = 0x40 + FlagTwoLevel HeaderFlags = 0x80 + FlagForceFlat HeaderFlags = 0x100 + FlagNoMultiDefs HeaderFlags = 0x200 + FlagNoFixPrebinding HeaderFlags = 0x400 + FlagPrebindable HeaderFlags = 0x800 + FlagAllModsBound HeaderFlags = 0x1000 + FlagSubsectionsViaSymbols HeaderFlags = 0x2000 + FlagCanonical HeaderFlags = 0x4000 + FlagWeakDefines HeaderFlags = 0x8000 + FlagBindsToWeak HeaderFlags = 0x10000 + FlagAllowStackExecution HeaderFlags = 0x20000 + FlagRootSafe HeaderFlags = 0x40000 + FlagSetuidSafe HeaderFlags = 0x80000 + FlagNoReexportedDylibs HeaderFlags = 0x100000 + FlagPIE HeaderFlags = 0x200000 + FlagDeadStrippableDylib HeaderFlags = 0x400000 + FlagHasTLVDescriptors HeaderFlags = 0x800000 + FlagNoHeapExecution HeaderFlags = 0x1000000 + FlagAppExtensionSafe HeaderFlags = 0x2000000 +) diff --git a/src/macho/HeaderType.go b/src/macho/HeaderType.go new file mode 100644 index 0000000..f106b97 --- /dev/null +++ b/src/macho/HeaderType.go @@ -0,0 +1,12 @@ +package macho + +type HeaderType uint32 + +const ( + TypeObject HeaderType = 0x1 + TypeExecute HeaderType = 0x2 + TypeCore HeaderType = 0x4 + TypeDylib HeaderType = 0x6 + TypeBundle HeaderType = 0x8 + TypeDsym HeaderType = 0xA +) diff --git a/src/macho/LoadCommand.go b/src/macho/LoadCommand.go new file mode 100644 index 0000000..0d4b04f --- /dev/null +++ b/src/macho/LoadCommand.go @@ -0,0 +1,34 @@ +package macho + +type LoadCommand uint32 + +const ( + LcSegment LoadCommand = 0x1 + LcSymtab LoadCommand = 0x2 + LcThread LoadCommand = 0x4 + LcUnixthread LoadCommand = 0x5 + LcDysymtab LoadCommand = 0xB + LcDylib LoadCommand = 0xC + LcIdDylib LoadCommand = 0xD + LcLoadDylinker LoadCommand = 0xE + LcIdDylinker LoadCommand = 0xF + LcSegment64 LoadCommand = 0x19 + LcUuid LoadCommand = 0x1B + LcCodeSignature LoadCommand = 0x1D + LcSegmentSplitInfo LoadCommand = 0x1E + LcRpath LoadCommand = 0x8000001C + LcEncryptionInfo LoadCommand = 0x21 + LcDyldInfo LoadCommand = 0x22 + LcDyldInfoOnly LoadCommand = 0x80000022 + LcVersionMinMacosx LoadCommand = 0x24 + LcVersionMinIphoneos LoadCommand = 0x25 + LcFunctionStarts LoadCommand = 0x26 + LcDyldEnvironment LoadCommand = 0x27 + LcMain LoadCommand = 0x80000028 + LcDataInCode LoadCommand = 0x29 + LcSourceVersion LoadCommand = 0x2A + LcDylibCodeSignDrs LoadCommand = 0x2B + LcEncryptionInfo64 LoadCommand = 0x2C + LcVersionMinTvos LoadCommand = 0x2F + LcVersionMinWatchos LoadCommand = 0x30 +) diff --git a/src/macho/MachO.go b/src/macho/MachO.go new file mode 100644 index 0000000..c2fd883 --- /dev/null +++ b/src/macho/MachO.go @@ -0,0 +1,107 @@ +package macho + +import ( + "bytes" + "encoding/binary" + "io" + + "git.akyoto.dev/cli/q/src/config" +) + +// MachO is the executable format used on MacOS. +type MachO struct { + Header + Code []byte + Data []byte +} + +// New creates a new Mach-O binary. +func New(code []byte, data []byte) *MachO { + return &MachO{ + Header: Header{ + Magic: 0xFEEDFACF, + Architecture: CPU_X86_64, + MicroArchitecture: 0x80000003, + Type: TypeExecute, + NumCommands: 3, + SizeCommands: 0x48*2 + 184, + Flags: FlagNoUndefs, + Reserved: 0, + }, + Code: code, + Data: data, + } +} + +// Write writes the Mach-O format to the given writer. +func (m *MachO) Write(writer io.Writer) { + binary.Write(writer, binary.LittleEndian, &m.Header) + + binary.Write(writer, binary.LittleEndian, &Segment64{ + LoadCommand: LcSegment64, + Length: 0x48, + Name: [16]byte{'_', '_', 'P', 'A', 'G', 'E', 'Z', 'E', 'R', 'O'}, + Address: 0, + SizeInMemory: config.BaseAddress, + Offset: 0, + SizeInFile: 0, + NumSections: 0, + Flag: 0, + MaxProt: 0, + InitProt: 0, + }) + + codeStart := 32 + m.Header.SizeCommands + totalSize := uint64(codeStart) + uint64(len(m.Code)) + + binary.Write(writer, binary.LittleEndian, &Segment64{ + LoadCommand: LcSegment64, + Length: 0x48, + Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, + Address: config.BaseAddress, + SizeInMemory: totalSize, + Offset: 0, + SizeInFile: totalSize, + NumSections: 0, + Flag: 0, + MaxProt: ProtReadable | ProtWritable | ProtExecutable, + InitProt: ProtReadable | ProtExecutable, + }) + + binary.Write(writer, binary.LittleEndian, &Thread{ + LoadCommand: LcUnixthread, + Len: 184, + Type: 0x4, + }) + + binary.Write(writer, binary.LittleEndian, []uint32{ + 42, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + config.BaseAddress + uint32(codeStart), 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + }) + + writer.Write(m.Code) + + if totalSize < 4096 { + writer.Write(bytes.Repeat([]byte{0}, 4096-int(totalSize))) + } +} diff --git a/src/macho/Prot.go b/src/macho/Prot.go new file mode 100644 index 0000000..562b624 --- /dev/null +++ b/src/macho/Prot.go @@ -0,0 +1,9 @@ +package macho + +type Prot uint32 + +const ( + ProtReadable Prot = 0x1 + ProtWritable Prot = 0x2 + ProtExecutable Prot = 0x4 +) diff --git a/src/macho/Segment64.go b/src/macho/Segment64.go new file mode 100644 index 0000000..382e18f --- /dev/null +++ b/src/macho/Segment64.go @@ -0,0 +1,16 @@ +package macho + +// Segment64 is a segment load command. +type Segment64 struct { + LoadCommand + Length uint32 + Name [16]byte + Address uint64 + SizeInMemory uint64 + Offset uint64 + SizeInFile uint64 + MaxProt Prot + InitProt Prot + NumSections uint32 + Flag uint32 +} diff --git a/src/macho/Thread.go b/src/macho/Thread.go new file mode 100644 index 0000000..69f9053 --- /dev/null +++ b/src/macho/Thread.go @@ -0,0 +1,8 @@ +package macho + +// Thread is a thread state load command. +type Thread struct { + LoadCommand + Len uint32 + Type uint32 +} From 58f010b81a5bf2ef179ce7b79cccd8499db40fb8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 11 Aug 2024 21:15:47 +0200 Subject: [PATCH 0494/1012] Added Mach-O file format --- src/compiler/Result.go | 29 +++++++++-- src/macho/CPU.go | 10 ++++ src/macho/Header.go | 13 +++++ src/macho/HeaderFlags.go | 32 ++++++++++++ src/macho/HeaderType.go | 12 +++++ src/macho/LoadCommand.go | 34 +++++++++++++ src/macho/MachO.go | 107 +++++++++++++++++++++++++++++++++++++++ src/macho/Prot.go | 9 ++++ src/macho/Segment64.go | 16 ++++++ src/macho/Thread.go | 8 +++ 10 files changed, 266 insertions(+), 4 deletions(-) create mode 100644 src/macho/CPU.go create mode 100644 src/macho/Header.go create mode 100644 src/macho/HeaderFlags.go create mode 100644 src/macho/HeaderType.go create mode 100644 src/macho/LoadCommand.go create mode 100644 src/macho/MachO.go create mode 100644 src/macho/Prot.go create mode 100644 src/macho/Segment64.go create mode 100644 src/macho/Thread.go diff --git a/src/compiler/Result.go b/src/compiler/Result.go index ca51ba1..8551388 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -4,11 +4,13 @@ import ( "bufio" "io" "os" + "runtime" "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/macho" "git.akyoto.dev/cli/q/src/os/linux" ) @@ -31,13 +33,22 @@ func (r *Result) finalize() ([]byte, []byte) { Data: make(map[string][]byte, r.DataCount), } + sysExit := 0 + + switch runtime.GOOS { + case "linux": + sysExit = linux.Exit + case "darwin": + sysExit = 1 | 0x2000000 + } + final.Call("main.main") - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() final.Label(asm.LABEL, "_crash") - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() @@ -117,7 +128,17 @@ func (r *Result) WriteFile(path string) error { // write writes an executable file to the given writer. func write(writer io.Writer, code []byte, data []byte) error { buffer := bufio.NewWriter(writer) - executable := elf.New(code, data) - executable.Write(buffer) + + switch runtime.GOOS { + case "darwin": + exe := macho.New(code, data) + exe.Write(buffer) + case "linux": + exe := elf.New(code, data) + exe.Write(buffer) + default: + panic("unsupported platform") + } + return buffer.Flush() } diff --git a/src/macho/CPU.go b/src/macho/CPU.go new file mode 100644 index 0000000..3888aaa --- /dev/null +++ b/src/macho/CPU.go @@ -0,0 +1,10 @@ +package macho + +type CPU uint32 + +const ( + CPU_X86 CPU = 7 + CPU_X86_64 CPU = CPU_X86 | 0x01000000 + CPU_ARM CPU = 12 + CPU_ARM_64 CPU = CPU_ARM | 0x01000000 +) diff --git a/src/macho/Header.go b/src/macho/Header.go new file mode 100644 index 0000000..f7798be --- /dev/null +++ b/src/macho/Header.go @@ -0,0 +1,13 @@ +package macho + +// Header contains general information. +type Header struct { + Magic uint32 + Architecture CPU + MicroArchitecture uint32 + Type HeaderType + NumCommands uint32 + SizeCommands uint32 + Flags HeaderFlags + Reserved uint32 +} diff --git a/src/macho/HeaderFlags.go b/src/macho/HeaderFlags.go new file mode 100644 index 0000000..4b17f50 --- /dev/null +++ b/src/macho/HeaderFlags.go @@ -0,0 +1,32 @@ +package macho + +type HeaderFlags uint32 + +const ( + FlagNoUndefs HeaderFlags = 0x1 + FlagIncrLink HeaderFlags = 0x2 + FlagDyldLink HeaderFlags = 0x4 + FlagBindAtLoad HeaderFlags = 0x8 + FlagPrebound HeaderFlags = 0x10 + FlagSplitSegs HeaderFlags = 0x20 + FlagLazyInit HeaderFlags = 0x40 + FlagTwoLevel HeaderFlags = 0x80 + FlagForceFlat HeaderFlags = 0x100 + FlagNoMultiDefs HeaderFlags = 0x200 + FlagNoFixPrebinding HeaderFlags = 0x400 + FlagPrebindable HeaderFlags = 0x800 + FlagAllModsBound HeaderFlags = 0x1000 + FlagSubsectionsViaSymbols HeaderFlags = 0x2000 + FlagCanonical HeaderFlags = 0x4000 + FlagWeakDefines HeaderFlags = 0x8000 + FlagBindsToWeak HeaderFlags = 0x10000 + FlagAllowStackExecution HeaderFlags = 0x20000 + FlagRootSafe HeaderFlags = 0x40000 + FlagSetuidSafe HeaderFlags = 0x80000 + FlagNoReexportedDylibs HeaderFlags = 0x100000 + FlagPIE HeaderFlags = 0x200000 + FlagDeadStrippableDylib HeaderFlags = 0x400000 + FlagHasTLVDescriptors HeaderFlags = 0x800000 + FlagNoHeapExecution HeaderFlags = 0x1000000 + FlagAppExtensionSafe HeaderFlags = 0x2000000 +) diff --git a/src/macho/HeaderType.go b/src/macho/HeaderType.go new file mode 100644 index 0000000..f106b97 --- /dev/null +++ b/src/macho/HeaderType.go @@ -0,0 +1,12 @@ +package macho + +type HeaderType uint32 + +const ( + TypeObject HeaderType = 0x1 + TypeExecute HeaderType = 0x2 + TypeCore HeaderType = 0x4 + TypeDylib HeaderType = 0x6 + TypeBundle HeaderType = 0x8 + TypeDsym HeaderType = 0xA +) diff --git a/src/macho/LoadCommand.go b/src/macho/LoadCommand.go new file mode 100644 index 0000000..0d4b04f --- /dev/null +++ b/src/macho/LoadCommand.go @@ -0,0 +1,34 @@ +package macho + +type LoadCommand uint32 + +const ( + LcSegment LoadCommand = 0x1 + LcSymtab LoadCommand = 0x2 + LcThread LoadCommand = 0x4 + LcUnixthread LoadCommand = 0x5 + LcDysymtab LoadCommand = 0xB + LcDylib LoadCommand = 0xC + LcIdDylib LoadCommand = 0xD + LcLoadDylinker LoadCommand = 0xE + LcIdDylinker LoadCommand = 0xF + LcSegment64 LoadCommand = 0x19 + LcUuid LoadCommand = 0x1B + LcCodeSignature LoadCommand = 0x1D + LcSegmentSplitInfo LoadCommand = 0x1E + LcRpath LoadCommand = 0x8000001C + LcEncryptionInfo LoadCommand = 0x21 + LcDyldInfo LoadCommand = 0x22 + LcDyldInfoOnly LoadCommand = 0x80000022 + LcVersionMinMacosx LoadCommand = 0x24 + LcVersionMinIphoneos LoadCommand = 0x25 + LcFunctionStarts LoadCommand = 0x26 + LcDyldEnvironment LoadCommand = 0x27 + LcMain LoadCommand = 0x80000028 + LcDataInCode LoadCommand = 0x29 + LcSourceVersion LoadCommand = 0x2A + LcDylibCodeSignDrs LoadCommand = 0x2B + LcEncryptionInfo64 LoadCommand = 0x2C + LcVersionMinTvos LoadCommand = 0x2F + LcVersionMinWatchos LoadCommand = 0x30 +) diff --git a/src/macho/MachO.go b/src/macho/MachO.go new file mode 100644 index 0000000..c2fd883 --- /dev/null +++ b/src/macho/MachO.go @@ -0,0 +1,107 @@ +package macho + +import ( + "bytes" + "encoding/binary" + "io" + + "git.akyoto.dev/cli/q/src/config" +) + +// MachO is the executable format used on MacOS. +type MachO struct { + Header + Code []byte + Data []byte +} + +// New creates a new Mach-O binary. +func New(code []byte, data []byte) *MachO { + return &MachO{ + Header: Header{ + Magic: 0xFEEDFACF, + Architecture: CPU_X86_64, + MicroArchitecture: 0x80000003, + Type: TypeExecute, + NumCommands: 3, + SizeCommands: 0x48*2 + 184, + Flags: FlagNoUndefs, + Reserved: 0, + }, + Code: code, + Data: data, + } +} + +// Write writes the Mach-O format to the given writer. +func (m *MachO) Write(writer io.Writer) { + binary.Write(writer, binary.LittleEndian, &m.Header) + + binary.Write(writer, binary.LittleEndian, &Segment64{ + LoadCommand: LcSegment64, + Length: 0x48, + Name: [16]byte{'_', '_', 'P', 'A', 'G', 'E', 'Z', 'E', 'R', 'O'}, + Address: 0, + SizeInMemory: config.BaseAddress, + Offset: 0, + SizeInFile: 0, + NumSections: 0, + Flag: 0, + MaxProt: 0, + InitProt: 0, + }) + + codeStart := 32 + m.Header.SizeCommands + totalSize := uint64(codeStart) + uint64(len(m.Code)) + + binary.Write(writer, binary.LittleEndian, &Segment64{ + LoadCommand: LcSegment64, + Length: 0x48, + Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, + Address: config.BaseAddress, + SizeInMemory: totalSize, + Offset: 0, + SizeInFile: totalSize, + NumSections: 0, + Flag: 0, + MaxProt: ProtReadable | ProtWritable | ProtExecutable, + InitProt: ProtReadable | ProtExecutable, + }) + + binary.Write(writer, binary.LittleEndian, &Thread{ + LoadCommand: LcUnixthread, + Len: 184, + Type: 0x4, + }) + + binary.Write(writer, binary.LittleEndian, []uint32{ + 42, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + config.BaseAddress + uint32(codeStart), 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + }) + + writer.Write(m.Code) + + if totalSize < 4096 { + writer.Write(bytes.Repeat([]byte{0}, 4096-int(totalSize))) + } +} diff --git a/src/macho/Prot.go b/src/macho/Prot.go new file mode 100644 index 0000000..562b624 --- /dev/null +++ b/src/macho/Prot.go @@ -0,0 +1,9 @@ +package macho + +type Prot uint32 + +const ( + ProtReadable Prot = 0x1 + ProtWritable Prot = 0x2 + ProtExecutable Prot = 0x4 +) diff --git a/src/macho/Segment64.go b/src/macho/Segment64.go new file mode 100644 index 0000000..382e18f --- /dev/null +++ b/src/macho/Segment64.go @@ -0,0 +1,16 @@ +package macho + +// Segment64 is a segment load command. +type Segment64 struct { + LoadCommand + Length uint32 + Name [16]byte + Address uint64 + SizeInMemory uint64 + Offset uint64 + SizeInFile uint64 + MaxProt Prot + InitProt Prot + NumSections uint32 + Flag uint32 +} diff --git a/src/macho/Thread.go b/src/macho/Thread.go new file mode 100644 index 0000000..69f9053 --- /dev/null +++ b/src/macho/Thread.go @@ -0,0 +1,8 @@ +package macho + +// Thread is a thread state load command. +type Thread struct { + LoadCommand + Len uint32 + Type uint32 +} From f70a2e848db5a33f97539d2f1c75fb9f30cb5695 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 12 Aug 2024 12:16:01 +0200 Subject: [PATCH 0495/1012] Improved mac support --- src/asm/Finalize.go | 2 +- src/cli/Build.go | 23 +++++++++++++++- src/cli/Main_test.go | 2 ++ src/cli/Run.go | 2 +- src/compiler/Result.go | 34 +++++++++++++----------- src/config/config.go | 27 ++++++++++++++++--- src/config/init.go | 1 + src/errors/ExpectedCLIParameter.go | 13 +++++++++ src/{ => os/linux}/elf/ELF.go | 0 src/{ => os/linux}/elf/ELF_test.go | 2 +- src/{ => os/linux}/elf/Header.go | 0 src/{ => os/linux}/elf/Padding.go | 2 +- src/{ => os/linux}/elf/ProgramHeader.go | 0 src/{ => os/linux}/elf/SectionHeader.go | 0 src/{ => os/linux}/elf/elf.md | 0 src/os/mac/Syscall.go | 10 +++++++ src/{ => os/mac}/macho/CPU.go | 0 src/{ => os/mac}/macho/Header.go | 0 src/{ => os/mac}/macho/HeaderFlags.go | 0 src/{ => os/mac}/macho/HeaderType.go | 0 src/{ => os/mac}/macho/LoadCommand.go | 0 src/{ => os/mac}/macho/MachO.go | 35 ++++++++++++++++++------- src/{ => os/mac}/macho/Prot.go | 0 src/{ => os/mac}/macho/Segment64.go | 0 src/{ => os/mac}/macho/Thread.go | 0 25 files changed, 119 insertions(+), 34 deletions(-) create mode 100644 src/errors/ExpectedCLIParameter.go rename src/{ => os/linux}/elf/ELF.go (100%) rename src/{ => os/linux}/elf/ELF_test.go (75%) rename src/{ => os/linux}/elf/Header.go (100%) rename src/{ => os/linux}/elf/Padding.go (62%) rename src/{ => os/linux}/elf/ProgramHeader.go (100%) rename src/{ => os/linux}/elf/SectionHeader.go (100%) rename src/{ => os/linux}/elf/elf.md (100%) create mode 100644 src/os/mac/Syscall.go rename src/{ => os/mac}/macho/CPU.go (100%) rename src/{ => os/mac}/macho/Header.go (100%) rename src/{ => os/mac}/macho/HeaderFlags.go (100%) rename src/{ => os/mac}/macho/HeaderType.go (100%) rename src/{ => os/mac}/macho/LoadCommand.go (100%) rename src/{ => os/mac}/macho/MachO.go (69%) rename src/{ => os/mac}/macho/Prot.go (100%) rename src/{ => os/mac}/macho/Segment64.go (100%) rename src/{ => os/mac}/macho/Thread.go (100%) diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 446048a..c59ed54 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -8,7 +8,7 @@ import ( "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/os/linux/elf" "git.akyoto.dev/cli/q/src/sizeof" ) diff --git a/src/cli/Build.go b/src/cli/Build.go index ea5926e..00fc49a 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -18,7 +18,7 @@ func Build(args []string) int { fmt.Fprintln(os.Stderr, err) switch err.(type) { - case *errors.UnknownCLIParameter: + case *errors.ExpectedCLIParameter, *errors.UnknownCLIParameter: return 2 default: @@ -37,10 +37,31 @@ func buildWithArgs(args []string) (*build.Build, error) { switch args[i] { case "-a", "--assembler": config.Assembler = true + case "-d", "--dry": config.Dry = true + case "-v", "--verbose": config.Assembler = true + + case "--arch": + i++ + + if i >= len(args) { + return b, &errors.ExpectedCLIParameter{Parameter: "arch"} + } + + config.TargetArch = args[i] + + case "--os": + i++ + + if i >= len(args) { + return b, &errors.ExpectedCLIParameter{Parameter: "os"} + } + + config.TargetOS = args[i] + default: if strings.HasPrefix(args[i], "-") { return b, &errors.UnknownCLIParameter{Parameter: args[i]} diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index 4d40adb..93138a8 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -4,6 +4,7 @@ import ( "testing" "git.akyoto.dev/cli/q/src/cli" + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/go/assert" ) @@ -31,6 +32,7 @@ func TestCLI(t *testing.T) { for _, test := range tests { t.Log(test.arguments) + config.Reset() exitCode := cli.Main(test.arguments) assert.Equal(t, exitCode, test.expectedExitCode) } diff --git a/src/cli/Run.go b/src/cli/Run.go index 20b49f6..11a5969 100644 --- a/src/cli/Run.go +++ b/src/cli/Run.go @@ -16,7 +16,7 @@ func Run(args []string) int { fmt.Fprintln(os.Stderr, err) switch err.(type) { - case *errors.UnknownCLIParameter: + case *errors.ExpectedCLIParameter, *errors.UnknownCLIParameter: return 2 default: diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 8551388..18880ae 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -2,16 +2,18 @@ package compiler import ( "bufio" + "fmt" "io" "os" - "runtime" "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/elf" - "git.akyoto.dev/cli/q/src/macho" "git.akyoto.dev/cli/q/src/os/linux" + "git.akyoto.dev/cli/q/src/os/linux/elf" + "git.akyoto.dev/cli/q/src/os/mac" + "git.akyoto.dev/cli/q/src/os/mac/macho" ) // Result contains all the compiled functions in a build. @@ -35,11 +37,11 @@ func (r *Result) finalize() ([]byte, []byte) { sysExit := 0 - switch runtime.GOOS { + switch config.TargetOS { case "linux": sysExit = linux.Exit - case "darwin": - sysExit = 1 | 0x2000000 + case "mac": + sysExit = mac.Exit } final.Call("main.main") @@ -47,17 +49,17 @@ func (r *Result) finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() - final.Label(asm.LABEL, "_crash") - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit) - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) - final.Syscall() - // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { final.Merge(f.Assembler) }) + final.Label(asm.LABEL, "_crash") + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) + final.Syscall() + code, data := final.Finalize() return code, data } @@ -129,15 +131,15 @@ func (r *Result) WriteFile(path string) error { func write(writer io.Writer, code []byte, data []byte) error { buffer := bufio.NewWriter(writer) - switch runtime.GOOS { - case "darwin": - exe := macho.New(code, data) - exe.Write(buffer) + switch config.TargetOS { case "linux": exe := elf.New(code, data) exe.Write(buffer) + case "mac": + exe := macho.New(code, data) + exe.Write(buffer) default: - panic("unsupported platform") + return fmt.Errorf("unsupported platform '%s'", config.TargetOS) } return buffer.Flush() diff --git a/src/config/config.go b/src/config/config.go index 9254e64..db3b7c1 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -1,5 +1,7 @@ package config +import "runtime" + const ( // This is the absolute virtual minimum address we can load a program at, see `sysctl vm.mmap_min_addr`. MinAddress = 0x10000 @@ -16,11 +18,30 @@ const ( var ( // Shows the assembly instructions at the end of the compilation. - Assembler = false + Assembler bool // Calculates the result of operations on constants at compile time. - ConstantFold = true + ConstantFold bool // Skips writing the executable to disk. - Dry = false + Dry bool + + // Target architecture. + TargetArch string + + // Target platform. + TargetOS string ) + +// Reset resets the configuration to its default values. +func Reset() { + Assembler = false + ConstantFold = true + Dry = false + TargetArch = runtime.GOARCH + TargetOS = runtime.GOOS + + if TargetOS == "darwin" { + TargetOS = "mac" + } +} diff --git a/src/config/init.go b/src/config/init.go index 5f0f972..8ef2be7 100644 --- a/src/config/init.go +++ b/src/config/init.go @@ -16,6 +16,7 @@ var ( func init() { debug.SetGCPercent(-1) + Reset() var err error Executable, err = os.Executable() diff --git a/src/errors/ExpectedCLIParameter.go b/src/errors/ExpectedCLIParameter.go new file mode 100644 index 0000000..0c25c6b --- /dev/null +++ b/src/errors/ExpectedCLIParameter.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// ExpectedCLIParameter error is created when a command line parameter is missing. +type ExpectedCLIParameter struct { + Parameter string +} + +// Error generates the string representation. +func (err *ExpectedCLIParameter) Error() string { + return fmt.Sprintf("Expected parameter '%s'", err.Parameter) +} diff --git a/src/elf/ELF.go b/src/os/linux/elf/ELF.go similarity index 100% rename from src/elf/ELF.go rename to src/os/linux/elf/ELF.go diff --git a/src/elf/ELF_test.go b/src/os/linux/elf/ELF_test.go similarity index 75% rename from src/elf/ELF_test.go rename to src/os/linux/elf/ELF_test.go index 45a9aa2..a2051d8 100644 --- a/src/elf/ELF_test.go +++ b/src/os/linux/elf/ELF_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/os/linux/elf" ) func TestELF(t *testing.T) { diff --git a/src/elf/Header.go b/src/os/linux/elf/Header.go similarity index 100% rename from src/elf/Header.go rename to src/os/linux/elf/Header.go diff --git a/src/elf/Padding.go b/src/os/linux/elf/Padding.go similarity index 62% rename from src/elf/Padding.go rename to src/os/linux/elf/Padding.go index 5cca58e..e56666a 100644 --- a/src/elf/Padding.go +++ b/src/os/linux/elf/Padding.go @@ -1,6 +1,6 @@ package elf // Padding calculates the padding needed to align `n` bytes with the specified alignment. -func Padding(n int64, align int64) int64 { +func Padding[T int | int32 | int64 | uint | uint32 | uint64](n T, align T) T { return align - (n % align) } diff --git a/src/elf/ProgramHeader.go b/src/os/linux/elf/ProgramHeader.go similarity index 100% rename from src/elf/ProgramHeader.go rename to src/os/linux/elf/ProgramHeader.go diff --git a/src/elf/SectionHeader.go b/src/os/linux/elf/SectionHeader.go similarity index 100% rename from src/elf/SectionHeader.go rename to src/os/linux/elf/SectionHeader.go diff --git a/src/elf/elf.md b/src/os/linux/elf/elf.md similarity index 100% rename from src/elf/elf.md rename to src/os/linux/elf/elf.md diff --git a/src/os/mac/Syscall.go b/src/os/mac/Syscall.go new file mode 100644 index 0000000..2111c25 --- /dev/null +++ b/src/os/mac/Syscall.go @@ -0,0 +1,10 @@ +package mac + +// Syscall numbers are divided into classes, here we need the BSD inherited syscalls. +const SyscallClassUnix = 0x2000000 + +// https://github.com/apple-oss-distributions/xnu/blob/main/bsd/kern/syscalls.master +const ( + Exit = 1 | SyscallClassUnix + Write = 4 | SyscallClassUnix +) diff --git a/src/macho/CPU.go b/src/os/mac/macho/CPU.go similarity index 100% rename from src/macho/CPU.go rename to src/os/mac/macho/CPU.go diff --git a/src/macho/Header.go b/src/os/mac/macho/Header.go similarity index 100% rename from src/macho/Header.go rename to src/os/mac/macho/Header.go diff --git a/src/macho/HeaderFlags.go b/src/os/mac/macho/HeaderFlags.go similarity index 100% rename from src/macho/HeaderFlags.go rename to src/os/mac/macho/HeaderFlags.go diff --git a/src/macho/HeaderType.go b/src/os/mac/macho/HeaderType.go similarity index 100% rename from src/macho/HeaderType.go rename to src/os/mac/macho/HeaderType.go diff --git a/src/macho/LoadCommand.go b/src/os/mac/macho/LoadCommand.go similarity index 100% rename from src/macho/LoadCommand.go rename to src/os/mac/macho/LoadCommand.go diff --git a/src/macho/MachO.go b/src/os/mac/macho/MachO.go similarity index 69% rename from src/macho/MachO.go rename to src/os/mac/macho/MachO.go index c2fd883..d4fc05b 100644 --- a/src/macho/MachO.go +++ b/src/os/mac/macho/MachO.go @@ -6,6 +6,7 @@ import ( "io" "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/os/linux/elf" ) // MachO is the executable format used on MacOS. @@ -21,10 +22,10 @@ func New(code []byte, data []byte) *MachO { Header: Header{ Magic: 0xFEEDFACF, Architecture: CPU_X86_64, - MicroArchitecture: 0x80000003, + MicroArchitecture: 3 | 0x80000000, Type: TypeExecute, - NumCommands: 3, - SizeCommands: 0x48*2 + 184, + NumCommands: 4, + SizeCommands: 0x48*3 + 184, Flags: FlagNoUndefs, Reserved: 0, }, @@ -52,22 +53,38 @@ func (m *MachO) Write(writer io.Writer) { }) codeStart := 32 + m.Header.SizeCommands - totalSize := uint64(codeStart) + uint64(len(m.Code)) + codeEnd := uint64(codeStart) + uint64(len(m.Code)) + dataPadding := elf.Padding(codeEnd, 4096) + dataStart := codeEnd + dataPadding binary.Write(writer, binary.LittleEndian, &Segment64{ LoadCommand: LcSegment64, Length: 0x48, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, Address: config.BaseAddress, - SizeInMemory: totalSize, + SizeInMemory: codeEnd, Offset: 0, - SizeInFile: totalSize, + SizeInFile: codeEnd, NumSections: 0, Flag: 0, MaxProt: ProtReadable | ProtWritable | ProtExecutable, InitProt: ProtReadable | ProtExecutable, }) + binary.Write(writer, binary.LittleEndian, &Segment64{ + LoadCommand: LcSegment64, + Length: 0x48, + Name: [16]byte{'_', '_', 'D', 'A', 'T', 'A'}, + Address: config.BaseAddress + dataStart, + SizeInMemory: uint64(len(m.Data)), + Offset: dataStart, + SizeInFile: uint64(len(m.Data)), + NumSections: 0, + Flag: 0, + MaxProt: ProtReadable, + InitProt: ProtReadable, + }) + binary.Write(writer, binary.LittleEndian, &Thread{ LoadCommand: LcUnixthread, Len: 184, @@ -100,8 +117,6 @@ func (m *MachO) Write(writer io.Writer) { }) writer.Write(m.Code) - - if totalSize < 4096 { - writer.Write(bytes.Repeat([]byte{0}, 4096-int(totalSize))) - } + writer.Write(bytes.Repeat([]byte{0}, int(dataPadding))) + writer.Write(m.Data) } diff --git a/src/macho/Prot.go b/src/os/mac/macho/Prot.go similarity index 100% rename from src/macho/Prot.go rename to src/os/mac/macho/Prot.go diff --git a/src/macho/Segment64.go b/src/os/mac/macho/Segment64.go similarity index 100% rename from src/macho/Segment64.go rename to src/os/mac/macho/Segment64.go diff --git a/src/macho/Thread.go b/src/os/mac/macho/Thread.go similarity index 100% rename from src/macho/Thread.go rename to src/os/mac/macho/Thread.go From cf52919edc385647a9e185ca00d59aab91f20780 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 12 Aug 2024 12:16:01 +0200 Subject: [PATCH 0496/1012] Improved mac support --- src/asm/Finalize.go | 2 +- src/cli/Build.go | 23 +++++++++++++++- src/cli/Main_test.go | 2 ++ src/cli/Run.go | 2 +- src/compiler/Result.go | 34 +++++++++++++----------- src/config/config.go | 27 ++++++++++++++++--- src/config/init.go | 1 + src/errors/ExpectedCLIParameter.go | 13 +++++++++ src/{ => os/linux}/elf/ELF.go | 0 src/{ => os/linux}/elf/ELF_test.go | 2 +- src/{ => os/linux}/elf/Header.go | 0 src/{ => os/linux}/elf/Padding.go | 2 +- src/{ => os/linux}/elf/ProgramHeader.go | 0 src/{ => os/linux}/elf/SectionHeader.go | 0 src/{ => os/linux}/elf/elf.md | 0 src/os/mac/Syscall.go | 10 +++++++ src/{ => os/mac}/macho/CPU.go | 0 src/{ => os/mac}/macho/Header.go | 0 src/{ => os/mac}/macho/HeaderFlags.go | 0 src/{ => os/mac}/macho/HeaderType.go | 0 src/{ => os/mac}/macho/LoadCommand.go | 0 src/{ => os/mac}/macho/MachO.go | 35 ++++++++++++++++++------- src/{ => os/mac}/macho/Prot.go | 0 src/{ => os/mac}/macho/Segment64.go | 0 src/{ => os/mac}/macho/Thread.go | 0 25 files changed, 119 insertions(+), 34 deletions(-) create mode 100644 src/errors/ExpectedCLIParameter.go rename src/{ => os/linux}/elf/ELF.go (100%) rename src/{ => os/linux}/elf/ELF_test.go (75%) rename src/{ => os/linux}/elf/Header.go (100%) rename src/{ => os/linux}/elf/Padding.go (62%) rename src/{ => os/linux}/elf/ProgramHeader.go (100%) rename src/{ => os/linux}/elf/SectionHeader.go (100%) rename src/{ => os/linux}/elf/elf.md (100%) create mode 100644 src/os/mac/Syscall.go rename src/{ => os/mac}/macho/CPU.go (100%) rename src/{ => os/mac}/macho/Header.go (100%) rename src/{ => os/mac}/macho/HeaderFlags.go (100%) rename src/{ => os/mac}/macho/HeaderType.go (100%) rename src/{ => os/mac}/macho/LoadCommand.go (100%) rename src/{ => os/mac}/macho/MachO.go (69%) rename src/{ => os/mac}/macho/Prot.go (100%) rename src/{ => os/mac}/macho/Segment64.go (100%) rename src/{ => os/mac}/macho/Thread.go (100%) diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 446048a..c59ed54 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -8,7 +8,7 @@ import ( "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/os/linux/elf" "git.akyoto.dev/cli/q/src/sizeof" ) diff --git a/src/cli/Build.go b/src/cli/Build.go index ea5926e..00fc49a 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -18,7 +18,7 @@ func Build(args []string) int { fmt.Fprintln(os.Stderr, err) switch err.(type) { - case *errors.UnknownCLIParameter: + case *errors.ExpectedCLIParameter, *errors.UnknownCLIParameter: return 2 default: @@ -37,10 +37,31 @@ func buildWithArgs(args []string) (*build.Build, error) { switch args[i] { case "-a", "--assembler": config.Assembler = true + case "-d", "--dry": config.Dry = true + case "-v", "--verbose": config.Assembler = true + + case "--arch": + i++ + + if i >= len(args) { + return b, &errors.ExpectedCLIParameter{Parameter: "arch"} + } + + config.TargetArch = args[i] + + case "--os": + i++ + + if i >= len(args) { + return b, &errors.ExpectedCLIParameter{Parameter: "os"} + } + + config.TargetOS = args[i] + default: if strings.HasPrefix(args[i], "-") { return b, &errors.UnknownCLIParameter{Parameter: args[i]} diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index 4d40adb..93138a8 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -4,6 +4,7 @@ import ( "testing" "git.akyoto.dev/cli/q/src/cli" + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/go/assert" ) @@ -31,6 +32,7 @@ func TestCLI(t *testing.T) { for _, test := range tests { t.Log(test.arguments) + config.Reset() exitCode := cli.Main(test.arguments) assert.Equal(t, exitCode, test.expectedExitCode) } diff --git a/src/cli/Run.go b/src/cli/Run.go index 20b49f6..11a5969 100644 --- a/src/cli/Run.go +++ b/src/cli/Run.go @@ -16,7 +16,7 @@ func Run(args []string) int { fmt.Fprintln(os.Stderr, err) switch err.(type) { - case *errors.UnknownCLIParameter: + case *errors.ExpectedCLIParameter, *errors.UnknownCLIParameter: return 2 default: diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 8551388..18880ae 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -2,16 +2,18 @@ package compiler import ( "bufio" + "fmt" "io" "os" - "runtime" "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/elf" - "git.akyoto.dev/cli/q/src/macho" "git.akyoto.dev/cli/q/src/os/linux" + "git.akyoto.dev/cli/q/src/os/linux/elf" + "git.akyoto.dev/cli/q/src/os/mac" + "git.akyoto.dev/cli/q/src/os/mac/macho" ) // Result contains all the compiled functions in a build. @@ -35,11 +37,11 @@ func (r *Result) finalize() ([]byte, []byte) { sysExit := 0 - switch runtime.GOOS { + switch config.TargetOS { case "linux": sysExit = linux.Exit - case "darwin": - sysExit = 1 | 0x2000000 + case "mac": + sysExit = mac.Exit } final.Call("main.main") @@ -47,17 +49,17 @@ func (r *Result) finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() - final.Label(asm.LABEL, "_crash") - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit) - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) - final.Syscall() - // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { final.Merge(f.Assembler) }) + final.Label(asm.LABEL, "_crash") + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) + final.Syscall() + code, data := final.Finalize() return code, data } @@ -129,15 +131,15 @@ func (r *Result) WriteFile(path string) error { func write(writer io.Writer, code []byte, data []byte) error { buffer := bufio.NewWriter(writer) - switch runtime.GOOS { - case "darwin": - exe := macho.New(code, data) - exe.Write(buffer) + switch config.TargetOS { case "linux": exe := elf.New(code, data) exe.Write(buffer) + case "mac": + exe := macho.New(code, data) + exe.Write(buffer) default: - panic("unsupported platform") + return fmt.Errorf("unsupported platform '%s'", config.TargetOS) } return buffer.Flush() diff --git a/src/config/config.go b/src/config/config.go index 9254e64..db3b7c1 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -1,5 +1,7 @@ package config +import "runtime" + const ( // This is the absolute virtual minimum address we can load a program at, see `sysctl vm.mmap_min_addr`. MinAddress = 0x10000 @@ -16,11 +18,30 @@ const ( var ( // Shows the assembly instructions at the end of the compilation. - Assembler = false + Assembler bool // Calculates the result of operations on constants at compile time. - ConstantFold = true + ConstantFold bool // Skips writing the executable to disk. - Dry = false + Dry bool + + // Target architecture. + TargetArch string + + // Target platform. + TargetOS string ) + +// Reset resets the configuration to its default values. +func Reset() { + Assembler = false + ConstantFold = true + Dry = false + TargetArch = runtime.GOARCH + TargetOS = runtime.GOOS + + if TargetOS == "darwin" { + TargetOS = "mac" + } +} diff --git a/src/config/init.go b/src/config/init.go index 5f0f972..8ef2be7 100644 --- a/src/config/init.go +++ b/src/config/init.go @@ -16,6 +16,7 @@ var ( func init() { debug.SetGCPercent(-1) + Reset() var err error Executable, err = os.Executable() diff --git a/src/errors/ExpectedCLIParameter.go b/src/errors/ExpectedCLIParameter.go new file mode 100644 index 0000000..0c25c6b --- /dev/null +++ b/src/errors/ExpectedCLIParameter.go @@ -0,0 +1,13 @@ +package errors + +import "fmt" + +// ExpectedCLIParameter error is created when a command line parameter is missing. +type ExpectedCLIParameter struct { + Parameter string +} + +// Error generates the string representation. +func (err *ExpectedCLIParameter) Error() string { + return fmt.Sprintf("Expected parameter '%s'", err.Parameter) +} diff --git a/src/elf/ELF.go b/src/os/linux/elf/ELF.go similarity index 100% rename from src/elf/ELF.go rename to src/os/linux/elf/ELF.go diff --git a/src/elf/ELF_test.go b/src/os/linux/elf/ELF_test.go similarity index 75% rename from src/elf/ELF_test.go rename to src/os/linux/elf/ELF_test.go index 45a9aa2..a2051d8 100644 --- a/src/elf/ELF_test.go +++ b/src/os/linux/elf/ELF_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/os/linux/elf" ) func TestELF(t *testing.T) { diff --git a/src/elf/Header.go b/src/os/linux/elf/Header.go similarity index 100% rename from src/elf/Header.go rename to src/os/linux/elf/Header.go diff --git a/src/elf/Padding.go b/src/os/linux/elf/Padding.go similarity index 62% rename from src/elf/Padding.go rename to src/os/linux/elf/Padding.go index 5cca58e..e56666a 100644 --- a/src/elf/Padding.go +++ b/src/os/linux/elf/Padding.go @@ -1,6 +1,6 @@ package elf // Padding calculates the padding needed to align `n` bytes with the specified alignment. -func Padding(n int64, align int64) int64 { +func Padding[T int | int32 | int64 | uint | uint32 | uint64](n T, align T) T { return align - (n % align) } diff --git a/src/elf/ProgramHeader.go b/src/os/linux/elf/ProgramHeader.go similarity index 100% rename from src/elf/ProgramHeader.go rename to src/os/linux/elf/ProgramHeader.go diff --git a/src/elf/SectionHeader.go b/src/os/linux/elf/SectionHeader.go similarity index 100% rename from src/elf/SectionHeader.go rename to src/os/linux/elf/SectionHeader.go diff --git a/src/elf/elf.md b/src/os/linux/elf/elf.md similarity index 100% rename from src/elf/elf.md rename to src/os/linux/elf/elf.md diff --git a/src/os/mac/Syscall.go b/src/os/mac/Syscall.go new file mode 100644 index 0000000..2111c25 --- /dev/null +++ b/src/os/mac/Syscall.go @@ -0,0 +1,10 @@ +package mac + +// Syscall numbers are divided into classes, here we need the BSD inherited syscalls. +const SyscallClassUnix = 0x2000000 + +// https://github.com/apple-oss-distributions/xnu/blob/main/bsd/kern/syscalls.master +const ( + Exit = 1 | SyscallClassUnix + Write = 4 | SyscallClassUnix +) diff --git a/src/macho/CPU.go b/src/os/mac/macho/CPU.go similarity index 100% rename from src/macho/CPU.go rename to src/os/mac/macho/CPU.go diff --git a/src/macho/Header.go b/src/os/mac/macho/Header.go similarity index 100% rename from src/macho/Header.go rename to src/os/mac/macho/Header.go diff --git a/src/macho/HeaderFlags.go b/src/os/mac/macho/HeaderFlags.go similarity index 100% rename from src/macho/HeaderFlags.go rename to src/os/mac/macho/HeaderFlags.go diff --git a/src/macho/HeaderType.go b/src/os/mac/macho/HeaderType.go similarity index 100% rename from src/macho/HeaderType.go rename to src/os/mac/macho/HeaderType.go diff --git a/src/macho/LoadCommand.go b/src/os/mac/macho/LoadCommand.go similarity index 100% rename from src/macho/LoadCommand.go rename to src/os/mac/macho/LoadCommand.go diff --git a/src/macho/MachO.go b/src/os/mac/macho/MachO.go similarity index 69% rename from src/macho/MachO.go rename to src/os/mac/macho/MachO.go index c2fd883..d4fc05b 100644 --- a/src/macho/MachO.go +++ b/src/os/mac/macho/MachO.go @@ -6,6 +6,7 @@ import ( "io" "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/os/linux/elf" ) // MachO is the executable format used on MacOS. @@ -21,10 +22,10 @@ func New(code []byte, data []byte) *MachO { Header: Header{ Magic: 0xFEEDFACF, Architecture: CPU_X86_64, - MicroArchitecture: 0x80000003, + MicroArchitecture: 3 | 0x80000000, Type: TypeExecute, - NumCommands: 3, - SizeCommands: 0x48*2 + 184, + NumCommands: 4, + SizeCommands: 0x48*3 + 184, Flags: FlagNoUndefs, Reserved: 0, }, @@ -52,22 +53,38 @@ func (m *MachO) Write(writer io.Writer) { }) codeStart := 32 + m.Header.SizeCommands - totalSize := uint64(codeStart) + uint64(len(m.Code)) + codeEnd := uint64(codeStart) + uint64(len(m.Code)) + dataPadding := elf.Padding(codeEnd, 4096) + dataStart := codeEnd + dataPadding binary.Write(writer, binary.LittleEndian, &Segment64{ LoadCommand: LcSegment64, Length: 0x48, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, Address: config.BaseAddress, - SizeInMemory: totalSize, + SizeInMemory: codeEnd, Offset: 0, - SizeInFile: totalSize, + SizeInFile: codeEnd, NumSections: 0, Flag: 0, MaxProt: ProtReadable | ProtWritable | ProtExecutable, InitProt: ProtReadable | ProtExecutable, }) + binary.Write(writer, binary.LittleEndian, &Segment64{ + LoadCommand: LcSegment64, + Length: 0x48, + Name: [16]byte{'_', '_', 'D', 'A', 'T', 'A'}, + Address: config.BaseAddress + dataStart, + SizeInMemory: uint64(len(m.Data)), + Offset: dataStart, + SizeInFile: uint64(len(m.Data)), + NumSections: 0, + Flag: 0, + MaxProt: ProtReadable, + InitProt: ProtReadable, + }) + binary.Write(writer, binary.LittleEndian, &Thread{ LoadCommand: LcUnixthread, Len: 184, @@ -100,8 +117,6 @@ func (m *MachO) Write(writer io.Writer) { }) writer.Write(m.Code) - - if totalSize < 4096 { - writer.Write(bytes.Repeat([]byte{0}, 4096-int(totalSize))) - } + writer.Write(bytes.Repeat([]byte{0}, int(dataPadding))) + writer.Write(m.Data) } diff --git a/src/macho/Prot.go b/src/os/mac/macho/Prot.go similarity index 100% rename from src/macho/Prot.go rename to src/os/mac/macho/Prot.go diff --git a/src/macho/Segment64.go b/src/os/mac/macho/Segment64.go similarity index 100% rename from src/macho/Segment64.go rename to src/os/mac/macho/Segment64.go diff --git a/src/macho/Thread.go b/src/os/mac/macho/Thread.go similarity index 100% rename from src/macho/Thread.go rename to src/os/mac/macho/Thread.go From 0e005d22ce9a4442da1dc5ebf07e6d05b5d361c5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 12 Aug 2024 12:47:09 +0200 Subject: [PATCH 0497/1012] Improved segment load --- src/os/mac/macho/MachO.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/os/mac/macho/MachO.go b/src/os/mac/macho/MachO.go index d4fc05b..41b47ce 100644 --- a/src/os/mac/macho/MachO.go +++ b/src/os/mac/macho/MachO.go @@ -52,8 +52,9 @@ func (m *MachO) Write(writer io.Writer) { InitProt: 0, }) - codeStart := 32 + m.Header.SizeCommands - codeEnd := uint64(codeStart) + uint64(len(m.Code)) + codeStart := uint64(32 + m.Header.SizeCommands) + codeLength := uint64(len(m.Code)) + codeEnd := codeStart + codeLength dataPadding := elf.Padding(codeEnd, 4096) dataStart := codeEnd + dataPadding @@ -61,13 +62,13 @@ func (m *MachO) Write(writer io.Writer) { LoadCommand: LcSegment64, Length: 0x48, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, - Address: config.BaseAddress, - SizeInMemory: codeEnd, + Address: config.BaseAddress + codeStart, + SizeInMemory: codeLength, Offset: 0, - SizeInFile: codeEnd, + SizeInFile: codeLength, NumSections: 0, Flag: 0, - MaxProt: ProtReadable | ProtWritable | ProtExecutable, + MaxProt: ProtReadable | ProtExecutable, InitProt: ProtReadable | ProtExecutable, }) From 985fa5ae14ccfc727f9fa3f50c1eaa02c2a7a27d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 12 Aug 2024 12:47:09 +0200 Subject: [PATCH 0498/1012] Improved segment load --- src/os/mac/macho/MachO.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/os/mac/macho/MachO.go b/src/os/mac/macho/MachO.go index d4fc05b..41b47ce 100644 --- a/src/os/mac/macho/MachO.go +++ b/src/os/mac/macho/MachO.go @@ -52,8 +52,9 @@ func (m *MachO) Write(writer io.Writer) { InitProt: 0, }) - codeStart := 32 + m.Header.SizeCommands - codeEnd := uint64(codeStart) + uint64(len(m.Code)) + codeStart := uint64(32 + m.Header.SizeCommands) + codeLength := uint64(len(m.Code)) + codeEnd := codeStart + codeLength dataPadding := elf.Padding(codeEnd, 4096) dataStart := codeEnd + dataPadding @@ -61,13 +62,13 @@ func (m *MachO) Write(writer io.Writer) { LoadCommand: LcSegment64, Length: 0x48, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, - Address: config.BaseAddress, - SizeInMemory: codeEnd, + Address: config.BaseAddress + codeStart, + SizeInMemory: codeLength, Offset: 0, - SizeInFile: codeEnd, + SizeInFile: codeLength, NumSections: 0, Flag: 0, - MaxProt: ProtReadable | ProtWritable | ProtExecutable, + MaxProt: ProtReadable | ProtExecutable, InitProt: ProtReadable | ProtExecutable, }) From 1a895aa3609a3cce045ed1b7d93d94fc8178e1d2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 12 Aug 2024 14:17:24 +0200 Subject: [PATCH 0499/1012] Added conditional compilation for each OS --- lib/sys/{linux.q => sys_linux.q} | 0 lib/sys/sys_mac.q | 27 +++++++++++++++++++++++++++ src/scanner/queueDirectory.go | 9 +++++++++ 3 files changed, 36 insertions(+) rename lib/sys/{linux.q => sys_linux.q} (100%) create mode 100644 lib/sys/sys_mac.q diff --git a/lib/sys/linux.q b/lib/sys/sys_linux.q similarity index 100% rename from lib/sys/linux.q rename to lib/sys/sys_linux.q diff --git a/lib/sys/sys_mac.q b/lib/sys/sys_mac.q new file mode 100644 index 0000000..9ef9e1f --- /dev/null +++ b/lib/sys/sys_mac.q @@ -0,0 +1,27 @@ +read(fd Int, address Pointer, length Int) -> Int { + return syscall(0x2000003, fd, address, length) +} + +write(fd Int, address Pointer, length Int) -> Int { + return syscall(0x2000004, fd, address, length) +} + +open(file Pointer, flags Int, mode Int) -> Int { + return syscall(0x2000005, file, flags, mode) +} + +close(fd Int) -> Int { + return syscall(0x2000006, fd) +} + +mmap(address Int, length Int, protection Int, flags Int) -> Pointer { + return syscall(0x20000C5, address, length, protection, flags) +} + +munmap(address Pointer, length Int) -> Int { + return syscall(0x2000049, address, length) +} + +exit(status Int) { + syscall(0x2000001, status) +} \ No newline at end of file diff --git a/src/scanner/queueDirectory.go b/src/scanner/queueDirectory.go index 167905b..f1abe54 100644 --- a/src/scanner/queueDirectory.go +++ b/src/scanner/queueDirectory.go @@ -4,6 +4,7 @@ import ( "path/filepath" "strings" + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/fs" ) @@ -20,6 +21,14 @@ func (s *Scanner) queueDirectory(directory string, pkg string) { return } + if strings.HasSuffix(name, "_linux.q") && config.TargetOS != "linux" { + return + } + + if strings.HasSuffix(name, "_mac.q") && config.TargetOS != "mac" { + return + } + fullPath := filepath.Join(directory, name) s.queueFile(fullPath, pkg) }) From de223908d71bb7559949ce6751afab82ecae259b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 12 Aug 2024 14:17:24 +0200 Subject: [PATCH 0500/1012] Added conditional compilation for each OS --- lib/sys/{linux.q => sys_linux.q} | 0 lib/sys/sys_mac.q | 27 +++++++++++++++++++++++++++ src/scanner/queueDirectory.go | 9 +++++++++ 3 files changed, 36 insertions(+) rename lib/sys/{linux.q => sys_linux.q} (100%) create mode 100644 lib/sys/sys_mac.q diff --git a/lib/sys/linux.q b/lib/sys/sys_linux.q similarity index 100% rename from lib/sys/linux.q rename to lib/sys/sys_linux.q diff --git a/lib/sys/sys_mac.q b/lib/sys/sys_mac.q new file mode 100644 index 0000000..9ef9e1f --- /dev/null +++ b/lib/sys/sys_mac.q @@ -0,0 +1,27 @@ +read(fd Int, address Pointer, length Int) -> Int { + return syscall(0x2000003, fd, address, length) +} + +write(fd Int, address Pointer, length Int) -> Int { + return syscall(0x2000004, fd, address, length) +} + +open(file Pointer, flags Int, mode Int) -> Int { + return syscall(0x2000005, file, flags, mode) +} + +close(fd Int) -> Int { + return syscall(0x2000006, fd) +} + +mmap(address Int, length Int, protection Int, flags Int) -> Pointer { + return syscall(0x20000C5, address, length, protection, flags) +} + +munmap(address Pointer, length Int) -> Int { + return syscall(0x2000049, address, length) +} + +exit(status Int) { + syscall(0x2000001, status) +} \ No newline at end of file diff --git a/src/scanner/queueDirectory.go b/src/scanner/queueDirectory.go index 167905b..f1abe54 100644 --- a/src/scanner/queueDirectory.go +++ b/src/scanner/queueDirectory.go @@ -4,6 +4,7 @@ import ( "path/filepath" "strings" + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/fs" ) @@ -20,6 +21,14 @@ func (s *Scanner) queueDirectory(directory string, pkg string) { return } + if strings.HasSuffix(name, "_linux.q") && config.TargetOS != "linux" { + return + } + + if strings.HasSuffix(name, "_mac.q") && config.TargetOS != "mac" { + return + } + fullPath := filepath.Join(directory, name) s.queueFile(fullPath, pkg) }) From 314aea5ffca5a274c55339a6c426bc87c40f2886 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 12 Aug 2024 15:31:23 +0200 Subject: [PATCH 0501/1012] Fixed memory allocation on MacOS --- lib/mem/alloc_linux.q | 5 +++++ lib/mem/alloc_mac.q | 5 +++++ lib/mem/{alloc.q => free.q} | 4 ---- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 lib/mem/alloc_linux.q create mode 100644 lib/mem/alloc_mac.q rename lib/mem/{alloc.q => free.q} (51%) diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_linux.q new file mode 100644 index 0000000..70929fe --- /dev/null +++ b/lib/mem/alloc_linux.q @@ -0,0 +1,5 @@ +import sys + +alloc(length Int) -> Pointer { + return sys.mmap(0, length, 0x1|0x2, 0x02|0x20) +} \ No newline at end of file diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q new file mode 100644 index 0000000..bdeb356 --- /dev/null +++ b/lib/mem/alloc_mac.q @@ -0,0 +1,5 @@ +import sys + +alloc(length Int) -> Pointer { + return sys.mmap(0, length, 0x1|0x2, 0x02|0x1000) +} \ No newline at end of file diff --git a/lib/mem/alloc.q b/lib/mem/free.q similarity index 51% rename from lib/mem/alloc.q rename to lib/mem/free.q index 747290a..432d65d 100644 --- a/lib/mem/alloc.q +++ b/lib/mem/free.q @@ -1,9 +1,5 @@ import sys -alloc(length Int) -> Pointer { - return sys.mmap(0, length, 0x1|0x2, 0x02|0x20|0x100) -} - free(address Pointer, length Int) -> Int { return sys.munmap(address, length) } \ No newline at end of file From 81b0cd813c782a9c6bd1f01b2223376b4b72b32c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 12 Aug 2024 15:31:23 +0200 Subject: [PATCH 0502/1012] Fixed memory allocation on MacOS --- lib/mem/alloc_linux.q | 5 +++++ lib/mem/alloc_mac.q | 5 +++++ lib/mem/{alloc.q => free.q} | 4 ---- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 lib/mem/alloc_linux.q create mode 100644 lib/mem/alloc_mac.q rename lib/mem/{alloc.q => free.q} (51%) diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_linux.q new file mode 100644 index 0000000..70929fe --- /dev/null +++ b/lib/mem/alloc_linux.q @@ -0,0 +1,5 @@ +import sys + +alloc(length Int) -> Pointer { + return sys.mmap(0, length, 0x1|0x2, 0x02|0x20) +} \ No newline at end of file diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q new file mode 100644 index 0000000..bdeb356 --- /dev/null +++ b/lib/mem/alloc_mac.q @@ -0,0 +1,5 @@ +import sys + +alloc(length Int) -> Pointer { + return sys.mmap(0, length, 0x1|0x2, 0x02|0x1000) +} \ No newline at end of file diff --git a/lib/mem/alloc.q b/lib/mem/free.q similarity index 51% rename from lib/mem/alloc.q rename to lib/mem/free.q index 747290a..432d65d 100644 --- a/lib/mem/alloc.q +++ b/lib/mem/free.q @@ -1,9 +1,5 @@ import sys -alloc(length Int) -> Pointer { - return sys.mmap(0, length, 0x1|0x2, 0x02|0x20|0x100) -} - free(address Pointer, length Int) -> Int { return sys.munmap(address, length) } \ No newline at end of file From 5d195da51e68d9e25adbc3806c36c593b6d2c9e3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 12 Aug 2024 15:44:09 +0200 Subject: [PATCH 0503/1012] Fixed out of memory test --- README.md | 2 +- tests/programs/out-of-memory.q | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0a34d68..12736ec 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ Build a Linux x86-64 ELF executable from `examples/hello` and run it: ### Platform - [x] Linux -- [ ] Mac +- [x] Mac - [ ] Windows ## Documentation diff --git a/tests/programs/out-of-memory.q b/tests/programs/out-of-memory.q index 4b7603f..e9ead5b 100644 --- a/tests/programs/out-of-memory.q +++ b/tests/programs/out-of-memory.q @@ -2,5 +2,5 @@ import mem main() { address := mem.alloc(1024 * 1024 * 1024 * 1024) - assert address < 0 + assert address < 0 || address == 12 } \ No newline at end of file From fdb47c904520d6de1c634539768a6598d5ab2fe3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 12 Aug 2024 15:44:09 +0200 Subject: [PATCH 0504/1012] Fixed out of memory test --- README.md | 2 +- tests/programs/out-of-memory.q | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0a34d68..12736ec 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ Build a Linux x86-64 ELF executable from `examples/hello` and run it: ### Platform - [x] Linux -- [ ] Mac +- [x] Mac - [ ] Windows ## Documentation diff --git a/tests/programs/out-of-memory.q b/tests/programs/out-of-memory.q index 4b7603f..e9ead5b 100644 --- a/tests/programs/out-of-memory.q +++ b/tests/programs/out-of-memory.q @@ -2,5 +2,5 @@ import mem main() { address := mem.alloc(1024 * 1024 * 1024 * 1024) - assert address < 0 + assert address < 0 || address == 12 } \ No newline at end of file From 2e354befc373269b8a0a0f8fee1f39911949357c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 12 Aug 2024 15:46:45 +0200 Subject: [PATCH 0505/1012] Improved out of memory test --- tests/programs/out-of-memory.q | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/programs/out-of-memory.q b/tests/programs/out-of-memory.q index e9ead5b..4fe1fcd 100644 --- a/tests/programs/out-of-memory.q +++ b/tests/programs/out-of-memory.q @@ -1,6 +1,6 @@ import mem main() { - address := mem.alloc(1024 * 1024 * 1024 * 1024) - assert address < 0 || address == 12 + address := mem.alloc(1024 * 1024 * 1024 * 1024 * 1024) + assert address == -12 || address == 12 } \ No newline at end of file From b90ee62b988a305d8b8fd0e32ffd96cb9de3c225 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 12 Aug 2024 15:46:45 +0200 Subject: [PATCH 0506/1012] Improved out of memory test --- tests/programs/out-of-memory.q | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/programs/out-of-memory.q b/tests/programs/out-of-memory.q index e9ead5b..4fe1fcd 100644 --- a/tests/programs/out-of-memory.q +++ b/tests/programs/out-of-memory.q @@ -1,6 +1,6 @@ import mem main() { - address := mem.alloc(1024 * 1024 * 1024 * 1024) - assert address < 0 || address == 12 + address := mem.alloc(1024 * 1024 * 1024 * 1024 * 1024) + assert address == -12 || address == 12 } \ No newline at end of file From 3dffa69f3774b7917e1f6ce8973629f4fd0a8cd2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 13 Aug 2024 14:07:40 +0200 Subject: [PATCH 0507/1012] Added Windows PE support --- src/asm/Finalize.go | 4 +- src/compiler/Result.go | 39 +++++-- src/os/{linux/elf => common}/Padding.go | 4 +- src/os/linux/elf/Constants.go | 55 +++++++++ src/os/linux/elf/ELF.go | 3 +- src/os/linux/elf/Header.go | 8 +- src/os/linux/elf/ProgramHeader.go | 21 ---- src/os/linux/elf/SectionHeader.go | 27 ----- src/os/mac/macho/CPU.go | 10 -- .../macho/{HeaderFlags.go => Constants.go} | 28 +++++ src/os/mac/macho/HeaderType.go | 12 -- src/os/mac/macho/MachO.go | 4 +- src/os/mac/macho/Prot.go | 9 -- src/os/windows/pe/Constants.go | 72 ++++++++++++ src/os/windows/pe/DOSHeader.go | 9 ++ src/os/windows/pe/DataDirectory.go | 6 + src/os/windows/pe/EXE.go | 105 ++++++++++++++++++ src/os/windows/pe/NTHeader.go | 12 ++ src/os/windows/pe/OptionalHeader64.go | 34 ++++++ src/os/windows/pe/SectionHeader.go | 14 +++ 20 files changed, 373 insertions(+), 103 deletions(-) rename src/os/{linux/elf => common}/Padding.go (57%) create mode 100644 src/os/linux/elf/Constants.go delete mode 100644 src/os/mac/macho/CPU.go rename src/os/mac/macho/{HeaderFlags.go => Constants.go} (73%) delete mode 100644 src/os/mac/macho/HeaderType.go delete mode 100644 src/os/mac/macho/Prot.go create mode 100644 src/os/windows/pe/Constants.go create mode 100644 src/os/windows/pe/DOSHeader.go create mode 100644 src/os/windows/pe/DataDirectory.go create mode 100644 src/os/windows/pe/EXE.go create mode 100644 src/os/windows/pe/NTHeader.go create mode 100644 src/os/windows/pe/OptionalHeader64.go create mode 100644 src/os/windows/pe/SectionHeader.go diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index c59ed54..c32b2fb 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -8,7 +8,7 @@ import ( "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/os/linux/elf" + "git.akyoto.dev/cli/q/src/os/common" "git.akyoto.dev/cli/q/src/sizeof" ) @@ -338,7 +338,7 @@ restart: data, dataLabels = a.Data.Finalize() dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) - dataStart += int32(elf.Padding(int64(dataStart), config.Align)) + dataStart += int32(common.Padding(int64(dataStart), config.Align)) for _, pointer := range dataPointers { address := dataStart + pointer.Resolve() diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 18880ae..f21ea29 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -14,6 +14,7 @@ import ( "git.akyoto.dev/cli/q/src/os/linux/elf" "git.akyoto.dev/cli/q/src/os/mac" "git.akyoto.dev/cli/q/src/os/mac/macho" + "git.akyoto.dev/cli/q/src/os/windows/pe" ) // Result contains all the compiled functions in a build. @@ -35,20 +36,22 @@ func (r *Result) finalize() ([]byte, []byte) { Data: make(map[string][]byte, r.DataCount), } - sysExit := 0 + final.Call("main.main") switch config.TargetOS { case "linux": - sysExit = linux.Exit + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) + final.Syscall() case "mac": - sysExit = mac.Exit + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], mac.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) + final.Syscall() + case "windows": + final.RegisterNumber(asm.MOVE, x64.RAX, 0) + final.Return() } - final.Call("main.main") - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit) - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) - final.Syscall() - // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { @@ -56,9 +59,20 @@ func (r *Result) finalize() ([]byte, []byte) { }) final.Label(asm.LABEL, "_crash") - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit) - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) - final.Syscall() + + switch config.TargetOS { + case "linux": + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) + final.Syscall() + case "mac": + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], mac.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) + final.Syscall() + case "windows": + final.RegisterNumber(asm.MOVE, x64.RAX, 1) + final.Return() + } code, data := final.Finalize() return code, data @@ -138,6 +152,9 @@ func write(writer io.Writer, code []byte, data []byte) error { case "mac": exe := macho.New(code, data) exe.Write(buffer) + case "windows": + exe := pe.New(code, data) + exe.Write(buffer) default: return fmt.Errorf("unsupported platform '%s'", config.TargetOS) } diff --git a/src/os/linux/elf/Padding.go b/src/os/common/Padding.go similarity index 57% rename from src/os/linux/elf/Padding.go rename to src/os/common/Padding.go index e56666a..da40c13 100644 --- a/src/os/linux/elf/Padding.go +++ b/src/os/common/Padding.go @@ -1,6 +1,6 @@ -package elf +package common // Padding calculates the padding needed to align `n` bytes with the specified alignment. -func Padding[T int | int32 | int64 | uint | uint32 | uint64](n T, align T) T { +func Padding[T int64 | uint64 | int32 | uint32](n T, align T) T { return align - (n % align) } diff --git a/src/os/linux/elf/Constants.go b/src/os/linux/elf/Constants.go new file mode 100644 index 0000000..bd4600f --- /dev/null +++ b/src/os/linux/elf/Constants.go @@ -0,0 +1,55 @@ +package elf + +const ( + LittleEndian = 1 + TypeExecutable = 2 + ArchitectureAMD64 = 0x3E +) + +type ProgramType int32 + +const ( + ProgramTypeNULL ProgramType = 0 + ProgramTypeLOAD ProgramType = 1 + ProgramTypeDYNAMIC ProgramType = 2 + ProgramTypeINTERP ProgramType = 3 + ProgramTypeNOTE ProgramType = 4 + ProgramTypeSHLIB ProgramType = 5 + ProgramTypePHDR ProgramType = 6 + ProgramTypeTLS ProgramType = 7 +) + +type ProgramFlags int32 + +const ( + ProgramFlagsExecutable ProgramFlags = 0x1 + ProgramFlagsWritable ProgramFlags = 0x2 + ProgramFlagsReadable ProgramFlags = 0x4 +) + +type SectionType int32 + +const ( + SectionTypeNULL SectionType = 0 + SectionTypePROGBITS SectionType = 1 + SectionTypeSYMTAB SectionType = 2 + SectionTypeSTRTAB SectionType = 3 + SectionTypeRELA SectionType = 4 + SectionTypeHASH SectionType = 5 + SectionTypeDYNAMIC SectionType = 6 + SectionTypeNOTE SectionType = 7 + SectionTypeNOBITS SectionType = 8 + SectionTypeREL SectionType = 9 + SectionTypeSHLIB SectionType = 10 + SectionTypeDYNSYM SectionType = 11 +) + +type SectionFlags int64 + +const ( + SectionFlagsWritable SectionFlags = 1 << 0 + SectionFlagsAllocate SectionFlags = 1 << 1 + SectionFlagsExecutable SectionFlags = 1 << 2 + SectionFlagsStrings SectionFlags = 1 << 5 + SectionFlagsTLS SectionFlags = 1 << 10 +) diff --git a/src/os/linux/elf/ELF.go b/src/os/linux/elf/ELF.go index 2556d1e..fbe2370 100644 --- a/src/os/linux/elf/ELF.go +++ b/src/os/linux/elf/ELF.go @@ -6,6 +6,7 @@ import ( "io" "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/os/common" ) // ELF represents an ELF file. @@ -22,7 +23,7 @@ type ELF struct { // New creates a new ELF binary. func New(code []byte, data []byte) *ELF { dataOffset := config.CodeOffset + int64(len(code)) - dataPadding := Padding(dataOffset, config.Align) + dataPadding := common.Padding(dataOffset, config.Align) dataOffset += dataPadding return &ELF{ diff --git a/src/os/linux/elf/Header.go b/src/os/linux/elf/Header.go index 672f065..9bc7af5 100644 --- a/src/os/linux/elf/Header.go +++ b/src/os/linux/elf/Header.go @@ -1,11 +1,7 @@ package elf -const ( - LittleEndian = 1 - TypeExecutable = 2 - ArchitectureAMD64 = 0x3E - HeaderSize = 64 -) +// HeaderSize is equal to the size of a header in bytes. +const HeaderSize = 64 // Header contains general information. type Header struct { diff --git a/src/os/linux/elf/ProgramHeader.go b/src/os/linux/elf/ProgramHeader.go index cef76c7..7405790 100644 --- a/src/os/linux/elf/ProgramHeader.go +++ b/src/os/linux/elf/ProgramHeader.go @@ -14,24 +14,3 @@ type ProgramHeader struct { SizeInMemory int64 Align int64 } - -type ProgramType int32 - -const ( - ProgramTypeNULL ProgramType = 0 - ProgramTypeLOAD ProgramType = 1 - ProgramTypeDYNAMIC ProgramType = 2 - ProgramTypeINTERP ProgramType = 3 - ProgramTypeNOTE ProgramType = 4 - ProgramTypeSHLIB ProgramType = 5 - ProgramTypePHDR ProgramType = 6 - ProgramTypeTLS ProgramType = 7 -) - -type ProgramFlags int32 - -const ( - ProgramFlagsExecutable ProgramFlags = 0x1 - ProgramFlagsWritable ProgramFlags = 0x2 - ProgramFlagsReadable ProgramFlags = 0x4 -) diff --git a/src/os/linux/elf/SectionHeader.go b/src/os/linux/elf/SectionHeader.go index 4745796..106ab93 100644 --- a/src/os/linux/elf/SectionHeader.go +++ b/src/os/linux/elf/SectionHeader.go @@ -16,30 +16,3 @@ type SectionHeader struct { Align int64 EntrySize int64 } - -type SectionType int32 - -const ( - SectionTypeNULL SectionType = 0 - SectionTypePROGBITS SectionType = 1 - SectionTypeSYMTAB SectionType = 2 - SectionTypeSTRTAB SectionType = 3 - SectionTypeRELA SectionType = 4 - SectionTypeHASH SectionType = 5 - SectionTypeDYNAMIC SectionType = 6 - SectionTypeNOTE SectionType = 7 - SectionTypeNOBITS SectionType = 8 - SectionTypeREL SectionType = 9 - SectionTypeSHLIB SectionType = 10 - SectionTypeDYNSYM SectionType = 11 -) - -type SectionFlags int64 - -const ( - SectionFlagsWritable SectionFlags = 1 << 0 - SectionFlagsAllocate SectionFlags = 1 << 1 - SectionFlagsExecutable SectionFlags = 1 << 2 - SectionFlagsStrings SectionFlags = 1 << 5 - SectionFlagsTLS SectionFlags = 1 << 10 -) diff --git a/src/os/mac/macho/CPU.go b/src/os/mac/macho/CPU.go deleted file mode 100644 index 3888aaa..0000000 --- a/src/os/mac/macho/CPU.go +++ /dev/null @@ -1,10 +0,0 @@ -package macho - -type CPU uint32 - -const ( - CPU_X86 CPU = 7 - CPU_X86_64 CPU = CPU_X86 | 0x01000000 - CPU_ARM CPU = 12 - CPU_ARM_64 CPU = CPU_ARM | 0x01000000 -) diff --git a/src/os/mac/macho/HeaderFlags.go b/src/os/mac/macho/Constants.go similarity index 73% rename from src/os/mac/macho/HeaderFlags.go rename to src/os/mac/macho/Constants.go index 4b17f50..eb9e320 100644 --- a/src/os/mac/macho/HeaderFlags.go +++ b/src/os/mac/macho/Constants.go @@ -1,5 +1,22 @@ package macho +type CPU uint32 + +const ( + CPU_X86 CPU = 7 + CPU_X86_64 CPU = CPU_X86 | 0x01000000 + CPU_ARM CPU = 12 + CPU_ARM_64 CPU = CPU_ARM | 0x01000000 +) + +type Prot uint32 + +const ( + ProtReadable Prot = 0x1 + ProtWritable Prot = 0x2 + ProtExecutable Prot = 0x4 +) + type HeaderFlags uint32 const ( @@ -30,3 +47,14 @@ const ( FlagNoHeapExecution HeaderFlags = 0x1000000 FlagAppExtensionSafe HeaderFlags = 0x2000000 ) + +type HeaderType uint32 + +const ( + TypeObject HeaderType = 0x1 + TypeExecute HeaderType = 0x2 + TypeCore HeaderType = 0x4 + TypeDylib HeaderType = 0x6 + TypeBundle HeaderType = 0x8 + TypeDsym HeaderType = 0xA +) diff --git a/src/os/mac/macho/HeaderType.go b/src/os/mac/macho/HeaderType.go deleted file mode 100644 index f106b97..0000000 --- a/src/os/mac/macho/HeaderType.go +++ /dev/null @@ -1,12 +0,0 @@ -package macho - -type HeaderType uint32 - -const ( - TypeObject HeaderType = 0x1 - TypeExecute HeaderType = 0x2 - TypeCore HeaderType = 0x4 - TypeDylib HeaderType = 0x6 - TypeBundle HeaderType = 0x8 - TypeDsym HeaderType = 0xA -) diff --git a/src/os/mac/macho/MachO.go b/src/os/mac/macho/MachO.go index 41b47ce..806c96d 100644 --- a/src/os/mac/macho/MachO.go +++ b/src/os/mac/macho/MachO.go @@ -6,7 +6,7 @@ import ( "io" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/os/linux/elf" + "git.akyoto.dev/cli/q/src/os/common" ) // MachO is the executable format used on MacOS. @@ -55,7 +55,7 @@ func (m *MachO) Write(writer io.Writer) { codeStart := uint64(32 + m.Header.SizeCommands) codeLength := uint64(len(m.Code)) codeEnd := codeStart + codeLength - dataPadding := elf.Padding(codeEnd, 4096) + dataPadding := common.Padding(codeEnd, config.Align) dataStart := codeEnd + dataPadding binary.Write(writer, binary.LittleEndian, &Segment64{ diff --git a/src/os/mac/macho/Prot.go b/src/os/mac/macho/Prot.go deleted file mode 100644 index 562b624..0000000 --- a/src/os/mac/macho/Prot.go +++ /dev/null @@ -1,9 +0,0 @@ -package macho - -type Prot uint32 - -const ( - ProtReadable Prot = 0x1 - ProtWritable Prot = 0x2 - ProtExecutable Prot = 0x4 -) diff --git a/src/os/windows/pe/Constants.go b/src/os/windows/pe/Constants.go new file mode 100644 index 0000000..c77bea7 --- /dev/null +++ b/src/os/windows/pe/Constants.go @@ -0,0 +1,72 @@ +package pe + +// CPU +const ( + IMAGE_FILE_MACHINE_AMD64 = 0x8664 + IMAGE_FILE_MACHINE_ARM64 = 0xAA64 + IMAGE_FILE_MACHINE_RISCV64 = 0x5064 +) + +// Subsystems +const ( + IMAGE_SUBSYSTEM_UNKNOWN = 0 + IMAGE_SUBSYSTEM_NATIVE = 1 + IMAGE_SUBSYSTEM_WINDOWS_GUI = 2 + IMAGE_SUBSYSTEM_WINDOWS_CUI = 3 + IMAGE_SUBSYSTEM_OS2_CUI = 5 + IMAGE_SUBSYSTEM_POSIX_CUI = 7 + IMAGE_SUBSYSTEM_NATIVE_WINDOWS = 8 + IMAGE_SUBSYSTEM_WINDOWS_CE_GUI = 9 + IMAGE_SUBSYSTEM_EFI_APPLICATION = 10 + IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER = 11 + IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER = 12 + IMAGE_SUBSYSTEM_EFI_ROM = 13 + IMAGE_SUBSYSTEM_XBOX = 14 + IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION = 16 +) + +// Characteristics +const ( + IMAGE_FILE_RELOCS_STRIPPED = 0x0001 + IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002 + IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004 + IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008 + IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010 + IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020 + IMAGE_FILE_BYTES_REVERSED_LO = 0x0080 + IMAGE_FILE_32BIT_MACHINE = 0x0100 + IMAGE_FILE_DEBUG_STRIPPED = 0x0200 + IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400 + IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800 + IMAGE_FILE_SYSTEM = 0x1000 + IMAGE_FILE_DLL = 0x2000 + IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000 + IMAGE_FILE_BYTES_REVERSED_HI = 0x8000 +) + +// DLL characteristics +const ( + IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020 + IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040 + IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY = 0x0080 + IMAGE_DLLCHARACTERISTICS_NX_COMPAT = 0x0100 + IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200 + IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400 + IMAGE_DLLCHARACTERISTICS_NO_BIND = 0x0800 + IMAGE_DLLCHARACTERISTICS_APPCONTAINER = 0x1000 + IMAGE_DLLCHARACTERISTICS_WDM_DRIVER = 0x2000 + IMAGE_DLLCHARACTERISTICS_GUARD_CF = 0x4000 + IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000 +) + +// Section characteristics +const ( + IMAGE_SCN_CNT_CODE = 0x00000020 + IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040 + IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080 + IMAGE_SCN_LNK_COMDAT = 0x00001000 + IMAGE_SCN_MEM_DISCARDABLE = 0x02000000 + IMAGE_SCN_MEM_EXECUTE = 0x20000000 + IMAGE_SCN_MEM_READ = 0x40000000 + IMAGE_SCN_MEM_WRITE = 0x80000000 +) diff --git a/src/os/windows/pe/DOSHeader.go b/src/os/windows/pe/DOSHeader.go new file mode 100644 index 0000000..809d8fc --- /dev/null +++ b/src/os/windows/pe/DOSHeader.go @@ -0,0 +1,9 @@ +package pe + +// DOSHeader is at the beginning of each EXE file and nowadays just points +// to the NTHeader using an absolute file offset. +type DOSHeader struct { + Magic [4]byte + _ [56]byte + NTHeaderOffset uint32 +} diff --git a/src/os/windows/pe/DataDirectory.go b/src/os/windows/pe/DataDirectory.go new file mode 100644 index 0000000..8a435b8 --- /dev/null +++ b/src/os/windows/pe/DataDirectory.go @@ -0,0 +1,6 @@ +package pe + +type DataDirectory struct { + VirtualAddress uint32 + Size uint32 +} diff --git a/src/os/windows/pe/EXE.go b/src/os/windows/pe/EXE.go new file mode 100644 index 0000000..13aa5e5 --- /dev/null +++ b/src/os/windows/pe/EXE.go @@ -0,0 +1,105 @@ +package pe + +import ( + "encoding/binary" + "io" + + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/os/common" +) + +// EXE is the portable executable format used on Windows. +type EXE struct { + DOSHeader + NTHeader + OptionalHeader64 + CodeHeader SectionHeader + Code []byte + Data []byte +} + +// New creates a new EXE file. +func New(code []byte, data []byte) *EXE { + const codeStart = 0x170 + const optHeaderSize = 0xF0 + + codeSize := uint32(len(code)) + headerSize := uint32(codeStart) + sectionAlign := uint32(0x10) + fileAlign := uint32(0x10) + imageSize := uint32(codeStart + len(code)) + imageSize += common.Padding(imageSize, sectionAlign) + + return &EXE{ + DOSHeader: DOSHeader{ + Magic: [4]byte{'M', 'Z', 0, 0}, + NTHeaderOffset: 0x40, + }, + NTHeader: NTHeader{ + Signature: [4]byte{'P', 'E', 0, 0}, + Machine: IMAGE_FILE_MACHINE_AMD64, + NumberOfSections: 1, + SizeOfOptionalHeader: optHeaderSize, + Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE, + }, + OptionalHeader64: OptionalHeader64{ + Magic: 0x020B, // PE32+ executable + MajorLinkerVersion: 0x0E, + MinorLinkerVersion: 0x16, + SizeOfCode: codeSize, + AddressOfEntryPoint: codeStart, + ImageBase: config.BaseAddress, + SectionAlignment: sectionAlign, // power of 2, must be greater than or equal to FileAlignment + FileAlignment: fileAlign, // power of 2 + MajorOperatingSystemVersion: 0x06, + MajorSubsystemVersion: 0x06, + SizeOfImage: imageSize, + SizeOfHeaders: headerSize, + Subsystem: IMAGE_SUBSYSTEM_WINDOWS_GUI, + DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, + SizeOfStackReserve: 0x100000, + SizeOfStackCommit: 0x1000, + SizeOfHeapReserve: 0x100000, + SizeOfHeapCommit: 0x1000, + NumberOfRvaAndSizes: 16, + DataDirectory: [16]DataDirectory{ + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + }, + }, + CodeHeader: SectionHeader{ + Name: [8]byte{'.', 't', 'e', 'x', 't'}, + VirtualSize: uint32(len(code)), + VirtualAddress: codeStart, + RawSize: uint32(len(code)), // must be a multiple of FileAlignment + RawAddress: codeStart, // must be a multiple of FileAlignment + Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ, + }, + Code: code, + // Data: data, + } +} + +// Write writes the EXE file to the given writer. +func (pe *EXE) Write(writer io.Writer) { + binary.Write(writer, binary.LittleEndian, &pe.DOSHeader) + binary.Write(writer, binary.LittleEndian, &pe.NTHeader) + binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) + binary.Write(writer, binary.LittleEndian, &pe.CodeHeader) + binary.Write(writer, binary.LittleEndian, &pe.Code) + // binary.Write(writer, binary.LittleEndian, &pe.Data) +} diff --git a/src/os/windows/pe/NTHeader.go b/src/os/windows/pe/NTHeader.go new file mode 100644 index 0000000..f563c82 --- /dev/null +++ b/src/os/windows/pe/NTHeader.go @@ -0,0 +1,12 @@ +package pe + +type NTHeader struct { + Signature [4]byte + Machine uint16 + NumberOfSections uint16 + TimeDateStamp uint32 + PointerToSymbolTable uint32 + NumberOfSymbols uint32 + SizeOfOptionalHeader uint16 + Characteristics uint16 +} diff --git a/src/os/windows/pe/OptionalHeader64.go b/src/os/windows/pe/OptionalHeader64.go new file mode 100644 index 0000000..9a26a7e --- /dev/null +++ b/src/os/windows/pe/OptionalHeader64.go @@ -0,0 +1,34 @@ +package pe + +type OptionalHeader64 struct { + Magic uint16 + MajorLinkerVersion uint8 + MinorLinkerVersion uint8 + SizeOfCode uint32 + SizeOfInitializedData uint32 + SizeOfUninitializedData uint32 + AddressOfEntryPoint uint32 + BaseOfCode uint32 + ImageBase uint64 + SectionAlignment uint32 + FileAlignment uint32 + MajorOperatingSystemVersion uint16 + MinorOperatingSystemVersion uint16 + MajorImageVersion uint16 + MinorImageVersion uint16 + MajorSubsystemVersion uint16 + MinorSubsystemVersion uint16 + Win32VersionValue uint32 + SizeOfImage uint32 + SizeOfHeaders uint32 + CheckSum uint32 + Subsystem uint16 + DllCharacteristics uint16 + SizeOfStackReserve uint64 + SizeOfStackCommit uint64 + SizeOfHeapReserve uint64 + SizeOfHeapCommit uint64 + LoaderFlags uint32 + NumberOfRvaAndSizes uint32 + DataDirectory [16]DataDirectory +} diff --git a/src/os/windows/pe/SectionHeader.go b/src/os/windows/pe/SectionHeader.go new file mode 100644 index 0000000..8e95e85 --- /dev/null +++ b/src/os/windows/pe/SectionHeader.go @@ -0,0 +1,14 @@ +package pe + +type SectionHeader struct { + Name [8]byte + VirtualSize uint32 + VirtualAddress uint32 + RawSize uint32 + RawAddress uint32 + PointerToRelocations uint32 + PointerToLineNumbers uint32 + NumberOfRelocations uint16 + NumberOfLineNumbers uint16 + Characteristics uint32 +} From 7b1a293cd065360382b6c6c20a13570a6fd97960 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 13 Aug 2024 14:07:40 +0200 Subject: [PATCH 0508/1012] Added Windows PE support --- src/asm/Finalize.go | 4 +- src/compiler/Result.go | 39 +++++-- src/os/{linux/elf => common}/Padding.go | 4 +- src/os/linux/elf/Constants.go | 55 +++++++++ src/os/linux/elf/ELF.go | 3 +- src/os/linux/elf/Header.go | 8 +- src/os/linux/elf/ProgramHeader.go | 21 ---- src/os/linux/elf/SectionHeader.go | 27 ----- src/os/mac/macho/CPU.go | 10 -- .../macho/{HeaderFlags.go => Constants.go} | 28 +++++ src/os/mac/macho/HeaderType.go | 12 -- src/os/mac/macho/MachO.go | 4 +- src/os/mac/macho/Prot.go | 9 -- src/os/windows/pe/Constants.go | 72 ++++++++++++ src/os/windows/pe/DOSHeader.go | 9 ++ src/os/windows/pe/DataDirectory.go | 6 + src/os/windows/pe/EXE.go | 105 ++++++++++++++++++ src/os/windows/pe/NTHeader.go | 12 ++ src/os/windows/pe/OptionalHeader64.go | 34 ++++++ src/os/windows/pe/SectionHeader.go | 14 +++ 20 files changed, 373 insertions(+), 103 deletions(-) rename src/os/{linux/elf => common}/Padding.go (57%) create mode 100644 src/os/linux/elf/Constants.go delete mode 100644 src/os/mac/macho/CPU.go rename src/os/mac/macho/{HeaderFlags.go => Constants.go} (73%) delete mode 100644 src/os/mac/macho/HeaderType.go delete mode 100644 src/os/mac/macho/Prot.go create mode 100644 src/os/windows/pe/Constants.go create mode 100644 src/os/windows/pe/DOSHeader.go create mode 100644 src/os/windows/pe/DataDirectory.go create mode 100644 src/os/windows/pe/EXE.go create mode 100644 src/os/windows/pe/NTHeader.go create mode 100644 src/os/windows/pe/OptionalHeader64.go create mode 100644 src/os/windows/pe/SectionHeader.go diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index c59ed54..c32b2fb 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -8,7 +8,7 @@ import ( "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/os/linux/elf" + "git.akyoto.dev/cli/q/src/os/common" "git.akyoto.dev/cli/q/src/sizeof" ) @@ -338,7 +338,7 @@ restart: data, dataLabels = a.Data.Finalize() dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) - dataStart += int32(elf.Padding(int64(dataStart), config.Align)) + dataStart += int32(common.Padding(int64(dataStart), config.Align)) for _, pointer := range dataPointers { address := dataStart + pointer.Resolve() diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 18880ae..f21ea29 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -14,6 +14,7 @@ import ( "git.akyoto.dev/cli/q/src/os/linux/elf" "git.akyoto.dev/cli/q/src/os/mac" "git.akyoto.dev/cli/q/src/os/mac/macho" + "git.akyoto.dev/cli/q/src/os/windows/pe" ) // Result contains all the compiled functions in a build. @@ -35,20 +36,22 @@ func (r *Result) finalize() ([]byte, []byte) { Data: make(map[string][]byte, r.DataCount), } - sysExit := 0 + final.Call("main.main") switch config.TargetOS { case "linux": - sysExit = linux.Exit + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) + final.Syscall() case "mac": - sysExit = mac.Exit + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], mac.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) + final.Syscall() + case "windows": + final.RegisterNumber(asm.MOVE, x64.RAX, 0) + final.Return() } - final.Call("main.main") - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit) - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) - final.Syscall() - // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { @@ -56,9 +59,20 @@ func (r *Result) finalize() ([]byte, []byte) { }) final.Label(asm.LABEL, "_crash") - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], sysExit) - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) - final.Syscall() + + switch config.TargetOS { + case "linux": + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) + final.Syscall() + case "mac": + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], mac.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) + final.Syscall() + case "windows": + final.RegisterNumber(asm.MOVE, x64.RAX, 1) + final.Return() + } code, data := final.Finalize() return code, data @@ -138,6 +152,9 @@ func write(writer io.Writer, code []byte, data []byte) error { case "mac": exe := macho.New(code, data) exe.Write(buffer) + case "windows": + exe := pe.New(code, data) + exe.Write(buffer) default: return fmt.Errorf("unsupported platform '%s'", config.TargetOS) } diff --git a/src/os/linux/elf/Padding.go b/src/os/common/Padding.go similarity index 57% rename from src/os/linux/elf/Padding.go rename to src/os/common/Padding.go index e56666a..da40c13 100644 --- a/src/os/linux/elf/Padding.go +++ b/src/os/common/Padding.go @@ -1,6 +1,6 @@ -package elf +package common // Padding calculates the padding needed to align `n` bytes with the specified alignment. -func Padding[T int | int32 | int64 | uint | uint32 | uint64](n T, align T) T { +func Padding[T int64 | uint64 | int32 | uint32](n T, align T) T { return align - (n % align) } diff --git a/src/os/linux/elf/Constants.go b/src/os/linux/elf/Constants.go new file mode 100644 index 0000000..bd4600f --- /dev/null +++ b/src/os/linux/elf/Constants.go @@ -0,0 +1,55 @@ +package elf + +const ( + LittleEndian = 1 + TypeExecutable = 2 + ArchitectureAMD64 = 0x3E +) + +type ProgramType int32 + +const ( + ProgramTypeNULL ProgramType = 0 + ProgramTypeLOAD ProgramType = 1 + ProgramTypeDYNAMIC ProgramType = 2 + ProgramTypeINTERP ProgramType = 3 + ProgramTypeNOTE ProgramType = 4 + ProgramTypeSHLIB ProgramType = 5 + ProgramTypePHDR ProgramType = 6 + ProgramTypeTLS ProgramType = 7 +) + +type ProgramFlags int32 + +const ( + ProgramFlagsExecutable ProgramFlags = 0x1 + ProgramFlagsWritable ProgramFlags = 0x2 + ProgramFlagsReadable ProgramFlags = 0x4 +) + +type SectionType int32 + +const ( + SectionTypeNULL SectionType = 0 + SectionTypePROGBITS SectionType = 1 + SectionTypeSYMTAB SectionType = 2 + SectionTypeSTRTAB SectionType = 3 + SectionTypeRELA SectionType = 4 + SectionTypeHASH SectionType = 5 + SectionTypeDYNAMIC SectionType = 6 + SectionTypeNOTE SectionType = 7 + SectionTypeNOBITS SectionType = 8 + SectionTypeREL SectionType = 9 + SectionTypeSHLIB SectionType = 10 + SectionTypeDYNSYM SectionType = 11 +) + +type SectionFlags int64 + +const ( + SectionFlagsWritable SectionFlags = 1 << 0 + SectionFlagsAllocate SectionFlags = 1 << 1 + SectionFlagsExecutable SectionFlags = 1 << 2 + SectionFlagsStrings SectionFlags = 1 << 5 + SectionFlagsTLS SectionFlags = 1 << 10 +) diff --git a/src/os/linux/elf/ELF.go b/src/os/linux/elf/ELF.go index 2556d1e..fbe2370 100644 --- a/src/os/linux/elf/ELF.go +++ b/src/os/linux/elf/ELF.go @@ -6,6 +6,7 @@ import ( "io" "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/os/common" ) // ELF represents an ELF file. @@ -22,7 +23,7 @@ type ELF struct { // New creates a new ELF binary. func New(code []byte, data []byte) *ELF { dataOffset := config.CodeOffset + int64(len(code)) - dataPadding := Padding(dataOffset, config.Align) + dataPadding := common.Padding(dataOffset, config.Align) dataOffset += dataPadding return &ELF{ diff --git a/src/os/linux/elf/Header.go b/src/os/linux/elf/Header.go index 672f065..9bc7af5 100644 --- a/src/os/linux/elf/Header.go +++ b/src/os/linux/elf/Header.go @@ -1,11 +1,7 @@ package elf -const ( - LittleEndian = 1 - TypeExecutable = 2 - ArchitectureAMD64 = 0x3E - HeaderSize = 64 -) +// HeaderSize is equal to the size of a header in bytes. +const HeaderSize = 64 // Header contains general information. type Header struct { diff --git a/src/os/linux/elf/ProgramHeader.go b/src/os/linux/elf/ProgramHeader.go index cef76c7..7405790 100644 --- a/src/os/linux/elf/ProgramHeader.go +++ b/src/os/linux/elf/ProgramHeader.go @@ -14,24 +14,3 @@ type ProgramHeader struct { SizeInMemory int64 Align int64 } - -type ProgramType int32 - -const ( - ProgramTypeNULL ProgramType = 0 - ProgramTypeLOAD ProgramType = 1 - ProgramTypeDYNAMIC ProgramType = 2 - ProgramTypeINTERP ProgramType = 3 - ProgramTypeNOTE ProgramType = 4 - ProgramTypeSHLIB ProgramType = 5 - ProgramTypePHDR ProgramType = 6 - ProgramTypeTLS ProgramType = 7 -) - -type ProgramFlags int32 - -const ( - ProgramFlagsExecutable ProgramFlags = 0x1 - ProgramFlagsWritable ProgramFlags = 0x2 - ProgramFlagsReadable ProgramFlags = 0x4 -) diff --git a/src/os/linux/elf/SectionHeader.go b/src/os/linux/elf/SectionHeader.go index 4745796..106ab93 100644 --- a/src/os/linux/elf/SectionHeader.go +++ b/src/os/linux/elf/SectionHeader.go @@ -16,30 +16,3 @@ type SectionHeader struct { Align int64 EntrySize int64 } - -type SectionType int32 - -const ( - SectionTypeNULL SectionType = 0 - SectionTypePROGBITS SectionType = 1 - SectionTypeSYMTAB SectionType = 2 - SectionTypeSTRTAB SectionType = 3 - SectionTypeRELA SectionType = 4 - SectionTypeHASH SectionType = 5 - SectionTypeDYNAMIC SectionType = 6 - SectionTypeNOTE SectionType = 7 - SectionTypeNOBITS SectionType = 8 - SectionTypeREL SectionType = 9 - SectionTypeSHLIB SectionType = 10 - SectionTypeDYNSYM SectionType = 11 -) - -type SectionFlags int64 - -const ( - SectionFlagsWritable SectionFlags = 1 << 0 - SectionFlagsAllocate SectionFlags = 1 << 1 - SectionFlagsExecutable SectionFlags = 1 << 2 - SectionFlagsStrings SectionFlags = 1 << 5 - SectionFlagsTLS SectionFlags = 1 << 10 -) diff --git a/src/os/mac/macho/CPU.go b/src/os/mac/macho/CPU.go deleted file mode 100644 index 3888aaa..0000000 --- a/src/os/mac/macho/CPU.go +++ /dev/null @@ -1,10 +0,0 @@ -package macho - -type CPU uint32 - -const ( - CPU_X86 CPU = 7 - CPU_X86_64 CPU = CPU_X86 | 0x01000000 - CPU_ARM CPU = 12 - CPU_ARM_64 CPU = CPU_ARM | 0x01000000 -) diff --git a/src/os/mac/macho/HeaderFlags.go b/src/os/mac/macho/Constants.go similarity index 73% rename from src/os/mac/macho/HeaderFlags.go rename to src/os/mac/macho/Constants.go index 4b17f50..eb9e320 100644 --- a/src/os/mac/macho/HeaderFlags.go +++ b/src/os/mac/macho/Constants.go @@ -1,5 +1,22 @@ package macho +type CPU uint32 + +const ( + CPU_X86 CPU = 7 + CPU_X86_64 CPU = CPU_X86 | 0x01000000 + CPU_ARM CPU = 12 + CPU_ARM_64 CPU = CPU_ARM | 0x01000000 +) + +type Prot uint32 + +const ( + ProtReadable Prot = 0x1 + ProtWritable Prot = 0x2 + ProtExecutable Prot = 0x4 +) + type HeaderFlags uint32 const ( @@ -30,3 +47,14 @@ const ( FlagNoHeapExecution HeaderFlags = 0x1000000 FlagAppExtensionSafe HeaderFlags = 0x2000000 ) + +type HeaderType uint32 + +const ( + TypeObject HeaderType = 0x1 + TypeExecute HeaderType = 0x2 + TypeCore HeaderType = 0x4 + TypeDylib HeaderType = 0x6 + TypeBundle HeaderType = 0x8 + TypeDsym HeaderType = 0xA +) diff --git a/src/os/mac/macho/HeaderType.go b/src/os/mac/macho/HeaderType.go deleted file mode 100644 index f106b97..0000000 --- a/src/os/mac/macho/HeaderType.go +++ /dev/null @@ -1,12 +0,0 @@ -package macho - -type HeaderType uint32 - -const ( - TypeObject HeaderType = 0x1 - TypeExecute HeaderType = 0x2 - TypeCore HeaderType = 0x4 - TypeDylib HeaderType = 0x6 - TypeBundle HeaderType = 0x8 - TypeDsym HeaderType = 0xA -) diff --git a/src/os/mac/macho/MachO.go b/src/os/mac/macho/MachO.go index 41b47ce..806c96d 100644 --- a/src/os/mac/macho/MachO.go +++ b/src/os/mac/macho/MachO.go @@ -6,7 +6,7 @@ import ( "io" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/os/linux/elf" + "git.akyoto.dev/cli/q/src/os/common" ) // MachO is the executable format used on MacOS. @@ -55,7 +55,7 @@ func (m *MachO) Write(writer io.Writer) { codeStart := uint64(32 + m.Header.SizeCommands) codeLength := uint64(len(m.Code)) codeEnd := codeStart + codeLength - dataPadding := elf.Padding(codeEnd, 4096) + dataPadding := common.Padding(codeEnd, config.Align) dataStart := codeEnd + dataPadding binary.Write(writer, binary.LittleEndian, &Segment64{ diff --git a/src/os/mac/macho/Prot.go b/src/os/mac/macho/Prot.go deleted file mode 100644 index 562b624..0000000 --- a/src/os/mac/macho/Prot.go +++ /dev/null @@ -1,9 +0,0 @@ -package macho - -type Prot uint32 - -const ( - ProtReadable Prot = 0x1 - ProtWritable Prot = 0x2 - ProtExecutable Prot = 0x4 -) diff --git a/src/os/windows/pe/Constants.go b/src/os/windows/pe/Constants.go new file mode 100644 index 0000000..c77bea7 --- /dev/null +++ b/src/os/windows/pe/Constants.go @@ -0,0 +1,72 @@ +package pe + +// CPU +const ( + IMAGE_FILE_MACHINE_AMD64 = 0x8664 + IMAGE_FILE_MACHINE_ARM64 = 0xAA64 + IMAGE_FILE_MACHINE_RISCV64 = 0x5064 +) + +// Subsystems +const ( + IMAGE_SUBSYSTEM_UNKNOWN = 0 + IMAGE_SUBSYSTEM_NATIVE = 1 + IMAGE_SUBSYSTEM_WINDOWS_GUI = 2 + IMAGE_SUBSYSTEM_WINDOWS_CUI = 3 + IMAGE_SUBSYSTEM_OS2_CUI = 5 + IMAGE_SUBSYSTEM_POSIX_CUI = 7 + IMAGE_SUBSYSTEM_NATIVE_WINDOWS = 8 + IMAGE_SUBSYSTEM_WINDOWS_CE_GUI = 9 + IMAGE_SUBSYSTEM_EFI_APPLICATION = 10 + IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER = 11 + IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER = 12 + IMAGE_SUBSYSTEM_EFI_ROM = 13 + IMAGE_SUBSYSTEM_XBOX = 14 + IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION = 16 +) + +// Characteristics +const ( + IMAGE_FILE_RELOCS_STRIPPED = 0x0001 + IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002 + IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004 + IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008 + IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010 + IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020 + IMAGE_FILE_BYTES_REVERSED_LO = 0x0080 + IMAGE_FILE_32BIT_MACHINE = 0x0100 + IMAGE_FILE_DEBUG_STRIPPED = 0x0200 + IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400 + IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800 + IMAGE_FILE_SYSTEM = 0x1000 + IMAGE_FILE_DLL = 0x2000 + IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000 + IMAGE_FILE_BYTES_REVERSED_HI = 0x8000 +) + +// DLL characteristics +const ( + IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020 + IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040 + IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY = 0x0080 + IMAGE_DLLCHARACTERISTICS_NX_COMPAT = 0x0100 + IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200 + IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400 + IMAGE_DLLCHARACTERISTICS_NO_BIND = 0x0800 + IMAGE_DLLCHARACTERISTICS_APPCONTAINER = 0x1000 + IMAGE_DLLCHARACTERISTICS_WDM_DRIVER = 0x2000 + IMAGE_DLLCHARACTERISTICS_GUARD_CF = 0x4000 + IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000 +) + +// Section characteristics +const ( + IMAGE_SCN_CNT_CODE = 0x00000020 + IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040 + IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080 + IMAGE_SCN_LNK_COMDAT = 0x00001000 + IMAGE_SCN_MEM_DISCARDABLE = 0x02000000 + IMAGE_SCN_MEM_EXECUTE = 0x20000000 + IMAGE_SCN_MEM_READ = 0x40000000 + IMAGE_SCN_MEM_WRITE = 0x80000000 +) diff --git a/src/os/windows/pe/DOSHeader.go b/src/os/windows/pe/DOSHeader.go new file mode 100644 index 0000000..809d8fc --- /dev/null +++ b/src/os/windows/pe/DOSHeader.go @@ -0,0 +1,9 @@ +package pe + +// DOSHeader is at the beginning of each EXE file and nowadays just points +// to the NTHeader using an absolute file offset. +type DOSHeader struct { + Magic [4]byte + _ [56]byte + NTHeaderOffset uint32 +} diff --git a/src/os/windows/pe/DataDirectory.go b/src/os/windows/pe/DataDirectory.go new file mode 100644 index 0000000..8a435b8 --- /dev/null +++ b/src/os/windows/pe/DataDirectory.go @@ -0,0 +1,6 @@ +package pe + +type DataDirectory struct { + VirtualAddress uint32 + Size uint32 +} diff --git a/src/os/windows/pe/EXE.go b/src/os/windows/pe/EXE.go new file mode 100644 index 0000000..13aa5e5 --- /dev/null +++ b/src/os/windows/pe/EXE.go @@ -0,0 +1,105 @@ +package pe + +import ( + "encoding/binary" + "io" + + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/os/common" +) + +// EXE is the portable executable format used on Windows. +type EXE struct { + DOSHeader + NTHeader + OptionalHeader64 + CodeHeader SectionHeader + Code []byte + Data []byte +} + +// New creates a new EXE file. +func New(code []byte, data []byte) *EXE { + const codeStart = 0x170 + const optHeaderSize = 0xF0 + + codeSize := uint32(len(code)) + headerSize := uint32(codeStart) + sectionAlign := uint32(0x10) + fileAlign := uint32(0x10) + imageSize := uint32(codeStart + len(code)) + imageSize += common.Padding(imageSize, sectionAlign) + + return &EXE{ + DOSHeader: DOSHeader{ + Magic: [4]byte{'M', 'Z', 0, 0}, + NTHeaderOffset: 0x40, + }, + NTHeader: NTHeader{ + Signature: [4]byte{'P', 'E', 0, 0}, + Machine: IMAGE_FILE_MACHINE_AMD64, + NumberOfSections: 1, + SizeOfOptionalHeader: optHeaderSize, + Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE, + }, + OptionalHeader64: OptionalHeader64{ + Magic: 0x020B, // PE32+ executable + MajorLinkerVersion: 0x0E, + MinorLinkerVersion: 0x16, + SizeOfCode: codeSize, + AddressOfEntryPoint: codeStart, + ImageBase: config.BaseAddress, + SectionAlignment: sectionAlign, // power of 2, must be greater than or equal to FileAlignment + FileAlignment: fileAlign, // power of 2 + MajorOperatingSystemVersion: 0x06, + MajorSubsystemVersion: 0x06, + SizeOfImage: imageSize, + SizeOfHeaders: headerSize, + Subsystem: IMAGE_SUBSYSTEM_WINDOWS_GUI, + DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, + SizeOfStackReserve: 0x100000, + SizeOfStackCommit: 0x1000, + SizeOfHeapReserve: 0x100000, + SizeOfHeapCommit: 0x1000, + NumberOfRvaAndSizes: 16, + DataDirectory: [16]DataDirectory{ + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + {VirtualAddress: 0, Size: 0}, + }, + }, + CodeHeader: SectionHeader{ + Name: [8]byte{'.', 't', 'e', 'x', 't'}, + VirtualSize: uint32(len(code)), + VirtualAddress: codeStart, + RawSize: uint32(len(code)), // must be a multiple of FileAlignment + RawAddress: codeStart, // must be a multiple of FileAlignment + Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ, + }, + Code: code, + // Data: data, + } +} + +// Write writes the EXE file to the given writer. +func (pe *EXE) Write(writer io.Writer) { + binary.Write(writer, binary.LittleEndian, &pe.DOSHeader) + binary.Write(writer, binary.LittleEndian, &pe.NTHeader) + binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) + binary.Write(writer, binary.LittleEndian, &pe.CodeHeader) + binary.Write(writer, binary.LittleEndian, &pe.Code) + // binary.Write(writer, binary.LittleEndian, &pe.Data) +} diff --git a/src/os/windows/pe/NTHeader.go b/src/os/windows/pe/NTHeader.go new file mode 100644 index 0000000..f563c82 --- /dev/null +++ b/src/os/windows/pe/NTHeader.go @@ -0,0 +1,12 @@ +package pe + +type NTHeader struct { + Signature [4]byte + Machine uint16 + NumberOfSections uint16 + TimeDateStamp uint32 + PointerToSymbolTable uint32 + NumberOfSymbols uint32 + SizeOfOptionalHeader uint16 + Characteristics uint16 +} diff --git a/src/os/windows/pe/OptionalHeader64.go b/src/os/windows/pe/OptionalHeader64.go new file mode 100644 index 0000000..9a26a7e --- /dev/null +++ b/src/os/windows/pe/OptionalHeader64.go @@ -0,0 +1,34 @@ +package pe + +type OptionalHeader64 struct { + Magic uint16 + MajorLinkerVersion uint8 + MinorLinkerVersion uint8 + SizeOfCode uint32 + SizeOfInitializedData uint32 + SizeOfUninitializedData uint32 + AddressOfEntryPoint uint32 + BaseOfCode uint32 + ImageBase uint64 + SectionAlignment uint32 + FileAlignment uint32 + MajorOperatingSystemVersion uint16 + MinorOperatingSystemVersion uint16 + MajorImageVersion uint16 + MinorImageVersion uint16 + MajorSubsystemVersion uint16 + MinorSubsystemVersion uint16 + Win32VersionValue uint32 + SizeOfImage uint32 + SizeOfHeaders uint32 + CheckSum uint32 + Subsystem uint16 + DllCharacteristics uint16 + SizeOfStackReserve uint64 + SizeOfStackCommit uint64 + SizeOfHeapReserve uint64 + SizeOfHeapCommit uint64 + LoaderFlags uint32 + NumberOfRvaAndSizes uint32 + DataDirectory [16]DataDirectory +} diff --git a/src/os/windows/pe/SectionHeader.go b/src/os/windows/pe/SectionHeader.go new file mode 100644 index 0000000..8e95e85 --- /dev/null +++ b/src/os/windows/pe/SectionHeader.go @@ -0,0 +1,14 @@ +package pe + +type SectionHeader struct { + Name [8]byte + VirtualSize uint32 + VirtualAddress uint32 + RawSize uint32 + RawAddress uint32 + PointerToRelocations uint32 + PointerToLineNumbers uint32 + NumberOfRelocations uint16 + NumberOfLineNumbers uint16 + Characteristics uint32 +} From 33da0cc3150575d079b70bc2a7da0dd47dbb88fc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 13 Aug 2024 19:34:54 +0200 Subject: [PATCH 0509/1012] Improved Windows support --- lib/sys/sys_windows.q | 3 + src/asm/Finalize.go | 25 +++++- src/config/config.go | 8 +- src/os/linux/elf/Constants.go | 8 ++ src/os/linux/elf/ELF.go | 16 ++-- src/os/mac/macho/Constants.go | 8 ++ src/os/mac/macho/MachO.go | 10 +-- src/os/windows/pe/Constants.go | 8 ++ src/os/windows/pe/DOSHeader.go | 6 +- src/os/windows/pe/EXE.go | 84 ++++++++++++------- src/os/windows/pe/ImportDirectory.go | 9 ++ src/os/windows/pe/OptionalHeader64.go | 2 + .../windows/pe/{NTHeader.go => PEHeader.go} | 4 +- src/os/windows/pe/SectionHeader.go | 2 + src/scanner/queueDirectory.go | 4 + src/scanner/scanFile.go | 2 +- 16 files changed, 142 insertions(+), 57 deletions(-) create mode 100644 lib/sys/sys_windows.q create mode 100644 src/os/windows/pe/ImportDirectory.go rename src/os/windows/pe/{NTHeader.go => PEHeader.go} (83%) diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q new file mode 100644 index 0000000..dfab144 --- /dev/null +++ b/lib/sys/sys_windows.q @@ -0,0 +1,3 @@ +write(_ Int, _ Pointer, _ Int) -> Int { + return 0 +} \ No newline at end of file diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index c32b2fb..1457c40 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -9,6 +9,9 @@ import ( "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/os/common" + "git.akyoto.dev/cli/q/src/os/linux/elf" + "git.akyoto.dev/cli/q/src/os/mac/macho" + "git.akyoto.dev/cli/q/src/os/windows/pe" "git.akyoto.dev/cli/q/src/sizeof" ) @@ -337,8 +340,26 @@ restart: } data, dataLabels = a.Data.Finalize() - dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) - dataStart += int32(common.Padding(int64(dataStart), config.Align)) + + var ( + codeOffset Address + align Address + ) + + switch config.TargetOS { + case "linux": + codeOffset = elf.CodeOffset + align = elf.Align + case "mac": + codeOffset = macho.CodeOffset + align = macho.Align + case "windows": + codeOffset = pe.CodeOffset + align = pe.Align + } + + dataStart := Address(config.BaseAddress) + codeOffset + Address(len(code)) + dataStart += int32(common.Padding(dataStart, align)) for _, pointer := range dataPointers { address := dataStart + pointer.Resolve() diff --git a/src/config/config.go b/src/config/config.go index db3b7c1..555e379 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -8,12 +8,6 @@ const ( // The base address is the virtual address for our ELF file. BaseAddress = 0x40 * MinAddress - - // The code offset is the offset of the executable machine code within the file. - CodeOffset = 64 + 56 + 56 - - // Align decides the alignment of the sections and it must be a multiple of the page size. - Align = 0x1000 ) var ( @@ -45,3 +39,5 @@ func Reset() { TargetOS = "mac" } } + +// diff --git a/src/os/linux/elf/Constants.go b/src/os/linux/elf/Constants.go index bd4600f..6396c66 100644 --- a/src/os/linux/elf/Constants.go +++ b/src/os/linux/elf/Constants.go @@ -1,5 +1,13 @@ package elf +const ( + // Align decides the alignment of the sections and it must be a multiple of the page size. + Align = 0x1000 + + // The code offset is the offset of the executable machine code within the file. + CodeOffset = HeaderSize + ProgramHeaderSize*2 +) + const ( LittleEndian = 1 TypeExecutable = 2 diff --git a/src/os/linux/elf/ELF.go b/src/os/linux/elf/ELF.go index fbe2370..084b903 100644 --- a/src/os/linux/elf/ELF.go +++ b/src/os/linux/elf/ELF.go @@ -22,8 +22,8 @@ type ELF struct { // New creates a new ELF binary. func New(code []byte, data []byte) *ELF { - dataOffset := config.CodeOffset + int64(len(code)) - dataPadding := common.Padding(dataOffset, config.Align) + dataOffset := CodeOffset + int64(len(code)) + dataPadding := common.Padding(dataOffset, Align) dataOffset += dataPadding return &ELF{ @@ -37,7 +37,7 @@ func New(code []byte, data []byte) *ELF { Type: TypeExecutable, Architecture: ArchitectureAMD64, FileVersion: 1, - EntryPointInMemory: config.BaseAddress + config.CodeOffset, + EntryPointInMemory: config.BaseAddress + CodeOffset, ProgramHeaderOffset: HeaderSize, SectionHeaderOffset: 0, Flags: 0, @@ -51,12 +51,12 @@ func New(code []byte, data []byte) *ELF { CodeHeader: ProgramHeader{ Type: ProgramTypeLOAD, Flags: ProgramFlagsExecutable | ProgramFlagsReadable, - Offset: config.CodeOffset, - VirtualAddress: config.BaseAddress + config.CodeOffset, - PhysicalAddress: config.BaseAddress + config.CodeOffset, + Offset: CodeOffset, + VirtualAddress: config.BaseAddress + CodeOffset, + PhysicalAddress: config.BaseAddress + CodeOffset, SizeInFile: int64(len(code)), SizeInMemory: int64(len(code)), - Align: config.Align, + Align: Align, }, DataHeader: ProgramHeader{ Type: ProgramTypeLOAD, @@ -66,7 +66,7 @@ func New(code []byte, data []byte) *ELF { PhysicalAddress: config.BaseAddress + dataOffset, SizeInFile: int64(len(data)), SizeInMemory: int64(len(data)), - Align: config.Align, + Align: Align, }, CodePadding: nil, Code: code, diff --git a/src/os/mac/macho/Constants.go b/src/os/mac/macho/Constants.go index eb9e320..83c62d9 100644 --- a/src/os/mac/macho/Constants.go +++ b/src/os/mac/macho/Constants.go @@ -1,5 +1,13 @@ package macho +const ( + // Align decides the alignment of the sections and it must be a multiple of the page size. + Align = 0x1000 + + // The code offset is the offset of the executable machine code within the file. + CodeOffset = 32 + 0x48*3 + 184 +) + type CPU uint32 const ( diff --git a/src/os/mac/macho/MachO.go b/src/os/mac/macho/MachO.go index 806c96d..1bc1325 100644 --- a/src/os/mac/macho/MachO.go +++ b/src/os/mac/macho/MachO.go @@ -25,7 +25,7 @@ func New(code []byte, data []byte) *MachO { MicroArchitecture: 3 | 0x80000000, Type: TypeExecute, NumCommands: 4, - SizeCommands: 0x48*3 + 184, + SizeCommands: 72*3 + 184, Flags: FlagNoUndefs, Reserved: 0, }, @@ -40,7 +40,7 @@ func (m *MachO) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &Segment64{ LoadCommand: LcSegment64, - Length: 0x48, + Length: 72, Name: [16]byte{'_', '_', 'P', 'A', 'G', 'E', 'Z', 'E', 'R', 'O'}, Address: 0, SizeInMemory: config.BaseAddress, @@ -55,12 +55,12 @@ func (m *MachO) Write(writer io.Writer) { codeStart := uint64(32 + m.Header.SizeCommands) codeLength := uint64(len(m.Code)) codeEnd := codeStart + codeLength - dataPadding := common.Padding(codeEnd, config.Align) + dataPadding := common.Padding(codeEnd, Align) dataStart := codeEnd + dataPadding binary.Write(writer, binary.LittleEndian, &Segment64{ LoadCommand: LcSegment64, - Length: 0x48, + Length: 72, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, Address: config.BaseAddress + codeStart, SizeInMemory: codeLength, @@ -74,7 +74,7 @@ func (m *MachO) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &Segment64{ LoadCommand: LcSegment64, - Length: 0x48, + Length: 72, Name: [16]byte{'_', '_', 'D', 'A', 'T', 'A'}, Address: config.BaseAddress + dataStart, SizeInMemory: uint64(len(m.Data)), diff --git a/src/os/windows/pe/Constants.go b/src/os/windows/pe/Constants.go index c77bea7..4d38ad5 100644 --- a/src/os/windows/pe/Constants.go +++ b/src/os/windows/pe/Constants.go @@ -1,5 +1,13 @@ package pe +const ( + // Align decides the alignment of the sections. + Align = 0x200 + + // The code offset is the offset of the executable machine code within the file. + CodeOffset = Align +) + // CPU const ( IMAGE_FILE_MACHINE_AMD64 = 0x8664 diff --git a/src/os/windows/pe/DOSHeader.go b/src/os/windows/pe/DOSHeader.go index 809d8fc..d9a638b 100644 --- a/src/os/windows/pe/DOSHeader.go +++ b/src/os/windows/pe/DOSHeader.go @@ -1,9 +1,11 @@ package pe +const DOSHeaderSize = 64 + // DOSHeader is at the beginning of each EXE file and nowadays just points -// to the NTHeader using an absolute file offset. +// to the PEHeader using an absolute file offset. type DOSHeader struct { Magic [4]byte _ [56]byte - NTHeaderOffset uint32 + PEHeaderOffset uint32 } diff --git a/src/os/windows/pe/EXE.go b/src/os/windows/pe/EXE.go index 13aa5e5..3be2970 100644 --- a/src/os/windows/pe/EXE.go +++ b/src/os/windows/pe/EXE.go @@ -1,6 +1,7 @@ package pe import ( + "bytes" "encoding/binary" "io" @@ -8,53 +9,58 @@ import ( "git.akyoto.dev/cli/q/src/os/common" ) +const NumSections = 2 + // EXE is the portable executable format used on Windows. type EXE struct { DOSHeader - NTHeader + PEHeader OptionalHeader64 - CodeHeader SectionHeader - Code []byte - Data []byte + Sections [NumSections]SectionHeader + CodePadding []byte + Code []byte + DataPadding []byte + Data []byte } // New creates a new EXE file. func New(code []byte, data []byte) *EXE { - const codeStart = 0x170 - const optHeaderSize = 0xF0 + codeStart := uint32(DOSHeaderSize + PEHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections) + codePadding := common.Padding(codeStart, Align) + codeStart += codePadding - codeSize := uint32(len(code)) - headerSize := uint32(codeStart) - sectionAlign := uint32(0x10) - fileAlign := uint32(0x10) - imageSize := uint32(codeStart + len(code)) - imageSize += common.Padding(imageSize, sectionAlign) + dataStart := codeStart + uint32(len(code)) + dataPadding := common.Padding(dataStart, Align) + dataStart += dataPadding + + imageSize := uint32(dataStart + uint32(len(data))) + imageSize += common.Padding(imageSize, Align) return &EXE{ DOSHeader: DOSHeader{ Magic: [4]byte{'M', 'Z', 0, 0}, - NTHeaderOffset: 0x40, + PEHeaderOffset: 0x40, }, - NTHeader: NTHeader{ + PEHeader: PEHeader{ Signature: [4]byte{'P', 'E', 0, 0}, Machine: IMAGE_FILE_MACHINE_AMD64, - NumberOfSections: 1, - SizeOfOptionalHeader: optHeaderSize, + NumberOfSections: NumSections, + SizeOfOptionalHeader: OptionalHeader64Size, Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE, }, OptionalHeader64: OptionalHeader64{ Magic: 0x020B, // PE32+ executable MajorLinkerVersion: 0x0E, MinorLinkerVersion: 0x16, - SizeOfCode: codeSize, + SizeOfCode: uint32(len(code)), AddressOfEntryPoint: codeStart, ImageBase: config.BaseAddress, - SectionAlignment: sectionAlign, // power of 2, must be greater than or equal to FileAlignment - FileAlignment: fileAlign, // power of 2 + SectionAlignment: Align, // power of 2, must be greater than or equal to FileAlignment + FileAlignment: Align, // power of 2 MajorOperatingSystemVersion: 0x06, MajorSubsystemVersion: 0x06, SizeOfImage: imageSize, - SizeOfHeaders: headerSize, + SizeOfHeaders: codeStart, // section bodies begin here Subsystem: IMAGE_SUBSYSTEM_WINDOWS_GUI, DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, SizeOfStackReserve: 0x100000, @@ -81,25 +87,39 @@ func New(code []byte, data []byte) *EXE { {VirtualAddress: 0, Size: 0}, }, }, - CodeHeader: SectionHeader{ - Name: [8]byte{'.', 't', 'e', 'x', 't'}, - VirtualSize: uint32(len(code)), - VirtualAddress: codeStart, - RawSize: uint32(len(code)), // must be a multiple of FileAlignment - RawAddress: codeStart, // must be a multiple of FileAlignment - Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ, + Sections: [NumSections]SectionHeader{ + { + Name: [8]byte{'.', 'c', 'o', 'd', 'e'}, + VirtualSize: uint32(len(code)), + VirtualAddress: codeStart, + RawSize: uint32(len(code)), // must be a multiple of FileAlignment + RawAddress: codeStart, // must be a multiple of FileAlignment + Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ, + }, + { + Name: [8]byte{'.', 'd', 'a', 't', 'a'}, + VirtualSize: uint32(len(data)), + VirtualAddress: dataStart, + RawSize: uint32(len(data)), // must be a multiple of FileAlignment + RawAddress: dataStart, // must be a multiple of FileAlignment + Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, + }, }, - Code: code, - // Data: data, + CodePadding: bytes.Repeat([]byte{0}, int(codePadding)), + Code: code, + DataPadding: bytes.Repeat([]byte{0}, int(dataPadding)), + Data: data, } } // Write writes the EXE file to the given writer. func (pe *EXE) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &pe.DOSHeader) - binary.Write(writer, binary.LittleEndian, &pe.NTHeader) + binary.Write(writer, binary.LittleEndian, &pe.PEHeader) binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) - binary.Write(writer, binary.LittleEndian, &pe.CodeHeader) + binary.Write(writer, binary.LittleEndian, &pe.Sections) + binary.Write(writer, binary.LittleEndian, &pe.CodePadding) binary.Write(writer, binary.LittleEndian, &pe.Code) - // binary.Write(writer, binary.LittleEndian, &pe.Data) + binary.Write(writer, binary.LittleEndian, &pe.DataPadding) + binary.Write(writer, binary.LittleEndian, &pe.Data) } diff --git a/src/os/windows/pe/ImportDirectory.go b/src/os/windows/pe/ImportDirectory.go new file mode 100644 index 0000000..4857781 --- /dev/null +++ b/src/os/windows/pe/ImportDirectory.go @@ -0,0 +1,9 @@ +package pe + +type ImportDirectory struct { + OriginalFirstThunk uint32 + TimeDateStamp uint32 + ForwarderChain uint32 + Name uint32 + FirstThunk uint32 +} diff --git a/src/os/windows/pe/OptionalHeader64.go b/src/os/windows/pe/OptionalHeader64.go index 9a26a7e..a6ce912 100644 --- a/src/os/windows/pe/OptionalHeader64.go +++ b/src/os/windows/pe/OptionalHeader64.go @@ -1,5 +1,7 @@ package pe +const OptionalHeader64Size = 240 + type OptionalHeader64 struct { Magic uint16 MajorLinkerVersion uint8 diff --git a/src/os/windows/pe/NTHeader.go b/src/os/windows/pe/PEHeader.go similarity index 83% rename from src/os/windows/pe/NTHeader.go rename to src/os/windows/pe/PEHeader.go index f563c82..2de3439 100644 --- a/src/os/windows/pe/NTHeader.go +++ b/src/os/windows/pe/PEHeader.go @@ -1,6 +1,8 @@ package pe -type NTHeader struct { +const PEHeaderSize = 24 + +type PEHeader struct { Signature [4]byte Machine uint16 NumberOfSections uint16 diff --git a/src/os/windows/pe/SectionHeader.go b/src/os/windows/pe/SectionHeader.go index 8e95e85..2ab40e7 100644 --- a/src/os/windows/pe/SectionHeader.go +++ b/src/os/windows/pe/SectionHeader.go @@ -1,5 +1,7 @@ package pe +const SectionHeaderSize = 40 + type SectionHeader struct { Name [8]byte VirtualSize uint32 diff --git a/src/scanner/queueDirectory.go b/src/scanner/queueDirectory.go index f1abe54..cc49a20 100644 --- a/src/scanner/queueDirectory.go +++ b/src/scanner/queueDirectory.go @@ -29,6 +29,10 @@ func (s *Scanner) queueDirectory(directory string, pkg string) { return } + if strings.HasSuffix(name, "_windows.q") && config.TargetOS != "windows" { + return + } + fullPath := filepath.Join(directory, name) s.queueFile(fullPath, pkg) }) diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 1b9c795..4f7c6e1 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -249,7 +249,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { register := x64.InputRegisters[count] uses := token.Count(function.Body, contents, token.Identifier, name) - if uses == 0 { + if uses == 0 && name != "_" { return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) } From e818e5b907cb4d75a5f8f8a1c65909906649bc81 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 13 Aug 2024 19:34:54 +0200 Subject: [PATCH 0510/1012] Improved Windows support --- lib/sys/sys_windows.q | 3 + src/asm/Finalize.go | 25 +++++- src/config/config.go | 8 +- src/os/linux/elf/Constants.go | 8 ++ src/os/linux/elf/ELF.go | 16 ++-- src/os/mac/macho/Constants.go | 8 ++ src/os/mac/macho/MachO.go | 10 +-- src/os/windows/pe/Constants.go | 8 ++ src/os/windows/pe/DOSHeader.go | 6 +- src/os/windows/pe/EXE.go | 84 ++++++++++++------- src/os/windows/pe/ImportDirectory.go | 9 ++ src/os/windows/pe/OptionalHeader64.go | 2 + .../windows/pe/{NTHeader.go => PEHeader.go} | 4 +- src/os/windows/pe/SectionHeader.go | 2 + src/scanner/queueDirectory.go | 4 + src/scanner/scanFile.go | 2 +- 16 files changed, 142 insertions(+), 57 deletions(-) create mode 100644 lib/sys/sys_windows.q create mode 100644 src/os/windows/pe/ImportDirectory.go rename src/os/windows/pe/{NTHeader.go => PEHeader.go} (83%) diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q new file mode 100644 index 0000000..dfab144 --- /dev/null +++ b/lib/sys/sys_windows.q @@ -0,0 +1,3 @@ +write(_ Int, _ Pointer, _ Int) -> Int { + return 0 +} \ No newline at end of file diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index c32b2fb..1457c40 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -9,6 +9,9 @@ import ( "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/os/common" + "git.akyoto.dev/cli/q/src/os/linux/elf" + "git.akyoto.dev/cli/q/src/os/mac/macho" + "git.akyoto.dev/cli/q/src/os/windows/pe" "git.akyoto.dev/cli/q/src/sizeof" ) @@ -337,8 +340,26 @@ restart: } data, dataLabels = a.Data.Finalize() - dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) - dataStart += int32(common.Padding(int64(dataStart), config.Align)) + + var ( + codeOffset Address + align Address + ) + + switch config.TargetOS { + case "linux": + codeOffset = elf.CodeOffset + align = elf.Align + case "mac": + codeOffset = macho.CodeOffset + align = macho.Align + case "windows": + codeOffset = pe.CodeOffset + align = pe.Align + } + + dataStart := Address(config.BaseAddress) + codeOffset + Address(len(code)) + dataStart += int32(common.Padding(dataStart, align)) for _, pointer := range dataPointers { address := dataStart + pointer.Resolve() diff --git a/src/config/config.go b/src/config/config.go index db3b7c1..555e379 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -8,12 +8,6 @@ const ( // The base address is the virtual address for our ELF file. BaseAddress = 0x40 * MinAddress - - // The code offset is the offset of the executable machine code within the file. - CodeOffset = 64 + 56 + 56 - - // Align decides the alignment of the sections and it must be a multiple of the page size. - Align = 0x1000 ) var ( @@ -45,3 +39,5 @@ func Reset() { TargetOS = "mac" } } + +// diff --git a/src/os/linux/elf/Constants.go b/src/os/linux/elf/Constants.go index bd4600f..6396c66 100644 --- a/src/os/linux/elf/Constants.go +++ b/src/os/linux/elf/Constants.go @@ -1,5 +1,13 @@ package elf +const ( + // Align decides the alignment of the sections and it must be a multiple of the page size. + Align = 0x1000 + + // The code offset is the offset of the executable machine code within the file. + CodeOffset = HeaderSize + ProgramHeaderSize*2 +) + const ( LittleEndian = 1 TypeExecutable = 2 diff --git a/src/os/linux/elf/ELF.go b/src/os/linux/elf/ELF.go index fbe2370..084b903 100644 --- a/src/os/linux/elf/ELF.go +++ b/src/os/linux/elf/ELF.go @@ -22,8 +22,8 @@ type ELF struct { // New creates a new ELF binary. func New(code []byte, data []byte) *ELF { - dataOffset := config.CodeOffset + int64(len(code)) - dataPadding := common.Padding(dataOffset, config.Align) + dataOffset := CodeOffset + int64(len(code)) + dataPadding := common.Padding(dataOffset, Align) dataOffset += dataPadding return &ELF{ @@ -37,7 +37,7 @@ func New(code []byte, data []byte) *ELF { Type: TypeExecutable, Architecture: ArchitectureAMD64, FileVersion: 1, - EntryPointInMemory: config.BaseAddress + config.CodeOffset, + EntryPointInMemory: config.BaseAddress + CodeOffset, ProgramHeaderOffset: HeaderSize, SectionHeaderOffset: 0, Flags: 0, @@ -51,12 +51,12 @@ func New(code []byte, data []byte) *ELF { CodeHeader: ProgramHeader{ Type: ProgramTypeLOAD, Flags: ProgramFlagsExecutable | ProgramFlagsReadable, - Offset: config.CodeOffset, - VirtualAddress: config.BaseAddress + config.CodeOffset, - PhysicalAddress: config.BaseAddress + config.CodeOffset, + Offset: CodeOffset, + VirtualAddress: config.BaseAddress + CodeOffset, + PhysicalAddress: config.BaseAddress + CodeOffset, SizeInFile: int64(len(code)), SizeInMemory: int64(len(code)), - Align: config.Align, + Align: Align, }, DataHeader: ProgramHeader{ Type: ProgramTypeLOAD, @@ -66,7 +66,7 @@ func New(code []byte, data []byte) *ELF { PhysicalAddress: config.BaseAddress + dataOffset, SizeInFile: int64(len(data)), SizeInMemory: int64(len(data)), - Align: config.Align, + Align: Align, }, CodePadding: nil, Code: code, diff --git a/src/os/mac/macho/Constants.go b/src/os/mac/macho/Constants.go index eb9e320..83c62d9 100644 --- a/src/os/mac/macho/Constants.go +++ b/src/os/mac/macho/Constants.go @@ -1,5 +1,13 @@ package macho +const ( + // Align decides the alignment of the sections and it must be a multiple of the page size. + Align = 0x1000 + + // The code offset is the offset of the executable machine code within the file. + CodeOffset = 32 + 0x48*3 + 184 +) + type CPU uint32 const ( diff --git a/src/os/mac/macho/MachO.go b/src/os/mac/macho/MachO.go index 806c96d..1bc1325 100644 --- a/src/os/mac/macho/MachO.go +++ b/src/os/mac/macho/MachO.go @@ -25,7 +25,7 @@ func New(code []byte, data []byte) *MachO { MicroArchitecture: 3 | 0x80000000, Type: TypeExecute, NumCommands: 4, - SizeCommands: 0x48*3 + 184, + SizeCommands: 72*3 + 184, Flags: FlagNoUndefs, Reserved: 0, }, @@ -40,7 +40,7 @@ func (m *MachO) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &Segment64{ LoadCommand: LcSegment64, - Length: 0x48, + Length: 72, Name: [16]byte{'_', '_', 'P', 'A', 'G', 'E', 'Z', 'E', 'R', 'O'}, Address: 0, SizeInMemory: config.BaseAddress, @@ -55,12 +55,12 @@ func (m *MachO) Write(writer io.Writer) { codeStart := uint64(32 + m.Header.SizeCommands) codeLength := uint64(len(m.Code)) codeEnd := codeStart + codeLength - dataPadding := common.Padding(codeEnd, config.Align) + dataPadding := common.Padding(codeEnd, Align) dataStart := codeEnd + dataPadding binary.Write(writer, binary.LittleEndian, &Segment64{ LoadCommand: LcSegment64, - Length: 0x48, + Length: 72, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, Address: config.BaseAddress + codeStart, SizeInMemory: codeLength, @@ -74,7 +74,7 @@ func (m *MachO) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &Segment64{ LoadCommand: LcSegment64, - Length: 0x48, + Length: 72, Name: [16]byte{'_', '_', 'D', 'A', 'T', 'A'}, Address: config.BaseAddress + dataStart, SizeInMemory: uint64(len(m.Data)), diff --git a/src/os/windows/pe/Constants.go b/src/os/windows/pe/Constants.go index c77bea7..4d38ad5 100644 --- a/src/os/windows/pe/Constants.go +++ b/src/os/windows/pe/Constants.go @@ -1,5 +1,13 @@ package pe +const ( + // Align decides the alignment of the sections. + Align = 0x200 + + // The code offset is the offset of the executable machine code within the file. + CodeOffset = Align +) + // CPU const ( IMAGE_FILE_MACHINE_AMD64 = 0x8664 diff --git a/src/os/windows/pe/DOSHeader.go b/src/os/windows/pe/DOSHeader.go index 809d8fc..d9a638b 100644 --- a/src/os/windows/pe/DOSHeader.go +++ b/src/os/windows/pe/DOSHeader.go @@ -1,9 +1,11 @@ package pe +const DOSHeaderSize = 64 + // DOSHeader is at the beginning of each EXE file and nowadays just points -// to the NTHeader using an absolute file offset. +// to the PEHeader using an absolute file offset. type DOSHeader struct { Magic [4]byte _ [56]byte - NTHeaderOffset uint32 + PEHeaderOffset uint32 } diff --git a/src/os/windows/pe/EXE.go b/src/os/windows/pe/EXE.go index 13aa5e5..3be2970 100644 --- a/src/os/windows/pe/EXE.go +++ b/src/os/windows/pe/EXE.go @@ -1,6 +1,7 @@ package pe import ( + "bytes" "encoding/binary" "io" @@ -8,53 +9,58 @@ import ( "git.akyoto.dev/cli/q/src/os/common" ) +const NumSections = 2 + // EXE is the portable executable format used on Windows. type EXE struct { DOSHeader - NTHeader + PEHeader OptionalHeader64 - CodeHeader SectionHeader - Code []byte - Data []byte + Sections [NumSections]SectionHeader + CodePadding []byte + Code []byte + DataPadding []byte + Data []byte } // New creates a new EXE file. func New(code []byte, data []byte) *EXE { - const codeStart = 0x170 - const optHeaderSize = 0xF0 + codeStart := uint32(DOSHeaderSize + PEHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections) + codePadding := common.Padding(codeStart, Align) + codeStart += codePadding - codeSize := uint32(len(code)) - headerSize := uint32(codeStart) - sectionAlign := uint32(0x10) - fileAlign := uint32(0x10) - imageSize := uint32(codeStart + len(code)) - imageSize += common.Padding(imageSize, sectionAlign) + dataStart := codeStart + uint32(len(code)) + dataPadding := common.Padding(dataStart, Align) + dataStart += dataPadding + + imageSize := uint32(dataStart + uint32(len(data))) + imageSize += common.Padding(imageSize, Align) return &EXE{ DOSHeader: DOSHeader{ Magic: [4]byte{'M', 'Z', 0, 0}, - NTHeaderOffset: 0x40, + PEHeaderOffset: 0x40, }, - NTHeader: NTHeader{ + PEHeader: PEHeader{ Signature: [4]byte{'P', 'E', 0, 0}, Machine: IMAGE_FILE_MACHINE_AMD64, - NumberOfSections: 1, - SizeOfOptionalHeader: optHeaderSize, + NumberOfSections: NumSections, + SizeOfOptionalHeader: OptionalHeader64Size, Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE, }, OptionalHeader64: OptionalHeader64{ Magic: 0x020B, // PE32+ executable MajorLinkerVersion: 0x0E, MinorLinkerVersion: 0x16, - SizeOfCode: codeSize, + SizeOfCode: uint32(len(code)), AddressOfEntryPoint: codeStart, ImageBase: config.BaseAddress, - SectionAlignment: sectionAlign, // power of 2, must be greater than or equal to FileAlignment - FileAlignment: fileAlign, // power of 2 + SectionAlignment: Align, // power of 2, must be greater than or equal to FileAlignment + FileAlignment: Align, // power of 2 MajorOperatingSystemVersion: 0x06, MajorSubsystemVersion: 0x06, SizeOfImage: imageSize, - SizeOfHeaders: headerSize, + SizeOfHeaders: codeStart, // section bodies begin here Subsystem: IMAGE_SUBSYSTEM_WINDOWS_GUI, DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, SizeOfStackReserve: 0x100000, @@ -81,25 +87,39 @@ func New(code []byte, data []byte) *EXE { {VirtualAddress: 0, Size: 0}, }, }, - CodeHeader: SectionHeader{ - Name: [8]byte{'.', 't', 'e', 'x', 't'}, - VirtualSize: uint32(len(code)), - VirtualAddress: codeStart, - RawSize: uint32(len(code)), // must be a multiple of FileAlignment - RawAddress: codeStart, // must be a multiple of FileAlignment - Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ, + Sections: [NumSections]SectionHeader{ + { + Name: [8]byte{'.', 'c', 'o', 'd', 'e'}, + VirtualSize: uint32(len(code)), + VirtualAddress: codeStart, + RawSize: uint32(len(code)), // must be a multiple of FileAlignment + RawAddress: codeStart, // must be a multiple of FileAlignment + Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ, + }, + { + Name: [8]byte{'.', 'd', 'a', 't', 'a'}, + VirtualSize: uint32(len(data)), + VirtualAddress: dataStart, + RawSize: uint32(len(data)), // must be a multiple of FileAlignment + RawAddress: dataStart, // must be a multiple of FileAlignment + Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, + }, }, - Code: code, - // Data: data, + CodePadding: bytes.Repeat([]byte{0}, int(codePadding)), + Code: code, + DataPadding: bytes.Repeat([]byte{0}, int(dataPadding)), + Data: data, } } // Write writes the EXE file to the given writer. func (pe *EXE) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &pe.DOSHeader) - binary.Write(writer, binary.LittleEndian, &pe.NTHeader) + binary.Write(writer, binary.LittleEndian, &pe.PEHeader) binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) - binary.Write(writer, binary.LittleEndian, &pe.CodeHeader) + binary.Write(writer, binary.LittleEndian, &pe.Sections) + binary.Write(writer, binary.LittleEndian, &pe.CodePadding) binary.Write(writer, binary.LittleEndian, &pe.Code) - // binary.Write(writer, binary.LittleEndian, &pe.Data) + binary.Write(writer, binary.LittleEndian, &pe.DataPadding) + binary.Write(writer, binary.LittleEndian, &pe.Data) } diff --git a/src/os/windows/pe/ImportDirectory.go b/src/os/windows/pe/ImportDirectory.go new file mode 100644 index 0000000..4857781 --- /dev/null +++ b/src/os/windows/pe/ImportDirectory.go @@ -0,0 +1,9 @@ +package pe + +type ImportDirectory struct { + OriginalFirstThunk uint32 + TimeDateStamp uint32 + ForwarderChain uint32 + Name uint32 + FirstThunk uint32 +} diff --git a/src/os/windows/pe/OptionalHeader64.go b/src/os/windows/pe/OptionalHeader64.go index 9a26a7e..a6ce912 100644 --- a/src/os/windows/pe/OptionalHeader64.go +++ b/src/os/windows/pe/OptionalHeader64.go @@ -1,5 +1,7 @@ package pe +const OptionalHeader64Size = 240 + type OptionalHeader64 struct { Magic uint16 MajorLinkerVersion uint8 diff --git a/src/os/windows/pe/NTHeader.go b/src/os/windows/pe/PEHeader.go similarity index 83% rename from src/os/windows/pe/NTHeader.go rename to src/os/windows/pe/PEHeader.go index f563c82..2de3439 100644 --- a/src/os/windows/pe/NTHeader.go +++ b/src/os/windows/pe/PEHeader.go @@ -1,6 +1,8 @@ package pe -type NTHeader struct { +const PEHeaderSize = 24 + +type PEHeader struct { Signature [4]byte Machine uint16 NumberOfSections uint16 diff --git a/src/os/windows/pe/SectionHeader.go b/src/os/windows/pe/SectionHeader.go index 8e95e85..2ab40e7 100644 --- a/src/os/windows/pe/SectionHeader.go +++ b/src/os/windows/pe/SectionHeader.go @@ -1,5 +1,7 @@ package pe +const SectionHeaderSize = 40 + type SectionHeader struct { Name [8]byte VirtualSize uint32 diff --git a/src/scanner/queueDirectory.go b/src/scanner/queueDirectory.go index f1abe54..cc49a20 100644 --- a/src/scanner/queueDirectory.go +++ b/src/scanner/queueDirectory.go @@ -29,6 +29,10 @@ func (s *Scanner) queueDirectory(directory string, pkg string) { return } + if strings.HasSuffix(name, "_windows.q") && config.TargetOS != "windows" { + return + } + fullPath := filepath.Join(directory, name) s.queueFile(fullPath, pkg) }) diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 1b9c795..4f7c6e1 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -249,7 +249,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { register := x64.InputRegisters[count] uses := token.Count(function.Body, contents, token.Identifier, name) - if uses == 0 { + if uses == 0 && name != "_" { return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) } From 124a48d2c8bc1546c5a8d1090f92d9d911286d37 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 13 Aug 2024 19:56:25 +0200 Subject: [PATCH 0511/1012] Improved Windows support --- src/fs/Walk.go | 2 ++ src/fs/Walk_windows.go | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 src/fs/Walk_windows.go diff --git a/src/fs/Walk.go b/src/fs/Walk.go index ce22ac5..0a169ab 100644 --- a/src/fs/Walk.go +++ b/src/fs/Walk.go @@ -1,3 +1,5 @@ +//go:build linux || darwin + package fs import ( diff --git a/src/fs/Walk_windows.go b/src/fs/Walk_windows.go new file mode 100644 index 0000000..37e284c --- /dev/null +++ b/src/fs/Walk_windows.go @@ -0,0 +1,22 @@ +//go:build windows + +package fs + +import ( + "io/fs" + "path/filepath" + "strings" +) + +// Walk calls your callback function for every file name inside the directory. +// It doesn't distinguish between files and directories. +func Walk(directory string, callBack func(string)) error { + return filepath.WalkDir(directory, func(path string, d fs.DirEntry, err error) error { + if strings.HasPrefix(d.Name(), ".") { + return filepath.SkipDir + } + + callBack(path) + return nil + }) +} From aedd6bf6a1c749dffadd7657c078e11e2efc0662 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 13 Aug 2024 19:56:25 +0200 Subject: [PATCH 0512/1012] Improved Windows support --- src/fs/Walk.go | 2 ++ src/fs/Walk_windows.go | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 src/fs/Walk_windows.go diff --git a/src/fs/Walk.go b/src/fs/Walk.go index ce22ac5..0a169ab 100644 --- a/src/fs/Walk.go +++ b/src/fs/Walk.go @@ -1,3 +1,5 @@ +//go:build linux || darwin + package fs import ( diff --git a/src/fs/Walk_windows.go b/src/fs/Walk_windows.go new file mode 100644 index 0000000..37e284c --- /dev/null +++ b/src/fs/Walk_windows.go @@ -0,0 +1,22 @@ +//go:build windows + +package fs + +import ( + "io/fs" + "path/filepath" + "strings" +) + +// Walk calls your callback function for every file name inside the directory. +// It doesn't distinguish between files and directories. +func Walk(directory string, callBack func(string)) error { + return filepath.WalkDir(directory, func(path string, d fs.DirEntry, err error) error { + if strings.HasPrefix(d.Name(), ".") { + return filepath.SkipDir + } + + callBack(path) + return nil + }) +} From 515fc5ce6e9d9b59976fba3e986f438199ec256e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 14 Aug 2024 12:25:35 +0200 Subject: [PATCH 0513/1012] Updated Go version --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index b143677..82dd598 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module git.akyoto.dev/cli/q -go 1.22.6 +go 1.23 require ( git.akyoto.dev/go/assert v0.1.3 From a2effb7db7ed1dc7b0163fe6fb3d3ea99d902fd7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 14 Aug 2024 12:25:35 +0200 Subject: [PATCH 0514/1012] Updated Go version --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index b143677..82dd598 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module git.akyoto.dev/cli/q -go 1.22.6 +go 1.23 require ( git.akyoto.dev/go/assert v0.1.3 From 7eff85cb2e524fe9b752d4c149081413753c1019 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 14 Aug 2024 12:27:01 +0200 Subject: [PATCH 0515/1012] Fixed incorrect paths on Windows --- src/fs/Walk_windows.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/fs/Walk_windows.go b/src/fs/Walk_windows.go index 37e284c..ab0b771 100644 --- a/src/fs/Walk_windows.go +++ b/src/fs/Walk_windows.go @@ -12,11 +12,13 @@ import ( // It doesn't distinguish between files and directories. func Walk(directory string, callBack func(string)) error { return filepath.WalkDir(directory, func(path string, d fs.DirEntry, err error) error { - if strings.HasPrefix(d.Name(), ".") { + fileName := d.Name() + + if strings.HasPrefix(fileName, ".") { return filepath.SkipDir } - callBack(path) + callBack(fileName) return nil }) } From 9d0077d1a4ec1fa5fffd44b18037e7a606e49a1b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 14 Aug 2024 12:27:01 +0200 Subject: [PATCH 0516/1012] Fixed incorrect paths on Windows --- src/fs/Walk_windows.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/fs/Walk_windows.go b/src/fs/Walk_windows.go index 37e284c..ab0b771 100644 --- a/src/fs/Walk_windows.go +++ b/src/fs/Walk_windows.go @@ -12,11 +12,13 @@ import ( // It doesn't distinguish between files and directories. func Walk(directory string, callBack func(string)) error { return filepath.WalkDir(directory, func(path string, d fs.DirEntry, err error) error { - if strings.HasPrefix(d.Name(), ".") { + fileName := d.Name() + + if strings.HasPrefix(fileName, ".") { return filepath.SkipDir } - callBack(path) + callBack(fileName) return nil }) } From 074ce259978bc17acee514033ffa673a44e6ffc0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 14 Aug 2024 13:41:22 +0200 Subject: [PATCH 0517/1012] Improved Windows support --- src/build/Build.go | 11 +++++++++-- tests/examples_test.go | 4 ++-- tests/programs_test.go | 13 +++++++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/build/Build.go b/src/build/Build.go index 2e740c6..cb221a4 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -5,6 +5,7 @@ import ( "strings" "git.akyoto.dev/cli/q/src/compiler" + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/scanner" ) @@ -31,10 +32,16 @@ func (build *Build) Executable() string { path, _ := filepath.Abs(build.Files[0]) if strings.HasSuffix(path, ".q") { - return fromFileName(path) + path = fromFileName(path) } else { - return fromDirectoryName(path) + path = fromDirectoryName(path) } + + if config.TargetOS == "windows" { + path += ".exe" + } + + return path } // fromDirectoryName returns the executable path based on the directory name. diff --git a/tests/examples_test.go b/tests/examples_test.go index 63dd7f3..bb9e4fe 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -33,12 +33,12 @@ func TestExamples(t *testing.T) { t.Run(test.Name+"/debug", func(t *testing.T) { config.ConstantFold = false - run(t, directory, "debug", test.Name, test.Input, test.Output, test.ExitCode) + run(t, directory, "debug", test.Input, test.Output, test.ExitCode) }) t.Run(test.Name+"/release", func(t *testing.T) { config.ConstantFold = true - run(t, directory, "release", test.Name, test.Input, test.Output, test.ExitCode) + run(t, directory, "release", test.Input, test.Output, test.ExitCode) }) } } diff --git a/tests/programs_test.go b/tests/programs_test.go index a6b117f..986cf81 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -66,12 +66,12 @@ func TestPrograms(t *testing.T) { t.Run(test.Name+"/debug", func(t *testing.T) { config.ConstantFold = false - run(t, file, "debug", test.Name, test.Input, test.Output, test.ExitCode) + run(t, file, "debug", test.Input, test.Output, test.ExitCode) }) t.Run(test.Name+"/release", func(t *testing.T) { config.ConstantFold = true - run(t, file, "release", test.Name, test.Input, test.Output, test.ExitCode) + run(t, file, "release", test.Input, test.Output, test.ExitCode) }) } } @@ -90,16 +90,17 @@ func BenchmarkPrograms(b *testing.B) { } // run builds and runs the file to check if the output matches the expected output. -func run(t *testing.T, path string, version string, name string, input string, expectedOutput string, expectedExitCode int) { +func run(t *testing.T, path string, version string, input string, expectedOutput string, expectedExitCode int) { b := build.New(path) result, err := b.Run() assert.Nil(t, err) - directory := filepath.Join(os.TempDir(), "q", version) - err = os.MkdirAll(directory, 0755) + tmpDir := filepath.Join(os.TempDir(), "q", version) + err = os.MkdirAll(tmpDir, 0755) assert.Nil(t, err) - executable := filepath.Join(directory, name) + executable := b.Executable() + executable = filepath.Join(tmpDir, filepath.Base(executable)) err = result.WriteFile(executable) assert.Nil(t, err) From 235188e457262b2f3471cd2fbf29a7c03920e1db Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 14 Aug 2024 13:41:22 +0200 Subject: [PATCH 0518/1012] Improved Windows support --- src/build/Build.go | 11 +++++++++-- tests/examples_test.go | 4 ++-- tests/programs_test.go | 13 +++++++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/build/Build.go b/src/build/Build.go index 2e740c6..cb221a4 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -5,6 +5,7 @@ import ( "strings" "git.akyoto.dev/cli/q/src/compiler" + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/scanner" ) @@ -31,10 +32,16 @@ func (build *Build) Executable() string { path, _ := filepath.Abs(build.Files[0]) if strings.HasSuffix(path, ".q") { - return fromFileName(path) + path = fromFileName(path) } else { - return fromDirectoryName(path) + path = fromDirectoryName(path) } + + if config.TargetOS == "windows" { + path += ".exe" + } + + return path } // fromDirectoryName returns the executable path based on the directory name. diff --git a/tests/examples_test.go b/tests/examples_test.go index 63dd7f3..bb9e4fe 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -33,12 +33,12 @@ func TestExamples(t *testing.T) { t.Run(test.Name+"/debug", func(t *testing.T) { config.ConstantFold = false - run(t, directory, "debug", test.Name, test.Input, test.Output, test.ExitCode) + run(t, directory, "debug", test.Input, test.Output, test.ExitCode) }) t.Run(test.Name+"/release", func(t *testing.T) { config.ConstantFold = true - run(t, directory, "release", test.Name, test.Input, test.Output, test.ExitCode) + run(t, directory, "release", test.Input, test.Output, test.ExitCode) }) } } diff --git a/tests/programs_test.go b/tests/programs_test.go index a6b117f..986cf81 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -66,12 +66,12 @@ func TestPrograms(t *testing.T) { t.Run(test.Name+"/debug", func(t *testing.T) { config.ConstantFold = false - run(t, file, "debug", test.Name, test.Input, test.Output, test.ExitCode) + run(t, file, "debug", test.Input, test.Output, test.ExitCode) }) t.Run(test.Name+"/release", func(t *testing.T) { config.ConstantFold = true - run(t, file, "release", test.Name, test.Input, test.Output, test.ExitCode) + run(t, file, "release", test.Input, test.Output, test.ExitCode) }) } } @@ -90,16 +90,17 @@ func BenchmarkPrograms(b *testing.B) { } // run builds and runs the file to check if the output matches the expected output. -func run(t *testing.T, path string, version string, name string, input string, expectedOutput string, expectedExitCode int) { +func run(t *testing.T, path string, version string, input string, expectedOutput string, expectedExitCode int) { b := build.New(path) result, err := b.Run() assert.Nil(t, err) - directory := filepath.Join(os.TempDir(), "q", version) - err = os.MkdirAll(directory, 0755) + tmpDir := filepath.Join(os.TempDir(), "q", version) + err = os.MkdirAll(tmpDir, 0755) assert.Nil(t, err) - executable := filepath.Join(directory, name) + executable := b.Executable() + executable = filepath.Join(tmpDir, filepath.Base(executable)) err = result.WriteFile(executable) assert.Nil(t, err) From a80207d105030942f1cb4e430a95165e25d5d79b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 14 Aug 2024 17:49:07 +0200 Subject: [PATCH 0519/1012] Improved security --- src/asm/Finalize.go | 24 ++---------------- src/config/config.go | 6 +++++ src/os/common/Padding.go | 2 +- src/os/linux/elf/Constants.go | 8 ------ src/os/linux/elf/ELF.go | 19 +++++++------- src/os/mac/macho/Constants.go | 8 ------ src/os/mac/macho/MachO.go | 13 +++++----- src/os/windows/pe/Constants.go | 8 ------ src/os/windows/pe/DOSHeader.go | 2 +- src/os/windows/pe/EXE.go | 25 ++++++++++--------- .../windows/pe/{PEHeader.go => NTHeader.go} | 4 +-- 11 files changed, 42 insertions(+), 77 deletions(-) rename src/os/windows/pe/{PEHeader.go => NTHeader.go} (84%) diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 1457c40..e0e0c87 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -9,9 +9,6 @@ import ( "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/os/common" - "git.akyoto.dev/cli/q/src/os/linux/elf" - "git.akyoto.dev/cli/q/src/os/mac/macho" - "git.akyoto.dev/cli/q/src/os/windows/pe" "git.akyoto.dev/cli/q/src/sizeof" ) @@ -341,25 +338,8 @@ restart: data, dataLabels = a.Data.Finalize() - var ( - codeOffset Address - align Address - ) - - switch config.TargetOS { - case "linux": - codeOffset = elf.CodeOffset - align = elf.Align - case "mac": - codeOffset = macho.CodeOffset - align = macho.Align - case "windows": - codeOffset = pe.CodeOffset - align = pe.Align - } - - dataStart := Address(config.BaseAddress) + codeOffset + Address(len(code)) - dataStart += int32(common.Padding(dataStart, align)) + dataStart := Address(config.BaseAddress) + config.CodeOffset + Address(len(code)) + dataStart += int32(common.Padding(dataStart, config.Align)) for _, pointer := range dataPointers { address := dataStart + pointer.Resolve() diff --git a/src/config/config.go b/src/config/config.go index 555e379..79f7d58 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -8,6 +8,12 @@ const ( // The base address is the virtual address for our ELF file. BaseAddress = 0x40 * MinAddress + + // Align decides the alignment of the sections and it must be a multiple of the page size. + Align = 0x1000 + + // The code offset is the offset of the executable machine code within the file. + CodeOffset = Align ) var ( diff --git a/src/os/common/Padding.go b/src/os/common/Padding.go index da40c13..4f2d726 100644 --- a/src/os/common/Padding.go +++ b/src/os/common/Padding.go @@ -1,6 +1,6 @@ package common // Padding calculates the padding needed to align `n` bytes with the specified alignment. -func Padding[T int64 | uint64 | int32 | uint32](n T, align T) T { +func Padding[T int | uint | int64 | uint64 | int32 | uint32](n T, align T) T { return align - (n % align) } diff --git a/src/os/linux/elf/Constants.go b/src/os/linux/elf/Constants.go index 6396c66..bd4600f 100644 --- a/src/os/linux/elf/Constants.go +++ b/src/os/linux/elf/Constants.go @@ -1,13 +1,5 @@ package elf -const ( - // Align decides the alignment of the sections and it must be a multiple of the page size. - Align = 0x1000 - - // The code offset is the offset of the executable machine code within the file. - CodeOffset = HeaderSize + ProgramHeaderSize*2 -) - const ( LittleEndian = 1 TypeExecutable = 2 diff --git a/src/os/linux/elf/ELF.go b/src/os/linux/elf/ELF.go index 084b903..87818dc 100644 --- a/src/os/linux/elf/ELF.go +++ b/src/os/linux/elf/ELF.go @@ -22,8 +22,9 @@ type ELF struct { // New creates a new ELF binary. func New(code []byte, data []byte) *ELF { - dataOffset := CodeOffset + int64(len(code)) - dataPadding := common.Padding(dataOffset, Align) + codePadding := common.Padding(HeaderSize+ProgramHeaderSize*2, config.Align) + dataOffset := config.CodeOffset + int64(len(code)) + dataPadding := common.Padding(dataOffset, config.Align) dataOffset += dataPadding return &ELF{ @@ -37,7 +38,7 @@ func New(code []byte, data []byte) *ELF { Type: TypeExecutable, Architecture: ArchitectureAMD64, FileVersion: 1, - EntryPointInMemory: config.BaseAddress + CodeOffset, + EntryPointInMemory: config.BaseAddress + config.CodeOffset, ProgramHeaderOffset: HeaderSize, SectionHeaderOffset: 0, Flags: 0, @@ -51,12 +52,12 @@ func New(code []byte, data []byte) *ELF { CodeHeader: ProgramHeader{ Type: ProgramTypeLOAD, Flags: ProgramFlagsExecutable | ProgramFlagsReadable, - Offset: CodeOffset, - VirtualAddress: config.BaseAddress + CodeOffset, - PhysicalAddress: config.BaseAddress + CodeOffset, + Offset: config.CodeOffset, + VirtualAddress: config.BaseAddress + config.CodeOffset, + PhysicalAddress: config.BaseAddress + config.CodeOffset, SizeInFile: int64(len(code)), SizeInMemory: int64(len(code)), - Align: Align, + Align: config.Align, }, DataHeader: ProgramHeader{ Type: ProgramTypeLOAD, @@ -66,9 +67,9 @@ func New(code []byte, data []byte) *ELF { PhysicalAddress: config.BaseAddress + dataOffset, SizeInFile: int64(len(data)), SizeInMemory: int64(len(data)), - Align: Align, + Align: config.Align, }, - CodePadding: nil, + CodePadding: bytes.Repeat([]byte{0}, int(codePadding)), Code: code, DataPadding: bytes.Repeat([]byte{0}, int(dataPadding)), Data: data, diff --git a/src/os/mac/macho/Constants.go b/src/os/mac/macho/Constants.go index 83c62d9..eb9e320 100644 --- a/src/os/mac/macho/Constants.go +++ b/src/os/mac/macho/Constants.go @@ -1,13 +1,5 @@ package macho -const ( - // Align decides the alignment of the sections and it must be a multiple of the page size. - Align = 0x1000 - - // The code offset is the offset of the executable machine code within the file. - CodeOffset = 32 + 0x48*3 + 184 -) - type CPU uint32 const ( diff --git a/src/os/mac/macho/MachO.go b/src/os/mac/macho/MachO.go index 1bc1325..449c11b 100644 --- a/src/os/mac/macho/MachO.go +++ b/src/os/mac/macho/MachO.go @@ -52,19 +52,19 @@ func (m *MachO) Write(writer io.Writer) { InitProt: 0, }) - codeStart := uint64(32 + m.Header.SizeCommands) + codePadding := common.Padding(32+m.Header.SizeCommands, config.Align) codeLength := uint64(len(m.Code)) - codeEnd := codeStart + codeLength - dataPadding := common.Padding(codeEnd, Align) + codeEnd := config.CodeOffset + codeLength + dataPadding := common.Padding(codeEnd, config.Align) dataStart := codeEnd + dataPadding binary.Write(writer, binary.LittleEndian, &Segment64{ LoadCommand: LcSegment64, Length: 72, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, - Address: config.BaseAddress + codeStart, + Address: config.BaseAddress + config.CodeOffset, SizeInMemory: codeLength, - Offset: 0, + Offset: config.CodeOffset, SizeInFile: codeLength, NumSections: 0, Flag: 0, @@ -110,13 +110,14 @@ func (m *MachO) Write(writer io.Writer) { 0, 0, 0, 0, 0, 0, - config.BaseAddress + uint32(codeStart), 0, + config.BaseAddress + config.CodeOffset, 0, 0, 0, 0, 0, 0, 0, 0, 0, }) + writer.Write(bytes.Repeat([]byte{0}, int(codePadding))) writer.Write(m.Code) writer.Write(bytes.Repeat([]byte{0}, int(dataPadding))) writer.Write(m.Data) diff --git a/src/os/windows/pe/Constants.go b/src/os/windows/pe/Constants.go index 4d38ad5..c77bea7 100644 --- a/src/os/windows/pe/Constants.go +++ b/src/os/windows/pe/Constants.go @@ -1,13 +1,5 @@ package pe -const ( - // Align decides the alignment of the sections. - Align = 0x200 - - // The code offset is the offset of the executable machine code within the file. - CodeOffset = Align -) - // CPU const ( IMAGE_FILE_MACHINE_AMD64 = 0x8664 diff --git a/src/os/windows/pe/DOSHeader.go b/src/os/windows/pe/DOSHeader.go index d9a638b..eabb2f9 100644 --- a/src/os/windows/pe/DOSHeader.go +++ b/src/os/windows/pe/DOSHeader.go @@ -7,5 +7,5 @@ const DOSHeaderSize = 64 type DOSHeader struct { Magic [4]byte _ [56]byte - PEHeaderOffset uint32 + NTHeaderOffset uint32 } diff --git a/src/os/windows/pe/EXE.go b/src/os/windows/pe/EXE.go index 3be2970..f1d5ff3 100644 --- a/src/os/windows/pe/EXE.go +++ b/src/os/windows/pe/EXE.go @@ -14,7 +14,7 @@ const NumSections = 2 // EXE is the portable executable format used on Windows. type EXE struct { DOSHeader - PEHeader + NTHeader OptionalHeader64 Sections [NumSections]SectionHeader CodePadding []byte @@ -25,23 +25,23 @@ type EXE struct { // New creates a new EXE file. func New(code []byte, data []byte) *EXE { - codeStart := uint32(DOSHeaderSize + PEHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections) - codePadding := common.Padding(codeStart, Align) + codeStart := uint32(DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections) + codePadding := common.Padding(codeStart, config.Align) codeStart += codePadding dataStart := codeStart + uint32(len(code)) - dataPadding := common.Padding(dataStart, Align) + dataPadding := common.Padding(dataStart, config.Align) dataStart += dataPadding imageSize := uint32(dataStart + uint32(len(data))) - imageSize += common.Padding(imageSize, Align) + imageSize += common.Padding(imageSize, config.Align) return &EXE{ DOSHeader: DOSHeader{ Magic: [4]byte{'M', 'Z', 0, 0}, - PEHeaderOffset: 0x40, + NTHeaderOffset: 0x40, }, - PEHeader: PEHeader{ + NTHeader: NTHeader{ Signature: [4]byte{'P', 'E', 0, 0}, Machine: IMAGE_FILE_MACHINE_AMD64, NumberOfSections: NumSections, @@ -54,9 +54,10 @@ func New(code []byte, data []byte) *EXE { MinorLinkerVersion: 0x16, SizeOfCode: uint32(len(code)), AddressOfEntryPoint: codeStart, + BaseOfCode: codeStart, ImageBase: config.BaseAddress, - SectionAlignment: Align, // power of 2, must be greater than or equal to FileAlignment - FileAlignment: Align, // power of 2 + SectionAlignment: config.Align, // power of 2, must be greater than or equal to FileAlignment + FileAlignment: config.Align, // power of 2 MajorOperatingSystemVersion: 0x06, MajorSubsystemVersion: 0x06, SizeOfImage: imageSize, @@ -89,7 +90,7 @@ func New(code []byte, data []byte) *EXE { }, Sections: [NumSections]SectionHeader{ { - Name: [8]byte{'.', 'c', 'o', 'd', 'e'}, + Name: [8]byte{'.', 't', 'e', 'x', 't'}, VirtualSize: uint32(len(code)), VirtualAddress: codeStart, RawSize: uint32(len(code)), // must be a multiple of FileAlignment @@ -97,7 +98,7 @@ func New(code []byte, data []byte) *EXE { Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ, }, { - Name: [8]byte{'.', 'd', 'a', 't', 'a'}, + Name: [8]byte{'.', 'r', 'd', 'a', 't', 'a'}, VirtualSize: uint32(len(data)), VirtualAddress: dataStart, RawSize: uint32(len(data)), // must be a multiple of FileAlignment @@ -115,7 +116,7 @@ func New(code []byte, data []byte) *EXE { // Write writes the EXE file to the given writer. func (pe *EXE) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &pe.DOSHeader) - binary.Write(writer, binary.LittleEndian, &pe.PEHeader) + binary.Write(writer, binary.LittleEndian, &pe.NTHeader) binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) binary.Write(writer, binary.LittleEndian, &pe.Sections) binary.Write(writer, binary.LittleEndian, &pe.CodePadding) diff --git a/src/os/windows/pe/PEHeader.go b/src/os/windows/pe/NTHeader.go similarity index 84% rename from src/os/windows/pe/PEHeader.go rename to src/os/windows/pe/NTHeader.go index 2de3439..08c577e 100644 --- a/src/os/windows/pe/PEHeader.go +++ b/src/os/windows/pe/NTHeader.go @@ -1,8 +1,8 @@ package pe -const PEHeaderSize = 24 +const NTHeaderSize = 24 -type PEHeader struct { +type NTHeader struct { Signature [4]byte Machine uint16 NumberOfSections uint16 From b1b83201ebcf26035f0eb9c66b88a951068cd08e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 14 Aug 2024 17:49:07 +0200 Subject: [PATCH 0520/1012] Improved security --- src/asm/Finalize.go | 24 ++---------------- src/config/config.go | 6 +++++ src/os/common/Padding.go | 2 +- src/os/linux/elf/Constants.go | 8 ------ src/os/linux/elf/ELF.go | 19 +++++++------- src/os/mac/macho/Constants.go | 8 ------ src/os/mac/macho/MachO.go | 13 +++++----- src/os/windows/pe/Constants.go | 8 ------ src/os/windows/pe/DOSHeader.go | 2 +- src/os/windows/pe/EXE.go | 25 ++++++++++--------- .../windows/pe/{PEHeader.go => NTHeader.go} | 4 +-- 11 files changed, 42 insertions(+), 77 deletions(-) rename src/os/windows/pe/{PEHeader.go => NTHeader.go} (84%) diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 1457c40..e0e0c87 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -9,9 +9,6 @@ import ( "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/os/common" - "git.akyoto.dev/cli/q/src/os/linux/elf" - "git.akyoto.dev/cli/q/src/os/mac/macho" - "git.akyoto.dev/cli/q/src/os/windows/pe" "git.akyoto.dev/cli/q/src/sizeof" ) @@ -341,25 +338,8 @@ restart: data, dataLabels = a.Data.Finalize() - var ( - codeOffset Address - align Address - ) - - switch config.TargetOS { - case "linux": - codeOffset = elf.CodeOffset - align = elf.Align - case "mac": - codeOffset = macho.CodeOffset - align = macho.Align - case "windows": - codeOffset = pe.CodeOffset - align = pe.Align - } - - dataStart := Address(config.BaseAddress) + codeOffset + Address(len(code)) - dataStart += int32(common.Padding(dataStart, align)) + dataStart := Address(config.BaseAddress) + config.CodeOffset + Address(len(code)) + dataStart += int32(common.Padding(dataStart, config.Align)) for _, pointer := range dataPointers { address := dataStart + pointer.Resolve() diff --git a/src/config/config.go b/src/config/config.go index 555e379..79f7d58 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -8,6 +8,12 @@ const ( // The base address is the virtual address for our ELF file. BaseAddress = 0x40 * MinAddress + + // Align decides the alignment of the sections and it must be a multiple of the page size. + Align = 0x1000 + + // The code offset is the offset of the executable machine code within the file. + CodeOffset = Align ) var ( diff --git a/src/os/common/Padding.go b/src/os/common/Padding.go index da40c13..4f2d726 100644 --- a/src/os/common/Padding.go +++ b/src/os/common/Padding.go @@ -1,6 +1,6 @@ package common // Padding calculates the padding needed to align `n` bytes with the specified alignment. -func Padding[T int64 | uint64 | int32 | uint32](n T, align T) T { +func Padding[T int | uint | int64 | uint64 | int32 | uint32](n T, align T) T { return align - (n % align) } diff --git a/src/os/linux/elf/Constants.go b/src/os/linux/elf/Constants.go index 6396c66..bd4600f 100644 --- a/src/os/linux/elf/Constants.go +++ b/src/os/linux/elf/Constants.go @@ -1,13 +1,5 @@ package elf -const ( - // Align decides the alignment of the sections and it must be a multiple of the page size. - Align = 0x1000 - - // The code offset is the offset of the executable machine code within the file. - CodeOffset = HeaderSize + ProgramHeaderSize*2 -) - const ( LittleEndian = 1 TypeExecutable = 2 diff --git a/src/os/linux/elf/ELF.go b/src/os/linux/elf/ELF.go index 084b903..87818dc 100644 --- a/src/os/linux/elf/ELF.go +++ b/src/os/linux/elf/ELF.go @@ -22,8 +22,9 @@ type ELF struct { // New creates a new ELF binary. func New(code []byte, data []byte) *ELF { - dataOffset := CodeOffset + int64(len(code)) - dataPadding := common.Padding(dataOffset, Align) + codePadding := common.Padding(HeaderSize+ProgramHeaderSize*2, config.Align) + dataOffset := config.CodeOffset + int64(len(code)) + dataPadding := common.Padding(dataOffset, config.Align) dataOffset += dataPadding return &ELF{ @@ -37,7 +38,7 @@ func New(code []byte, data []byte) *ELF { Type: TypeExecutable, Architecture: ArchitectureAMD64, FileVersion: 1, - EntryPointInMemory: config.BaseAddress + CodeOffset, + EntryPointInMemory: config.BaseAddress + config.CodeOffset, ProgramHeaderOffset: HeaderSize, SectionHeaderOffset: 0, Flags: 0, @@ -51,12 +52,12 @@ func New(code []byte, data []byte) *ELF { CodeHeader: ProgramHeader{ Type: ProgramTypeLOAD, Flags: ProgramFlagsExecutable | ProgramFlagsReadable, - Offset: CodeOffset, - VirtualAddress: config.BaseAddress + CodeOffset, - PhysicalAddress: config.BaseAddress + CodeOffset, + Offset: config.CodeOffset, + VirtualAddress: config.BaseAddress + config.CodeOffset, + PhysicalAddress: config.BaseAddress + config.CodeOffset, SizeInFile: int64(len(code)), SizeInMemory: int64(len(code)), - Align: Align, + Align: config.Align, }, DataHeader: ProgramHeader{ Type: ProgramTypeLOAD, @@ -66,9 +67,9 @@ func New(code []byte, data []byte) *ELF { PhysicalAddress: config.BaseAddress + dataOffset, SizeInFile: int64(len(data)), SizeInMemory: int64(len(data)), - Align: Align, + Align: config.Align, }, - CodePadding: nil, + CodePadding: bytes.Repeat([]byte{0}, int(codePadding)), Code: code, DataPadding: bytes.Repeat([]byte{0}, int(dataPadding)), Data: data, diff --git a/src/os/mac/macho/Constants.go b/src/os/mac/macho/Constants.go index 83c62d9..eb9e320 100644 --- a/src/os/mac/macho/Constants.go +++ b/src/os/mac/macho/Constants.go @@ -1,13 +1,5 @@ package macho -const ( - // Align decides the alignment of the sections and it must be a multiple of the page size. - Align = 0x1000 - - // The code offset is the offset of the executable machine code within the file. - CodeOffset = 32 + 0x48*3 + 184 -) - type CPU uint32 const ( diff --git a/src/os/mac/macho/MachO.go b/src/os/mac/macho/MachO.go index 1bc1325..449c11b 100644 --- a/src/os/mac/macho/MachO.go +++ b/src/os/mac/macho/MachO.go @@ -52,19 +52,19 @@ func (m *MachO) Write(writer io.Writer) { InitProt: 0, }) - codeStart := uint64(32 + m.Header.SizeCommands) + codePadding := common.Padding(32+m.Header.SizeCommands, config.Align) codeLength := uint64(len(m.Code)) - codeEnd := codeStart + codeLength - dataPadding := common.Padding(codeEnd, Align) + codeEnd := config.CodeOffset + codeLength + dataPadding := common.Padding(codeEnd, config.Align) dataStart := codeEnd + dataPadding binary.Write(writer, binary.LittleEndian, &Segment64{ LoadCommand: LcSegment64, Length: 72, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, - Address: config.BaseAddress + codeStart, + Address: config.BaseAddress + config.CodeOffset, SizeInMemory: codeLength, - Offset: 0, + Offset: config.CodeOffset, SizeInFile: codeLength, NumSections: 0, Flag: 0, @@ -110,13 +110,14 @@ func (m *MachO) Write(writer io.Writer) { 0, 0, 0, 0, 0, 0, - config.BaseAddress + uint32(codeStart), 0, + config.BaseAddress + config.CodeOffset, 0, 0, 0, 0, 0, 0, 0, 0, 0, }) + writer.Write(bytes.Repeat([]byte{0}, int(codePadding))) writer.Write(m.Code) writer.Write(bytes.Repeat([]byte{0}, int(dataPadding))) writer.Write(m.Data) diff --git a/src/os/windows/pe/Constants.go b/src/os/windows/pe/Constants.go index 4d38ad5..c77bea7 100644 --- a/src/os/windows/pe/Constants.go +++ b/src/os/windows/pe/Constants.go @@ -1,13 +1,5 @@ package pe -const ( - // Align decides the alignment of the sections. - Align = 0x200 - - // The code offset is the offset of the executable machine code within the file. - CodeOffset = Align -) - // CPU const ( IMAGE_FILE_MACHINE_AMD64 = 0x8664 diff --git a/src/os/windows/pe/DOSHeader.go b/src/os/windows/pe/DOSHeader.go index d9a638b..eabb2f9 100644 --- a/src/os/windows/pe/DOSHeader.go +++ b/src/os/windows/pe/DOSHeader.go @@ -7,5 +7,5 @@ const DOSHeaderSize = 64 type DOSHeader struct { Magic [4]byte _ [56]byte - PEHeaderOffset uint32 + NTHeaderOffset uint32 } diff --git a/src/os/windows/pe/EXE.go b/src/os/windows/pe/EXE.go index 3be2970..f1d5ff3 100644 --- a/src/os/windows/pe/EXE.go +++ b/src/os/windows/pe/EXE.go @@ -14,7 +14,7 @@ const NumSections = 2 // EXE is the portable executable format used on Windows. type EXE struct { DOSHeader - PEHeader + NTHeader OptionalHeader64 Sections [NumSections]SectionHeader CodePadding []byte @@ -25,23 +25,23 @@ type EXE struct { // New creates a new EXE file. func New(code []byte, data []byte) *EXE { - codeStart := uint32(DOSHeaderSize + PEHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections) - codePadding := common.Padding(codeStart, Align) + codeStart := uint32(DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections) + codePadding := common.Padding(codeStart, config.Align) codeStart += codePadding dataStart := codeStart + uint32(len(code)) - dataPadding := common.Padding(dataStart, Align) + dataPadding := common.Padding(dataStart, config.Align) dataStart += dataPadding imageSize := uint32(dataStart + uint32(len(data))) - imageSize += common.Padding(imageSize, Align) + imageSize += common.Padding(imageSize, config.Align) return &EXE{ DOSHeader: DOSHeader{ Magic: [4]byte{'M', 'Z', 0, 0}, - PEHeaderOffset: 0x40, + NTHeaderOffset: 0x40, }, - PEHeader: PEHeader{ + NTHeader: NTHeader{ Signature: [4]byte{'P', 'E', 0, 0}, Machine: IMAGE_FILE_MACHINE_AMD64, NumberOfSections: NumSections, @@ -54,9 +54,10 @@ func New(code []byte, data []byte) *EXE { MinorLinkerVersion: 0x16, SizeOfCode: uint32(len(code)), AddressOfEntryPoint: codeStart, + BaseOfCode: codeStart, ImageBase: config.BaseAddress, - SectionAlignment: Align, // power of 2, must be greater than or equal to FileAlignment - FileAlignment: Align, // power of 2 + SectionAlignment: config.Align, // power of 2, must be greater than or equal to FileAlignment + FileAlignment: config.Align, // power of 2 MajorOperatingSystemVersion: 0x06, MajorSubsystemVersion: 0x06, SizeOfImage: imageSize, @@ -89,7 +90,7 @@ func New(code []byte, data []byte) *EXE { }, Sections: [NumSections]SectionHeader{ { - Name: [8]byte{'.', 'c', 'o', 'd', 'e'}, + Name: [8]byte{'.', 't', 'e', 'x', 't'}, VirtualSize: uint32(len(code)), VirtualAddress: codeStart, RawSize: uint32(len(code)), // must be a multiple of FileAlignment @@ -97,7 +98,7 @@ func New(code []byte, data []byte) *EXE { Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ, }, { - Name: [8]byte{'.', 'd', 'a', 't', 'a'}, + Name: [8]byte{'.', 'r', 'd', 'a', 't', 'a'}, VirtualSize: uint32(len(data)), VirtualAddress: dataStart, RawSize: uint32(len(data)), // must be a multiple of FileAlignment @@ -115,7 +116,7 @@ func New(code []byte, data []byte) *EXE { // Write writes the EXE file to the given writer. func (pe *EXE) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &pe.DOSHeader) - binary.Write(writer, binary.LittleEndian, &pe.PEHeader) + binary.Write(writer, binary.LittleEndian, &pe.NTHeader) binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) binary.Write(writer, binary.LittleEndian, &pe.Sections) binary.Write(writer, binary.LittleEndian, &pe.CodePadding) diff --git a/src/os/windows/pe/PEHeader.go b/src/os/windows/pe/NTHeader.go similarity index 84% rename from src/os/windows/pe/PEHeader.go rename to src/os/windows/pe/NTHeader.go index 2de3439..08c577e 100644 --- a/src/os/windows/pe/PEHeader.go +++ b/src/os/windows/pe/NTHeader.go @@ -1,8 +1,8 @@ package pe -const PEHeaderSize = 24 +const NTHeaderSize = 24 -type PEHeader struct { +type NTHeader struct { Signature [4]byte Machine uint16 NumberOfSections uint16 From 1d3860f49ed7d8046f968622bee8ecdfee036f64 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 14 Aug 2024 19:35:03 +0200 Subject: [PATCH 0521/1012] Fixed MacOS executables --- src/os/mac/macho/MachO.go | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/os/mac/macho/MachO.go b/src/os/mac/macho/MachO.go index 449c11b..d48281c 100644 --- a/src/os/mac/macho/MachO.go +++ b/src/os/mac/macho/MachO.go @@ -24,8 +24,8 @@ func New(code []byte, data []byte) *MachO { Architecture: CPU_X86_64, MicroArchitecture: 3 | 0x80000000, Type: TypeExecute, - NumCommands: 4, - SizeCommands: 72*3 + 184, + NumCommands: 5, + SizeCommands: 72*4 + 184, Flags: FlagNoUndefs, Reserved: 0, }, @@ -53,19 +53,32 @@ func (m *MachO) Write(writer io.Writer) { }) codePadding := common.Padding(32+m.Header.SizeCommands, config.Align) - codeLength := uint64(len(m.Code)) - codeEnd := config.CodeOffset + codeLength + codeEnd := uint64(config.CodeOffset + len(m.Code)) dataPadding := common.Padding(codeEnd, config.Align) dataStart := codeEnd + dataPadding + binary.Write(writer, binary.LittleEndian, &Segment64{ + LoadCommand: LcSegment64, + Length: 72, + Name: [16]byte{'_', '_', 'H', 'E', 'A', 'D'}, + Address: config.BaseAddress, + SizeInMemory: config.CodeOffset, + Offset: 0, + SizeInFile: config.CodeOffset, + NumSections: 0, + Flag: 0, + MaxProt: ProtReadable | ProtExecutable, + InitProt: ProtReadable | ProtExecutable, + }) + binary.Write(writer, binary.LittleEndian, &Segment64{ LoadCommand: LcSegment64, Length: 72, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, Address: config.BaseAddress + config.CodeOffset, - SizeInMemory: codeLength, + SizeInMemory: uint64(len(m.Code)), Offset: config.CodeOffset, - SizeInFile: codeLength, + SizeInFile: uint64(len(m.Code)), NumSections: 0, Flag: 0, MaxProt: ProtReadable | ProtExecutable, From 35eeb420e11bc559f8460f57071a4d0189fdc6db Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 14 Aug 2024 19:35:03 +0200 Subject: [PATCH 0522/1012] Fixed MacOS executables --- src/os/mac/macho/MachO.go | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/os/mac/macho/MachO.go b/src/os/mac/macho/MachO.go index 449c11b..d48281c 100644 --- a/src/os/mac/macho/MachO.go +++ b/src/os/mac/macho/MachO.go @@ -24,8 +24,8 @@ func New(code []byte, data []byte) *MachO { Architecture: CPU_X86_64, MicroArchitecture: 3 | 0x80000000, Type: TypeExecute, - NumCommands: 4, - SizeCommands: 72*3 + 184, + NumCommands: 5, + SizeCommands: 72*4 + 184, Flags: FlagNoUndefs, Reserved: 0, }, @@ -53,19 +53,32 @@ func (m *MachO) Write(writer io.Writer) { }) codePadding := common.Padding(32+m.Header.SizeCommands, config.Align) - codeLength := uint64(len(m.Code)) - codeEnd := config.CodeOffset + codeLength + codeEnd := uint64(config.CodeOffset + len(m.Code)) dataPadding := common.Padding(codeEnd, config.Align) dataStart := codeEnd + dataPadding + binary.Write(writer, binary.LittleEndian, &Segment64{ + LoadCommand: LcSegment64, + Length: 72, + Name: [16]byte{'_', '_', 'H', 'E', 'A', 'D'}, + Address: config.BaseAddress, + SizeInMemory: config.CodeOffset, + Offset: 0, + SizeInFile: config.CodeOffset, + NumSections: 0, + Flag: 0, + MaxProt: ProtReadable | ProtExecutable, + InitProt: ProtReadable | ProtExecutable, + }) + binary.Write(writer, binary.LittleEndian, &Segment64{ LoadCommand: LcSegment64, Length: 72, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, Address: config.BaseAddress + config.CodeOffset, - SizeInMemory: codeLength, + SizeInMemory: uint64(len(m.Code)), Offset: config.CodeOffset, - SizeInFile: codeLength, + SizeInFile: uint64(len(m.Code)), NumSections: 0, Flag: 0, MaxProt: ProtReadable | ProtExecutable, From 1cb93b39a7d2a91b6ad27fec221ad33a23c4e47c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 14 Aug 2024 22:21:39 +0200 Subject: [PATCH 0523/1012] Reduced number of load commands --- src/arch/arm64/Registers.go | 5 ++- src/os/linux/elf/ELF.go | 2 +- src/os/mac/macho/Header.go | 2 + src/os/mac/macho/MachO.go | 79 ++++++++++++++--------------------- src/os/mac/macho/Segment64.go | 2 + src/os/mac/macho/Thread.go | 3 ++ 6 files changed, 43 insertions(+), 50 deletions(-) diff --git a/src/arch/arm64/Registers.go b/src/arch/arm64/Registers.go index cf8c5db..088e2b3 100644 --- a/src/arch/arm64/Registers.go +++ b/src/arch/arm64/Registers.go @@ -32,8 +32,9 @@ const ( X26 X27 X28 - X29 - X30 + FP // Frame pointer + LR // Link register + SP // Stack pointer ) var SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} diff --git a/src/os/linux/elf/ELF.go b/src/os/linux/elf/ELF.go index 87818dc..fb8b517 100644 --- a/src/os/linux/elf/ELF.go +++ b/src/os/linux/elf/ELF.go @@ -45,7 +45,7 @@ func New(code []byte, data []byte) *ELF { Size: HeaderSize, ProgramHeaderEntrySize: ProgramHeaderSize, ProgramHeaderEntryCount: 2, - SectionHeaderEntrySize: SectionHeaderSize, + SectionHeaderEntrySize: 0, SectionHeaderEntryCount: 0, SectionNameStringTableIndex: 0, }, diff --git a/src/os/mac/macho/Header.go b/src/os/mac/macho/Header.go index f7798be..21c0c54 100644 --- a/src/os/mac/macho/Header.go +++ b/src/os/mac/macho/Header.go @@ -1,5 +1,7 @@ package macho +const HeaderSize = 32 + // Header contains general information. type Header struct { Magic uint32 diff --git a/src/os/mac/macho/MachO.go b/src/os/mac/macho/MachO.go index d48281c..a96e37b 100644 --- a/src/os/mac/macho/MachO.go +++ b/src/os/mac/macho/MachO.go @@ -24,9 +24,9 @@ func New(code []byte, data []byte) *MachO { Architecture: CPU_X86_64, MicroArchitecture: 3 | 0x80000000, Type: TypeExecute, - NumCommands: 5, - SizeCommands: 72*4 + 184, - Flags: FlagNoUndefs, + NumCommands: 4, + SizeCommands: Segment64Size*3 + ThreadSize, + Flags: FlagNoUndefs | FlagNoHeapExecution, Reserved: 0, }, Code: code, @@ -52,33 +52,19 @@ func (m *MachO) Write(writer io.Writer) { InitProt: 0, }) - codePadding := common.Padding(32+m.Header.SizeCommands, config.Align) + codePadding := common.Padding(HeaderSize+m.Header.SizeCommands, config.Align) codeEnd := uint64(config.CodeOffset + len(m.Code)) dataPadding := common.Padding(codeEnd, config.Align) dataStart := codeEnd + dataPadding - binary.Write(writer, binary.LittleEndian, &Segment64{ - LoadCommand: LcSegment64, - Length: 72, - Name: [16]byte{'_', '_', 'H', 'E', 'A', 'D'}, - Address: config.BaseAddress, - SizeInMemory: config.CodeOffset, - Offset: 0, - SizeInFile: config.CodeOffset, - NumSections: 0, - Flag: 0, - MaxProt: ProtReadable | ProtExecutable, - InitProt: ProtReadable | ProtExecutable, - }) - binary.Write(writer, binary.LittleEndian, &Segment64{ LoadCommand: LcSegment64, Length: 72, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, - Address: config.BaseAddress + config.CodeOffset, - SizeInMemory: uint64(len(m.Code)), - Offset: config.CodeOffset, - SizeInFile: uint64(len(m.Code)), + Address: config.BaseAddress, + SizeInMemory: config.CodeOffset + uint64(len(m.Code)), + Offset: 0, + SizeInFile: config.CodeOffset + uint64(len(m.Code)), NumSections: 0, Flag: 0, MaxProt: ProtReadable | ProtExecutable, @@ -103,31 +89,30 @@ func (m *MachO) Write(writer io.Writer) { LoadCommand: LcUnixthread, Len: 184, Type: 0x4, - }) - - binary.Write(writer, binary.LittleEndian, []uint32{ - 42, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - config.BaseAddress + config.CodeOffset, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, + Data: [43]uint32{ + 42, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + config.BaseAddress + config.CodeOffset, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + }, }) writer.Write(bytes.Repeat([]byte{0}, int(codePadding))) diff --git a/src/os/mac/macho/Segment64.go b/src/os/mac/macho/Segment64.go index 382e18f..1f611f0 100644 --- a/src/os/mac/macho/Segment64.go +++ b/src/os/mac/macho/Segment64.go @@ -1,5 +1,7 @@ package macho +const Segment64Size = 72 + // Segment64 is a segment load command. type Segment64 struct { LoadCommand diff --git a/src/os/mac/macho/Thread.go b/src/os/mac/macho/Thread.go index 69f9053..004c196 100644 --- a/src/os/mac/macho/Thread.go +++ b/src/os/mac/macho/Thread.go @@ -1,8 +1,11 @@ package macho +const ThreadSize = 184 + // Thread is a thread state load command. type Thread struct { LoadCommand Len uint32 Type uint32 + Data [43]uint32 } From fe1b353fe68e0c9b3b48c0fb0da08cea3d564a14 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 14 Aug 2024 22:21:39 +0200 Subject: [PATCH 0524/1012] Reduced number of load commands --- src/arch/arm64/Registers.go | 5 ++- src/os/linux/elf/ELF.go | 2 +- src/os/mac/macho/Header.go | 2 + src/os/mac/macho/MachO.go | 79 ++++++++++++++--------------------- src/os/mac/macho/Segment64.go | 2 + src/os/mac/macho/Thread.go | 3 ++ 6 files changed, 43 insertions(+), 50 deletions(-) diff --git a/src/arch/arm64/Registers.go b/src/arch/arm64/Registers.go index cf8c5db..088e2b3 100644 --- a/src/arch/arm64/Registers.go +++ b/src/arch/arm64/Registers.go @@ -32,8 +32,9 @@ const ( X26 X27 X28 - X29 - X30 + FP // Frame pointer + LR // Link register + SP // Stack pointer ) var SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} diff --git a/src/os/linux/elf/ELF.go b/src/os/linux/elf/ELF.go index 87818dc..fb8b517 100644 --- a/src/os/linux/elf/ELF.go +++ b/src/os/linux/elf/ELF.go @@ -45,7 +45,7 @@ func New(code []byte, data []byte) *ELF { Size: HeaderSize, ProgramHeaderEntrySize: ProgramHeaderSize, ProgramHeaderEntryCount: 2, - SectionHeaderEntrySize: SectionHeaderSize, + SectionHeaderEntrySize: 0, SectionHeaderEntryCount: 0, SectionNameStringTableIndex: 0, }, diff --git a/src/os/mac/macho/Header.go b/src/os/mac/macho/Header.go index f7798be..21c0c54 100644 --- a/src/os/mac/macho/Header.go +++ b/src/os/mac/macho/Header.go @@ -1,5 +1,7 @@ package macho +const HeaderSize = 32 + // Header contains general information. type Header struct { Magic uint32 diff --git a/src/os/mac/macho/MachO.go b/src/os/mac/macho/MachO.go index d48281c..a96e37b 100644 --- a/src/os/mac/macho/MachO.go +++ b/src/os/mac/macho/MachO.go @@ -24,9 +24,9 @@ func New(code []byte, data []byte) *MachO { Architecture: CPU_X86_64, MicroArchitecture: 3 | 0x80000000, Type: TypeExecute, - NumCommands: 5, - SizeCommands: 72*4 + 184, - Flags: FlagNoUndefs, + NumCommands: 4, + SizeCommands: Segment64Size*3 + ThreadSize, + Flags: FlagNoUndefs | FlagNoHeapExecution, Reserved: 0, }, Code: code, @@ -52,33 +52,19 @@ func (m *MachO) Write(writer io.Writer) { InitProt: 0, }) - codePadding := common.Padding(32+m.Header.SizeCommands, config.Align) + codePadding := common.Padding(HeaderSize+m.Header.SizeCommands, config.Align) codeEnd := uint64(config.CodeOffset + len(m.Code)) dataPadding := common.Padding(codeEnd, config.Align) dataStart := codeEnd + dataPadding - binary.Write(writer, binary.LittleEndian, &Segment64{ - LoadCommand: LcSegment64, - Length: 72, - Name: [16]byte{'_', '_', 'H', 'E', 'A', 'D'}, - Address: config.BaseAddress, - SizeInMemory: config.CodeOffset, - Offset: 0, - SizeInFile: config.CodeOffset, - NumSections: 0, - Flag: 0, - MaxProt: ProtReadable | ProtExecutable, - InitProt: ProtReadable | ProtExecutable, - }) - binary.Write(writer, binary.LittleEndian, &Segment64{ LoadCommand: LcSegment64, Length: 72, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, - Address: config.BaseAddress + config.CodeOffset, - SizeInMemory: uint64(len(m.Code)), - Offset: config.CodeOffset, - SizeInFile: uint64(len(m.Code)), + Address: config.BaseAddress, + SizeInMemory: config.CodeOffset + uint64(len(m.Code)), + Offset: 0, + SizeInFile: config.CodeOffset + uint64(len(m.Code)), NumSections: 0, Flag: 0, MaxProt: ProtReadable | ProtExecutable, @@ -103,31 +89,30 @@ func (m *MachO) Write(writer io.Writer) { LoadCommand: LcUnixthread, Len: 184, Type: 0x4, - }) - - binary.Write(writer, binary.LittleEndian, []uint32{ - 42, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - config.BaseAddress + config.CodeOffset, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, + Data: [43]uint32{ + 42, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + config.BaseAddress + config.CodeOffset, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + }, }) writer.Write(bytes.Repeat([]byte{0}, int(codePadding))) diff --git a/src/os/mac/macho/Segment64.go b/src/os/mac/macho/Segment64.go index 382e18f..1f611f0 100644 --- a/src/os/mac/macho/Segment64.go +++ b/src/os/mac/macho/Segment64.go @@ -1,5 +1,7 @@ package macho +const Segment64Size = 72 + // Segment64 is a segment load command. type Segment64 struct { LoadCommand diff --git a/src/os/mac/macho/Thread.go b/src/os/mac/macho/Thread.go index 69f9053..004c196 100644 --- a/src/os/mac/macho/Thread.go +++ b/src/os/mac/macho/Thread.go @@ -1,8 +1,11 @@ package macho +const ThreadSize = 184 + // Thread is a thread state load command. type Thread struct { LoadCommand Len uint32 Type uint32 + Data [43]uint32 } From 999e60e2946785e2c756f79d4f3363257f65253c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 15 Aug 2024 00:46:49 +0200 Subject: [PATCH 0525/1012] Simplified executable file formats --- src/asm/Finalize.go | 4 +- src/compiler/Result.go | 15 +-- src/{os/common => exe}/Padding.go | 2 +- src/{os/linux => exe}/elf/Constants.go | 0 src/{os/linux => exe}/elf/ELF.go | 51 +++---- src/exe/elf/ELF_test.go | 12 ++ src/{os/linux => exe}/elf/Header.go | 0 src/{os/linux => exe}/elf/ProgramHeader.go | 0 src/{os/linux => exe}/elf/SectionHeader.go | 0 src/{os/linux => exe}/elf/elf.md | 12 +- src/{os/mac => exe}/macho/Constants.go | 0 src/{os/mac => exe}/macho/Header.go | 0 src/{os/mac => exe}/macho/LoadCommand.go | 0 src/exe/macho/MachO.go | 126 ++++++++++++++++++ src/{os/mac => exe}/macho/Segment64.go | 0 src/{os/mac => exe}/macho/Thread.go | 0 src/exe/macho/macho.md | 16 +++ src/{os/windows => exe}/pe/Constants.go | 0 src/{os/windows => exe}/pe/DOSHeader.go | 0 src/{os/windows => exe}/pe/DataDirectory.go | 0 src/{os/windows => exe}/pe/EXE.go | 67 +++++----- src/{os/windows => exe}/pe/ImportDirectory.go | 0 src/{os/windows => exe}/pe/NTHeader.go | 0 .../windows => exe}/pe/OptionalHeader64.go | 0 src/{os/windows => exe}/pe/SectionHeader.go | 0 src/exe/pe/pe.md | 11 ++ src/os/linux/Syscall.go | 3 - src/os/linux/elf/ELF_test.go | 13 -- src/os/mac/macho/MachO.go | 122 ----------------- 29 files changed, 236 insertions(+), 218 deletions(-) rename src/{os/common => exe}/Padding.go (93%) rename src/{os/linux => exe}/elf/Constants.go (100%) rename src/{os/linux => exe}/elf/ELF.go (66%) create mode 100644 src/exe/elf/ELF_test.go rename src/{os/linux => exe}/elf/Header.go (100%) rename src/{os/linux => exe}/elf/ProgramHeader.go (100%) rename src/{os/linux => exe}/elf/SectionHeader.go (100%) rename src/{os/linux => exe}/elf/elf.md (61%) rename src/{os/mac => exe}/macho/Constants.go (100%) rename src/{os/mac => exe}/macho/Header.go (100%) rename src/{os/mac => exe}/macho/LoadCommand.go (100%) create mode 100644 src/exe/macho/MachO.go rename src/{os/mac => exe}/macho/Segment64.go (100%) rename src/{os/mac => exe}/macho/Thread.go (100%) create mode 100644 src/exe/macho/macho.md rename src/{os/windows => exe}/pe/Constants.go (100%) rename src/{os/windows => exe}/pe/DOSHeader.go (100%) rename src/{os/windows => exe}/pe/DataDirectory.go (100%) rename src/{os/windows => exe}/pe/EXE.go (67%) rename src/{os/windows => exe}/pe/ImportDirectory.go (100%) rename src/{os/windows => exe}/pe/NTHeader.go (100%) rename src/{os/windows => exe}/pe/OptionalHeader64.go (100%) rename src/{os/windows => exe}/pe/SectionHeader.go (100%) create mode 100644 src/exe/pe/pe.md delete mode 100644 src/os/linux/elf/ELF_test.go delete mode 100644 src/os/mac/macho/MachO.go diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index e0e0c87..9f46845 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -8,7 +8,7 @@ import ( "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/os/common" + "git.akyoto.dev/cli/q/src/exe" "git.akyoto.dev/cli/q/src/sizeof" ) @@ -339,7 +339,7 @@ restart: data, dataLabels = a.Data.Finalize() dataStart := Address(config.BaseAddress) + config.CodeOffset + Address(len(code)) - dataStart += int32(common.Padding(dataStart, config.Align)) + dataStart += exe.Padding(dataStart, config.Align) for _, pointer := range dataPointers { address := dataStart + pointer.Resolve() diff --git a/src/compiler/Result.go b/src/compiler/Result.go index f21ea29..d9f7fcf 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -10,11 +10,11 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/exe/elf" + "git.akyoto.dev/cli/q/src/exe/macho" + "git.akyoto.dev/cli/q/src/exe/pe" "git.akyoto.dev/cli/q/src/os/linux" - "git.akyoto.dev/cli/q/src/os/linux/elf" "git.akyoto.dev/cli/q/src/os/mac" - "git.akyoto.dev/cli/q/src/os/mac/macho" - "git.akyoto.dev/cli/q/src/os/windows/pe" ) // Result contains all the compiled functions in a build. @@ -147,14 +147,11 @@ func write(writer io.Writer, code []byte, data []byte) error { switch config.TargetOS { case "linux": - exe := elf.New(code, data) - exe.Write(buffer) + elf.Write(buffer, code, data) case "mac": - exe := macho.New(code, data) - exe.Write(buffer) + macho.Write(buffer, code, data) case "windows": - exe := pe.New(code, data) - exe.Write(buffer) + pe.Write(buffer, code, data) default: return fmt.Errorf("unsupported platform '%s'", config.TargetOS) } diff --git a/src/os/common/Padding.go b/src/exe/Padding.go similarity index 93% rename from src/os/common/Padding.go rename to src/exe/Padding.go index 4f2d726..bf8195c 100644 --- a/src/os/common/Padding.go +++ b/src/exe/Padding.go @@ -1,4 +1,4 @@ -package common +package exe // Padding calculates the padding needed to align `n` bytes with the specified alignment. func Padding[T int | uint | int64 | uint64 | int32 | uint32](n T, align T) T { diff --git a/src/os/linux/elf/Constants.go b/src/exe/elf/Constants.go similarity index 100% rename from src/os/linux/elf/Constants.go rename to src/exe/elf/Constants.go diff --git a/src/os/linux/elf/ELF.go b/src/exe/elf/ELF.go similarity index 66% rename from src/os/linux/elf/ELF.go rename to src/exe/elf/ELF.go index fb8b517..413ece9 100644 --- a/src/os/linux/elf/ELF.go +++ b/src/exe/elf/ELF.go @@ -6,28 +6,28 @@ import ( "io" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/os/common" + "git.akyoto.dev/cli/q/src/exe" ) // ELF represents an ELF file. type ELF struct { Header - CodeHeader ProgramHeader - DataHeader ProgramHeader - CodePadding []byte - Code []byte - DataPadding []byte - Data []byte + CodeHeader ProgramHeader + DataHeader ProgramHeader } -// New creates a new ELF binary. -func New(code []byte, data []byte) *ELF { - codePadding := common.Padding(HeaderSize+ProgramHeaderSize*2, config.Align) - dataOffset := config.CodeOffset + int64(len(code)) - dataPadding := common.Padding(dataOffset, config.Align) - dataOffset += dataPadding +// Write writes the ELF64 format to the given writer. +func Write(writer io.Writer, code []byte, data []byte) { + const HeaderEnd = HeaderSize + ProgramHeaderSize*2 - return &ELF{ + var ( + codePadding = exe.Padding(HeaderEnd, config.Align) + codeEnd = config.CodeOffset + len(code) + dataPadding = exe.Padding(codeEnd, config.Align) + dataStart = codeEnd + dataPadding + ) + + elf := &ELF{ Header: Header{ Magic: [4]byte{0x7F, 'E', 'L', 'F'}, Class: 2, @@ -62,30 +62,23 @@ func New(code []byte, data []byte) *ELF { DataHeader: ProgramHeader{ Type: ProgramTypeLOAD, Flags: ProgramFlagsReadable, - Offset: dataOffset, - VirtualAddress: config.BaseAddress + dataOffset, - PhysicalAddress: config.BaseAddress + dataOffset, + Offset: int64(dataStart), + VirtualAddress: int64(config.BaseAddress + dataStart), + PhysicalAddress: int64(config.BaseAddress + dataStart), SizeInFile: int64(len(data)), SizeInMemory: int64(len(data)), Align: config.Align, }, - CodePadding: bytes.Repeat([]byte{0}, int(codePadding)), - Code: code, - DataPadding: bytes.Repeat([]byte{0}, int(dataPadding)), - Data: data, } -} -// Write writes the ELF64 format to the given writer. -func (elf *ELF) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.CodeHeader) binary.Write(writer, binary.LittleEndian, &elf.DataHeader) - writer.Write(elf.CodePadding) - writer.Write(elf.Code) + writer.Write(bytes.Repeat([]byte{0}, codePadding)) + writer.Write(code) - if len(elf.Data) > 0 { - writer.Write(elf.DataPadding) - writer.Write(elf.Data) + if len(data) > 0 { + writer.Write(bytes.Repeat([]byte{0}, dataPadding)) + writer.Write(data) } } diff --git a/src/exe/elf/ELF_test.go b/src/exe/elf/ELF_test.go new file mode 100644 index 0000000..aa28d0d --- /dev/null +++ b/src/exe/elf/ELF_test.go @@ -0,0 +1,12 @@ +package elf_test + +import ( + "io" + "testing" + + "git.akyoto.dev/cli/q/src/exe/elf" +) + +func TestWrite(t *testing.T) { + elf.Write(io.Discard, nil, nil) +} diff --git a/src/os/linux/elf/Header.go b/src/exe/elf/Header.go similarity index 100% rename from src/os/linux/elf/Header.go rename to src/exe/elf/Header.go diff --git a/src/os/linux/elf/ProgramHeader.go b/src/exe/elf/ProgramHeader.go similarity index 100% rename from src/os/linux/elf/ProgramHeader.go rename to src/exe/elf/ProgramHeader.go diff --git a/src/os/linux/elf/SectionHeader.go b/src/exe/elf/SectionHeader.go similarity index 100% rename from src/os/linux/elf/SectionHeader.go rename to src/exe/elf/SectionHeader.go diff --git a/src/os/linux/elf/elf.md b/src/exe/elf/elf.md similarity index 61% rename from src/os/linux/elf/elf.md rename to src/exe/elf/elf.md index c40f005..92a0096 100644 --- a/src/os/linux/elf/elf.md +++ b/src/exe/elf/elf.md @@ -4,8 +4,8 @@ 1. ELF header (0x00 - 0x40) 2. Program header (0x40 - 0x78) -3. Padding (0x78 - 0x80) -4. Machine code (0x80) +3. Padding +4. Machine code ## Entry point @@ -28,3 +28,11 @@ https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/binfm ELF register definitions: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/include/asm/elf.h + +## Links + +- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/elf.h +- https://lwn.net/Articles/631631/ +- https://en.wikipedia.org/wiki/Executable_and_Linkable_Format +- https://www.muppetlabs.com/~breadbox/software/tiny/teensy.html +- https://nathanotterness.com/2021/10/tiny_elf_modernized.html \ No newline at end of file diff --git a/src/os/mac/macho/Constants.go b/src/exe/macho/Constants.go similarity index 100% rename from src/os/mac/macho/Constants.go rename to src/exe/macho/Constants.go diff --git a/src/os/mac/macho/Header.go b/src/exe/macho/Header.go similarity index 100% rename from src/os/mac/macho/Header.go rename to src/exe/macho/Header.go diff --git a/src/os/mac/macho/LoadCommand.go b/src/exe/macho/LoadCommand.go similarity index 100% rename from src/os/mac/macho/LoadCommand.go rename to src/exe/macho/LoadCommand.go diff --git a/src/exe/macho/MachO.go b/src/exe/macho/MachO.go new file mode 100644 index 0000000..5f2cbcb --- /dev/null +++ b/src/exe/macho/MachO.go @@ -0,0 +1,126 @@ +package macho + +import ( + "bytes" + "encoding/binary" + "io" + + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/exe" +) + +// MachO is the executable format used on MacOS. +type MachO struct { + Header + PageZero Segment64 + CodeHeader Segment64 + DataHeader Segment64 + UnixThread Thread +} + +// Write writes the Mach-O format to the given writer. +func Write(writer io.Writer, code []byte, data []byte) { + const ( + SizeCommands = Segment64Size*3 + ThreadSize + HeaderEnd = HeaderSize + SizeCommands + ) + + var ( + codePadding = exe.Padding(HeaderEnd, config.Align) + codeEnd = config.CodeOffset + len(code) + dataPadding = exe.Padding(codeEnd, config.Align) + dataStart = codeEnd + dataPadding + ) + + m := &MachO{ + Header: Header{ + Magic: 0xFEEDFACF, + Architecture: CPU_X86_64, + MicroArchitecture: 3 | 0x80000000, + Type: TypeExecute, + NumCommands: 4, + SizeCommands: SizeCommands, + Flags: FlagNoUndefs | FlagNoHeapExecution, + Reserved: 0, + }, + PageZero: Segment64{ + LoadCommand: LcSegment64, + Length: 72, + Name: [16]byte{'_', '_', 'P', 'A', 'G', 'E', 'Z', 'E', 'R', 'O'}, + Address: 0, + SizeInMemory: config.BaseAddress, + Offset: 0, + SizeInFile: 0, + NumSections: 0, + Flag: 0, + MaxProt: 0, + InitProt: 0, + }, + CodeHeader: Segment64{ + LoadCommand: LcSegment64, + Length: Segment64Size, + Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, + Address: config.BaseAddress, + SizeInMemory: uint64(codeEnd), + Offset: 0, + SizeInFile: uint64(codeEnd), + NumSections: 0, + Flag: 0, + MaxProt: ProtReadable | ProtExecutable, + InitProt: ProtReadable | ProtExecutable, + }, + DataHeader: Segment64{ + LoadCommand: LcSegment64, + Length: Segment64Size, + Name: [16]byte{'_', '_', 'D', 'A', 'T', 'A'}, + Address: uint64(config.BaseAddress + dataStart), + SizeInMemory: uint64(len(data)), + Offset: uint64(dataStart), + SizeInFile: uint64(len(data)), + NumSections: 0, + Flag: 0, + MaxProt: ProtReadable, + InitProt: ProtReadable, + }, + UnixThread: Thread{ + LoadCommand: LcUnixthread, + Len: ThreadSize, + Type: 0x4, + Data: [43]uint32{ + 42, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + config.BaseAddress + config.CodeOffset, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + }, + }, + } + + binary.Write(writer, binary.LittleEndian, &m.Header) + binary.Write(writer, binary.LittleEndian, &m.PageZero) + binary.Write(writer, binary.LittleEndian, &m.CodeHeader) + binary.Write(writer, binary.LittleEndian, &m.DataHeader) + binary.Write(writer, binary.LittleEndian, &m.UnixThread) + + writer.Write(bytes.Repeat([]byte{0}, int(codePadding))) + writer.Write(code) + writer.Write(bytes.Repeat([]byte{0}, int(dataPadding))) + writer.Write(data) +} diff --git a/src/os/mac/macho/Segment64.go b/src/exe/macho/Segment64.go similarity index 100% rename from src/os/mac/macho/Segment64.go rename to src/exe/macho/Segment64.go diff --git a/src/os/mac/macho/Thread.go b/src/exe/macho/Thread.go similarity index 100% rename from src/os/mac/macho/Thread.go rename to src/exe/macho/Thread.go diff --git a/src/exe/macho/macho.md b/src/exe/macho/macho.md new file mode 100644 index 0000000..01c1be6 --- /dev/null +++ b/src/exe/macho/macho.md @@ -0,0 +1,16 @@ +# Mach-O + +## Notes + +MacOS is the only operating system that requires loading the headers +explicitly with both readable and executable permissions. + +## Loader + +https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/loader.h + +## Links + +- https://en.wikipedia.org/wiki/Mach-O +- https://github.com/aidansteele/osx-abi-macho-file-format-reference +- https://stackoverflow.com/questions/39863112/what-is-required-for-a-mach-o-executable-to-load \ No newline at end of file diff --git a/src/os/windows/pe/Constants.go b/src/exe/pe/Constants.go similarity index 100% rename from src/os/windows/pe/Constants.go rename to src/exe/pe/Constants.go diff --git a/src/os/windows/pe/DOSHeader.go b/src/exe/pe/DOSHeader.go similarity index 100% rename from src/os/windows/pe/DOSHeader.go rename to src/exe/pe/DOSHeader.go diff --git a/src/os/windows/pe/DataDirectory.go b/src/exe/pe/DataDirectory.go similarity index 100% rename from src/os/windows/pe/DataDirectory.go rename to src/exe/pe/DataDirectory.go diff --git a/src/os/windows/pe/EXE.go b/src/exe/pe/EXE.go similarity index 67% rename from src/os/windows/pe/EXE.go rename to src/exe/pe/EXE.go index f1d5ff3..f9e55c4 100644 --- a/src/os/windows/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -6,7 +6,7 @@ import ( "io" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/os/common" + "git.akyoto.dev/cli/q/src/exe" ) const NumSections = 2 @@ -16,30 +16,29 @@ type EXE struct { DOSHeader NTHeader OptionalHeader64 - Sections [NumSections]SectionHeader - CodePadding []byte - Code []byte - DataPadding []byte - Data []byte + Sections [NumSections]SectionHeader } -// New creates a new EXE file. -func New(code []byte, data []byte) *EXE { - codeStart := uint32(DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections) - codePadding := common.Padding(codeStart, config.Align) - codeStart += codePadding +// Write writes the EXE file to the given writer. +func Write(writer io.Writer, code []byte, data []byte) { + const ( + HeaderEnd = DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections + ) - dataStart := codeStart + uint32(len(code)) - dataPadding := common.Padding(dataStart, config.Align) - dataStart += dataPadding + var ( + codePadding = exe.Padding(HeaderEnd, config.Align) + codeEnd = config.CodeOffset + len(code) + dataPadding = exe.Padding(codeEnd, config.Align) + dataStart = codeEnd + dataPadding + ) - imageSize := uint32(dataStart + uint32(len(data))) - imageSize += common.Padding(imageSize, config.Align) + imageSize := dataStart + len(data) + imageSize += exe.Padding(imageSize, config.Align) - return &EXE{ + pe := &EXE{ DOSHeader: DOSHeader{ Magic: [4]byte{'M', 'Z', 0, 0}, - NTHeaderOffset: 0x40, + NTHeaderOffset: DOSHeaderSize, }, NTHeader: NTHeader{ Signature: [4]byte{'P', 'E', 0, 0}, @@ -53,15 +52,15 @@ func New(code []byte, data []byte) *EXE { MajorLinkerVersion: 0x0E, MinorLinkerVersion: 0x16, SizeOfCode: uint32(len(code)), - AddressOfEntryPoint: codeStart, - BaseOfCode: codeStart, + AddressOfEntryPoint: config.CodeOffset, + BaseOfCode: config.CodeOffset, ImageBase: config.BaseAddress, SectionAlignment: config.Align, // power of 2, must be greater than or equal to FileAlignment FileAlignment: config.Align, // power of 2 MajorOperatingSystemVersion: 0x06, MajorSubsystemVersion: 0x06, - SizeOfImage: imageSize, - SizeOfHeaders: codeStart, // section bodies begin here + SizeOfImage: uint32(imageSize), + SizeOfHeaders: config.CodeOffset, // section bodies begin here Subsystem: IMAGE_SUBSYSTEM_WINDOWS_GUI, DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, SizeOfStackReserve: 0x100000, @@ -92,35 +91,29 @@ func New(code []byte, data []byte) *EXE { { Name: [8]byte{'.', 't', 'e', 'x', 't'}, VirtualSize: uint32(len(code)), - VirtualAddress: codeStart, + VirtualAddress: config.CodeOffset, RawSize: uint32(len(code)), // must be a multiple of FileAlignment - RawAddress: codeStart, // must be a multiple of FileAlignment + RawAddress: config.CodeOffset, // must be a multiple of FileAlignment Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ, }, { Name: [8]byte{'.', 'r', 'd', 'a', 't', 'a'}, VirtualSize: uint32(len(data)), - VirtualAddress: dataStart, + VirtualAddress: uint32(dataStart), RawSize: uint32(len(data)), // must be a multiple of FileAlignment - RawAddress: dataStart, // must be a multiple of FileAlignment + RawAddress: uint32(dataStart), // must be a multiple of FileAlignment Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, }, }, - CodePadding: bytes.Repeat([]byte{0}, int(codePadding)), - Code: code, - DataPadding: bytes.Repeat([]byte{0}, int(dataPadding)), - Data: data, } -} -// Write writes the EXE file to the given writer. -func (pe *EXE) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &pe.DOSHeader) binary.Write(writer, binary.LittleEndian, &pe.NTHeader) binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) binary.Write(writer, binary.LittleEndian, &pe.Sections) - binary.Write(writer, binary.LittleEndian, &pe.CodePadding) - binary.Write(writer, binary.LittleEndian, &pe.Code) - binary.Write(writer, binary.LittleEndian, &pe.DataPadding) - binary.Write(writer, binary.LittleEndian, &pe.Data) + + writer.Write(bytes.Repeat([]byte{0}, int(codePadding))) + writer.Write(code) + writer.Write(bytes.Repeat([]byte{0}, int(dataPadding))) + writer.Write(data) } diff --git a/src/os/windows/pe/ImportDirectory.go b/src/exe/pe/ImportDirectory.go similarity index 100% rename from src/os/windows/pe/ImportDirectory.go rename to src/exe/pe/ImportDirectory.go diff --git a/src/os/windows/pe/NTHeader.go b/src/exe/pe/NTHeader.go similarity index 100% rename from src/os/windows/pe/NTHeader.go rename to src/exe/pe/NTHeader.go diff --git a/src/os/windows/pe/OptionalHeader64.go b/src/exe/pe/OptionalHeader64.go similarity index 100% rename from src/os/windows/pe/OptionalHeader64.go rename to src/exe/pe/OptionalHeader64.go diff --git a/src/os/windows/pe/SectionHeader.go b/src/exe/pe/SectionHeader.go similarity index 100% rename from src/os/windows/pe/SectionHeader.go rename to src/exe/pe/SectionHeader.go diff --git a/src/exe/pe/pe.md b/src/exe/pe/pe.md new file mode 100644 index 0000000..ae779df --- /dev/null +++ b/src/exe/pe/pe.md @@ -0,0 +1,11 @@ +## Portable Executable + +## Links + +- https://learn.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10) +- https://learn.microsoft.com/en-us/archive/msdn-magazine/2002/february/inside-windows-win32-portable-executable-file-format-in-detail +- https://learn.microsoft.com/en-us/archive/msdn-magazine/2002/march/inside-windows-an-in-depth-look-into-the-win32-portable-executable-file-format-part-2 +- https://blog.kowalczyk.info/articles/pefileformat.html +- https://keyj.emphy.de/win32-pe/ +- https://corkamiwiki.github.io/PE +- https://github.com/ayaka14732/TinyPE-on-Win10 \ No newline at end of file diff --git a/src/os/linux/Syscall.go b/src/os/linux/Syscall.go index 7c94f6a..82eb953 100644 --- a/src/os/linux/Syscall.go +++ b/src/os/linux/Syscall.go @@ -2,9 +2,6 @@ package linux // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/entry/syscalls/syscall_64.tbl const ( - Read = 0 Write = 1 - Open = 2 - Close = 3 Exit = 60 ) diff --git a/src/os/linux/elf/ELF_test.go b/src/os/linux/elf/ELF_test.go deleted file mode 100644 index a2051d8..0000000 --- a/src/os/linux/elf/ELF_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package elf_test - -import ( - "io" - "testing" - - "git.akyoto.dev/cli/q/src/os/linux/elf" -) - -func TestELF(t *testing.T) { - exe := elf.New(nil, nil) - exe.Write(io.Discard) -} diff --git a/src/os/mac/macho/MachO.go b/src/os/mac/macho/MachO.go deleted file mode 100644 index a96e37b..0000000 --- a/src/os/mac/macho/MachO.go +++ /dev/null @@ -1,122 +0,0 @@ -package macho - -import ( - "bytes" - "encoding/binary" - "io" - - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/os/common" -) - -// MachO is the executable format used on MacOS. -type MachO struct { - Header - Code []byte - Data []byte -} - -// New creates a new Mach-O binary. -func New(code []byte, data []byte) *MachO { - return &MachO{ - Header: Header{ - Magic: 0xFEEDFACF, - Architecture: CPU_X86_64, - MicroArchitecture: 3 | 0x80000000, - Type: TypeExecute, - NumCommands: 4, - SizeCommands: Segment64Size*3 + ThreadSize, - Flags: FlagNoUndefs | FlagNoHeapExecution, - Reserved: 0, - }, - Code: code, - Data: data, - } -} - -// Write writes the Mach-O format to the given writer. -func (m *MachO) Write(writer io.Writer) { - binary.Write(writer, binary.LittleEndian, &m.Header) - - binary.Write(writer, binary.LittleEndian, &Segment64{ - LoadCommand: LcSegment64, - Length: 72, - Name: [16]byte{'_', '_', 'P', 'A', 'G', 'E', 'Z', 'E', 'R', 'O'}, - Address: 0, - SizeInMemory: config.BaseAddress, - Offset: 0, - SizeInFile: 0, - NumSections: 0, - Flag: 0, - MaxProt: 0, - InitProt: 0, - }) - - codePadding := common.Padding(HeaderSize+m.Header.SizeCommands, config.Align) - codeEnd := uint64(config.CodeOffset + len(m.Code)) - dataPadding := common.Padding(codeEnd, config.Align) - dataStart := codeEnd + dataPadding - - binary.Write(writer, binary.LittleEndian, &Segment64{ - LoadCommand: LcSegment64, - Length: 72, - Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, - Address: config.BaseAddress, - SizeInMemory: config.CodeOffset + uint64(len(m.Code)), - Offset: 0, - SizeInFile: config.CodeOffset + uint64(len(m.Code)), - NumSections: 0, - Flag: 0, - MaxProt: ProtReadable | ProtExecutable, - InitProt: ProtReadable | ProtExecutable, - }) - - binary.Write(writer, binary.LittleEndian, &Segment64{ - LoadCommand: LcSegment64, - Length: 72, - Name: [16]byte{'_', '_', 'D', 'A', 'T', 'A'}, - Address: config.BaseAddress + dataStart, - SizeInMemory: uint64(len(m.Data)), - Offset: dataStart, - SizeInFile: uint64(len(m.Data)), - NumSections: 0, - Flag: 0, - MaxProt: ProtReadable, - InitProt: ProtReadable, - }) - - binary.Write(writer, binary.LittleEndian, &Thread{ - LoadCommand: LcUnixthread, - Len: 184, - Type: 0x4, - Data: [43]uint32{ - 42, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - config.BaseAddress + config.CodeOffset, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - }, - }) - - writer.Write(bytes.Repeat([]byte{0}, int(codePadding))) - writer.Write(m.Code) - writer.Write(bytes.Repeat([]byte{0}, int(dataPadding))) - writer.Write(m.Data) -} From 7092cb66263a8d5381c7f39b3a183c95be34db71 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 15 Aug 2024 00:46:49 +0200 Subject: [PATCH 0526/1012] Simplified executable file formats --- src/asm/Finalize.go | 4 +- src/compiler/Result.go | 15 +-- src/{os/common => exe}/Padding.go | 2 +- src/{os/linux => exe}/elf/Constants.go | 0 src/{os/linux => exe}/elf/ELF.go | 51 +++---- src/exe/elf/ELF_test.go | 12 ++ src/{os/linux => exe}/elf/Header.go | 0 src/{os/linux => exe}/elf/ProgramHeader.go | 0 src/{os/linux => exe}/elf/SectionHeader.go | 0 src/{os/linux => exe}/elf/elf.md | 12 +- src/{os/mac => exe}/macho/Constants.go | 0 src/{os/mac => exe}/macho/Header.go | 0 src/{os/mac => exe}/macho/LoadCommand.go | 0 src/exe/macho/MachO.go | 126 ++++++++++++++++++ src/{os/mac => exe}/macho/Segment64.go | 0 src/{os/mac => exe}/macho/Thread.go | 0 src/exe/macho/macho.md | 16 +++ src/{os/windows => exe}/pe/Constants.go | 0 src/{os/windows => exe}/pe/DOSHeader.go | 0 src/{os/windows => exe}/pe/DataDirectory.go | 0 src/{os/windows => exe}/pe/EXE.go | 67 +++++----- src/{os/windows => exe}/pe/ImportDirectory.go | 0 src/{os/windows => exe}/pe/NTHeader.go | 0 .../windows => exe}/pe/OptionalHeader64.go | 0 src/{os/windows => exe}/pe/SectionHeader.go | 0 src/exe/pe/pe.md | 11 ++ src/os/linux/Syscall.go | 3 - src/os/linux/elf/ELF_test.go | 13 -- src/os/mac/macho/MachO.go | 122 ----------------- 29 files changed, 236 insertions(+), 218 deletions(-) rename src/{os/common => exe}/Padding.go (93%) rename src/{os/linux => exe}/elf/Constants.go (100%) rename src/{os/linux => exe}/elf/ELF.go (66%) create mode 100644 src/exe/elf/ELF_test.go rename src/{os/linux => exe}/elf/Header.go (100%) rename src/{os/linux => exe}/elf/ProgramHeader.go (100%) rename src/{os/linux => exe}/elf/SectionHeader.go (100%) rename src/{os/linux => exe}/elf/elf.md (61%) rename src/{os/mac => exe}/macho/Constants.go (100%) rename src/{os/mac => exe}/macho/Header.go (100%) rename src/{os/mac => exe}/macho/LoadCommand.go (100%) create mode 100644 src/exe/macho/MachO.go rename src/{os/mac => exe}/macho/Segment64.go (100%) rename src/{os/mac => exe}/macho/Thread.go (100%) create mode 100644 src/exe/macho/macho.md rename src/{os/windows => exe}/pe/Constants.go (100%) rename src/{os/windows => exe}/pe/DOSHeader.go (100%) rename src/{os/windows => exe}/pe/DataDirectory.go (100%) rename src/{os/windows => exe}/pe/EXE.go (67%) rename src/{os/windows => exe}/pe/ImportDirectory.go (100%) rename src/{os/windows => exe}/pe/NTHeader.go (100%) rename src/{os/windows => exe}/pe/OptionalHeader64.go (100%) rename src/{os/windows => exe}/pe/SectionHeader.go (100%) create mode 100644 src/exe/pe/pe.md delete mode 100644 src/os/linux/elf/ELF_test.go delete mode 100644 src/os/mac/macho/MachO.go diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index e0e0c87..9f46845 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -8,7 +8,7 @@ import ( "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/os/common" + "git.akyoto.dev/cli/q/src/exe" "git.akyoto.dev/cli/q/src/sizeof" ) @@ -339,7 +339,7 @@ restart: data, dataLabels = a.Data.Finalize() dataStart := Address(config.BaseAddress) + config.CodeOffset + Address(len(code)) - dataStart += int32(common.Padding(dataStart, config.Align)) + dataStart += exe.Padding(dataStart, config.Align) for _, pointer := range dataPointers { address := dataStart + pointer.Resolve() diff --git a/src/compiler/Result.go b/src/compiler/Result.go index f21ea29..d9f7fcf 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -10,11 +10,11 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/exe/elf" + "git.akyoto.dev/cli/q/src/exe/macho" + "git.akyoto.dev/cli/q/src/exe/pe" "git.akyoto.dev/cli/q/src/os/linux" - "git.akyoto.dev/cli/q/src/os/linux/elf" "git.akyoto.dev/cli/q/src/os/mac" - "git.akyoto.dev/cli/q/src/os/mac/macho" - "git.akyoto.dev/cli/q/src/os/windows/pe" ) // Result contains all the compiled functions in a build. @@ -147,14 +147,11 @@ func write(writer io.Writer, code []byte, data []byte) error { switch config.TargetOS { case "linux": - exe := elf.New(code, data) - exe.Write(buffer) + elf.Write(buffer, code, data) case "mac": - exe := macho.New(code, data) - exe.Write(buffer) + macho.Write(buffer, code, data) case "windows": - exe := pe.New(code, data) - exe.Write(buffer) + pe.Write(buffer, code, data) default: return fmt.Errorf("unsupported platform '%s'", config.TargetOS) } diff --git a/src/os/common/Padding.go b/src/exe/Padding.go similarity index 93% rename from src/os/common/Padding.go rename to src/exe/Padding.go index 4f2d726..bf8195c 100644 --- a/src/os/common/Padding.go +++ b/src/exe/Padding.go @@ -1,4 +1,4 @@ -package common +package exe // Padding calculates the padding needed to align `n` bytes with the specified alignment. func Padding[T int | uint | int64 | uint64 | int32 | uint32](n T, align T) T { diff --git a/src/os/linux/elf/Constants.go b/src/exe/elf/Constants.go similarity index 100% rename from src/os/linux/elf/Constants.go rename to src/exe/elf/Constants.go diff --git a/src/os/linux/elf/ELF.go b/src/exe/elf/ELF.go similarity index 66% rename from src/os/linux/elf/ELF.go rename to src/exe/elf/ELF.go index fb8b517..413ece9 100644 --- a/src/os/linux/elf/ELF.go +++ b/src/exe/elf/ELF.go @@ -6,28 +6,28 @@ import ( "io" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/os/common" + "git.akyoto.dev/cli/q/src/exe" ) // ELF represents an ELF file. type ELF struct { Header - CodeHeader ProgramHeader - DataHeader ProgramHeader - CodePadding []byte - Code []byte - DataPadding []byte - Data []byte + CodeHeader ProgramHeader + DataHeader ProgramHeader } -// New creates a new ELF binary. -func New(code []byte, data []byte) *ELF { - codePadding := common.Padding(HeaderSize+ProgramHeaderSize*2, config.Align) - dataOffset := config.CodeOffset + int64(len(code)) - dataPadding := common.Padding(dataOffset, config.Align) - dataOffset += dataPadding +// Write writes the ELF64 format to the given writer. +func Write(writer io.Writer, code []byte, data []byte) { + const HeaderEnd = HeaderSize + ProgramHeaderSize*2 - return &ELF{ + var ( + codePadding = exe.Padding(HeaderEnd, config.Align) + codeEnd = config.CodeOffset + len(code) + dataPadding = exe.Padding(codeEnd, config.Align) + dataStart = codeEnd + dataPadding + ) + + elf := &ELF{ Header: Header{ Magic: [4]byte{0x7F, 'E', 'L', 'F'}, Class: 2, @@ -62,30 +62,23 @@ func New(code []byte, data []byte) *ELF { DataHeader: ProgramHeader{ Type: ProgramTypeLOAD, Flags: ProgramFlagsReadable, - Offset: dataOffset, - VirtualAddress: config.BaseAddress + dataOffset, - PhysicalAddress: config.BaseAddress + dataOffset, + Offset: int64(dataStart), + VirtualAddress: int64(config.BaseAddress + dataStart), + PhysicalAddress: int64(config.BaseAddress + dataStart), SizeInFile: int64(len(data)), SizeInMemory: int64(len(data)), Align: config.Align, }, - CodePadding: bytes.Repeat([]byte{0}, int(codePadding)), - Code: code, - DataPadding: bytes.Repeat([]byte{0}, int(dataPadding)), - Data: data, } -} -// Write writes the ELF64 format to the given writer. -func (elf *ELF) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.CodeHeader) binary.Write(writer, binary.LittleEndian, &elf.DataHeader) - writer.Write(elf.CodePadding) - writer.Write(elf.Code) + writer.Write(bytes.Repeat([]byte{0}, codePadding)) + writer.Write(code) - if len(elf.Data) > 0 { - writer.Write(elf.DataPadding) - writer.Write(elf.Data) + if len(data) > 0 { + writer.Write(bytes.Repeat([]byte{0}, dataPadding)) + writer.Write(data) } } diff --git a/src/exe/elf/ELF_test.go b/src/exe/elf/ELF_test.go new file mode 100644 index 0000000..aa28d0d --- /dev/null +++ b/src/exe/elf/ELF_test.go @@ -0,0 +1,12 @@ +package elf_test + +import ( + "io" + "testing" + + "git.akyoto.dev/cli/q/src/exe/elf" +) + +func TestWrite(t *testing.T) { + elf.Write(io.Discard, nil, nil) +} diff --git a/src/os/linux/elf/Header.go b/src/exe/elf/Header.go similarity index 100% rename from src/os/linux/elf/Header.go rename to src/exe/elf/Header.go diff --git a/src/os/linux/elf/ProgramHeader.go b/src/exe/elf/ProgramHeader.go similarity index 100% rename from src/os/linux/elf/ProgramHeader.go rename to src/exe/elf/ProgramHeader.go diff --git a/src/os/linux/elf/SectionHeader.go b/src/exe/elf/SectionHeader.go similarity index 100% rename from src/os/linux/elf/SectionHeader.go rename to src/exe/elf/SectionHeader.go diff --git a/src/os/linux/elf/elf.md b/src/exe/elf/elf.md similarity index 61% rename from src/os/linux/elf/elf.md rename to src/exe/elf/elf.md index c40f005..92a0096 100644 --- a/src/os/linux/elf/elf.md +++ b/src/exe/elf/elf.md @@ -4,8 +4,8 @@ 1. ELF header (0x00 - 0x40) 2. Program header (0x40 - 0x78) -3. Padding (0x78 - 0x80) -4. Machine code (0x80) +3. Padding +4. Machine code ## Entry point @@ -28,3 +28,11 @@ https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/binfm ELF register definitions: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/include/asm/elf.h + +## Links + +- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/elf.h +- https://lwn.net/Articles/631631/ +- https://en.wikipedia.org/wiki/Executable_and_Linkable_Format +- https://www.muppetlabs.com/~breadbox/software/tiny/teensy.html +- https://nathanotterness.com/2021/10/tiny_elf_modernized.html \ No newline at end of file diff --git a/src/os/mac/macho/Constants.go b/src/exe/macho/Constants.go similarity index 100% rename from src/os/mac/macho/Constants.go rename to src/exe/macho/Constants.go diff --git a/src/os/mac/macho/Header.go b/src/exe/macho/Header.go similarity index 100% rename from src/os/mac/macho/Header.go rename to src/exe/macho/Header.go diff --git a/src/os/mac/macho/LoadCommand.go b/src/exe/macho/LoadCommand.go similarity index 100% rename from src/os/mac/macho/LoadCommand.go rename to src/exe/macho/LoadCommand.go diff --git a/src/exe/macho/MachO.go b/src/exe/macho/MachO.go new file mode 100644 index 0000000..5f2cbcb --- /dev/null +++ b/src/exe/macho/MachO.go @@ -0,0 +1,126 @@ +package macho + +import ( + "bytes" + "encoding/binary" + "io" + + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/exe" +) + +// MachO is the executable format used on MacOS. +type MachO struct { + Header + PageZero Segment64 + CodeHeader Segment64 + DataHeader Segment64 + UnixThread Thread +} + +// Write writes the Mach-O format to the given writer. +func Write(writer io.Writer, code []byte, data []byte) { + const ( + SizeCommands = Segment64Size*3 + ThreadSize + HeaderEnd = HeaderSize + SizeCommands + ) + + var ( + codePadding = exe.Padding(HeaderEnd, config.Align) + codeEnd = config.CodeOffset + len(code) + dataPadding = exe.Padding(codeEnd, config.Align) + dataStart = codeEnd + dataPadding + ) + + m := &MachO{ + Header: Header{ + Magic: 0xFEEDFACF, + Architecture: CPU_X86_64, + MicroArchitecture: 3 | 0x80000000, + Type: TypeExecute, + NumCommands: 4, + SizeCommands: SizeCommands, + Flags: FlagNoUndefs | FlagNoHeapExecution, + Reserved: 0, + }, + PageZero: Segment64{ + LoadCommand: LcSegment64, + Length: 72, + Name: [16]byte{'_', '_', 'P', 'A', 'G', 'E', 'Z', 'E', 'R', 'O'}, + Address: 0, + SizeInMemory: config.BaseAddress, + Offset: 0, + SizeInFile: 0, + NumSections: 0, + Flag: 0, + MaxProt: 0, + InitProt: 0, + }, + CodeHeader: Segment64{ + LoadCommand: LcSegment64, + Length: Segment64Size, + Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, + Address: config.BaseAddress, + SizeInMemory: uint64(codeEnd), + Offset: 0, + SizeInFile: uint64(codeEnd), + NumSections: 0, + Flag: 0, + MaxProt: ProtReadable | ProtExecutable, + InitProt: ProtReadable | ProtExecutable, + }, + DataHeader: Segment64{ + LoadCommand: LcSegment64, + Length: Segment64Size, + Name: [16]byte{'_', '_', 'D', 'A', 'T', 'A'}, + Address: uint64(config.BaseAddress + dataStart), + SizeInMemory: uint64(len(data)), + Offset: uint64(dataStart), + SizeInFile: uint64(len(data)), + NumSections: 0, + Flag: 0, + MaxProt: ProtReadable, + InitProt: ProtReadable, + }, + UnixThread: Thread{ + LoadCommand: LcUnixthread, + Len: ThreadSize, + Type: 0x4, + Data: [43]uint32{ + 42, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + config.BaseAddress + config.CodeOffset, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + }, + }, + } + + binary.Write(writer, binary.LittleEndian, &m.Header) + binary.Write(writer, binary.LittleEndian, &m.PageZero) + binary.Write(writer, binary.LittleEndian, &m.CodeHeader) + binary.Write(writer, binary.LittleEndian, &m.DataHeader) + binary.Write(writer, binary.LittleEndian, &m.UnixThread) + + writer.Write(bytes.Repeat([]byte{0}, int(codePadding))) + writer.Write(code) + writer.Write(bytes.Repeat([]byte{0}, int(dataPadding))) + writer.Write(data) +} diff --git a/src/os/mac/macho/Segment64.go b/src/exe/macho/Segment64.go similarity index 100% rename from src/os/mac/macho/Segment64.go rename to src/exe/macho/Segment64.go diff --git a/src/os/mac/macho/Thread.go b/src/exe/macho/Thread.go similarity index 100% rename from src/os/mac/macho/Thread.go rename to src/exe/macho/Thread.go diff --git a/src/exe/macho/macho.md b/src/exe/macho/macho.md new file mode 100644 index 0000000..01c1be6 --- /dev/null +++ b/src/exe/macho/macho.md @@ -0,0 +1,16 @@ +# Mach-O + +## Notes + +MacOS is the only operating system that requires loading the headers +explicitly with both readable and executable permissions. + +## Loader + +https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/loader.h + +## Links + +- https://en.wikipedia.org/wiki/Mach-O +- https://github.com/aidansteele/osx-abi-macho-file-format-reference +- https://stackoverflow.com/questions/39863112/what-is-required-for-a-mach-o-executable-to-load \ No newline at end of file diff --git a/src/os/windows/pe/Constants.go b/src/exe/pe/Constants.go similarity index 100% rename from src/os/windows/pe/Constants.go rename to src/exe/pe/Constants.go diff --git a/src/os/windows/pe/DOSHeader.go b/src/exe/pe/DOSHeader.go similarity index 100% rename from src/os/windows/pe/DOSHeader.go rename to src/exe/pe/DOSHeader.go diff --git a/src/os/windows/pe/DataDirectory.go b/src/exe/pe/DataDirectory.go similarity index 100% rename from src/os/windows/pe/DataDirectory.go rename to src/exe/pe/DataDirectory.go diff --git a/src/os/windows/pe/EXE.go b/src/exe/pe/EXE.go similarity index 67% rename from src/os/windows/pe/EXE.go rename to src/exe/pe/EXE.go index f1d5ff3..f9e55c4 100644 --- a/src/os/windows/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -6,7 +6,7 @@ import ( "io" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/os/common" + "git.akyoto.dev/cli/q/src/exe" ) const NumSections = 2 @@ -16,30 +16,29 @@ type EXE struct { DOSHeader NTHeader OptionalHeader64 - Sections [NumSections]SectionHeader - CodePadding []byte - Code []byte - DataPadding []byte - Data []byte + Sections [NumSections]SectionHeader } -// New creates a new EXE file. -func New(code []byte, data []byte) *EXE { - codeStart := uint32(DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections) - codePadding := common.Padding(codeStart, config.Align) - codeStart += codePadding +// Write writes the EXE file to the given writer. +func Write(writer io.Writer, code []byte, data []byte) { + const ( + HeaderEnd = DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections + ) - dataStart := codeStart + uint32(len(code)) - dataPadding := common.Padding(dataStart, config.Align) - dataStart += dataPadding + var ( + codePadding = exe.Padding(HeaderEnd, config.Align) + codeEnd = config.CodeOffset + len(code) + dataPadding = exe.Padding(codeEnd, config.Align) + dataStart = codeEnd + dataPadding + ) - imageSize := uint32(dataStart + uint32(len(data))) - imageSize += common.Padding(imageSize, config.Align) + imageSize := dataStart + len(data) + imageSize += exe.Padding(imageSize, config.Align) - return &EXE{ + pe := &EXE{ DOSHeader: DOSHeader{ Magic: [4]byte{'M', 'Z', 0, 0}, - NTHeaderOffset: 0x40, + NTHeaderOffset: DOSHeaderSize, }, NTHeader: NTHeader{ Signature: [4]byte{'P', 'E', 0, 0}, @@ -53,15 +52,15 @@ func New(code []byte, data []byte) *EXE { MajorLinkerVersion: 0x0E, MinorLinkerVersion: 0x16, SizeOfCode: uint32(len(code)), - AddressOfEntryPoint: codeStart, - BaseOfCode: codeStart, + AddressOfEntryPoint: config.CodeOffset, + BaseOfCode: config.CodeOffset, ImageBase: config.BaseAddress, SectionAlignment: config.Align, // power of 2, must be greater than or equal to FileAlignment FileAlignment: config.Align, // power of 2 MajorOperatingSystemVersion: 0x06, MajorSubsystemVersion: 0x06, - SizeOfImage: imageSize, - SizeOfHeaders: codeStart, // section bodies begin here + SizeOfImage: uint32(imageSize), + SizeOfHeaders: config.CodeOffset, // section bodies begin here Subsystem: IMAGE_SUBSYSTEM_WINDOWS_GUI, DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, SizeOfStackReserve: 0x100000, @@ -92,35 +91,29 @@ func New(code []byte, data []byte) *EXE { { Name: [8]byte{'.', 't', 'e', 'x', 't'}, VirtualSize: uint32(len(code)), - VirtualAddress: codeStart, + VirtualAddress: config.CodeOffset, RawSize: uint32(len(code)), // must be a multiple of FileAlignment - RawAddress: codeStart, // must be a multiple of FileAlignment + RawAddress: config.CodeOffset, // must be a multiple of FileAlignment Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ, }, { Name: [8]byte{'.', 'r', 'd', 'a', 't', 'a'}, VirtualSize: uint32(len(data)), - VirtualAddress: dataStart, + VirtualAddress: uint32(dataStart), RawSize: uint32(len(data)), // must be a multiple of FileAlignment - RawAddress: dataStart, // must be a multiple of FileAlignment + RawAddress: uint32(dataStart), // must be a multiple of FileAlignment Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, }, }, - CodePadding: bytes.Repeat([]byte{0}, int(codePadding)), - Code: code, - DataPadding: bytes.Repeat([]byte{0}, int(dataPadding)), - Data: data, } -} -// Write writes the EXE file to the given writer. -func (pe *EXE) Write(writer io.Writer) { binary.Write(writer, binary.LittleEndian, &pe.DOSHeader) binary.Write(writer, binary.LittleEndian, &pe.NTHeader) binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) binary.Write(writer, binary.LittleEndian, &pe.Sections) - binary.Write(writer, binary.LittleEndian, &pe.CodePadding) - binary.Write(writer, binary.LittleEndian, &pe.Code) - binary.Write(writer, binary.LittleEndian, &pe.DataPadding) - binary.Write(writer, binary.LittleEndian, &pe.Data) + + writer.Write(bytes.Repeat([]byte{0}, int(codePadding))) + writer.Write(code) + writer.Write(bytes.Repeat([]byte{0}, int(dataPadding))) + writer.Write(data) } diff --git a/src/os/windows/pe/ImportDirectory.go b/src/exe/pe/ImportDirectory.go similarity index 100% rename from src/os/windows/pe/ImportDirectory.go rename to src/exe/pe/ImportDirectory.go diff --git a/src/os/windows/pe/NTHeader.go b/src/exe/pe/NTHeader.go similarity index 100% rename from src/os/windows/pe/NTHeader.go rename to src/exe/pe/NTHeader.go diff --git a/src/os/windows/pe/OptionalHeader64.go b/src/exe/pe/OptionalHeader64.go similarity index 100% rename from src/os/windows/pe/OptionalHeader64.go rename to src/exe/pe/OptionalHeader64.go diff --git a/src/os/windows/pe/SectionHeader.go b/src/exe/pe/SectionHeader.go similarity index 100% rename from src/os/windows/pe/SectionHeader.go rename to src/exe/pe/SectionHeader.go diff --git a/src/exe/pe/pe.md b/src/exe/pe/pe.md new file mode 100644 index 0000000..ae779df --- /dev/null +++ b/src/exe/pe/pe.md @@ -0,0 +1,11 @@ +## Portable Executable + +## Links + +- https://learn.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10) +- https://learn.microsoft.com/en-us/archive/msdn-magazine/2002/february/inside-windows-win32-portable-executable-file-format-in-detail +- https://learn.microsoft.com/en-us/archive/msdn-magazine/2002/march/inside-windows-an-in-depth-look-into-the-win32-portable-executable-file-format-part-2 +- https://blog.kowalczyk.info/articles/pefileformat.html +- https://keyj.emphy.de/win32-pe/ +- https://corkamiwiki.github.io/PE +- https://github.com/ayaka14732/TinyPE-on-Win10 \ No newline at end of file diff --git a/src/os/linux/Syscall.go b/src/os/linux/Syscall.go index 7c94f6a..82eb953 100644 --- a/src/os/linux/Syscall.go +++ b/src/os/linux/Syscall.go @@ -2,9 +2,6 @@ package linux // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/entry/syscalls/syscall_64.tbl const ( - Read = 0 Write = 1 - Open = 2 - Close = 3 Exit = 60 ) diff --git a/src/os/linux/elf/ELF_test.go b/src/os/linux/elf/ELF_test.go deleted file mode 100644 index a2051d8..0000000 --- a/src/os/linux/elf/ELF_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package elf_test - -import ( - "io" - "testing" - - "git.akyoto.dev/cli/q/src/os/linux/elf" -) - -func TestELF(t *testing.T) { - exe := elf.New(nil, nil) - exe.Write(io.Discard) -} diff --git a/src/os/mac/macho/MachO.go b/src/os/mac/macho/MachO.go deleted file mode 100644 index a96e37b..0000000 --- a/src/os/mac/macho/MachO.go +++ /dev/null @@ -1,122 +0,0 @@ -package macho - -import ( - "bytes" - "encoding/binary" - "io" - - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/os/common" -) - -// MachO is the executable format used on MacOS. -type MachO struct { - Header - Code []byte - Data []byte -} - -// New creates a new Mach-O binary. -func New(code []byte, data []byte) *MachO { - return &MachO{ - Header: Header{ - Magic: 0xFEEDFACF, - Architecture: CPU_X86_64, - MicroArchitecture: 3 | 0x80000000, - Type: TypeExecute, - NumCommands: 4, - SizeCommands: Segment64Size*3 + ThreadSize, - Flags: FlagNoUndefs | FlagNoHeapExecution, - Reserved: 0, - }, - Code: code, - Data: data, - } -} - -// Write writes the Mach-O format to the given writer. -func (m *MachO) Write(writer io.Writer) { - binary.Write(writer, binary.LittleEndian, &m.Header) - - binary.Write(writer, binary.LittleEndian, &Segment64{ - LoadCommand: LcSegment64, - Length: 72, - Name: [16]byte{'_', '_', 'P', 'A', 'G', 'E', 'Z', 'E', 'R', 'O'}, - Address: 0, - SizeInMemory: config.BaseAddress, - Offset: 0, - SizeInFile: 0, - NumSections: 0, - Flag: 0, - MaxProt: 0, - InitProt: 0, - }) - - codePadding := common.Padding(HeaderSize+m.Header.SizeCommands, config.Align) - codeEnd := uint64(config.CodeOffset + len(m.Code)) - dataPadding := common.Padding(codeEnd, config.Align) - dataStart := codeEnd + dataPadding - - binary.Write(writer, binary.LittleEndian, &Segment64{ - LoadCommand: LcSegment64, - Length: 72, - Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, - Address: config.BaseAddress, - SizeInMemory: config.CodeOffset + uint64(len(m.Code)), - Offset: 0, - SizeInFile: config.CodeOffset + uint64(len(m.Code)), - NumSections: 0, - Flag: 0, - MaxProt: ProtReadable | ProtExecutable, - InitProt: ProtReadable | ProtExecutable, - }) - - binary.Write(writer, binary.LittleEndian, &Segment64{ - LoadCommand: LcSegment64, - Length: 72, - Name: [16]byte{'_', '_', 'D', 'A', 'T', 'A'}, - Address: config.BaseAddress + dataStart, - SizeInMemory: uint64(len(m.Data)), - Offset: dataStart, - SizeInFile: uint64(len(m.Data)), - NumSections: 0, - Flag: 0, - MaxProt: ProtReadable, - InitProt: ProtReadable, - }) - - binary.Write(writer, binary.LittleEndian, &Thread{ - LoadCommand: LcUnixthread, - Len: 184, - Type: 0x4, - Data: [43]uint32{ - 42, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - config.BaseAddress + config.CodeOffset, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - }, - }) - - writer.Write(bytes.Repeat([]byte{0}, int(codePadding))) - writer.Write(m.Code) - writer.Write(bytes.Repeat([]byte{0}, int(dataPadding))) - writer.Write(m.Data) -} From c7881ab0ef1757826f72a6f5050a54bde725067d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 15 Aug 2024 13:53:00 +0200 Subject: [PATCH 0527/1012] Improved alignment function --- lib/sys/sys_windows.q | 4 ++++ src/asm/Finalize.go | 7 +++---- src/exe/Align.go | 7 +++++++ src/exe/Padding.go | 6 ------ src/exe/elf/ELF.go | 10 ++++----- src/exe/macho/MachO.go | 14 ++++++------- src/exe/pe/EXE.go | 46 +++++++++++++++++++++++++----------------- src/exe/pe/pe.md | 5 +++++ 8 files changed, 56 insertions(+), 43 deletions(-) create mode 100644 src/exe/Align.go delete mode 100644 src/exe/Padding.go diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q index dfab144..105b33d 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/sys_windows.q @@ -1,3 +1,7 @@ write(_ Int, _ Pointer, _ Int) -> Int { return 0 +} + +exit(_ Int) { + return } \ No newline at end of file diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 9f46845..4276e38 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -337,12 +337,11 @@ restart: } data, dataLabels = a.Data.Finalize() - - dataStart := Address(config.BaseAddress) + config.CodeOffset + Address(len(code)) - dataStart += exe.Padding(dataStart, config.Align) + dataStart := config.BaseAddress + config.CodeOffset + len(code) + dataStart, _ = exe.Align(dataStart, config.Align) for _, pointer := range dataPointers { - address := dataStart + pointer.Resolve() + address := Address(dataStart) + pointer.Resolve() slice := code[pointer.Position : pointer.Position+4] binary.LittleEndian.PutUint32(slice, uint32(address)) } diff --git a/src/exe/Align.go b/src/exe/Align.go new file mode 100644 index 0000000..024df62 --- /dev/null +++ b/src/exe/Align.go @@ -0,0 +1,7 @@ +package exe + +// Align calculates the next aligned address and the padding needed. +func Align[T int | uint | int64 | uint64 | int32 | uint32](n T, alignment T) (T, T) { + aligned := (n + (alignment - 1)) & ^(alignment - 1) + return aligned, aligned - n +} diff --git a/src/exe/Padding.go b/src/exe/Padding.go deleted file mode 100644 index bf8195c..0000000 --- a/src/exe/Padding.go +++ /dev/null @@ -1,6 +0,0 @@ -package exe - -// Padding calculates the padding needed to align `n` bytes with the specified alignment. -func Padding[T int | uint | int64 | uint64 | int32 | uint32](n T, align T) T { - return align - (n % align) -} diff --git a/src/exe/elf/ELF.go b/src/exe/elf/ELF.go index 413ece9..af9b469 100644 --- a/src/exe/elf/ELF.go +++ b/src/exe/elf/ELF.go @@ -21,10 +21,8 @@ func Write(writer io.Writer, code []byte, data []byte) { const HeaderEnd = HeaderSize + ProgramHeaderSize*2 var ( - codePadding = exe.Padding(HeaderEnd, config.Align) - codeEnd = config.CodeOffset + len(code) - dataPadding = exe.Padding(codeEnd, config.Align) - dataStart = codeEnd + dataPadding + codeStart, codePadding = exe.Align(HeaderEnd, config.Align) + dataStart, dataPadding = exe.Align(codeStart+len(code), config.Align) ) elf := &ELF{ @@ -74,11 +72,11 @@ func Write(writer io.Writer, code []byte, data []byte) { binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.CodeHeader) binary.Write(writer, binary.LittleEndian, &elf.DataHeader) - writer.Write(bytes.Repeat([]byte{0}, codePadding)) + writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) writer.Write(code) if len(data) > 0 { - writer.Write(bytes.Repeat([]byte{0}, dataPadding)) + writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) writer.Write(data) } } diff --git a/src/exe/macho/MachO.go b/src/exe/macho/MachO.go index 5f2cbcb..ef44ff1 100644 --- a/src/exe/macho/MachO.go +++ b/src/exe/macho/MachO.go @@ -26,10 +26,8 @@ func Write(writer io.Writer, code []byte, data []byte) { ) var ( - codePadding = exe.Padding(HeaderEnd, config.Align) - codeEnd = config.CodeOffset + len(code) - dataPadding = exe.Padding(codeEnd, config.Align) - dataStart = codeEnd + dataPadding + codeStart, codePadding = exe.Align(HeaderEnd, config.Align) + dataStart, dataPadding = exe.Align(codeStart+len(code), config.Align) ) m := &MachO{ @@ -61,9 +59,9 @@ func Write(writer io.Writer, code []byte, data []byte) { Length: Segment64Size, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, Address: config.BaseAddress, - SizeInMemory: uint64(codeEnd), + SizeInMemory: uint64(codeStart + len(code)), Offset: 0, - SizeInFile: uint64(codeEnd), + SizeInFile: uint64(codeStart + len(code)), NumSections: 0, Flag: 0, MaxProt: ProtReadable | ProtExecutable, @@ -119,8 +117,8 @@ func Write(writer io.Writer, code []byte, data []byte) { binary.Write(writer, binary.LittleEndian, &m.DataHeader) binary.Write(writer, binary.LittleEndian, &m.UnixThread) - writer.Write(bytes.Repeat([]byte{0}, int(codePadding))) + writer.Write(bytes.Repeat([]byte{0x00}, int(codePadding))) writer.Write(code) - writer.Write(bytes.Repeat([]byte{0}, int(dataPadding))) + writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) writer.Write(data) } diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index f9e55c4..09f96f3 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -9,31 +9,36 @@ import ( "git.akyoto.dev/cli/q/src/exe" ) -const NumSections = 2 - // EXE is the portable executable format used on Windows. type EXE struct { DOSHeader NTHeader OptionalHeader64 - Sections [NumSections]SectionHeader + Sections []SectionHeader } // Write writes the EXE file to the given writer. func Write(writer io.Writer, code []byte, data []byte) { - const ( - HeaderEnd = DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections - ) + NumSections := 1 + + if len(data) != 0 { + NumSections += 1 + } + + HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections var ( - codePadding = exe.Padding(HeaderEnd, config.Align) - codeEnd = config.CodeOffset + len(code) - dataPadding = exe.Padding(codeEnd, config.Align) - dataStart = codeEnd + dataPadding + codeStart, codePadding = exe.Align(HeaderEnd, config.Align) + dataStart, dataPadding = exe.Align(codeStart+len(code), config.Align) ) - imageSize := dataStart + len(data) - imageSize += exe.Padding(imageSize, config.Align) + imageSize := codeStart + len(code) + + if len(data) != 0 { + imageSize = dataStart + len(data) + } + + imageSize, _ = exe.Align(imageSize, config.Align) pe := &EXE{ DOSHeader: DOSHeader{ @@ -43,7 +48,7 @@ func Write(writer io.Writer, code []byte, data []byte) { NTHeader: NTHeader{ Signature: [4]byte{'P', 'E', 0, 0}, Machine: IMAGE_FILE_MACHINE_AMD64, - NumberOfSections: NumSections, + NumberOfSections: uint16(NumSections), SizeOfOptionalHeader: OptionalHeader64Size, Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE, }, @@ -61,7 +66,7 @@ func Write(writer io.Writer, code []byte, data []byte) { MajorSubsystemVersion: 0x06, SizeOfImage: uint32(imageSize), SizeOfHeaders: config.CodeOffset, // section bodies begin here - Subsystem: IMAGE_SUBSYSTEM_WINDOWS_GUI, + Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI, DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, SizeOfStackReserve: 0x100000, SizeOfStackCommit: 0x1000, @@ -87,7 +92,7 @@ func Write(writer io.Writer, code []byte, data []byte) { {VirtualAddress: 0, Size: 0}, }, }, - Sections: [NumSections]SectionHeader{ + Sections: []SectionHeader{ { Name: [8]byte{'.', 't', 'e', 'x', 't'}, VirtualSize: uint32(len(code)), @@ -110,10 +115,13 @@ func Write(writer io.Writer, code []byte, data []byte) { binary.Write(writer, binary.LittleEndian, &pe.DOSHeader) binary.Write(writer, binary.LittleEndian, &pe.NTHeader) binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) - binary.Write(writer, binary.LittleEndian, &pe.Sections) + binary.Write(writer, binary.LittleEndian, pe.Sections[:NumSections]) - writer.Write(bytes.Repeat([]byte{0}, int(codePadding))) + writer.Write(bytes.Repeat([]byte{0x00}, int(codePadding))) writer.Write(code) - writer.Write(bytes.Repeat([]byte{0}, int(dataPadding))) - writer.Write(data) + + if len(data) != 0 { + writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) + writer.Write(data) + } } diff --git a/src/exe/pe/pe.md b/src/exe/pe/pe.md index ae779df..7297fbb 100644 --- a/src/exe/pe/pe.md +++ b/src/exe/pe/pe.md @@ -1,5 +1,10 @@ ## Portable Executable +## Notes + +Unlike Linux, Windows does not ignore zero-length sections and will fail loading them because they don't exist within the file. +Adding a single byte to the section can fix this problem, but it's easier to just remove the section header entirely. + ## Links - https://learn.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10) From bec409dbd0359750f7bf8c224d5160922ec28713 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 15 Aug 2024 13:53:00 +0200 Subject: [PATCH 0528/1012] Improved alignment function --- lib/sys/sys_windows.q | 4 ++++ src/asm/Finalize.go | 7 +++---- src/exe/Align.go | 7 +++++++ src/exe/Padding.go | 6 ------ src/exe/elf/ELF.go | 10 ++++----- src/exe/macho/MachO.go | 14 ++++++------- src/exe/pe/EXE.go | 46 +++++++++++++++++++++++++----------------- src/exe/pe/pe.md | 5 +++++ 8 files changed, 56 insertions(+), 43 deletions(-) create mode 100644 src/exe/Align.go delete mode 100644 src/exe/Padding.go diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q index dfab144..105b33d 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/sys_windows.q @@ -1,3 +1,7 @@ write(_ Int, _ Pointer, _ Int) -> Int { return 0 +} + +exit(_ Int) { + return } \ No newline at end of file diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 9f46845..4276e38 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -337,12 +337,11 @@ restart: } data, dataLabels = a.Data.Finalize() - - dataStart := Address(config.BaseAddress) + config.CodeOffset + Address(len(code)) - dataStart += exe.Padding(dataStart, config.Align) + dataStart := config.BaseAddress + config.CodeOffset + len(code) + dataStart, _ = exe.Align(dataStart, config.Align) for _, pointer := range dataPointers { - address := dataStart + pointer.Resolve() + address := Address(dataStart) + pointer.Resolve() slice := code[pointer.Position : pointer.Position+4] binary.LittleEndian.PutUint32(slice, uint32(address)) } diff --git a/src/exe/Align.go b/src/exe/Align.go new file mode 100644 index 0000000..024df62 --- /dev/null +++ b/src/exe/Align.go @@ -0,0 +1,7 @@ +package exe + +// Align calculates the next aligned address and the padding needed. +func Align[T int | uint | int64 | uint64 | int32 | uint32](n T, alignment T) (T, T) { + aligned := (n + (alignment - 1)) & ^(alignment - 1) + return aligned, aligned - n +} diff --git a/src/exe/Padding.go b/src/exe/Padding.go deleted file mode 100644 index bf8195c..0000000 --- a/src/exe/Padding.go +++ /dev/null @@ -1,6 +0,0 @@ -package exe - -// Padding calculates the padding needed to align `n` bytes with the specified alignment. -func Padding[T int | uint | int64 | uint64 | int32 | uint32](n T, align T) T { - return align - (n % align) -} diff --git a/src/exe/elf/ELF.go b/src/exe/elf/ELF.go index 413ece9..af9b469 100644 --- a/src/exe/elf/ELF.go +++ b/src/exe/elf/ELF.go @@ -21,10 +21,8 @@ func Write(writer io.Writer, code []byte, data []byte) { const HeaderEnd = HeaderSize + ProgramHeaderSize*2 var ( - codePadding = exe.Padding(HeaderEnd, config.Align) - codeEnd = config.CodeOffset + len(code) - dataPadding = exe.Padding(codeEnd, config.Align) - dataStart = codeEnd + dataPadding + codeStart, codePadding = exe.Align(HeaderEnd, config.Align) + dataStart, dataPadding = exe.Align(codeStart+len(code), config.Align) ) elf := &ELF{ @@ -74,11 +72,11 @@ func Write(writer io.Writer, code []byte, data []byte) { binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.CodeHeader) binary.Write(writer, binary.LittleEndian, &elf.DataHeader) - writer.Write(bytes.Repeat([]byte{0}, codePadding)) + writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) writer.Write(code) if len(data) > 0 { - writer.Write(bytes.Repeat([]byte{0}, dataPadding)) + writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) writer.Write(data) } } diff --git a/src/exe/macho/MachO.go b/src/exe/macho/MachO.go index 5f2cbcb..ef44ff1 100644 --- a/src/exe/macho/MachO.go +++ b/src/exe/macho/MachO.go @@ -26,10 +26,8 @@ func Write(writer io.Writer, code []byte, data []byte) { ) var ( - codePadding = exe.Padding(HeaderEnd, config.Align) - codeEnd = config.CodeOffset + len(code) - dataPadding = exe.Padding(codeEnd, config.Align) - dataStart = codeEnd + dataPadding + codeStart, codePadding = exe.Align(HeaderEnd, config.Align) + dataStart, dataPadding = exe.Align(codeStart+len(code), config.Align) ) m := &MachO{ @@ -61,9 +59,9 @@ func Write(writer io.Writer, code []byte, data []byte) { Length: Segment64Size, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, Address: config.BaseAddress, - SizeInMemory: uint64(codeEnd), + SizeInMemory: uint64(codeStart + len(code)), Offset: 0, - SizeInFile: uint64(codeEnd), + SizeInFile: uint64(codeStart + len(code)), NumSections: 0, Flag: 0, MaxProt: ProtReadable | ProtExecutable, @@ -119,8 +117,8 @@ func Write(writer io.Writer, code []byte, data []byte) { binary.Write(writer, binary.LittleEndian, &m.DataHeader) binary.Write(writer, binary.LittleEndian, &m.UnixThread) - writer.Write(bytes.Repeat([]byte{0}, int(codePadding))) + writer.Write(bytes.Repeat([]byte{0x00}, int(codePadding))) writer.Write(code) - writer.Write(bytes.Repeat([]byte{0}, int(dataPadding))) + writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) writer.Write(data) } diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index f9e55c4..09f96f3 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -9,31 +9,36 @@ import ( "git.akyoto.dev/cli/q/src/exe" ) -const NumSections = 2 - // EXE is the portable executable format used on Windows. type EXE struct { DOSHeader NTHeader OptionalHeader64 - Sections [NumSections]SectionHeader + Sections []SectionHeader } // Write writes the EXE file to the given writer. func Write(writer io.Writer, code []byte, data []byte) { - const ( - HeaderEnd = DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections - ) + NumSections := 1 + + if len(data) != 0 { + NumSections += 1 + } + + HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections var ( - codePadding = exe.Padding(HeaderEnd, config.Align) - codeEnd = config.CodeOffset + len(code) - dataPadding = exe.Padding(codeEnd, config.Align) - dataStart = codeEnd + dataPadding + codeStart, codePadding = exe.Align(HeaderEnd, config.Align) + dataStart, dataPadding = exe.Align(codeStart+len(code), config.Align) ) - imageSize := dataStart + len(data) - imageSize += exe.Padding(imageSize, config.Align) + imageSize := codeStart + len(code) + + if len(data) != 0 { + imageSize = dataStart + len(data) + } + + imageSize, _ = exe.Align(imageSize, config.Align) pe := &EXE{ DOSHeader: DOSHeader{ @@ -43,7 +48,7 @@ func Write(writer io.Writer, code []byte, data []byte) { NTHeader: NTHeader{ Signature: [4]byte{'P', 'E', 0, 0}, Machine: IMAGE_FILE_MACHINE_AMD64, - NumberOfSections: NumSections, + NumberOfSections: uint16(NumSections), SizeOfOptionalHeader: OptionalHeader64Size, Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE, }, @@ -61,7 +66,7 @@ func Write(writer io.Writer, code []byte, data []byte) { MajorSubsystemVersion: 0x06, SizeOfImage: uint32(imageSize), SizeOfHeaders: config.CodeOffset, // section bodies begin here - Subsystem: IMAGE_SUBSYSTEM_WINDOWS_GUI, + Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI, DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, SizeOfStackReserve: 0x100000, SizeOfStackCommit: 0x1000, @@ -87,7 +92,7 @@ func Write(writer io.Writer, code []byte, data []byte) { {VirtualAddress: 0, Size: 0}, }, }, - Sections: [NumSections]SectionHeader{ + Sections: []SectionHeader{ { Name: [8]byte{'.', 't', 'e', 'x', 't'}, VirtualSize: uint32(len(code)), @@ -110,10 +115,13 @@ func Write(writer io.Writer, code []byte, data []byte) { binary.Write(writer, binary.LittleEndian, &pe.DOSHeader) binary.Write(writer, binary.LittleEndian, &pe.NTHeader) binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) - binary.Write(writer, binary.LittleEndian, &pe.Sections) + binary.Write(writer, binary.LittleEndian, pe.Sections[:NumSections]) - writer.Write(bytes.Repeat([]byte{0}, int(codePadding))) + writer.Write(bytes.Repeat([]byte{0x00}, int(codePadding))) writer.Write(code) - writer.Write(bytes.Repeat([]byte{0}, int(dataPadding))) - writer.Write(data) + + if len(data) != 0 { + writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) + writer.Write(data) + } } diff --git a/src/exe/pe/pe.md b/src/exe/pe/pe.md index ae779df..7297fbb 100644 --- a/src/exe/pe/pe.md +++ b/src/exe/pe/pe.md @@ -1,5 +1,10 @@ ## Portable Executable +## Notes + +Unlike Linux, Windows does not ignore zero-length sections and will fail loading them because they don't exist within the file. +Adding a single byte to the section can fix this problem, but it's easier to just remove the section header entirely. + ## Links - https://learn.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10) From 49eaf064a34add0b422ebac45f4390964b09196e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 15 Aug 2024 13:57:05 +0200 Subject: [PATCH 0529/1012] Added more tests --- src/exe/macho/MachO_test.go | 12 ++++++++++++ src/exe/pe/EXE_test.go | 12 ++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 src/exe/macho/MachO_test.go create mode 100644 src/exe/pe/EXE_test.go diff --git a/src/exe/macho/MachO_test.go b/src/exe/macho/MachO_test.go new file mode 100644 index 0000000..29b63c5 --- /dev/null +++ b/src/exe/macho/MachO_test.go @@ -0,0 +1,12 @@ +package macho_test + +import ( + "io" + "testing" + + "git.akyoto.dev/cli/q/src/exe/macho" +) + +func TestWrite(t *testing.T) { + macho.Write(io.Discard, nil, nil) +} diff --git a/src/exe/pe/EXE_test.go b/src/exe/pe/EXE_test.go new file mode 100644 index 0000000..71eede8 --- /dev/null +++ b/src/exe/pe/EXE_test.go @@ -0,0 +1,12 @@ +package pe_test + +import ( + "io" + "testing" + + "git.akyoto.dev/cli/q/src/exe/pe" +) + +func TestWrite(t *testing.T) { + pe.Write(io.Discard, nil, nil) +} From 0e7c66e44fb518e57f3b1255023ca1834fd35bbd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 15 Aug 2024 13:57:05 +0200 Subject: [PATCH 0530/1012] Added more tests --- src/exe/macho/MachO_test.go | 12 ++++++++++++ src/exe/pe/EXE_test.go | 12 ++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 src/exe/macho/MachO_test.go create mode 100644 src/exe/pe/EXE_test.go diff --git a/src/exe/macho/MachO_test.go b/src/exe/macho/MachO_test.go new file mode 100644 index 0000000..29b63c5 --- /dev/null +++ b/src/exe/macho/MachO_test.go @@ -0,0 +1,12 @@ +package macho_test + +import ( + "io" + "testing" + + "git.akyoto.dev/cli/q/src/exe/macho" +) + +func TestWrite(t *testing.T) { + macho.Write(io.Discard, nil, nil) +} diff --git a/src/exe/pe/EXE_test.go b/src/exe/pe/EXE_test.go new file mode 100644 index 0000000..71eede8 --- /dev/null +++ b/src/exe/pe/EXE_test.go @@ -0,0 +1,12 @@ +package pe_test + +import ( + "io" + "testing" + + "git.akyoto.dev/cli/q/src/exe/pe" +) + +func TestWrite(t *testing.T) { + pe.Write(io.Discard, nil, nil) +} From ca3d0ee2e839c086fd8a34e2e932b9283fbf15cb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 15 Aug 2024 22:01:04 +0200 Subject: [PATCH 0531/1012] Added DLL imports on Windows --- src/exe/pe/DLLImport.go | 11 ++++ src/exe/pe/EXE.go | 116 ++++++++++++++++++++++++++-------- src/exe/pe/ImportDirectory.go | 9 --- src/exe/pe/pe.md | 6 +- 4 files changed, 106 insertions(+), 36 deletions(-) create mode 100644 src/exe/pe/DLLImport.go delete mode 100644 src/exe/pe/ImportDirectory.go diff --git a/src/exe/pe/DLLImport.go b/src/exe/pe/DLLImport.go new file mode 100644 index 0000000..d769dd5 --- /dev/null +++ b/src/exe/pe/DLLImport.go @@ -0,0 +1,11 @@ +package pe + +const DLLImportSize = 20 + +type DLLImport struct { + RvaFunctionNameList uint32 + TimeDateStamp uint32 + ForwarderChain uint32 + RvaModuleName uint32 + RvaFunctionAddressList uint32 +} diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index 09f96f3..5e520c3 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -17,27 +17,73 @@ type EXE struct { Sections []SectionHeader } +type DLL struct { + Name string + Functions []string +} + // Write writes the EXE file to the given writer. func Write(writer io.Writer, code []byte, data []byte) { - NumSections := 1 - - if len(data) != 0 { - NumSections += 1 - } - + NumSections := 3 HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections + codeStart, codePadding := exe.Align(HeaderEnd, config.Align) + dataStart, dataPadding := exe.Align(codeStart+len(code), config.Align) - var ( - codeStart, codePadding = exe.Align(HeaderEnd, config.Align) - dataStart, dataPadding = exe.Align(codeStart+len(code), config.Align) - ) - - imageSize := codeStart + len(code) - - if len(data) != 0 { - imageSize = dataStart + len(data) + dlls := []DLL{ + { + Name: "kernel32.dll", + Functions: []string{ + "ExitProcess", + "GetStdHandle", + "WriteFile", + }, + }, } + dllAddresses := []uint64{} + dllImports := []DLLImport{} + + dllName := len(data) + data = append(data, dlls[0].Name...) + data = append(data, 0x00) + + for _, f := range dlls[0].Functions { + pos := len(data) + data = append(data, 0x00, 0x00) + data = append(data, f...) + data = append(data, 0x00) + + if len(data)&1 != 0 { + data = append(data, 0x00) // align the next entry on an even boundary + } + + dllAddresses = append(dllAddresses, uint64(dataStart+pos)) + } + + dllAddresses = append(dllAddresses, 0) + importStart, importPadding := exe.Align(dataStart+len(data), config.Align) + itblSize := DLLImportSize*len(dlls) + DLLImportSize + iatblSize := 8 * len(dllAddresses) + itblStart := importStart + iatblStart := itblStart + itblSize + + dllImports = append(dllImports, DLLImport{ + RvaFunctionNameList: uint32(iatblStart), + TimeDateStamp: 0, + ForwarderChain: 0, + RvaModuleName: uint32(dataStart + dllName), + RvaFunctionAddressList: uint32(iatblStart), + }) + + dllImports = append(dllImports, DLLImport{ + RvaFunctionNameList: 0, + TimeDateStamp: 0, + ForwarderChain: 0, + RvaModuleName: 0, // must be zero + RvaFunctionAddressList: 0, + }) + + imageSize := iatblStart + iatblSize imageSize, _ = exe.Align(imageSize, config.Align) pe := &EXE{ @@ -49,6 +95,9 @@ func Write(writer io.Writer, code []byte, data []byte) { Signature: [4]byte{'P', 'E', 0, 0}, Machine: IMAGE_FILE_MACHINE_AMD64, NumberOfSections: uint16(NumSections), + TimeDateStamp: 0, + PointerToSymbolTable: 0, + NumberOfSymbols: 0, SizeOfOptionalHeader: OptionalHeader64Size, Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE, }, @@ -57,24 +106,34 @@ func Write(writer io.Writer, code []byte, data []byte) { MajorLinkerVersion: 0x0E, MinorLinkerVersion: 0x16, SizeOfCode: uint32(len(code)), + SizeOfInitializedData: 0, + SizeOfUninitializedData: 0, AddressOfEntryPoint: config.CodeOffset, BaseOfCode: config.CodeOffset, ImageBase: config.BaseAddress, SectionAlignment: config.Align, // power of 2, must be greater than or equal to FileAlignment FileAlignment: config.Align, // power of 2 MajorOperatingSystemVersion: 0x06, + MinorOperatingSystemVersion: 0, + MajorImageVersion: 0, + MinorImageVersion: 0, MajorSubsystemVersion: 0x06, + MinorSubsystemVersion: 0, + Win32VersionValue: 0, SizeOfImage: uint32(imageSize), SizeOfHeaders: config.CodeOffset, // section bodies begin here + CheckSum: 0, Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI, DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, SizeOfStackReserve: 0x100000, SizeOfStackCommit: 0x1000, SizeOfHeapReserve: 0x100000, SizeOfHeapCommit: 0x1000, + LoaderFlags: 0, NumberOfRvaAndSizes: 16, DataDirectory: [16]DataDirectory{ {VirtualAddress: 0, Size: 0}, + {VirtualAddress: uint32(itblStart), Size: uint32(itblSize)}, // RVA of the imported function table {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, @@ -85,8 +144,7 @@ func Write(writer io.Writer, code []byte, data []byte) { {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, - {VirtualAddress: 0, Size: 0}, - {VirtualAddress: 0, Size: 0}, + {VirtualAddress: uint32(iatblStart), Size: uint32(iatblSize)}, // RVA of the import address table {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, @@ -105,23 +163,31 @@ func Write(writer io.Writer, code []byte, data []byte) { Name: [8]byte{'.', 'r', 'd', 'a', 't', 'a'}, VirtualSize: uint32(len(data)), VirtualAddress: uint32(dataStart), - RawSize: uint32(len(data)), // must be a multiple of FileAlignment - RawAddress: uint32(dataStart), // must be a multiple of FileAlignment + RawSize: uint32(len(data)), + RawAddress: uint32(dataStart), Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, }, + { + Name: [8]byte{'.', 'i', 'd', 'a', 't', 'a'}, + VirtualSize: uint32(iatblSize + itblSize), + VirtualAddress: uint32(importStart), + RawSize: uint32(iatblSize + itblSize), + RawAddress: uint32(importStart), + Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE, + }, }, } binary.Write(writer, binary.LittleEndian, &pe.DOSHeader) binary.Write(writer, binary.LittleEndian, &pe.NTHeader) binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) - binary.Write(writer, binary.LittleEndian, pe.Sections[:NumSections]) + binary.Write(writer, binary.LittleEndian, &pe.Sections) writer.Write(bytes.Repeat([]byte{0x00}, int(codePadding))) writer.Write(code) - - if len(data) != 0 { - writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) - writer.Write(data) - } + writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) + writer.Write(data) + writer.Write(bytes.Repeat([]byte{0x00}, int(importPadding))) + binary.Write(writer, binary.LittleEndian, &dllImports) + binary.Write(writer, binary.LittleEndian, &dllAddresses) } diff --git a/src/exe/pe/ImportDirectory.go b/src/exe/pe/ImportDirectory.go deleted file mode 100644 index 4857781..0000000 --- a/src/exe/pe/ImportDirectory.go +++ /dev/null @@ -1,9 +0,0 @@ -package pe - -type ImportDirectory struct { - OriginalFirstThunk uint32 - TimeDateStamp uint32 - ForwarderChain uint32 - Name uint32 - FirstThunk uint32 -} diff --git a/src/exe/pe/pe.md b/src/exe/pe/pe.md index 7297fbb..5fc034e 100644 --- a/src/exe/pe/pe.md +++ b/src/exe/pe/pe.md @@ -2,11 +2,13 @@ ## Notes -Unlike Linux, Windows does not ignore zero-length sections and will fail loading them because they don't exist within the file. -Adding a single byte to the section can fix this problem, but it's easier to just remove the section header entirely. +Unlike Linux, Windows does not ignore zero-length sections at the end of a file and will +fail loading them because they don't exist within the file. Adding a single byte to the +section can fix this problem, but it's easier to just remove the section header entirely. ## Links +- https://learn.microsoft.com/en-us/windows/win32/debug/pe-format - https://learn.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10) - https://learn.microsoft.com/en-us/archive/msdn-magazine/2002/february/inside-windows-win32-portable-executable-file-format-in-detail - https://learn.microsoft.com/en-us/archive/msdn-magazine/2002/march/inside-windows-an-in-depth-look-into-the-win32-portable-executable-file-format-part-2 From 07bf488657abb6c6d4c42c11e779305bd9fc59f9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 15 Aug 2024 22:01:04 +0200 Subject: [PATCH 0532/1012] Added DLL imports on Windows --- src/exe/pe/DLLImport.go | 11 ++++ src/exe/pe/EXE.go | 116 ++++++++++++++++++++++++++-------- src/exe/pe/ImportDirectory.go | 9 --- src/exe/pe/pe.md | 6 +- 4 files changed, 106 insertions(+), 36 deletions(-) create mode 100644 src/exe/pe/DLLImport.go delete mode 100644 src/exe/pe/ImportDirectory.go diff --git a/src/exe/pe/DLLImport.go b/src/exe/pe/DLLImport.go new file mode 100644 index 0000000..d769dd5 --- /dev/null +++ b/src/exe/pe/DLLImport.go @@ -0,0 +1,11 @@ +package pe + +const DLLImportSize = 20 + +type DLLImport struct { + RvaFunctionNameList uint32 + TimeDateStamp uint32 + ForwarderChain uint32 + RvaModuleName uint32 + RvaFunctionAddressList uint32 +} diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index 09f96f3..5e520c3 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -17,27 +17,73 @@ type EXE struct { Sections []SectionHeader } +type DLL struct { + Name string + Functions []string +} + // Write writes the EXE file to the given writer. func Write(writer io.Writer, code []byte, data []byte) { - NumSections := 1 - - if len(data) != 0 { - NumSections += 1 - } - + NumSections := 3 HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections + codeStart, codePadding := exe.Align(HeaderEnd, config.Align) + dataStart, dataPadding := exe.Align(codeStart+len(code), config.Align) - var ( - codeStart, codePadding = exe.Align(HeaderEnd, config.Align) - dataStart, dataPadding = exe.Align(codeStart+len(code), config.Align) - ) - - imageSize := codeStart + len(code) - - if len(data) != 0 { - imageSize = dataStart + len(data) + dlls := []DLL{ + { + Name: "kernel32.dll", + Functions: []string{ + "ExitProcess", + "GetStdHandle", + "WriteFile", + }, + }, } + dllAddresses := []uint64{} + dllImports := []DLLImport{} + + dllName := len(data) + data = append(data, dlls[0].Name...) + data = append(data, 0x00) + + for _, f := range dlls[0].Functions { + pos := len(data) + data = append(data, 0x00, 0x00) + data = append(data, f...) + data = append(data, 0x00) + + if len(data)&1 != 0 { + data = append(data, 0x00) // align the next entry on an even boundary + } + + dllAddresses = append(dllAddresses, uint64(dataStart+pos)) + } + + dllAddresses = append(dllAddresses, 0) + importStart, importPadding := exe.Align(dataStart+len(data), config.Align) + itblSize := DLLImportSize*len(dlls) + DLLImportSize + iatblSize := 8 * len(dllAddresses) + itblStart := importStart + iatblStart := itblStart + itblSize + + dllImports = append(dllImports, DLLImport{ + RvaFunctionNameList: uint32(iatblStart), + TimeDateStamp: 0, + ForwarderChain: 0, + RvaModuleName: uint32(dataStart + dllName), + RvaFunctionAddressList: uint32(iatblStart), + }) + + dllImports = append(dllImports, DLLImport{ + RvaFunctionNameList: 0, + TimeDateStamp: 0, + ForwarderChain: 0, + RvaModuleName: 0, // must be zero + RvaFunctionAddressList: 0, + }) + + imageSize := iatblStart + iatblSize imageSize, _ = exe.Align(imageSize, config.Align) pe := &EXE{ @@ -49,6 +95,9 @@ func Write(writer io.Writer, code []byte, data []byte) { Signature: [4]byte{'P', 'E', 0, 0}, Machine: IMAGE_FILE_MACHINE_AMD64, NumberOfSections: uint16(NumSections), + TimeDateStamp: 0, + PointerToSymbolTable: 0, + NumberOfSymbols: 0, SizeOfOptionalHeader: OptionalHeader64Size, Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE, }, @@ -57,24 +106,34 @@ func Write(writer io.Writer, code []byte, data []byte) { MajorLinkerVersion: 0x0E, MinorLinkerVersion: 0x16, SizeOfCode: uint32(len(code)), + SizeOfInitializedData: 0, + SizeOfUninitializedData: 0, AddressOfEntryPoint: config.CodeOffset, BaseOfCode: config.CodeOffset, ImageBase: config.BaseAddress, SectionAlignment: config.Align, // power of 2, must be greater than or equal to FileAlignment FileAlignment: config.Align, // power of 2 MajorOperatingSystemVersion: 0x06, + MinorOperatingSystemVersion: 0, + MajorImageVersion: 0, + MinorImageVersion: 0, MajorSubsystemVersion: 0x06, + MinorSubsystemVersion: 0, + Win32VersionValue: 0, SizeOfImage: uint32(imageSize), SizeOfHeaders: config.CodeOffset, // section bodies begin here + CheckSum: 0, Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI, DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, SizeOfStackReserve: 0x100000, SizeOfStackCommit: 0x1000, SizeOfHeapReserve: 0x100000, SizeOfHeapCommit: 0x1000, + LoaderFlags: 0, NumberOfRvaAndSizes: 16, DataDirectory: [16]DataDirectory{ {VirtualAddress: 0, Size: 0}, + {VirtualAddress: uint32(itblStart), Size: uint32(itblSize)}, // RVA of the imported function table {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, @@ -85,8 +144,7 @@ func Write(writer io.Writer, code []byte, data []byte) { {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, - {VirtualAddress: 0, Size: 0}, - {VirtualAddress: 0, Size: 0}, + {VirtualAddress: uint32(iatblStart), Size: uint32(iatblSize)}, // RVA of the import address table {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, @@ -105,23 +163,31 @@ func Write(writer io.Writer, code []byte, data []byte) { Name: [8]byte{'.', 'r', 'd', 'a', 't', 'a'}, VirtualSize: uint32(len(data)), VirtualAddress: uint32(dataStart), - RawSize: uint32(len(data)), // must be a multiple of FileAlignment - RawAddress: uint32(dataStart), // must be a multiple of FileAlignment + RawSize: uint32(len(data)), + RawAddress: uint32(dataStart), Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, }, + { + Name: [8]byte{'.', 'i', 'd', 'a', 't', 'a'}, + VirtualSize: uint32(iatblSize + itblSize), + VirtualAddress: uint32(importStart), + RawSize: uint32(iatblSize + itblSize), + RawAddress: uint32(importStart), + Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE, + }, }, } binary.Write(writer, binary.LittleEndian, &pe.DOSHeader) binary.Write(writer, binary.LittleEndian, &pe.NTHeader) binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) - binary.Write(writer, binary.LittleEndian, pe.Sections[:NumSections]) + binary.Write(writer, binary.LittleEndian, &pe.Sections) writer.Write(bytes.Repeat([]byte{0x00}, int(codePadding))) writer.Write(code) - - if len(data) != 0 { - writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) - writer.Write(data) - } + writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) + writer.Write(data) + writer.Write(bytes.Repeat([]byte{0x00}, int(importPadding))) + binary.Write(writer, binary.LittleEndian, &dllImports) + binary.Write(writer, binary.LittleEndian, &dllAddresses) } diff --git a/src/exe/pe/ImportDirectory.go b/src/exe/pe/ImportDirectory.go deleted file mode 100644 index 4857781..0000000 --- a/src/exe/pe/ImportDirectory.go +++ /dev/null @@ -1,9 +0,0 @@ -package pe - -type ImportDirectory struct { - OriginalFirstThunk uint32 - TimeDateStamp uint32 - ForwarderChain uint32 - Name uint32 - FirstThunk uint32 -} diff --git a/src/exe/pe/pe.md b/src/exe/pe/pe.md index 7297fbb..5fc034e 100644 --- a/src/exe/pe/pe.md +++ b/src/exe/pe/pe.md @@ -2,11 +2,13 @@ ## Notes -Unlike Linux, Windows does not ignore zero-length sections and will fail loading them because they don't exist within the file. -Adding a single byte to the section can fix this problem, but it's easier to just remove the section header entirely. +Unlike Linux, Windows does not ignore zero-length sections at the end of a file and will +fail loading them because they don't exist within the file. Adding a single byte to the +section can fix this problem, but it's easier to just remove the section header entirely. ## Links +- https://learn.microsoft.com/en-us/windows/win32/debug/pe-format - https://learn.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10) - https://learn.microsoft.com/en-us/archive/msdn-magazine/2002/february/inside-windows-win32-portable-executable-file-format-in-detail - https://learn.microsoft.com/en-us/archive/msdn-magazine/2002/march/inside-windows-an-in-depth-look-into-the-win32-portable-executable-file-format-part-2 From 125514679477a0a45517d27612133d70bea88394 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 16 Aug 2024 11:38:48 +0200 Subject: [PATCH 0533/1012] Reduced size of Windows executables --- src/exe/pe/EXE.go | 56 ++++++++++++++++++++++------------------------- src/exe/pe/pe.md | 7 ++++++ 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index 5e520c3..69034d1 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -24,7 +24,7 @@ type DLL struct { // Write writes the EXE file to the given writer. func Write(writer io.Writer, code []byte, data []byte) { - NumSections := 3 + NumSections := 2 HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections codeStart, codePadding := exe.Align(HeaderEnd, config.Align) dataStart, dataPadding := exe.Align(codeStart+len(code), config.Align) @@ -61,29 +61,36 @@ func Write(writer io.Writer, code []byte, data []byte) { } dllAddresses = append(dllAddresses, 0) - importStart, importPadding := exe.Align(dataStart+len(data), config.Align) - itblSize := DLLImportSize*len(dlls) + DLLImportSize - iatblSize := 8 * len(dllAddresses) - itblStart := importStart - iatblStart := itblStart + itblSize + + // Add the address table to the data section + functionAddressesStart := dataStart + len(data) + functionAddressesSize := 8 * len(dllAddresses) + data, err := binary.Append(data, binary.LittleEndian, &dllAddresses) + + if err != nil { + panic(err) + } dllImports = append(dllImports, DLLImport{ - RvaFunctionNameList: uint32(iatblStart), + RvaFunctionNameList: uint32(functionAddressesStart), TimeDateStamp: 0, ForwarderChain: 0, RvaModuleName: uint32(dataStart + dllName), - RvaFunctionAddressList: uint32(iatblStart), + RvaFunctionAddressList: uint32(functionAddressesStart), }) - dllImports = append(dllImports, DLLImport{ - RvaFunctionNameList: 0, - TimeDateStamp: 0, - ForwarderChain: 0, - RvaModuleName: 0, // must be zero - RvaFunctionAddressList: 0, - }) + dllImports = append(dllImports, DLLImport{}) // a zeroed structure marks the end of the list - imageSize := iatblStart + iatblSize + // Add imports to the data section + importsStart := dataStart + len(data) + importsSize := DLLImportSize * len(dllImports) + data, err = binary.Append(data, binary.LittleEndian, &dllImports) + + if err != nil { + panic(err) + } + + imageSize := functionAddressesStart + functionAddressesSize imageSize, _ = exe.Align(imageSize, config.Align) pe := &EXE{ @@ -102,7 +109,7 @@ func Write(writer io.Writer, code []byte, data []byte) { Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE, }, OptionalHeader64: OptionalHeader64{ - Magic: 0x020B, // PE32+ executable + Magic: 0x020B, // PE32+ / 64-bit executable MajorLinkerVersion: 0x0E, MinorLinkerVersion: 0x16, SizeOfCode: uint32(len(code)), @@ -133,7 +140,7 @@ func Write(writer io.Writer, code []byte, data []byte) { NumberOfRvaAndSizes: 16, DataDirectory: [16]DataDirectory{ {VirtualAddress: 0, Size: 0}, - {VirtualAddress: uint32(itblStart), Size: uint32(itblSize)}, // RVA of the imported function table + {VirtualAddress: uint32(importsStart), Size: uint32(importsSize)}, // RVA of the imported function table {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, @@ -144,7 +151,7 @@ func Write(writer io.Writer, code []byte, data []byte) { {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, - {VirtualAddress: uint32(iatblStart), Size: uint32(iatblSize)}, // RVA of the import address table + {VirtualAddress: uint32(functionAddressesStart), Size: uint32(functionAddressesSize)}, // RVA of the import address table {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, @@ -167,14 +174,6 @@ func Write(writer io.Writer, code []byte, data []byte) { RawAddress: uint32(dataStart), Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, }, - { - Name: [8]byte{'.', 'i', 'd', 'a', 't', 'a'}, - VirtualSize: uint32(iatblSize + itblSize), - VirtualAddress: uint32(importStart), - RawSize: uint32(iatblSize + itblSize), - RawAddress: uint32(importStart), - Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE, - }, }, } @@ -187,7 +186,4 @@ func Write(writer io.Writer, code []byte, data []byte) { writer.Write(code) writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) writer.Write(data) - writer.Write(bytes.Repeat([]byte{0x00}, int(importPadding))) - binary.Write(writer, binary.LittleEndian, &dllImports) - binary.Write(writer, binary.LittleEndian, &dllAddresses) } diff --git a/src/exe/pe/pe.md b/src/exe/pe/pe.md index 5fc034e..3d92799 100644 --- a/src/exe/pe/pe.md +++ b/src/exe/pe/pe.md @@ -5,6 +5,13 @@ Unlike Linux, Windows does not ignore zero-length sections at the end of a file and will fail loading them because they don't exist within the file. Adding a single byte to the section can fix this problem, but it's easier to just remove the section header entirely. +The solution used here is to guarantee that the data section is never empty by always +importing a few core functions from "kernel32.dll". + +## DLL function pointers + +The section where the DLL function pointers are stored does not need to be marked as writable. +The Windows executable loader resolves the pointers before they are loaded into memory. ## Links From 141ec1158d85e49148e2d8eaeac6a65187ceba83 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 16 Aug 2024 11:38:48 +0200 Subject: [PATCH 0534/1012] Reduced size of Windows executables --- src/exe/pe/EXE.go | 56 ++++++++++++++++++++++------------------------- src/exe/pe/pe.md | 7 ++++++ 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index 5e520c3..69034d1 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -24,7 +24,7 @@ type DLL struct { // Write writes the EXE file to the given writer. func Write(writer io.Writer, code []byte, data []byte) { - NumSections := 3 + NumSections := 2 HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections codeStart, codePadding := exe.Align(HeaderEnd, config.Align) dataStart, dataPadding := exe.Align(codeStart+len(code), config.Align) @@ -61,29 +61,36 @@ func Write(writer io.Writer, code []byte, data []byte) { } dllAddresses = append(dllAddresses, 0) - importStart, importPadding := exe.Align(dataStart+len(data), config.Align) - itblSize := DLLImportSize*len(dlls) + DLLImportSize - iatblSize := 8 * len(dllAddresses) - itblStart := importStart - iatblStart := itblStart + itblSize + + // Add the address table to the data section + functionAddressesStart := dataStart + len(data) + functionAddressesSize := 8 * len(dllAddresses) + data, err := binary.Append(data, binary.LittleEndian, &dllAddresses) + + if err != nil { + panic(err) + } dllImports = append(dllImports, DLLImport{ - RvaFunctionNameList: uint32(iatblStart), + RvaFunctionNameList: uint32(functionAddressesStart), TimeDateStamp: 0, ForwarderChain: 0, RvaModuleName: uint32(dataStart + dllName), - RvaFunctionAddressList: uint32(iatblStart), + RvaFunctionAddressList: uint32(functionAddressesStart), }) - dllImports = append(dllImports, DLLImport{ - RvaFunctionNameList: 0, - TimeDateStamp: 0, - ForwarderChain: 0, - RvaModuleName: 0, // must be zero - RvaFunctionAddressList: 0, - }) + dllImports = append(dllImports, DLLImport{}) // a zeroed structure marks the end of the list - imageSize := iatblStart + iatblSize + // Add imports to the data section + importsStart := dataStart + len(data) + importsSize := DLLImportSize * len(dllImports) + data, err = binary.Append(data, binary.LittleEndian, &dllImports) + + if err != nil { + panic(err) + } + + imageSize := functionAddressesStart + functionAddressesSize imageSize, _ = exe.Align(imageSize, config.Align) pe := &EXE{ @@ -102,7 +109,7 @@ func Write(writer io.Writer, code []byte, data []byte) { Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE, }, OptionalHeader64: OptionalHeader64{ - Magic: 0x020B, // PE32+ executable + Magic: 0x020B, // PE32+ / 64-bit executable MajorLinkerVersion: 0x0E, MinorLinkerVersion: 0x16, SizeOfCode: uint32(len(code)), @@ -133,7 +140,7 @@ func Write(writer io.Writer, code []byte, data []byte) { NumberOfRvaAndSizes: 16, DataDirectory: [16]DataDirectory{ {VirtualAddress: 0, Size: 0}, - {VirtualAddress: uint32(itblStart), Size: uint32(itblSize)}, // RVA of the imported function table + {VirtualAddress: uint32(importsStart), Size: uint32(importsSize)}, // RVA of the imported function table {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, @@ -144,7 +151,7 @@ func Write(writer io.Writer, code []byte, data []byte) { {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, - {VirtualAddress: uint32(iatblStart), Size: uint32(iatblSize)}, // RVA of the import address table + {VirtualAddress: uint32(functionAddressesStart), Size: uint32(functionAddressesSize)}, // RVA of the import address table {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, @@ -167,14 +174,6 @@ func Write(writer io.Writer, code []byte, data []byte) { RawAddress: uint32(dataStart), Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, }, - { - Name: [8]byte{'.', 'i', 'd', 'a', 't', 'a'}, - VirtualSize: uint32(iatblSize + itblSize), - VirtualAddress: uint32(importStart), - RawSize: uint32(iatblSize + itblSize), - RawAddress: uint32(importStart), - Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE, - }, }, } @@ -187,7 +186,4 @@ func Write(writer io.Writer, code []byte, data []byte) { writer.Write(code) writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) writer.Write(data) - writer.Write(bytes.Repeat([]byte{0x00}, int(importPadding))) - binary.Write(writer, binary.LittleEndian, &dllImports) - binary.Write(writer, binary.LittleEndian, &dllAddresses) } diff --git a/src/exe/pe/pe.md b/src/exe/pe/pe.md index 5fc034e..3d92799 100644 --- a/src/exe/pe/pe.md +++ b/src/exe/pe/pe.md @@ -5,6 +5,13 @@ Unlike Linux, Windows does not ignore zero-length sections at the end of a file and will fail loading them because they don't exist within the file. Adding a single byte to the section can fix this problem, but it's easier to just remove the section header entirely. +The solution used here is to guarantee that the data section is never empty by always +importing a few core functions from "kernel32.dll". + +## DLL function pointers + +The section where the DLL function pointers are stored does not need to be marked as writable. +The Windows executable loader resolves the pointers before they are loaded into memory. ## Links From bdd5e5a28e6697b2793c3dcc48b6bcea6b9e8610 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 16 Aug 2024 12:49:17 +0200 Subject: [PATCH 0535/1012] Improved documentation --- README.md | 2 +- lib/sys/sys_windows.q | 7 +++++++ src/exe/pe/pe.md | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 12736ec..91aedf2 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ go build ## Usage -Build a Linux x86-64 ELF executable from `examples/hello` and run it: +Build an executable from `examples/hello` and run it: ```shell ./q run examples/hello diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q index 105b33d..575bb00 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/sys_windows.q @@ -1,7 +1,14 @@ write(_ Int, _ Pointer, _ Int) -> Int { + // WriteFile(fd, buffer, length, out numberOfBytesWritten, out overlapped) + return 0 +} + +mmap(_ Int, _ Int, _ Int, _ Int) -> Pointer { + // VirtualAlloc(address, length, flags, protection) return 0 } exit(_ Int) { + // ExitProcess(code) return } \ No newline at end of file diff --git a/src/exe/pe/pe.md b/src/exe/pe/pe.md index 3d92799..8db558c 100644 --- a/src/exe/pe/pe.md +++ b/src/exe/pe/pe.md @@ -13,6 +13,8 @@ importing a few core functions from "kernel32.dll". The section where the DLL function pointers are stored does not need to be marked as writable. The Windows executable loader resolves the pointers before they are loaded into memory. +The stack must be 16 byte aligned before a DLL function is called. + ## Links - https://learn.microsoft.com/en-us/windows/win32/debug/pe-format From 499fe8aec8591a47bfefd16ec0ed589a4dcd9137 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 16 Aug 2024 12:49:17 +0200 Subject: [PATCH 0536/1012] Improved documentation --- README.md | 2 +- lib/sys/sys_windows.q | 7 +++++++ src/exe/pe/pe.md | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 12736ec..91aedf2 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ go build ## Usage -Build a Linux x86-64 ELF executable from `examples/hello` and run it: +Build an executable from `examples/hello` and run it: ```shell ./q run examples/hello diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q index 105b33d..575bb00 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/sys_windows.q @@ -1,7 +1,14 @@ write(_ Int, _ Pointer, _ Int) -> Int { + // WriteFile(fd, buffer, length, out numberOfBytesWritten, out overlapped) + return 0 +} + +mmap(_ Int, _ Int, _ Int, _ Int) -> Pointer { + // VirtualAlloc(address, length, flags, protection) return 0 } exit(_ Int) { + // ExitProcess(code) return } \ No newline at end of file diff --git a/src/exe/pe/pe.md b/src/exe/pe/pe.md index 3d92799..8db558c 100644 --- a/src/exe/pe/pe.md +++ b/src/exe/pe/pe.md @@ -13,6 +13,8 @@ importing a few core functions from "kernel32.dll". The section where the DLL function pointers are stored does not need to be marked as writable. The Windows executable loader resolves the pointers before they are loaded into memory. +The stack must be 16 byte aligned before a DLL function is called. + ## Links - https://learn.microsoft.com/en-us/windows/win32/debug/pe-format From 9c6eb45fe980e09e73a75c754f0f7897f725dabe Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 16 Aug 2024 20:39:04 +0200 Subject: [PATCH 0537/1012] Implemented indirect calls --- src/arch/x64/Call.go | 18 +++++++++++++++++- src/asm/Finalize.go | 19 +++++++++++++++++++ src/asm/Mnemonic.go | 3 +++ src/compiler/Result.go | 10 ++++++---- 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/arch/x64/Call.go b/src/arch/x64/Call.go index 1bbfc46..6c37d7b 100644 --- a/src/arch/x64/Call.go +++ b/src/arch/x64/Call.go @@ -1,7 +1,8 @@ package x64 // Call places the return address on the top of the stack and continues -// program flow at the new address. The address is relative to the next instruction. +// program flow at the new address. +// The address is relative to the next instruction. func Call(code []byte, address uint32) []byte { return append( code, @@ -12,3 +13,18 @@ func Call(code []byte, address uint32) []byte { byte(address>>24), ) } + +// CallAtAddress places the return address on the top of the stack and +// continues program flow at the address stored at the given memory address. +// The memory address is relative to the next instruction. +func CallAtAddress(code []byte, address uint32) []byte { + return append( + code, + 0xFF, + 0x15, + byte(address), + byte(address>>8), + byte(address>>16), + byte(address>>24), + ) +} diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 4276e38..b4f7b3c 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -111,6 +111,25 @@ func (a Assembler) Finalize() ([]byte, []byte) { codePointers = append(codePointers, pointer) + case CALL_AT: + code = x64.CallAtAddress(code, 0x00_00_00_00) + size := 4 + // label := x.Data.(*Label) + + pointer := &Pointer{ + Position: Address(len(code) - size), + OpSize: 2, + Size: uint8(size), + } + + pointer.Resolve = func() Address { + destination := Address(0x1038) + distance := destination - (pointer.Position + Address(pointer.Size)) + return Address(distance) + } + + codePointers = append(codePointers, pointer) + case COMMENT: continue diff --git a/src/asm/Mnemonic.go b/src/asm/Mnemonic.go index e8c778b..36687b7 100644 --- a/src/asm/Mnemonic.go +++ b/src/asm/Mnemonic.go @@ -29,6 +29,7 @@ const ( // Control flow CALL + CALL_AT JE JNE JG @@ -54,6 +55,8 @@ func (m Mnemonic) String() string { return "and" case CALL: return "call" + case CALL_AT: + return "call at" case COMMENT: return "comment" case COMPARE: diff --git a/src/compiler/Result.go b/src/compiler/Result.go index d9f7fcf..5ca790f 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -48,8 +48,9 @@ func (r *Result) finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() case "windows": - final.RegisterNumber(asm.MOVE, x64.RAX, 0) - final.Return() + final.RegisterNumber(asm.SUB, x64.RSP, 32+8) + final.RegisterNumber(asm.MOVE, x64.RCX, 0) + final.Label(asm.CALL_AT, "ExitProcess") } // This will place the main function immediately after the entry point @@ -70,8 +71,9 @@ func (r *Result) finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() case "windows": - final.RegisterNumber(asm.MOVE, x64.RAX, 1) - final.Return() + final.RegisterNumber(asm.SUB, x64.RSP, 32+8) + final.RegisterNumber(asm.MOVE, x64.RCX, 1) + final.Label(asm.CALL_AT, "ExitProcess") } code, data := final.Finalize() From 34aeba740a2ffb110c0bfd96e1ec88276d121f48 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 16 Aug 2024 20:39:04 +0200 Subject: [PATCH 0538/1012] Implemented indirect calls --- src/arch/x64/Call.go | 18 +++++++++++++++++- src/asm/Finalize.go | 19 +++++++++++++++++++ src/asm/Mnemonic.go | 3 +++ src/compiler/Result.go | 10 ++++++---- 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/arch/x64/Call.go b/src/arch/x64/Call.go index 1bbfc46..6c37d7b 100644 --- a/src/arch/x64/Call.go +++ b/src/arch/x64/Call.go @@ -1,7 +1,8 @@ package x64 // Call places the return address on the top of the stack and continues -// program flow at the new address. The address is relative to the next instruction. +// program flow at the new address. +// The address is relative to the next instruction. func Call(code []byte, address uint32) []byte { return append( code, @@ -12,3 +13,18 @@ func Call(code []byte, address uint32) []byte { byte(address>>24), ) } + +// CallAtAddress places the return address on the top of the stack and +// continues program flow at the address stored at the given memory address. +// The memory address is relative to the next instruction. +func CallAtAddress(code []byte, address uint32) []byte { + return append( + code, + 0xFF, + 0x15, + byte(address), + byte(address>>8), + byte(address>>16), + byte(address>>24), + ) +} diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 4276e38..b4f7b3c 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -111,6 +111,25 @@ func (a Assembler) Finalize() ([]byte, []byte) { codePointers = append(codePointers, pointer) + case CALL_AT: + code = x64.CallAtAddress(code, 0x00_00_00_00) + size := 4 + // label := x.Data.(*Label) + + pointer := &Pointer{ + Position: Address(len(code) - size), + OpSize: 2, + Size: uint8(size), + } + + pointer.Resolve = func() Address { + destination := Address(0x1038) + distance := destination - (pointer.Position + Address(pointer.Size)) + return Address(distance) + } + + codePointers = append(codePointers, pointer) + case COMMENT: continue diff --git a/src/asm/Mnemonic.go b/src/asm/Mnemonic.go index e8c778b..36687b7 100644 --- a/src/asm/Mnemonic.go +++ b/src/asm/Mnemonic.go @@ -29,6 +29,7 @@ const ( // Control flow CALL + CALL_AT JE JNE JG @@ -54,6 +55,8 @@ func (m Mnemonic) String() string { return "and" case CALL: return "call" + case CALL_AT: + return "call at" case COMMENT: return "comment" case COMPARE: diff --git a/src/compiler/Result.go b/src/compiler/Result.go index d9f7fcf..5ca790f 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -48,8 +48,9 @@ func (r *Result) finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() case "windows": - final.RegisterNumber(asm.MOVE, x64.RAX, 0) - final.Return() + final.RegisterNumber(asm.SUB, x64.RSP, 32+8) + final.RegisterNumber(asm.MOVE, x64.RCX, 0) + final.Label(asm.CALL_AT, "ExitProcess") } // This will place the main function immediately after the entry point @@ -70,8 +71,9 @@ func (r *Result) finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() case "windows": - final.RegisterNumber(asm.MOVE, x64.RAX, 1) - final.Return() + final.RegisterNumber(asm.SUB, x64.RSP, 32+8) + final.RegisterNumber(asm.MOVE, x64.RCX, 1) + final.Label(asm.CALL_AT, "ExitProcess") } code, data := final.Finalize() From 58a157c864038f007e8834cb54ed67cda1318ca4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 17 Aug 2024 15:08:18 +0200 Subject: [PATCH 0539/1012] Added Windows calling convention registers --- src/compiler/Result.go | 3 ++- src/os/windows/Registers.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 src/os/windows/Registers.go diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 5ca790f..3cb48d1 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -15,6 +15,7 @@ import ( "git.akyoto.dev/cli/q/src/exe/pe" "git.akyoto.dev/cli/q/src/os/linux" "git.akyoto.dev/cli/q/src/os/mac" + "git.akyoto.dev/cli/q/src/os/windows" ) // Result contains all the compiled functions in a build. @@ -72,7 +73,7 @@ func (r *Result) finalize() ([]byte, []byte) { final.Syscall() case "windows": final.RegisterNumber(asm.SUB, x64.RSP, 32+8) - final.RegisterNumber(asm.MOVE, x64.RCX, 1) + final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 1) final.Label(asm.CALL_AT, "ExitProcess") } diff --git a/src/os/windows/Registers.go b/src/os/windows/Registers.go new file mode 100644 index 0000000..ffbbd21 --- /dev/null +++ b/src/os/windows/Registers.go @@ -0,0 +1,36 @@ +package windows + +import ( + "git.akyoto.dev/cli/q/src/arch/arm64" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" +) + +var ( + X64InputRegisters = []cpu.Register{ + x64.RCX, + x64.RDX, + x64.R8, + x64.R9, + } + + X64OutputRegisters = []cpu.Register{ + x64.RAX, + } + + ARM64InputRegisters = []cpu.Register{ + arm64.X0, + arm64.X1, + arm64.X2, + arm64.X3, + arm64.X4, + arm64.X5, + arm64.X6, + arm64.X7, + } + + ARM64OutputRegisters = []cpu.Register{ + arm64.X0, + arm64.X1, + } +) From c3699ac6ac6724a83cd224287556ecf8c6907a86 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 17 Aug 2024 15:08:18 +0200 Subject: [PATCH 0540/1012] Added Windows calling convention registers --- src/compiler/Result.go | 3 ++- src/os/windows/Registers.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 src/os/windows/Registers.go diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 5ca790f..3cb48d1 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -15,6 +15,7 @@ import ( "git.akyoto.dev/cli/q/src/exe/pe" "git.akyoto.dev/cli/q/src/os/linux" "git.akyoto.dev/cli/q/src/os/mac" + "git.akyoto.dev/cli/q/src/os/windows" ) // Result contains all the compiled functions in a build. @@ -72,7 +73,7 @@ func (r *Result) finalize() ([]byte, []byte) { final.Syscall() case "windows": final.RegisterNumber(asm.SUB, x64.RSP, 32+8) - final.RegisterNumber(asm.MOVE, x64.RCX, 1) + final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 1) final.Label(asm.CALL_AT, "ExitProcess") } diff --git a/src/os/windows/Registers.go b/src/os/windows/Registers.go new file mode 100644 index 0000000..ffbbd21 --- /dev/null +++ b/src/os/windows/Registers.go @@ -0,0 +1,36 @@ +package windows + +import ( + "git.akyoto.dev/cli/q/src/arch/arm64" + "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/cpu" +) + +var ( + X64InputRegisters = []cpu.Register{ + x64.RCX, + x64.RDX, + x64.R8, + x64.R9, + } + + X64OutputRegisters = []cpu.Register{ + x64.RAX, + } + + ARM64InputRegisters = []cpu.Register{ + arm64.X0, + arm64.X1, + arm64.X2, + arm64.X3, + arm64.X4, + arm64.X5, + arm64.X6, + arm64.X7, + } + + ARM64OutputRegisters = []cpu.Register{ + arm64.X0, + arm64.X1, + } +) From 3eba9fb526ceb255c0e07924ff47a9c6ab6141c2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 17 Aug 2024 15:34:42 +0200 Subject: [PATCH 0541/1012] Implemented loading of multiple DLLs --- src/compiler/Result.go | 19 ++++++++- src/exe/pe/DLL.go | 6 +++ src/exe/pe/EXE.go | 87 ++++++++++++++++-------------------------- src/exe/pe/EXE_test.go | 2 +- 4 files changed, 57 insertions(+), 57 deletions(-) create mode 100644 src/exe/pe/DLL.go diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 3cb48d1..b4b0979 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -154,7 +154,24 @@ func write(writer io.Writer, code []byte, data []byte) error { case "mac": macho.Write(buffer, code, data) case "windows": - pe.Write(buffer, code, data) + dlls := []pe.DLL{ + { + Name: "kernel32.dll", + Functions: []string{ + "ExitProcess", + "GetStdHandle", + "WriteFile", + }, + }, + { + Name: "user32.dll", + Functions: []string{ + "MessageBoxA", + }, + }, + } + + pe.Write(buffer, code, data, dlls) default: return fmt.Errorf("unsupported platform '%s'", config.TargetOS) } diff --git a/src/exe/pe/DLL.go b/src/exe/pe/DLL.go new file mode 100644 index 0000000..78a2b48 --- /dev/null +++ b/src/exe/pe/DLL.go @@ -0,0 +1,6 @@ +package pe + +type DLL struct { + Name string + Functions []string +} diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index 69034d1..3e20183 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -17,80 +17,57 @@ type EXE struct { Sections []SectionHeader } -type DLL struct { - Name string - Functions []string -} - // Write writes the EXE file to the given writer. -func Write(writer io.Writer, code []byte, data []byte) { +func Write(writer io.Writer, code []byte, data []byte, dlls []DLL) { NumSections := 2 HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections codeStart, codePadding := exe.Align(HeaderEnd, config.Align) dataStart, dataPadding := exe.Align(codeStart+len(code), config.Align) - dlls := []DLL{ - { - Name: "kernel32.dll", - Functions: []string{ - "ExitProcess", - "GetStdHandle", - "WriteFile", - }, - }, - } - - dllAddresses := []uint64{} dllImports := []DLLImport{} - dllName := len(data) - data = append(data, dlls[0].Name...) - data = append(data, 0x00) - - for _, f := range dlls[0].Functions { - pos := len(data) - data = append(data, 0x00, 0x00) - data = append(data, f...) + for _, dll := range dlls { + dllAddresses := []uint64{} + dllNamePos := len(data) + data = append(data, dll.Name...) data = append(data, 0x00) - if len(data)&1 != 0 { - data = append(data, 0x00) // align the next entry on an even boundary + for _, f := range dll.Functions { + pos := len(data) + data = append(data, 0x00, 0x00) + data = append(data, f...) + data = append(data, 0x00) + + if len(data)&1 != 0 { + data = append(data, 0x00) // align the next entry on an even boundary + } + + dllAddresses = append(dllAddresses, uint64(dataStart+pos)) } - dllAddresses = append(dllAddresses, uint64(dataStart+pos)) + dllAddresses = append(dllAddresses, 0) + + // Add the address table to the data section + functionAddressesStart := dataStart + len(data) + data, _ = binary.Append(data, binary.LittleEndian, &dllAddresses) + + dllImports = append(dllImports, DLLImport{ + RvaFunctionNameList: uint32(functionAddressesStart), + TimeDateStamp: 0, + ForwarderChain: 0, + RvaModuleName: uint32(dataStart + dllNamePos), + RvaFunctionAddressList: uint32(functionAddressesStart), + }) } - dllAddresses = append(dllAddresses, 0) - - // Add the address table to the data section - functionAddressesStart := dataStart + len(data) - functionAddressesSize := 8 * len(dllAddresses) - data, err := binary.Append(data, binary.LittleEndian, &dllAddresses) - - if err != nil { - panic(err) - } - - dllImports = append(dllImports, DLLImport{ - RvaFunctionNameList: uint32(functionAddressesStart), - TimeDateStamp: 0, - ForwarderChain: 0, - RvaModuleName: uint32(dataStart + dllName), - RvaFunctionAddressList: uint32(functionAddressesStart), - }) - dllImports = append(dllImports, DLLImport{}) // a zeroed structure marks the end of the list // Add imports to the data section importsStart := dataStart + len(data) importsSize := DLLImportSize * len(dllImports) - data, err = binary.Append(data, binary.LittleEndian, &dllImports) + data, _ = binary.Append(data, binary.LittleEndian, &dllImports) - if err != nil { - panic(err) - } - - imageSize := functionAddressesStart + functionAddressesSize + imageSize := dataStart + len(data) imageSize, _ = exe.Align(imageSize, config.Align) pe := &EXE{ @@ -151,7 +128,7 @@ func Write(writer io.Writer, code []byte, data []byte) { {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, - {VirtualAddress: uint32(functionAddressesStart), Size: uint32(functionAddressesSize)}, // RVA of the import address table + {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, diff --git a/src/exe/pe/EXE_test.go b/src/exe/pe/EXE_test.go index 71eede8..5f8d07e 100644 --- a/src/exe/pe/EXE_test.go +++ b/src/exe/pe/EXE_test.go @@ -8,5 +8,5 @@ import ( ) func TestWrite(t *testing.T) { - pe.Write(io.Discard, nil, nil) + pe.Write(io.Discard, nil, nil, nil) } From 771b993dd8b1c9e4118bf9fd3dd5df31963d1005 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 17 Aug 2024 15:34:42 +0200 Subject: [PATCH 0542/1012] Implemented loading of multiple DLLs --- src/compiler/Result.go | 19 ++++++++- src/exe/pe/DLL.go | 6 +++ src/exe/pe/EXE.go | 87 ++++++++++++++++-------------------------- src/exe/pe/EXE_test.go | 2 +- 4 files changed, 57 insertions(+), 57 deletions(-) create mode 100644 src/exe/pe/DLL.go diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 3cb48d1..b4b0979 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -154,7 +154,24 @@ func write(writer io.Writer, code []byte, data []byte) error { case "mac": macho.Write(buffer, code, data) case "windows": - pe.Write(buffer, code, data) + dlls := []pe.DLL{ + { + Name: "kernel32.dll", + Functions: []string{ + "ExitProcess", + "GetStdHandle", + "WriteFile", + }, + }, + { + Name: "user32.dll", + Functions: []string{ + "MessageBoxA", + }, + }, + } + + pe.Write(buffer, code, data, dlls) default: return fmt.Errorf("unsupported platform '%s'", config.TargetOS) } diff --git a/src/exe/pe/DLL.go b/src/exe/pe/DLL.go new file mode 100644 index 0000000..78a2b48 --- /dev/null +++ b/src/exe/pe/DLL.go @@ -0,0 +1,6 @@ +package pe + +type DLL struct { + Name string + Functions []string +} diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index 69034d1..3e20183 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -17,80 +17,57 @@ type EXE struct { Sections []SectionHeader } -type DLL struct { - Name string - Functions []string -} - // Write writes the EXE file to the given writer. -func Write(writer io.Writer, code []byte, data []byte) { +func Write(writer io.Writer, code []byte, data []byte, dlls []DLL) { NumSections := 2 HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections codeStart, codePadding := exe.Align(HeaderEnd, config.Align) dataStart, dataPadding := exe.Align(codeStart+len(code), config.Align) - dlls := []DLL{ - { - Name: "kernel32.dll", - Functions: []string{ - "ExitProcess", - "GetStdHandle", - "WriteFile", - }, - }, - } - - dllAddresses := []uint64{} dllImports := []DLLImport{} - dllName := len(data) - data = append(data, dlls[0].Name...) - data = append(data, 0x00) - - for _, f := range dlls[0].Functions { - pos := len(data) - data = append(data, 0x00, 0x00) - data = append(data, f...) + for _, dll := range dlls { + dllAddresses := []uint64{} + dllNamePos := len(data) + data = append(data, dll.Name...) data = append(data, 0x00) - if len(data)&1 != 0 { - data = append(data, 0x00) // align the next entry on an even boundary + for _, f := range dll.Functions { + pos := len(data) + data = append(data, 0x00, 0x00) + data = append(data, f...) + data = append(data, 0x00) + + if len(data)&1 != 0 { + data = append(data, 0x00) // align the next entry on an even boundary + } + + dllAddresses = append(dllAddresses, uint64(dataStart+pos)) } - dllAddresses = append(dllAddresses, uint64(dataStart+pos)) + dllAddresses = append(dllAddresses, 0) + + // Add the address table to the data section + functionAddressesStart := dataStart + len(data) + data, _ = binary.Append(data, binary.LittleEndian, &dllAddresses) + + dllImports = append(dllImports, DLLImport{ + RvaFunctionNameList: uint32(functionAddressesStart), + TimeDateStamp: 0, + ForwarderChain: 0, + RvaModuleName: uint32(dataStart + dllNamePos), + RvaFunctionAddressList: uint32(functionAddressesStart), + }) } - dllAddresses = append(dllAddresses, 0) - - // Add the address table to the data section - functionAddressesStart := dataStart + len(data) - functionAddressesSize := 8 * len(dllAddresses) - data, err := binary.Append(data, binary.LittleEndian, &dllAddresses) - - if err != nil { - panic(err) - } - - dllImports = append(dllImports, DLLImport{ - RvaFunctionNameList: uint32(functionAddressesStart), - TimeDateStamp: 0, - ForwarderChain: 0, - RvaModuleName: uint32(dataStart + dllName), - RvaFunctionAddressList: uint32(functionAddressesStart), - }) - dllImports = append(dllImports, DLLImport{}) // a zeroed structure marks the end of the list // Add imports to the data section importsStart := dataStart + len(data) importsSize := DLLImportSize * len(dllImports) - data, err = binary.Append(data, binary.LittleEndian, &dllImports) + data, _ = binary.Append(data, binary.LittleEndian, &dllImports) - if err != nil { - panic(err) - } - - imageSize := functionAddressesStart + functionAddressesSize + imageSize := dataStart + len(data) imageSize, _ = exe.Align(imageSize, config.Align) pe := &EXE{ @@ -151,7 +128,7 @@ func Write(writer io.Writer, code []byte, data []byte) { {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, - {VirtualAddress: uint32(functionAddressesStart), Size: uint32(functionAddressesSize)}, // RVA of the import address table + {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, diff --git a/src/exe/pe/EXE_test.go b/src/exe/pe/EXE_test.go index 71eede8..5f8d07e 100644 --- a/src/exe/pe/EXE_test.go +++ b/src/exe/pe/EXE_test.go @@ -8,5 +8,5 @@ import ( ) func TestWrite(t *testing.T) { - pe.Write(io.Discard, nil, nil) + pe.Write(io.Discard, nil, nil, nil) } From 201f783a4de4bce71fcbaffa75d9c3b27cc454c8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 18 Aug 2024 09:52:45 +0200 Subject: [PATCH 0543/1012] Added more RISC-V registers --- src/arch/riscv/Registers.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/arch/riscv/Registers.go b/src/arch/riscv/Registers.go index 5267be3..66bf189 100644 --- a/src/arch/riscv/Registers.go +++ b/src/arch/riscv/Registers.go @@ -37,4 +37,7 @@ const ( X31 ) -var SyscallInputRegisters = []cpu.Register{X10, X11, X12, X13, X14, X15, X16} +var ( + SyscallInputRegisters = []cpu.Register{X17, X10, X11, X12, X13, X14, X15, X16} + SyscallOutputRegisters = []cpu.Register{X10, X11} +) From 3fa3ff9227654ef3b5f9202edda4da8b311319f5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 18 Aug 2024 09:52:45 +0200 Subject: [PATCH 0544/1012] Added more RISC-V registers --- src/arch/riscv/Registers.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/arch/riscv/Registers.go b/src/arch/riscv/Registers.go index 5267be3..66bf189 100644 --- a/src/arch/riscv/Registers.go +++ b/src/arch/riscv/Registers.go @@ -37,4 +37,7 @@ const ( X31 ) -var SyscallInputRegisters = []cpu.Register{X10, X11, X12, X13, X14, X15, X16} +var ( + SyscallInputRegisters = []cpu.Register{X17, X10, X11, X12, X13, X14, X15, X16} + SyscallOutputRegisters = []cpu.Register{X10, X11} +) From fa1dce31f3c7b8696cfbe5f23a8bc47a630e4a41 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 18 Aug 2024 13:29:44 +0200 Subject: [PATCH 0545/1012] Improved Windows support --- src/asm/Finalize.go | 20 +++++--- src/compiler/Result.go | 21 +------- src/{exe/pe => dll}/DLL.go | 2 +- src/dll/List.go | 24 +++++++++ src/exe/pe/EXE.go | 102 +++++++++++++++++++++++-------------- src/os/windows/DLLs.go | 21 ++++++++ 6 files changed, 127 insertions(+), 63 deletions(-) rename src/{exe/pe => dll}/DLL.go (83%) create mode 100644 src/dll/List.go create mode 100644 src/os/windows/DLLs.go diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index b4f7b3c..de3eae6 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -8,12 +8,13 @@ import ( "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/exe" "git.akyoto.dev/cli/q/src/sizeof" ) // Finalize generates the final machine code. -func (a Assembler) Finalize() ([]byte, []byte) { +func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { var ( code = make([]byte, 0, len(a.Instructions)*8) data []byte @@ -21,6 +22,7 @@ func (a Assembler) Finalize() ([]byte, []byte) { dataLabels map[string]Address codePointers []*Pointer dataPointers []*Pointer + dllPointers []*Pointer ) for _, x := range a.Instructions { @@ -114,7 +116,7 @@ func (a Assembler) Finalize() ([]byte, []byte) { case CALL_AT: code = x64.CallAtAddress(code, 0x00_00_00_00) size := 4 - // label := x.Data.(*Label) + label := x.Data.(*Label) pointer := &Pointer{ Position: Address(len(code) - size), @@ -123,12 +125,11 @@ func (a Assembler) Finalize() ([]byte, []byte) { } pointer.Resolve = func() Address { - destination := Address(0x1038) - distance := destination - (pointer.Position + Address(pointer.Size)) - return Address(distance) + index := dlls.Index("kernel32.dll", label.Name) + return Address(index * 8) } - codePointers = append(codePointers, pointer) + dllPointers = append(dllPointers, pointer) case COMMENT: continue @@ -365,5 +366,12 @@ restart: binary.LittleEndian.PutUint32(slice, uint32(address)) } + for _, pointer := range dllPointers { + destination := Address(0x3000) + pointer.Resolve() + address := destination - Address(config.CodeOffset+pointer.Position+Address(pointer.Size)) + slice := code[pointer.Position : pointer.Position+4] + binary.LittleEndian.PutUint32(slice, uint32(address)) + } + return code, data } diff --git a/src/compiler/Result.go b/src/compiler/Result.go index b4b0979..fda6953 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -77,7 +77,7 @@ func (r *Result) finalize() ([]byte, []byte) { final.Label(asm.CALL_AT, "ExitProcess") } - code, data := final.Finalize() + code, data := final.Finalize(windows.DLLs) return code, data } @@ -154,24 +154,7 @@ func write(writer io.Writer, code []byte, data []byte) error { case "mac": macho.Write(buffer, code, data) case "windows": - dlls := []pe.DLL{ - { - Name: "kernel32.dll", - Functions: []string{ - "ExitProcess", - "GetStdHandle", - "WriteFile", - }, - }, - { - Name: "user32.dll", - Functions: []string{ - "MessageBoxA", - }, - }, - } - - pe.Write(buffer, code, data, dlls) + pe.Write(buffer, code, data, windows.DLLs) default: return fmt.Errorf("unsupported platform '%s'", config.TargetOS) } diff --git a/src/exe/pe/DLL.go b/src/dll/DLL.go similarity index 83% rename from src/exe/pe/DLL.go rename to src/dll/DLL.go index 78a2b48..99c04b4 100644 --- a/src/exe/pe/DLL.go +++ b/src/dll/DLL.go @@ -1,4 +1,4 @@ -package pe +package dll type DLL struct { Name string diff --git a/src/dll/List.go b/src/dll/List.go new file mode 100644 index 0000000..b031981 --- /dev/null +++ b/src/dll/List.go @@ -0,0 +1,24 @@ +package dll + +// List is a slice of DLLs. +type List []DLL + +// Index returns the position of the given function name. +func (list List) Index(dllName string, funcName string) int { + index := 0 + + for _, dll := range list { + if dll.Name != dllName { + index += len(dll.Functions) + 1 + continue + } + + for i, fn := range dll.Functions { + if fn == funcName { + return index + i + } + } + } + + return -1 +} diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index 3e20183..60a7a79 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -6,6 +6,7 @@ import ( "io" "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/exe" ) @@ -18,56 +19,71 @@ type EXE struct { } // Write writes the EXE file to the given writer. -func Write(writer io.Writer, code []byte, data []byte, dlls []DLL) { - NumSections := 2 +func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { + if len(data) == 0 { + data = []byte{0} + } + + NumSections := 3 HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections codeStart, codePadding := exe.Align(HeaderEnd, config.Align) dataStart, dataPadding := exe.Align(codeStart+len(code), config.Align) + importsStart, importsPadding := exe.Align(dataStart+len(data), config.Align) + imports := make([]uint64, 0) + dllData := make([]byte, 0) dllImports := []DLLImport{} - for _, dll := range dlls { - dllAddresses := []uint64{} - dllNamePos := len(data) - data = append(data, dll.Name...) - data = append(data, 0x00) - - for _, f := range dll.Functions { - pos := len(data) - data = append(data, 0x00, 0x00) - data = append(data, f...) - data = append(data, 0x00) - - if len(data)&1 != 0 { - data = append(data, 0x00) // align the next entry on an even boundary - } - - dllAddresses = append(dllAddresses, uint64(dataStart+pos)) - } - - dllAddresses = append(dllAddresses, 0) - - // Add the address table to the data section - functionAddressesStart := dataStart + len(data) - data, _ = binary.Append(data, binary.LittleEndian, &dllAddresses) + for _, library := range dlls { + functionsStart := len(imports) * 8 + dllNamePos := len(dllData) + dllData = append(dllData, library.Name...) + dllData = append(dllData, 0x00) dllImports = append(dllImports, DLLImport{ - RvaFunctionNameList: uint32(functionAddressesStart), + RvaFunctionNameList: uint32(importsStart + functionsStart), TimeDateStamp: 0, ForwarderChain: 0, - RvaModuleName: uint32(dataStart + dllNamePos), - RvaFunctionAddressList: uint32(functionAddressesStart), + RvaModuleName: uint32(dllNamePos), + RvaFunctionAddressList: uint32(importsStart + functionsStart), }) + + for _, fn := range library.Functions { + if len(dllData)&1 != 0 { + dllData = append(dllData, 0x00) // align the next entry on an even boundary + } + + offset := len(dllData) + dllData = append(dllData, 0x00, 0x00) + dllData = append(dllData, fn...) + dllData = append(dllData, 0x00) + + imports = append(imports, uint64(offset)) + } + + imports = append(imports, 0) + } + + dllDataStart := importsStart + len(imports)*8 + + for i := range imports { + if imports[i] == 0 { + continue + } + + imports[i] += uint64(dllDataStart) + } + + for i := range dllImports { + dllImports[i].RvaModuleName += uint32(dllDataStart) } dllImports = append(dllImports, DLLImport{}) // a zeroed structure marks the end of the list + importDirectoryStart := dllDataStart + len(dllData) + importDirectorySize := DLLImportSize * len(dllImports) - // Add imports to the data section - importsStart := dataStart + len(data) - importsSize := DLLImportSize * len(dllImports) - data, _ = binary.Append(data, binary.LittleEndian, &dllImports) - - imageSize := dataStart + len(data) + importSectionSize := len(imports)*8 + len(dllData) + importDirectorySize + imageSize := importsStart + importSectionSize imageSize, _ = exe.Align(imageSize, config.Align) pe := &EXE{ @@ -117,8 +133,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls []DLL) { NumberOfRvaAndSizes: 16, DataDirectory: [16]DataDirectory{ {VirtualAddress: 0, Size: 0}, - {VirtualAddress: uint32(importsStart), Size: uint32(importsSize)}, // RVA of the imported function table - {VirtualAddress: 0, Size: 0}, + {VirtualAddress: uint32(importDirectoryStart), Size: uint32(importDirectorySize)}, // RVA of the imported function table {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, @@ -129,6 +144,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls []DLL) { {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, + {VirtualAddress: uint32(importsStart), Size: uint32(len(imports) * 8)}, // RVA of the import address table {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, @@ -151,6 +167,14 @@ func Write(writer io.Writer, code []byte, data []byte, dlls []DLL) { RawAddress: uint32(dataStart), Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, }, + { + Name: [8]byte{'.', 'i', 'd', 'a', 't', 'a'}, + VirtualSize: uint32(importSectionSize), + VirtualAddress: uint32(importsStart), + RawSize: uint32(importSectionSize), + RawAddress: uint32(importsStart), + Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, + }, }, } @@ -163,4 +187,8 @@ func Write(writer io.Writer, code []byte, data []byte, dlls []DLL) { writer.Write(code) writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) writer.Write(data) + writer.Write(bytes.Repeat([]byte{0x00}, int(importsPadding))) + binary.Write(writer, binary.LittleEndian, &imports) + binary.Write(writer, binary.LittleEndian, &dllData) + binary.Write(writer, binary.LittleEndian, &dllImports) } diff --git a/src/os/windows/DLLs.go b/src/os/windows/DLLs.go new file mode 100644 index 0000000..3b148ac --- /dev/null +++ b/src/os/windows/DLLs.go @@ -0,0 +1,21 @@ +package windows + +import "git.akyoto.dev/cli/q/src/dll" + +// Temporary fix... +var DLLs = dll.List{ + { + Name: "kernel32.dll", + Functions: []string{ + "ExitProcess", + "GetStdHandle", + "WriteFile", + }, + }, + { + Name: "user32.dll", + Functions: []string{ + "MessageBoxA", + }, + }, +} From 0db54ff6398758ed01b9870ff99fc646bf579404 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 18 Aug 2024 13:29:44 +0200 Subject: [PATCH 0546/1012] Improved Windows support --- src/asm/Finalize.go | 20 +++++--- src/compiler/Result.go | 21 +------- src/{exe/pe => dll}/DLL.go | 2 +- src/dll/List.go | 24 +++++++++ src/exe/pe/EXE.go | 102 +++++++++++++++++++++++-------------- src/os/windows/DLLs.go | 21 ++++++++ 6 files changed, 127 insertions(+), 63 deletions(-) rename src/{exe/pe => dll}/DLL.go (83%) create mode 100644 src/dll/List.go create mode 100644 src/os/windows/DLLs.go diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index b4f7b3c..de3eae6 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -8,12 +8,13 @@ import ( "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/exe" "git.akyoto.dev/cli/q/src/sizeof" ) // Finalize generates the final machine code. -func (a Assembler) Finalize() ([]byte, []byte) { +func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { var ( code = make([]byte, 0, len(a.Instructions)*8) data []byte @@ -21,6 +22,7 @@ func (a Assembler) Finalize() ([]byte, []byte) { dataLabels map[string]Address codePointers []*Pointer dataPointers []*Pointer + dllPointers []*Pointer ) for _, x := range a.Instructions { @@ -114,7 +116,7 @@ func (a Assembler) Finalize() ([]byte, []byte) { case CALL_AT: code = x64.CallAtAddress(code, 0x00_00_00_00) size := 4 - // label := x.Data.(*Label) + label := x.Data.(*Label) pointer := &Pointer{ Position: Address(len(code) - size), @@ -123,12 +125,11 @@ func (a Assembler) Finalize() ([]byte, []byte) { } pointer.Resolve = func() Address { - destination := Address(0x1038) - distance := destination - (pointer.Position + Address(pointer.Size)) - return Address(distance) + index := dlls.Index("kernel32.dll", label.Name) + return Address(index * 8) } - codePointers = append(codePointers, pointer) + dllPointers = append(dllPointers, pointer) case COMMENT: continue @@ -365,5 +366,12 @@ restart: binary.LittleEndian.PutUint32(slice, uint32(address)) } + for _, pointer := range dllPointers { + destination := Address(0x3000) + pointer.Resolve() + address := destination - Address(config.CodeOffset+pointer.Position+Address(pointer.Size)) + slice := code[pointer.Position : pointer.Position+4] + binary.LittleEndian.PutUint32(slice, uint32(address)) + } + return code, data } diff --git a/src/compiler/Result.go b/src/compiler/Result.go index b4b0979..fda6953 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -77,7 +77,7 @@ func (r *Result) finalize() ([]byte, []byte) { final.Label(asm.CALL_AT, "ExitProcess") } - code, data := final.Finalize() + code, data := final.Finalize(windows.DLLs) return code, data } @@ -154,24 +154,7 @@ func write(writer io.Writer, code []byte, data []byte) error { case "mac": macho.Write(buffer, code, data) case "windows": - dlls := []pe.DLL{ - { - Name: "kernel32.dll", - Functions: []string{ - "ExitProcess", - "GetStdHandle", - "WriteFile", - }, - }, - { - Name: "user32.dll", - Functions: []string{ - "MessageBoxA", - }, - }, - } - - pe.Write(buffer, code, data, dlls) + pe.Write(buffer, code, data, windows.DLLs) default: return fmt.Errorf("unsupported platform '%s'", config.TargetOS) } diff --git a/src/exe/pe/DLL.go b/src/dll/DLL.go similarity index 83% rename from src/exe/pe/DLL.go rename to src/dll/DLL.go index 78a2b48..99c04b4 100644 --- a/src/exe/pe/DLL.go +++ b/src/dll/DLL.go @@ -1,4 +1,4 @@ -package pe +package dll type DLL struct { Name string diff --git a/src/dll/List.go b/src/dll/List.go new file mode 100644 index 0000000..b031981 --- /dev/null +++ b/src/dll/List.go @@ -0,0 +1,24 @@ +package dll + +// List is a slice of DLLs. +type List []DLL + +// Index returns the position of the given function name. +func (list List) Index(dllName string, funcName string) int { + index := 0 + + for _, dll := range list { + if dll.Name != dllName { + index += len(dll.Functions) + 1 + continue + } + + for i, fn := range dll.Functions { + if fn == funcName { + return index + i + } + } + } + + return -1 +} diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index 3e20183..60a7a79 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -6,6 +6,7 @@ import ( "io" "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/exe" ) @@ -18,56 +19,71 @@ type EXE struct { } // Write writes the EXE file to the given writer. -func Write(writer io.Writer, code []byte, data []byte, dlls []DLL) { - NumSections := 2 +func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { + if len(data) == 0 { + data = []byte{0} + } + + NumSections := 3 HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections codeStart, codePadding := exe.Align(HeaderEnd, config.Align) dataStart, dataPadding := exe.Align(codeStart+len(code), config.Align) + importsStart, importsPadding := exe.Align(dataStart+len(data), config.Align) + imports := make([]uint64, 0) + dllData := make([]byte, 0) dllImports := []DLLImport{} - for _, dll := range dlls { - dllAddresses := []uint64{} - dllNamePos := len(data) - data = append(data, dll.Name...) - data = append(data, 0x00) - - for _, f := range dll.Functions { - pos := len(data) - data = append(data, 0x00, 0x00) - data = append(data, f...) - data = append(data, 0x00) - - if len(data)&1 != 0 { - data = append(data, 0x00) // align the next entry on an even boundary - } - - dllAddresses = append(dllAddresses, uint64(dataStart+pos)) - } - - dllAddresses = append(dllAddresses, 0) - - // Add the address table to the data section - functionAddressesStart := dataStart + len(data) - data, _ = binary.Append(data, binary.LittleEndian, &dllAddresses) + for _, library := range dlls { + functionsStart := len(imports) * 8 + dllNamePos := len(dllData) + dllData = append(dllData, library.Name...) + dllData = append(dllData, 0x00) dllImports = append(dllImports, DLLImport{ - RvaFunctionNameList: uint32(functionAddressesStart), + RvaFunctionNameList: uint32(importsStart + functionsStart), TimeDateStamp: 0, ForwarderChain: 0, - RvaModuleName: uint32(dataStart + dllNamePos), - RvaFunctionAddressList: uint32(functionAddressesStart), + RvaModuleName: uint32(dllNamePos), + RvaFunctionAddressList: uint32(importsStart + functionsStart), }) + + for _, fn := range library.Functions { + if len(dllData)&1 != 0 { + dllData = append(dllData, 0x00) // align the next entry on an even boundary + } + + offset := len(dllData) + dllData = append(dllData, 0x00, 0x00) + dllData = append(dllData, fn...) + dllData = append(dllData, 0x00) + + imports = append(imports, uint64(offset)) + } + + imports = append(imports, 0) + } + + dllDataStart := importsStart + len(imports)*8 + + for i := range imports { + if imports[i] == 0 { + continue + } + + imports[i] += uint64(dllDataStart) + } + + for i := range dllImports { + dllImports[i].RvaModuleName += uint32(dllDataStart) } dllImports = append(dllImports, DLLImport{}) // a zeroed structure marks the end of the list + importDirectoryStart := dllDataStart + len(dllData) + importDirectorySize := DLLImportSize * len(dllImports) - // Add imports to the data section - importsStart := dataStart + len(data) - importsSize := DLLImportSize * len(dllImports) - data, _ = binary.Append(data, binary.LittleEndian, &dllImports) - - imageSize := dataStart + len(data) + importSectionSize := len(imports)*8 + len(dllData) + importDirectorySize + imageSize := importsStart + importSectionSize imageSize, _ = exe.Align(imageSize, config.Align) pe := &EXE{ @@ -117,8 +133,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls []DLL) { NumberOfRvaAndSizes: 16, DataDirectory: [16]DataDirectory{ {VirtualAddress: 0, Size: 0}, - {VirtualAddress: uint32(importsStart), Size: uint32(importsSize)}, // RVA of the imported function table - {VirtualAddress: 0, Size: 0}, + {VirtualAddress: uint32(importDirectoryStart), Size: uint32(importDirectorySize)}, // RVA of the imported function table {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, @@ -129,6 +144,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls []DLL) { {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, + {VirtualAddress: uint32(importsStart), Size: uint32(len(imports) * 8)}, // RVA of the import address table {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, @@ -151,6 +167,14 @@ func Write(writer io.Writer, code []byte, data []byte, dlls []DLL) { RawAddress: uint32(dataStart), Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, }, + { + Name: [8]byte{'.', 'i', 'd', 'a', 't', 'a'}, + VirtualSize: uint32(importSectionSize), + VirtualAddress: uint32(importsStart), + RawSize: uint32(importSectionSize), + RawAddress: uint32(importsStart), + Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, + }, }, } @@ -163,4 +187,8 @@ func Write(writer io.Writer, code []byte, data []byte, dlls []DLL) { writer.Write(code) writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) writer.Write(data) + writer.Write(bytes.Repeat([]byte{0x00}, int(importsPadding))) + binary.Write(writer, binary.LittleEndian, &imports) + binary.Write(writer, binary.LittleEndian, &dllData) + binary.Write(writer, binary.LittleEndian, &dllImports) } diff --git a/src/os/windows/DLLs.go b/src/os/windows/DLLs.go new file mode 100644 index 0000000..3b148ac --- /dev/null +++ b/src/os/windows/DLLs.go @@ -0,0 +1,21 @@ +package windows + +import "git.akyoto.dev/cli/q/src/dll" + +// Temporary fix... +var DLLs = dll.List{ + { + Name: "kernel32.dll", + Functions: []string{ + "ExitProcess", + "GetStdHandle", + "WriteFile", + }, + }, + { + Name: "user32.dll", + Functions: []string{ + "MessageBoxA", + }, + }, +} From ed6cee0acc346226aca7a6772d2a2259123bc062 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 19 Aug 2024 11:11:45 +0200 Subject: [PATCH 0547/1012] Improved Windows DLL calls --- lib/sys/sys_windows.q | 17 +++++++-------- src/asm/Finalize.go | 48 +++++++++++++++++++++++++---------------- src/asm/Instructions.go | 10 +++++++++ src/asm/Mnemonic.go | 6 +++--- src/compiler/Result.go | 31 +++++++++++++++----------- src/config/config.go | 2 -- src/core/CompileCall.go | 20 ++++++++++++++++- src/core/Function.go | 2 ++ src/dll/List.go | 20 +++++++++++++++++ src/exe/pe/EXE.go | 6 ++++-- src/os/windows/DLLs.go | 21 ------------------ src/register/DLLCall.go | 9 ++++++++ 12 files changed, 124 insertions(+), 68 deletions(-) delete mode 100644 src/os/windows/DLLs.go create mode 100644 src/register/DLLCall.go diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q index 575bb00..0ebf0a6 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/sys_windows.q @@ -1,14 +1,13 @@ -write(_ Int, _ Pointer, _ Int) -> Int { - // WriteFile(fd, buffer, length, out numberOfBytesWritten, out overlapped) - return 0 +write(fd Int, address Pointer, length Int) -> Int { + // out numberOfBytesWritten + // out overlapped + return kernel32.WriteFile(fd, address, length) } -mmap(_ Int, _ Int, _ Int, _ Int) -> Pointer { - // VirtualAlloc(address, length, flags, protection) - return 0 +mmap(address Int, length Int, protection Int, flags Int) -> Pointer { + return kernel32.VirtualAlloc(address, length, flags, protection) } -exit(_ Int) { - // ExitProcess(code) - return +exit(code Int) { + kernel32.ExitProcess(code) } \ No newline at end of file diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index de3eae6..676eaba 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -113,24 +113,6 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { codePointers = append(codePointers, pointer) - case CALL_AT: - code = x64.CallAtAddress(code, 0x00_00_00_00) - size := 4 - label := x.Data.(*Label) - - pointer := &Pointer{ - Position: Address(len(code) - size), - OpSize: 2, - Size: uint8(size), - } - - pointer.Resolve = func() Address { - index := dlls.Index("kernel32.dll", label.Name) - return Address(index * 8) - } - - dllPointers = append(dllPointers, pointer) - case COMMENT: continue @@ -142,6 +124,36 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source) } + case DLLCALL: + size := 4 + + code = x64.SubRegisterNumber(code, x64.RSP, 32) + code = x64.CallAtAddress(code, 0x00_00_00_00) + position := len(code) - size + code = x64.AddRegisterNumber(code, x64.RSP, 32) + + label := x.Data.(*Label) + pointer := &Pointer{ + Position: Address(position), + OpSize: 2, + Size: uint8(size), + } + + pointer.Resolve = func() Address { + dot := strings.Index(label.Name, ".") + library := label.Name[:dot] + funcName := label.Name[dot+1:] + index := dlls.Index(library, funcName) + + if index == -1 { + panic("unknown DLL function " + label.Name) + } + + return Address(index * 8) + } + + dllPointers = append(dllPointers, pointer) + case JE, JNE, JG, JGE, JL, JLE, JUMP: switch x.Mnemonic { case JE: diff --git a/src/asm/Instructions.go b/src/asm/Instructions.go index ced8cf5..acfa639 100644 --- a/src/asm/Instructions.go +++ b/src/asm/Instructions.go @@ -20,6 +20,16 @@ func (a *Assembler) Call(name string) { }) } +// DLLCall calls a function in a DLL file. +func (a *Assembler) DLLCall(name string) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: DLLCALL, + Data: &Label{ + Name: name, + }, + }) +} + // Return returns back to the caller. func (a *Assembler) Return() { if len(a.Instructions) > 0 { diff --git a/src/asm/Mnemonic.go b/src/asm/Mnemonic.go index 36687b7..c1e61e0 100644 --- a/src/asm/Mnemonic.go +++ b/src/asm/Mnemonic.go @@ -29,7 +29,7 @@ const ( // Control flow CALL - CALL_AT + DLLCALL JE JNE JG @@ -55,8 +55,8 @@ func (m Mnemonic) String() string { return "and" case CALL: return "call" - case CALL_AT: - return "call at" + case DLLCALL: + return "dllcall" case COMMENT: return "comment" case COMPARE: diff --git a/src/compiler/Result.go b/src/compiler/Result.go index fda6953..7c9a7c6 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -10,6 +10,7 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/exe/elf" "git.akyoto.dev/cli/q/src/exe/macho" "git.akyoto.dev/cli/q/src/exe/pe" @@ -27,7 +28,7 @@ type Result struct { } // finalize generates the final machine code. -func (r *Result) finalize() ([]byte, []byte) { +func (r *Result) finalize() ([]byte, []byte, dll.List) { // This will be the entry point of the executable. // The only job of the entry function is to call `main` and exit cleanly. // The reason we call `main` instead of using `main` itself is to place @@ -49,15 +50,22 @@ func (r *Result) finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() case "windows": - final.RegisterNumber(asm.SUB, x64.RSP, 32+8) - final.RegisterNumber(asm.MOVE, x64.RCX, 0) - final.Label(asm.CALL_AT, "ExitProcess") + final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 0) + final.DLLCall("kernel32.ExitProcess") } + dlls := dll.List{} + // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { final.Merge(f.Assembler) + + for _, library := range f.DLLs { + for _, fn := range library.Functions { + dlls = dlls.Append(library.Name, fn) + } + } }) final.Label(asm.LABEL, "_crash") @@ -72,13 +80,12 @@ func (r *Result) finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() case "windows": - final.RegisterNumber(asm.SUB, x64.RSP, 32+8) final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 1) - final.Label(asm.CALL_AT, "ExitProcess") + final.Label(asm.DLLCALL, "kernel32.ExitProcess") } - code, data := final.Finalize(windows.DLLs) - return code, data + code, data := final.Finalize(dlls) + return code, data, dlls } // eachFunction recursively finds all the calls to external functions. @@ -116,8 +123,8 @@ func (r *Result) PrintInstructions() { // Write writes the executable to the given writer. func (r *Result) Write(writer io.Writer) error { - code, data := r.finalize() - return write(writer, code, data) + code, data, dlls := r.finalize() + return write(writer, code, data, dlls) } // Write writes an executable file to disk. @@ -145,7 +152,7 @@ func (r *Result) WriteFile(path string) error { } // write writes an executable file to the given writer. -func write(writer io.Writer, code []byte, data []byte) error { +func write(writer io.Writer, code []byte, data []byte, dlls dll.List) error { buffer := bufio.NewWriter(writer) switch config.TargetOS { @@ -154,7 +161,7 @@ func write(writer io.Writer, code []byte, data []byte) error { case "mac": macho.Write(buffer, code, data) case "windows": - pe.Write(buffer, code, data, windows.DLLs) + pe.Write(buffer, code, data, dlls) default: return fmt.Errorf("unsupported platform '%s'", config.TargetOS) } diff --git a/src/config/config.go b/src/config/config.go index 79f7d58..32153a1 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -45,5 +45,3 @@ func Reset() { TargetOS = "mac" } } - -// diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 14ae685..2bf126a 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -1,9 +1,12 @@ package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/os/windows" "git.akyoto.dev/cli/q/src/types" ) @@ -31,7 +34,22 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { name = nameNode.Children[1].Token.Text(f.File.Bytes) } - if pkg != f.File.Package { + if pkg == "kernel32" || pkg == "user32" || pkg == "gdi32" || pkg == "comctl32" { + parameters := root.Children[1:] + registers := windows.X64InputRegisters[:len(parameters)] + + for i := len(parameters) - 1; i >= 0; i-- { + _, err := f.ExpressionToRegister(parameters[i], registers[i]) + + if err != nil { + return nil, err + } + } + + f.DLLs = f.DLLs.Append(pkg, name) + f.DLLCall(fmt.Sprintf("%s.%s", pkg, name)) + return nil, nil + } else if pkg != f.File.Package { if f.File.Imports == nil { return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position) } diff --git a/src/core/Function.go b/src/core/Function.go index 121ae8c..4694800 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -1,6 +1,7 @@ package core import ( + "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/register" "git.akyoto.dev/cli/q/src/scope" @@ -19,6 +20,7 @@ type Function struct { Parameters []*scope.Variable ReturnTypes []*types.Type Functions map[string]*Function + DLLs dll.List Err error deferred []func() count counter diff --git a/src/dll/List.go b/src/dll/List.go index b031981..236de5f 100644 --- a/src/dll/List.go +++ b/src/dll/List.go @@ -3,6 +3,26 @@ package dll // List is a slice of DLLs. type List []DLL +// Append adds a function for the given DLL if it doesn't exist yet. +func (list List) Append(dllName string, funcName string) List { + for _, dll := range list { + if dll.Name != dllName { + continue + } + + for _, fn := range dll.Functions { + if fn == funcName { + return list + } + } + + dll.Functions = append(dll.Functions, funcName) + return list + } + + return append(list, DLL{Name: dllName, Functions: []string{funcName}}) +} + // Index returns the position of the given function name. func (list List) Index(dllName string, funcName string) int { index := 0 diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index 60a7a79..726fcc4 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "io" + "time" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/dll" @@ -38,6 +39,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { functionsStart := len(imports) * 8 dllNamePos := len(dllData) dllData = append(dllData, library.Name...) + dllData = append(dllData, ".dll"...) dllData = append(dllData, 0x00) dllImports = append(dllImports, DLLImport{ @@ -95,7 +97,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { Signature: [4]byte{'P', 'E', 0, 0}, Machine: IMAGE_FILE_MACHINE_AMD64, NumberOfSections: uint16(NumSections), - TimeDateStamp: 0, + TimeDateStamp: uint32(time.Now().Unix()), PointerToSymbolTable: 0, NumberOfSymbols: 0, SizeOfOptionalHeader: OptionalHeader64Size, @@ -124,7 +126,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { SizeOfHeaders: config.CodeOffset, // section bodies begin here CheckSum: 0, Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI, - DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, + DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, // IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE SizeOfStackReserve: 0x100000, SizeOfStackCommit: 0x1000, SizeOfHeapReserve: 0x100000, diff --git a/src/os/windows/DLLs.go b/src/os/windows/DLLs.go deleted file mode 100644 index 3b148ac..0000000 --- a/src/os/windows/DLLs.go +++ /dev/null @@ -1,21 +0,0 @@ -package windows - -import "git.akyoto.dev/cli/q/src/dll" - -// Temporary fix... -var DLLs = dll.List{ - { - Name: "kernel32.dll", - Functions: []string{ - "ExitProcess", - "GetStdHandle", - "WriteFile", - }, - }, - { - Name: "user32.dll", - Functions: []string{ - "MessageBoxA", - }, - }, -} diff --git a/src/register/DLLCall.go b/src/register/DLLCall.go new file mode 100644 index 0000000..c62eb55 --- /dev/null +++ b/src/register/DLLCall.go @@ -0,0 +1,9 @@ +package register + +import "git.akyoto.dev/cli/q/src/asm" + +func (f *Machine) DLLCall(label string) { + f.Assembler.Label(asm.DLLCALL, label) + f.UseRegister(f.CPU.Output[0]) + f.postInstruction() +} From 05789d9626f9a59debf2150ab48264ca79abef43 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 19 Aug 2024 11:11:45 +0200 Subject: [PATCH 0548/1012] Improved Windows DLL calls --- lib/sys/sys_windows.q | 17 +++++++-------- src/asm/Finalize.go | 48 +++++++++++++++++++++++++---------------- src/asm/Instructions.go | 10 +++++++++ src/asm/Mnemonic.go | 6 +++--- src/compiler/Result.go | 31 +++++++++++++++----------- src/config/config.go | 2 -- src/core/CompileCall.go | 20 ++++++++++++++++- src/core/Function.go | 2 ++ src/dll/List.go | 20 +++++++++++++++++ src/exe/pe/EXE.go | 6 ++++-- src/os/windows/DLLs.go | 21 ------------------ src/register/DLLCall.go | 9 ++++++++ 12 files changed, 124 insertions(+), 68 deletions(-) delete mode 100644 src/os/windows/DLLs.go create mode 100644 src/register/DLLCall.go diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q index 575bb00..0ebf0a6 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/sys_windows.q @@ -1,14 +1,13 @@ -write(_ Int, _ Pointer, _ Int) -> Int { - // WriteFile(fd, buffer, length, out numberOfBytesWritten, out overlapped) - return 0 +write(fd Int, address Pointer, length Int) -> Int { + // out numberOfBytesWritten + // out overlapped + return kernel32.WriteFile(fd, address, length) } -mmap(_ Int, _ Int, _ Int, _ Int) -> Pointer { - // VirtualAlloc(address, length, flags, protection) - return 0 +mmap(address Int, length Int, protection Int, flags Int) -> Pointer { + return kernel32.VirtualAlloc(address, length, flags, protection) } -exit(_ Int) { - // ExitProcess(code) - return +exit(code Int) { + kernel32.ExitProcess(code) } \ No newline at end of file diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index de3eae6..676eaba 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -113,24 +113,6 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { codePointers = append(codePointers, pointer) - case CALL_AT: - code = x64.CallAtAddress(code, 0x00_00_00_00) - size := 4 - label := x.Data.(*Label) - - pointer := &Pointer{ - Position: Address(len(code) - size), - OpSize: 2, - Size: uint8(size), - } - - pointer.Resolve = func() Address { - index := dlls.Index("kernel32.dll", label.Name) - return Address(index * 8) - } - - dllPointers = append(dllPointers, pointer) - case COMMENT: continue @@ -142,6 +124,36 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source) } + case DLLCALL: + size := 4 + + code = x64.SubRegisterNumber(code, x64.RSP, 32) + code = x64.CallAtAddress(code, 0x00_00_00_00) + position := len(code) - size + code = x64.AddRegisterNumber(code, x64.RSP, 32) + + label := x.Data.(*Label) + pointer := &Pointer{ + Position: Address(position), + OpSize: 2, + Size: uint8(size), + } + + pointer.Resolve = func() Address { + dot := strings.Index(label.Name, ".") + library := label.Name[:dot] + funcName := label.Name[dot+1:] + index := dlls.Index(library, funcName) + + if index == -1 { + panic("unknown DLL function " + label.Name) + } + + return Address(index * 8) + } + + dllPointers = append(dllPointers, pointer) + case JE, JNE, JG, JGE, JL, JLE, JUMP: switch x.Mnemonic { case JE: diff --git a/src/asm/Instructions.go b/src/asm/Instructions.go index ced8cf5..acfa639 100644 --- a/src/asm/Instructions.go +++ b/src/asm/Instructions.go @@ -20,6 +20,16 @@ func (a *Assembler) Call(name string) { }) } +// DLLCall calls a function in a DLL file. +func (a *Assembler) DLLCall(name string) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: DLLCALL, + Data: &Label{ + Name: name, + }, + }) +} + // Return returns back to the caller. func (a *Assembler) Return() { if len(a.Instructions) > 0 { diff --git a/src/asm/Mnemonic.go b/src/asm/Mnemonic.go index 36687b7..c1e61e0 100644 --- a/src/asm/Mnemonic.go +++ b/src/asm/Mnemonic.go @@ -29,7 +29,7 @@ const ( // Control flow CALL - CALL_AT + DLLCALL JE JNE JG @@ -55,8 +55,8 @@ func (m Mnemonic) String() string { return "and" case CALL: return "call" - case CALL_AT: - return "call at" + case DLLCALL: + return "dllcall" case COMMENT: return "comment" case COMPARE: diff --git a/src/compiler/Result.go b/src/compiler/Result.go index fda6953..7c9a7c6 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -10,6 +10,7 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/exe/elf" "git.akyoto.dev/cli/q/src/exe/macho" "git.akyoto.dev/cli/q/src/exe/pe" @@ -27,7 +28,7 @@ type Result struct { } // finalize generates the final machine code. -func (r *Result) finalize() ([]byte, []byte) { +func (r *Result) finalize() ([]byte, []byte, dll.List) { // This will be the entry point of the executable. // The only job of the entry function is to call `main` and exit cleanly. // The reason we call `main` instead of using `main` itself is to place @@ -49,15 +50,22 @@ func (r *Result) finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() case "windows": - final.RegisterNumber(asm.SUB, x64.RSP, 32+8) - final.RegisterNumber(asm.MOVE, x64.RCX, 0) - final.Label(asm.CALL_AT, "ExitProcess") + final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 0) + final.DLLCall("kernel32.ExitProcess") } + dlls := dll.List{} + // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { final.Merge(f.Assembler) + + for _, library := range f.DLLs { + for _, fn := range library.Functions { + dlls = dlls.Append(library.Name, fn) + } + } }) final.Label(asm.LABEL, "_crash") @@ -72,13 +80,12 @@ func (r *Result) finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() case "windows": - final.RegisterNumber(asm.SUB, x64.RSP, 32+8) final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 1) - final.Label(asm.CALL_AT, "ExitProcess") + final.Label(asm.DLLCALL, "kernel32.ExitProcess") } - code, data := final.Finalize(windows.DLLs) - return code, data + code, data := final.Finalize(dlls) + return code, data, dlls } // eachFunction recursively finds all the calls to external functions. @@ -116,8 +123,8 @@ func (r *Result) PrintInstructions() { // Write writes the executable to the given writer. func (r *Result) Write(writer io.Writer) error { - code, data := r.finalize() - return write(writer, code, data) + code, data, dlls := r.finalize() + return write(writer, code, data, dlls) } // Write writes an executable file to disk. @@ -145,7 +152,7 @@ func (r *Result) WriteFile(path string) error { } // write writes an executable file to the given writer. -func write(writer io.Writer, code []byte, data []byte) error { +func write(writer io.Writer, code []byte, data []byte, dlls dll.List) error { buffer := bufio.NewWriter(writer) switch config.TargetOS { @@ -154,7 +161,7 @@ func write(writer io.Writer, code []byte, data []byte) error { case "mac": macho.Write(buffer, code, data) case "windows": - pe.Write(buffer, code, data, windows.DLLs) + pe.Write(buffer, code, data, dlls) default: return fmt.Errorf("unsupported platform '%s'", config.TargetOS) } diff --git a/src/config/config.go b/src/config/config.go index 79f7d58..32153a1 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -45,5 +45,3 @@ func Reset() { TargetOS = "mac" } } - -// diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 14ae685..2bf126a 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -1,9 +1,12 @@ package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/os/windows" "git.akyoto.dev/cli/q/src/types" ) @@ -31,7 +34,22 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { name = nameNode.Children[1].Token.Text(f.File.Bytes) } - if pkg != f.File.Package { + if pkg == "kernel32" || pkg == "user32" || pkg == "gdi32" || pkg == "comctl32" { + parameters := root.Children[1:] + registers := windows.X64InputRegisters[:len(parameters)] + + for i := len(parameters) - 1; i >= 0; i-- { + _, err := f.ExpressionToRegister(parameters[i], registers[i]) + + if err != nil { + return nil, err + } + } + + f.DLLs = f.DLLs.Append(pkg, name) + f.DLLCall(fmt.Sprintf("%s.%s", pkg, name)) + return nil, nil + } else if pkg != f.File.Package { if f.File.Imports == nil { return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position) } diff --git a/src/core/Function.go b/src/core/Function.go index 121ae8c..4694800 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -1,6 +1,7 @@ package core import ( + "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/register" "git.akyoto.dev/cli/q/src/scope" @@ -19,6 +20,7 @@ type Function struct { Parameters []*scope.Variable ReturnTypes []*types.Type Functions map[string]*Function + DLLs dll.List Err error deferred []func() count counter diff --git a/src/dll/List.go b/src/dll/List.go index b031981..236de5f 100644 --- a/src/dll/List.go +++ b/src/dll/List.go @@ -3,6 +3,26 @@ package dll // List is a slice of DLLs. type List []DLL +// Append adds a function for the given DLL if it doesn't exist yet. +func (list List) Append(dllName string, funcName string) List { + for _, dll := range list { + if dll.Name != dllName { + continue + } + + for _, fn := range dll.Functions { + if fn == funcName { + return list + } + } + + dll.Functions = append(dll.Functions, funcName) + return list + } + + return append(list, DLL{Name: dllName, Functions: []string{funcName}}) +} + // Index returns the position of the given function name. func (list List) Index(dllName string, funcName string) int { index := 0 diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index 60a7a79..726fcc4 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "io" + "time" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/dll" @@ -38,6 +39,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { functionsStart := len(imports) * 8 dllNamePos := len(dllData) dllData = append(dllData, library.Name...) + dllData = append(dllData, ".dll"...) dllData = append(dllData, 0x00) dllImports = append(dllImports, DLLImport{ @@ -95,7 +97,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { Signature: [4]byte{'P', 'E', 0, 0}, Machine: IMAGE_FILE_MACHINE_AMD64, NumberOfSections: uint16(NumSections), - TimeDateStamp: 0, + TimeDateStamp: uint32(time.Now().Unix()), PointerToSymbolTable: 0, NumberOfSymbols: 0, SizeOfOptionalHeader: OptionalHeader64Size, @@ -124,7 +126,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { SizeOfHeaders: config.CodeOffset, // section bodies begin here CheckSum: 0, Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI, - DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, + DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, // IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE SizeOfStackReserve: 0x100000, SizeOfStackCommit: 0x1000, SizeOfHeapReserve: 0x100000, diff --git a/src/os/windows/DLLs.go b/src/os/windows/DLLs.go deleted file mode 100644 index 3b148ac..0000000 --- a/src/os/windows/DLLs.go +++ /dev/null @@ -1,21 +0,0 @@ -package windows - -import "git.akyoto.dev/cli/q/src/dll" - -// Temporary fix... -var DLLs = dll.List{ - { - Name: "kernel32.dll", - Functions: []string{ - "ExitProcess", - "GetStdHandle", - "WriteFile", - }, - }, - { - Name: "user32.dll", - Functions: []string{ - "MessageBoxA", - }, - }, -} diff --git a/src/register/DLLCall.go b/src/register/DLLCall.go new file mode 100644 index 0000000..c62eb55 --- /dev/null +++ b/src/register/DLLCall.go @@ -0,0 +1,9 @@ +package register + +import "git.akyoto.dev/cli/q/src/asm" + +func (f *Machine) DLLCall(label string) { + f.Assembler.Label(asm.DLLCALL, label) + f.UseRegister(f.CPU.Output[0]) + f.postInstruction() +} From a7835a4494f54b63b14291718013ee64cde27091 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 19 Aug 2024 11:26:24 +0200 Subject: [PATCH 0549/1012] Added temporary fix for stack alignment --- src/compiler/Result.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 7c9a7c6..ee73c1c 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -50,11 +50,14 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() case "windows": + final.RegisterNumber(asm.SUB, x64.RSP, 8) final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 0) final.DLLCall("kernel32.ExitProcess") } - dlls := dll.List{} + dlls := dll.List{ + {Name: "kernel32", Functions: []string{"ExitProcess"}}, + } // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. @@ -80,6 +83,7 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() case "windows": + final.RegisterNumber(asm.SUB, x64.RSP, 8) final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 1) final.Label(asm.DLLCALL, "kernel32.ExitProcess") } From e9a0494aa7738227d07085ff30eb85a4ba00880d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 19 Aug 2024 11:26:24 +0200 Subject: [PATCH 0550/1012] Added temporary fix for stack alignment --- src/compiler/Result.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 7c9a7c6..ee73c1c 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -50,11 +50,14 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() case "windows": + final.RegisterNumber(asm.SUB, x64.RSP, 8) final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 0) final.DLLCall("kernel32.ExitProcess") } - dlls := dll.List{} + dlls := dll.List{ + {Name: "kernel32", Functions: []string{"ExitProcess"}}, + } // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. @@ -80,6 +83,7 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() case "windows": + final.RegisterNumber(asm.SUB, x64.RSP, 8) final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 1) final.Label(asm.DLLCALL, "kernel32.ExitProcess") } From 32ae625af4d3e980cc6e2727eeda92b889f4ec71 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 19 Aug 2024 17:25:51 +0200 Subject: [PATCH 0551/1012] Improved Windows support --- examples/winapi/winapi.q | 10 ++++++++++ lib/mem/alloc_windows.q | 5 +++++ lib/sys/sys_windows.q | 4 ++++ src/arch/x64/AlignStack.go | 6 ++++++ src/asm/Finalize.go | 6 ++++-- src/compiler/Result.go | 4 +--- src/dll/List.go | 15 +++++++++++++-- src/exe/pe/EXE.go | 8 +++++++- 8 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 examples/winapi/winapi.q create mode 100644 lib/mem/alloc_windows.q create mode 100644 src/arch/x64/AlignStack.go diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q new file mode 100644 index 0000000..e232a1b --- /dev/null +++ b/examples/winapi/winapi.q @@ -0,0 +1,10 @@ +import mem + +main() { + text := mem.alloc(4) + text[0] = 'H' + text[1] = 'i' + text[2] = '!' + user32.MessageBoxA(0, text, text, 0x00240040) + mem.free(text, 4) +} \ No newline at end of file diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q new file mode 100644 index 0000000..a6fafde --- /dev/null +++ b/lib/mem/alloc_windows.q @@ -0,0 +1,5 @@ +import sys + +alloc(length Int) -> Pointer { + return sys.mmap(0, length, 0x0004, 0x3000) +} \ No newline at end of file diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q index 0ebf0a6..3c9c82f 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/sys_windows.q @@ -8,6 +8,10 @@ mmap(address Int, length Int, protection Int, flags Int) -> Pointer { return kernel32.VirtualAlloc(address, length, flags, protection) } +munmap(address Pointer, length Int) -> Int { + return kernel32.VirtualFree(address, length, 0x4000) +} + exit(code Int) { kernel32.ExitProcess(code) } \ No newline at end of file diff --git a/src/arch/x64/AlignStack.go b/src/arch/x64/AlignStack.go new file mode 100644 index 0000000..d3199e1 --- /dev/null +++ b/src/arch/x64/AlignStack.go @@ -0,0 +1,6 @@ +package x64 + +// AlignStack aligns RSP on a 16-byte boundary. +func AlignStack(code []byte) []byte { + return append(code, 0x48, 0x83, 0xE4, 0xF0) +} diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 676eaba..492f722 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -126,11 +126,13 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { case DLLCALL: size := 4 - + // TODO: R15 could be in use. + code = x64.MoveRegisterRegister(code, x64.R15, x64.RSP) + code = x64.AlignStack(code) code = x64.SubRegisterNumber(code, x64.RSP, 32) code = x64.CallAtAddress(code, 0x00_00_00_00) position := len(code) - size - code = x64.AddRegisterNumber(code, x64.RSP, 32) + code = x64.MoveRegisterRegister(code, x64.RSP, x64.R15) label := x.Data.(*Label) pointer := &Pointer{ diff --git a/src/compiler/Result.go b/src/compiler/Result.go index ee73c1c..121d55a 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -50,7 +50,6 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() case "windows": - final.RegisterNumber(asm.SUB, x64.RSP, 8) final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 0) final.DLLCall("kernel32.ExitProcess") } @@ -83,9 +82,8 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() case "windows": - final.RegisterNumber(asm.SUB, x64.RSP, 8) final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 1) - final.Label(asm.DLLCALL, "kernel32.ExitProcess") + final.DLLCall("kernel32.ExitProcess") } code, data := final.Finalize(dlls) diff --git a/src/dll/List.go b/src/dll/List.go index 236de5f..8b3c3bb 100644 --- a/src/dll/List.go +++ b/src/dll/List.go @@ -5,7 +5,7 @@ type List []DLL // Append adds a function for the given DLL if it doesn't exist yet. func (list List) Append(dllName string, funcName string) List { - for _, dll := range list { + for i, dll := range list { if dll.Name != dllName { continue } @@ -16,13 +16,24 @@ func (list List) Append(dllName string, funcName string) List { } } - dll.Functions = append(dll.Functions, funcName) + list[i].Functions = append(list[i].Functions, funcName) return list } return append(list, DLL{Name: dllName, Functions: []string{funcName}}) } +// Contains returns true if the library exists. +func (list List) Contains(dllName string) bool { + for _, dll := range list { + if dll.Name == dllName { + return true + } + } + + return false +} + // Index returns the position of the given function name. func (list List) Index(dllName string, funcName string) int { index := 0 diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index 726fcc4..676f5f0 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -31,6 +31,12 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { dataStart, dataPadding := exe.Align(codeStart+len(code), config.Align) importsStart, importsPadding := exe.Align(dataStart+len(data), config.Align) + subSystem := IMAGE_SUBSYSTEM_WINDOWS_CUI + + if dlls.Contains("user32") { + subSystem = IMAGE_SUBSYSTEM_WINDOWS_GUI + } + imports := make([]uint64, 0) dllData := make([]byte, 0) dllImports := []DLLImport{} @@ -125,7 +131,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { SizeOfImage: uint32(imageSize), SizeOfHeaders: config.CodeOffset, // section bodies begin here CheckSum: 0, - Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI, + Subsystem: uint16(subSystem), DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, // IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE SizeOfStackReserve: 0x100000, SizeOfStackCommit: 0x1000, From 6b48ee0a48516003bbe67cd2d5d3bf760dfec545 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 19 Aug 2024 17:25:51 +0200 Subject: [PATCH 0552/1012] Improved Windows support --- examples/winapi/winapi.q | 10 ++++++++++ lib/mem/alloc_windows.q | 5 +++++ lib/sys/sys_windows.q | 4 ++++ src/arch/x64/AlignStack.go | 6 ++++++ src/asm/Finalize.go | 6 ++++-- src/compiler/Result.go | 4 +--- src/dll/List.go | 15 +++++++++++++-- src/exe/pe/EXE.go | 8 +++++++- 8 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 examples/winapi/winapi.q create mode 100644 lib/mem/alloc_windows.q create mode 100644 src/arch/x64/AlignStack.go diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q new file mode 100644 index 0000000..e232a1b --- /dev/null +++ b/examples/winapi/winapi.q @@ -0,0 +1,10 @@ +import mem + +main() { + text := mem.alloc(4) + text[0] = 'H' + text[1] = 'i' + text[2] = '!' + user32.MessageBoxA(0, text, text, 0x00240040) + mem.free(text, 4) +} \ No newline at end of file diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q new file mode 100644 index 0000000..a6fafde --- /dev/null +++ b/lib/mem/alloc_windows.q @@ -0,0 +1,5 @@ +import sys + +alloc(length Int) -> Pointer { + return sys.mmap(0, length, 0x0004, 0x3000) +} \ No newline at end of file diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q index 0ebf0a6..3c9c82f 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/sys_windows.q @@ -8,6 +8,10 @@ mmap(address Int, length Int, protection Int, flags Int) -> Pointer { return kernel32.VirtualAlloc(address, length, flags, protection) } +munmap(address Pointer, length Int) -> Int { + return kernel32.VirtualFree(address, length, 0x4000) +} + exit(code Int) { kernel32.ExitProcess(code) } \ No newline at end of file diff --git a/src/arch/x64/AlignStack.go b/src/arch/x64/AlignStack.go new file mode 100644 index 0000000..d3199e1 --- /dev/null +++ b/src/arch/x64/AlignStack.go @@ -0,0 +1,6 @@ +package x64 + +// AlignStack aligns RSP on a 16-byte boundary. +func AlignStack(code []byte) []byte { + return append(code, 0x48, 0x83, 0xE4, 0xF0) +} diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 676eaba..492f722 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -126,11 +126,13 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { case DLLCALL: size := 4 - + // TODO: R15 could be in use. + code = x64.MoveRegisterRegister(code, x64.R15, x64.RSP) + code = x64.AlignStack(code) code = x64.SubRegisterNumber(code, x64.RSP, 32) code = x64.CallAtAddress(code, 0x00_00_00_00) position := len(code) - size - code = x64.AddRegisterNumber(code, x64.RSP, 32) + code = x64.MoveRegisterRegister(code, x64.RSP, x64.R15) label := x.Data.(*Label) pointer := &Pointer{ diff --git a/src/compiler/Result.go b/src/compiler/Result.go index ee73c1c..121d55a 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -50,7 +50,6 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() case "windows": - final.RegisterNumber(asm.SUB, x64.RSP, 8) final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 0) final.DLLCall("kernel32.ExitProcess") } @@ -83,9 +82,8 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() case "windows": - final.RegisterNumber(asm.SUB, x64.RSP, 8) final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 1) - final.Label(asm.DLLCALL, "kernel32.ExitProcess") + final.DLLCall("kernel32.ExitProcess") } code, data := final.Finalize(dlls) diff --git a/src/dll/List.go b/src/dll/List.go index 236de5f..8b3c3bb 100644 --- a/src/dll/List.go +++ b/src/dll/List.go @@ -5,7 +5,7 @@ type List []DLL // Append adds a function for the given DLL if it doesn't exist yet. func (list List) Append(dllName string, funcName string) List { - for _, dll := range list { + for i, dll := range list { if dll.Name != dllName { continue } @@ -16,13 +16,24 @@ func (list List) Append(dllName string, funcName string) List { } } - dll.Functions = append(dll.Functions, funcName) + list[i].Functions = append(list[i].Functions, funcName) return list } return append(list, DLL{Name: dllName, Functions: []string{funcName}}) } +// Contains returns true if the library exists. +func (list List) Contains(dllName string) bool { + for _, dll := range list { + if dll.Name == dllName { + return true + } + } + + return false +} + // Index returns the position of the given function name. func (list List) Index(dllName string, funcName string) int { index := 0 diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index 726fcc4..676f5f0 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -31,6 +31,12 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { dataStart, dataPadding := exe.Align(codeStart+len(code), config.Align) importsStart, importsPadding := exe.Align(dataStart+len(data), config.Align) + subSystem := IMAGE_SUBSYSTEM_WINDOWS_CUI + + if dlls.Contains("user32") { + subSystem = IMAGE_SUBSYSTEM_WINDOWS_GUI + } + imports := make([]uint64, 0) dllData := make([]byte, 0) dllImports := []DLLImport{} @@ -125,7 +131,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { SizeOfImage: uint32(imageSize), SizeOfHeaders: config.CodeOffset, // section bodies begin here CheckSum: 0, - Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI, + Subsystem: uint16(subSystem), DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, // IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE SizeOfStackReserve: 0x100000, SizeOfStackCommit: 0x1000, From 680d541ef6a8f2e55fdc566431be9ff4bf8717a3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 19 Aug 2024 18:20:52 +0200 Subject: [PATCH 0553/1012] Removed timestamp --- src/exe/pe/EXE.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index 676f5f0..6b2c5df 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/binary" "io" - "time" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/dll" @@ -103,7 +102,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { Signature: [4]byte{'P', 'E', 0, 0}, Machine: IMAGE_FILE_MACHINE_AMD64, NumberOfSections: uint16(NumSections), - TimeDateStamp: uint32(time.Now().Unix()), + TimeDateStamp: 0, PointerToSymbolTable: 0, NumberOfSymbols: 0, SizeOfOptionalHeader: OptionalHeader64Size, From 07c7e6907874c2b93ac165efa0a70149cbef64e3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 19 Aug 2024 18:20:52 +0200 Subject: [PATCH 0554/1012] Removed timestamp --- src/exe/pe/EXE.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index 676f5f0..6b2c5df 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/binary" "io" - "time" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/dll" @@ -103,7 +102,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { Signature: [4]byte{'P', 'E', 0, 0}, Machine: IMAGE_FILE_MACHINE_AMD64, NumberOfSections: uint16(NumSections), - TimeDateStamp: uint32(time.Now().Unix()), + TimeDateStamp: 0, PointerToSymbolTable: 0, NumberOfSymbols: 0, SizeOfOptionalHeader: OptionalHeader64Size, From 12dcf672cb97d8cf9abc7c0108d40c45289a6f96 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 20 Aug 2024 12:37:54 +0200 Subject: [PATCH 0555/1012] Updated documentation --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 91aedf2..7fa9087 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # q -A simple programming language. +A programming language for use in systems programming, game development and web servers. ## Features * Fast compilation -* Small binaries +* Small executables * High performance ## Installation @@ -24,6 +24,18 @@ Build an executable from `examples/hello` and run it: ./q run examples/hello ``` +## Tests + +```shell +go test ./... -v -cover +``` + +## Benchmarks + +```shell +go test ./tests -run='^$' -bench=. -benchmem +``` + ## Todo ### Compiler @@ -178,18 +190,6 @@ This is what generates the AST from tokens. This is what generates expressions from tokens. -## Tests - -```shell -go test ./... -v -cover -``` - -## Benchmarks - -```shell -go test ./tests -bench=. -benchmem -``` - ## License Please see the [license documentation](https://akyoto.dev/license). From 2d8fe15abbc867b3391333a441c327c3412533a3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 20 Aug 2024 12:37:54 +0200 Subject: [PATCH 0556/1012] Updated documentation --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 91aedf2..7fa9087 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # q -A simple programming language. +A programming language for use in systems programming, game development and web servers. ## Features * Fast compilation -* Small binaries +* Small executables * High performance ## Installation @@ -24,6 +24,18 @@ Build an executable from `examples/hello` and run it: ./q run examples/hello ``` +## Tests + +```shell +go test ./... -v -cover +``` + +## Benchmarks + +```shell +go test ./tests -run='^$' -bench=. -benchmem +``` + ## Todo ### Compiler @@ -178,18 +190,6 @@ This is what generates the AST from tokens. This is what generates expressions from tokens. -## Tests - -```shell -go test ./... -v -cover -``` - -## Benchmarks - -```shell -go test ./tests -bench=. -benchmem -``` - ## License Please see the [license documentation](https://akyoto.dev/license). From a4dc5a4eff57e0447c761a21c403ef1a286ae9fd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 25 Aug 2024 20:38:22 +0200 Subject: [PATCH 0557/1012] Flattened package hierarchy --- src/{arch => }/arm64/Registers.go | 0 src/{arch => }/arm64/Registers_test.go | 2 +- src/asm/Finalize.go | 2 +- src/compiler/Result.go | 14 +++++++------- src/core/CompileAssignDivision.go | 2 +- src/core/CompileCall.go | 2 +- src/core/ExecuteRegisterNumber.go | 2 +- src/core/ExecuteRegisterRegister.go | 2 +- src/core/NewFunction.go | 2 +- src/{exe => }/elf/Constants.go | 0 src/{exe => }/elf/ELF.go | 0 src/{exe => }/elf/ELF_test.go | 2 +- src/{exe => }/elf/Header.go | 0 src/{exe => }/elf/ProgramHeader.go | 0 src/{exe => }/elf/SectionHeader.go | 0 src/{exe => }/elf/elf.md | 0 src/{os => }/linux/Syscall.go | 0 src/{os => }/mac/Syscall.go | 0 src/{exe => }/macho/Constants.go | 0 src/{exe => }/macho/Header.go | 0 src/{exe => }/macho/LoadCommand.go | 0 src/{exe => }/macho/MachO.go | 0 src/{exe => }/macho/MachO_test.go | 2 +- src/{exe => }/macho/Segment64.go | 0 src/{exe => }/macho/Thread.go | 0 src/{exe => }/macho/macho.md | 0 src/{exe => }/pe/Constants.go | 0 src/{exe => }/pe/DLLImport.go | 0 src/{exe => }/pe/DOSHeader.go | 0 src/{exe => }/pe/DataDirectory.go | 0 src/{exe => }/pe/EXE.go | 0 src/{exe => }/pe/EXE_test.go | 2 +- src/{exe => }/pe/NTHeader.go | 0 src/{exe => }/pe/OptionalHeader64.go | 0 src/{exe => }/pe/SectionHeader.go | 0 src/{exe => }/pe/pe.md | 0 src/{arch => }/riscv/Registers.go | 0 src/{arch => }/riscv/Registers_test.go | 2 +- src/scanner/scanFile.go | 2 +- src/{os => }/windows/Registers.go | 4 ++-- src/{arch => }/x64/Add.go | 0 src/{arch => }/x64/Add_test.go | 2 +- src/{arch => }/x64/AlignStack.go | 0 src/{arch => }/x64/And.go | 0 src/{arch => }/x64/And_test.go | 2 +- src/{arch => }/x64/Call.go | 0 src/{arch => }/x64/Compare.go | 0 src/{arch => }/x64/Compare_test.go | 2 +- src/{arch => }/x64/Div.go | 0 src/{arch => }/x64/Div_test.go | 2 +- src/{arch => }/x64/ExtendRAXToRDX.go | 0 src/{arch => }/x64/Jump.go | 0 src/{arch => }/x64/Jump_test.go | 2 +- src/{arch => }/x64/Load.go | 0 src/{arch => }/x64/Load_test.go | 2 +- src/{arch => }/x64/ModRM.go | 0 src/{arch => }/x64/ModRM_test.go | 2 +- src/{arch => }/x64/Move.go | 0 src/{arch => }/x64/Move_test.go | 2 +- src/{arch => }/x64/Mul.go | 0 src/{arch => }/x64/Mul_test.go | 2 +- src/{arch => }/x64/Negate.go | 0 src/{arch => }/x64/Negate_test.go | 2 +- src/{arch => }/x64/Or.go | 0 src/{arch => }/x64/Or_test.go | 2 +- src/{arch => }/x64/Pop.go | 0 src/{arch => }/x64/Pop_test.go | 2 +- src/{arch => }/x64/Push.go | 0 src/{arch => }/x64/Push_test.go | 2 +- src/{arch => }/x64/REX.go | 0 src/{arch => }/x64/REX_test.go | 2 +- src/{arch => }/x64/Registers.go | 0 src/{arch => }/x64/Registers_test.go | 2 +- src/{arch => }/x64/Return.go | 0 src/{arch => }/x64/SIB.go | 0 src/{arch => }/x64/SIB_test.go | 2 +- src/{arch => }/x64/Shift.go | 0 src/{arch => }/x64/Shift_test.go | 2 +- src/{arch => }/x64/Store.go | 0 src/{arch => }/x64/Store_test.go | 2 +- src/{arch => }/x64/Sub.go | 0 src/{arch => }/x64/Sub_test.go | 2 +- src/{arch => }/x64/Syscall.go | 0 src/{arch => }/x64/Xor.go | 0 src/{arch => }/x64/Xor_test.go | 2 +- src/{arch => }/x64/encode.go | 0 src/{arch => }/x64/encodeNum.go | 0 src/{arch => }/x64/memoryAccess.go | 0 src/{arch => }/x64/x64_test.go | 2 +- 89 files changed, 42 insertions(+), 42 deletions(-) rename src/{arch => }/arm64/Registers.go (100%) rename src/{arch => }/arm64/Registers_test.go (80%) rename src/{exe => }/elf/Constants.go (100%) rename src/{exe => }/elf/ELF.go (100%) rename src/{exe => }/elf/ELF_test.go (76%) rename src/{exe => }/elf/Header.go (100%) rename src/{exe => }/elf/ProgramHeader.go (100%) rename src/{exe => }/elf/SectionHeader.go (100%) rename src/{exe => }/elf/elf.md (100%) rename src/{os => }/linux/Syscall.go (100%) rename src/{os => }/mac/Syscall.go (100%) rename src/{exe => }/macho/Constants.go (100%) rename src/{exe => }/macho/Header.go (100%) rename src/{exe => }/macho/LoadCommand.go (100%) rename src/{exe => }/macho/MachO.go (100%) rename src/{exe => }/macho/MachO_test.go (75%) rename src/{exe => }/macho/Segment64.go (100%) rename src/{exe => }/macho/Thread.go (100%) rename src/{exe => }/macho/macho.md (100%) rename src/{exe => }/pe/Constants.go (100%) rename src/{exe => }/pe/DLLImport.go (100%) rename src/{exe => }/pe/DOSHeader.go (100%) rename src/{exe => }/pe/DataDirectory.go (100%) rename src/{exe => }/pe/EXE.go (100%) rename src/{exe => }/pe/EXE_test.go (76%) rename src/{exe => }/pe/NTHeader.go (100%) rename src/{exe => }/pe/OptionalHeader64.go (100%) rename src/{exe => }/pe/SectionHeader.go (100%) rename src/{exe => }/pe/pe.md (100%) rename src/{arch => }/riscv/Registers.go (100%) rename src/{arch => }/riscv/Registers_test.go (80%) rename src/{os => }/windows/Registers.go (84%) rename src/{arch => }/x64/Add.go (100%) rename src/{arch => }/x64/Add_test.go (98%) rename src/{arch => }/x64/AlignStack.go (100%) rename src/{arch => }/x64/And.go (100%) rename src/{arch => }/x64/And_test.go (98%) rename src/{arch => }/x64/Call.go (100%) rename src/{arch => }/x64/Compare.go (100%) rename src/{arch => }/x64/Compare_test.go (98%) rename src/{arch => }/x64/Div.go (100%) rename src/{arch => }/x64/Div_test.go (96%) rename src/{arch => }/x64/ExtendRAXToRDX.go (100%) rename src/{arch => }/x64/Jump.go (100%) rename src/{arch => }/x64/Jump_test.go (96%) rename src/{arch => }/x64/Load.go (100%) rename src/{arch => }/x64/Load_test.go (99%) rename src/{arch => }/x64/ModRM.go (100%) rename src/{arch => }/x64/ModRM_test.go (96%) rename src/{arch => }/x64/Move.go (100%) rename src/{arch => }/x64/Move_test.go (99%) rename src/{arch => }/x64/Mul.go (100%) rename src/{arch => }/x64/Mul_test.go (98%) rename src/{arch => }/x64/Negate.go (100%) rename src/{arch => }/x64/Negate_test.go (96%) rename src/{arch => }/x64/Or.go (100%) rename src/{arch => }/x64/Or_test.go (98%) rename src/{arch => }/x64/Pop.go (100%) rename src/{arch => }/x64/Pop_test.go (95%) rename src/{arch => }/x64/Push.go (100%) rename src/{arch => }/x64/Push_test.go (95%) rename src/{arch => }/x64/REX.go (100%) rename src/{arch => }/x64/REX_test.go (95%) rename src/{arch => }/x64/Registers.go (100%) rename src/{arch => }/x64/Registers_test.go (81%) rename src/{arch => }/x64/Return.go (100%) rename src/{arch => }/x64/SIB.go (100%) rename src/{arch => }/x64/SIB_test.go (96%) rename src/{arch => }/x64/Shift.go (100%) rename src/{arch => }/x64/Shift_test.go (98%) rename src/{arch => }/x64/Store.go (100%) rename src/{arch => }/x64/Store_test.go (99%) rename src/{arch => }/x64/Sub.go (100%) rename src/{arch => }/x64/Sub_test.go (98%) rename src/{arch => }/x64/Syscall.go (100%) rename src/{arch => }/x64/Xor.go (100%) rename src/{arch => }/x64/Xor_test.go (98%) rename src/{arch => }/x64/encode.go (100%) rename src/{arch => }/x64/encodeNum.go (100%) rename src/{arch => }/x64/memoryAccess.go (100%) rename src/{arch => }/x64/x64_test.go (93%) diff --git a/src/arch/arm64/Registers.go b/src/arm64/Registers.go similarity index 100% rename from src/arch/arm64/Registers.go rename to src/arm64/Registers.go diff --git a/src/arch/arm64/Registers_test.go b/src/arm64/Registers_test.go similarity index 80% rename from src/arch/arm64/Registers_test.go rename to src/arm64/Registers_test.go index 75f5b7c..14ce983 100644 --- a/src/arch/arm64/Registers_test.go +++ b/src/arm64/Registers_test.go @@ -3,7 +3,7 @@ package arm64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/arm64" + "git.akyoto.dev/cli/q/src/arm64" "git.akyoto.dev/go/assert" ) diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 492f722..3ae543a 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -6,11 +6,11 @@ import ( "slices" "strings" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/exe" "git.akyoto.dev/cli/q/src/sizeof" + "git.akyoto.dev/cli/q/src/x64" ) // Finalize generates the final machine code. diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 121d55a..7343241 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -6,17 +6,17 @@ import ( "io" "os" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/exe/elf" - "git.akyoto.dev/cli/q/src/exe/macho" - "git.akyoto.dev/cli/q/src/exe/pe" - "git.akyoto.dev/cli/q/src/os/linux" - "git.akyoto.dev/cli/q/src/os/mac" - "git.akyoto.dev/cli/q/src/os/windows" + "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/linux" + "git.akyoto.dev/cli/q/src/mac" + "git.akyoto.dev/cli/q/src/macho" + "git.akyoto.dev/cli/q/src/pe" + "git.akyoto.dev/cli/q/src/windows" + "git.akyoto.dev/cli/q/src/x64" ) // Result contains all the compiled functions in a build. diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index 7531001..22f68fd 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/x64" ) // CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right. diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 2bf126a..fe63447 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -6,8 +6,8 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/os/windows" "git.akyoto.dev/cli/q/src/types" + "git.akyoto.dev/cli/q/src/windows" ) // CompileCall executes a function call. diff --git a/src/core/ExecuteRegisterNumber.go b/src/core/ExecuteRegisterNumber.go index e3cfda4..0714751 100644 --- a/src/core/ExecuteRegisterNumber.go +++ b/src/core/ExecuteRegisterNumber.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/x64" ) // ExecuteRegisterNumber performs an operation on a register and a number. diff --git a/src/core/ExecuteRegisterRegister.go b/src/core/ExecuteRegisterRegister.go index 738ed9c..3add7ef 100644 --- a/src/core/ExecuteRegisterRegister.go +++ b/src/core/ExecuteRegisterRegister.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/x64" ) // ExecuteRegisterRegister performs an operation on two registers. diff --git a/src/core/NewFunction.go b/src/core/NewFunction.go index 0f7bccc..5e5a4ae 100644 --- a/src/core/NewFunction.go +++ b/src/core/NewFunction.go @@ -1,13 +1,13 @@ package core import ( - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/register" "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/x64" ) // NewFunction creates a new function. diff --git a/src/exe/elf/Constants.go b/src/elf/Constants.go similarity index 100% rename from src/exe/elf/Constants.go rename to src/elf/Constants.go diff --git a/src/exe/elf/ELF.go b/src/elf/ELF.go similarity index 100% rename from src/exe/elf/ELF.go rename to src/elf/ELF.go diff --git a/src/exe/elf/ELF_test.go b/src/elf/ELF_test.go similarity index 76% rename from src/exe/elf/ELF_test.go rename to src/elf/ELF_test.go index aa28d0d..c813536 100644 --- a/src/exe/elf/ELF_test.go +++ b/src/elf/ELF_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/exe/elf" + "git.akyoto.dev/cli/q/src/elf" ) func TestWrite(t *testing.T) { diff --git a/src/exe/elf/Header.go b/src/elf/Header.go similarity index 100% rename from src/exe/elf/Header.go rename to src/elf/Header.go diff --git a/src/exe/elf/ProgramHeader.go b/src/elf/ProgramHeader.go similarity index 100% rename from src/exe/elf/ProgramHeader.go rename to src/elf/ProgramHeader.go diff --git a/src/exe/elf/SectionHeader.go b/src/elf/SectionHeader.go similarity index 100% rename from src/exe/elf/SectionHeader.go rename to src/elf/SectionHeader.go diff --git a/src/exe/elf/elf.md b/src/elf/elf.md similarity index 100% rename from src/exe/elf/elf.md rename to src/elf/elf.md diff --git a/src/os/linux/Syscall.go b/src/linux/Syscall.go similarity index 100% rename from src/os/linux/Syscall.go rename to src/linux/Syscall.go diff --git a/src/os/mac/Syscall.go b/src/mac/Syscall.go similarity index 100% rename from src/os/mac/Syscall.go rename to src/mac/Syscall.go diff --git a/src/exe/macho/Constants.go b/src/macho/Constants.go similarity index 100% rename from src/exe/macho/Constants.go rename to src/macho/Constants.go diff --git a/src/exe/macho/Header.go b/src/macho/Header.go similarity index 100% rename from src/exe/macho/Header.go rename to src/macho/Header.go diff --git a/src/exe/macho/LoadCommand.go b/src/macho/LoadCommand.go similarity index 100% rename from src/exe/macho/LoadCommand.go rename to src/macho/LoadCommand.go diff --git a/src/exe/macho/MachO.go b/src/macho/MachO.go similarity index 100% rename from src/exe/macho/MachO.go rename to src/macho/MachO.go diff --git a/src/exe/macho/MachO_test.go b/src/macho/MachO_test.go similarity index 75% rename from src/exe/macho/MachO_test.go rename to src/macho/MachO_test.go index 29b63c5..a026960 100644 --- a/src/exe/macho/MachO_test.go +++ b/src/macho/MachO_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/exe/macho" + "git.akyoto.dev/cli/q/src/macho" ) func TestWrite(t *testing.T) { diff --git a/src/exe/macho/Segment64.go b/src/macho/Segment64.go similarity index 100% rename from src/exe/macho/Segment64.go rename to src/macho/Segment64.go diff --git a/src/exe/macho/Thread.go b/src/macho/Thread.go similarity index 100% rename from src/exe/macho/Thread.go rename to src/macho/Thread.go diff --git a/src/exe/macho/macho.md b/src/macho/macho.md similarity index 100% rename from src/exe/macho/macho.md rename to src/macho/macho.md diff --git a/src/exe/pe/Constants.go b/src/pe/Constants.go similarity index 100% rename from src/exe/pe/Constants.go rename to src/pe/Constants.go diff --git a/src/exe/pe/DLLImport.go b/src/pe/DLLImport.go similarity index 100% rename from src/exe/pe/DLLImport.go rename to src/pe/DLLImport.go diff --git a/src/exe/pe/DOSHeader.go b/src/pe/DOSHeader.go similarity index 100% rename from src/exe/pe/DOSHeader.go rename to src/pe/DOSHeader.go diff --git a/src/exe/pe/DataDirectory.go b/src/pe/DataDirectory.go similarity index 100% rename from src/exe/pe/DataDirectory.go rename to src/pe/DataDirectory.go diff --git a/src/exe/pe/EXE.go b/src/pe/EXE.go similarity index 100% rename from src/exe/pe/EXE.go rename to src/pe/EXE.go diff --git a/src/exe/pe/EXE_test.go b/src/pe/EXE_test.go similarity index 76% rename from src/exe/pe/EXE_test.go rename to src/pe/EXE_test.go index 5f8d07e..64a6356 100644 --- a/src/exe/pe/EXE_test.go +++ b/src/pe/EXE_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/exe/pe" + "git.akyoto.dev/cli/q/src/pe" ) func TestWrite(t *testing.T) { diff --git a/src/exe/pe/NTHeader.go b/src/pe/NTHeader.go similarity index 100% rename from src/exe/pe/NTHeader.go rename to src/pe/NTHeader.go diff --git a/src/exe/pe/OptionalHeader64.go b/src/pe/OptionalHeader64.go similarity index 100% rename from src/exe/pe/OptionalHeader64.go rename to src/pe/OptionalHeader64.go diff --git a/src/exe/pe/SectionHeader.go b/src/pe/SectionHeader.go similarity index 100% rename from src/exe/pe/SectionHeader.go rename to src/pe/SectionHeader.go diff --git a/src/exe/pe/pe.md b/src/pe/pe.md similarity index 100% rename from src/exe/pe/pe.md rename to src/pe/pe.md diff --git a/src/arch/riscv/Registers.go b/src/riscv/Registers.go similarity index 100% rename from src/arch/riscv/Registers.go rename to src/riscv/Registers.go diff --git a/src/arch/riscv/Registers_test.go b/src/riscv/Registers_test.go similarity index 80% rename from src/arch/riscv/Registers_test.go rename to src/riscv/Registers_test.go index bc74568..efef49d 100644 --- a/src/arch/riscv/Registers_test.go +++ b/src/riscv/Registers_test.go @@ -3,7 +3,7 @@ package riscv_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/riscv" + "git.akyoto.dev/cli/q/src/riscv" "git.akyoto.dev/go/assert" ) diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 4f7c6e1..6d57c0a 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -4,7 +4,6 @@ import ( "os" "path/filepath" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/errors" @@ -12,6 +11,7 @@ import ( "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/types" + "git.akyoto.dev/cli/q/src/x64" ) // scanFile scans a single file. diff --git a/src/os/windows/Registers.go b/src/windows/Registers.go similarity index 84% rename from src/os/windows/Registers.go rename to src/windows/Registers.go index ffbbd21..1043298 100644 --- a/src/os/windows/Registers.go +++ b/src/windows/Registers.go @@ -1,9 +1,9 @@ package windows import ( - "git.akyoto.dev/cli/q/src/arch/arm64" - "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/arm64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" ) var ( diff --git a/src/arch/x64/Add.go b/src/x64/Add.go similarity index 100% rename from src/arch/x64/Add.go rename to src/x64/Add.go diff --git a/src/arch/x64/Add_test.go b/src/x64/Add_test.go similarity index 98% rename from src/arch/x64/Add_test.go rename to src/x64/Add_test.go index 52d6645..134e09e 100644 --- a/src/arch/x64/Add_test.go +++ b/src/x64/Add_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/AlignStack.go b/src/x64/AlignStack.go similarity index 100% rename from src/arch/x64/AlignStack.go rename to src/x64/AlignStack.go diff --git a/src/arch/x64/And.go b/src/x64/And.go similarity index 100% rename from src/arch/x64/And.go rename to src/x64/And.go diff --git a/src/arch/x64/And_test.go b/src/x64/And_test.go similarity index 98% rename from src/arch/x64/And_test.go rename to src/x64/And_test.go index e0e60c8..249d7b8 100644 --- a/src/arch/x64/And_test.go +++ b/src/x64/And_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Call.go b/src/x64/Call.go similarity index 100% rename from src/arch/x64/Call.go rename to src/x64/Call.go diff --git a/src/arch/x64/Compare.go b/src/x64/Compare.go similarity index 100% rename from src/arch/x64/Compare.go rename to src/x64/Compare.go diff --git a/src/arch/x64/Compare_test.go b/src/x64/Compare_test.go similarity index 98% rename from src/arch/x64/Compare_test.go rename to src/x64/Compare_test.go index 6372665..0105ed0 100644 --- a/src/arch/x64/Compare_test.go +++ b/src/x64/Compare_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Div.go b/src/x64/Div.go similarity index 100% rename from src/arch/x64/Div.go rename to src/x64/Div.go diff --git a/src/arch/x64/Div_test.go b/src/x64/Div_test.go similarity index 96% rename from src/arch/x64/Div_test.go rename to src/x64/Div_test.go index 3598dbf..de684b4 100644 --- a/src/arch/x64/Div_test.go +++ b/src/x64/Div_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/ExtendRAXToRDX.go b/src/x64/ExtendRAXToRDX.go similarity index 100% rename from src/arch/x64/ExtendRAXToRDX.go rename to src/x64/ExtendRAXToRDX.go diff --git a/src/arch/x64/Jump.go b/src/x64/Jump.go similarity index 100% rename from src/arch/x64/Jump.go rename to src/x64/Jump.go diff --git a/src/arch/x64/Jump_test.go b/src/x64/Jump_test.go similarity index 96% rename from src/arch/x64/Jump_test.go rename to src/x64/Jump_test.go index fe40c50..4dde162 100644 --- a/src/arch/x64/Jump_test.go +++ b/src/x64/Jump_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Load.go b/src/x64/Load.go similarity index 100% rename from src/arch/x64/Load.go rename to src/x64/Load.go diff --git a/src/arch/x64/Load_test.go b/src/x64/Load_test.go similarity index 99% rename from src/arch/x64/Load_test.go rename to src/x64/Load_test.go index 31330c6..9585f15 100644 --- a/src/arch/x64/Load_test.go +++ b/src/x64/Load_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/ModRM.go b/src/x64/ModRM.go similarity index 100% rename from src/arch/x64/ModRM.go rename to src/x64/ModRM.go diff --git a/src/arch/x64/ModRM_test.go b/src/x64/ModRM_test.go similarity index 96% rename from src/arch/x64/ModRM_test.go rename to src/x64/ModRM_test.go index edcaffe..09f72eb 100644 --- a/src/arch/x64/ModRM_test.go +++ b/src/x64/ModRM_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Move.go b/src/x64/Move.go similarity index 100% rename from src/arch/x64/Move.go rename to src/x64/Move.go diff --git a/src/arch/x64/Move_test.go b/src/x64/Move_test.go similarity index 99% rename from src/arch/x64/Move_test.go rename to src/x64/Move_test.go index faf9fcc..071e05b 100644 --- a/src/arch/x64/Move_test.go +++ b/src/x64/Move_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Mul.go b/src/x64/Mul.go similarity index 100% rename from src/arch/x64/Mul.go rename to src/x64/Mul.go diff --git a/src/arch/x64/Mul_test.go b/src/x64/Mul_test.go similarity index 98% rename from src/arch/x64/Mul_test.go rename to src/x64/Mul_test.go index cd8da70..03ffae1 100644 --- a/src/arch/x64/Mul_test.go +++ b/src/x64/Mul_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Negate.go b/src/x64/Negate.go similarity index 100% rename from src/arch/x64/Negate.go rename to src/x64/Negate.go diff --git a/src/arch/x64/Negate_test.go b/src/x64/Negate_test.go similarity index 96% rename from src/arch/x64/Negate_test.go rename to src/x64/Negate_test.go index 98aa13e..625e601 100644 --- a/src/arch/x64/Negate_test.go +++ b/src/x64/Negate_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Or.go b/src/x64/Or.go similarity index 100% rename from src/arch/x64/Or.go rename to src/x64/Or.go diff --git a/src/arch/x64/Or_test.go b/src/x64/Or_test.go similarity index 98% rename from src/arch/x64/Or_test.go rename to src/x64/Or_test.go index 89bdc5d..d85065a 100644 --- a/src/arch/x64/Or_test.go +++ b/src/x64/Or_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Pop.go b/src/x64/Pop.go similarity index 100% rename from src/arch/x64/Pop.go rename to src/x64/Pop.go diff --git a/src/arch/x64/Pop_test.go b/src/x64/Pop_test.go similarity index 95% rename from src/arch/x64/Pop_test.go rename to src/x64/Pop_test.go index 0a4ce98..446eebe 100644 --- a/src/arch/x64/Pop_test.go +++ b/src/x64/Pop_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Push.go b/src/x64/Push.go similarity index 100% rename from src/arch/x64/Push.go rename to src/x64/Push.go diff --git a/src/arch/x64/Push_test.go b/src/x64/Push_test.go similarity index 95% rename from src/arch/x64/Push_test.go rename to src/x64/Push_test.go index 0dc092b..a49095d 100644 --- a/src/arch/x64/Push_test.go +++ b/src/x64/Push_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/REX.go b/src/x64/REX.go similarity index 100% rename from src/arch/x64/REX.go rename to src/x64/REX.go diff --git a/src/arch/x64/REX_test.go b/src/x64/REX_test.go similarity index 95% rename from src/arch/x64/REX_test.go rename to src/x64/REX_test.go index b212b24..c754354 100644 --- a/src/arch/x64/REX_test.go +++ b/src/x64/REX_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Registers.go b/src/x64/Registers.go similarity index 100% rename from src/arch/x64/Registers.go rename to src/x64/Registers.go diff --git a/src/arch/x64/Registers_test.go b/src/x64/Registers_test.go similarity index 81% rename from src/arch/x64/Registers_test.go rename to src/x64/Registers_test.go index bb8ff19..626bb84 100644 --- a/src/arch/x64/Registers_test.go +++ b/src/x64/Registers_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Return.go b/src/x64/Return.go similarity index 100% rename from src/arch/x64/Return.go rename to src/x64/Return.go diff --git a/src/arch/x64/SIB.go b/src/x64/SIB.go similarity index 100% rename from src/arch/x64/SIB.go rename to src/x64/SIB.go diff --git a/src/arch/x64/SIB_test.go b/src/x64/SIB_test.go similarity index 96% rename from src/arch/x64/SIB_test.go rename to src/x64/SIB_test.go index 7dedf6e..911b133 100644 --- a/src/arch/x64/SIB_test.go +++ b/src/x64/SIB_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Shift.go b/src/x64/Shift.go similarity index 100% rename from src/arch/x64/Shift.go rename to src/x64/Shift.go diff --git a/src/arch/x64/Shift_test.go b/src/x64/Shift_test.go similarity index 98% rename from src/arch/x64/Shift_test.go rename to src/x64/Shift_test.go index 926c98d..dc95cf2 100644 --- a/src/arch/x64/Shift_test.go +++ b/src/x64/Shift_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Store.go b/src/x64/Store.go similarity index 100% rename from src/arch/x64/Store.go rename to src/x64/Store.go diff --git a/src/arch/x64/Store_test.go b/src/x64/Store_test.go similarity index 99% rename from src/arch/x64/Store_test.go rename to src/x64/Store_test.go index 1d9d9a9..3f4bd05 100644 --- a/src/arch/x64/Store_test.go +++ b/src/x64/Store_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Sub.go b/src/x64/Sub.go similarity index 100% rename from src/arch/x64/Sub.go rename to src/x64/Sub.go diff --git a/src/arch/x64/Sub_test.go b/src/x64/Sub_test.go similarity index 98% rename from src/arch/x64/Sub_test.go rename to src/x64/Sub_test.go index d6898e9..7f03b53 100644 --- a/src/arch/x64/Sub_test.go +++ b/src/x64/Sub_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Syscall.go b/src/x64/Syscall.go similarity index 100% rename from src/arch/x64/Syscall.go rename to src/x64/Syscall.go diff --git a/src/arch/x64/Xor.go b/src/x64/Xor.go similarity index 100% rename from src/arch/x64/Xor.go rename to src/x64/Xor.go diff --git a/src/arch/x64/Xor_test.go b/src/x64/Xor_test.go similarity index 98% rename from src/arch/x64/Xor_test.go rename to src/x64/Xor_test.go index c9e0744..1a2b629 100644 --- a/src/arch/x64/Xor_test.go +++ b/src/x64/Xor_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/encode.go b/src/x64/encode.go similarity index 100% rename from src/arch/x64/encode.go rename to src/x64/encode.go diff --git a/src/arch/x64/encodeNum.go b/src/x64/encodeNum.go similarity index 100% rename from src/arch/x64/encodeNum.go rename to src/x64/encodeNum.go diff --git a/src/arch/x64/memoryAccess.go b/src/x64/memoryAccess.go similarity index 100% rename from src/arch/x64/memoryAccess.go rename to src/x64/memoryAccess.go diff --git a/src/arch/x64/x64_test.go b/src/x64/x64_test.go similarity index 93% rename from src/arch/x64/x64_test.go rename to src/x64/x64_test.go index 0ed00d5..23f61ef 100644 --- a/src/arch/x64/x64_test.go +++ b/src/x64/x64_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) From b35b17bb32d1b914816c98a2164eb4bf026f5d7b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 25 Aug 2024 20:38:22 +0200 Subject: [PATCH 0558/1012] Flattened package hierarchy --- src/{arch => }/arm64/Registers.go | 0 src/{arch => }/arm64/Registers_test.go | 2 +- src/asm/Finalize.go | 2 +- src/compiler/Result.go | 14 +++++++------- src/core/CompileAssignDivision.go | 2 +- src/core/CompileCall.go | 2 +- src/core/ExecuteRegisterNumber.go | 2 +- src/core/ExecuteRegisterRegister.go | 2 +- src/core/NewFunction.go | 2 +- src/{exe => }/elf/Constants.go | 0 src/{exe => }/elf/ELF.go | 0 src/{exe => }/elf/ELF_test.go | 2 +- src/{exe => }/elf/Header.go | 0 src/{exe => }/elf/ProgramHeader.go | 0 src/{exe => }/elf/SectionHeader.go | 0 src/{exe => }/elf/elf.md | 0 src/{os => }/linux/Syscall.go | 0 src/{os => }/mac/Syscall.go | 0 src/{exe => }/macho/Constants.go | 0 src/{exe => }/macho/Header.go | 0 src/{exe => }/macho/LoadCommand.go | 0 src/{exe => }/macho/MachO.go | 0 src/{exe => }/macho/MachO_test.go | 2 +- src/{exe => }/macho/Segment64.go | 0 src/{exe => }/macho/Thread.go | 0 src/{exe => }/macho/macho.md | 0 src/{exe => }/pe/Constants.go | 0 src/{exe => }/pe/DLLImport.go | 0 src/{exe => }/pe/DOSHeader.go | 0 src/{exe => }/pe/DataDirectory.go | 0 src/{exe => }/pe/EXE.go | 0 src/{exe => }/pe/EXE_test.go | 2 +- src/{exe => }/pe/NTHeader.go | 0 src/{exe => }/pe/OptionalHeader64.go | 0 src/{exe => }/pe/SectionHeader.go | 0 src/{exe => }/pe/pe.md | 0 src/{arch => }/riscv/Registers.go | 0 src/{arch => }/riscv/Registers_test.go | 2 +- src/scanner/scanFile.go | 2 +- src/{os => }/windows/Registers.go | 4 ++-- src/{arch => }/x64/Add.go | 0 src/{arch => }/x64/Add_test.go | 2 +- src/{arch => }/x64/AlignStack.go | 0 src/{arch => }/x64/And.go | 0 src/{arch => }/x64/And_test.go | 2 +- src/{arch => }/x64/Call.go | 0 src/{arch => }/x64/Compare.go | 0 src/{arch => }/x64/Compare_test.go | 2 +- src/{arch => }/x64/Div.go | 0 src/{arch => }/x64/Div_test.go | 2 +- src/{arch => }/x64/ExtendRAXToRDX.go | 0 src/{arch => }/x64/Jump.go | 0 src/{arch => }/x64/Jump_test.go | 2 +- src/{arch => }/x64/Load.go | 0 src/{arch => }/x64/Load_test.go | 2 +- src/{arch => }/x64/ModRM.go | 0 src/{arch => }/x64/ModRM_test.go | 2 +- src/{arch => }/x64/Move.go | 0 src/{arch => }/x64/Move_test.go | 2 +- src/{arch => }/x64/Mul.go | 0 src/{arch => }/x64/Mul_test.go | 2 +- src/{arch => }/x64/Negate.go | 0 src/{arch => }/x64/Negate_test.go | 2 +- src/{arch => }/x64/Or.go | 0 src/{arch => }/x64/Or_test.go | 2 +- src/{arch => }/x64/Pop.go | 0 src/{arch => }/x64/Pop_test.go | 2 +- src/{arch => }/x64/Push.go | 0 src/{arch => }/x64/Push_test.go | 2 +- src/{arch => }/x64/REX.go | 0 src/{arch => }/x64/REX_test.go | 2 +- src/{arch => }/x64/Registers.go | 0 src/{arch => }/x64/Registers_test.go | 2 +- src/{arch => }/x64/Return.go | 0 src/{arch => }/x64/SIB.go | 0 src/{arch => }/x64/SIB_test.go | 2 +- src/{arch => }/x64/Shift.go | 0 src/{arch => }/x64/Shift_test.go | 2 +- src/{arch => }/x64/Store.go | 0 src/{arch => }/x64/Store_test.go | 2 +- src/{arch => }/x64/Sub.go | 0 src/{arch => }/x64/Sub_test.go | 2 +- src/{arch => }/x64/Syscall.go | 0 src/{arch => }/x64/Xor.go | 0 src/{arch => }/x64/Xor_test.go | 2 +- src/{arch => }/x64/encode.go | 0 src/{arch => }/x64/encodeNum.go | 0 src/{arch => }/x64/memoryAccess.go | 0 src/{arch => }/x64/x64_test.go | 2 +- 89 files changed, 42 insertions(+), 42 deletions(-) rename src/{arch => }/arm64/Registers.go (100%) rename src/{arch => }/arm64/Registers_test.go (80%) rename src/{exe => }/elf/Constants.go (100%) rename src/{exe => }/elf/ELF.go (100%) rename src/{exe => }/elf/ELF_test.go (76%) rename src/{exe => }/elf/Header.go (100%) rename src/{exe => }/elf/ProgramHeader.go (100%) rename src/{exe => }/elf/SectionHeader.go (100%) rename src/{exe => }/elf/elf.md (100%) rename src/{os => }/linux/Syscall.go (100%) rename src/{os => }/mac/Syscall.go (100%) rename src/{exe => }/macho/Constants.go (100%) rename src/{exe => }/macho/Header.go (100%) rename src/{exe => }/macho/LoadCommand.go (100%) rename src/{exe => }/macho/MachO.go (100%) rename src/{exe => }/macho/MachO_test.go (75%) rename src/{exe => }/macho/Segment64.go (100%) rename src/{exe => }/macho/Thread.go (100%) rename src/{exe => }/macho/macho.md (100%) rename src/{exe => }/pe/Constants.go (100%) rename src/{exe => }/pe/DLLImport.go (100%) rename src/{exe => }/pe/DOSHeader.go (100%) rename src/{exe => }/pe/DataDirectory.go (100%) rename src/{exe => }/pe/EXE.go (100%) rename src/{exe => }/pe/EXE_test.go (76%) rename src/{exe => }/pe/NTHeader.go (100%) rename src/{exe => }/pe/OptionalHeader64.go (100%) rename src/{exe => }/pe/SectionHeader.go (100%) rename src/{exe => }/pe/pe.md (100%) rename src/{arch => }/riscv/Registers.go (100%) rename src/{arch => }/riscv/Registers_test.go (80%) rename src/{os => }/windows/Registers.go (84%) rename src/{arch => }/x64/Add.go (100%) rename src/{arch => }/x64/Add_test.go (98%) rename src/{arch => }/x64/AlignStack.go (100%) rename src/{arch => }/x64/And.go (100%) rename src/{arch => }/x64/And_test.go (98%) rename src/{arch => }/x64/Call.go (100%) rename src/{arch => }/x64/Compare.go (100%) rename src/{arch => }/x64/Compare_test.go (98%) rename src/{arch => }/x64/Div.go (100%) rename src/{arch => }/x64/Div_test.go (96%) rename src/{arch => }/x64/ExtendRAXToRDX.go (100%) rename src/{arch => }/x64/Jump.go (100%) rename src/{arch => }/x64/Jump_test.go (96%) rename src/{arch => }/x64/Load.go (100%) rename src/{arch => }/x64/Load_test.go (99%) rename src/{arch => }/x64/ModRM.go (100%) rename src/{arch => }/x64/ModRM_test.go (96%) rename src/{arch => }/x64/Move.go (100%) rename src/{arch => }/x64/Move_test.go (99%) rename src/{arch => }/x64/Mul.go (100%) rename src/{arch => }/x64/Mul_test.go (98%) rename src/{arch => }/x64/Negate.go (100%) rename src/{arch => }/x64/Negate_test.go (96%) rename src/{arch => }/x64/Or.go (100%) rename src/{arch => }/x64/Or_test.go (98%) rename src/{arch => }/x64/Pop.go (100%) rename src/{arch => }/x64/Pop_test.go (95%) rename src/{arch => }/x64/Push.go (100%) rename src/{arch => }/x64/Push_test.go (95%) rename src/{arch => }/x64/REX.go (100%) rename src/{arch => }/x64/REX_test.go (95%) rename src/{arch => }/x64/Registers.go (100%) rename src/{arch => }/x64/Registers_test.go (81%) rename src/{arch => }/x64/Return.go (100%) rename src/{arch => }/x64/SIB.go (100%) rename src/{arch => }/x64/SIB_test.go (96%) rename src/{arch => }/x64/Shift.go (100%) rename src/{arch => }/x64/Shift_test.go (98%) rename src/{arch => }/x64/Store.go (100%) rename src/{arch => }/x64/Store_test.go (99%) rename src/{arch => }/x64/Sub.go (100%) rename src/{arch => }/x64/Sub_test.go (98%) rename src/{arch => }/x64/Syscall.go (100%) rename src/{arch => }/x64/Xor.go (100%) rename src/{arch => }/x64/Xor_test.go (98%) rename src/{arch => }/x64/encode.go (100%) rename src/{arch => }/x64/encodeNum.go (100%) rename src/{arch => }/x64/memoryAccess.go (100%) rename src/{arch => }/x64/x64_test.go (93%) diff --git a/src/arch/arm64/Registers.go b/src/arm64/Registers.go similarity index 100% rename from src/arch/arm64/Registers.go rename to src/arm64/Registers.go diff --git a/src/arch/arm64/Registers_test.go b/src/arm64/Registers_test.go similarity index 80% rename from src/arch/arm64/Registers_test.go rename to src/arm64/Registers_test.go index 75f5b7c..14ce983 100644 --- a/src/arch/arm64/Registers_test.go +++ b/src/arm64/Registers_test.go @@ -3,7 +3,7 @@ package arm64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/arm64" + "git.akyoto.dev/cli/q/src/arm64" "git.akyoto.dev/go/assert" ) diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 492f722..3ae543a 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -6,11 +6,11 @@ import ( "slices" "strings" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/exe" "git.akyoto.dev/cli/q/src/sizeof" + "git.akyoto.dev/cli/q/src/x64" ) // Finalize generates the final machine code. diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 121d55a..7343241 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -6,17 +6,17 @@ import ( "io" "os" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/exe/elf" - "git.akyoto.dev/cli/q/src/exe/macho" - "git.akyoto.dev/cli/q/src/exe/pe" - "git.akyoto.dev/cli/q/src/os/linux" - "git.akyoto.dev/cli/q/src/os/mac" - "git.akyoto.dev/cli/q/src/os/windows" + "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/linux" + "git.akyoto.dev/cli/q/src/mac" + "git.akyoto.dev/cli/q/src/macho" + "git.akyoto.dev/cli/q/src/pe" + "git.akyoto.dev/cli/q/src/windows" + "git.akyoto.dev/cli/q/src/x64" ) // Result contains all the compiled functions in a build. diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index 7531001..22f68fd 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/x64" ) // CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right. diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 2bf126a..fe63447 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -6,8 +6,8 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/os/windows" "git.akyoto.dev/cli/q/src/types" + "git.akyoto.dev/cli/q/src/windows" ) // CompileCall executes a function call. diff --git a/src/core/ExecuteRegisterNumber.go b/src/core/ExecuteRegisterNumber.go index e3cfda4..0714751 100644 --- a/src/core/ExecuteRegisterNumber.go +++ b/src/core/ExecuteRegisterNumber.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/x64" ) // ExecuteRegisterNumber performs an operation on a register and a number. diff --git a/src/core/ExecuteRegisterRegister.go b/src/core/ExecuteRegisterRegister.go index 738ed9c..3add7ef 100644 --- a/src/core/ExecuteRegisterRegister.go +++ b/src/core/ExecuteRegisterRegister.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/x64" ) // ExecuteRegisterRegister performs an operation on two registers. diff --git a/src/core/NewFunction.go b/src/core/NewFunction.go index 0f7bccc..5e5a4ae 100644 --- a/src/core/NewFunction.go +++ b/src/core/NewFunction.go @@ -1,13 +1,13 @@ package core import ( - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/register" "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/x64" ) // NewFunction creates a new function. diff --git a/src/exe/elf/Constants.go b/src/elf/Constants.go similarity index 100% rename from src/exe/elf/Constants.go rename to src/elf/Constants.go diff --git a/src/exe/elf/ELF.go b/src/elf/ELF.go similarity index 100% rename from src/exe/elf/ELF.go rename to src/elf/ELF.go diff --git a/src/exe/elf/ELF_test.go b/src/elf/ELF_test.go similarity index 76% rename from src/exe/elf/ELF_test.go rename to src/elf/ELF_test.go index aa28d0d..c813536 100644 --- a/src/exe/elf/ELF_test.go +++ b/src/elf/ELF_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/exe/elf" + "git.akyoto.dev/cli/q/src/elf" ) func TestWrite(t *testing.T) { diff --git a/src/exe/elf/Header.go b/src/elf/Header.go similarity index 100% rename from src/exe/elf/Header.go rename to src/elf/Header.go diff --git a/src/exe/elf/ProgramHeader.go b/src/elf/ProgramHeader.go similarity index 100% rename from src/exe/elf/ProgramHeader.go rename to src/elf/ProgramHeader.go diff --git a/src/exe/elf/SectionHeader.go b/src/elf/SectionHeader.go similarity index 100% rename from src/exe/elf/SectionHeader.go rename to src/elf/SectionHeader.go diff --git a/src/exe/elf/elf.md b/src/elf/elf.md similarity index 100% rename from src/exe/elf/elf.md rename to src/elf/elf.md diff --git a/src/os/linux/Syscall.go b/src/linux/Syscall.go similarity index 100% rename from src/os/linux/Syscall.go rename to src/linux/Syscall.go diff --git a/src/os/mac/Syscall.go b/src/mac/Syscall.go similarity index 100% rename from src/os/mac/Syscall.go rename to src/mac/Syscall.go diff --git a/src/exe/macho/Constants.go b/src/macho/Constants.go similarity index 100% rename from src/exe/macho/Constants.go rename to src/macho/Constants.go diff --git a/src/exe/macho/Header.go b/src/macho/Header.go similarity index 100% rename from src/exe/macho/Header.go rename to src/macho/Header.go diff --git a/src/exe/macho/LoadCommand.go b/src/macho/LoadCommand.go similarity index 100% rename from src/exe/macho/LoadCommand.go rename to src/macho/LoadCommand.go diff --git a/src/exe/macho/MachO.go b/src/macho/MachO.go similarity index 100% rename from src/exe/macho/MachO.go rename to src/macho/MachO.go diff --git a/src/exe/macho/MachO_test.go b/src/macho/MachO_test.go similarity index 75% rename from src/exe/macho/MachO_test.go rename to src/macho/MachO_test.go index 29b63c5..a026960 100644 --- a/src/exe/macho/MachO_test.go +++ b/src/macho/MachO_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/exe/macho" + "git.akyoto.dev/cli/q/src/macho" ) func TestWrite(t *testing.T) { diff --git a/src/exe/macho/Segment64.go b/src/macho/Segment64.go similarity index 100% rename from src/exe/macho/Segment64.go rename to src/macho/Segment64.go diff --git a/src/exe/macho/Thread.go b/src/macho/Thread.go similarity index 100% rename from src/exe/macho/Thread.go rename to src/macho/Thread.go diff --git a/src/exe/macho/macho.md b/src/macho/macho.md similarity index 100% rename from src/exe/macho/macho.md rename to src/macho/macho.md diff --git a/src/exe/pe/Constants.go b/src/pe/Constants.go similarity index 100% rename from src/exe/pe/Constants.go rename to src/pe/Constants.go diff --git a/src/exe/pe/DLLImport.go b/src/pe/DLLImport.go similarity index 100% rename from src/exe/pe/DLLImport.go rename to src/pe/DLLImport.go diff --git a/src/exe/pe/DOSHeader.go b/src/pe/DOSHeader.go similarity index 100% rename from src/exe/pe/DOSHeader.go rename to src/pe/DOSHeader.go diff --git a/src/exe/pe/DataDirectory.go b/src/pe/DataDirectory.go similarity index 100% rename from src/exe/pe/DataDirectory.go rename to src/pe/DataDirectory.go diff --git a/src/exe/pe/EXE.go b/src/pe/EXE.go similarity index 100% rename from src/exe/pe/EXE.go rename to src/pe/EXE.go diff --git a/src/exe/pe/EXE_test.go b/src/pe/EXE_test.go similarity index 76% rename from src/exe/pe/EXE_test.go rename to src/pe/EXE_test.go index 5f8d07e..64a6356 100644 --- a/src/exe/pe/EXE_test.go +++ b/src/pe/EXE_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/exe/pe" + "git.akyoto.dev/cli/q/src/pe" ) func TestWrite(t *testing.T) { diff --git a/src/exe/pe/NTHeader.go b/src/pe/NTHeader.go similarity index 100% rename from src/exe/pe/NTHeader.go rename to src/pe/NTHeader.go diff --git a/src/exe/pe/OptionalHeader64.go b/src/pe/OptionalHeader64.go similarity index 100% rename from src/exe/pe/OptionalHeader64.go rename to src/pe/OptionalHeader64.go diff --git a/src/exe/pe/SectionHeader.go b/src/pe/SectionHeader.go similarity index 100% rename from src/exe/pe/SectionHeader.go rename to src/pe/SectionHeader.go diff --git a/src/exe/pe/pe.md b/src/pe/pe.md similarity index 100% rename from src/exe/pe/pe.md rename to src/pe/pe.md diff --git a/src/arch/riscv/Registers.go b/src/riscv/Registers.go similarity index 100% rename from src/arch/riscv/Registers.go rename to src/riscv/Registers.go diff --git a/src/arch/riscv/Registers_test.go b/src/riscv/Registers_test.go similarity index 80% rename from src/arch/riscv/Registers_test.go rename to src/riscv/Registers_test.go index bc74568..efef49d 100644 --- a/src/arch/riscv/Registers_test.go +++ b/src/riscv/Registers_test.go @@ -3,7 +3,7 @@ package riscv_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/riscv" + "git.akyoto.dev/cli/q/src/riscv" "git.akyoto.dev/go/assert" ) diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 4f7c6e1..6d57c0a 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -4,7 +4,6 @@ import ( "os" "path/filepath" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/errors" @@ -12,6 +11,7 @@ import ( "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/types" + "git.akyoto.dev/cli/q/src/x64" ) // scanFile scans a single file. diff --git a/src/os/windows/Registers.go b/src/windows/Registers.go similarity index 84% rename from src/os/windows/Registers.go rename to src/windows/Registers.go index ffbbd21..1043298 100644 --- a/src/os/windows/Registers.go +++ b/src/windows/Registers.go @@ -1,9 +1,9 @@ package windows import ( - "git.akyoto.dev/cli/q/src/arch/arm64" - "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/arm64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" ) var ( diff --git a/src/arch/x64/Add.go b/src/x64/Add.go similarity index 100% rename from src/arch/x64/Add.go rename to src/x64/Add.go diff --git a/src/arch/x64/Add_test.go b/src/x64/Add_test.go similarity index 98% rename from src/arch/x64/Add_test.go rename to src/x64/Add_test.go index 52d6645..134e09e 100644 --- a/src/arch/x64/Add_test.go +++ b/src/x64/Add_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/AlignStack.go b/src/x64/AlignStack.go similarity index 100% rename from src/arch/x64/AlignStack.go rename to src/x64/AlignStack.go diff --git a/src/arch/x64/And.go b/src/x64/And.go similarity index 100% rename from src/arch/x64/And.go rename to src/x64/And.go diff --git a/src/arch/x64/And_test.go b/src/x64/And_test.go similarity index 98% rename from src/arch/x64/And_test.go rename to src/x64/And_test.go index e0e60c8..249d7b8 100644 --- a/src/arch/x64/And_test.go +++ b/src/x64/And_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Call.go b/src/x64/Call.go similarity index 100% rename from src/arch/x64/Call.go rename to src/x64/Call.go diff --git a/src/arch/x64/Compare.go b/src/x64/Compare.go similarity index 100% rename from src/arch/x64/Compare.go rename to src/x64/Compare.go diff --git a/src/arch/x64/Compare_test.go b/src/x64/Compare_test.go similarity index 98% rename from src/arch/x64/Compare_test.go rename to src/x64/Compare_test.go index 6372665..0105ed0 100644 --- a/src/arch/x64/Compare_test.go +++ b/src/x64/Compare_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Div.go b/src/x64/Div.go similarity index 100% rename from src/arch/x64/Div.go rename to src/x64/Div.go diff --git a/src/arch/x64/Div_test.go b/src/x64/Div_test.go similarity index 96% rename from src/arch/x64/Div_test.go rename to src/x64/Div_test.go index 3598dbf..de684b4 100644 --- a/src/arch/x64/Div_test.go +++ b/src/x64/Div_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/ExtendRAXToRDX.go b/src/x64/ExtendRAXToRDX.go similarity index 100% rename from src/arch/x64/ExtendRAXToRDX.go rename to src/x64/ExtendRAXToRDX.go diff --git a/src/arch/x64/Jump.go b/src/x64/Jump.go similarity index 100% rename from src/arch/x64/Jump.go rename to src/x64/Jump.go diff --git a/src/arch/x64/Jump_test.go b/src/x64/Jump_test.go similarity index 96% rename from src/arch/x64/Jump_test.go rename to src/x64/Jump_test.go index fe40c50..4dde162 100644 --- a/src/arch/x64/Jump_test.go +++ b/src/x64/Jump_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Load.go b/src/x64/Load.go similarity index 100% rename from src/arch/x64/Load.go rename to src/x64/Load.go diff --git a/src/arch/x64/Load_test.go b/src/x64/Load_test.go similarity index 99% rename from src/arch/x64/Load_test.go rename to src/x64/Load_test.go index 31330c6..9585f15 100644 --- a/src/arch/x64/Load_test.go +++ b/src/x64/Load_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/ModRM.go b/src/x64/ModRM.go similarity index 100% rename from src/arch/x64/ModRM.go rename to src/x64/ModRM.go diff --git a/src/arch/x64/ModRM_test.go b/src/x64/ModRM_test.go similarity index 96% rename from src/arch/x64/ModRM_test.go rename to src/x64/ModRM_test.go index edcaffe..09f72eb 100644 --- a/src/arch/x64/ModRM_test.go +++ b/src/x64/ModRM_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Move.go b/src/x64/Move.go similarity index 100% rename from src/arch/x64/Move.go rename to src/x64/Move.go diff --git a/src/arch/x64/Move_test.go b/src/x64/Move_test.go similarity index 99% rename from src/arch/x64/Move_test.go rename to src/x64/Move_test.go index faf9fcc..071e05b 100644 --- a/src/arch/x64/Move_test.go +++ b/src/x64/Move_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Mul.go b/src/x64/Mul.go similarity index 100% rename from src/arch/x64/Mul.go rename to src/x64/Mul.go diff --git a/src/arch/x64/Mul_test.go b/src/x64/Mul_test.go similarity index 98% rename from src/arch/x64/Mul_test.go rename to src/x64/Mul_test.go index cd8da70..03ffae1 100644 --- a/src/arch/x64/Mul_test.go +++ b/src/x64/Mul_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Negate.go b/src/x64/Negate.go similarity index 100% rename from src/arch/x64/Negate.go rename to src/x64/Negate.go diff --git a/src/arch/x64/Negate_test.go b/src/x64/Negate_test.go similarity index 96% rename from src/arch/x64/Negate_test.go rename to src/x64/Negate_test.go index 98aa13e..625e601 100644 --- a/src/arch/x64/Negate_test.go +++ b/src/x64/Negate_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Or.go b/src/x64/Or.go similarity index 100% rename from src/arch/x64/Or.go rename to src/x64/Or.go diff --git a/src/arch/x64/Or_test.go b/src/x64/Or_test.go similarity index 98% rename from src/arch/x64/Or_test.go rename to src/x64/Or_test.go index 89bdc5d..d85065a 100644 --- a/src/arch/x64/Or_test.go +++ b/src/x64/Or_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Pop.go b/src/x64/Pop.go similarity index 100% rename from src/arch/x64/Pop.go rename to src/x64/Pop.go diff --git a/src/arch/x64/Pop_test.go b/src/x64/Pop_test.go similarity index 95% rename from src/arch/x64/Pop_test.go rename to src/x64/Pop_test.go index 0a4ce98..446eebe 100644 --- a/src/arch/x64/Pop_test.go +++ b/src/x64/Pop_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Push.go b/src/x64/Push.go similarity index 100% rename from src/arch/x64/Push.go rename to src/x64/Push.go diff --git a/src/arch/x64/Push_test.go b/src/x64/Push_test.go similarity index 95% rename from src/arch/x64/Push_test.go rename to src/x64/Push_test.go index 0dc092b..a49095d 100644 --- a/src/arch/x64/Push_test.go +++ b/src/x64/Push_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/REX.go b/src/x64/REX.go similarity index 100% rename from src/arch/x64/REX.go rename to src/x64/REX.go diff --git a/src/arch/x64/REX_test.go b/src/x64/REX_test.go similarity index 95% rename from src/arch/x64/REX_test.go rename to src/x64/REX_test.go index b212b24..c754354 100644 --- a/src/arch/x64/REX_test.go +++ b/src/x64/REX_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Registers.go b/src/x64/Registers.go similarity index 100% rename from src/arch/x64/Registers.go rename to src/x64/Registers.go diff --git a/src/arch/x64/Registers_test.go b/src/x64/Registers_test.go similarity index 81% rename from src/arch/x64/Registers_test.go rename to src/x64/Registers_test.go index bb8ff19..626bb84 100644 --- a/src/arch/x64/Registers_test.go +++ b/src/x64/Registers_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Return.go b/src/x64/Return.go similarity index 100% rename from src/arch/x64/Return.go rename to src/x64/Return.go diff --git a/src/arch/x64/SIB.go b/src/x64/SIB.go similarity index 100% rename from src/arch/x64/SIB.go rename to src/x64/SIB.go diff --git a/src/arch/x64/SIB_test.go b/src/x64/SIB_test.go similarity index 96% rename from src/arch/x64/SIB_test.go rename to src/x64/SIB_test.go index 7dedf6e..911b133 100644 --- a/src/arch/x64/SIB_test.go +++ b/src/x64/SIB_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Shift.go b/src/x64/Shift.go similarity index 100% rename from src/arch/x64/Shift.go rename to src/x64/Shift.go diff --git a/src/arch/x64/Shift_test.go b/src/x64/Shift_test.go similarity index 98% rename from src/arch/x64/Shift_test.go rename to src/x64/Shift_test.go index 926c98d..dc95cf2 100644 --- a/src/arch/x64/Shift_test.go +++ b/src/x64/Shift_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Store.go b/src/x64/Store.go similarity index 100% rename from src/arch/x64/Store.go rename to src/x64/Store.go diff --git a/src/arch/x64/Store_test.go b/src/x64/Store_test.go similarity index 99% rename from src/arch/x64/Store_test.go rename to src/x64/Store_test.go index 1d9d9a9..3f4bd05 100644 --- a/src/arch/x64/Store_test.go +++ b/src/x64/Store_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Sub.go b/src/x64/Sub.go similarity index 100% rename from src/arch/x64/Sub.go rename to src/x64/Sub.go diff --git a/src/arch/x64/Sub_test.go b/src/x64/Sub_test.go similarity index 98% rename from src/arch/x64/Sub_test.go rename to src/x64/Sub_test.go index d6898e9..7f03b53 100644 --- a/src/arch/x64/Sub_test.go +++ b/src/x64/Sub_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/Syscall.go b/src/x64/Syscall.go similarity index 100% rename from src/arch/x64/Syscall.go rename to src/x64/Syscall.go diff --git a/src/arch/x64/Xor.go b/src/x64/Xor.go similarity index 100% rename from src/arch/x64/Xor.go rename to src/x64/Xor.go diff --git a/src/arch/x64/Xor_test.go b/src/x64/Xor_test.go similarity index 98% rename from src/arch/x64/Xor_test.go rename to src/x64/Xor_test.go index c9e0744..1a2b629 100644 --- a/src/arch/x64/Xor_test.go +++ b/src/x64/Xor_test.go @@ -3,8 +3,8 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) diff --git a/src/arch/x64/encode.go b/src/x64/encode.go similarity index 100% rename from src/arch/x64/encode.go rename to src/x64/encode.go diff --git a/src/arch/x64/encodeNum.go b/src/x64/encodeNum.go similarity index 100% rename from src/arch/x64/encodeNum.go rename to src/x64/encodeNum.go diff --git a/src/arch/x64/memoryAccess.go b/src/x64/memoryAccess.go similarity index 100% rename from src/arch/x64/memoryAccess.go rename to src/x64/memoryAccess.go diff --git a/src/arch/x64/x64_test.go b/src/x64/x64_test.go similarity index 93% rename from src/arch/x64/x64_test.go rename to src/x64/x64_test.go index 0ed00d5..23f61ef 100644 --- a/src/arch/x64/x64_test.go +++ b/src/x64/x64_test.go @@ -3,7 +3,7 @@ package x64_test import ( "testing" - "git.akyoto.dev/cli/q/src/arch/x64" + "git.akyoto.dev/cli/q/src/x64" "git.akyoto.dev/go/assert" ) From 2c964791f7dd512766c531c425b625568e0281f0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 26 Aug 2024 12:46:47 +0200 Subject: [PATCH 0559/1012] Updated documentation --- docs/readme.md | 39 ++++++++++ docs/todo.md | 80 +++++++++++++++++++++ README.md => src/readme.md | 143 +++---------------------------------- tests/readme.md | 11 +++ 4 files changed, 138 insertions(+), 135 deletions(-) create mode 100644 docs/readme.md create mode 100644 docs/todo.md rename README.md => src/readme.md (51%) create mode 100644 tests/readme.md diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 0000000..746cb76 --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,39 @@ +# q + +A programming language that compiles down to machine code. + +## Features + +- Fast compilation +- High performance +- Small executables + +## Installation + +```shell +git clone https://git.akyoto.dev/cli/q +cd q +go build +``` + +## Usage + +```shell +./q run examples/hello +``` + +Builds an executable from `examples/hello` and runs it. + +## Links + +- [Chat](https://matrix.to/#/#community:akyoto.dev) +- [Donate](https://buy.stripe.com/4gw7vf5Jxflf83m7st) +- [Todo](/docs/todo.md) + +## License + +Please see the [license documentation](https://akyoto.dev/license). + +## Copyright + +© 2023 Eduard Urbach diff --git a/docs/todo.md b/docs/todo.md new file mode 100644 index 0000000..b8655f1 --- /dev/null +++ b/docs/todo.md @@ -0,0 +1,80 @@ +## Todo + +### Compiler + +- [x] Tokenizer +- [x] Scanner +- [x] Functions +- [x] Variables +- [x] Error messages +- [x] Expression parser +- [x] Function calls +- [x] Parallel compilation +- [x] Syscalls +- [x] Variable lifetimes +- [x] Branches +- [x] Loops +- [x] Hexadecimal, octal and binary literals +- [x] Escape sequences +- [x] Multiple return values +- [ ] Type system +- [ ] Type operator `?` +- [ ] Data structures +- [ ] Slices +- [ ] Floating-point arithmetic +- [ ] Error handling +- [ ] Threading library +- [ ] Self-hosted compiler + +### Keywords + +- [x] `assert` +- [x] `else` +- [ ] `for` +- [x] `if` +- [x] `import` +- [x] `loop` +- [x] `return` +- [x] `switch` + +### Optimizations + +- [x] Exclude unused functions +- [x] Constant folding +- [ ] Constant propagation +- [ ] Function call inlining +- [ ] Loop unrolls +- [ ] Assembler optimization backend + +### Linter + +- [x] Unused variables +- [x] Unused parameters +- [x] Unused imports +- [ ] Unnecessary newlines +- [ ] Ineffective assignments + +### Operators + +- [x] `=`, `:=` +- [x] `+`, `-`, `*`, `/`, `%` +- [x] `+=`, `-=`, `*=`, `/=`, `%=` +- [x] `&`, `|`, `^` +- [x] `&=`, `|=`, `^=` +- [x] `<<`, `>>` +- [x] `<<=`, `>>=` +- [x] `==`, `!=`, `<`, `<=`, `>`, `>=` +- [x] `&&`, `||` +- [ ] `!`, `-` + +### Architecture + +- [ ] arm64 +- [ ] riscv +- [x] x64 + +### Platform + +- [x] Linux +- [x] Mac +- [ ] Windows \ No newline at end of file diff --git a/README.md b/src/readme.md similarity index 51% rename from README.md rename to src/readme.md index 7fa9087..50bd8e9 100644 --- a/README.md +++ b/src/readme.md @@ -1,135 +1,16 @@ -# q - -A programming language for use in systems programming, game development and web servers. - -## Features - -* Fast compilation -* Small executables -* High performance - -## Installation - -```shell -git clone https://git.akyoto.dev/cli/q -cd q -go build -``` - -## Usage - -Build an executable from `examples/hello` and run it: - -```shell -./q run examples/hello -``` - -## Tests - -```shell -go test ./... -v -cover -``` - -## Benchmarks - -```shell -go test ./tests -run='^$' -bench=. -benchmem -``` - -## Todo - -### Compiler - -- [x] Tokenizer -- [x] Scanner -- [x] Functions -- [x] Variables -- [x] Error messages -- [x] Expression parser -- [x] Function calls -- [x] Parallel compilation -- [x] Syscalls -- [x] Variable lifetimes -- [x] Branches -- [x] Loops -- [x] Hexadecimal, octal and binary literals -- [x] Escape sequences -- [x] Multiple return values -- [ ] Type system -- [ ] Type operator `?` -- [ ] Data structures -- [ ] Slices -- [ ] Floating-point arithmetic -- [ ] Error handling -- [ ] Threading library -- [ ] Self-hosted compiler - -### Keywords - -- [x] `assert` -- [x] `else` -- [ ] `for` -- [x] `if` -- [x] `import` -- [x] `loop` -- [x] `return` -- [x] `switch` - -### Optimizations - -- [x] Exclude unused functions -- [x] Constant folding -- [ ] Constant propagation -- [ ] Function call inlining -- [ ] Loop unrolls -- [ ] Assembler optimization backend - -### Linter - -- [x] Unused variables -- [x] Unused parameters -- [x] Unused imports -- [ ] Unnecessary newlines -- [ ] Ineffective assignments - -### Operators - -- [x] `=`, `:=` -- [x] `+`, `-`, `*`, `/`, `%` -- [x] `+=`, `-=`, `*=`, `/=`, `%=` -- [x] `&`, `|`, `^` -- [x] `&=`, `|=`, `^=` -- [x] `<<`, `>>` -- [x] `<<=`, `>>=` -- [x] `==`, `!=`, `<`, `<=`, `>`, `>=` -- [x] `&&`, `||` -- [ ] `!`, `-` - -### Architecture - -- [ ] arm64 -- [ ] riscv -- [x] x64 - -### Platform - -- [x] Linux -- [x] Mac -- [ ] Windows - ## Documentation -### [main.go](main.go) +### [main.go](/main.go) Entry point. It simply calls `cli.Main` which we can use for testing. -### [src/cli/Main.go](src/cli/Main.go) +### [src/cli/Main.go](/src/cli/Main.go) The command line interface expects a command like `build` as the first argument. Commands are implemented as functions in the [src/cli](src/cli) directory. Each command has its own set of parameters. -### [src/cli/Build.go](src/cli/Build.go) +### [src/cli/Build.go](/src/cli/Build.go) The build command creates a new `Build` instance with the given directory and calls the `Run` method. @@ -156,7 +37,7 @@ Adding the `-v` or `--verbose` flag shows verbose compiler information: q build examples/hello -v ``` -### [src/build/Build.go](src/build/Build.go) +### [src/build/Build.go](/src/build/Build.go) The `Build` type defines all the information needed to start building an executable file. The name of the executable will be equal to the name of the build directory. @@ -176,24 +57,16 @@ Each function will then be translated to generic assembler instructions. All the functions that are required to run the program will be added to the final assembler. The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. -### [src/core/Function.go](src/core/Function.go) +### [src/core/Function.go](/src/core/Function.go) This is the "heart" of the compiler. Each function runs `f.Compile` which organizes the source code into an abstract syntax tree that is then compiled via `f.CompileAST`. You can think of AST nodes as the individual statements in your source code. -### [src/ast/Parse.go](src/ast/Parse.go) +### [src/ast/Parse.go](/src/ast/Parse.go) This is what generates the AST from tokens. -### [src/expression/Parse.go](src/expression/Parse.go) +### [src/expression/Parse.go](/src/expression/Parse.go) -This is what generates expressions from tokens. - -## License - -Please see the [license documentation](https://akyoto.dev/license). - -## Copyright - -© 2023 Eduard Urbach +This is what generates expressions from tokens. \ No newline at end of file diff --git a/tests/readme.md b/tests/readme.md new file mode 100644 index 0000000..07bcf2e --- /dev/null +++ b/tests/readme.md @@ -0,0 +1,11 @@ +## Tests + +```shell +go test ./... -v -cover +``` + +## Benchmarks + +```shell +go test ./tests -run='^$' -bench=. -benchmem +``` \ No newline at end of file From 63d72c8d223de7772b4acb5e0fcd5077f1f47304 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 26 Aug 2024 12:46:47 +0200 Subject: [PATCH 0560/1012] Updated documentation --- docs/readme.md | 39 ++++++++++ docs/todo.md | 80 +++++++++++++++++++++ README.md => src/readme.md | 143 +++---------------------------------- tests/readme.md | 11 +++ 4 files changed, 138 insertions(+), 135 deletions(-) create mode 100644 docs/readme.md create mode 100644 docs/todo.md rename README.md => src/readme.md (51%) create mode 100644 tests/readme.md diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 0000000..746cb76 --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,39 @@ +# q + +A programming language that compiles down to machine code. + +## Features + +- Fast compilation +- High performance +- Small executables + +## Installation + +```shell +git clone https://git.akyoto.dev/cli/q +cd q +go build +``` + +## Usage + +```shell +./q run examples/hello +``` + +Builds an executable from `examples/hello` and runs it. + +## Links + +- [Chat](https://matrix.to/#/#community:akyoto.dev) +- [Donate](https://buy.stripe.com/4gw7vf5Jxflf83m7st) +- [Todo](/docs/todo.md) + +## License + +Please see the [license documentation](https://akyoto.dev/license). + +## Copyright + +© 2023 Eduard Urbach diff --git a/docs/todo.md b/docs/todo.md new file mode 100644 index 0000000..b8655f1 --- /dev/null +++ b/docs/todo.md @@ -0,0 +1,80 @@ +## Todo + +### Compiler + +- [x] Tokenizer +- [x] Scanner +- [x] Functions +- [x] Variables +- [x] Error messages +- [x] Expression parser +- [x] Function calls +- [x] Parallel compilation +- [x] Syscalls +- [x] Variable lifetimes +- [x] Branches +- [x] Loops +- [x] Hexadecimal, octal and binary literals +- [x] Escape sequences +- [x] Multiple return values +- [ ] Type system +- [ ] Type operator `?` +- [ ] Data structures +- [ ] Slices +- [ ] Floating-point arithmetic +- [ ] Error handling +- [ ] Threading library +- [ ] Self-hosted compiler + +### Keywords + +- [x] `assert` +- [x] `else` +- [ ] `for` +- [x] `if` +- [x] `import` +- [x] `loop` +- [x] `return` +- [x] `switch` + +### Optimizations + +- [x] Exclude unused functions +- [x] Constant folding +- [ ] Constant propagation +- [ ] Function call inlining +- [ ] Loop unrolls +- [ ] Assembler optimization backend + +### Linter + +- [x] Unused variables +- [x] Unused parameters +- [x] Unused imports +- [ ] Unnecessary newlines +- [ ] Ineffective assignments + +### Operators + +- [x] `=`, `:=` +- [x] `+`, `-`, `*`, `/`, `%` +- [x] `+=`, `-=`, `*=`, `/=`, `%=` +- [x] `&`, `|`, `^` +- [x] `&=`, `|=`, `^=` +- [x] `<<`, `>>` +- [x] `<<=`, `>>=` +- [x] `==`, `!=`, `<`, `<=`, `>`, `>=` +- [x] `&&`, `||` +- [ ] `!`, `-` + +### Architecture + +- [ ] arm64 +- [ ] riscv +- [x] x64 + +### Platform + +- [x] Linux +- [x] Mac +- [ ] Windows \ No newline at end of file diff --git a/README.md b/src/readme.md similarity index 51% rename from README.md rename to src/readme.md index 7fa9087..50bd8e9 100644 --- a/README.md +++ b/src/readme.md @@ -1,135 +1,16 @@ -# q - -A programming language for use in systems programming, game development and web servers. - -## Features - -* Fast compilation -* Small executables -* High performance - -## Installation - -```shell -git clone https://git.akyoto.dev/cli/q -cd q -go build -``` - -## Usage - -Build an executable from `examples/hello` and run it: - -```shell -./q run examples/hello -``` - -## Tests - -```shell -go test ./... -v -cover -``` - -## Benchmarks - -```shell -go test ./tests -run='^$' -bench=. -benchmem -``` - -## Todo - -### Compiler - -- [x] Tokenizer -- [x] Scanner -- [x] Functions -- [x] Variables -- [x] Error messages -- [x] Expression parser -- [x] Function calls -- [x] Parallel compilation -- [x] Syscalls -- [x] Variable lifetimes -- [x] Branches -- [x] Loops -- [x] Hexadecimal, octal and binary literals -- [x] Escape sequences -- [x] Multiple return values -- [ ] Type system -- [ ] Type operator `?` -- [ ] Data structures -- [ ] Slices -- [ ] Floating-point arithmetic -- [ ] Error handling -- [ ] Threading library -- [ ] Self-hosted compiler - -### Keywords - -- [x] `assert` -- [x] `else` -- [ ] `for` -- [x] `if` -- [x] `import` -- [x] `loop` -- [x] `return` -- [x] `switch` - -### Optimizations - -- [x] Exclude unused functions -- [x] Constant folding -- [ ] Constant propagation -- [ ] Function call inlining -- [ ] Loop unrolls -- [ ] Assembler optimization backend - -### Linter - -- [x] Unused variables -- [x] Unused parameters -- [x] Unused imports -- [ ] Unnecessary newlines -- [ ] Ineffective assignments - -### Operators - -- [x] `=`, `:=` -- [x] `+`, `-`, `*`, `/`, `%` -- [x] `+=`, `-=`, `*=`, `/=`, `%=` -- [x] `&`, `|`, `^` -- [x] `&=`, `|=`, `^=` -- [x] `<<`, `>>` -- [x] `<<=`, `>>=` -- [x] `==`, `!=`, `<`, `<=`, `>`, `>=` -- [x] `&&`, `||` -- [ ] `!`, `-` - -### Architecture - -- [ ] arm64 -- [ ] riscv -- [x] x64 - -### Platform - -- [x] Linux -- [x] Mac -- [ ] Windows - ## Documentation -### [main.go](main.go) +### [main.go](/main.go) Entry point. It simply calls `cli.Main` which we can use for testing. -### [src/cli/Main.go](src/cli/Main.go) +### [src/cli/Main.go](/src/cli/Main.go) The command line interface expects a command like `build` as the first argument. Commands are implemented as functions in the [src/cli](src/cli) directory. Each command has its own set of parameters. -### [src/cli/Build.go](src/cli/Build.go) +### [src/cli/Build.go](/src/cli/Build.go) The build command creates a new `Build` instance with the given directory and calls the `Run` method. @@ -156,7 +37,7 @@ Adding the `-v` or `--verbose` flag shows verbose compiler information: q build examples/hello -v ``` -### [src/build/Build.go](src/build/Build.go) +### [src/build/Build.go](/src/build/Build.go) The `Build` type defines all the information needed to start building an executable file. The name of the executable will be equal to the name of the build directory. @@ -176,24 +57,16 @@ Each function will then be translated to generic assembler instructions. All the functions that are required to run the program will be added to the final assembler. The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. -### [src/core/Function.go](src/core/Function.go) +### [src/core/Function.go](/src/core/Function.go) This is the "heart" of the compiler. Each function runs `f.Compile` which organizes the source code into an abstract syntax tree that is then compiled via `f.CompileAST`. You can think of AST nodes as the individual statements in your source code. -### [src/ast/Parse.go](src/ast/Parse.go) +### [src/ast/Parse.go](/src/ast/Parse.go) This is what generates the AST from tokens. -### [src/expression/Parse.go](src/expression/Parse.go) +### [src/expression/Parse.go](/src/expression/Parse.go) -This is what generates expressions from tokens. - -## License - -Please see the [license documentation](https://akyoto.dev/license). - -## Copyright - -© 2023 Eduard Urbach +This is what generates expressions from tokens. \ No newline at end of file diff --git a/tests/readme.md b/tests/readme.md new file mode 100644 index 0000000..07bcf2e --- /dev/null +++ b/tests/readme.md @@ -0,0 +1,11 @@ +## Tests + +```shell +go test ./... -v -cover +``` + +## Benchmarks + +```shell +go test ./tests -run='^$' -bench=. -benchmem +``` \ No newline at end of file From 3d393a10810974c3c9a99991fd00c26d04e2e233 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 26 Aug 2024 13:39:01 +0200 Subject: [PATCH 0561/1012] Updated documentation --- docs/readme.md | 2 +- src/readme.md | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 746cb76..42962d6 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -28,7 +28,7 @@ Builds an executable from `examples/hello` and runs it. - [Chat](https://matrix.to/#/#community:akyoto.dev) - [Donate](https://buy.stripe.com/4gw7vf5Jxflf83m7st) -- [Todo](/docs/todo.md) +- [Todo](todo.md) ## License diff --git a/src/readme.md b/src/readme.md index 50bd8e9..c29d252 100644 --- a/src/readme.md +++ b/src/readme.md @@ -1,16 +1,14 @@ ## Documentation -### [main.go](/main.go) +### [cli/Main.go](cli/Main.go) -Entry point. It simply calls `cli.Main` which we can use for testing. - -### [src/cli/Main.go](/src/cli/Main.go) +Entry point. The command line interface expects a command like `build` as the first argument. -Commands are implemented as functions in the [src/cli](src/cli) directory. +Commands are implemented as functions in the [cli](cli) directory. Each command has its own set of parameters. -### [src/cli/Build.go](/src/cli/Build.go) +### [cli/Build.go](cli/Build.go) The build command creates a new `Build` instance with the given directory and calls the `Run` method. @@ -37,7 +35,7 @@ Adding the `-v` or `--verbose` flag shows verbose compiler information: q build examples/hello -v ``` -### [src/build/Build.go](/src/build/Build.go) +### [build/Build.go](build/Build.go) The `Build` type defines all the information needed to start building an executable file. The name of the executable will be equal to the name of the build directory. @@ -57,16 +55,16 @@ Each function will then be translated to generic assembler instructions. All the functions that are required to run the program will be added to the final assembler. The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. -### [src/core/Function.go](/src/core/Function.go) +### [core/Function.go](core/Function.go) This is the "heart" of the compiler. Each function runs `f.Compile` which organizes the source code into an abstract syntax tree that is then compiled via `f.CompileAST`. You can think of AST nodes as the individual statements in your source code. -### [src/ast/Parse.go](/src/ast/Parse.go) +### [ast/Parse.go](ast/Parse.go) This is what generates the AST from tokens. -### [src/expression/Parse.go](/src/expression/Parse.go) +### [expression/Parse.go](expression/Parse.go) This is what generates expressions from tokens. \ No newline at end of file From fd64bc6358c9a6ff5584937d50efe57413771bfb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 26 Aug 2024 13:39:01 +0200 Subject: [PATCH 0562/1012] Updated documentation --- docs/readme.md | 2 +- src/readme.md | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 746cb76..42962d6 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -28,7 +28,7 @@ Builds an executable from `examples/hello` and runs it. - [Chat](https://matrix.to/#/#community:akyoto.dev) - [Donate](https://buy.stripe.com/4gw7vf5Jxflf83m7st) -- [Todo](/docs/todo.md) +- [Todo](todo.md) ## License diff --git a/src/readme.md b/src/readme.md index 50bd8e9..c29d252 100644 --- a/src/readme.md +++ b/src/readme.md @@ -1,16 +1,14 @@ ## Documentation -### [main.go](/main.go) +### [cli/Main.go](cli/Main.go) -Entry point. It simply calls `cli.Main` which we can use for testing. - -### [src/cli/Main.go](/src/cli/Main.go) +Entry point. The command line interface expects a command like `build` as the first argument. -Commands are implemented as functions in the [src/cli](src/cli) directory. +Commands are implemented as functions in the [cli](cli) directory. Each command has its own set of parameters. -### [src/cli/Build.go](/src/cli/Build.go) +### [cli/Build.go](cli/Build.go) The build command creates a new `Build` instance with the given directory and calls the `Run` method. @@ -37,7 +35,7 @@ Adding the `-v` or `--verbose` flag shows verbose compiler information: q build examples/hello -v ``` -### [src/build/Build.go](/src/build/Build.go) +### [build/Build.go](build/Build.go) The `Build` type defines all the information needed to start building an executable file. The name of the executable will be equal to the name of the build directory. @@ -57,16 +55,16 @@ Each function will then be translated to generic assembler instructions. All the functions that are required to run the program will be added to the final assembler. The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. -### [src/core/Function.go](/src/core/Function.go) +### [core/Function.go](core/Function.go) This is the "heart" of the compiler. Each function runs `f.Compile` which organizes the source code into an abstract syntax tree that is then compiled via `f.CompileAST`. You can think of AST nodes as the individual statements in your source code. -### [src/ast/Parse.go](/src/ast/Parse.go) +### [ast/Parse.go](ast/Parse.go) This is what generates the AST from tokens. -### [src/expression/Parse.go](/src/expression/Parse.go) +### [expression/Parse.go](expression/Parse.go) This is what generates expressions from tokens. \ No newline at end of file From 868ec694073477119f0d0d6379b69eaf388705cc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 26 Aug 2024 15:03:39 +0200 Subject: [PATCH 0563/1012] Added more tests --- src/x64/x64_test.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/x64/x64_test.go b/src/x64/x64_test.go index 23f61ef..8216f1a 100644 --- a/src/x64/x64_test.go +++ b/src/x64/x64_test.go @@ -8,10 +8,12 @@ import ( ) func TestX64(t *testing.T) { - assert.DeepEqual(t, x64.Call(nil, 1), []byte{0xe8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 0, 1), []byte{0xb8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 1, 1), []byte{0xb9, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.Return(nil), []byte{0xc3}) - assert.DeepEqual(t, x64.Syscall(nil), []byte{0x0f, 0x05}) + assert.DeepEqual(t, x64.AlignStack(nil), []byte{0x48, 0x83, 0xE4, 0xF0}) + assert.DeepEqual(t, x64.Call(nil, 1), []byte{0xE8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.CallAtAddress(nil, 1), []byte{0xFF, 0x15, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x64.ExtendRAXToRDX(nil), []byte{0x48, 0x99}) + assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 0, 1), []byte{0xB8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 1, 1), []byte{0xB9, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.Return(nil), []byte{0xC3}) + assert.DeepEqual(t, x64.Syscall(nil), []byte{0x0F, 0x05}) } From e5dd47b5dd6293c06cb3fbb1757aef4ca3cc966e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 26 Aug 2024 15:03:39 +0200 Subject: [PATCH 0564/1012] Added more tests --- src/x64/x64_test.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/x64/x64_test.go b/src/x64/x64_test.go index 23f61ef..8216f1a 100644 --- a/src/x64/x64_test.go +++ b/src/x64/x64_test.go @@ -8,10 +8,12 @@ import ( ) func TestX64(t *testing.T) { - assert.DeepEqual(t, x64.Call(nil, 1), []byte{0xe8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 0, 1), []byte{0xb8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 1, 1), []byte{0xb9, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.Return(nil), []byte{0xc3}) - assert.DeepEqual(t, x64.Syscall(nil), []byte{0x0f, 0x05}) + assert.DeepEqual(t, x64.AlignStack(nil), []byte{0x48, 0x83, 0xE4, 0xF0}) + assert.DeepEqual(t, x64.Call(nil, 1), []byte{0xE8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.CallAtAddress(nil, 1), []byte{0xFF, 0x15, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x64.ExtendRAXToRDX(nil), []byte{0x48, 0x99}) + assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 0, 1), []byte{0xB8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 1, 1), []byte{0xB9, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x64.Return(nil), []byte{0xC3}) + assert.DeepEqual(t, x64.Syscall(nil), []byte{0x0F, 0x05}) } From e04598da3e44b76dd536d4a99312f24e1b4a947c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 26 Aug 2024 15:06:30 +0200 Subject: [PATCH 0565/1012] Simplified readme --- docs/readme.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 42962d6..ef9bec5 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -28,12 +28,9 @@ Builds an executable from `examples/hello` and runs it. - [Chat](https://matrix.to/#/#community:akyoto.dev) - [Donate](https://buy.stripe.com/4gw7vf5Jxflf83m7st) +- [License](https://akyoto.dev/license) - [Todo](todo.md) -## License - -Please see the [license documentation](https://akyoto.dev/license). - ## Copyright © 2023 Eduard Urbach From b22571777596b92f0213b5e56848eec5097e98ec Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 26 Aug 2024 15:06:30 +0200 Subject: [PATCH 0566/1012] Simplified readme --- docs/readme.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 42962d6..ef9bec5 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -28,12 +28,9 @@ Builds an executable from `examples/hello` and runs it. - [Chat](https://matrix.to/#/#community:akyoto.dev) - [Donate](https://buy.stripe.com/4gw7vf5Jxflf83m7st) +- [License](https://akyoto.dev/license) - [Todo](todo.md) -## License - -Please see the [license documentation](https://akyoto.dev/license). - ## Copyright © 2023 Eduard Urbach From dfc39f1f04d3da1e85f3f1e33b13361d5348e52c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 26 Aug 2024 15:34:34 +0200 Subject: [PATCH 0567/1012] Reduced number of packages --- src/arm64/Registers.go | 6 +++- src/asm/Finalize.go | 4 +-- src/{linux/Syscall.go => compiler/Linux.go} | 6 ++-- src/{mac/Syscall.go => compiler/Mac.go} | 8 ++--- src/compiler/Result.go | 15 ++++----- src/core/CompileCall.go | 4 +-- src/elf/ELF.go | 6 ++-- src/{exe => fs}/Align.go | 2 +- src/macho/MachO.go | 6 ++-- src/pe/EXE.go | 10 +++--- src/windows/Registers.go | 36 --------------------- src/x64/Registers.go | 2 ++ 12 files changed, 36 insertions(+), 69 deletions(-) rename src/{linux/Syscall.go => compiler/Linux.go} (71%) rename src/{mac/Syscall.go => compiler/Mac.go} (63%) rename src/{exe => fs}/Align.go (95%) delete mode 100644 src/windows/Registers.go diff --git a/src/arm64/Registers.go b/src/arm64/Registers.go index 088e2b3..7cee1b7 100644 --- a/src/arm64/Registers.go +++ b/src/arm64/Registers.go @@ -37,4 +37,8 @@ const ( SP // Stack pointer ) -var SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} +var ( + SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} + WindowsInputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5, X6, X7} + WindowsOutputRegisters = []cpu.Register{X0, X1} +) diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 3ae543a..b8a2622 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -8,7 +8,7 @@ import ( "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/exe" + "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/sizeof" "git.akyoto.dev/cli/q/src/x64" ) @@ -372,7 +372,7 @@ restart: data, dataLabels = a.Data.Finalize() dataStart := config.BaseAddress + config.CodeOffset + len(code) - dataStart, _ = exe.Align(dataStart, config.Align) + dataStart, _ = fs.Align(dataStart, config.Align) for _, pointer := range dataPointers { address := Address(dataStart) + pointer.Resolve() diff --git a/src/linux/Syscall.go b/src/compiler/Linux.go similarity index 71% rename from src/linux/Syscall.go rename to src/compiler/Linux.go index 82eb953..9a12592 100644 --- a/src/linux/Syscall.go +++ b/src/compiler/Linux.go @@ -1,7 +1,7 @@ -package linux +package compiler // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/entry/syscalls/syscall_64.tbl const ( - Write = 1 - Exit = 60 + LinuxWrite = 1 + LinuxExit = 60 ) diff --git a/src/mac/Syscall.go b/src/compiler/Mac.go similarity index 63% rename from src/mac/Syscall.go rename to src/compiler/Mac.go index 2111c25..b1fac49 100644 --- a/src/mac/Syscall.go +++ b/src/compiler/Mac.go @@ -1,10 +1,10 @@ -package mac +package compiler // Syscall numbers are divided into classes, here we need the BSD inherited syscalls. -const SyscallClassUnix = 0x2000000 +const classUnix = 0x2000000 // https://github.com/apple-oss-distributions/xnu/blob/main/bsd/kern/syscalls.master const ( - Exit = 1 | SyscallClassUnix - Write = 4 | SyscallClassUnix + MacExit = 1 | classUnix + MacWrite = 4 | classUnix ) diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 7343241..5186858 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -11,11 +11,8 @@ import ( "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/elf" - "git.akyoto.dev/cli/q/src/linux" - "git.akyoto.dev/cli/q/src/mac" "git.akyoto.dev/cli/q/src/macho" "git.akyoto.dev/cli/q/src/pe" - "git.akyoto.dev/cli/q/src/windows" "git.akyoto.dev/cli/q/src/x64" ) @@ -42,15 +39,15 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { switch config.TargetOS { case "linux": - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], LinuxExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() case "mac": - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], mac.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], MacExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() case "windows": - final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 0) + final.RegisterNumber(asm.MOVE, x64.WindowsInputRegisters[0], 0) final.DLLCall("kernel32.ExitProcess") } @@ -74,15 +71,15 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { switch config.TargetOS { case "linux": - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], LinuxExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() case "mac": - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], mac.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], MacExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() case "windows": - final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 1) + final.RegisterNumber(asm.MOVE, x64.WindowsInputRegisters[0], 1) final.DLLCall("kernel32.ExitProcess") } diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index fe63447..8b62b0a 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -7,7 +7,7 @@ import ( "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/cli/q/src/windows" + "git.akyoto.dev/cli/q/src/x64" ) // CompileCall executes a function call. @@ -36,7 +36,7 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { if pkg == "kernel32" || pkg == "user32" || pkg == "gdi32" || pkg == "comctl32" { parameters := root.Children[1:] - registers := windows.X64InputRegisters[:len(parameters)] + registers := x64.WindowsInputRegisters[:len(parameters)] for i := len(parameters) - 1; i >= 0; i-- { _, err := f.ExpressionToRegister(parameters[i], registers[i]) diff --git a/src/elf/ELF.go b/src/elf/ELF.go index af9b469..5ac1cb2 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -6,7 +6,7 @@ import ( "io" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/exe" + "git.akyoto.dev/cli/q/src/fs" ) // ELF represents an ELF file. @@ -21,8 +21,8 @@ func Write(writer io.Writer, code []byte, data []byte) { const HeaderEnd = HeaderSize + ProgramHeaderSize*2 var ( - codeStart, codePadding = exe.Align(HeaderEnd, config.Align) - dataStart, dataPadding = exe.Align(codeStart+len(code), config.Align) + codeStart, codePadding = fs.Align(HeaderEnd, config.Align) + dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) ) elf := &ELF{ diff --git a/src/exe/Align.go b/src/fs/Align.go similarity index 95% rename from src/exe/Align.go rename to src/fs/Align.go index 024df62..1ae4f0b 100644 --- a/src/exe/Align.go +++ b/src/fs/Align.go @@ -1,4 +1,4 @@ -package exe +package fs // Align calculates the next aligned address and the padding needed. func Align[T int | uint | int64 | uint64 | int32 | uint32](n T, alignment T) (T, T) { diff --git a/src/macho/MachO.go b/src/macho/MachO.go index ef44ff1..5353a6d 100644 --- a/src/macho/MachO.go +++ b/src/macho/MachO.go @@ -6,7 +6,7 @@ import ( "io" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/exe" + "git.akyoto.dev/cli/q/src/fs" ) // MachO is the executable format used on MacOS. @@ -26,8 +26,8 @@ func Write(writer io.Writer, code []byte, data []byte) { ) var ( - codeStart, codePadding = exe.Align(HeaderEnd, config.Align) - dataStart, dataPadding = exe.Align(codeStart+len(code), config.Align) + codeStart, codePadding = fs.Align(HeaderEnd, config.Align) + dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) ) m := &MachO{ diff --git a/src/pe/EXE.go b/src/pe/EXE.go index 6b2c5df..5ab108f 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -7,7 +7,7 @@ import ( "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/exe" + "git.akyoto.dev/cli/q/src/fs" ) // EXE is the portable executable format used on Windows. @@ -26,9 +26,9 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { NumSections := 3 HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections - codeStart, codePadding := exe.Align(HeaderEnd, config.Align) - dataStart, dataPadding := exe.Align(codeStart+len(code), config.Align) - importsStart, importsPadding := exe.Align(dataStart+len(data), config.Align) + codeStart, codePadding := fs.Align(HeaderEnd, config.Align) + dataStart, dataPadding := fs.Align(codeStart+len(code), config.Align) + importsStart, importsPadding := fs.Align(dataStart+len(data), config.Align) subSystem := IMAGE_SUBSYSTEM_WINDOWS_CUI @@ -91,7 +91,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { importSectionSize := len(imports)*8 + len(dllData) + importDirectorySize imageSize := importsStart + importSectionSize - imageSize, _ = exe.Align(imageSize, config.Align) + imageSize, _ = fs.Align(imageSize, config.Align) pe := &EXE{ DOSHeader: DOSHeader{ diff --git a/src/windows/Registers.go b/src/windows/Registers.go deleted file mode 100644 index 1043298..0000000 --- a/src/windows/Registers.go +++ /dev/null @@ -1,36 +0,0 @@ -package windows - -import ( - "git.akyoto.dev/cli/q/src/arm64" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" -) - -var ( - X64InputRegisters = []cpu.Register{ - x64.RCX, - x64.RDX, - x64.R8, - x64.R9, - } - - X64OutputRegisters = []cpu.Register{ - x64.RAX, - } - - ARM64InputRegisters = []cpu.Register{ - arm64.X0, - arm64.X1, - arm64.X2, - arm64.X3, - arm64.X4, - arm64.X5, - arm64.X6, - arm64.X7, - } - - ARM64OutputRegisters = []cpu.Register{ - arm64.X0, - arm64.X1, - } -) diff --git a/src/x64/Registers.go b/src/x64/Registers.go index 9162141..0dc55b9 100644 --- a/src/x64/Registers.go +++ b/src/x64/Registers.go @@ -28,4 +28,6 @@ var ( GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} InputRegisters = SyscallInputRegisters OutputRegisters = SyscallInputRegisters + WindowsInputRegisters = []cpu.Register{RCX, RDX, R8, R9} + WindowsOutputRegisters = []cpu.Register{RAX} ) From e2a6a31d8e7e5a4ec8a0852c88d3981ea3ff2112 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 26 Aug 2024 15:34:34 +0200 Subject: [PATCH 0568/1012] Reduced number of packages --- src/arm64/Registers.go | 6 +++- src/asm/Finalize.go | 4 +-- src/{linux/Syscall.go => compiler/Linux.go} | 6 ++-- src/{mac/Syscall.go => compiler/Mac.go} | 8 ++--- src/compiler/Result.go | 15 ++++----- src/core/CompileCall.go | 4 +-- src/elf/ELF.go | 6 ++-- src/{exe => fs}/Align.go | 2 +- src/macho/MachO.go | 6 ++-- src/pe/EXE.go | 10 +++--- src/windows/Registers.go | 36 --------------------- src/x64/Registers.go | 2 ++ 12 files changed, 36 insertions(+), 69 deletions(-) rename src/{linux/Syscall.go => compiler/Linux.go} (71%) rename src/{mac/Syscall.go => compiler/Mac.go} (63%) rename src/{exe => fs}/Align.go (95%) delete mode 100644 src/windows/Registers.go diff --git a/src/arm64/Registers.go b/src/arm64/Registers.go index 088e2b3..7cee1b7 100644 --- a/src/arm64/Registers.go +++ b/src/arm64/Registers.go @@ -37,4 +37,8 @@ const ( SP // Stack pointer ) -var SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} +var ( + SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} + WindowsInputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5, X6, X7} + WindowsOutputRegisters = []cpu.Register{X0, X1} +) diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 3ae543a..b8a2622 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -8,7 +8,7 @@ import ( "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/exe" + "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/sizeof" "git.akyoto.dev/cli/q/src/x64" ) @@ -372,7 +372,7 @@ restart: data, dataLabels = a.Data.Finalize() dataStart := config.BaseAddress + config.CodeOffset + len(code) - dataStart, _ = exe.Align(dataStart, config.Align) + dataStart, _ = fs.Align(dataStart, config.Align) for _, pointer := range dataPointers { address := Address(dataStart) + pointer.Resolve() diff --git a/src/linux/Syscall.go b/src/compiler/Linux.go similarity index 71% rename from src/linux/Syscall.go rename to src/compiler/Linux.go index 82eb953..9a12592 100644 --- a/src/linux/Syscall.go +++ b/src/compiler/Linux.go @@ -1,7 +1,7 @@ -package linux +package compiler // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/entry/syscalls/syscall_64.tbl const ( - Write = 1 - Exit = 60 + LinuxWrite = 1 + LinuxExit = 60 ) diff --git a/src/mac/Syscall.go b/src/compiler/Mac.go similarity index 63% rename from src/mac/Syscall.go rename to src/compiler/Mac.go index 2111c25..b1fac49 100644 --- a/src/mac/Syscall.go +++ b/src/compiler/Mac.go @@ -1,10 +1,10 @@ -package mac +package compiler // Syscall numbers are divided into classes, here we need the BSD inherited syscalls. -const SyscallClassUnix = 0x2000000 +const classUnix = 0x2000000 // https://github.com/apple-oss-distributions/xnu/blob/main/bsd/kern/syscalls.master const ( - Exit = 1 | SyscallClassUnix - Write = 4 | SyscallClassUnix + MacExit = 1 | classUnix + MacWrite = 4 | classUnix ) diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 7343241..5186858 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -11,11 +11,8 @@ import ( "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/elf" - "git.akyoto.dev/cli/q/src/linux" - "git.akyoto.dev/cli/q/src/mac" "git.akyoto.dev/cli/q/src/macho" "git.akyoto.dev/cli/q/src/pe" - "git.akyoto.dev/cli/q/src/windows" "git.akyoto.dev/cli/q/src/x64" ) @@ -42,15 +39,15 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { switch config.TargetOS { case "linux": - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], LinuxExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() case "mac": - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], mac.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], MacExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() case "windows": - final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 0) + final.RegisterNumber(asm.MOVE, x64.WindowsInputRegisters[0], 0) final.DLLCall("kernel32.ExitProcess") } @@ -74,15 +71,15 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { switch config.TargetOS { case "linux": - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], LinuxExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() case "mac": - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], mac.Exit) + final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], MacExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() case "windows": - final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 1) + final.RegisterNumber(asm.MOVE, x64.WindowsInputRegisters[0], 1) final.DLLCall("kernel32.ExitProcess") } diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index fe63447..8b62b0a 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -7,7 +7,7 @@ import ( "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/cli/q/src/windows" + "git.akyoto.dev/cli/q/src/x64" ) // CompileCall executes a function call. @@ -36,7 +36,7 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { if pkg == "kernel32" || pkg == "user32" || pkg == "gdi32" || pkg == "comctl32" { parameters := root.Children[1:] - registers := windows.X64InputRegisters[:len(parameters)] + registers := x64.WindowsInputRegisters[:len(parameters)] for i := len(parameters) - 1; i >= 0; i-- { _, err := f.ExpressionToRegister(parameters[i], registers[i]) diff --git a/src/elf/ELF.go b/src/elf/ELF.go index af9b469..5ac1cb2 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -6,7 +6,7 @@ import ( "io" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/exe" + "git.akyoto.dev/cli/q/src/fs" ) // ELF represents an ELF file. @@ -21,8 +21,8 @@ func Write(writer io.Writer, code []byte, data []byte) { const HeaderEnd = HeaderSize + ProgramHeaderSize*2 var ( - codeStart, codePadding = exe.Align(HeaderEnd, config.Align) - dataStart, dataPadding = exe.Align(codeStart+len(code), config.Align) + codeStart, codePadding = fs.Align(HeaderEnd, config.Align) + dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) ) elf := &ELF{ diff --git a/src/exe/Align.go b/src/fs/Align.go similarity index 95% rename from src/exe/Align.go rename to src/fs/Align.go index 024df62..1ae4f0b 100644 --- a/src/exe/Align.go +++ b/src/fs/Align.go @@ -1,4 +1,4 @@ -package exe +package fs // Align calculates the next aligned address and the padding needed. func Align[T int | uint | int64 | uint64 | int32 | uint32](n T, alignment T) (T, T) { diff --git a/src/macho/MachO.go b/src/macho/MachO.go index ef44ff1..5353a6d 100644 --- a/src/macho/MachO.go +++ b/src/macho/MachO.go @@ -6,7 +6,7 @@ import ( "io" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/exe" + "git.akyoto.dev/cli/q/src/fs" ) // MachO is the executable format used on MacOS. @@ -26,8 +26,8 @@ func Write(writer io.Writer, code []byte, data []byte) { ) var ( - codeStart, codePadding = exe.Align(HeaderEnd, config.Align) - dataStart, dataPadding = exe.Align(codeStart+len(code), config.Align) + codeStart, codePadding = fs.Align(HeaderEnd, config.Align) + dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) ) m := &MachO{ diff --git a/src/pe/EXE.go b/src/pe/EXE.go index 6b2c5df..5ab108f 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -7,7 +7,7 @@ import ( "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/exe" + "git.akyoto.dev/cli/q/src/fs" ) // EXE is the portable executable format used on Windows. @@ -26,9 +26,9 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { NumSections := 3 HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections - codeStart, codePadding := exe.Align(HeaderEnd, config.Align) - dataStart, dataPadding := exe.Align(codeStart+len(code), config.Align) - importsStart, importsPadding := exe.Align(dataStart+len(data), config.Align) + codeStart, codePadding := fs.Align(HeaderEnd, config.Align) + dataStart, dataPadding := fs.Align(codeStart+len(code), config.Align) + importsStart, importsPadding := fs.Align(dataStart+len(data), config.Align) subSystem := IMAGE_SUBSYSTEM_WINDOWS_CUI @@ -91,7 +91,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { importSectionSize := len(imports)*8 + len(dllData) + importDirectorySize imageSize := importsStart + importSectionSize - imageSize, _ = exe.Align(imageSize, config.Align) + imageSize, _ = fs.Align(imageSize, config.Align) pe := &EXE{ DOSHeader: DOSHeader{ diff --git a/src/windows/Registers.go b/src/windows/Registers.go deleted file mode 100644 index 1043298..0000000 --- a/src/windows/Registers.go +++ /dev/null @@ -1,36 +0,0 @@ -package windows - -import ( - "git.akyoto.dev/cli/q/src/arm64" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" -) - -var ( - X64InputRegisters = []cpu.Register{ - x64.RCX, - x64.RDX, - x64.R8, - x64.R9, - } - - X64OutputRegisters = []cpu.Register{ - x64.RAX, - } - - ARM64InputRegisters = []cpu.Register{ - arm64.X0, - arm64.X1, - arm64.X2, - arm64.X3, - arm64.X4, - arm64.X5, - arm64.X6, - arm64.X7, - } - - ARM64OutputRegisters = []cpu.Register{ - arm64.X0, - arm64.X1, - } -) diff --git a/src/x64/Registers.go b/src/x64/Registers.go index 9162141..0dc55b9 100644 --- a/src/x64/Registers.go +++ b/src/x64/Registers.go @@ -28,4 +28,6 @@ var ( GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} InputRegisters = SyscallInputRegisters OutputRegisters = SyscallInputRegisters + WindowsInputRegisters = []cpu.Register{RCX, RDX, R8, R9} + WindowsOutputRegisters = []cpu.Register{RAX} ) From bad8e9ac850a6c206403bc45b81e6f437c726112 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 20 Jan 2025 13:51:47 +0100 Subject: [PATCH 0569/1012] Improved section offset calculation --- go.mod | 2 +- go.sum | 4 ++-- src/asm/Finalize.go | 38 ++++++++++++++++++++++++++++++-------- src/config/config.go | 5 +---- src/elf/ELF.go | 12 ++++++------ src/macho/MachO.go | 12 ++++++------ src/pe/EXE.go | 21 ++++++++++----------- 7 files changed, 56 insertions(+), 38 deletions(-) diff --git a/go.mod b/go.mod index 82dd598..29d0c49 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,4 @@ require ( git.akyoto.dev/go/color v0.1.1 ) -require golang.org/x/sys v0.24.0 // indirect +require golang.org/x/sys v0.29.0 // indirect diff --git a/go.sum b/go.sum index 929bcce..5b3d719 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,5 @@ git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= git.akyoto.dev/go/color v0.1.1 h1:mMAoMIwLBPNy7ocRSxdsCFs7onPC3GfDEiJErCneqRE= git.akyoto.dev/go/color v0.1.1/go.mod h1:ywOjoD0O0sk6bIn92uAJf7mErlEFCuQInL84y4Lqi3Q= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index b8a2622..22bebac 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -8,7 +8,10 @@ import ( "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/dll" + "git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/macho" + "git.akyoto.dev/cli/q/src/pe" "git.akyoto.dev/cli/q/src/sizeof" "git.akyoto.dev/cli/q/src/x64" ) @@ -370,21 +373,40 @@ restart: } } + headerEnd := Address(0) + + switch config.TargetOS { + case "linux": + headerEnd = elf.HeaderEnd + case "macos": + headerEnd = macho.HeaderEnd + case "windows": + headerEnd = pe.HeaderEnd + } + + codeStart, _ := fs.Align(headerEnd, config.Align) + dataStart, _ := fs.Align(codeStart+Address(len(code)), config.Align) data, dataLabels = a.Data.Finalize() - dataStart := config.BaseAddress + config.CodeOffset + len(code) - dataStart, _ = fs.Align(dataStart, config.Align) for _, pointer := range dataPointers { - address := Address(dataStart) + pointer.Resolve() + address := config.BaseAddress + Address(dataStart) + pointer.Resolve() slice := code[pointer.Position : pointer.Position+4] binary.LittleEndian.PutUint32(slice, uint32(address)) } - for _, pointer := range dllPointers { - destination := Address(0x3000) + pointer.Resolve() - address := destination - Address(config.CodeOffset+pointer.Position+Address(pointer.Size)) - slice := code[pointer.Position : pointer.Position+4] - binary.LittleEndian.PutUint32(slice, uint32(address)) + if config.TargetOS == "windows" { + if len(data) == 0 { + data = []byte{0} + } + + importsStart, _ := fs.Align(dataStart+Address(len(data)), config.Align) + + for _, pointer := range dllPointers { + destination := Address(importsStart) + pointer.Resolve() + delta := destination - Address(codeStart+pointer.Position+Address(pointer.Size)) + slice := code[pointer.Position : pointer.Position+4] + binary.LittleEndian.PutUint32(slice, uint32(delta)) + } } return code, data diff --git a/src/config/config.go b/src/config/config.go index 32153a1..6689c3e 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -9,11 +9,8 @@ const ( // The base address is the virtual address for our ELF file. BaseAddress = 0x40 * MinAddress - // Align decides the alignment of the sections and it must be a multiple of the page size. + // Align is the alignment of the sections and it must be a multiple of the page size. Align = 0x1000 - - // The code offset is the offset of the executable machine code within the file. - CodeOffset = Align ) var ( diff --git a/src/elf/ELF.go b/src/elf/ELF.go index 5ac1cb2..0248c7e 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -9,6 +9,8 @@ import ( "git.akyoto.dev/cli/q/src/fs" ) +const HeaderEnd = HeaderSize + ProgramHeaderSize*2 + // ELF represents an ELF file. type ELF struct { Header @@ -18,8 +20,6 @@ type ELF struct { // Write writes the ELF64 format to the given writer. func Write(writer io.Writer, code []byte, data []byte) { - const HeaderEnd = HeaderSize + ProgramHeaderSize*2 - var ( codeStart, codePadding = fs.Align(HeaderEnd, config.Align) dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) @@ -36,7 +36,7 @@ func Write(writer io.Writer, code []byte, data []byte) { Type: TypeExecutable, Architecture: ArchitectureAMD64, FileVersion: 1, - EntryPointInMemory: config.BaseAddress + config.CodeOffset, + EntryPointInMemory: int64(config.BaseAddress + codeStart), ProgramHeaderOffset: HeaderSize, SectionHeaderOffset: 0, Flags: 0, @@ -50,9 +50,9 @@ func Write(writer io.Writer, code []byte, data []byte) { CodeHeader: ProgramHeader{ Type: ProgramTypeLOAD, Flags: ProgramFlagsExecutable | ProgramFlagsReadable, - Offset: config.CodeOffset, - VirtualAddress: config.BaseAddress + config.CodeOffset, - PhysicalAddress: config.BaseAddress + config.CodeOffset, + Offset: int64(codeStart), + VirtualAddress: int64(config.BaseAddress + codeStart), + PhysicalAddress: int64(config.BaseAddress + codeStart), SizeInFile: int64(len(code)), SizeInMemory: int64(len(code)), Align: config.Align, diff --git a/src/macho/MachO.go b/src/macho/MachO.go index 5353a6d..21b5b19 100644 --- a/src/macho/MachO.go +++ b/src/macho/MachO.go @@ -9,6 +9,11 @@ import ( "git.akyoto.dev/cli/q/src/fs" ) +const ( + SizeCommands = Segment64Size*3 + ThreadSize + HeaderEnd = HeaderSize + SizeCommands +) + // MachO is the executable format used on MacOS. type MachO struct { Header @@ -20,11 +25,6 @@ type MachO struct { // Write writes the Mach-O format to the given writer. func Write(writer io.Writer, code []byte, data []byte) { - const ( - SizeCommands = Segment64Size*3 + ThreadSize - HeaderEnd = HeaderSize + SizeCommands - ) - var ( codeStart, codePadding = fs.Align(HeaderEnd, config.Align) dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) @@ -102,7 +102,7 @@ func Write(writer io.Writer, code []byte, data []byte) { 0, 0, 0, 0, 0, 0, - config.BaseAddress + config.CodeOffset, 0, + uint32(config.BaseAddress + codeStart), 0, 0, 0, 0, 0, 0, 0, diff --git a/src/pe/EXE.go b/src/pe/EXE.go index 5ab108f..ea856b5 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -10,6 +10,11 @@ import ( "git.akyoto.dev/cli/q/src/fs" ) +const ( + NumSections = 3 + HeaderEnd = DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections +) + // EXE is the portable executable format used on Windows. type EXE struct { DOSHeader @@ -20,12 +25,6 @@ type EXE struct { // Write writes the EXE file to the given writer. func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { - if len(data) == 0 { - data = []byte{0} - } - - NumSections := 3 - HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections codeStart, codePadding := fs.Align(HeaderEnd, config.Align) dataStart, dataPadding := fs.Align(codeStart+len(code), config.Align) importsStart, importsPadding := fs.Align(dataStart+len(data), config.Align) @@ -115,8 +114,8 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { SizeOfCode: uint32(len(code)), SizeOfInitializedData: 0, SizeOfUninitializedData: 0, - AddressOfEntryPoint: config.CodeOffset, - BaseOfCode: config.CodeOffset, + AddressOfEntryPoint: uint32(codeStart), + BaseOfCode: uint32(codeStart), ImageBase: config.BaseAddress, SectionAlignment: config.Align, // power of 2, must be greater than or equal to FileAlignment FileAlignment: config.Align, // power of 2 @@ -128,7 +127,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { MinorSubsystemVersion: 0, Win32VersionValue: 0, SizeOfImage: uint32(imageSize), - SizeOfHeaders: config.CodeOffset, // section bodies begin here + SizeOfHeaders: uint32(codeStart), // section bodies begin here CheckSum: 0, Subsystem: uint16(subSystem), DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, // IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE @@ -161,9 +160,9 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { { Name: [8]byte{'.', 't', 'e', 'x', 't'}, VirtualSize: uint32(len(code)), - VirtualAddress: config.CodeOffset, + VirtualAddress: uint32(codeStart), RawSize: uint32(len(code)), // must be a multiple of FileAlignment - RawAddress: config.CodeOffset, // must be a multiple of FileAlignment + RawAddress: uint32(codeStart), // must be a multiple of FileAlignment Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ, }, { From 4b7c9f387d9d760852957b474cf9054ca328695d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 20 Jan 2025 13:51:47 +0100 Subject: [PATCH 0570/1012] Improved section offset calculation --- go.mod | 2 +- go.sum | 4 ++-- src/asm/Finalize.go | 38 ++++++++++++++++++++++++++++++-------- src/config/config.go | 5 +---- src/elf/ELF.go | 12 ++++++------ src/macho/MachO.go | 12 ++++++------ src/pe/EXE.go | 21 ++++++++++----------- 7 files changed, 56 insertions(+), 38 deletions(-) diff --git a/go.mod b/go.mod index 82dd598..29d0c49 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,4 @@ require ( git.akyoto.dev/go/color v0.1.1 ) -require golang.org/x/sys v0.24.0 // indirect +require golang.org/x/sys v0.29.0 // indirect diff --git a/go.sum b/go.sum index 929bcce..5b3d719 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,5 @@ git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= git.akyoto.dev/go/color v0.1.1 h1:mMAoMIwLBPNy7ocRSxdsCFs7onPC3GfDEiJErCneqRE= git.akyoto.dev/go/color v0.1.1/go.mod h1:ywOjoD0O0sk6bIn92uAJf7mErlEFCuQInL84y4Lqi3Q= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index b8a2622..22bebac 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -8,7 +8,10 @@ import ( "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/dll" + "git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/macho" + "git.akyoto.dev/cli/q/src/pe" "git.akyoto.dev/cli/q/src/sizeof" "git.akyoto.dev/cli/q/src/x64" ) @@ -370,21 +373,40 @@ restart: } } + headerEnd := Address(0) + + switch config.TargetOS { + case "linux": + headerEnd = elf.HeaderEnd + case "macos": + headerEnd = macho.HeaderEnd + case "windows": + headerEnd = pe.HeaderEnd + } + + codeStart, _ := fs.Align(headerEnd, config.Align) + dataStart, _ := fs.Align(codeStart+Address(len(code)), config.Align) data, dataLabels = a.Data.Finalize() - dataStart := config.BaseAddress + config.CodeOffset + len(code) - dataStart, _ = fs.Align(dataStart, config.Align) for _, pointer := range dataPointers { - address := Address(dataStart) + pointer.Resolve() + address := config.BaseAddress + Address(dataStart) + pointer.Resolve() slice := code[pointer.Position : pointer.Position+4] binary.LittleEndian.PutUint32(slice, uint32(address)) } - for _, pointer := range dllPointers { - destination := Address(0x3000) + pointer.Resolve() - address := destination - Address(config.CodeOffset+pointer.Position+Address(pointer.Size)) - slice := code[pointer.Position : pointer.Position+4] - binary.LittleEndian.PutUint32(slice, uint32(address)) + if config.TargetOS == "windows" { + if len(data) == 0 { + data = []byte{0} + } + + importsStart, _ := fs.Align(dataStart+Address(len(data)), config.Align) + + for _, pointer := range dllPointers { + destination := Address(importsStart) + pointer.Resolve() + delta := destination - Address(codeStart+pointer.Position+Address(pointer.Size)) + slice := code[pointer.Position : pointer.Position+4] + binary.LittleEndian.PutUint32(slice, uint32(delta)) + } } return code, data diff --git a/src/config/config.go b/src/config/config.go index 32153a1..6689c3e 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -9,11 +9,8 @@ const ( // The base address is the virtual address for our ELF file. BaseAddress = 0x40 * MinAddress - // Align decides the alignment of the sections and it must be a multiple of the page size. + // Align is the alignment of the sections and it must be a multiple of the page size. Align = 0x1000 - - // The code offset is the offset of the executable machine code within the file. - CodeOffset = Align ) var ( diff --git a/src/elf/ELF.go b/src/elf/ELF.go index 5ac1cb2..0248c7e 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -9,6 +9,8 @@ import ( "git.akyoto.dev/cli/q/src/fs" ) +const HeaderEnd = HeaderSize + ProgramHeaderSize*2 + // ELF represents an ELF file. type ELF struct { Header @@ -18,8 +20,6 @@ type ELF struct { // Write writes the ELF64 format to the given writer. func Write(writer io.Writer, code []byte, data []byte) { - const HeaderEnd = HeaderSize + ProgramHeaderSize*2 - var ( codeStart, codePadding = fs.Align(HeaderEnd, config.Align) dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) @@ -36,7 +36,7 @@ func Write(writer io.Writer, code []byte, data []byte) { Type: TypeExecutable, Architecture: ArchitectureAMD64, FileVersion: 1, - EntryPointInMemory: config.BaseAddress + config.CodeOffset, + EntryPointInMemory: int64(config.BaseAddress + codeStart), ProgramHeaderOffset: HeaderSize, SectionHeaderOffset: 0, Flags: 0, @@ -50,9 +50,9 @@ func Write(writer io.Writer, code []byte, data []byte) { CodeHeader: ProgramHeader{ Type: ProgramTypeLOAD, Flags: ProgramFlagsExecutable | ProgramFlagsReadable, - Offset: config.CodeOffset, - VirtualAddress: config.BaseAddress + config.CodeOffset, - PhysicalAddress: config.BaseAddress + config.CodeOffset, + Offset: int64(codeStart), + VirtualAddress: int64(config.BaseAddress + codeStart), + PhysicalAddress: int64(config.BaseAddress + codeStart), SizeInFile: int64(len(code)), SizeInMemory: int64(len(code)), Align: config.Align, diff --git a/src/macho/MachO.go b/src/macho/MachO.go index 5353a6d..21b5b19 100644 --- a/src/macho/MachO.go +++ b/src/macho/MachO.go @@ -9,6 +9,11 @@ import ( "git.akyoto.dev/cli/q/src/fs" ) +const ( + SizeCommands = Segment64Size*3 + ThreadSize + HeaderEnd = HeaderSize + SizeCommands +) + // MachO is the executable format used on MacOS. type MachO struct { Header @@ -20,11 +25,6 @@ type MachO struct { // Write writes the Mach-O format to the given writer. func Write(writer io.Writer, code []byte, data []byte) { - const ( - SizeCommands = Segment64Size*3 + ThreadSize - HeaderEnd = HeaderSize + SizeCommands - ) - var ( codeStart, codePadding = fs.Align(HeaderEnd, config.Align) dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) @@ -102,7 +102,7 @@ func Write(writer io.Writer, code []byte, data []byte) { 0, 0, 0, 0, 0, 0, - config.BaseAddress + config.CodeOffset, 0, + uint32(config.BaseAddress + codeStart), 0, 0, 0, 0, 0, 0, 0, diff --git a/src/pe/EXE.go b/src/pe/EXE.go index 5ab108f..ea856b5 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -10,6 +10,11 @@ import ( "git.akyoto.dev/cli/q/src/fs" ) +const ( + NumSections = 3 + HeaderEnd = DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections +) + // EXE is the portable executable format used on Windows. type EXE struct { DOSHeader @@ -20,12 +25,6 @@ type EXE struct { // Write writes the EXE file to the given writer. func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { - if len(data) == 0 { - data = []byte{0} - } - - NumSections := 3 - HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections codeStart, codePadding := fs.Align(HeaderEnd, config.Align) dataStart, dataPadding := fs.Align(codeStart+len(code), config.Align) importsStart, importsPadding := fs.Align(dataStart+len(data), config.Align) @@ -115,8 +114,8 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { SizeOfCode: uint32(len(code)), SizeOfInitializedData: 0, SizeOfUninitializedData: 0, - AddressOfEntryPoint: config.CodeOffset, - BaseOfCode: config.CodeOffset, + AddressOfEntryPoint: uint32(codeStart), + BaseOfCode: uint32(codeStart), ImageBase: config.BaseAddress, SectionAlignment: config.Align, // power of 2, must be greater than or equal to FileAlignment FileAlignment: config.Align, // power of 2 @@ -128,7 +127,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { MinorSubsystemVersion: 0, Win32VersionValue: 0, SizeOfImage: uint32(imageSize), - SizeOfHeaders: config.CodeOffset, // section bodies begin here + SizeOfHeaders: uint32(codeStart), // section bodies begin here CheckSum: 0, Subsystem: uint16(subSystem), DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, // IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE @@ -161,9 +160,9 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { { Name: [8]byte{'.', 't', 'e', 'x', 't'}, VirtualSize: uint32(len(code)), - VirtualAddress: config.CodeOffset, + VirtualAddress: uint32(codeStart), RawSize: uint32(len(code)), // must be a multiple of FileAlignment - RawAddress: config.CodeOffset, // must be a multiple of FileAlignment + RawAddress: uint32(codeStart), // must be a multiple of FileAlignment Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ, }, { From 791573bbfcf62a373cd604fc21aefe9b93d1fefb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 20 Jan 2025 21:37:23 +0100 Subject: [PATCH 0571/1012] Added more syscalls --- examples/shell/shell.q | 31 +++++++++++++++++++++++++++++++ lib/sys/sys_linux.q | 12 ++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 examples/shell/shell.q diff --git a/examples/shell/shell.q b/examples/shell/shell.q new file mode 100644 index 0000000..e434a35 --- /dev/null +++ b/examples/shell/shell.q @@ -0,0 +1,31 @@ +import mem +import sys + +main() { + length := 256 + command := mem.alloc(length) + argv := mem.alloc(1) + envp := mem.alloc(1) + info := mem.alloc(24) + + loop { + sys.write(1, "$ ", 2) + n := sys.read(0, command, length) + + if n <= 0 { + return + } + + // TODO: Indexing by register + command[7] = '\0' + + pid := sys.fork() + + if pid == 0 { + sys.execve(command, argv, envp) + return + } + + sys.waitid(0, pid, info, 4) + } +} \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q index c28f77d..cc99c0c 100644 --- a/lib/sys/sys_linux.q +++ b/lib/sys/sys_linux.q @@ -26,6 +26,14 @@ clone(flags Int, stack Pointer) -> Int { return syscall(56, flags, stack) } +fork() -> Int { + return syscall(57) +} + +execve(path Pointer, argv Pointer, envp Pointer) -> Int { + return syscall(59, path, argv, envp) +} + exit(status Int) { syscall(60, status) } @@ -69,3 +77,7 @@ rmdir(path Pointer) -> Int { unlink(file Pointer) -> Int { return syscall(87, file) } + +waitid(type Int, id Int, info Pointer, options Int) -> Int { + return syscall(247, type, id, info, options) +} From c0b30e9c224824b81344a32346326b56d42e20d8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 20 Jan 2025 21:37:23 +0100 Subject: [PATCH 0572/1012] Added more syscalls --- examples/shell/shell.q | 31 +++++++++++++++++++++++++++++++ lib/sys/sys_linux.q | 12 ++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 examples/shell/shell.q diff --git a/examples/shell/shell.q b/examples/shell/shell.q new file mode 100644 index 0000000..e434a35 --- /dev/null +++ b/examples/shell/shell.q @@ -0,0 +1,31 @@ +import mem +import sys + +main() { + length := 256 + command := mem.alloc(length) + argv := mem.alloc(1) + envp := mem.alloc(1) + info := mem.alloc(24) + + loop { + sys.write(1, "$ ", 2) + n := sys.read(0, command, length) + + if n <= 0 { + return + } + + // TODO: Indexing by register + command[7] = '\0' + + pid := sys.fork() + + if pid == 0 { + sys.execve(command, argv, envp) + return + } + + sys.waitid(0, pid, info, 4) + } +} \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q index c28f77d..cc99c0c 100644 --- a/lib/sys/sys_linux.q +++ b/lib/sys/sys_linux.q @@ -26,6 +26,14 @@ clone(flags Int, stack Pointer) -> Int { return syscall(56, flags, stack) } +fork() -> Int { + return syscall(57) +} + +execve(path Pointer, argv Pointer, envp Pointer) -> Int { + return syscall(59, path, argv, envp) +} + exit(status Int) { syscall(60, status) } @@ -69,3 +77,7 @@ rmdir(path Pointer) -> Int { unlink(file Pointer) -> Int { return syscall(87, file) } + +waitid(type Int, id Int, info Pointer, options Int) -> Int { + return syscall(247, type, id, info, options) +} From 169810e3a27a60bb118debb88f1016dc98d67c69 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 24 Jan 2025 22:47:28 +0100 Subject: [PATCH 0573/1012] Implemented dynamic array indices --- examples/shell/shell.q | 4 +- src/asm/Finalize.go | 13 ++- src/asm/Memory.go | 7 +- src/asm/MemoryNumber.go | 2 +- src/asm/MemoryRegister.go | 2 +- src/core/CompileAssignArray.go | 40 +++++-- src/x64/StoreDynamicOffset.go | 130 ++++++++++++++++++++++ src/x64/StoreDynamicOffset_test.go | 171 +++++++++++++++++++++++++++++ tests/programs/array.q | 44 ++++++++ tests/programs_test.go | 1 + 10 files changed, 393 insertions(+), 21 deletions(-) create mode 100644 src/x64/StoreDynamicOffset.go create mode 100644 src/x64/StoreDynamicOffset_test.go create mode 100644 tests/programs/array.q diff --git a/examples/shell/shell.q b/examples/shell/shell.q index e434a35..0b094f6 100644 --- a/examples/shell/shell.q +++ b/examples/shell/shell.q @@ -16,9 +16,7 @@ main() { return } - // TODO: Indexing by register - command[7] = '\0' - + command[n-1] = '\0' pid := sys.fork() if pid == 0 { diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 22bebac..d60556d 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -3,6 +3,7 @@ package asm import ( "encoding/binary" "fmt" + "math" "slices" "strings" @@ -287,9 +288,17 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { case STORE: switch operands := x.Data.(type) { case *MemoryNumber: - code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) + if operands.Address.OffsetRegister == math.MaxUint8 { + code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) + } else { + code = x64.StoreDynamicOffsetNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) + } case *MemoryRegister: - code = x64.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) + if operands.Address.OffsetRegister == math.MaxUint8 { + code = x64.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) + } else { + code = x64.StoreDynamicOffsetRegister(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) + } } case SYSCALL: diff --git a/src/asm/Memory.go b/src/asm/Memory.go index 3e98a71..1c0cc4e 100644 --- a/src/asm/Memory.go +++ b/src/asm/Memory.go @@ -3,7 +3,8 @@ package asm import "git.akyoto.dev/cli/q/src/cpu" type Memory struct { - Base cpu.Register - Offset byte - Length byte + Base cpu.Register + Offset byte + OffsetRegister cpu.Register + Length byte } diff --git a/src/asm/MemoryNumber.go b/src/asm/MemoryNumber.go index 0923bd1..3e7a54a 100644 --- a/src/asm/MemoryNumber.go +++ b/src/asm/MemoryNumber.go @@ -12,7 +12,7 @@ type MemoryNumber struct { // String returns a human readable version. func (data *MemoryNumber) String() string { - return fmt.Sprintf("%dB [%s+%d], %d", data.Address.Length, data.Address.Base, data.Address.Offset, data.Number) + return fmt.Sprintf("%dB [%s+%s+%d], %d", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Number) } // MemoryNumber adds an instruction with a memory address and a number. diff --git a/src/asm/MemoryRegister.go b/src/asm/MemoryRegister.go index 21232ce..ae232e5 100644 --- a/src/asm/MemoryRegister.go +++ b/src/asm/MemoryRegister.go @@ -14,7 +14,7 @@ type MemoryRegister struct { // String returns a human readable version. func (data *MemoryRegister) String() string { - return fmt.Sprintf("%dB [%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.Offset, data.Register) + return fmt.Sprintf("%dB [%s+%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Register) } // MemoryRegister adds an instruction with a memory address and a number. diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 8fb040b..339f3f6 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -1,9 +1,13 @@ package core import ( + "math" + "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" ) // CompileAssignArray compiles an assign statement for array elements. @@ -20,19 +24,33 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { defer f.UseVariable(variable) - index := left.Children[1] - offset, err := f.Number(index.Token) - - if err != nil { - return err - } - memory := asm.Memory{ - Base: variable.Register, - Offset: byte(offset), - Length: byte(1), + Base: variable.Register, + Offset: 0, + OffsetRegister: cpu.Register(math.MaxUint8), + Length: byte(1), } - _, err = f.ExpressionToMemory(right, memory) + index := left.Children[1] + + if index.Token.Kind == token.Number { + offset, err := f.Number(index.Token) + + if err != nil { + return err + } + + memory.Offset = byte(offset) + } else { + _, indexRegister, err := f.Evaluate(index) + + if err != nil { + return err + } + + memory.OffsetRegister = indexRegister + } + + _, err := f.ExpressionToMemory(right, memory) return err } diff --git a/src/x64/StoreDynamicOffset.go b/src/x64/StoreDynamicOffset.go new file mode 100644 index 0000000..c070aca --- /dev/null +++ b/src/x64/StoreDynamicOffset.go @@ -0,0 +1,130 @@ +package x64 + +import ( + "encoding/binary" + + "git.akyoto.dev/cli/q/src/cpu" +) + +// StoreDynamicOffsetNumber stores a number into the memory address at `destination` with a register offset. +func StoreDynamicOffsetNumber(code []byte, destination cpu.Register, offset cpu.Register, length byte, number int) []byte { + var ( + w = byte(0) + r = byte(0) + x = byte(0) + b = byte(0) + opCode = byte(0xC7) + mod = AddressMemory + ) + + if length == 1 { + opCode = 0xC6 + } + + if offset == RSP { + tmp := offset + offset = destination + destination = tmp + } + + if length == 8 { + w = 1 + } + + if offset > 0b111 { + x = 1 + offset &= 0b111 + } + + if destination > 0b111 { + b = 1 + destination &= 0b111 + } + + if destination == RBP || destination == R13 { + mod = AddressMemoryOffset8 + } + + if length == 2 { + code = append(code, 0x66) + } + + code = append(code, REX(w, r, x, b)) + code = append(code, opCode) + code = append(code, ModRM(mod, 0b000, 0b100)) + code = append(code, SIB(Scale1, byte(offset), byte(destination))) + + if mod == AddressMemoryOffset8 { + code = append(code, 0x00) + } + + switch length { + case 8, 4: + return binary.LittleEndian.AppendUint32(code, uint32(number)) + + case 2: + return binary.LittleEndian.AppendUint16(code, uint16(number)) + } + + return append(code, byte(number)) +} + +// StoreDynamicOffsetRegister stores the contents of the `source` register into the memory address at `destination` with a register offset. +func StoreDynamicOffsetRegister(code []byte, destination cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { + var ( + w = byte(0) + r = byte(0) + x = byte(0) + b = byte(0) + opCode = byte(0x89) + mod = AddressMemory + ) + + if length == 1 { + opCode = 0x88 + } + + if offset == RSP { + tmp := offset + offset = destination + destination = tmp + } + + if length == 8 { + w = 1 + } + + if source > 0b111 { + r = 1 + source &= 0b111 + } + + if offset > 0b111 { + x = 1 + offset &= 0b111 + } + + if destination > 0b111 { + b = 1 + destination &= 0b111 + } + + if destination == RBP || destination == R13 { + mod = AddressMemoryOffset8 + } + + if length == 2 { + code = append(code, 0x66) + } + + code = append(code, REX(w, r, x, b)) + code = append(code, opCode) + code = append(code, ModRM(mod, byte(source), 0b100)) + code = append(code, SIB(Scale1, byte(offset), byte(destination))) + + if mod == AddressMemoryOffset8 { + code = append(code, 0x00) + } + + return code +} diff --git a/src/x64/StoreDynamicOffset_test.go b/src/x64/StoreDynamicOffset_test.go new file mode 100644 index 0000000..dfb72b3 --- /dev/null +++ b/src/x64/StoreDynamicOffset_test.go @@ -0,0 +1,171 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/go/assert" +) + +func TestStoreDynamicOffsetNumber(t *testing.T) { + usagePatterns := []struct { + RegisterTo cpu.Register + Offset cpu.Register + Length byte + Number int + Code []byte + }{ + {x64.RAX, x64.R15, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RAX, x64.R15, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RAX, x64.R15, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, + {x64.RAX, x64.R15, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x38, 0x7F}}, + {x64.RCX, x64.R14, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RCX, x64.R14, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RCX, x64.R14, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, + {x64.RCX, x64.R14, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x31, 0x7F}}, + {x64.RDX, x64.R13, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDX, x64.R13, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDX, x64.R13, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, + {x64.RDX, x64.R13, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x2A, 0x7F}}, + {x64.RBX, x64.R12, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBX, x64.R12, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBX, x64.R12, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00}}, + {x64.RBX, x64.R12, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x23, 0x7F}}, + {x64.RSP, x64.R11, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSP, x64.R11, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSP, x64.R11, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x64.RSP, x64.R11, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, + {x64.RBP, x64.R10, 8, 0x7F, []byte{0x4A, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBP, x64.R10, 4, 0x7F, []byte{0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBP, x64.R10, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, + {x64.RBP, x64.R10, 1, 0x7F, []byte{0x42, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, + {x64.RSI, x64.R9, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSI, x64.R9, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSI, x64.R9, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, + {x64.RSI, x64.R9, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x0E, 0x7F}}, + {x64.RDI, x64.R8, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDI, x64.R8, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDI, x64.R8, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, + {x64.RDI, x64.R8, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x07, 0x7F}}, + {x64.R8, x64.RDI, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R8, x64.RDI, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R8, x64.RDI, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, + {x64.R8, x64.RDI, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x38, 0x7F}}, + {x64.R9, x64.RSI, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R9, x64.RSI, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R9, x64.RSI, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, + {x64.R9, x64.RSI, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x31, 0x7F}}, + {x64.R10, x64.RBP, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R10, x64.RBP, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R10, x64.RBP, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, + {x64.R10, x64.RBP, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x2A, 0x7F}}, + {x64.R11, x64.RSP, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R11, x64.RSP, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R11, x64.RSP, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x64.R11, x64.RSP, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, + {x64.R12, x64.RBX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R12, x64.RBX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R12, x64.RBX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x64.R12, x64.RBX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x1C, 0x7F}}, + {x64.R13, x64.RDX, 8, 0x7F, []byte{0x49, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R13, x64.RDX, 4, 0x7F, []byte{0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R13, x64.RDX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, + {x64.R13, x64.RDX, 1, 0x7F, []byte{0x41, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, + {x64.R14, x64.RCX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R14, x64.RCX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R14, x64.RCX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, + {x64.R14, x64.RCX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x0E, 0x7F}}, + {x64.R15, x64.RAX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R15, x64.RAX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R15, x64.RAX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, + {x64.R15, x64.RAX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x07, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("store %dB [%s+%s], %d", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.Number) + code := x64.StoreDynamicOffsetNumber(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestStoreDynamicOffsetRegister(t *testing.T) { + usagePatterns := []struct { + RegisterTo cpu.Register + Offset cpu.Register + Length byte + RegisterFrom cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, 8, x64.R15, []byte{0x4E, 0x89, 0x3C, 0x38}}, + {x64.RAX, x64.R15, 4, x64.R15, []byte{0x46, 0x89, 0x3C, 0x38}}, + {x64.RAX, x64.R15, 2, x64.R15, []byte{0x66, 0x46, 0x89, 0x3C, 0x38}}, + {x64.RAX, x64.R15, 1, x64.R15, []byte{0x46, 0x88, 0x3C, 0x38}}, + {x64.RCX, x64.R14, 8, x64.R14, []byte{0x4E, 0x89, 0x34, 0x31}}, + {x64.RCX, x64.R14, 4, x64.R14, []byte{0x46, 0x89, 0x34, 0x31}}, + {x64.RCX, x64.R14, 2, x64.R14, []byte{0x66, 0x46, 0x89, 0x34, 0x31}}, + {x64.RCX, x64.R14, 1, x64.R14, []byte{0x46, 0x88, 0x34, 0x31}}, + {x64.RDX, x64.R13, 8, x64.R13, []byte{0x4E, 0x89, 0x2C, 0x2A}}, + {x64.RDX, x64.R13, 4, x64.R13, []byte{0x46, 0x89, 0x2C, 0x2A}}, + {x64.RDX, x64.R13, 2, x64.R13, []byte{0x66, 0x46, 0x89, 0x2C, 0x2A}}, + {x64.RDX, x64.R13, 1, x64.R13, []byte{0x46, 0x88, 0x2C, 0x2A}}, + {x64.RBX, x64.R12, 8, x64.R12, []byte{0x4E, 0x89, 0x24, 0x23}}, + {x64.RBX, x64.R12, 4, x64.R12, []byte{0x46, 0x89, 0x24, 0x23}}, + {x64.RBX, x64.R12, 2, x64.R12, []byte{0x66, 0x46, 0x89, 0x24, 0x23}}, + {x64.RBX, x64.R12, 1, x64.R12, []byte{0x46, 0x88, 0x24, 0x23}}, + {x64.RSP, x64.R11, 8, x64.R11, []byte{0x4E, 0x89, 0x1C, 0x1C}}, + {x64.RSP, x64.R11, 4, x64.R11, []byte{0x46, 0x89, 0x1C, 0x1C}}, + {x64.RSP, x64.R11, 2, x64.R11, []byte{0x66, 0x46, 0x89, 0x1C, 0x1C}}, + {x64.RSP, x64.R11, 1, x64.R11, []byte{0x46, 0x88, 0x1C, 0x1C}}, + {x64.RBP, x64.R10, 8, x64.R10, []byte{0x4E, 0x89, 0x54, 0x15, 0x00}}, + {x64.RBP, x64.R10, 4, x64.R10, []byte{0x46, 0x89, 0x54, 0x15, 0x00}}, + {x64.RBP, x64.R10, 2, x64.R10, []byte{0x66, 0x46, 0x89, 0x54, 0x15, 0x00}}, + {x64.RBP, x64.R10, 1, x64.R10, []byte{0x46, 0x88, 0x54, 0x15, 0x00}}, + {x64.RSI, x64.R9, 8, x64.R9, []byte{0x4E, 0x89, 0x0C, 0x0E}}, + {x64.RSI, x64.R9, 4, x64.R9, []byte{0x46, 0x89, 0x0C, 0x0E}}, + {x64.RSI, x64.R9, 2, x64.R9, []byte{0x66, 0x46, 0x89, 0x0C, 0x0E}}, + {x64.RSI, x64.R9, 1, x64.R9, []byte{0x46, 0x88, 0x0C, 0x0E}}, + {x64.RDI, x64.R8, 8, x64.R8, []byte{0x4E, 0x89, 0x04, 0x07}}, + {x64.RDI, x64.R8, 4, x64.R8, []byte{0x46, 0x89, 0x04, 0x07}}, + {x64.RDI, x64.R8, 2, x64.R8, []byte{0x66, 0x46, 0x89, 0x04, 0x07}}, + {x64.RDI, x64.R8, 1, x64.R8, []byte{0x46, 0x88, 0x04, 0x07}}, + {x64.R8, x64.RDI, 8, x64.RDI, []byte{0x49, 0x89, 0x3C, 0x38}}, + {x64.R8, x64.RDI, 4, x64.RDI, []byte{0x41, 0x89, 0x3C, 0x38}}, + {x64.R8, x64.RDI, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x3C, 0x38}}, + {x64.R8, x64.RDI, 1, x64.RDI, []byte{0x41, 0x88, 0x3C, 0x38}}, + {x64.R9, x64.RSI, 8, x64.RSI, []byte{0x49, 0x89, 0x34, 0x31}}, + {x64.R9, x64.RSI, 4, x64.RSI, []byte{0x41, 0x89, 0x34, 0x31}}, + {x64.R9, x64.RSI, 2, x64.RSI, []byte{0x66, 0x41, 0x89, 0x34, 0x31}}, + {x64.R9, x64.RSI, 1, x64.RSI, []byte{0x41, 0x88, 0x34, 0x31}}, + {x64.R10, x64.RBP, 8, x64.RBP, []byte{0x49, 0x89, 0x2C, 0x2A}}, + {x64.R10, x64.RBP, 4, x64.RBP, []byte{0x41, 0x89, 0x2C, 0x2A}}, + {x64.R10, x64.RBP, 2, x64.RBP, []byte{0x66, 0x41, 0x89, 0x2C, 0x2A}}, + {x64.R10, x64.RBP, 1, x64.RBP, []byte{0x41, 0x88, 0x2C, 0x2A}}, + {x64.R11, x64.RSP, 8, x64.RSP, []byte{0x4A, 0x89, 0x24, 0x1C}}, + {x64.R11, x64.RSP, 4, x64.RSP, []byte{0x42, 0x89, 0x24, 0x1C}}, + {x64.R11, x64.RSP, 2, x64.RSP, []byte{0x66, 0x42, 0x89, 0x24, 0x1C}}, + {x64.R11, x64.RSP, 1, x64.RSP, []byte{0x42, 0x88, 0x24, 0x1C}}, + {x64.R12, x64.RBX, 8, x64.RBX, []byte{0x49, 0x89, 0x1C, 0x1C}}, + {x64.R12, x64.RBX, 4, x64.RBX, []byte{0x41, 0x89, 0x1C, 0x1C}}, + {x64.R12, x64.RBX, 2, x64.RBX, []byte{0x66, 0x41, 0x89, 0x1C, 0x1C}}, + {x64.R12, x64.RBX, 1, x64.RBX, []byte{0x41, 0x88, 0x1C, 0x1C}}, + {x64.R13, x64.RDX, 8, x64.RDX, []byte{0x49, 0x89, 0x54, 0x15, 0x00}}, + {x64.R13, x64.RDX, 4, x64.RDX, []byte{0x41, 0x89, 0x54, 0x15, 0x00}}, + {x64.R13, x64.RDX, 2, x64.RDX, []byte{0x66, 0x41, 0x89, 0x54, 0x15, 0x00}}, + {x64.R13, x64.RDX, 1, x64.RDX, []byte{0x41, 0x88, 0x54, 0x15, 0x00}}, + {x64.R14, x64.RCX, 8, x64.RCX, []byte{0x49, 0x89, 0x0C, 0x0E}}, + {x64.R14, x64.RCX, 4, x64.RCX, []byte{0x41, 0x89, 0x0C, 0x0E}}, + {x64.R14, x64.RCX, 2, x64.RCX, []byte{0x66, 0x41, 0x89, 0x0C, 0x0E}}, + {x64.R14, x64.RCX, 1, x64.RCX, []byte{0x41, 0x88, 0x0C, 0x0E}}, + {x64.R15, x64.RAX, 8, x64.RAX, []byte{0x49, 0x89, 0x04, 0x07}}, + {x64.R15, x64.RAX, 4, x64.RAX, []byte{0x41, 0x89, 0x04, 0x07}}, + {x64.R15, x64.RAX, 2, x64.RAX, []byte{0x66, 0x41, 0x89, 0x04, 0x07}}, + {x64.R15, x64.RAX, 1, x64.RAX, []byte{0x41, 0x88, 0x04, 0x07}}, + } + + for _, pattern := range usagePatterns { + t.Logf("store %dB [%s+%s], %s", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) + code := x64.StoreDynamicOffsetRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/tests/programs/array.q b/tests/programs/array.q new file mode 100644 index 0000000..6b4d16a --- /dev/null +++ b/tests/programs/array.q @@ -0,0 +1,44 @@ +import mem + +main() { + a := mem.alloc(5) + + assert a[0] == 0 + assert a[1] == 0 + assert a[2] == 0 + assert a[3] == 0 + assert a[4] == 0 + + a[0] = 0 + a[1] = 1 + a[2] = 2 + a[3] = 3 + a[4] = 4 + + assert a[0] == 0 + assert a[1] == 1 + assert a[2] == 2 + assert a[3] == 3 + assert a[4] == 4 + + i := 0 + a[i] = i * 2 + + i += 1 + a[i] = i * 2 + + i += 1 + a[i] = i * 2 + + i += 1 + a[i] = i * 2 + + i += 1 + a[i] = i * 2 + + assert a[0] == 0 + assert a[1] == 2 + assert a[2] == 4 + assert a[3] == 6 + assert a[4] == 8 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 986cf81..dfeba4d 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -31,6 +31,7 @@ var programs = []struct { {"binary", "", "", 0}, {"octal", "", "", 0}, {"hexadecimal", "", "", 0}, + {"array", "", "", 0}, {"escape-rune", "", "", 0}, {"escape-string", "", "", 0}, {"bitwise-and", "", "", 0}, From 6dad6fb6a6efea16c4e2de2210504c24c49e4179 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 24 Jan 2025 22:47:28 +0100 Subject: [PATCH 0574/1012] Implemented dynamic array indices --- examples/shell/shell.q | 4 +- src/asm/Finalize.go | 13 ++- src/asm/Memory.go | 7 +- src/asm/MemoryNumber.go | 2 +- src/asm/MemoryRegister.go | 2 +- src/core/CompileAssignArray.go | 40 +++++-- src/x64/StoreDynamicOffset.go | 130 ++++++++++++++++++++++ src/x64/StoreDynamicOffset_test.go | 171 +++++++++++++++++++++++++++++ tests/programs/array.q | 44 ++++++++ tests/programs_test.go | 1 + 10 files changed, 393 insertions(+), 21 deletions(-) create mode 100644 src/x64/StoreDynamicOffset.go create mode 100644 src/x64/StoreDynamicOffset_test.go create mode 100644 tests/programs/array.q diff --git a/examples/shell/shell.q b/examples/shell/shell.q index e434a35..0b094f6 100644 --- a/examples/shell/shell.q +++ b/examples/shell/shell.q @@ -16,9 +16,7 @@ main() { return } - // TODO: Indexing by register - command[7] = '\0' - + command[n-1] = '\0' pid := sys.fork() if pid == 0 { diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 22bebac..d60556d 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -3,6 +3,7 @@ package asm import ( "encoding/binary" "fmt" + "math" "slices" "strings" @@ -287,9 +288,17 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { case STORE: switch operands := x.Data.(type) { case *MemoryNumber: - code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) + if operands.Address.OffsetRegister == math.MaxUint8 { + code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) + } else { + code = x64.StoreDynamicOffsetNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) + } case *MemoryRegister: - code = x64.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) + if operands.Address.OffsetRegister == math.MaxUint8 { + code = x64.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) + } else { + code = x64.StoreDynamicOffsetRegister(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) + } } case SYSCALL: diff --git a/src/asm/Memory.go b/src/asm/Memory.go index 3e98a71..1c0cc4e 100644 --- a/src/asm/Memory.go +++ b/src/asm/Memory.go @@ -3,7 +3,8 @@ package asm import "git.akyoto.dev/cli/q/src/cpu" type Memory struct { - Base cpu.Register - Offset byte - Length byte + Base cpu.Register + Offset byte + OffsetRegister cpu.Register + Length byte } diff --git a/src/asm/MemoryNumber.go b/src/asm/MemoryNumber.go index 0923bd1..3e7a54a 100644 --- a/src/asm/MemoryNumber.go +++ b/src/asm/MemoryNumber.go @@ -12,7 +12,7 @@ type MemoryNumber struct { // String returns a human readable version. func (data *MemoryNumber) String() string { - return fmt.Sprintf("%dB [%s+%d], %d", data.Address.Length, data.Address.Base, data.Address.Offset, data.Number) + return fmt.Sprintf("%dB [%s+%s+%d], %d", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Number) } // MemoryNumber adds an instruction with a memory address and a number. diff --git a/src/asm/MemoryRegister.go b/src/asm/MemoryRegister.go index 21232ce..ae232e5 100644 --- a/src/asm/MemoryRegister.go +++ b/src/asm/MemoryRegister.go @@ -14,7 +14,7 @@ type MemoryRegister struct { // String returns a human readable version. func (data *MemoryRegister) String() string { - return fmt.Sprintf("%dB [%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.Offset, data.Register) + return fmt.Sprintf("%dB [%s+%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Register) } // MemoryRegister adds an instruction with a memory address and a number. diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 8fb040b..339f3f6 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -1,9 +1,13 @@ package core import ( + "math" + "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/token" ) // CompileAssignArray compiles an assign statement for array elements. @@ -20,19 +24,33 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { defer f.UseVariable(variable) - index := left.Children[1] - offset, err := f.Number(index.Token) - - if err != nil { - return err - } - memory := asm.Memory{ - Base: variable.Register, - Offset: byte(offset), - Length: byte(1), + Base: variable.Register, + Offset: 0, + OffsetRegister: cpu.Register(math.MaxUint8), + Length: byte(1), } - _, err = f.ExpressionToMemory(right, memory) + index := left.Children[1] + + if index.Token.Kind == token.Number { + offset, err := f.Number(index.Token) + + if err != nil { + return err + } + + memory.Offset = byte(offset) + } else { + _, indexRegister, err := f.Evaluate(index) + + if err != nil { + return err + } + + memory.OffsetRegister = indexRegister + } + + _, err := f.ExpressionToMemory(right, memory) return err } diff --git a/src/x64/StoreDynamicOffset.go b/src/x64/StoreDynamicOffset.go new file mode 100644 index 0000000..c070aca --- /dev/null +++ b/src/x64/StoreDynamicOffset.go @@ -0,0 +1,130 @@ +package x64 + +import ( + "encoding/binary" + + "git.akyoto.dev/cli/q/src/cpu" +) + +// StoreDynamicOffsetNumber stores a number into the memory address at `destination` with a register offset. +func StoreDynamicOffsetNumber(code []byte, destination cpu.Register, offset cpu.Register, length byte, number int) []byte { + var ( + w = byte(0) + r = byte(0) + x = byte(0) + b = byte(0) + opCode = byte(0xC7) + mod = AddressMemory + ) + + if length == 1 { + opCode = 0xC6 + } + + if offset == RSP { + tmp := offset + offset = destination + destination = tmp + } + + if length == 8 { + w = 1 + } + + if offset > 0b111 { + x = 1 + offset &= 0b111 + } + + if destination > 0b111 { + b = 1 + destination &= 0b111 + } + + if destination == RBP || destination == R13 { + mod = AddressMemoryOffset8 + } + + if length == 2 { + code = append(code, 0x66) + } + + code = append(code, REX(w, r, x, b)) + code = append(code, opCode) + code = append(code, ModRM(mod, 0b000, 0b100)) + code = append(code, SIB(Scale1, byte(offset), byte(destination))) + + if mod == AddressMemoryOffset8 { + code = append(code, 0x00) + } + + switch length { + case 8, 4: + return binary.LittleEndian.AppendUint32(code, uint32(number)) + + case 2: + return binary.LittleEndian.AppendUint16(code, uint16(number)) + } + + return append(code, byte(number)) +} + +// StoreDynamicOffsetRegister stores the contents of the `source` register into the memory address at `destination` with a register offset. +func StoreDynamicOffsetRegister(code []byte, destination cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { + var ( + w = byte(0) + r = byte(0) + x = byte(0) + b = byte(0) + opCode = byte(0x89) + mod = AddressMemory + ) + + if length == 1 { + opCode = 0x88 + } + + if offset == RSP { + tmp := offset + offset = destination + destination = tmp + } + + if length == 8 { + w = 1 + } + + if source > 0b111 { + r = 1 + source &= 0b111 + } + + if offset > 0b111 { + x = 1 + offset &= 0b111 + } + + if destination > 0b111 { + b = 1 + destination &= 0b111 + } + + if destination == RBP || destination == R13 { + mod = AddressMemoryOffset8 + } + + if length == 2 { + code = append(code, 0x66) + } + + code = append(code, REX(w, r, x, b)) + code = append(code, opCode) + code = append(code, ModRM(mod, byte(source), 0b100)) + code = append(code, SIB(Scale1, byte(offset), byte(destination))) + + if mod == AddressMemoryOffset8 { + code = append(code, 0x00) + } + + return code +} diff --git a/src/x64/StoreDynamicOffset_test.go b/src/x64/StoreDynamicOffset_test.go new file mode 100644 index 0000000..dfb72b3 --- /dev/null +++ b/src/x64/StoreDynamicOffset_test.go @@ -0,0 +1,171 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/go/assert" +) + +func TestStoreDynamicOffsetNumber(t *testing.T) { + usagePatterns := []struct { + RegisterTo cpu.Register + Offset cpu.Register + Length byte + Number int + Code []byte + }{ + {x64.RAX, x64.R15, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RAX, x64.R15, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RAX, x64.R15, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, + {x64.RAX, x64.R15, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x38, 0x7F}}, + {x64.RCX, x64.R14, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RCX, x64.R14, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RCX, x64.R14, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, + {x64.RCX, x64.R14, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x31, 0x7F}}, + {x64.RDX, x64.R13, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDX, x64.R13, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDX, x64.R13, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, + {x64.RDX, x64.R13, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x2A, 0x7F}}, + {x64.RBX, x64.R12, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBX, x64.R12, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBX, x64.R12, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00}}, + {x64.RBX, x64.R12, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x23, 0x7F}}, + {x64.RSP, x64.R11, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSP, x64.R11, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSP, x64.R11, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x64.RSP, x64.R11, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, + {x64.RBP, x64.R10, 8, 0x7F, []byte{0x4A, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBP, x64.R10, 4, 0x7F, []byte{0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RBP, x64.R10, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, + {x64.RBP, x64.R10, 1, 0x7F, []byte{0x42, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, + {x64.RSI, x64.R9, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSI, x64.R9, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RSI, x64.R9, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, + {x64.RSI, x64.R9, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x0E, 0x7F}}, + {x64.RDI, x64.R8, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDI, x64.R8, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x64.RDI, x64.R8, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, + {x64.RDI, x64.R8, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x07, 0x7F}}, + {x64.R8, x64.RDI, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R8, x64.RDI, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R8, x64.RDI, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, + {x64.R8, x64.RDI, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x38, 0x7F}}, + {x64.R9, x64.RSI, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R9, x64.RSI, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R9, x64.RSI, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, + {x64.R9, x64.RSI, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x31, 0x7F}}, + {x64.R10, x64.RBP, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R10, x64.RBP, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R10, x64.RBP, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, + {x64.R10, x64.RBP, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x2A, 0x7F}}, + {x64.R11, x64.RSP, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R11, x64.RSP, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R11, x64.RSP, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x64.R11, x64.RSP, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, + {x64.R12, x64.RBX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R12, x64.RBX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R12, x64.RBX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x64.R12, x64.RBX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x1C, 0x7F}}, + {x64.R13, x64.RDX, 8, 0x7F, []byte{0x49, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R13, x64.RDX, 4, 0x7F, []byte{0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R13, x64.RDX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, + {x64.R13, x64.RDX, 1, 0x7F, []byte{0x41, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, + {x64.R14, x64.RCX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R14, x64.RCX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R14, x64.RCX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, + {x64.R14, x64.RCX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x0E, 0x7F}}, + {x64.R15, x64.RAX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R15, x64.RAX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x64.R15, x64.RAX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, + {x64.R15, x64.RAX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x07, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("store %dB [%s+%s], %d", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.Number) + code := x64.StoreDynamicOffsetNumber(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestStoreDynamicOffsetRegister(t *testing.T) { + usagePatterns := []struct { + RegisterTo cpu.Register + Offset cpu.Register + Length byte + RegisterFrom cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, 8, x64.R15, []byte{0x4E, 0x89, 0x3C, 0x38}}, + {x64.RAX, x64.R15, 4, x64.R15, []byte{0x46, 0x89, 0x3C, 0x38}}, + {x64.RAX, x64.R15, 2, x64.R15, []byte{0x66, 0x46, 0x89, 0x3C, 0x38}}, + {x64.RAX, x64.R15, 1, x64.R15, []byte{0x46, 0x88, 0x3C, 0x38}}, + {x64.RCX, x64.R14, 8, x64.R14, []byte{0x4E, 0x89, 0x34, 0x31}}, + {x64.RCX, x64.R14, 4, x64.R14, []byte{0x46, 0x89, 0x34, 0x31}}, + {x64.RCX, x64.R14, 2, x64.R14, []byte{0x66, 0x46, 0x89, 0x34, 0x31}}, + {x64.RCX, x64.R14, 1, x64.R14, []byte{0x46, 0x88, 0x34, 0x31}}, + {x64.RDX, x64.R13, 8, x64.R13, []byte{0x4E, 0x89, 0x2C, 0x2A}}, + {x64.RDX, x64.R13, 4, x64.R13, []byte{0x46, 0x89, 0x2C, 0x2A}}, + {x64.RDX, x64.R13, 2, x64.R13, []byte{0x66, 0x46, 0x89, 0x2C, 0x2A}}, + {x64.RDX, x64.R13, 1, x64.R13, []byte{0x46, 0x88, 0x2C, 0x2A}}, + {x64.RBX, x64.R12, 8, x64.R12, []byte{0x4E, 0x89, 0x24, 0x23}}, + {x64.RBX, x64.R12, 4, x64.R12, []byte{0x46, 0x89, 0x24, 0x23}}, + {x64.RBX, x64.R12, 2, x64.R12, []byte{0x66, 0x46, 0x89, 0x24, 0x23}}, + {x64.RBX, x64.R12, 1, x64.R12, []byte{0x46, 0x88, 0x24, 0x23}}, + {x64.RSP, x64.R11, 8, x64.R11, []byte{0x4E, 0x89, 0x1C, 0x1C}}, + {x64.RSP, x64.R11, 4, x64.R11, []byte{0x46, 0x89, 0x1C, 0x1C}}, + {x64.RSP, x64.R11, 2, x64.R11, []byte{0x66, 0x46, 0x89, 0x1C, 0x1C}}, + {x64.RSP, x64.R11, 1, x64.R11, []byte{0x46, 0x88, 0x1C, 0x1C}}, + {x64.RBP, x64.R10, 8, x64.R10, []byte{0x4E, 0x89, 0x54, 0x15, 0x00}}, + {x64.RBP, x64.R10, 4, x64.R10, []byte{0x46, 0x89, 0x54, 0x15, 0x00}}, + {x64.RBP, x64.R10, 2, x64.R10, []byte{0x66, 0x46, 0x89, 0x54, 0x15, 0x00}}, + {x64.RBP, x64.R10, 1, x64.R10, []byte{0x46, 0x88, 0x54, 0x15, 0x00}}, + {x64.RSI, x64.R9, 8, x64.R9, []byte{0x4E, 0x89, 0x0C, 0x0E}}, + {x64.RSI, x64.R9, 4, x64.R9, []byte{0x46, 0x89, 0x0C, 0x0E}}, + {x64.RSI, x64.R9, 2, x64.R9, []byte{0x66, 0x46, 0x89, 0x0C, 0x0E}}, + {x64.RSI, x64.R9, 1, x64.R9, []byte{0x46, 0x88, 0x0C, 0x0E}}, + {x64.RDI, x64.R8, 8, x64.R8, []byte{0x4E, 0x89, 0x04, 0x07}}, + {x64.RDI, x64.R8, 4, x64.R8, []byte{0x46, 0x89, 0x04, 0x07}}, + {x64.RDI, x64.R8, 2, x64.R8, []byte{0x66, 0x46, 0x89, 0x04, 0x07}}, + {x64.RDI, x64.R8, 1, x64.R8, []byte{0x46, 0x88, 0x04, 0x07}}, + {x64.R8, x64.RDI, 8, x64.RDI, []byte{0x49, 0x89, 0x3C, 0x38}}, + {x64.R8, x64.RDI, 4, x64.RDI, []byte{0x41, 0x89, 0x3C, 0x38}}, + {x64.R8, x64.RDI, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x3C, 0x38}}, + {x64.R8, x64.RDI, 1, x64.RDI, []byte{0x41, 0x88, 0x3C, 0x38}}, + {x64.R9, x64.RSI, 8, x64.RSI, []byte{0x49, 0x89, 0x34, 0x31}}, + {x64.R9, x64.RSI, 4, x64.RSI, []byte{0x41, 0x89, 0x34, 0x31}}, + {x64.R9, x64.RSI, 2, x64.RSI, []byte{0x66, 0x41, 0x89, 0x34, 0x31}}, + {x64.R9, x64.RSI, 1, x64.RSI, []byte{0x41, 0x88, 0x34, 0x31}}, + {x64.R10, x64.RBP, 8, x64.RBP, []byte{0x49, 0x89, 0x2C, 0x2A}}, + {x64.R10, x64.RBP, 4, x64.RBP, []byte{0x41, 0x89, 0x2C, 0x2A}}, + {x64.R10, x64.RBP, 2, x64.RBP, []byte{0x66, 0x41, 0x89, 0x2C, 0x2A}}, + {x64.R10, x64.RBP, 1, x64.RBP, []byte{0x41, 0x88, 0x2C, 0x2A}}, + {x64.R11, x64.RSP, 8, x64.RSP, []byte{0x4A, 0x89, 0x24, 0x1C}}, + {x64.R11, x64.RSP, 4, x64.RSP, []byte{0x42, 0x89, 0x24, 0x1C}}, + {x64.R11, x64.RSP, 2, x64.RSP, []byte{0x66, 0x42, 0x89, 0x24, 0x1C}}, + {x64.R11, x64.RSP, 1, x64.RSP, []byte{0x42, 0x88, 0x24, 0x1C}}, + {x64.R12, x64.RBX, 8, x64.RBX, []byte{0x49, 0x89, 0x1C, 0x1C}}, + {x64.R12, x64.RBX, 4, x64.RBX, []byte{0x41, 0x89, 0x1C, 0x1C}}, + {x64.R12, x64.RBX, 2, x64.RBX, []byte{0x66, 0x41, 0x89, 0x1C, 0x1C}}, + {x64.R12, x64.RBX, 1, x64.RBX, []byte{0x41, 0x88, 0x1C, 0x1C}}, + {x64.R13, x64.RDX, 8, x64.RDX, []byte{0x49, 0x89, 0x54, 0x15, 0x00}}, + {x64.R13, x64.RDX, 4, x64.RDX, []byte{0x41, 0x89, 0x54, 0x15, 0x00}}, + {x64.R13, x64.RDX, 2, x64.RDX, []byte{0x66, 0x41, 0x89, 0x54, 0x15, 0x00}}, + {x64.R13, x64.RDX, 1, x64.RDX, []byte{0x41, 0x88, 0x54, 0x15, 0x00}}, + {x64.R14, x64.RCX, 8, x64.RCX, []byte{0x49, 0x89, 0x0C, 0x0E}}, + {x64.R14, x64.RCX, 4, x64.RCX, []byte{0x41, 0x89, 0x0C, 0x0E}}, + {x64.R14, x64.RCX, 2, x64.RCX, []byte{0x66, 0x41, 0x89, 0x0C, 0x0E}}, + {x64.R14, x64.RCX, 1, x64.RCX, []byte{0x41, 0x88, 0x0C, 0x0E}}, + {x64.R15, x64.RAX, 8, x64.RAX, []byte{0x49, 0x89, 0x04, 0x07}}, + {x64.R15, x64.RAX, 4, x64.RAX, []byte{0x41, 0x89, 0x04, 0x07}}, + {x64.R15, x64.RAX, 2, x64.RAX, []byte{0x66, 0x41, 0x89, 0x04, 0x07}}, + {x64.R15, x64.RAX, 1, x64.RAX, []byte{0x41, 0x88, 0x04, 0x07}}, + } + + for _, pattern := range usagePatterns { + t.Logf("store %dB [%s+%s], %s", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) + code := x64.StoreDynamicOffsetRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/tests/programs/array.q b/tests/programs/array.q new file mode 100644 index 0000000..6b4d16a --- /dev/null +++ b/tests/programs/array.q @@ -0,0 +1,44 @@ +import mem + +main() { + a := mem.alloc(5) + + assert a[0] == 0 + assert a[1] == 0 + assert a[2] == 0 + assert a[3] == 0 + assert a[4] == 0 + + a[0] = 0 + a[1] = 1 + a[2] = 2 + a[3] = 3 + a[4] = 4 + + assert a[0] == 0 + assert a[1] == 1 + assert a[2] == 2 + assert a[3] == 3 + assert a[4] == 4 + + i := 0 + a[i] = i * 2 + + i += 1 + a[i] = i * 2 + + i += 1 + a[i] = i * 2 + + i += 1 + a[i] = i * 2 + + i += 1 + a[i] = i * 2 + + assert a[0] == 0 + assert a[1] == 2 + assert a[2] == 4 + assert a[3] == 6 + assert a[4] == 8 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 986cf81..dfeba4d 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -31,6 +31,7 @@ var programs = []struct { {"binary", "", "", 0}, {"octal", "", "", 0}, {"hexadecimal", "", "", 0}, + {"array", "", "", 0}, {"escape-rune", "", "", 0}, {"escape-string", "", "", 0}, {"bitwise-and", "", "", 0}, From 9153a4ba301149bf8333e1a68b5f1029c201aafd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 25 Jan 2025 23:18:57 +0100 Subject: [PATCH 0575/1012] Renamed x64 store function --- src/asm/Finalize.go | 4 ++-- src/x64/{StoreDynamicOffset.go => StoreDynamic.go} | 8 ++++---- .../{StoreDynamicOffset_test.go => StoreDynamic_test.go} | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) rename src/x64/{StoreDynamicOffset.go => StoreDynamic.go} (78%) rename src/x64/{StoreDynamicOffset_test.go => StoreDynamic_test.go} (96%) diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index d60556d..0e3b3e2 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -291,13 +291,13 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { if operands.Address.OffsetRegister == math.MaxUint8 { code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) } else { - code = x64.StoreDynamicOffsetNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) + code = x64.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) } case *MemoryRegister: if operands.Address.OffsetRegister == math.MaxUint8 { code = x64.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) } else { - code = x64.StoreDynamicOffsetRegister(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) + code = x64.StoreDynamicRegister(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) } } diff --git a/src/x64/StoreDynamicOffset.go b/src/x64/StoreDynamic.go similarity index 78% rename from src/x64/StoreDynamicOffset.go rename to src/x64/StoreDynamic.go index c070aca..0fdeb9c 100644 --- a/src/x64/StoreDynamicOffset.go +++ b/src/x64/StoreDynamic.go @@ -6,8 +6,8 @@ import ( "git.akyoto.dev/cli/q/src/cpu" ) -// StoreDynamicOffsetNumber stores a number into the memory address at `destination` with a register offset. -func StoreDynamicOffsetNumber(code []byte, destination cpu.Register, offset cpu.Register, length byte, number int) []byte { +// StoreDynamicNumber stores a number into the memory address at `destination` with a register offset. +func StoreDynamicNumber(code []byte, destination cpu.Register, offset cpu.Register, length byte, number int) []byte { var ( w = byte(0) r = byte(0) @@ -69,8 +69,8 @@ func StoreDynamicOffsetNumber(code []byte, destination cpu.Register, offset cpu. return append(code, byte(number)) } -// StoreDynamicOffsetRegister stores the contents of the `source` register into the memory address at `destination` with a register offset. -func StoreDynamicOffsetRegister(code []byte, destination cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { +// StoreDynamicRegister stores the contents of the `source` register into the memory address at `destination` with a register offset. +func StoreDynamicRegister(code []byte, destination cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { var ( w = byte(0) r = byte(0) diff --git a/src/x64/StoreDynamicOffset_test.go b/src/x64/StoreDynamic_test.go similarity index 96% rename from src/x64/StoreDynamicOffset_test.go rename to src/x64/StoreDynamic_test.go index dfb72b3..0ff1f82 100644 --- a/src/x64/StoreDynamicOffset_test.go +++ b/src/x64/StoreDynamic_test.go @@ -8,7 +8,7 @@ import ( "git.akyoto.dev/go/assert" ) -func TestStoreDynamicOffsetNumber(t *testing.T) { +func TestStoreDynamicNumber(t *testing.T) { usagePatterns := []struct { RegisterTo cpu.Register Offset cpu.Register @@ -84,12 +84,12 @@ func TestStoreDynamicOffsetNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("store %dB [%s+%s], %d", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.Number) - code := x64.StoreDynamicOffsetNumber(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.Number) + code := x64.StoreDynamicNumber(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } -func TestStoreDynamicOffsetRegister(t *testing.T) { +func TestStoreDynamicRegister(t *testing.T) { usagePatterns := []struct { RegisterTo cpu.Register Offset cpu.Register @@ -165,7 +165,7 @@ func TestStoreDynamicOffsetRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("store %dB [%s+%s], %s", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) - code := x64.StoreDynamicOffsetRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom) + code := x64.StoreDynamicRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom) assert.DeepEqual(t, code, pattern.Code) } } From bc8e7e452d4d4a436e9bed442423ddf1f6335422 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 25 Jan 2025 23:18:57 +0100 Subject: [PATCH 0576/1012] Renamed x64 store function --- src/asm/Finalize.go | 4 ++-- src/x64/{StoreDynamicOffset.go => StoreDynamic.go} | 8 ++++---- .../{StoreDynamicOffset_test.go => StoreDynamic_test.go} | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) rename src/x64/{StoreDynamicOffset.go => StoreDynamic.go} (78%) rename src/x64/{StoreDynamicOffset_test.go => StoreDynamic_test.go} (96%) diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index d60556d..0e3b3e2 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -291,13 +291,13 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { if operands.Address.OffsetRegister == math.MaxUint8 { code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) } else { - code = x64.StoreDynamicOffsetNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) + code = x64.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) } case *MemoryRegister: if operands.Address.OffsetRegister == math.MaxUint8 { code = x64.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) } else { - code = x64.StoreDynamicOffsetRegister(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) + code = x64.StoreDynamicRegister(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) } } diff --git a/src/x64/StoreDynamicOffset.go b/src/x64/StoreDynamic.go similarity index 78% rename from src/x64/StoreDynamicOffset.go rename to src/x64/StoreDynamic.go index c070aca..0fdeb9c 100644 --- a/src/x64/StoreDynamicOffset.go +++ b/src/x64/StoreDynamic.go @@ -6,8 +6,8 @@ import ( "git.akyoto.dev/cli/q/src/cpu" ) -// StoreDynamicOffsetNumber stores a number into the memory address at `destination` with a register offset. -func StoreDynamicOffsetNumber(code []byte, destination cpu.Register, offset cpu.Register, length byte, number int) []byte { +// StoreDynamicNumber stores a number into the memory address at `destination` with a register offset. +func StoreDynamicNumber(code []byte, destination cpu.Register, offset cpu.Register, length byte, number int) []byte { var ( w = byte(0) r = byte(0) @@ -69,8 +69,8 @@ func StoreDynamicOffsetNumber(code []byte, destination cpu.Register, offset cpu. return append(code, byte(number)) } -// StoreDynamicOffsetRegister stores the contents of the `source` register into the memory address at `destination` with a register offset. -func StoreDynamicOffsetRegister(code []byte, destination cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { +// StoreDynamicRegister stores the contents of the `source` register into the memory address at `destination` with a register offset. +func StoreDynamicRegister(code []byte, destination cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { var ( w = byte(0) r = byte(0) diff --git a/src/x64/StoreDynamicOffset_test.go b/src/x64/StoreDynamic_test.go similarity index 96% rename from src/x64/StoreDynamicOffset_test.go rename to src/x64/StoreDynamic_test.go index dfb72b3..0ff1f82 100644 --- a/src/x64/StoreDynamicOffset_test.go +++ b/src/x64/StoreDynamic_test.go @@ -8,7 +8,7 @@ import ( "git.akyoto.dev/go/assert" ) -func TestStoreDynamicOffsetNumber(t *testing.T) { +func TestStoreDynamicNumber(t *testing.T) { usagePatterns := []struct { RegisterTo cpu.Register Offset cpu.Register @@ -84,12 +84,12 @@ func TestStoreDynamicOffsetNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("store %dB [%s+%s], %d", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.Number) - code := x64.StoreDynamicOffsetNumber(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.Number) + code := x64.StoreDynamicNumber(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } -func TestStoreDynamicOffsetRegister(t *testing.T) { +func TestStoreDynamicRegister(t *testing.T) { usagePatterns := []struct { RegisterTo cpu.Register Offset cpu.Register @@ -165,7 +165,7 @@ func TestStoreDynamicOffsetRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("store %dB [%s+%s], %s", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) - code := x64.StoreDynamicOffsetRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom) + code := x64.StoreDynamicRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom) assert.DeepEqual(t, code, pattern.Code) } } From ce5302a965cc233344eeda97b0f85d4205a42a10 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 26 Jan 2025 12:28:38 +0100 Subject: [PATCH 0577/1012] Improved visibility of token groups --- src/token/Kind.go | 136 ++++++++++++++++++++++----------------------- src/token/Token.go | 10 ++-- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/src/token/Kind.go b/src/token/Kind.go index e0c23f8..737ef55 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -4,72 +4,72 @@ package token type Kind uint8 const ( - Invalid Kind = iota // Invalid is an invalid token. - EOF // EOF is the end of file. - NewLine // NewLine is the newline character. - Identifier // Identifier is a series of characters used to identify a variable or function. - Number // Number is a series of numerical characters. - Rune // Rune is a single unicode code point. - String // String is an uninterpreted series of characters in the source code. - Comment // Comment is a comment. - GroupStart // ( - GroupEnd // ) - BlockStart // { - BlockEnd // } - ArrayStart // [ - ArrayEnd // ] - ReturnType // -> - _operators // - Add // + - Sub // - - Mul // * - Div // / - Mod // % - And // & - Or // | - Xor // ^ - Shl // << - Shr // >> - LogicalAnd // && - LogicalOr // || - _comparisons // - Equal // == - NotEqual // != - Less // < - Greater // > - LessEqual // <= - GreaterEqual // >= - _comparisonsEnd // - Define // := - Period // . - Call // x() - Array // [x] - Separator // , - _unary // - Not // ! (unary) - Negate // - (unary) - _unaryEnd // - _assignments // - Assign // = - AddAssign // += - SubAssign // -= - MulAssign // *= - DivAssign // /= - ModAssign // %= - AndAssign // &= - OrAssign // |= - XorAssign // ^= - ShlAssign // <<= - ShrAssign // >>= - _assignmentsEnd // - _operatorsEnd // - _keywords // - Assert // assert - Else // else - If // if - Import // import - Loop // loop - Return // return - Switch // switch - _keywordsEnd // + Invalid Kind = iota // Invalid is an invalid token. + EOF // EOF is the end of file. + NewLine // NewLine is the newline character. + Identifier // Identifier is a series of characters used to identify a variable or function. + Number // Number is a series of numerical characters. + Rune // Rune is a single unicode code point. + String // String is an uninterpreted series of characters in the source code. + Comment // Comment is a comment. + GroupStart // ( + GroupEnd // ) + BlockStart // { + BlockEnd // } + ArrayStart // [ + ArrayEnd // ] + ReturnType // -> + ___OPERATORS___ // + Add // + + Sub // - + Mul // * + Div // / + Mod // % + And // & + Or // | + Xor // ^ + Shl // << + Shr // >> + LogicalAnd // && + LogicalOr // || + Define // := + Period // . + Call // x() + Array // [x] + Separator // , + ___ASSIGNMENTS___ // + Assign // = + AddAssign // += + SubAssign // -= + MulAssign // *= + DivAssign // /= + ModAssign // %= + AndAssign // &= + OrAssign // |= + XorAssign // ^= + ShlAssign // <<= + ShrAssign // >>= + ___END_ASSIGNMENTS___ // + ___COMPARISONS___ // + Equal // == + NotEqual // != + Less // < + Greater // > + LessEqual // <= + GreaterEqual // >= + ___END_COMPARISONS___ // + ___UNARY___ // + Not // ! (unary) + Negate // - (unary) + ___END_UNARY___ // + ___END_OPERATORS___ // + ___KEYWORDS___ // + Assert // assert + Else // else + If // if + Import // import + Loop // loop + Return // return + Switch // switch + ___END_KEYWORDS___ // ) diff --git a/src/token/Token.go b/src/token/Token.go index 0476cbd..69e7e83 100644 --- a/src/token/Token.go +++ b/src/token/Token.go @@ -25,12 +25,12 @@ func (t Token) End() Position { // IsAssignment returns true if the token is an assignment operator. func (t Token) IsAssignment() bool { - return t.Kind > _assignments && t.Kind < _assignmentsEnd + return t.Kind > ___ASSIGNMENTS___ && t.Kind < ___END_ASSIGNMENTS___ } // IsComparison returns true if the token is a comparison operator. func (t Token) IsComparison() bool { - return t.Kind > _comparisons && t.Kind < _comparisonsEnd + return t.Kind > ___COMPARISONS___ && t.Kind < ___END_COMPARISONS___ } // IsExpressionStart returns true if the token starts an expression. @@ -40,7 +40,7 @@ func (t Token) IsExpressionStart() bool { // IsKeyword returns true if the token is a keyword. func (t Token) IsKeyword() bool { - return t.Kind > _keywords && t.Kind < _keywordsEnd + return t.Kind > ___KEYWORDS___ && t.Kind < ___END_KEYWORDS___ } // IsNumeric returns true if the token is a number or rune. @@ -50,12 +50,12 @@ func (t Token) IsNumeric() bool { // IsOperator returns true if the token is an operator. func (t Token) IsOperator() bool { - return t.Kind > _operators && t.Kind < _operatorsEnd + return t.Kind > ___OPERATORS___ && t.Kind < ___END_OPERATORS___ } // IsUnaryOperator returns true if the token is a unary operator. func (t Token) IsUnaryOperator() bool { - return t.Kind > _unary && t.Kind < _unaryEnd + return t.Kind > ___UNARY___ && t.Kind < ___END_UNARY___ } // Reset resets the token to default values. From e62eaba6e62908f4b39d0faef0b7f6b5e0c38548 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 26 Jan 2025 12:28:38 +0100 Subject: [PATCH 0578/1012] Improved visibility of token groups --- src/token/Kind.go | 136 ++++++++++++++++++++++----------------------- src/token/Token.go | 10 ++-- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/src/token/Kind.go b/src/token/Kind.go index e0c23f8..737ef55 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -4,72 +4,72 @@ package token type Kind uint8 const ( - Invalid Kind = iota // Invalid is an invalid token. - EOF // EOF is the end of file. - NewLine // NewLine is the newline character. - Identifier // Identifier is a series of characters used to identify a variable or function. - Number // Number is a series of numerical characters. - Rune // Rune is a single unicode code point. - String // String is an uninterpreted series of characters in the source code. - Comment // Comment is a comment. - GroupStart // ( - GroupEnd // ) - BlockStart // { - BlockEnd // } - ArrayStart // [ - ArrayEnd // ] - ReturnType // -> - _operators // - Add // + - Sub // - - Mul // * - Div // / - Mod // % - And // & - Or // | - Xor // ^ - Shl // << - Shr // >> - LogicalAnd // && - LogicalOr // || - _comparisons // - Equal // == - NotEqual // != - Less // < - Greater // > - LessEqual // <= - GreaterEqual // >= - _comparisonsEnd // - Define // := - Period // . - Call // x() - Array // [x] - Separator // , - _unary // - Not // ! (unary) - Negate // - (unary) - _unaryEnd // - _assignments // - Assign // = - AddAssign // += - SubAssign // -= - MulAssign // *= - DivAssign // /= - ModAssign // %= - AndAssign // &= - OrAssign // |= - XorAssign // ^= - ShlAssign // <<= - ShrAssign // >>= - _assignmentsEnd // - _operatorsEnd // - _keywords // - Assert // assert - Else // else - If // if - Import // import - Loop // loop - Return // return - Switch // switch - _keywordsEnd // + Invalid Kind = iota // Invalid is an invalid token. + EOF // EOF is the end of file. + NewLine // NewLine is the newline character. + Identifier // Identifier is a series of characters used to identify a variable or function. + Number // Number is a series of numerical characters. + Rune // Rune is a single unicode code point. + String // String is an uninterpreted series of characters in the source code. + Comment // Comment is a comment. + GroupStart // ( + GroupEnd // ) + BlockStart // { + BlockEnd // } + ArrayStart // [ + ArrayEnd // ] + ReturnType // -> + ___OPERATORS___ // + Add // + + Sub // - + Mul // * + Div // / + Mod // % + And // & + Or // | + Xor // ^ + Shl // << + Shr // >> + LogicalAnd // && + LogicalOr // || + Define // := + Period // . + Call // x() + Array // [x] + Separator // , + ___ASSIGNMENTS___ // + Assign // = + AddAssign // += + SubAssign // -= + MulAssign // *= + DivAssign // /= + ModAssign // %= + AndAssign // &= + OrAssign // |= + XorAssign // ^= + ShlAssign // <<= + ShrAssign // >>= + ___END_ASSIGNMENTS___ // + ___COMPARISONS___ // + Equal // == + NotEqual // != + Less // < + Greater // > + LessEqual // <= + GreaterEqual // >= + ___END_COMPARISONS___ // + ___UNARY___ // + Not // ! (unary) + Negate // - (unary) + ___END_UNARY___ // + ___END_OPERATORS___ // + ___KEYWORDS___ // + Assert // assert + Else // else + If // if + Import // import + Loop // loop + Return // return + Switch // switch + ___END_KEYWORDS___ // ) diff --git a/src/token/Token.go b/src/token/Token.go index 0476cbd..69e7e83 100644 --- a/src/token/Token.go +++ b/src/token/Token.go @@ -25,12 +25,12 @@ func (t Token) End() Position { // IsAssignment returns true if the token is an assignment operator. func (t Token) IsAssignment() bool { - return t.Kind > _assignments && t.Kind < _assignmentsEnd + return t.Kind > ___ASSIGNMENTS___ && t.Kind < ___END_ASSIGNMENTS___ } // IsComparison returns true if the token is a comparison operator. func (t Token) IsComparison() bool { - return t.Kind > _comparisons && t.Kind < _comparisonsEnd + return t.Kind > ___COMPARISONS___ && t.Kind < ___END_COMPARISONS___ } // IsExpressionStart returns true if the token starts an expression. @@ -40,7 +40,7 @@ func (t Token) IsExpressionStart() bool { // IsKeyword returns true if the token is a keyword. func (t Token) IsKeyword() bool { - return t.Kind > _keywords && t.Kind < _keywordsEnd + return t.Kind > ___KEYWORDS___ && t.Kind < ___END_KEYWORDS___ } // IsNumeric returns true if the token is a number or rune. @@ -50,12 +50,12 @@ func (t Token) IsNumeric() bool { // IsOperator returns true if the token is an operator. func (t Token) IsOperator() bool { - return t.Kind > _operators && t.Kind < _operatorsEnd + return t.Kind > ___OPERATORS___ && t.Kind < ___END_OPERATORS___ } // IsUnaryOperator returns true if the token is a unary operator. func (t Token) IsUnaryOperator() bool { - return t.Kind > _unary && t.Kind < _unaryEnd + return t.Kind > ___UNARY___ && t.Kind < ___END_UNARY___ } // Reset resets the token to default values. From 764b7f5c2937f9f88d7e759f052a3b72bff0aa6c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 26 Jan 2025 14:31:38 +0100 Subject: [PATCH 0579/1012] Added missing register deallocation --- src/core/CompileAssignArray.go | 6 +++++- src/core/CompileAssignDivision.go | 7 ++++++- src/core/Evaluate.go | 6 +++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 339f3f6..08f73e6 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -42,13 +42,17 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { memory.Offset = byte(offset) } else { - _, indexRegister, err := f.Evaluate(index) + _, indexRegister, isTemporary, err := f.Evaluate(index) if err != nil { return err } memory.OffsetRegister = indexRegister + + if isTemporary { + defer f.FreeRegister(indexRegister) + } } _, err := f.ExpressionToMemory(right, memory) diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index 22f68fd..e169827 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -29,7 +29,7 @@ func (f *Function) CompileAssignDivision(node *ast.Assign) error { } dividend := right.Children[0] - _, dividendRegister, err := f.Evaluate(dividend) + _, dividendRegister, isTemporary, err := f.Evaluate(dividend) if err != nil { return err @@ -39,5 +39,10 @@ func (f *Function) CompileAssignDivision(node *ast.Assign) error { err = f.Execute(right.Token, dividendRegister, divisor) f.RegisterRegister(asm.MOVE, quotientVariable.Register, x64.RAX) f.RegisterRegister(asm.MOVE, remainderVariable.Register, x64.RDX) + + if isTemporary { + f.FreeRegister(dividendRegister) + } + return err } diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 235f917..349c077 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -8,18 +8,18 @@ import ( ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. -func (f *Function) Evaluate(expr *expression.Expression) (*types.Type, cpu.Register, error) { +func (f *Function) Evaluate(expr *expression.Expression) (*types.Type, cpu.Register, bool, error) { if expr.Token.Kind == token.Identifier { name := expr.Token.Text(f.File.Bytes) variable := f.VariableByName(name) if variable.Alive == 1 { f.UseVariable(variable) - return variable.Type, variable.Register, nil + return variable.Type, variable.Register, false, nil } } tmp := f.NewRegister() typ, err := f.ExpressionToRegister(expr, tmp) - return typ, tmp, err + return typ, tmp, true, err } From fb40059113abad2eb49f247b43ae90226beed090 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 26 Jan 2025 14:31:38 +0100 Subject: [PATCH 0580/1012] Added missing register deallocation --- src/core/CompileAssignArray.go | 6 +++++- src/core/CompileAssignDivision.go | 7 ++++++- src/core/Evaluate.go | 6 +++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 339f3f6..08f73e6 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -42,13 +42,17 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { memory.Offset = byte(offset) } else { - _, indexRegister, err := f.Evaluate(index) + _, indexRegister, isTemporary, err := f.Evaluate(index) if err != nil { return err } memory.OffsetRegister = indexRegister + + if isTemporary { + defer f.FreeRegister(indexRegister) + } } _, err := f.ExpressionToMemory(right, memory) diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index 22f68fd..e169827 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -29,7 +29,7 @@ func (f *Function) CompileAssignDivision(node *ast.Assign) error { } dividend := right.Children[0] - _, dividendRegister, err := f.Evaluate(dividend) + _, dividendRegister, isTemporary, err := f.Evaluate(dividend) if err != nil { return err @@ -39,5 +39,10 @@ func (f *Function) CompileAssignDivision(node *ast.Assign) error { err = f.Execute(right.Token, dividendRegister, divisor) f.RegisterRegister(asm.MOVE, quotientVariable.Register, x64.RAX) f.RegisterRegister(asm.MOVE, remainderVariable.Register, x64.RDX) + + if isTemporary { + f.FreeRegister(dividendRegister) + } + return err } diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 235f917..349c077 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -8,18 +8,18 @@ import ( ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. -func (f *Function) Evaluate(expr *expression.Expression) (*types.Type, cpu.Register, error) { +func (f *Function) Evaluate(expr *expression.Expression) (*types.Type, cpu.Register, bool, error) { if expr.Token.Kind == token.Identifier { name := expr.Token.Text(f.File.Bytes) variable := f.VariableByName(name) if variable.Alive == 1 { f.UseVariable(variable) - return variable.Type, variable.Register, nil + return variable.Type, variable.Register, false, nil } } tmp := f.NewRegister() typ, err := f.ExpressionToRegister(expr, tmp) - return typ, tmp, err + return typ, tmp, true, err } From b8dab29c546792315e70195c0048cb30f70f798d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 28 Jan 2025 13:43:38 +0100 Subject: [PATCH 0581/1012] Improved documentation --- src/data/Data.go | 41 ----------------------------------------- src/data/Finalize.go | 38 ++++++++++++++++++++++++++++++++++++++ src/data/Insert.go | 6 ++++++ src/errors/Base.go | 22 ---------------------- src/errors/Common.go | 23 +++++++++++++++++++++++ src/readme.md | 44 ++++++++++++++++++++++++++++++++++++-------- 6 files changed, 103 insertions(+), 71 deletions(-) create mode 100644 src/data/Finalize.go create mode 100644 src/data/Insert.go create mode 100644 src/errors/Common.go diff --git a/src/data/Data.go b/src/data/Data.go index 16aca87..b6923a0 100644 --- a/src/data/Data.go +++ b/src/data/Data.go @@ -1,45 +1,4 @@ package data -import ( - "bytes" - "sort" -) - // Data saves slices of bytes referenced by labels. type Data map[string][]byte - -// Finalize returns the final raw data slice and a map of labels with their respective indices. -// It will try to reuse existing data whenever possible. -func (data Data) Finalize() ([]byte, map[string]int32) { - var ( - final []byte - keys = make([]string, 0, len(data)) - positions = make(map[string]int32, len(data)) - ) - - for key := range data { - keys = append(keys, key) - } - - sort.SliceStable(keys, func(i, j int) bool { - return len(data[keys[i]]) > len(data[keys[j]]) - }) - - for _, key := range keys { - raw := data[key] - position := bytes.Index(final, raw) - - if position != -1 { - positions[key] = int32(position) - } else { - positions[key] = int32(len(final)) - final = append(final, raw...) - } - } - - return final, positions -} - -func (data Data) Insert(label string, raw []byte) { - data[label] = raw -} diff --git a/src/data/Finalize.go b/src/data/Finalize.go new file mode 100644 index 0000000..b890ae0 --- /dev/null +++ b/src/data/Finalize.go @@ -0,0 +1,38 @@ +package data + +import ( + "bytes" + "sort" +) + +// Finalize returns the final raw data slice and a map of labels with their respective indices. +// It will try to reuse existing data whenever possible. +func (data Data) Finalize() ([]byte, map[string]int32) { + var ( + final []byte + keys = make([]string, 0, len(data)) + positions = make(map[string]int32, len(data)) + ) + + for key := range data { + keys = append(keys, key) + } + + sort.SliceStable(keys, func(i, j int) bool { + return len(data[keys[i]]) > len(data[keys[j]]) + }) + + for _, key := range keys { + raw := data[key] + position := bytes.Index(final, raw) + + if position != -1 { + positions[key] = int32(position) + } else { + positions[key] = int32(len(final)) + final = append(final, raw...) + } + } + + return final, positions +} diff --git a/src/data/Insert.go b/src/data/Insert.go new file mode 100644 index 0000000..8dcc690 --- /dev/null +++ b/src/data/Insert.go @@ -0,0 +1,6 @@ +package data + +// Insert registers a slice of bytes for the given label. +func (data Data) Insert(label string, raw []byte) { + data[label] = raw +} diff --git a/src/errors/Base.go b/src/errors/Base.go index 0933e59..a019d58 100644 --- a/src/errors/Base.go +++ b/src/errors/Base.go @@ -1,27 +1,5 @@ package errors -var ( - EmptySwitch = &Base{"Empty switch"} - ExpectedFunctionName = &Base{"Expected function name"} - ExpectedFunctionParameters = &Base{"Expected function parameters"} - ExpectedFunctionDefinition = &Base{"Expected function definition"} - ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} - InvalidNumber = &Base{"Invalid number"} - InvalidExpression = &Base{"Invalid expression"} - InvalidRune = &Base{"Invalid rune"} - InvalidStatement = &Base{"Invalid statement"} - MissingBlockStart = &Base{"Missing '{'"} - MissingBlockEnd = &Base{"Missing '}'"} - MissingExpression = &Base{"Missing expression"} - MissingGroupStart = &Base{"Missing '('"} - MissingGroupEnd = &Base{"Missing ')'"} - MissingMainFunction = &Base{"Missing main function"} - MissingOperand = &Base{"Missing operand"} - MissingType = &Base{"Missing type"} - NotImplemented = &Base{"Not implemented"} - UnknownType = &Base{"Unknown type"} -) - // Base is the base class for errors that have no parameters. type Base struct { Message string diff --git a/src/errors/Common.go b/src/errors/Common.go new file mode 100644 index 0000000..a4ebdc9 --- /dev/null +++ b/src/errors/Common.go @@ -0,0 +1,23 @@ +package errors + +var ( + EmptySwitch = &Base{"Empty switch"} + ExpectedFunctionName = &Base{"Expected function name"} + ExpectedFunctionParameters = &Base{"Expected function parameters"} + ExpectedFunctionDefinition = &Base{"Expected function definition"} + ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} + InvalidNumber = &Base{"Invalid number"} + InvalidExpression = &Base{"Invalid expression"} + InvalidRune = &Base{"Invalid rune"} + InvalidStatement = &Base{"Invalid statement"} + MissingBlockStart = &Base{"Missing '{'"} + MissingBlockEnd = &Base{"Missing '}'"} + MissingExpression = &Base{"Missing expression"} + MissingGroupStart = &Base{"Missing '('"} + MissingGroupEnd = &Base{"Missing ')'"} + MissingMainFunction = &Base{"Missing main function"} + MissingOperand = &Base{"Missing operand"} + MissingType = &Base{"Missing type"} + NotImplemented = &Base{"Not implemented"} + UnknownType = &Base{"Unknown type"} +) diff --git a/src/readme.md b/src/readme.md index c29d252..59c7d81 100644 --- a/src/readme.md +++ b/src/readme.md @@ -1,6 +1,34 @@ -## Documentation +# Overview -### [cli/Main.go](cli/Main.go) +- [arm64](arm64) - ARM64 implementation (w.i.p.) +- [asm](asm) - Pseudo-assembler stage +- [ast](ast) - Abstract syntax tree generation with the `Parse` function +- [build](build) - Build command +- [cli](cli) - Command line interface +- [compiler](compiler) - Compiler frontend used by `build` +- [config](config) - Globals for the entire project +- [core](core) - Definition of `Function` and how to compile it (uses `register.Machine`) +- [cpu](cpu) - Types to simulate a generic CPU during compilation +- [data](data) - Data container that can re-use existing data (e.g. the `Hello` in `Hello World`) +- [dll](dll) - DLL support for Windows systems (w.i.p.) +- [elf](elf) - ELF format for Linux executables +- [errors](errors) - Error types +- [expression](expression) - Expression parser generating trees with the `Parse` function +- [fs](fs) - File system access +- [macho](macho) - MachO format for Mac executables +- [pe](pe) - PE format for Windows executables +- [register](register) - Defines `Machine` type combining an assembler with CPU states +- [riscv](riscv) - RISCV implementation (w.i.p.) +- [scanner](scanner) - Scanner frontend used by `build` +- [scope](scope) - Defines a `Scope` used for code blocks +- [sizeof](sizeof) - Calculates the byte size of numbers +- [token](token) - Converts a file to tokens with the `Tokenize` function +- [types](types) - Type system (w.i.p.) +- [x64](x64) - x86-64 implementation + +# Documentation + +## [cli/Main.go](cli/Main.go) Entry point. @@ -8,7 +36,7 @@ The command line interface expects a command like `build` as the first argument. Commands are implemented as functions in the [cli](cli) directory. Each command has its own set of parameters. -### [cli/Build.go](cli/Build.go) +## [cli/Build.go](cli/Build.go) The build command creates a new `Build` instance with the given directory and calls the `Run` method. @@ -35,7 +63,7 @@ Adding the `-v` or `--verbose` flag shows verbose compiler information: q build examples/hello -v ``` -### [build/Build.go](build/Build.go) +## [build/Build.go](build/Build.go) The `Build` type defines all the information needed to start building an executable file. The name of the executable will be equal to the name of the build directory. @@ -53,18 +81,18 @@ We create a separate goroutine for each function compilation. Each function will then be translated to generic assembler instructions. All the functions that are required to run the program will be added to the final assembler. -The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. +The final assembler resolves label addresses, optimizes the performance and generates the specific machine code from the generic instruction set. -### [core/Function.go](core/Function.go) +## [core/Function.go](core/Function.go) This is the "heart" of the compiler. Each function runs `f.Compile` which organizes the source code into an abstract syntax tree that is then compiled via `f.CompileAST`. You can think of AST nodes as the individual statements in your source code. -### [ast/Parse.go](ast/Parse.go) +## [ast/Parse.go](ast/Parse.go) This is what generates the AST from tokens. -### [expression/Parse.go](expression/Parse.go) +## [expression/Parse.go](expression/Parse.go) This is what generates expressions from tokens. \ No newline at end of file From 9a8cffe2f4665c1666a26c33f5492304a526ffbf Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 28 Jan 2025 13:43:38 +0100 Subject: [PATCH 0582/1012] Improved documentation --- src/data/Data.go | 41 ----------------------------------------- src/data/Finalize.go | 38 ++++++++++++++++++++++++++++++++++++++ src/data/Insert.go | 6 ++++++ src/errors/Base.go | 22 ---------------------- src/errors/Common.go | 23 +++++++++++++++++++++++ src/readme.md | 44 ++++++++++++++++++++++++++++++++++++-------- 6 files changed, 103 insertions(+), 71 deletions(-) create mode 100644 src/data/Finalize.go create mode 100644 src/data/Insert.go create mode 100644 src/errors/Common.go diff --git a/src/data/Data.go b/src/data/Data.go index 16aca87..b6923a0 100644 --- a/src/data/Data.go +++ b/src/data/Data.go @@ -1,45 +1,4 @@ package data -import ( - "bytes" - "sort" -) - // Data saves slices of bytes referenced by labels. type Data map[string][]byte - -// Finalize returns the final raw data slice and a map of labels with their respective indices. -// It will try to reuse existing data whenever possible. -func (data Data) Finalize() ([]byte, map[string]int32) { - var ( - final []byte - keys = make([]string, 0, len(data)) - positions = make(map[string]int32, len(data)) - ) - - for key := range data { - keys = append(keys, key) - } - - sort.SliceStable(keys, func(i, j int) bool { - return len(data[keys[i]]) > len(data[keys[j]]) - }) - - for _, key := range keys { - raw := data[key] - position := bytes.Index(final, raw) - - if position != -1 { - positions[key] = int32(position) - } else { - positions[key] = int32(len(final)) - final = append(final, raw...) - } - } - - return final, positions -} - -func (data Data) Insert(label string, raw []byte) { - data[label] = raw -} diff --git a/src/data/Finalize.go b/src/data/Finalize.go new file mode 100644 index 0000000..b890ae0 --- /dev/null +++ b/src/data/Finalize.go @@ -0,0 +1,38 @@ +package data + +import ( + "bytes" + "sort" +) + +// Finalize returns the final raw data slice and a map of labels with their respective indices. +// It will try to reuse existing data whenever possible. +func (data Data) Finalize() ([]byte, map[string]int32) { + var ( + final []byte + keys = make([]string, 0, len(data)) + positions = make(map[string]int32, len(data)) + ) + + for key := range data { + keys = append(keys, key) + } + + sort.SliceStable(keys, func(i, j int) bool { + return len(data[keys[i]]) > len(data[keys[j]]) + }) + + for _, key := range keys { + raw := data[key] + position := bytes.Index(final, raw) + + if position != -1 { + positions[key] = int32(position) + } else { + positions[key] = int32(len(final)) + final = append(final, raw...) + } + } + + return final, positions +} diff --git a/src/data/Insert.go b/src/data/Insert.go new file mode 100644 index 0000000..8dcc690 --- /dev/null +++ b/src/data/Insert.go @@ -0,0 +1,6 @@ +package data + +// Insert registers a slice of bytes for the given label. +func (data Data) Insert(label string, raw []byte) { + data[label] = raw +} diff --git a/src/errors/Base.go b/src/errors/Base.go index 0933e59..a019d58 100644 --- a/src/errors/Base.go +++ b/src/errors/Base.go @@ -1,27 +1,5 @@ package errors -var ( - EmptySwitch = &Base{"Empty switch"} - ExpectedFunctionName = &Base{"Expected function name"} - ExpectedFunctionParameters = &Base{"Expected function parameters"} - ExpectedFunctionDefinition = &Base{"Expected function definition"} - ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} - InvalidNumber = &Base{"Invalid number"} - InvalidExpression = &Base{"Invalid expression"} - InvalidRune = &Base{"Invalid rune"} - InvalidStatement = &Base{"Invalid statement"} - MissingBlockStart = &Base{"Missing '{'"} - MissingBlockEnd = &Base{"Missing '}'"} - MissingExpression = &Base{"Missing expression"} - MissingGroupStart = &Base{"Missing '('"} - MissingGroupEnd = &Base{"Missing ')'"} - MissingMainFunction = &Base{"Missing main function"} - MissingOperand = &Base{"Missing operand"} - MissingType = &Base{"Missing type"} - NotImplemented = &Base{"Not implemented"} - UnknownType = &Base{"Unknown type"} -) - // Base is the base class for errors that have no parameters. type Base struct { Message string diff --git a/src/errors/Common.go b/src/errors/Common.go new file mode 100644 index 0000000..a4ebdc9 --- /dev/null +++ b/src/errors/Common.go @@ -0,0 +1,23 @@ +package errors + +var ( + EmptySwitch = &Base{"Empty switch"} + ExpectedFunctionName = &Base{"Expected function name"} + ExpectedFunctionParameters = &Base{"Expected function parameters"} + ExpectedFunctionDefinition = &Base{"Expected function definition"} + ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} + InvalidNumber = &Base{"Invalid number"} + InvalidExpression = &Base{"Invalid expression"} + InvalidRune = &Base{"Invalid rune"} + InvalidStatement = &Base{"Invalid statement"} + MissingBlockStart = &Base{"Missing '{'"} + MissingBlockEnd = &Base{"Missing '}'"} + MissingExpression = &Base{"Missing expression"} + MissingGroupStart = &Base{"Missing '('"} + MissingGroupEnd = &Base{"Missing ')'"} + MissingMainFunction = &Base{"Missing main function"} + MissingOperand = &Base{"Missing operand"} + MissingType = &Base{"Missing type"} + NotImplemented = &Base{"Not implemented"} + UnknownType = &Base{"Unknown type"} +) diff --git a/src/readme.md b/src/readme.md index c29d252..59c7d81 100644 --- a/src/readme.md +++ b/src/readme.md @@ -1,6 +1,34 @@ -## Documentation +# Overview -### [cli/Main.go](cli/Main.go) +- [arm64](arm64) - ARM64 implementation (w.i.p.) +- [asm](asm) - Pseudo-assembler stage +- [ast](ast) - Abstract syntax tree generation with the `Parse` function +- [build](build) - Build command +- [cli](cli) - Command line interface +- [compiler](compiler) - Compiler frontend used by `build` +- [config](config) - Globals for the entire project +- [core](core) - Definition of `Function` and how to compile it (uses `register.Machine`) +- [cpu](cpu) - Types to simulate a generic CPU during compilation +- [data](data) - Data container that can re-use existing data (e.g. the `Hello` in `Hello World`) +- [dll](dll) - DLL support for Windows systems (w.i.p.) +- [elf](elf) - ELF format for Linux executables +- [errors](errors) - Error types +- [expression](expression) - Expression parser generating trees with the `Parse` function +- [fs](fs) - File system access +- [macho](macho) - MachO format for Mac executables +- [pe](pe) - PE format for Windows executables +- [register](register) - Defines `Machine` type combining an assembler with CPU states +- [riscv](riscv) - RISCV implementation (w.i.p.) +- [scanner](scanner) - Scanner frontend used by `build` +- [scope](scope) - Defines a `Scope` used for code blocks +- [sizeof](sizeof) - Calculates the byte size of numbers +- [token](token) - Converts a file to tokens with the `Tokenize` function +- [types](types) - Type system (w.i.p.) +- [x64](x64) - x86-64 implementation + +# Documentation + +## [cli/Main.go](cli/Main.go) Entry point. @@ -8,7 +36,7 @@ The command line interface expects a command like `build` as the first argument. Commands are implemented as functions in the [cli](cli) directory. Each command has its own set of parameters. -### [cli/Build.go](cli/Build.go) +## [cli/Build.go](cli/Build.go) The build command creates a new `Build` instance with the given directory and calls the `Run` method. @@ -35,7 +63,7 @@ Adding the `-v` or `--verbose` flag shows verbose compiler information: q build examples/hello -v ``` -### [build/Build.go](build/Build.go) +## [build/Build.go](build/Build.go) The `Build` type defines all the information needed to start building an executable file. The name of the executable will be equal to the name of the build directory. @@ -53,18 +81,18 @@ We create a separate goroutine for each function compilation. Each function will then be translated to generic assembler instructions. All the functions that are required to run the program will be added to the final assembler. -The final assembler resolves label addresses, optimizes the performance and generates the specific x86-64 machine code from the generic instruction set. +The final assembler resolves label addresses, optimizes the performance and generates the specific machine code from the generic instruction set. -### [core/Function.go](core/Function.go) +## [core/Function.go](core/Function.go) This is the "heart" of the compiler. Each function runs `f.Compile` which organizes the source code into an abstract syntax tree that is then compiled via `f.CompileAST`. You can think of AST nodes as the individual statements in your source code. -### [ast/Parse.go](ast/Parse.go) +## [ast/Parse.go](ast/Parse.go) This is what generates the AST from tokens. -### [expression/Parse.go](expression/Parse.go) +## [expression/Parse.go](expression/Parse.go) This is what generates expressions from tokens. \ No newline at end of file From 21e544640d8c5e6410b68ad1f1d121dd5687d485 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 28 Jan 2025 14:04:30 +0100 Subject: [PATCH 0583/1012] Improved documentation --- src/build/readme.md | 19 ++++++++++++ src/cli/readme.md | 34 ++++++++++++++++++++++ src/core/readme.md | 5 ++++ src/readme.md | 71 --------------------------------------------- 4 files changed, 58 insertions(+), 71 deletions(-) create mode 100644 src/build/readme.md create mode 100644 src/cli/readme.md create mode 100644 src/core/readme.md diff --git a/src/build/readme.md b/src/build/readme.md new file mode 100644 index 0000000..2d30e68 --- /dev/null +++ b/src/build/readme.md @@ -0,0 +1,19 @@ +### [Build.go](Build.go) + +The `Build` type defines all the information needed to start building an executable file. +The name of the executable will be equal to the name of the build directory. + +`Run` starts the build which will scan all `.q` source files in the build directory. +Every source file is scanned in its own goroutine for performance reasons. +Parallelization here is possible because the order of files in a directory is not significant. + +The main thread is meanwhile waiting for new function objects to arrive from the scanners. +Once a function has arrived, it will be stored for compilation later. +We need to wait with the compilation step until we have enough information about all identifiers from the scan. + +Then all the functions that were scanned will be compiled in parallel. +We create a separate goroutine for each function compilation. +Each function will then be translated to generic assembler instructions. + +All the functions that are required to run the program will be added to the final assembler. +The final assembler resolves label addresses, optimizes the performance and generates the specific machine code from the generic instruction set. \ No newline at end of file diff --git a/src/cli/readme.md b/src/cli/readme.md new file mode 100644 index 0000000..262701d --- /dev/null +++ b/src/cli/readme.md @@ -0,0 +1,34 @@ +### [Main.go](Main.go) + +Entry point. + +The command line interface expects a command like `build` as the first argument. +Commands are implemented as functions in the `cli` directory. +Each command has its own set of parameters. + +### [Build.go](Build.go) + +The build command creates a new `Build` instance with the given directory and calls the `Run` method. + +If no directory is specified, it will use the current directory. + +If the `--dry` flag is specified, it will perform all tasks except the final write to disk. +This flag should be used in most tests and benchmarks to avoid needless disk writes. + +```shell +q build +q build examples/hello +q build examples/hello --dry +``` + +Adding the `-a` or `--assembler` flag shows the generated assembly instructions: + +```shell +q build examples/hello -a +``` + +Adding the `-v` or `--verbose` flag shows verbose compiler information: + +```shell +q build examples/hello -v +``` \ No newline at end of file diff --git a/src/core/readme.md b/src/core/readme.md new file mode 100644 index 0000000..da1e2c2 --- /dev/null +++ b/src/core/readme.md @@ -0,0 +1,5 @@ +## [Function.go](Function.go) + +This is the "heart" of the compiler. +Each function runs [Compile](Compile.go) which organizes the source code into an abstract syntax tree that is then compiled via [CompileAST](CompileAST.go). +You can think of AST nodes as the individual statements in your source code. \ No newline at end of file diff --git a/src/readme.md b/src/readme.md index 59c7d81..6483efc 100644 --- a/src/readme.md +++ b/src/readme.md @@ -25,74 +25,3 @@ - [token](token) - Converts a file to tokens with the `Tokenize` function - [types](types) - Type system (w.i.p.) - [x64](x64) - x86-64 implementation - -# Documentation - -## [cli/Main.go](cli/Main.go) - -Entry point. - -The command line interface expects a command like `build` as the first argument. -Commands are implemented as functions in the [cli](cli) directory. -Each command has its own set of parameters. - -## [cli/Build.go](cli/Build.go) - -The build command creates a new `Build` instance with the given directory and calls the `Run` method. - -If no directory is specified, it will use the current directory. - -If the `--dry` flag is specified, it will perform all tasks except the final write to disk. -This flag should be used in most tests and benchmarks to avoid needless disk writes. - -```shell -q build -q build examples/hello -q build examples/hello --dry -``` - -Adding the `-a` or `--assembler` flag shows the generated assembly instructions: - -```shell -q build examples/hello -a -``` - -Adding the `-v` or `--verbose` flag shows verbose compiler information: - -```shell -q build examples/hello -v -``` - -## [build/Build.go](build/Build.go) - -The `Build` type defines all the information needed to start building an executable file. -The name of the executable will be equal to the name of the build directory. - -`Run` starts the build which will scan all `.q` source files in the build directory. -Every source file is scanned in its own goroutine for performance reasons. -Parallelization here is possible because the order of files in a directory is not significant. - -The main thread is meanwhile waiting for new function objects to arrive from the scanners. -Once a function has arrived, it will be stored for compilation later. -We need to wait with the compilation step until we have enough information about all identifiers from the scan. - -Then all the functions that were scanned will be compiled in parallel. -We create a separate goroutine for each function compilation. -Each function will then be translated to generic assembler instructions. - -All the functions that are required to run the program will be added to the final assembler. -The final assembler resolves label addresses, optimizes the performance and generates the specific machine code from the generic instruction set. - -## [core/Function.go](core/Function.go) - -This is the "heart" of the compiler. -Each function runs `f.Compile` which organizes the source code into an abstract syntax tree that is then compiled via `f.CompileAST`. -You can think of AST nodes as the individual statements in your source code. - -## [ast/Parse.go](ast/Parse.go) - -This is what generates the AST from tokens. - -## [expression/Parse.go](expression/Parse.go) - -This is what generates expressions from tokens. \ No newline at end of file From 92e4175bbd441181a38648532b73dcdbfe18ea5e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 28 Jan 2025 14:04:30 +0100 Subject: [PATCH 0584/1012] Improved documentation --- src/build/readme.md | 19 ++++++++++++ src/cli/readme.md | 34 ++++++++++++++++++++++ src/core/readme.md | 5 ++++ src/readme.md | 71 --------------------------------------------- 4 files changed, 58 insertions(+), 71 deletions(-) create mode 100644 src/build/readme.md create mode 100644 src/cli/readme.md create mode 100644 src/core/readme.md diff --git a/src/build/readme.md b/src/build/readme.md new file mode 100644 index 0000000..2d30e68 --- /dev/null +++ b/src/build/readme.md @@ -0,0 +1,19 @@ +### [Build.go](Build.go) + +The `Build` type defines all the information needed to start building an executable file. +The name of the executable will be equal to the name of the build directory. + +`Run` starts the build which will scan all `.q` source files in the build directory. +Every source file is scanned in its own goroutine for performance reasons. +Parallelization here is possible because the order of files in a directory is not significant. + +The main thread is meanwhile waiting for new function objects to arrive from the scanners. +Once a function has arrived, it will be stored for compilation later. +We need to wait with the compilation step until we have enough information about all identifiers from the scan. + +Then all the functions that were scanned will be compiled in parallel. +We create a separate goroutine for each function compilation. +Each function will then be translated to generic assembler instructions. + +All the functions that are required to run the program will be added to the final assembler. +The final assembler resolves label addresses, optimizes the performance and generates the specific machine code from the generic instruction set. \ No newline at end of file diff --git a/src/cli/readme.md b/src/cli/readme.md new file mode 100644 index 0000000..262701d --- /dev/null +++ b/src/cli/readme.md @@ -0,0 +1,34 @@ +### [Main.go](Main.go) + +Entry point. + +The command line interface expects a command like `build` as the first argument. +Commands are implemented as functions in the `cli` directory. +Each command has its own set of parameters. + +### [Build.go](Build.go) + +The build command creates a new `Build` instance with the given directory and calls the `Run` method. + +If no directory is specified, it will use the current directory. + +If the `--dry` flag is specified, it will perform all tasks except the final write to disk. +This flag should be used in most tests and benchmarks to avoid needless disk writes. + +```shell +q build +q build examples/hello +q build examples/hello --dry +``` + +Adding the `-a` or `--assembler` flag shows the generated assembly instructions: + +```shell +q build examples/hello -a +``` + +Adding the `-v` or `--verbose` flag shows verbose compiler information: + +```shell +q build examples/hello -v +``` \ No newline at end of file diff --git a/src/core/readme.md b/src/core/readme.md new file mode 100644 index 0000000..da1e2c2 --- /dev/null +++ b/src/core/readme.md @@ -0,0 +1,5 @@ +## [Function.go](Function.go) + +This is the "heart" of the compiler. +Each function runs [Compile](Compile.go) which organizes the source code into an abstract syntax tree that is then compiled via [CompileAST](CompileAST.go). +You can think of AST nodes as the individual statements in your source code. \ No newline at end of file diff --git a/src/readme.md b/src/readme.md index 59c7d81..6483efc 100644 --- a/src/readme.md +++ b/src/readme.md @@ -25,74 +25,3 @@ - [token](token) - Converts a file to tokens with the `Tokenize` function - [types](types) - Type system (w.i.p.) - [x64](x64) - x86-64 implementation - -# Documentation - -## [cli/Main.go](cli/Main.go) - -Entry point. - -The command line interface expects a command like `build` as the first argument. -Commands are implemented as functions in the [cli](cli) directory. -Each command has its own set of parameters. - -## [cli/Build.go](cli/Build.go) - -The build command creates a new `Build` instance with the given directory and calls the `Run` method. - -If no directory is specified, it will use the current directory. - -If the `--dry` flag is specified, it will perform all tasks except the final write to disk. -This flag should be used in most tests and benchmarks to avoid needless disk writes. - -```shell -q build -q build examples/hello -q build examples/hello --dry -``` - -Adding the `-a` or `--assembler` flag shows the generated assembly instructions: - -```shell -q build examples/hello -a -``` - -Adding the `-v` or `--verbose` flag shows verbose compiler information: - -```shell -q build examples/hello -v -``` - -## [build/Build.go](build/Build.go) - -The `Build` type defines all the information needed to start building an executable file. -The name of the executable will be equal to the name of the build directory. - -`Run` starts the build which will scan all `.q` source files in the build directory. -Every source file is scanned in its own goroutine for performance reasons. -Parallelization here is possible because the order of files in a directory is not significant. - -The main thread is meanwhile waiting for new function objects to arrive from the scanners. -Once a function has arrived, it will be stored for compilation later. -We need to wait with the compilation step until we have enough information about all identifiers from the scan. - -Then all the functions that were scanned will be compiled in parallel. -We create a separate goroutine for each function compilation. -Each function will then be translated to generic assembler instructions. - -All the functions that are required to run the program will be added to the final assembler. -The final assembler resolves label addresses, optimizes the performance and generates the specific machine code from the generic instruction set. - -## [core/Function.go](core/Function.go) - -This is the "heart" of the compiler. -Each function runs `f.Compile` which organizes the source code into an abstract syntax tree that is then compiled via `f.CompileAST`. -You can think of AST nodes as the individual statements in your source code. - -## [ast/Parse.go](ast/Parse.go) - -This is what generates the AST from tokens. - -## [expression/Parse.go](expression/Parse.go) - -This is what generates expressions from tokens. \ No newline at end of file From 47f2bc7947d29606b6b6b7d19865936d10fcdf0b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 29 Jan 2025 15:22:36 +0100 Subject: [PATCH 0585/1012] Simplified memory access encoding --- src/x64/StoreDynamic.go | 107 +-------------------------------- src/x64/memoryAccess.go | 8 +-- src/x64/memoryAccessDynamic.go | 63 +++++++++++++++++++ 3 files changed, 69 insertions(+), 109 deletions(-) create mode 100644 src/x64/memoryAccessDynamic.go diff --git a/src/x64/StoreDynamic.go b/src/x64/StoreDynamic.go index 0fdeb9c..3a214c9 100644 --- a/src/x64/StoreDynamic.go +++ b/src/x64/StoreDynamic.go @@ -8,55 +8,7 @@ import ( // StoreDynamicNumber stores a number into the memory address at `destination` with a register offset. func StoreDynamicNumber(code []byte, destination cpu.Register, offset cpu.Register, length byte, number int) []byte { - var ( - w = byte(0) - r = byte(0) - x = byte(0) - b = byte(0) - opCode = byte(0xC7) - mod = AddressMemory - ) - - if length == 1 { - opCode = 0xC6 - } - - if offset == RSP { - tmp := offset - offset = destination - destination = tmp - } - - if length == 8 { - w = 1 - } - - if offset > 0b111 { - x = 1 - offset &= 0b111 - } - - if destination > 0b111 { - b = 1 - destination &= 0b111 - } - - if destination == RBP || destination == R13 { - mod = AddressMemoryOffset8 - } - - if length == 2 { - code = append(code, 0x66) - } - - code = append(code, REX(w, r, x, b)) - code = append(code, opCode) - code = append(code, ModRM(mod, 0b000, 0b100)) - code = append(code, SIB(Scale1, byte(offset), byte(destination))) - - if mod == AddressMemoryOffset8 { - code = append(code, 0x00) - } + code = memoryAccessDynamic(code, 0xC6, 0xC7, destination, offset, length, 0b000) switch length { case 8, 4: @@ -71,60 +23,5 @@ func StoreDynamicNumber(code []byte, destination cpu.Register, offset cpu.Regist // StoreDynamicRegister stores the contents of the `source` register into the memory address at `destination` with a register offset. func StoreDynamicRegister(code []byte, destination cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { - var ( - w = byte(0) - r = byte(0) - x = byte(0) - b = byte(0) - opCode = byte(0x89) - mod = AddressMemory - ) - - if length == 1 { - opCode = 0x88 - } - - if offset == RSP { - tmp := offset - offset = destination - destination = tmp - } - - if length == 8 { - w = 1 - } - - if source > 0b111 { - r = 1 - source &= 0b111 - } - - if offset > 0b111 { - x = 1 - offset &= 0b111 - } - - if destination > 0b111 { - b = 1 - destination &= 0b111 - } - - if destination == RBP || destination == R13 { - mod = AddressMemoryOffset8 - } - - if length == 2 { - code = append(code, 0x66) - } - - code = append(code, REX(w, r, x, b)) - code = append(code, opCode) - code = append(code, ModRM(mod, byte(source), 0b100)) - code = append(code, SIB(Scale1, byte(offset), byte(destination))) - - if mod == AddressMemoryOffset8 { - code = append(code, 0x00) - } - - return code + return memoryAccessDynamic(code, 0x88, 0x89, destination, offset, length, source) } diff --git a/src/x64/memoryAccess.go b/src/x64/memoryAccess.go index 1ff2bca..c1c658c 100644 --- a/src/x64/memoryAccess.go +++ b/src/x64/memoryAccess.go @@ -4,10 +4,6 @@ import "git.akyoto.dev/cli/q/src/cpu" // memoryAccess encodes a memory access. func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { - if numBytes == 2 { - code = append(code, 0x66) - } - opCode := opCode32 if numBytes == 1 { @@ -20,6 +16,10 @@ func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Registe mod = AddressMemoryOffset8 } + if numBytes == 2 { + code = append(code, 0x66) + } + code = encode(code, mod, source, register, numBytes, opCode) if register == RSP || register == R12 { diff --git a/src/x64/memoryAccessDynamic.go b/src/x64/memoryAccessDynamic.go new file mode 100644 index 0000000..8400351 --- /dev/null +++ b/src/x64/memoryAccessDynamic.go @@ -0,0 +1,63 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/cpu" + +// memoryAccessDynamic encodes a memory access using the value of a register as an offset. +func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, destination cpu.Register, offset cpu.Register, numBytes byte, source cpu.Register) []byte { + var ( + w = byte(0) + r = byte(0) + x = byte(0) + b = byte(0) + opCode = opCode32 + mod = AddressMemory + ) + + if numBytes == 1 { + opCode = opCode8 + } + + if offset == RSP { + tmp := offset + offset = destination + destination = tmp + } + + if numBytes == 8 { + w = 1 + } + + if source > 0b111 { + r = 1 + source &= 0b111 + } + + if offset > 0b111 { + x = 1 + offset &= 0b111 + } + + if destination > 0b111 { + b = 1 + destination &= 0b111 + } + + if destination == RBP || destination == R13 { + mod = AddressMemoryOffset8 + } + + if numBytes == 2 { + code = append(code, 0x66) + } + + code = append(code, REX(w, r, x, b)) + code = append(code, opCode) + code = append(code, ModRM(mod, byte(source), 0b100)) + code = append(code, SIB(Scale1, byte(offset), byte(destination))) + + if mod == AddressMemoryOffset8 { + code = append(code, 0x00) + } + + return code +} From a2d80b0c21d22e3b3cc714a48b718204a6ff3f1e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 29 Jan 2025 15:22:36 +0100 Subject: [PATCH 0586/1012] Simplified memory access encoding --- src/x64/StoreDynamic.go | 107 +-------------------------------- src/x64/memoryAccess.go | 8 +-- src/x64/memoryAccessDynamic.go | 63 +++++++++++++++++++ 3 files changed, 69 insertions(+), 109 deletions(-) create mode 100644 src/x64/memoryAccessDynamic.go diff --git a/src/x64/StoreDynamic.go b/src/x64/StoreDynamic.go index 0fdeb9c..3a214c9 100644 --- a/src/x64/StoreDynamic.go +++ b/src/x64/StoreDynamic.go @@ -8,55 +8,7 @@ import ( // StoreDynamicNumber stores a number into the memory address at `destination` with a register offset. func StoreDynamicNumber(code []byte, destination cpu.Register, offset cpu.Register, length byte, number int) []byte { - var ( - w = byte(0) - r = byte(0) - x = byte(0) - b = byte(0) - opCode = byte(0xC7) - mod = AddressMemory - ) - - if length == 1 { - opCode = 0xC6 - } - - if offset == RSP { - tmp := offset - offset = destination - destination = tmp - } - - if length == 8 { - w = 1 - } - - if offset > 0b111 { - x = 1 - offset &= 0b111 - } - - if destination > 0b111 { - b = 1 - destination &= 0b111 - } - - if destination == RBP || destination == R13 { - mod = AddressMemoryOffset8 - } - - if length == 2 { - code = append(code, 0x66) - } - - code = append(code, REX(w, r, x, b)) - code = append(code, opCode) - code = append(code, ModRM(mod, 0b000, 0b100)) - code = append(code, SIB(Scale1, byte(offset), byte(destination))) - - if mod == AddressMemoryOffset8 { - code = append(code, 0x00) - } + code = memoryAccessDynamic(code, 0xC6, 0xC7, destination, offset, length, 0b000) switch length { case 8, 4: @@ -71,60 +23,5 @@ func StoreDynamicNumber(code []byte, destination cpu.Register, offset cpu.Regist // StoreDynamicRegister stores the contents of the `source` register into the memory address at `destination` with a register offset. func StoreDynamicRegister(code []byte, destination cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { - var ( - w = byte(0) - r = byte(0) - x = byte(0) - b = byte(0) - opCode = byte(0x89) - mod = AddressMemory - ) - - if length == 1 { - opCode = 0x88 - } - - if offset == RSP { - tmp := offset - offset = destination - destination = tmp - } - - if length == 8 { - w = 1 - } - - if source > 0b111 { - r = 1 - source &= 0b111 - } - - if offset > 0b111 { - x = 1 - offset &= 0b111 - } - - if destination > 0b111 { - b = 1 - destination &= 0b111 - } - - if destination == RBP || destination == R13 { - mod = AddressMemoryOffset8 - } - - if length == 2 { - code = append(code, 0x66) - } - - code = append(code, REX(w, r, x, b)) - code = append(code, opCode) - code = append(code, ModRM(mod, byte(source), 0b100)) - code = append(code, SIB(Scale1, byte(offset), byte(destination))) - - if mod == AddressMemoryOffset8 { - code = append(code, 0x00) - } - - return code + return memoryAccessDynamic(code, 0x88, 0x89, destination, offset, length, source) } diff --git a/src/x64/memoryAccess.go b/src/x64/memoryAccess.go index 1ff2bca..c1c658c 100644 --- a/src/x64/memoryAccess.go +++ b/src/x64/memoryAccess.go @@ -4,10 +4,6 @@ import "git.akyoto.dev/cli/q/src/cpu" // memoryAccess encodes a memory access. func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { - if numBytes == 2 { - code = append(code, 0x66) - } - opCode := opCode32 if numBytes == 1 { @@ -20,6 +16,10 @@ func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Registe mod = AddressMemoryOffset8 } + if numBytes == 2 { + code = append(code, 0x66) + } + code = encode(code, mod, source, register, numBytes, opCode) if register == RSP || register == R12 { diff --git a/src/x64/memoryAccessDynamic.go b/src/x64/memoryAccessDynamic.go new file mode 100644 index 0000000..8400351 --- /dev/null +++ b/src/x64/memoryAccessDynamic.go @@ -0,0 +1,63 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/cpu" + +// memoryAccessDynamic encodes a memory access using the value of a register as an offset. +func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, destination cpu.Register, offset cpu.Register, numBytes byte, source cpu.Register) []byte { + var ( + w = byte(0) + r = byte(0) + x = byte(0) + b = byte(0) + opCode = opCode32 + mod = AddressMemory + ) + + if numBytes == 1 { + opCode = opCode8 + } + + if offset == RSP { + tmp := offset + offset = destination + destination = tmp + } + + if numBytes == 8 { + w = 1 + } + + if source > 0b111 { + r = 1 + source &= 0b111 + } + + if offset > 0b111 { + x = 1 + offset &= 0b111 + } + + if destination > 0b111 { + b = 1 + destination &= 0b111 + } + + if destination == RBP || destination == R13 { + mod = AddressMemoryOffset8 + } + + if numBytes == 2 { + code = append(code, 0x66) + } + + code = append(code, REX(w, r, x, b)) + code = append(code, opCode) + code = append(code, ModRM(mod, byte(source), 0b100)) + code = append(code, SIB(Scale1, byte(offset), byte(destination))) + + if mod == AddressMemoryOffset8 { + code = append(code, 0x00) + } + + return code +} From 860795c1ece4559843ac76b21f5de79924ae9a93 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 30 Jan 2025 16:33:20 +0100 Subject: [PATCH 0587/1012] Implemented basic support for function pointers --- examples/thread/thread.q | 23 ++++++++++++++++ lib/sys/sys_linux.q | 4 +++ src/asm/Finalize.go | 34 ++++++++++++++++++++++++ src/asm/MemoryLabel.go | 27 +++++++++++++++++++ src/core/CompileCall.go | 4 +++ src/core/CompileMemoryStore.go | 33 +++++++++++++++++++++++ src/core/ExpressionToMemory.go | 48 +++++++++++++++++++++++----------- src/register/MemoryLabel.go | 8 ++++++ 8 files changed, 166 insertions(+), 15 deletions(-) create mode 100644 examples/thread/thread.q create mode 100644 src/asm/MemoryLabel.go create mode 100644 src/core/CompileMemoryStore.go create mode 100644 src/register/MemoryLabel.go diff --git a/examples/thread/thread.q b/examples/thread/thread.q new file mode 100644 index 0000000..09f0da6 --- /dev/null +++ b/examples/thread/thread.q @@ -0,0 +1,23 @@ +import mem +import sys + +main() { + start() + start() + start() + thread() +} + +start() { + size := 4096 + stack := mem.alloc(size) + pointer := stack + size - 8 + store(pointer, 8, thread) + sys.clone(0x100 | 0x200 | 0x400 | 0x800 | 0x8000 | 0x10000 | 0x80000000, pointer) +} + +thread() { + sys.write(1, "[ ] start\n", 10) + sys.write(1, "[x] end\n", 8) + sys.exit(0) +} \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q index cc99c0c..73fcb76 100644 --- a/lib/sys/sys_linux.q +++ b/lib/sys/sys_linux.q @@ -22,6 +22,10 @@ munmap(address Pointer, length Int) -> Int { return syscall(11, address, length) } +nanosleep(requested Pointer, remaining Pointer) -> Int { + return syscall(35, requested, remaining) +} + clone(flags Int, stack Pointer) -> Int { return syscall(56, flags, stack) } diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 0e3b3e2..0f92006 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -26,6 +26,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { dataLabels map[string]Address codePointers []*Pointer dataPointers []*Pointer + funcPointers []*Pointer dllPointers []*Pointer ) @@ -293,6 +294,33 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { } else { code = x64.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) } + case *MemoryLabel: + start := len(code) + + if operands.Address.OffsetRegister == math.MaxUint8 { + code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, 0b00_00_00_00) + } else { + code = x64.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, 0b00_00_00_00) + } + + size := 4 + opSize := len(code) - size - start + memLabel := x.Data.(*MemoryLabel) + + funcPointers = append(funcPointers, &Pointer{ + Position: Address(len(code) - size), + OpSize: uint8(opSize), + Size: uint8(size), + Resolve: func() Address { + destination, exists := codeLabels[memLabel.Label] + + if !exists { + panic("unknown label") + } + + return Address(destination) + }, + }) case *MemoryRegister: if operands.Address.OffsetRegister == math.MaxUint8 { code = x64.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) @@ -397,6 +425,12 @@ restart: dataStart, _ := fs.Align(codeStart+Address(len(code)), config.Align) data, dataLabels = a.Data.Finalize() + for _, pointer := range funcPointers { + address := config.BaseAddress + Address(codeStart) + pointer.Resolve() + slice := code[pointer.Position : pointer.Position+4] + binary.LittleEndian.PutUint32(slice, uint32(address)) + } + for _, pointer := range dataPointers { address := config.BaseAddress + Address(dataStart) + pointer.Resolve() slice := code[pointer.Position : pointer.Position+4] diff --git a/src/asm/MemoryLabel.go b/src/asm/MemoryLabel.go new file mode 100644 index 0000000..96850a9 --- /dev/null +++ b/src/asm/MemoryLabel.go @@ -0,0 +1,27 @@ +package asm + +import ( + "fmt" +) + +// MemoryLabel operates with a memory address and a number. +type MemoryLabel struct { + Address Memory + Label string +} + +// String returns a human readable version. +func (data *MemoryLabel) String() string { + return fmt.Sprintf("%dB [%s+%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Label) +} + +// MemoryLabel adds an instruction with a memory address and a label. +func (a *Assembler) MemoryLabel(mnemonic Mnemonic, address Memory, label string) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &MemoryLabel{ + Address: address, + Label: label, + }, + }) +} diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 8b62b0a..a723240 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -29,6 +29,10 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { if name == "syscall" { return nil, f.CompileSyscall(root) } + + if name == "store" { + return nil, f.CompileMemoryStore(root) + } } else { pkg = nameNode.Children[0].Token.Text(f.File.Bytes) name = nameNode.Children[1].Token.Text(f.File.Bytes) diff --git a/src/core/CompileMemoryStore.go b/src/core/CompileMemoryStore.go new file mode 100644 index 0000000..b0dd120 --- /dev/null +++ b/src/core/CompileMemoryStore.go @@ -0,0 +1,33 @@ +package core + +import ( + "math" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" +) + +// CompileMemoryStore ... +func (f *Function) CompileMemoryStore(root *expression.Expression) error { + parameters := root.Children[1:] + name := parameters[0].Token.Text(f.File.Bytes) + numBytes, _ := f.Number(parameters[1].Token) + value := parameters[2] + variable := f.VariableByName(name) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, parameters[0].Token.Position) + } + + defer f.UseVariable(variable) + + memory := asm.Memory{ + Base: variable.Register, + OffsetRegister: math.MaxUint8, + Length: byte(numBytes), + } + + _, err := f.ExpressionToMemory(value, memory) + return err +} diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index 9077a1e..dfeb7da 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -1,35 +1,53 @@ package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/sizeof" + "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/types" ) // ExpressionToMemory puts the result of an expression into the specified memory address. func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (*types.Type, error) { - if node.IsLeaf() && node.Token.IsNumeric() { - number, err := f.Number(node.Token) - - if err != nil { - return nil, err + if node.IsLeaf() { + if node.Token.Kind == token.Identifier { + f.MemoryLabel(asm.STORE, memory, fmt.Sprintf("%s.%s", f.Package, node.Token.Text(f.File.Bytes))) + return types.Pointer, nil } - size := byte(sizeof.Signed(int64(number))) + if node.Token.IsNumeric() { + number, err := f.Number(node.Token) - if size != memory.Length { - return nil, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) + if err != nil { + return nil, err + } + + size := byte(sizeof.Signed(int64(number))) + + if size != memory.Length { + return nil, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) + } + + f.MemoryNumber(asm.STORE, memory, number) + return types.Int, nil } - - f.MemoryNumber(asm.STORE, memory, number) - return types.Int, nil } - tmp := f.NewRegister() - defer f.FreeRegister(tmp) - typ, err := f.ExpressionToRegister(node, tmp) - f.MemoryRegister(asm.STORE, memory, tmp) + typ, register, isTemporary, err := f.Evaluate(node) + + if err != nil { + return nil, err + } + + f.MemoryRegister(asm.STORE, memory, register) + + if isTemporary { + f.FreeRegister(register) + } + return typ, err } diff --git a/src/register/MemoryLabel.go b/src/register/MemoryLabel.go new file mode 100644 index 0000000..98deda9 --- /dev/null +++ b/src/register/MemoryLabel.go @@ -0,0 +1,8 @@ +package register + +import "git.akyoto.dev/cli/q/src/asm" + +func (f *Machine) MemoryLabel(mnemonic asm.Mnemonic, a asm.Memory, b string) { + f.Assembler.MemoryLabel(mnemonic, a, b) + f.postInstruction() +} From 162824ec1c7912fe937f72934b4626830edc9ee3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 30 Jan 2025 16:33:20 +0100 Subject: [PATCH 0588/1012] Implemented basic support for function pointers --- examples/thread/thread.q | 23 ++++++++++++++++ lib/sys/sys_linux.q | 4 +++ src/asm/Finalize.go | 34 ++++++++++++++++++++++++ src/asm/MemoryLabel.go | 27 +++++++++++++++++++ src/core/CompileCall.go | 4 +++ src/core/CompileMemoryStore.go | 33 +++++++++++++++++++++++ src/core/ExpressionToMemory.go | 48 +++++++++++++++++++++++----------- src/register/MemoryLabel.go | 8 ++++++ 8 files changed, 166 insertions(+), 15 deletions(-) create mode 100644 examples/thread/thread.q create mode 100644 src/asm/MemoryLabel.go create mode 100644 src/core/CompileMemoryStore.go create mode 100644 src/register/MemoryLabel.go diff --git a/examples/thread/thread.q b/examples/thread/thread.q new file mode 100644 index 0000000..09f0da6 --- /dev/null +++ b/examples/thread/thread.q @@ -0,0 +1,23 @@ +import mem +import sys + +main() { + start() + start() + start() + thread() +} + +start() { + size := 4096 + stack := mem.alloc(size) + pointer := stack + size - 8 + store(pointer, 8, thread) + sys.clone(0x100 | 0x200 | 0x400 | 0x800 | 0x8000 | 0x10000 | 0x80000000, pointer) +} + +thread() { + sys.write(1, "[ ] start\n", 10) + sys.write(1, "[x] end\n", 8) + sys.exit(0) +} \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q index cc99c0c..73fcb76 100644 --- a/lib/sys/sys_linux.q +++ b/lib/sys/sys_linux.q @@ -22,6 +22,10 @@ munmap(address Pointer, length Int) -> Int { return syscall(11, address, length) } +nanosleep(requested Pointer, remaining Pointer) -> Int { + return syscall(35, requested, remaining) +} + clone(flags Int, stack Pointer) -> Int { return syscall(56, flags, stack) } diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 0e3b3e2..0f92006 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -26,6 +26,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { dataLabels map[string]Address codePointers []*Pointer dataPointers []*Pointer + funcPointers []*Pointer dllPointers []*Pointer ) @@ -293,6 +294,33 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { } else { code = x64.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) } + case *MemoryLabel: + start := len(code) + + if operands.Address.OffsetRegister == math.MaxUint8 { + code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, 0b00_00_00_00) + } else { + code = x64.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, 0b00_00_00_00) + } + + size := 4 + opSize := len(code) - size - start + memLabel := x.Data.(*MemoryLabel) + + funcPointers = append(funcPointers, &Pointer{ + Position: Address(len(code) - size), + OpSize: uint8(opSize), + Size: uint8(size), + Resolve: func() Address { + destination, exists := codeLabels[memLabel.Label] + + if !exists { + panic("unknown label") + } + + return Address(destination) + }, + }) case *MemoryRegister: if operands.Address.OffsetRegister == math.MaxUint8 { code = x64.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) @@ -397,6 +425,12 @@ restart: dataStart, _ := fs.Align(codeStart+Address(len(code)), config.Align) data, dataLabels = a.Data.Finalize() + for _, pointer := range funcPointers { + address := config.BaseAddress + Address(codeStart) + pointer.Resolve() + slice := code[pointer.Position : pointer.Position+4] + binary.LittleEndian.PutUint32(slice, uint32(address)) + } + for _, pointer := range dataPointers { address := config.BaseAddress + Address(dataStart) + pointer.Resolve() slice := code[pointer.Position : pointer.Position+4] diff --git a/src/asm/MemoryLabel.go b/src/asm/MemoryLabel.go new file mode 100644 index 0000000..96850a9 --- /dev/null +++ b/src/asm/MemoryLabel.go @@ -0,0 +1,27 @@ +package asm + +import ( + "fmt" +) + +// MemoryLabel operates with a memory address and a number. +type MemoryLabel struct { + Address Memory + Label string +} + +// String returns a human readable version. +func (data *MemoryLabel) String() string { + return fmt.Sprintf("%dB [%s+%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Label) +} + +// MemoryLabel adds an instruction with a memory address and a label. +func (a *Assembler) MemoryLabel(mnemonic Mnemonic, address Memory, label string) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &MemoryLabel{ + Address: address, + Label: label, + }, + }) +} diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 8b62b0a..a723240 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -29,6 +29,10 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { if name == "syscall" { return nil, f.CompileSyscall(root) } + + if name == "store" { + return nil, f.CompileMemoryStore(root) + } } else { pkg = nameNode.Children[0].Token.Text(f.File.Bytes) name = nameNode.Children[1].Token.Text(f.File.Bytes) diff --git a/src/core/CompileMemoryStore.go b/src/core/CompileMemoryStore.go new file mode 100644 index 0000000..b0dd120 --- /dev/null +++ b/src/core/CompileMemoryStore.go @@ -0,0 +1,33 @@ +package core + +import ( + "math" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" +) + +// CompileMemoryStore ... +func (f *Function) CompileMemoryStore(root *expression.Expression) error { + parameters := root.Children[1:] + name := parameters[0].Token.Text(f.File.Bytes) + numBytes, _ := f.Number(parameters[1].Token) + value := parameters[2] + variable := f.VariableByName(name) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, parameters[0].Token.Position) + } + + defer f.UseVariable(variable) + + memory := asm.Memory{ + Base: variable.Register, + OffsetRegister: math.MaxUint8, + Length: byte(numBytes), + } + + _, err := f.ExpressionToMemory(value, memory) + return err +} diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index 9077a1e..dfeb7da 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -1,35 +1,53 @@ package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/sizeof" + "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/types" ) // ExpressionToMemory puts the result of an expression into the specified memory address. func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (*types.Type, error) { - if node.IsLeaf() && node.Token.IsNumeric() { - number, err := f.Number(node.Token) - - if err != nil { - return nil, err + if node.IsLeaf() { + if node.Token.Kind == token.Identifier { + f.MemoryLabel(asm.STORE, memory, fmt.Sprintf("%s.%s", f.Package, node.Token.Text(f.File.Bytes))) + return types.Pointer, nil } - size := byte(sizeof.Signed(int64(number))) + if node.Token.IsNumeric() { + number, err := f.Number(node.Token) - if size != memory.Length { - return nil, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) + if err != nil { + return nil, err + } + + size := byte(sizeof.Signed(int64(number))) + + if size != memory.Length { + return nil, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) + } + + f.MemoryNumber(asm.STORE, memory, number) + return types.Int, nil } - - f.MemoryNumber(asm.STORE, memory, number) - return types.Int, nil } - tmp := f.NewRegister() - defer f.FreeRegister(tmp) - typ, err := f.ExpressionToRegister(node, tmp) - f.MemoryRegister(asm.STORE, memory, tmp) + typ, register, isTemporary, err := f.Evaluate(node) + + if err != nil { + return nil, err + } + + f.MemoryRegister(asm.STORE, memory, register) + + if isTemporary { + f.FreeRegister(register) + } + return typ, err } diff --git a/src/register/MemoryLabel.go b/src/register/MemoryLabel.go new file mode 100644 index 0000000..98deda9 --- /dev/null +++ b/src/register/MemoryLabel.go @@ -0,0 +1,8 @@ +package register + +import "git.akyoto.dev/cli/q/src/asm" + +func (f *Machine) MemoryLabel(mnemonic asm.Mnemonic, a asm.Memory, b string) { + f.Assembler.MemoryLabel(mnemonic, a, b) + f.postInstruction() +} From 977028f8abb7887a919057f660a8d90d61022387 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 30 Jan 2025 22:23:38 +0100 Subject: [PATCH 0589/1012] Improved code style --- src/asm/MemoryLabel.go | 2 +- src/expression/Expression_test.go | 7 ++----- src/expression/bench_test.go | 2 +- src/macho/MachO.go | 4 ++-- src/pe/Constants.go | 2 +- src/pe/EXE.go | 6 +++--- src/token/Count.go | 2 +- src/token/List_test.go | 4 ++-- src/token/bench_test.go | 2 +- src/x64/memoryAccessDynamic.go | 4 +--- tests/examples_test.go | 2 +- tests/programs_test.go | 2 +- 12 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/asm/MemoryLabel.go b/src/asm/MemoryLabel.go index 96850a9..0d4ebf7 100644 --- a/src/asm/MemoryLabel.go +++ b/src/asm/MemoryLabel.go @@ -6,8 +6,8 @@ import ( // MemoryLabel operates with a memory address and a number. type MemoryLabel struct { - Address Memory Label string + Address Memory } // String returns a human readable version. diff --git a/src/expression/Expression_test.go b/src/expression/Expression_test.go index 1e03ba8..64a09ba 100644 --- a/src/expression/Expression_test.go +++ b/src/expression/Expression_test.go @@ -1,7 +1,7 @@ package expression_test import ( - "fmt" + "errors" "testing" "git.akyoto.dev/cli/q/src/expression" @@ -110,14 +110,11 @@ func TestParse(t *testing.T) { } for _, test := range tests { - test := test - t.Run(test.Name, func(t *testing.T) { src := []byte(test.Expression) tokens := token.Tokenize(src) expr := expression.Parse(tokens) defer expr.Reset() - assert.NotNil(t, expr) assert.Equal(t, expr.String(src), test.Result) }) @@ -150,7 +147,7 @@ func TestEachLeaf(t *testing.T) { assert.DeepEqual(t, leaves, []string{"1", "2", "3", "4", "5", "6", "7", "8"}) err = expr.EachLeaf(func(leaf *expression.Expression) error { - return fmt.Errorf("error") + return errors.New("error") }) assert.NotNil(t, err) diff --git a/src/expression/bench_test.go b/src/expression/bench_test.go index c6266c3..ec63729 100644 --- a/src/expression/bench_test.go +++ b/src/expression/bench_test.go @@ -11,7 +11,7 @@ func BenchmarkExpression(b *testing.B) { src := []byte("(1+2-3*4)+(5*6-7+8)") tokens := token.Tokenize(src) - for i := 0; i < b.N; i++ { + for range b.N { expression.Parse(tokens) } } diff --git a/src/macho/MachO.go b/src/macho/MachO.go index 21b5b19..c463694 100644 --- a/src/macho/MachO.go +++ b/src/macho/MachO.go @@ -117,8 +117,8 @@ func Write(writer io.Writer, code []byte, data []byte) { binary.Write(writer, binary.LittleEndian, &m.DataHeader) binary.Write(writer, binary.LittleEndian, &m.UnixThread) - writer.Write(bytes.Repeat([]byte{0x00}, int(codePadding))) + writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) writer.Write(code) - writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) + writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) writer.Write(data) } diff --git a/src/pe/Constants.go b/src/pe/Constants.go index c77bea7..00b470e 100644 --- a/src/pe/Constants.go +++ b/src/pe/Constants.go @@ -31,7 +31,7 @@ const ( IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002 IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004 IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008 - IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010 + IMAGE_FILE_AGGRESSIVE_WS_TRIM = 0x0010 IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020 IMAGE_FILE_BYTES_REVERSED_LO = 0x0080 IMAGE_FILE_32BIT_MACHINE = 0x0100 diff --git a/src/pe/EXE.go b/src/pe/EXE.go index ea856b5..238b3a7 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -189,11 +189,11 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) binary.Write(writer, binary.LittleEndian, &pe.Sections) - writer.Write(bytes.Repeat([]byte{0x00}, int(codePadding))) + writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) writer.Write(code) - writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) + writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) writer.Write(data) - writer.Write(bytes.Repeat([]byte{0x00}, int(importsPadding))) + writer.Write(bytes.Repeat([]byte{0x00}, importsPadding)) binary.Write(writer, binary.LittleEndian, &imports) binary.Write(writer, binary.LittleEndian, &dllData) binary.Write(writer, binary.LittleEndian, &dllImports) diff --git a/src/token/Count.go b/src/token/Count.go index cf460a3..3dbcd38 100644 --- a/src/token/Count.go +++ b/src/token/Count.go @@ -5,7 +5,7 @@ func Count(tokens []Token, buffer []byte, kind Kind, name string) uint8 { count := uint8(0) for _, t := range tokens { - if t.Kind == Identifier && t.Text(buffer) == name { + if t.Kind == kind && t.Text(buffer) == name { count++ } } diff --git a/src/token/List_test.go b/src/token/List_test.go index 71fbf84..249ca17 100644 --- a/src/token/List_test.go +++ b/src/token/List_test.go @@ -1,7 +1,7 @@ package token_test import ( - "fmt" + "errors" "testing" "git.akyoto.dev/cli/q/src/token" @@ -32,7 +32,7 @@ func TestSplit(t *testing.T) { assert.DeepEqual(t, parameters, []string{"1+2", "3*4", "5*6", "7+8"}) err = tokens.Split(func(parameter token.List) error { - return fmt.Errorf("error") + return errors.New("error") }) assert.NotNil(t, err) diff --git a/src/token/bench_test.go b/src/token/bench_test.go index acc6359..a502019 100644 --- a/src/token/bench_test.go +++ b/src/token/bench_test.go @@ -19,7 +19,7 @@ func bench(n int) func(b *testing.B) { return func(b *testing.B) { input := bytes.Repeat(line, n) - for i := 0; i < b.N; i++ { + for range b.N { token.Tokenize(input) } } diff --git a/src/x64/memoryAccessDynamic.go b/src/x64/memoryAccessDynamic.go index 8400351..c9e323c 100644 --- a/src/x64/memoryAccessDynamic.go +++ b/src/x64/memoryAccessDynamic.go @@ -18,9 +18,7 @@ func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, destination c } if offset == RSP { - tmp := offset - offset = destination - destination = tmp + offset, destination = destination, offset } if numBytes == 8 { diff --git a/tests/examples_test.go b/tests/examples_test.go index bb9e4fe..f889c1d 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -48,7 +48,7 @@ func BenchmarkExamples(b *testing.B) { b.Run(test.Name, func(b *testing.B) { compiler := build.New(filepath.Join("..", "examples", test.Name)) - for i := 0; i < b.N; i++ { + for range b.N { _, err := compiler.Run() assert.Nil(b, err) } diff --git a/tests/programs_test.go b/tests/programs_test.go index dfeba4d..23fc44a 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -82,7 +82,7 @@ func BenchmarkPrograms(b *testing.B) { b.Run(test.Name, func(b *testing.B) { compiler := build.New(filepath.Join("programs", test.Name+".q")) - for i := 0; i < b.N; i++ { + for range b.N { _, err := compiler.Run() assert.Nil(b, err) } From 313302b9c8ba7e34a2c0df36f16bb5a3a4daca0e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 30 Jan 2025 22:23:38 +0100 Subject: [PATCH 0590/1012] Improved code style --- src/asm/MemoryLabel.go | 2 +- src/expression/Expression_test.go | 7 ++----- src/expression/bench_test.go | 2 +- src/macho/MachO.go | 4 ++-- src/pe/Constants.go | 2 +- src/pe/EXE.go | 6 +++--- src/token/Count.go | 2 +- src/token/List_test.go | 4 ++-- src/token/bench_test.go | 2 +- src/x64/memoryAccessDynamic.go | 4 +--- tests/examples_test.go | 2 +- tests/programs_test.go | 2 +- 12 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/asm/MemoryLabel.go b/src/asm/MemoryLabel.go index 96850a9..0d4ebf7 100644 --- a/src/asm/MemoryLabel.go +++ b/src/asm/MemoryLabel.go @@ -6,8 +6,8 @@ import ( // MemoryLabel operates with a memory address and a number. type MemoryLabel struct { - Address Memory Label string + Address Memory } // String returns a human readable version. diff --git a/src/expression/Expression_test.go b/src/expression/Expression_test.go index 1e03ba8..64a09ba 100644 --- a/src/expression/Expression_test.go +++ b/src/expression/Expression_test.go @@ -1,7 +1,7 @@ package expression_test import ( - "fmt" + "errors" "testing" "git.akyoto.dev/cli/q/src/expression" @@ -110,14 +110,11 @@ func TestParse(t *testing.T) { } for _, test := range tests { - test := test - t.Run(test.Name, func(t *testing.T) { src := []byte(test.Expression) tokens := token.Tokenize(src) expr := expression.Parse(tokens) defer expr.Reset() - assert.NotNil(t, expr) assert.Equal(t, expr.String(src), test.Result) }) @@ -150,7 +147,7 @@ func TestEachLeaf(t *testing.T) { assert.DeepEqual(t, leaves, []string{"1", "2", "3", "4", "5", "6", "7", "8"}) err = expr.EachLeaf(func(leaf *expression.Expression) error { - return fmt.Errorf("error") + return errors.New("error") }) assert.NotNil(t, err) diff --git a/src/expression/bench_test.go b/src/expression/bench_test.go index c6266c3..ec63729 100644 --- a/src/expression/bench_test.go +++ b/src/expression/bench_test.go @@ -11,7 +11,7 @@ func BenchmarkExpression(b *testing.B) { src := []byte("(1+2-3*4)+(5*6-7+8)") tokens := token.Tokenize(src) - for i := 0; i < b.N; i++ { + for range b.N { expression.Parse(tokens) } } diff --git a/src/macho/MachO.go b/src/macho/MachO.go index 21b5b19..c463694 100644 --- a/src/macho/MachO.go +++ b/src/macho/MachO.go @@ -117,8 +117,8 @@ func Write(writer io.Writer, code []byte, data []byte) { binary.Write(writer, binary.LittleEndian, &m.DataHeader) binary.Write(writer, binary.LittleEndian, &m.UnixThread) - writer.Write(bytes.Repeat([]byte{0x00}, int(codePadding))) + writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) writer.Write(code) - writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) + writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) writer.Write(data) } diff --git a/src/pe/Constants.go b/src/pe/Constants.go index c77bea7..00b470e 100644 --- a/src/pe/Constants.go +++ b/src/pe/Constants.go @@ -31,7 +31,7 @@ const ( IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002 IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004 IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008 - IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010 + IMAGE_FILE_AGGRESSIVE_WS_TRIM = 0x0010 IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020 IMAGE_FILE_BYTES_REVERSED_LO = 0x0080 IMAGE_FILE_32BIT_MACHINE = 0x0100 diff --git a/src/pe/EXE.go b/src/pe/EXE.go index ea856b5..238b3a7 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -189,11 +189,11 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) binary.Write(writer, binary.LittleEndian, &pe.Sections) - writer.Write(bytes.Repeat([]byte{0x00}, int(codePadding))) + writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) writer.Write(code) - writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) + writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) writer.Write(data) - writer.Write(bytes.Repeat([]byte{0x00}, int(importsPadding))) + writer.Write(bytes.Repeat([]byte{0x00}, importsPadding)) binary.Write(writer, binary.LittleEndian, &imports) binary.Write(writer, binary.LittleEndian, &dllData) binary.Write(writer, binary.LittleEndian, &dllImports) diff --git a/src/token/Count.go b/src/token/Count.go index cf460a3..3dbcd38 100644 --- a/src/token/Count.go +++ b/src/token/Count.go @@ -5,7 +5,7 @@ func Count(tokens []Token, buffer []byte, kind Kind, name string) uint8 { count := uint8(0) for _, t := range tokens { - if t.Kind == Identifier && t.Text(buffer) == name { + if t.Kind == kind && t.Text(buffer) == name { count++ } } diff --git a/src/token/List_test.go b/src/token/List_test.go index 71fbf84..249ca17 100644 --- a/src/token/List_test.go +++ b/src/token/List_test.go @@ -1,7 +1,7 @@ package token_test import ( - "fmt" + "errors" "testing" "git.akyoto.dev/cli/q/src/token" @@ -32,7 +32,7 @@ func TestSplit(t *testing.T) { assert.DeepEqual(t, parameters, []string{"1+2", "3*4", "5*6", "7+8"}) err = tokens.Split(func(parameter token.List) error { - return fmt.Errorf("error") + return errors.New("error") }) assert.NotNil(t, err) diff --git a/src/token/bench_test.go b/src/token/bench_test.go index acc6359..a502019 100644 --- a/src/token/bench_test.go +++ b/src/token/bench_test.go @@ -19,7 +19,7 @@ func bench(n int) func(b *testing.B) { return func(b *testing.B) { input := bytes.Repeat(line, n) - for i := 0; i < b.N; i++ { + for range b.N { token.Tokenize(input) } } diff --git a/src/x64/memoryAccessDynamic.go b/src/x64/memoryAccessDynamic.go index 8400351..c9e323c 100644 --- a/src/x64/memoryAccessDynamic.go +++ b/src/x64/memoryAccessDynamic.go @@ -18,9 +18,7 @@ func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, destination c } if offset == RSP { - tmp := offset - offset = destination - destination = tmp + offset, destination = destination, offset } if numBytes == 8 { diff --git a/tests/examples_test.go b/tests/examples_test.go index bb9e4fe..f889c1d 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -48,7 +48,7 @@ func BenchmarkExamples(b *testing.B) { b.Run(test.Name, func(b *testing.B) { compiler := build.New(filepath.Join("..", "examples", test.Name)) - for i := 0; i < b.N; i++ { + for range b.N { _, err := compiler.Run() assert.Nil(b, err) } diff --git a/tests/programs_test.go b/tests/programs_test.go index dfeba4d..23fc44a 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -82,7 +82,7 @@ func BenchmarkPrograms(b *testing.B) { b.Run(test.Name, func(b *testing.B) { compiler := build.New(filepath.Join("programs", test.Name+".q")) - for i := 0; i < b.N; i++ { + for range b.N { _, err := compiler.Run() assert.Nil(b, err) } From 699dbe4fd5fdc84ae239c3c99083c6ff31b5466b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 30 Jan 2025 23:57:41 +0100 Subject: [PATCH 0591/1012] Implemented function pointers as parameters --- examples/thread/thread.q | 14 +++++----- src/asm/Finalize.go | 47 ++++++++++++++++++++++------------ src/core/ExpressionToMemory.go | 20 +++++++++++++-- src/core/TokenToRegister.go | 23 ++++++++++++----- tests/programs/param-order.q | 8 +++--- 5 files changed, 76 insertions(+), 36 deletions(-) diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 09f0da6..3ef7e5e 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -2,18 +2,18 @@ import mem import sys main() { - start() - start() - start() + start(thread) + start(thread) + start(thread) thread() } -start() { +start(func Pointer) { size := 4096 stack := mem.alloc(size) - pointer := stack + size - 8 - store(pointer, 8, thread) - sys.clone(0x100 | 0x200 | 0x400 | 0x800 | 0x8000 | 0x10000 | 0x80000000, pointer) + rip := stack + size - 8 + store(rip, 8, func) + sys.clone(0x100|0x200|0x400|0x800|0x8000|0x10000|0x80000000, rip) } thread() { diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 0f92006..688f377 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -225,24 +225,37 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { opSize := len(code) - size - start regLabel := x.Data.(*RegisterLabel) - if !strings.HasPrefix(regLabel.Label, "data_") { - panic("non-data moves not implemented yet") + if strings.HasPrefix(regLabel.Label, "data_") { + dataPointers = append(dataPointers, &Pointer{ + Position: Address(len(code) - size), + OpSize: uint8(opSize), + Size: uint8(size), + Resolve: func() Address { + destination, exists := dataLabels[regLabel.Label] + + if !exists { + panic("unknown label") + } + + return Address(destination) + }, + }) + } else { + funcPointers = append(funcPointers, &Pointer{ + Position: Address(len(code) - size), + OpSize: uint8(opSize), + Size: uint8(size), + Resolve: func() Address { + destination, exists := codeLabels[regLabel.Label] + + if !exists { + panic("unknown label") + } + + return Address(destination) + }, + }) } - - dataPointers = append(dataPointers, &Pointer{ - Position: Address(len(code) - size), - OpSize: uint8(opSize), - Size: uint8(size), - Resolve: func() Address { - destination, exists := dataLabels[regLabel.Label] - - if !exists { - panic("unknown label") - } - - return Address(destination) - }, - }) } case NEGATE: diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index dfeb7da..bd84657 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -15,8 +15,24 @@ import ( func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (*types.Type, error) { if node.IsLeaf() { if node.Token.Kind == token.Identifier { - f.MemoryLabel(asm.STORE, memory, fmt.Sprintf("%s.%s", f.Package, node.Token.Text(f.File.Bytes))) - return types.Pointer, nil + name := node.Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + + if variable != nil { + f.UseVariable(variable) + f.MemoryRegister(asm.STORE, memory, variable.Register) + return types.Pointer, nil + } + + uniqueName := fmt.Sprintf("%s.%s", f.Package, name) + _, exists := f.Functions[uniqueName] + + if exists { + f.MemoryLabel(asm.STORE, memory, uniqueName) + return types.Pointer, nil + } + + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Token.Position) } if node.Token.IsNumeric() { diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index 117d8db..0b189e5 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -1,6 +1,8 @@ package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" @@ -16,14 +18,23 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (*types name := t.Text(f.File.Bytes) variable := f.VariableByName(name) - if variable == nil { - return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) + if variable != nil { + f.UseVariable(variable) + f.SaveRegister(register) + f.RegisterRegister(asm.MOVE, register, variable.Register) + return variable.Type, nil } - f.UseVariable(variable) - f.SaveRegister(register) - f.RegisterRegister(asm.MOVE, register, variable.Register) - return variable.Type, nil + uniqueName := fmt.Sprintf("%s.%s", f.Package, name) + _, exists := f.Functions[uniqueName] + + if exists { + f.SaveRegister(register) + f.RegisterLabel(asm.MOVE, register, uniqueName) + return types.Pointer, nil + } + + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) case token.Number, token.Rune: number, err := f.Number(t) diff --git a/tests/programs/param-order.q b/tests/programs/param-order.q index 2b8ef0f..3e299b1 100644 --- a/tests/programs/param-order.q +++ b/tests/programs/param-order.q @@ -1,12 +1,12 @@ main() { - f(1, 2, 3, 4, 5, 6) + f1(1, 2, 3, 4, 5, 6) } -f(a Int, b Int, c Int, d Int, e Int, f Int) { - g(f, e, d, c, b, a) +f1(a Int, b Int, c Int, d Int, e Int, f Int) { + f2(f, e, d, c, b, a) } -g(a Int, b Int, c Int, d Int, e Int, f Int) { +f2(a Int, b Int, c Int, d Int, e Int, f Int) { assert a == 6 assert b == 5 assert c == 4 From dd6d1cc16c446fe017808b8ea1f7b88518466c01 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 30 Jan 2025 23:57:41 +0100 Subject: [PATCH 0592/1012] Implemented function pointers as parameters --- examples/thread/thread.q | 14 +++++----- src/asm/Finalize.go | 47 ++++++++++++++++++++++------------ src/core/ExpressionToMemory.go | 20 +++++++++++++-- src/core/TokenToRegister.go | 23 ++++++++++++----- tests/programs/param-order.q | 8 +++--- 5 files changed, 76 insertions(+), 36 deletions(-) diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 09f0da6..3ef7e5e 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -2,18 +2,18 @@ import mem import sys main() { - start() - start() - start() + start(thread) + start(thread) + start(thread) thread() } -start() { +start(func Pointer) { size := 4096 stack := mem.alloc(size) - pointer := stack + size - 8 - store(pointer, 8, thread) - sys.clone(0x100 | 0x200 | 0x400 | 0x800 | 0x8000 | 0x10000 | 0x80000000, pointer) + rip := stack + size - 8 + store(rip, 8, func) + sys.clone(0x100|0x200|0x400|0x800|0x8000|0x10000|0x80000000, rip) } thread() { diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 0f92006..688f377 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -225,24 +225,37 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { opSize := len(code) - size - start regLabel := x.Data.(*RegisterLabel) - if !strings.HasPrefix(regLabel.Label, "data_") { - panic("non-data moves not implemented yet") + if strings.HasPrefix(regLabel.Label, "data_") { + dataPointers = append(dataPointers, &Pointer{ + Position: Address(len(code) - size), + OpSize: uint8(opSize), + Size: uint8(size), + Resolve: func() Address { + destination, exists := dataLabels[regLabel.Label] + + if !exists { + panic("unknown label") + } + + return Address(destination) + }, + }) + } else { + funcPointers = append(funcPointers, &Pointer{ + Position: Address(len(code) - size), + OpSize: uint8(opSize), + Size: uint8(size), + Resolve: func() Address { + destination, exists := codeLabels[regLabel.Label] + + if !exists { + panic("unknown label") + } + + return Address(destination) + }, + }) } - - dataPointers = append(dataPointers, &Pointer{ - Position: Address(len(code) - size), - OpSize: uint8(opSize), - Size: uint8(size), - Resolve: func() Address { - destination, exists := dataLabels[regLabel.Label] - - if !exists { - panic("unknown label") - } - - return Address(destination) - }, - }) } case NEGATE: diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index dfeb7da..bd84657 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -15,8 +15,24 @@ import ( func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (*types.Type, error) { if node.IsLeaf() { if node.Token.Kind == token.Identifier { - f.MemoryLabel(asm.STORE, memory, fmt.Sprintf("%s.%s", f.Package, node.Token.Text(f.File.Bytes))) - return types.Pointer, nil + name := node.Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + + if variable != nil { + f.UseVariable(variable) + f.MemoryRegister(asm.STORE, memory, variable.Register) + return types.Pointer, nil + } + + uniqueName := fmt.Sprintf("%s.%s", f.Package, name) + _, exists := f.Functions[uniqueName] + + if exists { + f.MemoryLabel(asm.STORE, memory, uniqueName) + return types.Pointer, nil + } + + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Token.Position) } if node.Token.IsNumeric() { diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index 117d8db..0b189e5 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -1,6 +1,8 @@ package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" @@ -16,14 +18,23 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (*types name := t.Text(f.File.Bytes) variable := f.VariableByName(name) - if variable == nil { - return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) + if variable != nil { + f.UseVariable(variable) + f.SaveRegister(register) + f.RegisterRegister(asm.MOVE, register, variable.Register) + return variable.Type, nil } - f.UseVariable(variable) - f.SaveRegister(register) - f.RegisterRegister(asm.MOVE, register, variable.Register) - return variable.Type, nil + uniqueName := fmt.Sprintf("%s.%s", f.Package, name) + _, exists := f.Functions[uniqueName] + + if exists { + f.SaveRegister(register) + f.RegisterLabel(asm.MOVE, register, uniqueName) + return types.Pointer, nil + } + + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) case token.Number, token.Rune: number, err := f.Number(t) diff --git a/tests/programs/param-order.q b/tests/programs/param-order.q index 2b8ef0f..3e299b1 100644 --- a/tests/programs/param-order.q +++ b/tests/programs/param-order.q @@ -1,12 +1,12 @@ main() { - f(1, 2, 3, 4, 5, 6) + f1(1, 2, 3, 4, 5, 6) } -f(a Int, b Int, c Int, d Int, e Int, f Int) { - g(f, e, d, c, b, a) +f1(a Int, b Int, c Int, d Int, e Int, f Int) { + f2(f, e, d, c, b, a) } -g(a Int, b Int, c Int, d Int, e Int, f Int) { +f2(a Int, b Int, c Int, d Int, e Int, f Int) { assert a == 6 assert b == 5 assert c == 4 From 4149fcec807ee7cdf40373ce508ff5d96994a37f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 31 Jan 2025 11:14:04 +0100 Subject: [PATCH 0593/1012] Improved performance of the address resolver --- src/asm/Finalize.go | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 688f377..d231a22 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -26,10 +26,21 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { dataLabels map[string]Address codePointers []*Pointer dataPointers []*Pointer - funcPointers []*Pointer dllPointers []*Pointer + headerEnd = Address(0) ) + switch config.TargetOS { + case "linux": + headerEnd = elf.HeaderEnd + case "macos": + headerEnd = macho.HeaderEnd + case "windows": + headerEnd = pe.HeaderEnd + } + + codeStart, _ := fs.Align(headerEnd, config.Align) + for _, x := range a.Instructions { switch x.Mnemonic { case ADD: @@ -241,7 +252,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { }, }) } else { - funcPointers = append(funcPointers, &Pointer{ + codePointers = append(codePointers, &Pointer{ Position: Address(len(code) - size), OpSize: uint8(opSize), Size: uint8(size), @@ -252,7 +263,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { panic("unknown label") } - return Address(destination) + return config.BaseAddress + codeStart + destination }, }) } @@ -320,7 +331,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { opSize := len(code) - size - start memLabel := x.Data.(*MemoryLabel) - funcPointers = append(funcPointers, &Pointer{ + codePointers = append(codePointers, &Pointer{ Position: Address(len(code) - size), OpSize: uint8(opSize), Size: uint8(size), @@ -331,7 +342,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { panic("unknown label") } - return Address(destination) + return config.BaseAddress + codeStart + destination }, }) case *MemoryRegister: @@ -423,27 +434,9 @@ restart: } } - headerEnd := Address(0) - - switch config.TargetOS { - case "linux": - headerEnd = elf.HeaderEnd - case "macos": - headerEnd = macho.HeaderEnd - case "windows": - headerEnd = pe.HeaderEnd - } - - codeStart, _ := fs.Align(headerEnd, config.Align) dataStart, _ := fs.Align(codeStart+Address(len(code)), config.Align) data, dataLabels = a.Data.Finalize() - for _, pointer := range funcPointers { - address := config.BaseAddress + Address(codeStart) + pointer.Resolve() - slice := code[pointer.Position : pointer.Position+4] - binary.LittleEndian.PutUint32(slice, uint32(address)) - } - for _, pointer := range dataPointers { address := config.BaseAddress + Address(dataStart) + pointer.Resolve() slice := code[pointer.Position : pointer.Position+4] From be1b8723f4ac4534c29eb198282d3f06d28e56c1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 31 Jan 2025 11:14:04 +0100 Subject: [PATCH 0594/1012] Improved performance of the address resolver --- src/asm/Finalize.go | 39 ++++++++++++++++----------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 688f377..d231a22 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -26,10 +26,21 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { dataLabels map[string]Address codePointers []*Pointer dataPointers []*Pointer - funcPointers []*Pointer dllPointers []*Pointer + headerEnd = Address(0) ) + switch config.TargetOS { + case "linux": + headerEnd = elf.HeaderEnd + case "macos": + headerEnd = macho.HeaderEnd + case "windows": + headerEnd = pe.HeaderEnd + } + + codeStart, _ := fs.Align(headerEnd, config.Align) + for _, x := range a.Instructions { switch x.Mnemonic { case ADD: @@ -241,7 +252,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { }, }) } else { - funcPointers = append(funcPointers, &Pointer{ + codePointers = append(codePointers, &Pointer{ Position: Address(len(code) - size), OpSize: uint8(opSize), Size: uint8(size), @@ -252,7 +263,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { panic("unknown label") } - return Address(destination) + return config.BaseAddress + codeStart + destination }, }) } @@ -320,7 +331,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { opSize := len(code) - size - start memLabel := x.Data.(*MemoryLabel) - funcPointers = append(funcPointers, &Pointer{ + codePointers = append(codePointers, &Pointer{ Position: Address(len(code) - size), OpSize: uint8(opSize), Size: uint8(size), @@ -331,7 +342,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { panic("unknown label") } - return Address(destination) + return config.BaseAddress + codeStart + destination }, }) case *MemoryRegister: @@ -423,27 +434,9 @@ restart: } } - headerEnd := Address(0) - - switch config.TargetOS { - case "linux": - headerEnd = elf.HeaderEnd - case "macos": - headerEnd = macho.HeaderEnd - case "windows": - headerEnd = pe.HeaderEnd - } - - codeStart, _ := fs.Align(headerEnd, config.Align) dataStart, _ := fs.Align(codeStart+Address(len(code)), config.Align) data, dataLabels = a.Data.Finalize() - for _, pointer := range funcPointers { - address := config.BaseAddress + Address(codeStart) + pointer.Resolve() - slice := code[pointer.Position : pointer.Position+4] - binary.LittleEndian.PutUint32(slice, uint32(address)) - } - for _, pointer := range dataPointers { address := config.BaseAddress + Address(dataStart) + pointer.Resolve() slice := code[pointer.Position : pointer.Position+4] From ff4076018686f93b2942453632ec57dec292dfd8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 31 Jan 2025 11:42:34 +0100 Subject: [PATCH 0595/1012] Added more tokenizer tests --- src/token/Tokenize.go | 8 ++-- src/token/Tokenize_test.go | 88 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 7 deletions(-) diff --git a/src/token/Tokenize.go b/src/token/Tokenize.go index 2e15e25..355b3df 100644 --- a/src/token/Tokenize.go +++ b/src/token/Tokenize.go @@ -228,10 +228,10 @@ func Tokenize(buffer []byte) List { // kind = ReturnType case ".": kind = Period - case "/": - kind = Div - case "/=": - kind = DivAssign + // case "/": + // kind = Div + // case "/=": + // kind = DivAssign case ":=": kind = Define case "<": diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index e00f978..64d7045 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -25,7 +25,7 @@ func TestFunction(t *testing.T) { } func TestKeyword(t *testing.T) { - tokens := token.Tokenize([]byte("assert if import else loop return")) + tokens := token.Tokenize([]byte("assert if import else loop return switch")) expected := []token.Kind{ token.Assert, @@ -34,6 +34,7 @@ func TestKeyword(t *testing.T) { token.Else, token.Loop, token.Return, + token.Switch, token.EOF, } @@ -87,7 +88,7 @@ func TestNumber(t *testing.T) { } func TestOperator(t *testing.T) { - tokens := token.Tokenize([]byte(`a + b - c * d / e`)) + tokens := token.Tokenize([]byte(`a + b - c * d / e % f << g >> h & i | j ^ k`)) expected := []token.Kind{ token.Identifier, @@ -99,6 +100,18 @@ func TestOperator(t *testing.T) { token.Identifier, token.Div, token.Identifier, + token.Mod, + token.Identifier, + token.Shl, + token.Identifier, + token.Shr, + token.Identifier, + token.And, + token.Identifier, + token.Or, + token.Identifier, + token.Xor, + token.Identifier, token.EOF, } @@ -108,7 +121,7 @@ func TestOperator(t *testing.T) { } func TestOperatorAssign(t *testing.T) { - tokens := token.Tokenize([]byte(`a += b -= c *= d /= e &= f |= g ^= h <<= i >>= j`)) + tokens := token.Tokenize([]byte(`a += b -= c *= d /= e %= f &= g |= h ^= i <<= j >>= k`)) expected := []token.Kind{ token.Identifier, @@ -120,6 +133,8 @@ func TestOperatorAssign(t *testing.T) { token.Identifier, token.DivAssign, token.Identifier, + token.ModAssign, + token.Identifier, token.AndAssign, token.Identifier, token.OrAssign, @@ -138,6 +153,31 @@ func TestOperatorAssign(t *testing.T) { } } +func TestOperatorEquality(t *testing.T) { + tokens := token.Tokenize([]byte(`a == b != c <= d >= e < f > g`)) + + expected := []token.Kind{ + token.Identifier, + token.Equal, + token.Identifier, + token.NotEqual, + token.Identifier, + token.LessEqual, + token.Identifier, + token.GreaterEqual, + token.Identifier, + token.Less, + token.Identifier, + token.Greater, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + func TestNegateFirstToken(t *testing.T) { tokens := token.Tokenize([]byte(`-a`)) @@ -409,3 +449,45 @@ func TestStringEOF(t *testing.T) { assert.Equal(t, tokens[i].Kind, kind) } } + +func TestReturnType(t *testing.T) { + tokens := token.Tokenize([]byte("()->")) + + expected := []token.Kind{ + token.GroupStart, + token.GroupEnd, + token.ReturnType, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestMinusAtEOF(t *testing.T) { + tokens := token.Tokenize([]byte("1-")) + + expected := []token.Kind{ + token.Number, + token.Sub, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestRune(t *testing.T) { + tokens := token.Tokenize([]byte("'a'")) + + expected := []token.Kind{ + token.Rune, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} From eba0176ae60125e3dbfd4b92f99e0f91bbc3df5c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 31 Jan 2025 11:42:34 +0100 Subject: [PATCH 0596/1012] Added more tokenizer tests --- src/token/Tokenize.go | 8 ++-- src/token/Tokenize_test.go | 88 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 7 deletions(-) diff --git a/src/token/Tokenize.go b/src/token/Tokenize.go index 2e15e25..355b3df 100644 --- a/src/token/Tokenize.go +++ b/src/token/Tokenize.go @@ -228,10 +228,10 @@ func Tokenize(buffer []byte) List { // kind = ReturnType case ".": kind = Period - case "/": - kind = Div - case "/=": - kind = DivAssign + // case "/": + // kind = Div + // case "/=": + // kind = DivAssign case ":=": kind = Define case "<": diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index e00f978..64d7045 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -25,7 +25,7 @@ func TestFunction(t *testing.T) { } func TestKeyword(t *testing.T) { - tokens := token.Tokenize([]byte("assert if import else loop return")) + tokens := token.Tokenize([]byte("assert if import else loop return switch")) expected := []token.Kind{ token.Assert, @@ -34,6 +34,7 @@ func TestKeyword(t *testing.T) { token.Else, token.Loop, token.Return, + token.Switch, token.EOF, } @@ -87,7 +88,7 @@ func TestNumber(t *testing.T) { } func TestOperator(t *testing.T) { - tokens := token.Tokenize([]byte(`a + b - c * d / e`)) + tokens := token.Tokenize([]byte(`a + b - c * d / e % f << g >> h & i | j ^ k`)) expected := []token.Kind{ token.Identifier, @@ -99,6 +100,18 @@ func TestOperator(t *testing.T) { token.Identifier, token.Div, token.Identifier, + token.Mod, + token.Identifier, + token.Shl, + token.Identifier, + token.Shr, + token.Identifier, + token.And, + token.Identifier, + token.Or, + token.Identifier, + token.Xor, + token.Identifier, token.EOF, } @@ -108,7 +121,7 @@ func TestOperator(t *testing.T) { } func TestOperatorAssign(t *testing.T) { - tokens := token.Tokenize([]byte(`a += b -= c *= d /= e &= f |= g ^= h <<= i >>= j`)) + tokens := token.Tokenize([]byte(`a += b -= c *= d /= e %= f &= g |= h ^= i <<= j >>= k`)) expected := []token.Kind{ token.Identifier, @@ -120,6 +133,8 @@ func TestOperatorAssign(t *testing.T) { token.Identifier, token.DivAssign, token.Identifier, + token.ModAssign, + token.Identifier, token.AndAssign, token.Identifier, token.OrAssign, @@ -138,6 +153,31 @@ func TestOperatorAssign(t *testing.T) { } } +func TestOperatorEquality(t *testing.T) { + tokens := token.Tokenize([]byte(`a == b != c <= d >= e < f > g`)) + + expected := []token.Kind{ + token.Identifier, + token.Equal, + token.Identifier, + token.NotEqual, + token.Identifier, + token.LessEqual, + token.Identifier, + token.GreaterEqual, + token.Identifier, + token.Less, + token.Identifier, + token.Greater, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + func TestNegateFirstToken(t *testing.T) { tokens := token.Tokenize([]byte(`-a`)) @@ -409,3 +449,45 @@ func TestStringEOF(t *testing.T) { assert.Equal(t, tokens[i].Kind, kind) } } + +func TestReturnType(t *testing.T) { + tokens := token.Tokenize([]byte("()->")) + + expected := []token.Kind{ + token.GroupStart, + token.GroupEnd, + token.ReturnType, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestMinusAtEOF(t *testing.T) { + tokens := token.Tokenize([]byte("1-")) + + expected := []token.Kind{ + token.Number, + token.Sub, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestRune(t *testing.T) { + tokens := token.Tokenize([]byte("'a'")) + + expected := []token.Kind{ + token.Rune, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} From 326db85e41e83cc284c8ecda9f02885a704bb869 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 31 Jan 2025 11:54:42 +0100 Subject: [PATCH 0597/1012] Added more CLI tests --- src/cli/Main_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index 93138a8..b27a964 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -24,6 +24,9 @@ func TestCLI(t *testing.T) { {[]string{"build", "../../examples/hello", "--invalid"}, 2}, {[]string{"build", "../../examples/hello", "--dry"}, 0}, {[]string{"build", "../../examples/hello", "--dry", "--verbose"}, 0}, + {[]string{"build", "../../examples/hello", "--dry", "--os", "linux"}, 0}, + {[]string{"build", "../../examples/hello", "--dry", "--os", "mac"}, 0}, + {[]string{"build", "../../examples/hello", "--dry", "--os", "windows"}, 0}, {[]string{"build", "../../examples/hello/hello.q", "--dry"}, 0}, {[]string{"build", "../../examples/hello"}, 0}, {[]string{"run", "../../examples/hello", "--invalid"}, 2}, From 547e7d066b3513bc605e505b43695b79b81556c0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 31 Jan 2025 11:54:42 +0100 Subject: [PATCH 0598/1012] Added more CLI tests --- src/cli/Main_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index 93138a8..b27a964 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -24,6 +24,9 @@ func TestCLI(t *testing.T) { {[]string{"build", "../../examples/hello", "--invalid"}, 2}, {[]string{"build", "../../examples/hello", "--dry"}, 0}, {[]string{"build", "../../examples/hello", "--dry", "--verbose"}, 0}, + {[]string{"build", "../../examples/hello", "--dry", "--os", "linux"}, 0}, + {[]string{"build", "../../examples/hello", "--dry", "--os", "mac"}, 0}, + {[]string{"build", "../../examples/hello", "--dry", "--os", "windows"}, 0}, {[]string{"build", "../../examples/hello/hello.q", "--dry"}, 0}, {[]string{"build", "../../examples/hello"}, 0}, {[]string{"run", "../../examples/hello", "--invalid"}, 2}, From 2ae8b04593b33f8760bfe9b7bc24b0eef6e10fe0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 31 Jan 2025 12:11:39 +0100 Subject: [PATCH 0599/1012] Simplified thread example --- examples/thread/thread.q | 20 ++++++-------------- lib/thread/thread.q | 10 ++++++++++ 2 files changed, 16 insertions(+), 14 deletions(-) create mode 100644 lib/thread/thread.q diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 3ef7e5e..350168c 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -1,22 +1,14 @@ -import mem import sys +import thread main() { - start(thread) - start(thread) - start(thread) - thread() + thread.create(work) + thread.create(work) + thread.create(work) + work() } -start(func Pointer) { - size := 4096 - stack := mem.alloc(size) - rip := stack + size - 8 - store(rip, 8, func) - sys.clone(0x100|0x200|0x400|0x800|0x8000|0x10000|0x80000000, rip) -} - -thread() { +work() { sys.write(1, "[ ] start\n", 10) sys.write(1, "[x] end\n", 8) sys.exit(0) diff --git a/lib/thread/thread.q b/lib/thread/thread.q new file mode 100644 index 0000000..d2b2b73 --- /dev/null +++ b/lib/thread/thread.q @@ -0,0 +1,10 @@ +import mem +import sys + +create(func Pointer) -> Int { + size := 4096 + stack := mem.alloc(size) + rip := stack + size - 8 + store(rip, 8, func) + return sys.clone(0x100|0x200|0x400|0x800|0x8000|0x10000|0x80000000, rip) +} \ No newline at end of file From 6163ba547ebaacdc6e4f57fe7984e0554594f0ec Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 31 Jan 2025 12:11:39 +0100 Subject: [PATCH 0600/1012] Simplified thread example --- examples/thread/thread.q | 20 ++++++-------------- lib/thread/thread.q | 10 ++++++++++ 2 files changed, 16 insertions(+), 14 deletions(-) create mode 100644 lib/thread/thread.q diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 3ef7e5e..350168c 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -1,22 +1,14 @@ -import mem import sys +import thread main() { - start(thread) - start(thread) - start(thread) - thread() + thread.create(work) + thread.create(work) + thread.create(work) + work() } -start(func Pointer) { - size := 4096 - stack := mem.alloc(size) - rip := stack + size - 8 - store(rip, 8, func) - sys.clone(0x100|0x200|0x400|0x800|0x8000|0x10000|0x80000000, rip) -} - -thread() { +work() { sys.write(1, "[ ] start\n", 10) sys.write(1, "[x] end\n", 8) sys.exit(0) diff --git a/lib/thread/thread.q b/lib/thread/thread.q new file mode 100644 index 0000000..d2b2b73 --- /dev/null +++ b/lib/thread/thread.q @@ -0,0 +1,10 @@ +import mem +import sys + +create(func Pointer) -> Int { + size := 4096 + stack := mem.alloc(size) + rip := stack + size - 8 + store(rip, 8, func) + return sys.clone(0x100|0x200|0x400|0x800|0x8000|0x10000|0x80000000, rip) +} \ No newline at end of file From 7fb5c8407f062715b78416a36f8f5e280c5789b0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 31 Jan 2025 21:50:35 +0100 Subject: [PATCH 0601/1012] Fixed incorrect OS detection --- src/asm/Finalize.go | 8 ++++---- src/build/Build.go | 2 +- src/cli/Build.go | 16 +++++++++++++++- src/compiler/Result.go | 21 +++++++++------------ src/config/config.go | 13 +++++++++---- src/config/os.go | 10 ++++++++++ src/errors/InvalidParameterValue.go | 14 ++++++++++++++ src/scanner/queueDirectory.go | 6 +++--- 8 files changed, 65 insertions(+), 25 deletions(-) create mode 100644 src/config/os.go create mode 100644 src/errors/InvalidParameterValue.go diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index d231a22..bff469b 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -31,11 +31,11 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { ) switch config.TargetOS { - case "linux": + case config.Linux: headerEnd = elf.HeaderEnd - case "macos": + case config.Mac: headerEnd = macho.HeaderEnd - case "windows": + case config.Windows: headerEnd = pe.HeaderEnd } @@ -443,7 +443,7 @@ restart: binary.LittleEndian.PutUint32(slice, uint32(address)) } - if config.TargetOS == "windows" { + if config.TargetOS == config.Windows { if len(data) == 0 { data = []byte{0} } diff --git a/src/build/Build.go b/src/build/Build.go index cb221a4..52c9f5f 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -37,7 +37,7 @@ func (build *Build) Executable() string { path = fromDirectoryName(path) } - if config.TargetOS == "windows" { + if config.TargetOS == config.Windows { path += ".exe" } diff --git a/src/cli/Build.go b/src/cli/Build.go index 00fc49a..206ef7c 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "os" + "runtime" "strings" "git.akyoto.dev/cli/q/src/build" @@ -60,7 +61,16 @@ func buildWithArgs(args []string) (*build.Build, error) { return b, &errors.ExpectedCLIParameter{Parameter: "os"} } - config.TargetOS = args[i] + switch args[i] { + case "linux": + config.TargetOS = config.Linux + case "mac": + config.TargetOS = config.Mac + case "windows": + config.TargetOS = config.Windows + default: + return b, &errors.InvalidParameterValue{Value: args[i], Parameter: "os"} + } default: if strings.HasPrefix(args[i], "-") { @@ -71,6 +81,10 @@ func buildWithArgs(args []string) (*build.Build, error) { } } + if config.TargetOS == config.Unknown { + return b, &errors.InvalidParameterValue{Value: runtime.GOOS, Parameter: "os"} + } + if len(b.Files) == 0 { b.Files = append(b.Files, ".") } diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 5186858..722ab99 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -2,7 +2,6 @@ package compiler import ( "bufio" - "fmt" "io" "os" @@ -38,15 +37,15 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { final.Call("main.main") switch config.TargetOS { - case "linux": + case config.Linux: final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], LinuxExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() - case "mac": + case config.Mac: final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], MacExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() - case "windows": + case config.Windows: final.RegisterNumber(asm.MOVE, x64.WindowsInputRegisters[0], 0) final.DLLCall("kernel32.ExitProcess") } @@ -70,15 +69,15 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { final.Label(asm.LABEL, "_crash") switch config.TargetOS { - case "linux": + case config.Linux: final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], LinuxExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() - case "mac": + case config.Mac: final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], MacExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() - case "windows": + case config.Windows: final.RegisterNumber(asm.MOVE, x64.WindowsInputRegisters[0], 1) final.DLLCall("kernel32.ExitProcess") } @@ -155,14 +154,12 @@ func write(writer io.Writer, code []byte, data []byte, dlls dll.List) error { buffer := bufio.NewWriter(writer) switch config.TargetOS { - case "linux": + case config.Linux: elf.Write(buffer, code, data) - case "mac": + case config.Mac: macho.Write(buffer, code, data) - case "windows": + case config.Windows: pe.Write(buffer, code, data, dlls) - default: - return fmt.Errorf("unsupported platform '%s'", config.TargetOS) } return buffer.Flush() diff --git a/src/config/config.go b/src/config/config.go index 6689c3e..b012e55 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -27,7 +27,7 @@ var ( TargetArch string // Target platform. - TargetOS string + TargetOS OS ) // Reset resets the configuration to its default values. @@ -36,9 +36,14 @@ func Reset() { ConstantFold = true Dry = false TargetArch = runtime.GOARCH - TargetOS = runtime.GOOS + TargetOS = Unknown - if TargetOS == "darwin" { - TargetOS = "mac" + switch runtime.GOOS { + case "linux": + TargetOS = Linux + case "darwin": + TargetOS = Mac + case "windows": + TargetOS = Windows } } diff --git a/src/config/os.go b/src/config/os.go new file mode 100644 index 0000000..d6893e2 --- /dev/null +++ b/src/config/os.go @@ -0,0 +1,10 @@ +package config + +type OS uint8 + +const ( + Unknown OS = iota + Linux + Mac + Windows +) diff --git a/src/errors/InvalidParameterValue.go b/src/errors/InvalidParameterValue.go new file mode 100644 index 0000000..cd2989e --- /dev/null +++ b/src/errors/InvalidParameterValue.go @@ -0,0 +1,14 @@ +package errors + +import "fmt" + +// InvalidParameterValue error is created when a parameter has an invalid value. +type InvalidParameterValue struct { + Value string + Parameter string +} + +// Error generates the string representation. +func (err *InvalidParameterValue) Error() string { + return fmt.Sprintf("Invalid value '%s' for parameter '%s'", err.Value, err.Parameter) +} diff --git a/src/scanner/queueDirectory.go b/src/scanner/queueDirectory.go index cc49a20..eebd2d3 100644 --- a/src/scanner/queueDirectory.go +++ b/src/scanner/queueDirectory.go @@ -21,15 +21,15 @@ func (s *Scanner) queueDirectory(directory string, pkg string) { return } - if strings.HasSuffix(name, "_linux.q") && config.TargetOS != "linux" { + if strings.HasSuffix(name, "_linux.q") && config.TargetOS != config.Linux { return } - if strings.HasSuffix(name, "_mac.q") && config.TargetOS != "mac" { + if strings.HasSuffix(name, "_mac.q") && config.TargetOS != config.Mac { return } - if strings.HasSuffix(name, "_windows.q") && config.TargetOS != "windows" { + if strings.HasSuffix(name, "_windows.q") && config.TargetOS != config.Windows { return } From 8de582abf6a911c4cc97276a7f56053cf49d6212 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 31 Jan 2025 21:50:35 +0100 Subject: [PATCH 0602/1012] Fixed incorrect OS detection --- src/asm/Finalize.go | 8 ++++---- src/build/Build.go | 2 +- src/cli/Build.go | 16 +++++++++++++++- src/compiler/Result.go | 21 +++++++++------------ src/config/config.go | 13 +++++++++---- src/config/os.go | 10 ++++++++++ src/errors/InvalidParameterValue.go | 14 ++++++++++++++ src/scanner/queueDirectory.go | 6 +++--- 8 files changed, 65 insertions(+), 25 deletions(-) create mode 100644 src/config/os.go create mode 100644 src/errors/InvalidParameterValue.go diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index d231a22..bff469b 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -31,11 +31,11 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { ) switch config.TargetOS { - case "linux": + case config.Linux: headerEnd = elf.HeaderEnd - case "macos": + case config.Mac: headerEnd = macho.HeaderEnd - case "windows": + case config.Windows: headerEnd = pe.HeaderEnd } @@ -443,7 +443,7 @@ restart: binary.LittleEndian.PutUint32(slice, uint32(address)) } - if config.TargetOS == "windows" { + if config.TargetOS == config.Windows { if len(data) == 0 { data = []byte{0} } diff --git a/src/build/Build.go b/src/build/Build.go index cb221a4..52c9f5f 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -37,7 +37,7 @@ func (build *Build) Executable() string { path = fromDirectoryName(path) } - if config.TargetOS == "windows" { + if config.TargetOS == config.Windows { path += ".exe" } diff --git a/src/cli/Build.go b/src/cli/Build.go index 00fc49a..206ef7c 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "os" + "runtime" "strings" "git.akyoto.dev/cli/q/src/build" @@ -60,7 +61,16 @@ func buildWithArgs(args []string) (*build.Build, error) { return b, &errors.ExpectedCLIParameter{Parameter: "os"} } - config.TargetOS = args[i] + switch args[i] { + case "linux": + config.TargetOS = config.Linux + case "mac": + config.TargetOS = config.Mac + case "windows": + config.TargetOS = config.Windows + default: + return b, &errors.InvalidParameterValue{Value: args[i], Parameter: "os"} + } default: if strings.HasPrefix(args[i], "-") { @@ -71,6 +81,10 @@ func buildWithArgs(args []string) (*build.Build, error) { } } + if config.TargetOS == config.Unknown { + return b, &errors.InvalidParameterValue{Value: runtime.GOOS, Parameter: "os"} + } + if len(b.Files) == 0 { b.Files = append(b.Files, ".") } diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 5186858..722ab99 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -2,7 +2,6 @@ package compiler import ( "bufio" - "fmt" "io" "os" @@ -38,15 +37,15 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { final.Call("main.main") switch config.TargetOS { - case "linux": + case config.Linux: final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], LinuxExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() - case "mac": + case config.Mac: final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], MacExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() - case "windows": + case config.Windows: final.RegisterNumber(asm.MOVE, x64.WindowsInputRegisters[0], 0) final.DLLCall("kernel32.ExitProcess") } @@ -70,15 +69,15 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { final.Label(asm.LABEL, "_crash") switch config.TargetOS { - case "linux": + case config.Linux: final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], LinuxExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() - case "mac": + case config.Mac: final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], MacExit) final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() - case "windows": + case config.Windows: final.RegisterNumber(asm.MOVE, x64.WindowsInputRegisters[0], 1) final.DLLCall("kernel32.ExitProcess") } @@ -155,14 +154,12 @@ func write(writer io.Writer, code []byte, data []byte, dlls dll.List) error { buffer := bufio.NewWriter(writer) switch config.TargetOS { - case "linux": + case config.Linux: elf.Write(buffer, code, data) - case "mac": + case config.Mac: macho.Write(buffer, code, data) - case "windows": + case config.Windows: pe.Write(buffer, code, data, dlls) - default: - return fmt.Errorf("unsupported platform '%s'", config.TargetOS) } return buffer.Flush() diff --git a/src/config/config.go b/src/config/config.go index 6689c3e..b012e55 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -27,7 +27,7 @@ var ( TargetArch string // Target platform. - TargetOS string + TargetOS OS ) // Reset resets the configuration to its default values. @@ -36,9 +36,14 @@ func Reset() { ConstantFold = true Dry = false TargetArch = runtime.GOARCH - TargetOS = runtime.GOOS + TargetOS = Unknown - if TargetOS == "darwin" { - TargetOS = "mac" + switch runtime.GOOS { + case "linux": + TargetOS = Linux + case "darwin": + TargetOS = Mac + case "windows": + TargetOS = Windows } } diff --git a/src/config/os.go b/src/config/os.go new file mode 100644 index 0000000..d6893e2 --- /dev/null +++ b/src/config/os.go @@ -0,0 +1,10 @@ +package config + +type OS uint8 + +const ( + Unknown OS = iota + Linux + Mac + Windows +) diff --git a/src/errors/InvalidParameterValue.go b/src/errors/InvalidParameterValue.go new file mode 100644 index 0000000..cd2989e --- /dev/null +++ b/src/errors/InvalidParameterValue.go @@ -0,0 +1,14 @@ +package errors + +import "fmt" + +// InvalidParameterValue error is created when a parameter has an invalid value. +type InvalidParameterValue struct { + Value string + Parameter string +} + +// Error generates the string representation. +func (err *InvalidParameterValue) Error() string { + return fmt.Sprintf("Invalid value '%s' for parameter '%s'", err.Value, err.Parameter) +} diff --git a/src/scanner/queueDirectory.go b/src/scanner/queueDirectory.go index cc49a20..eebd2d3 100644 --- a/src/scanner/queueDirectory.go +++ b/src/scanner/queueDirectory.go @@ -21,15 +21,15 @@ func (s *Scanner) queueDirectory(directory string, pkg string) { return } - if strings.HasSuffix(name, "_linux.q") && config.TargetOS != "linux" { + if strings.HasSuffix(name, "_linux.q") && config.TargetOS != config.Linux { return } - if strings.HasSuffix(name, "_mac.q") && config.TargetOS != "mac" { + if strings.HasSuffix(name, "_mac.q") && config.TargetOS != config.Mac { return } - if strings.HasSuffix(name, "_windows.q") && config.TargetOS != "windows" { + if strings.HasSuffix(name, "_windows.q") && config.TargetOS != config.Windows { return } From a878448fc53cd3c2a3a445beb0a54b028aa06888 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 31 Jan 2025 21:56:00 +0100 Subject: [PATCH 0603/1012] Added fork and execve syscalls for Mac --- lib/sys/sys_mac.q | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/sys/sys_mac.q b/lib/sys/sys_mac.q index 9ef9e1f..25389b5 100644 --- a/lib/sys/sys_mac.q +++ b/lib/sys/sys_mac.q @@ -1,3 +1,7 @@ +fork() -> Int { + return syscall(0x2000002) +} + read(fd Int, address Pointer, length Int) -> Int { return syscall(0x2000003, fd, address, length) } @@ -22,6 +26,10 @@ munmap(address Pointer, length Int) -> Int { return syscall(0x2000049, address, length) } +execve(path Pointer, argv Pointer, envp Pointer) -> Int { + return syscall(0x2000059, path, argv, envp) +} + exit(status Int) { syscall(0x2000001, status) } \ No newline at end of file From 6f6af979193885e041cc9e8503ea7b77f698d1e3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 31 Jan 2025 21:56:00 +0100 Subject: [PATCH 0604/1012] Added fork and execve syscalls for Mac --- lib/sys/sys_mac.q | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/sys/sys_mac.q b/lib/sys/sys_mac.q index 9ef9e1f..25389b5 100644 --- a/lib/sys/sys_mac.q +++ b/lib/sys/sys_mac.q @@ -1,3 +1,7 @@ +fork() -> Int { + return syscall(0x2000002) +} + read(fd Int, address Pointer, length Int) -> Int { return syscall(0x2000003, fd, address, length) } @@ -22,6 +26,10 @@ munmap(address Pointer, length Int) -> Int { return syscall(0x2000049, address, length) } +execve(path Pointer, argv Pointer, envp Pointer) -> Int { + return syscall(0x2000059, path, argv, envp) +} + exit(status Int) { syscall(0x2000001, status) } \ No newline at end of file From cad7e4b6250221badabff4edc4390d3d7aafb2a0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 31 Jan 2025 21:59:30 +0100 Subject: [PATCH 0605/1012] Fixed incorrect syscall numbers for Mac --- lib/sys/sys_mac.q | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/sys/sys_mac.q b/lib/sys/sys_mac.q index 25389b5..24fd36d 100644 --- a/lib/sys/sys_mac.q +++ b/lib/sys/sys_mac.q @@ -1,3 +1,7 @@ +exit(status Int) { + syscall(0x2000001, status) +} + fork() -> Int { return syscall(0x2000002) } @@ -27,9 +31,9 @@ munmap(address Pointer, length Int) -> Int { } execve(path Pointer, argv Pointer, envp Pointer) -> Int { - return syscall(0x2000059, path, argv, envp) + return syscall(0x200003B, path, argv, envp) } -exit(status Int) { - syscall(0x2000001, status) +waitid(type Int, id Int, info Pointer, options Int) -> Int { + return syscall(0x20000AD, type, id, info, options) } \ No newline at end of file From 8b54823f6e925806e12b0c57b4a199f11333208f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 31 Jan 2025 21:59:30 +0100 Subject: [PATCH 0606/1012] Fixed incorrect syscall numbers for Mac --- lib/sys/sys_mac.q | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/sys/sys_mac.q b/lib/sys/sys_mac.q index 25389b5..24fd36d 100644 --- a/lib/sys/sys_mac.q +++ b/lib/sys/sys_mac.q @@ -1,3 +1,7 @@ +exit(status Int) { + syscall(0x2000001, status) +} + fork() -> Int { return syscall(0x2000002) } @@ -27,9 +31,9 @@ munmap(address Pointer, length Int) -> Int { } execve(path Pointer, argv Pointer, envp Pointer) -> Int { - return syscall(0x2000059, path, argv, envp) + return syscall(0x200003B, path, argv, envp) } -exit(status Int) { - syscall(0x2000001, status) +waitid(type Int, id Int, info Pointer, options Int) -> Int { + return syscall(0x20000AD, type, id, info, options) } \ No newline at end of file From dd6ebd9cd436ea72da383f2fd78978e7db890d54 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 1 Feb 2025 14:44:24 +0100 Subject: [PATCH 0607/1012] Added more tokenizer tests --- src/token/List_test.go | 31 +++++++++++++++++- src/token/Token_test.go | 6 ++++ src/token/Tokenize_test.go | 67 +++++++++++++++++++++++++++++++++++++- 3 files changed, 102 insertions(+), 2 deletions(-) diff --git a/src/token/List_test.go b/src/token/List_test.go index 249ca17..9daf6a9 100644 --- a/src/token/List_test.go +++ b/src/token/List_test.go @@ -30,8 +30,37 @@ func TestSplit(t *testing.T) { assert.Nil(t, err) assert.DeepEqual(t, parameters, []string{"1+2", "3*4", "5*6", "7+8"}) +} - err = tokens.Split(func(parameter token.List) error { +func TestSplitGroups(t *testing.T) { + src := []byte("f(1,2),g(3,4)") + tokens := token.Tokenize(src) + parameters := []string{} + + err := tokens.Split(func(parameter token.List) error { + parameters = append(parameters, parameter.Text(src)) + return nil + }) + + assert.Nil(t, err) + assert.DeepEqual(t, parameters, []string{"f(1,2)", "g(3,4)"}) +} + +func TestSplitEmpty(t *testing.T) { + tokens := token.List{} + + err := tokens.Split(func(parameter token.List) error { + return errors.New("error") + }) + + assert.Nil(t, err) +} + +func TestSplitError(t *testing.T) { + src := []byte("1,2,3") + tokens := token.Tokenize(src) + + err := tokens.Split(func(parameter token.List) error { return errors.New("error") }) diff --git a/src/token/Token_test.go b/src/token/Token_test.go index 9396480..1eb9092 100644 --- a/src/token/Token_test.go +++ b/src/token/Token_test.go @@ -45,8 +45,14 @@ func TestTokenGroups(t *testing.T) { assignment := token.Token{Kind: token.Assign} operator := token.Token{Kind: token.Add} keyword := token.Token{Kind: token.If} + unary := token.Token{Kind: token.Not} + number := token.Token{Kind: token.Number} + comparison := token.Token{Kind: token.Equal} assert.True(t, assignment.IsAssignment()) assert.True(t, operator.IsOperator()) assert.True(t, keyword.IsKeyword()) + assert.True(t, unary.IsUnaryOperator()) + assert.True(t, number.IsNumeric()) + assert.True(t, comparison.IsComparison()) } diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index 64d7045..267f736 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -121,9 +121,11 @@ func TestOperator(t *testing.T) { } func TestOperatorAssign(t *testing.T) { - tokens := token.Tokenize([]byte(`a += b -= c *= d /= e %= f &= g |= h ^= i <<= j >>= k`)) + tokens := token.Tokenize([]byte(`a = b += c -= d *= e /= f %= g &= h |= i ^= j <<= k >>= l`)) expected := []token.Kind{ + token.Identifier, + token.Assign, token.Identifier, token.AddAssign, token.Identifier, @@ -178,6 +180,69 @@ func TestOperatorEquality(t *testing.T) { } } +func TestOperatorLogical(t *testing.T) { + tokens := token.Tokenize([]byte(`a && b || c`)) + + expected := []token.Kind{ + token.Identifier, + token.LogicalAnd, + token.Identifier, + token.LogicalOr, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestDefine(t *testing.T) { + tokens := token.Tokenize([]byte(`a := b`)) + + expected := []token.Kind{ + token.Identifier, + token.Define, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestPeriod(t *testing.T) { + tokens := token.Tokenize([]byte(`a.b.c`)) + + expected := []token.Kind{ + token.Identifier, + token.Period, + token.Identifier, + token.Period, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestNot(t *testing.T) { + tokens := token.Tokenize([]byte(`!a`)) + + expected := []token.Kind{ + token.Not, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + func TestNegateFirstToken(t *testing.T) { tokens := token.Tokenize([]byte(`-a`)) From 1be26f288cb5fd52593dbf343c7f6923a831df79 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 1 Feb 2025 14:44:24 +0100 Subject: [PATCH 0608/1012] Added more tokenizer tests --- src/token/List_test.go | 31 +++++++++++++++++- src/token/Token_test.go | 6 ++++ src/token/Tokenize_test.go | 67 +++++++++++++++++++++++++++++++++++++- 3 files changed, 102 insertions(+), 2 deletions(-) diff --git a/src/token/List_test.go b/src/token/List_test.go index 249ca17..9daf6a9 100644 --- a/src/token/List_test.go +++ b/src/token/List_test.go @@ -30,8 +30,37 @@ func TestSplit(t *testing.T) { assert.Nil(t, err) assert.DeepEqual(t, parameters, []string{"1+2", "3*4", "5*6", "7+8"}) +} - err = tokens.Split(func(parameter token.List) error { +func TestSplitGroups(t *testing.T) { + src := []byte("f(1,2),g(3,4)") + tokens := token.Tokenize(src) + parameters := []string{} + + err := tokens.Split(func(parameter token.List) error { + parameters = append(parameters, parameter.Text(src)) + return nil + }) + + assert.Nil(t, err) + assert.DeepEqual(t, parameters, []string{"f(1,2)", "g(3,4)"}) +} + +func TestSplitEmpty(t *testing.T) { + tokens := token.List{} + + err := tokens.Split(func(parameter token.List) error { + return errors.New("error") + }) + + assert.Nil(t, err) +} + +func TestSplitError(t *testing.T) { + src := []byte("1,2,3") + tokens := token.Tokenize(src) + + err := tokens.Split(func(parameter token.List) error { return errors.New("error") }) diff --git a/src/token/Token_test.go b/src/token/Token_test.go index 9396480..1eb9092 100644 --- a/src/token/Token_test.go +++ b/src/token/Token_test.go @@ -45,8 +45,14 @@ func TestTokenGroups(t *testing.T) { assignment := token.Token{Kind: token.Assign} operator := token.Token{Kind: token.Add} keyword := token.Token{Kind: token.If} + unary := token.Token{Kind: token.Not} + number := token.Token{Kind: token.Number} + comparison := token.Token{Kind: token.Equal} assert.True(t, assignment.IsAssignment()) assert.True(t, operator.IsOperator()) assert.True(t, keyword.IsKeyword()) + assert.True(t, unary.IsUnaryOperator()) + assert.True(t, number.IsNumeric()) + assert.True(t, comparison.IsComparison()) } diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index 64d7045..267f736 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -121,9 +121,11 @@ func TestOperator(t *testing.T) { } func TestOperatorAssign(t *testing.T) { - tokens := token.Tokenize([]byte(`a += b -= c *= d /= e %= f &= g |= h ^= i <<= j >>= k`)) + tokens := token.Tokenize([]byte(`a = b += c -= d *= e /= f %= g &= h |= i ^= j <<= k >>= l`)) expected := []token.Kind{ + token.Identifier, + token.Assign, token.Identifier, token.AddAssign, token.Identifier, @@ -178,6 +180,69 @@ func TestOperatorEquality(t *testing.T) { } } +func TestOperatorLogical(t *testing.T) { + tokens := token.Tokenize([]byte(`a && b || c`)) + + expected := []token.Kind{ + token.Identifier, + token.LogicalAnd, + token.Identifier, + token.LogicalOr, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestDefine(t *testing.T) { + tokens := token.Tokenize([]byte(`a := b`)) + + expected := []token.Kind{ + token.Identifier, + token.Define, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestPeriod(t *testing.T) { + tokens := token.Tokenize([]byte(`a.b.c`)) + + expected := []token.Kind{ + token.Identifier, + token.Period, + token.Identifier, + token.Period, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + +func TestNot(t *testing.T) { + tokens := token.Tokenize([]byte(`!a`)) + + expected := []token.Kind{ + token.Not, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + func TestNegateFirstToken(t *testing.T) { tokens := token.Tokenize([]byte(`-a`)) From 25f6928f4006540bbc61b5aef54bf954c7a4ca36 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 2 Feb 2025 11:11:59 +0100 Subject: [PATCH 0609/1012] Simplified tokenizer --- src/ast/AST.go | 3 + src/token/Tokenize.go | 268 ++-------------------------------------- src/token/dash.go | 25 ++++ src/token/digit.go | 38 ++++++ src/token/identifier.go | 46 +++++++ src/token/operator.go | 84 +++++++++++++ src/token/quote.go | 28 +++++ src/token/slash.go | 34 +++++ src/token/zero.go | 35 ++++++ 9 files changed, 300 insertions(+), 261 deletions(-) create mode 100644 src/token/dash.go create mode 100644 src/token/digit.go create mode 100644 src/token/identifier.go create mode 100644 src/token/operator.go create mode 100644 src/token/quote.go create mode 100644 src/token/slash.go create mode 100644 src/token/zero.go diff --git a/src/ast/AST.go b/src/ast/AST.go index c7c4c47..8dcaa24 100644 --- a/src/ast/AST.go +++ b/src/ast/AST.go @@ -1,4 +1,7 @@ package ast +// Node is an interface used for all types of AST nodes. type Node any + +// AST is an abstract syntax tree which is simply a list of nodes. type AST []Node diff --git a/src/token/Tokenize.go b/src/token/Tokenize.go index 355b3df..9178314 100644 --- a/src/token/Tokenize.go +++ b/src/token/Tokenize.go @@ -27,246 +27,29 @@ func Tokenize(buffer []byte) List { case '\n': tokens = append(tokens, Token{Kind: NewLine, Position: i, Length: 1}) case '-': - if len(tokens) == 0 || tokens[len(tokens)-1].IsOperator() || tokens[len(tokens)-1].IsExpressionStart() || tokens[len(tokens)-1].IsKeyword() { - tokens = append(tokens, Token{Kind: Negate, Position: i, Length: 1}) - } else { - if i+1 < Position(len(buffer)) { - switch buffer[i+1] { - case '=': - tokens = append(tokens, Token{Kind: SubAssign, Position: i, Length: 2}) - i++ - case '>': - tokens = append(tokens, Token{Kind: ReturnType, Position: i, Length: 2}) - i++ - default: - tokens = append(tokens, Token{Kind: Sub, Position: i, Length: 1}) - } - } else { - tokens = append(tokens, Token{Kind: Sub, Position: i, Length: 1}) - } - } - + tokens, i = dash(tokens, buffer, i) case '/': - if i+1 < Position(len(buffer)) && buffer[i+1] == '/' { - position := i - - for i < Position(len(buffer)) && buffer[i] != '\n' { - i++ - } - - tokens = append(tokens, Token{Kind: Comment, Position: position, Length: Length(i - position)}) - } else { - position := i - i++ - - for i < Position(len(buffer)) && isOperator(buffer[i]) { - i++ - } - - kind := Invalid - - switch string(buffer[position:i]) { - case "/": - kind = Div - case "/=": - kind = DivAssign - } - - tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(i - position)}) - } - + tokens, i = slash(tokens, buffer, i) continue - case '"', '\'': - limiter := buffer[i] - start := i - end := Position(len(buffer)) - i++ - - for i < Position(len(buffer)) { - if buffer[i] == limiter && (buffer[i-1] != '\\' || buffer[i-2] == '\\') { - end = i + 1 - i++ - break - } - - i++ - } - - kind := String - - if limiter == '\'' { - kind = Rune - } - - tokens = append(tokens, Token{Kind: kind, Position: start, Length: Length(end - start)}) + tokens, i = quote(tokens, buffer, i) continue - case '0': - position := i - i++ - - if i >= Position(len(buffer)) { - tokens = append(tokens, Token{Kind: Number, Position: position, Length: 1}) - break - } - - filter := isDigit - - switch buffer[i] { - case 'x': - i++ - filter = isHexDigit - - case 'b': - i++ - filter = isBinaryDigit - - case 'o': - i++ - filter = isOctalDigit - } - - for i < Position(len(buffer)) && filter(buffer[i]) { - i++ - } - - tokens = append(tokens, Token{Kind: Number, Position: position, Length: Length(i - position)}) + tokens, i = zero(tokens, buffer, i) continue - default: if isIdentifierStart(buffer[i]) { - position := i - i++ - - for i < Position(len(buffer)) && isIdentifier(buffer[i]) { - i++ - } - - identifier := buffer[position:i] - kind := Identifier - - switch string(identifier) { - case "assert": - kind = Assert - case "if": - kind = If - case "else": - kind = Else - case "import": - kind = Import - case "loop": - kind = Loop - case "return": - kind = Return - case "switch": - kind = Switch - } - - tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(len(identifier))}) + tokens, i = identifier(tokens, buffer, i) continue } if isDigit(buffer[i]) { - position := i - i++ - - for i < Position(len(buffer)) && isDigit(buffer[i]) { - i++ - } - - last := len(tokens) - 1 - - if len(tokens) > 0 && tokens[last].Kind == Negate { - tokens[last].Kind = Number - tokens[last].Length = Length(i-position) + 1 - } else { - tokens = append(tokens, Token{Kind: Number, Position: position, Length: Length(i - position)}) - } - + tokens, i = digit(tokens, buffer, i) continue } if isOperator(buffer[i]) { - position := i - i++ - - for i < Position(len(buffer)) && isOperator(buffer[i]) { - i++ - } - - kind := Invalid - - switch string(buffer[position:i]) { - case "!": - kind = Not - case "!=": - kind = NotEqual - case "%": - kind = Mod - case "%=": - kind = ModAssign - case "&": - kind = And - case "&&": - kind = LogicalAnd - case "&=": - kind = AndAssign - case "*": - kind = Mul - case "*=": - kind = MulAssign - case "+": - kind = Add - case "+=": - kind = AddAssign - // case "-": - // kind = Sub - // case "-=": - // kind = SubAssign - // case "->": - // kind = ReturnType - case ".": - kind = Period - // case "/": - // kind = Div - // case "/=": - // kind = DivAssign - case ":=": - kind = Define - case "<": - kind = Less - case "<<": - kind = Shl - case "<<=": - kind = ShlAssign - case "<=": - kind = LessEqual - case "=": - kind = Assign - case "==": - kind = Equal - case ">": - kind = Greater - case ">=": - kind = GreaterEqual - case ">>": - kind = Shr - case ">>=": - kind = ShrAssign - case "^": - kind = Xor - case "^=": - kind = XorAssign - case "|": - kind = Or - case "|=": - kind = OrAssign - case "||": - kind = LogicalOr - } - - tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(i - position)}) + tokens, i = operator(tokens, buffer, i) continue } @@ -279,40 +62,3 @@ func Tokenize(buffer []byte) List { tokens = append(tokens, Token{Kind: EOF, Position: i, Length: 0}) return tokens } - -func isIdentifier(c byte) bool { - return isLetter(c) || isDigit(c) || c == '_' -} - -func isIdentifierStart(c byte) bool { - return isLetter(c) || c == '_' -} - -func isLetter(c byte) bool { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') -} - -func isDigit(c byte) bool { - return c >= '0' && c <= '9' -} - -func isHexDigit(c byte) bool { - return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') -} - -func isBinaryDigit(c byte) bool { - return c == '0' || c == '1' -} - -func isOctalDigit(c byte) bool { - return c >= '0' && c <= '7' -} - -func isOperator(c byte) bool { - switch c { - case '=', ':', '.', '+', '-', '*', '/', '<', '>', '&', '|', '^', '%', '!': - return true - default: - return false - } -} diff --git a/src/token/dash.go b/src/token/dash.go new file mode 100644 index 0000000..63b5453 --- /dev/null +++ b/src/token/dash.go @@ -0,0 +1,25 @@ +package token + +// dash handles all tokens starting with '-'. +func dash(tokens List, buffer []byte, i Position) (List, Position) { + if len(tokens) == 0 || tokens[len(tokens)-1].IsOperator() || tokens[len(tokens)-1].IsExpressionStart() || tokens[len(tokens)-1].IsKeyword() { + tokens = append(tokens, Token{Kind: Negate, Position: i, Length: 1}) + } else { + if i+1 < Position(len(buffer)) { + switch buffer[i+1] { + case '=': + tokens = append(tokens, Token{Kind: SubAssign, Position: i, Length: 2}) + i++ + case '>': + tokens = append(tokens, Token{Kind: ReturnType, Position: i, Length: 2}) + i++ + default: + tokens = append(tokens, Token{Kind: Sub, Position: i, Length: 1}) + } + } else { + tokens = append(tokens, Token{Kind: Sub, Position: i, Length: 1}) + } + } + + return tokens, i +} diff --git a/src/token/digit.go b/src/token/digit.go new file mode 100644 index 0000000..ffdb192 --- /dev/null +++ b/src/token/digit.go @@ -0,0 +1,38 @@ +package token + +// digit handles all tokens that qualify as a digit. +func digit(tokens List, buffer []byte, i Position) (List, Position) { + position := i + i++ + + for i < Position(len(buffer)) && isDigit(buffer[i]) { + i++ + } + + last := len(tokens) - 1 + + if len(tokens) > 0 && tokens[last].Kind == Negate { + tokens[last].Kind = Number + tokens[last].Length = Length(i-position) + 1 + } else { + tokens = append(tokens, Token{Kind: Number, Position: position, Length: Length(i - position)}) + } + + return tokens, i +} + +func isDigit(c byte) bool { + return c >= '0' && c <= '9' +} + +func isHexDigit(c byte) bool { + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') +} + +func isBinaryDigit(c byte) bool { + return c == '0' || c == '1' +} + +func isOctalDigit(c byte) bool { + return c >= '0' && c <= '7' +} diff --git a/src/token/identifier.go b/src/token/identifier.go new file mode 100644 index 0000000..f44bcf6 --- /dev/null +++ b/src/token/identifier.go @@ -0,0 +1,46 @@ +package token + +// identifier handles all tokens that qualify as an identifier. +func identifier(tokens List, buffer []byte, i Position) (List, Position) { + position := i + i++ + + for i < Position(len(buffer)) && isIdentifier(buffer[i]) { + i++ + } + + identifier := buffer[position:i] + kind := Identifier + + switch string(identifier) { + case "assert": + kind = Assert + case "if": + kind = If + case "else": + kind = Else + case "import": + kind = Import + case "loop": + kind = Loop + case "return": + kind = Return + case "switch": + kind = Switch + } + + tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(len(identifier))}) + return tokens, i +} + +func isIdentifier(c byte) bool { + return isLetter(c) || isDigit(c) || c == '_' +} + +func isIdentifierStart(c byte) bool { + return isLetter(c) || c == '_' +} + +func isLetter(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') +} diff --git a/src/token/operator.go b/src/token/operator.go new file mode 100644 index 0000000..46251ad --- /dev/null +++ b/src/token/operator.go @@ -0,0 +1,84 @@ +package token + +// operator handles all tokens that qualify as an operator. +func operator(tokens List, buffer []byte, i Position) (List, Position) { + position := i + i++ + + for i < Position(len(buffer)) && isOperator(buffer[i]) { + i++ + } + + kind := Invalid + + switch string(buffer[position:i]) { + case "!": + kind = Not + case "!=": + kind = NotEqual + case "%": + kind = Mod + case "%=": + kind = ModAssign + case "&": + kind = And + case "&&": + kind = LogicalAnd + case "&=": + kind = AndAssign + case "*": + kind = Mul + case "*=": + kind = MulAssign + case "+": + kind = Add + case "+=": + kind = AddAssign + case ".": + kind = Period + case ":=": + kind = Define + case "<": + kind = Less + case "<<": + kind = Shl + case "<<=": + kind = ShlAssign + case "<=": + kind = LessEqual + case "=": + kind = Assign + case "==": + kind = Equal + case ">": + kind = Greater + case ">=": + kind = GreaterEqual + case ">>": + kind = Shr + case ">>=": + kind = ShrAssign + case "^": + kind = Xor + case "^=": + kind = XorAssign + case "|": + kind = Or + case "|=": + kind = OrAssign + case "||": + kind = LogicalOr + } + + tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(i - position)}) + return tokens, i +} + +func isOperator(c byte) bool { + switch c { + case '=', ':', '.', '+', '-', '*', '/', '<', '>', '&', '|', '^', '%', '!': + return true + default: + return false + } +} diff --git a/src/token/quote.go b/src/token/quote.go new file mode 100644 index 0000000..e49ca34 --- /dev/null +++ b/src/token/quote.go @@ -0,0 +1,28 @@ +package token + +// quote handles all tokens starting with a single or double quote. +func quote(tokens List, buffer []byte, i Position) (List, Position) { + limiter := buffer[i] + start := i + end := Position(len(buffer)) + i++ + + for i < Position(len(buffer)) { + if buffer[i] == limiter && (buffer[i-1] != '\\' || buffer[i-2] == '\\') { + end = i + 1 + i++ + break + } + + i++ + } + + kind := String + + if limiter == '\'' { + kind = Rune + } + + tokens = append(tokens, Token{Kind: kind, Position: start, Length: Length(end - start)}) + return tokens, i +} diff --git a/src/token/slash.go b/src/token/slash.go new file mode 100644 index 0000000..c8196a3 --- /dev/null +++ b/src/token/slash.go @@ -0,0 +1,34 @@ +package token + +// slash handles all tokens starting with '/'. +func slash(tokens List, buffer []byte, i Position) (List, Position) { + if i+1 < Position(len(buffer)) && buffer[i+1] == '/' { + position := i + + for i < Position(len(buffer)) && buffer[i] != '\n' { + i++ + } + + tokens = append(tokens, Token{Kind: Comment, Position: position, Length: Length(i - position)}) + } else { + position := i + i++ + + for i < Position(len(buffer)) && isOperator(buffer[i]) { + i++ + } + + kind := Invalid + + switch string(buffer[position:i]) { + case "/": + kind = Div + case "/=": + kind = DivAssign + } + + tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(i - position)}) + } + + return tokens, i +} diff --git a/src/token/zero.go b/src/token/zero.go new file mode 100644 index 0000000..df414c4 --- /dev/null +++ b/src/token/zero.go @@ -0,0 +1,35 @@ +package token + +// zero handles all tokens starting with a '0'. +func zero(tokens List, buffer []byte, i Position) (List, Position) { + position := i + i++ + + if i >= Position(len(buffer)) { + tokens = append(tokens, Token{Kind: Number, Position: position, Length: 1}) + return tokens, i + } + + filter := isDigit + + switch buffer[i] { + case 'x': + i++ + filter = isHexDigit + + case 'b': + i++ + filter = isBinaryDigit + + case 'o': + i++ + filter = isOctalDigit + } + + for i < Position(len(buffer)) && filter(buffer[i]) { + i++ + } + + tokens = append(tokens, Token{Kind: Number, Position: position, Length: Length(i - position)}) + return tokens, i +} From 858d0f21cf27e5200c0e3eb95e192bce486ddc92 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 2 Feb 2025 11:11:59 +0100 Subject: [PATCH 0610/1012] Simplified tokenizer --- src/ast/AST.go | 3 + src/token/Tokenize.go | 268 ++-------------------------------------- src/token/dash.go | 25 ++++ src/token/digit.go | 38 ++++++ src/token/identifier.go | 46 +++++++ src/token/operator.go | 84 +++++++++++++ src/token/quote.go | 28 +++++ src/token/slash.go | 34 +++++ src/token/zero.go | 35 ++++++ 9 files changed, 300 insertions(+), 261 deletions(-) create mode 100644 src/token/dash.go create mode 100644 src/token/digit.go create mode 100644 src/token/identifier.go create mode 100644 src/token/operator.go create mode 100644 src/token/quote.go create mode 100644 src/token/slash.go create mode 100644 src/token/zero.go diff --git a/src/ast/AST.go b/src/ast/AST.go index c7c4c47..8dcaa24 100644 --- a/src/ast/AST.go +++ b/src/ast/AST.go @@ -1,4 +1,7 @@ package ast +// Node is an interface used for all types of AST nodes. type Node any + +// AST is an abstract syntax tree which is simply a list of nodes. type AST []Node diff --git a/src/token/Tokenize.go b/src/token/Tokenize.go index 355b3df..9178314 100644 --- a/src/token/Tokenize.go +++ b/src/token/Tokenize.go @@ -27,246 +27,29 @@ func Tokenize(buffer []byte) List { case '\n': tokens = append(tokens, Token{Kind: NewLine, Position: i, Length: 1}) case '-': - if len(tokens) == 0 || tokens[len(tokens)-1].IsOperator() || tokens[len(tokens)-1].IsExpressionStart() || tokens[len(tokens)-1].IsKeyword() { - tokens = append(tokens, Token{Kind: Negate, Position: i, Length: 1}) - } else { - if i+1 < Position(len(buffer)) { - switch buffer[i+1] { - case '=': - tokens = append(tokens, Token{Kind: SubAssign, Position: i, Length: 2}) - i++ - case '>': - tokens = append(tokens, Token{Kind: ReturnType, Position: i, Length: 2}) - i++ - default: - tokens = append(tokens, Token{Kind: Sub, Position: i, Length: 1}) - } - } else { - tokens = append(tokens, Token{Kind: Sub, Position: i, Length: 1}) - } - } - + tokens, i = dash(tokens, buffer, i) case '/': - if i+1 < Position(len(buffer)) && buffer[i+1] == '/' { - position := i - - for i < Position(len(buffer)) && buffer[i] != '\n' { - i++ - } - - tokens = append(tokens, Token{Kind: Comment, Position: position, Length: Length(i - position)}) - } else { - position := i - i++ - - for i < Position(len(buffer)) && isOperator(buffer[i]) { - i++ - } - - kind := Invalid - - switch string(buffer[position:i]) { - case "/": - kind = Div - case "/=": - kind = DivAssign - } - - tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(i - position)}) - } - + tokens, i = slash(tokens, buffer, i) continue - case '"', '\'': - limiter := buffer[i] - start := i - end := Position(len(buffer)) - i++ - - for i < Position(len(buffer)) { - if buffer[i] == limiter && (buffer[i-1] != '\\' || buffer[i-2] == '\\') { - end = i + 1 - i++ - break - } - - i++ - } - - kind := String - - if limiter == '\'' { - kind = Rune - } - - tokens = append(tokens, Token{Kind: kind, Position: start, Length: Length(end - start)}) + tokens, i = quote(tokens, buffer, i) continue - case '0': - position := i - i++ - - if i >= Position(len(buffer)) { - tokens = append(tokens, Token{Kind: Number, Position: position, Length: 1}) - break - } - - filter := isDigit - - switch buffer[i] { - case 'x': - i++ - filter = isHexDigit - - case 'b': - i++ - filter = isBinaryDigit - - case 'o': - i++ - filter = isOctalDigit - } - - for i < Position(len(buffer)) && filter(buffer[i]) { - i++ - } - - tokens = append(tokens, Token{Kind: Number, Position: position, Length: Length(i - position)}) + tokens, i = zero(tokens, buffer, i) continue - default: if isIdentifierStart(buffer[i]) { - position := i - i++ - - for i < Position(len(buffer)) && isIdentifier(buffer[i]) { - i++ - } - - identifier := buffer[position:i] - kind := Identifier - - switch string(identifier) { - case "assert": - kind = Assert - case "if": - kind = If - case "else": - kind = Else - case "import": - kind = Import - case "loop": - kind = Loop - case "return": - kind = Return - case "switch": - kind = Switch - } - - tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(len(identifier))}) + tokens, i = identifier(tokens, buffer, i) continue } if isDigit(buffer[i]) { - position := i - i++ - - for i < Position(len(buffer)) && isDigit(buffer[i]) { - i++ - } - - last := len(tokens) - 1 - - if len(tokens) > 0 && tokens[last].Kind == Negate { - tokens[last].Kind = Number - tokens[last].Length = Length(i-position) + 1 - } else { - tokens = append(tokens, Token{Kind: Number, Position: position, Length: Length(i - position)}) - } - + tokens, i = digit(tokens, buffer, i) continue } if isOperator(buffer[i]) { - position := i - i++ - - for i < Position(len(buffer)) && isOperator(buffer[i]) { - i++ - } - - kind := Invalid - - switch string(buffer[position:i]) { - case "!": - kind = Not - case "!=": - kind = NotEqual - case "%": - kind = Mod - case "%=": - kind = ModAssign - case "&": - kind = And - case "&&": - kind = LogicalAnd - case "&=": - kind = AndAssign - case "*": - kind = Mul - case "*=": - kind = MulAssign - case "+": - kind = Add - case "+=": - kind = AddAssign - // case "-": - // kind = Sub - // case "-=": - // kind = SubAssign - // case "->": - // kind = ReturnType - case ".": - kind = Period - // case "/": - // kind = Div - // case "/=": - // kind = DivAssign - case ":=": - kind = Define - case "<": - kind = Less - case "<<": - kind = Shl - case "<<=": - kind = ShlAssign - case "<=": - kind = LessEqual - case "=": - kind = Assign - case "==": - kind = Equal - case ">": - kind = Greater - case ">=": - kind = GreaterEqual - case ">>": - kind = Shr - case ">>=": - kind = ShrAssign - case "^": - kind = Xor - case "^=": - kind = XorAssign - case "|": - kind = Or - case "|=": - kind = OrAssign - case "||": - kind = LogicalOr - } - - tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(i - position)}) + tokens, i = operator(tokens, buffer, i) continue } @@ -279,40 +62,3 @@ func Tokenize(buffer []byte) List { tokens = append(tokens, Token{Kind: EOF, Position: i, Length: 0}) return tokens } - -func isIdentifier(c byte) bool { - return isLetter(c) || isDigit(c) || c == '_' -} - -func isIdentifierStart(c byte) bool { - return isLetter(c) || c == '_' -} - -func isLetter(c byte) bool { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') -} - -func isDigit(c byte) bool { - return c >= '0' && c <= '9' -} - -func isHexDigit(c byte) bool { - return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') -} - -func isBinaryDigit(c byte) bool { - return c == '0' || c == '1' -} - -func isOctalDigit(c byte) bool { - return c >= '0' && c <= '7' -} - -func isOperator(c byte) bool { - switch c { - case '=', ':', '.', '+', '-', '*', '/', '<', '>', '&', '|', '^', '%', '!': - return true - default: - return false - } -} diff --git a/src/token/dash.go b/src/token/dash.go new file mode 100644 index 0000000..63b5453 --- /dev/null +++ b/src/token/dash.go @@ -0,0 +1,25 @@ +package token + +// dash handles all tokens starting with '-'. +func dash(tokens List, buffer []byte, i Position) (List, Position) { + if len(tokens) == 0 || tokens[len(tokens)-1].IsOperator() || tokens[len(tokens)-1].IsExpressionStart() || tokens[len(tokens)-1].IsKeyword() { + tokens = append(tokens, Token{Kind: Negate, Position: i, Length: 1}) + } else { + if i+1 < Position(len(buffer)) { + switch buffer[i+1] { + case '=': + tokens = append(tokens, Token{Kind: SubAssign, Position: i, Length: 2}) + i++ + case '>': + tokens = append(tokens, Token{Kind: ReturnType, Position: i, Length: 2}) + i++ + default: + tokens = append(tokens, Token{Kind: Sub, Position: i, Length: 1}) + } + } else { + tokens = append(tokens, Token{Kind: Sub, Position: i, Length: 1}) + } + } + + return tokens, i +} diff --git a/src/token/digit.go b/src/token/digit.go new file mode 100644 index 0000000..ffdb192 --- /dev/null +++ b/src/token/digit.go @@ -0,0 +1,38 @@ +package token + +// digit handles all tokens that qualify as a digit. +func digit(tokens List, buffer []byte, i Position) (List, Position) { + position := i + i++ + + for i < Position(len(buffer)) && isDigit(buffer[i]) { + i++ + } + + last := len(tokens) - 1 + + if len(tokens) > 0 && tokens[last].Kind == Negate { + tokens[last].Kind = Number + tokens[last].Length = Length(i-position) + 1 + } else { + tokens = append(tokens, Token{Kind: Number, Position: position, Length: Length(i - position)}) + } + + return tokens, i +} + +func isDigit(c byte) bool { + return c >= '0' && c <= '9' +} + +func isHexDigit(c byte) bool { + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') +} + +func isBinaryDigit(c byte) bool { + return c == '0' || c == '1' +} + +func isOctalDigit(c byte) bool { + return c >= '0' && c <= '7' +} diff --git a/src/token/identifier.go b/src/token/identifier.go new file mode 100644 index 0000000..f44bcf6 --- /dev/null +++ b/src/token/identifier.go @@ -0,0 +1,46 @@ +package token + +// identifier handles all tokens that qualify as an identifier. +func identifier(tokens List, buffer []byte, i Position) (List, Position) { + position := i + i++ + + for i < Position(len(buffer)) && isIdentifier(buffer[i]) { + i++ + } + + identifier := buffer[position:i] + kind := Identifier + + switch string(identifier) { + case "assert": + kind = Assert + case "if": + kind = If + case "else": + kind = Else + case "import": + kind = Import + case "loop": + kind = Loop + case "return": + kind = Return + case "switch": + kind = Switch + } + + tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(len(identifier))}) + return tokens, i +} + +func isIdentifier(c byte) bool { + return isLetter(c) || isDigit(c) || c == '_' +} + +func isIdentifierStart(c byte) bool { + return isLetter(c) || c == '_' +} + +func isLetter(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') +} diff --git a/src/token/operator.go b/src/token/operator.go new file mode 100644 index 0000000..46251ad --- /dev/null +++ b/src/token/operator.go @@ -0,0 +1,84 @@ +package token + +// operator handles all tokens that qualify as an operator. +func operator(tokens List, buffer []byte, i Position) (List, Position) { + position := i + i++ + + for i < Position(len(buffer)) && isOperator(buffer[i]) { + i++ + } + + kind := Invalid + + switch string(buffer[position:i]) { + case "!": + kind = Not + case "!=": + kind = NotEqual + case "%": + kind = Mod + case "%=": + kind = ModAssign + case "&": + kind = And + case "&&": + kind = LogicalAnd + case "&=": + kind = AndAssign + case "*": + kind = Mul + case "*=": + kind = MulAssign + case "+": + kind = Add + case "+=": + kind = AddAssign + case ".": + kind = Period + case ":=": + kind = Define + case "<": + kind = Less + case "<<": + kind = Shl + case "<<=": + kind = ShlAssign + case "<=": + kind = LessEqual + case "=": + kind = Assign + case "==": + kind = Equal + case ">": + kind = Greater + case ">=": + kind = GreaterEqual + case ">>": + kind = Shr + case ">>=": + kind = ShrAssign + case "^": + kind = Xor + case "^=": + kind = XorAssign + case "|": + kind = Or + case "|=": + kind = OrAssign + case "||": + kind = LogicalOr + } + + tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(i - position)}) + return tokens, i +} + +func isOperator(c byte) bool { + switch c { + case '=', ':', '.', '+', '-', '*', '/', '<', '>', '&', '|', '^', '%', '!': + return true + default: + return false + } +} diff --git a/src/token/quote.go b/src/token/quote.go new file mode 100644 index 0000000..e49ca34 --- /dev/null +++ b/src/token/quote.go @@ -0,0 +1,28 @@ +package token + +// quote handles all tokens starting with a single or double quote. +func quote(tokens List, buffer []byte, i Position) (List, Position) { + limiter := buffer[i] + start := i + end := Position(len(buffer)) + i++ + + for i < Position(len(buffer)) { + if buffer[i] == limiter && (buffer[i-1] != '\\' || buffer[i-2] == '\\') { + end = i + 1 + i++ + break + } + + i++ + } + + kind := String + + if limiter == '\'' { + kind = Rune + } + + tokens = append(tokens, Token{Kind: kind, Position: start, Length: Length(end - start)}) + return tokens, i +} diff --git a/src/token/slash.go b/src/token/slash.go new file mode 100644 index 0000000..c8196a3 --- /dev/null +++ b/src/token/slash.go @@ -0,0 +1,34 @@ +package token + +// slash handles all tokens starting with '/'. +func slash(tokens List, buffer []byte, i Position) (List, Position) { + if i+1 < Position(len(buffer)) && buffer[i+1] == '/' { + position := i + + for i < Position(len(buffer)) && buffer[i] != '\n' { + i++ + } + + tokens = append(tokens, Token{Kind: Comment, Position: position, Length: Length(i - position)}) + } else { + position := i + i++ + + for i < Position(len(buffer)) && isOperator(buffer[i]) { + i++ + } + + kind := Invalid + + switch string(buffer[position:i]) { + case "/": + kind = Div + case "/=": + kind = DivAssign + } + + tokens = append(tokens, Token{Kind: kind, Position: position, Length: Length(i - position)}) + } + + return tokens, i +} diff --git a/src/token/zero.go b/src/token/zero.go new file mode 100644 index 0000000..df414c4 --- /dev/null +++ b/src/token/zero.go @@ -0,0 +1,35 @@ +package token + +// zero handles all tokens starting with a '0'. +func zero(tokens List, buffer []byte, i Position) (List, Position) { + position := i + i++ + + if i >= Position(len(buffer)) { + tokens = append(tokens, Token{Kind: Number, Position: position, Length: 1}) + return tokens, i + } + + filter := isDigit + + switch buffer[i] { + case 'x': + i++ + filter = isHexDigit + + case 'b': + i++ + filter = isBinaryDigit + + case 'o': + i++ + filter = isOctalDigit + } + + for i < Position(len(buffer)) && filter(buffer[i]) { + i++ + } + + tokens = append(tokens, Token{Kind: Number, Position: position, Length: Length(i - position)}) + return tokens, i +} From 6c432154e64a461ef9f142586c123e2b1f4af7b1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 2 Feb 2025 11:38:55 +0100 Subject: [PATCH 0611/1012] Removed unnecessary benchmarks --- src/token/bench_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/token/bench_test.go b/src/token/bench_test.go index a502019..3c54881 100644 --- a/src/token/bench_test.go +++ b/src/token/bench_test.go @@ -8,9 +8,7 @@ import ( ) func BenchmarkLines(b *testing.B) { - b.Run("__1", bench(1)) - b.Run("_10", bench(10)) - b.Run("100", bench(100)) + b.Run("1000", bench(1000)) } func bench(n int) func(b *testing.B) { From dd9222c162a681e53540e6a1a9f7842dc1ae4817 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 2 Feb 2025 11:38:55 +0100 Subject: [PATCH 0612/1012] Removed unnecessary benchmarks --- src/token/bench_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/token/bench_test.go b/src/token/bench_test.go index a502019..3c54881 100644 --- a/src/token/bench_test.go +++ b/src/token/bench_test.go @@ -8,9 +8,7 @@ import ( ) func BenchmarkLines(b *testing.B) { - b.Run("__1", bench(1)) - b.Run("_10", bench(10)) - b.Run("100", bench(100)) + b.Run("1000", bench(1000)) } func bench(n int) func(b *testing.B) { From a0388ae15aff6156554480e3359d365b55bf1a9d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 2 Feb 2025 11:48:08 +0100 Subject: [PATCH 0613/1012] Simplified the assembler --- src/asm/CodeOffset.go | 26 +++++ src/asm/Finalize.go | 128 ++--------------------- src/asm/{Pointer.go => pointer.go} | 4 +- src/asm/resolvePointers.go | 104 ++++++++++++++++++ src/asm/{Optimizer.go => unnecessary.go} | 6 +- 5 files changed, 146 insertions(+), 122 deletions(-) create mode 100644 src/asm/CodeOffset.go rename src/asm/{Pointer.go => pointer.go} (78%) create mode 100644 src/asm/resolvePointers.go rename src/asm/{Optimizer.go => unnecessary.go} (72%) diff --git a/src/asm/CodeOffset.go b/src/asm/CodeOffset.go new file mode 100644 index 0000000..4b8d4ba --- /dev/null +++ b/src/asm/CodeOffset.go @@ -0,0 +1,26 @@ +package asm + +import ( + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/macho" + "git.akyoto.dev/cli/q/src/pe" +) + +// CodeOffset returns the file offset of the code section. +func CodeOffset() Address { + headerEnd := Address(0) + + switch config.TargetOS { + case config.Linux: + headerEnd = elf.HeaderEnd + case config.Mac: + headerEnd = macho.HeaderEnd + case config.Windows: + headerEnd = pe.HeaderEnd + } + + offset, _ := fs.Align(headerEnd, config.Align) + return offset +} diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index bff469b..f5393b5 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -1,19 +1,11 @@ package asm import ( - "encoding/binary" - "fmt" "math" - "slices" "strings" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/elf" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/macho" - "git.akyoto.dev/cli/q/src/pe" - "git.akyoto.dev/cli/q/src/sizeof" "git.akyoto.dev/cli/q/src/x64" ) @@ -24,23 +16,12 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { data []byte codeLabels = map[string]Address{} dataLabels map[string]Address - codePointers []*Pointer - dataPointers []*Pointer - dllPointers []*Pointer - headerEnd = Address(0) + codePointers []*pointer + dataPointers []*pointer + dllPointers []*pointer + codeStart = CodeOffset() ) - switch config.TargetOS { - case config.Linux: - headerEnd = elf.HeaderEnd - case config.Mac: - headerEnd = macho.HeaderEnd - case config.Windows: - headerEnd = pe.HeaderEnd - } - - codeStart, _ := fs.Align(headerEnd, config.Align) - for _, x := range a.Instructions { switch x.Mnemonic { case ADD: @@ -110,7 +91,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { size := 4 label := x.Data.(*Label) - pointer := &Pointer{ + pointer := &pointer{ Position: Address(len(code) - size), OpSize: 1, Size: uint8(size), @@ -151,7 +132,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { code = x64.MoveRegisterRegister(code, x64.RSP, x64.R15) label := x.Data.(*Label) - pointer := &Pointer{ + pointer := &pointer{ Position: Address(position), OpSize: 2, Size: uint8(size), @@ -193,7 +174,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { size := 1 label := x.Data.(*Label) - pointer := &Pointer{ + pointer := &pointer{ Position: Address(len(code) - size), OpSize: 1, Size: uint8(size), @@ -237,7 +218,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { regLabel := x.Data.(*RegisterLabel) if strings.HasPrefix(regLabel.Label, "data_") { - dataPointers = append(dataPointers, &Pointer{ + dataPointers = append(dataPointers, &pointer{ Position: Address(len(code) - size), OpSize: uint8(opSize), Size: uint8(size), @@ -252,7 +233,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { }, }) } else { - codePointers = append(codePointers, &Pointer{ + codePointers = append(codePointers, &pointer{ Position: Address(len(code) - size), OpSize: uint8(opSize), Size: uint8(size), @@ -331,7 +312,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { opSize := len(code) - size - start memLabel := x.Data.(*MemoryLabel) - codePointers = append(codePointers, &Pointer{ + codePointers = append(codePointers, &pointer{ Position: Address(len(code) - size), OpSize: uint8(opSize), Size: uint8(size), @@ -369,94 +350,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { } } -restart: - for i, pointer := range codePointers { - address := pointer.Resolve() - - if sizeof.Signed(int64(address)) > int(pointer.Size) { - left := code[:pointer.Position-Address(pointer.OpSize)] - right := code[pointer.Position+Address(pointer.Size):] - size := pointer.Size + pointer.OpSize - opCode := code[pointer.Position-Address(pointer.OpSize)] - - var jump []byte - - switch opCode { - case 0x74: // JE - jump = []byte{0x0F, 0x84} - case 0x75: // JNE - jump = []byte{0x0F, 0x85} - case 0x7C: // JL - jump = []byte{0x0F, 0x8C} - case 0x7D: // JGE - jump = []byte{0x0F, 0x8D} - case 0x7E: // JLE - jump = []byte{0x0F, 0x8E} - case 0x7F: // JG - jump = []byte{0x0F, 0x8F} - case 0xEB: // JMP - jump = []byte{0xE9} - default: - panic(fmt.Errorf("failed to increase pointer size for instruction 0x%x", opCode)) - } - - pointer.Position += Address(len(jump) - int(pointer.OpSize)) - pointer.OpSize = uint8(len(jump)) - pointer.Size = 4 - jump = binary.LittleEndian.AppendUint32(jump, uint32(address)) - offset := Address(len(jump)) - Address(size) - - for _, following := range codePointers[i+1:] { - following.Position += offset - } - - for key, address := range codeLabels { - if address > pointer.Position { - codeLabels[key] += offset - } - } - - code = slices.Concat(left, jump, right) - goto restart - } - - slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] - - switch pointer.Size { - case 1: - slice[0] = uint8(address) - case 2: - binary.LittleEndian.PutUint16(slice, uint16(address)) - case 4: - binary.LittleEndian.PutUint32(slice, uint32(address)) - case 8: - binary.LittleEndian.PutUint64(slice, uint64(address)) - } - } - - dataStart, _ := fs.Align(codeStart+Address(len(code)), config.Align) data, dataLabels = a.Data.Finalize() - - for _, pointer := range dataPointers { - address := config.BaseAddress + Address(dataStart) + pointer.Resolve() - slice := code[pointer.Position : pointer.Position+4] - binary.LittleEndian.PutUint32(slice, uint32(address)) - } - - if config.TargetOS == config.Windows { - if len(data) == 0 { - data = []byte{0} - } - - importsStart, _ := fs.Align(dataStart+Address(len(data)), config.Align) - - for _, pointer := range dllPointers { - destination := Address(importsStart) + pointer.Resolve() - delta := destination - Address(codeStart+pointer.Position+Address(pointer.Size)) - slice := code[pointer.Position : pointer.Position+4] - binary.LittleEndian.PutUint32(slice, uint32(delta)) - } - } - + code = a.resolvePointers(code, data, codeStart, codeLabels, codePointers, dataPointers, dllPointers) return code, data } diff --git a/src/asm/Pointer.go b/src/asm/pointer.go similarity index 78% rename from src/asm/Pointer.go rename to src/asm/pointer.go index bb4f9d1..1d4880b 100644 --- a/src/asm/Pointer.go +++ b/src/asm/pointer.go @@ -3,10 +3,10 @@ package asm // Address represents a memory address. type Address = int32 -// Pointer stores a relative memory address that we can later turn into an absolute one. +// pointer stores a relative memory address that we can later turn into an absolute one. // Position: The machine code offset where the address was inserted. // Resolve: The function that will return the final address. -type Pointer struct { +type pointer struct { Resolve func() Address Position Address OpSize uint8 diff --git a/src/asm/resolvePointers.go b/src/asm/resolvePointers.go new file mode 100644 index 0000000..58ef114 --- /dev/null +++ b/src/asm/resolvePointers.go @@ -0,0 +1,104 @@ +package asm + +import ( + "encoding/binary" + "fmt" + "slices" + + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/sizeof" +) + +// resolvePointers resolves the addresses of all pointers within the code and writes the correct addresses to the code slice. +func (a Assembler) resolvePointers(code []byte, data []byte, codeStart Address, codeLabels map[string]Address, codePointers []*pointer, dataPointers []*pointer, dllPointers []*pointer) []byte { +restart: + for i, pointer := range codePointers { + address := pointer.Resolve() + + if sizeof.Signed(int64(address)) > int(pointer.Size) { + left := code[:pointer.Position-Address(pointer.OpSize)] + right := code[pointer.Position+Address(pointer.Size):] + size := pointer.Size + pointer.OpSize + opCode := code[pointer.Position-Address(pointer.OpSize)] + + var jump []byte + + switch opCode { + case 0x74: // JE + jump = []byte{0x0F, 0x84} + case 0x75: // JNE + jump = []byte{0x0F, 0x85} + case 0x7C: // JL + jump = []byte{0x0F, 0x8C} + case 0x7D: // JGE + jump = []byte{0x0F, 0x8D} + case 0x7E: // JLE + jump = []byte{0x0F, 0x8E} + case 0x7F: // JG + jump = []byte{0x0F, 0x8F} + case 0xEB: // JMP + jump = []byte{0xE9} + default: + panic(fmt.Errorf("failed to increase pointer size for instruction 0x%x", opCode)) + } + + pointer.Position += Address(len(jump) - int(pointer.OpSize)) + pointer.OpSize = uint8(len(jump)) + pointer.Size = 4 + jump = binary.LittleEndian.AppendUint32(jump, uint32(address)) + offset := Address(len(jump)) - Address(size) + + for _, following := range codePointers[i+1:] { + following.Position += offset + } + + for key, address := range codeLabels { + if address > pointer.Position { + codeLabels[key] += offset + } + } + + code = slices.Concat(left, jump, right) + goto restart + } + + slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] + + switch pointer.Size { + case 1: + slice[0] = uint8(address) + case 2: + binary.LittleEndian.PutUint16(slice, uint16(address)) + case 4: + binary.LittleEndian.PutUint32(slice, uint32(address)) + case 8: + binary.LittleEndian.PutUint64(slice, uint64(address)) + } + } + + dataStart, _ := fs.Align(codeStart+Address(len(code)), config.Align) + + for _, pointer := range dataPointers { + address := config.BaseAddress + Address(dataStart) + pointer.Resolve() + slice := code[pointer.Position : pointer.Position+4] + binary.LittleEndian.PutUint32(slice, uint32(address)) + } + + if config.TargetOS == config.Windows { + if len(data) == 0 { + data = []byte{0} + } + + importsStart, _ := fs.Align(dataStart+Address(len(data)), config.Align) + + for _, pointer := range dllPointers { + destination := Address(importsStart) + pointer.Resolve() + delta := destination - Address(codeStart+pointer.Position+Address(pointer.Size)) + slice := code[pointer.Position : pointer.Position+4] + binary.LittleEndian.PutUint32(slice, uint32(delta)) + } + } + + return code +} diff --git a/src/asm/Optimizer.go b/src/asm/unnecessary.go similarity index 72% rename from src/asm/Optimizer.go rename to src/asm/unnecessary.go index 7bf6b99..c3a915a 100644 --- a/src/asm/Optimizer.go +++ b/src/asm/unnecessary.go @@ -11,17 +11,17 @@ func (a *Assembler) unnecessary(mnemonic Mnemonic, left cpu.Register, right cpu. last := a.Instructions[len(a.Instructions)-1] if mnemonic == MOVE && last.Mnemonic == MOVE { - data, isRegReg := last.Data.(*RegisterRegister) + lastData, isRegReg := last.Data.(*RegisterRegister) if !isRegReg { return false } - if data.Destination == left && data.Source == right { + if lastData.Destination == left && lastData.Source == right { return true } - if data.Destination == right && data.Source == left { + if lastData.Destination == right && lastData.Source == left { return true } } From 11d2521aee1ffc7906bee2df393f7258e8963120 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 2 Feb 2025 11:48:08 +0100 Subject: [PATCH 0614/1012] Simplified the assembler --- src/asm/CodeOffset.go | 26 +++++ src/asm/Finalize.go | 128 ++--------------------- src/asm/{Pointer.go => pointer.go} | 4 +- src/asm/resolvePointers.go | 104 ++++++++++++++++++ src/asm/{Optimizer.go => unnecessary.go} | 6 +- 5 files changed, 146 insertions(+), 122 deletions(-) create mode 100644 src/asm/CodeOffset.go rename src/asm/{Pointer.go => pointer.go} (78%) create mode 100644 src/asm/resolvePointers.go rename src/asm/{Optimizer.go => unnecessary.go} (72%) diff --git a/src/asm/CodeOffset.go b/src/asm/CodeOffset.go new file mode 100644 index 0000000..4b8d4ba --- /dev/null +++ b/src/asm/CodeOffset.go @@ -0,0 +1,26 @@ +package asm + +import ( + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/macho" + "git.akyoto.dev/cli/q/src/pe" +) + +// CodeOffset returns the file offset of the code section. +func CodeOffset() Address { + headerEnd := Address(0) + + switch config.TargetOS { + case config.Linux: + headerEnd = elf.HeaderEnd + case config.Mac: + headerEnd = macho.HeaderEnd + case config.Windows: + headerEnd = pe.HeaderEnd + } + + offset, _ := fs.Align(headerEnd, config.Align) + return offset +} diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index bff469b..f5393b5 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -1,19 +1,11 @@ package asm import ( - "encoding/binary" - "fmt" "math" - "slices" "strings" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/elf" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/macho" - "git.akyoto.dev/cli/q/src/pe" - "git.akyoto.dev/cli/q/src/sizeof" "git.akyoto.dev/cli/q/src/x64" ) @@ -24,23 +16,12 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { data []byte codeLabels = map[string]Address{} dataLabels map[string]Address - codePointers []*Pointer - dataPointers []*Pointer - dllPointers []*Pointer - headerEnd = Address(0) + codePointers []*pointer + dataPointers []*pointer + dllPointers []*pointer + codeStart = CodeOffset() ) - switch config.TargetOS { - case config.Linux: - headerEnd = elf.HeaderEnd - case config.Mac: - headerEnd = macho.HeaderEnd - case config.Windows: - headerEnd = pe.HeaderEnd - } - - codeStart, _ := fs.Align(headerEnd, config.Align) - for _, x := range a.Instructions { switch x.Mnemonic { case ADD: @@ -110,7 +91,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { size := 4 label := x.Data.(*Label) - pointer := &Pointer{ + pointer := &pointer{ Position: Address(len(code) - size), OpSize: 1, Size: uint8(size), @@ -151,7 +132,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { code = x64.MoveRegisterRegister(code, x64.RSP, x64.R15) label := x.Data.(*Label) - pointer := &Pointer{ + pointer := &pointer{ Position: Address(position), OpSize: 2, Size: uint8(size), @@ -193,7 +174,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { size := 1 label := x.Data.(*Label) - pointer := &Pointer{ + pointer := &pointer{ Position: Address(len(code) - size), OpSize: 1, Size: uint8(size), @@ -237,7 +218,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { regLabel := x.Data.(*RegisterLabel) if strings.HasPrefix(regLabel.Label, "data_") { - dataPointers = append(dataPointers, &Pointer{ + dataPointers = append(dataPointers, &pointer{ Position: Address(len(code) - size), OpSize: uint8(opSize), Size: uint8(size), @@ -252,7 +233,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { }, }) } else { - codePointers = append(codePointers, &Pointer{ + codePointers = append(codePointers, &pointer{ Position: Address(len(code) - size), OpSize: uint8(opSize), Size: uint8(size), @@ -331,7 +312,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { opSize := len(code) - size - start memLabel := x.Data.(*MemoryLabel) - codePointers = append(codePointers, &Pointer{ + codePointers = append(codePointers, &pointer{ Position: Address(len(code) - size), OpSize: uint8(opSize), Size: uint8(size), @@ -369,94 +350,7 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { } } -restart: - for i, pointer := range codePointers { - address := pointer.Resolve() - - if sizeof.Signed(int64(address)) > int(pointer.Size) { - left := code[:pointer.Position-Address(pointer.OpSize)] - right := code[pointer.Position+Address(pointer.Size):] - size := pointer.Size + pointer.OpSize - opCode := code[pointer.Position-Address(pointer.OpSize)] - - var jump []byte - - switch opCode { - case 0x74: // JE - jump = []byte{0x0F, 0x84} - case 0x75: // JNE - jump = []byte{0x0F, 0x85} - case 0x7C: // JL - jump = []byte{0x0F, 0x8C} - case 0x7D: // JGE - jump = []byte{0x0F, 0x8D} - case 0x7E: // JLE - jump = []byte{0x0F, 0x8E} - case 0x7F: // JG - jump = []byte{0x0F, 0x8F} - case 0xEB: // JMP - jump = []byte{0xE9} - default: - panic(fmt.Errorf("failed to increase pointer size for instruction 0x%x", opCode)) - } - - pointer.Position += Address(len(jump) - int(pointer.OpSize)) - pointer.OpSize = uint8(len(jump)) - pointer.Size = 4 - jump = binary.LittleEndian.AppendUint32(jump, uint32(address)) - offset := Address(len(jump)) - Address(size) - - for _, following := range codePointers[i+1:] { - following.Position += offset - } - - for key, address := range codeLabels { - if address > pointer.Position { - codeLabels[key] += offset - } - } - - code = slices.Concat(left, jump, right) - goto restart - } - - slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] - - switch pointer.Size { - case 1: - slice[0] = uint8(address) - case 2: - binary.LittleEndian.PutUint16(slice, uint16(address)) - case 4: - binary.LittleEndian.PutUint32(slice, uint32(address)) - case 8: - binary.LittleEndian.PutUint64(slice, uint64(address)) - } - } - - dataStart, _ := fs.Align(codeStart+Address(len(code)), config.Align) data, dataLabels = a.Data.Finalize() - - for _, pointer := range dataPointers { - address := config.BaseAddress + Address(dataStart) + pointer.Resolve() - slice := code[pointer.Position : pointer.Position+4] - binary.LittleEndian.PutUint32(slice, uint32(address)) - } - - if config.TargetOS == config.Windows { - if len(data) == 0 { - data = []byte{0} - } - - importsStart, _ := fs.Align(dataStart+Address(len(data)), config.Align) - - for _, pointer := range dllPointers { - destination := Address(importsStart) + pointer.Resolve() - delta := destination - Address(codeStart+pointer.Position+Address(pointer.Size)) - slice := code[pointer.Position : pointer.Position+4] - binary.LittleEndian.PutUint32(slice, uint32(delta)) - } - } - + code = a.resolvePointers(code, data, codeStart, codeLabels, codePointers, dataPointers, dllPointers) return code, data } diff --git a/src/asm/Pointer.go b/src/asm/pointer.go similarity index 78% rename from src/asm/Pointer.go rename to src/asm/pointer.go index bb4f9d1..1d4880b 100644 --- a/src/asm/Pointer.go +++ b/src/asm/pointer.go @@ -3,10 +3,10 @@ package asm // Address represents a memory address. type Address = int32 -// Pointer stores a relative memory address that we can later turn into an absolute one. +// pointer stores a relative memory address that we can later turn into an absolute one. // Position: The machine code offset where the address was inserted. // Resolve: The function that will return the final address. -type Pointer struct { +type pointer struct { Resolve func() Address Position Address OpSize uint8 diff --git a/src/asm/resolvePointers.go b/src/asm/resolvePointers.go new file mode 100644 index 0000000..58ef114 --- /dev/null +++ b/src/asm/resolvePointers.go @@ -0,0 +1,104 @@ +package asm + +import ( + "encoding/binary" + "fmt" + "slices" + + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/sizeof" +) + +// resolvePointers resolves the addresses of all pointers within the code and writes the correct addresses to the code slice. +func (a Assembler) resolvePointers(code []byte, data []byte, codeStart Address, codeLabels map[string]Address, codePointers []*pointer, dataPointers []*pointer, dllPointers []*pointer) []byte { +restart: + for i, pointer := range codePointers { + address := pointer.Resolve() + + if sizeof.Signed(int64(address)) > int(pointer.Size) { + left := code[:pointer.Position-Address(pointer.OpSize)] + right := code[pointer.Position+Address(pointer.Size):] + size := pointer.Size + pointer.OpSize + opCode := code[pointer.Position-Address(pointer.OpSize)] + + var jump []byte + + switch opCode { + case 0x74: // JE + jump = []byte{0x0F, 0x84} + case 0x75: // JNE + jump = []byte{0x0F, 0x85} + case 0x7C: // JL + jump = []byte{0x0F, 0x8C} + case 0x7D: // JGE + jump = []byte{0x0F, 0x8D} + case 0x7E: // JLE + jump = []byte{0x0F, 0x8E} + case 0x7F: // JG + jump = []byte{0x0F, 0x8F} + case 0xEB: // JMP + jump = []byte{0xE9} + default: + panic(fmt.Errorf("failed to increase pointer size for instruction 0x%x", opCode)) + } + + pointer.Position += Address(len(jump) - int(pointer.OpSize)) + pointer.OpSize = uint8(len(jump)) + pointer.Size = 4 + jump = binary.LittleEndian.AppendUint32(jump, uint32(address)) + offset := Address(len(jump)) - Address(size) + + for _, following := range codePointers[i+1:] { + following.Position += offset + } + + for key, address := range codeLabels { + if address > pointer.Position { + codeLabels[key] += offset + } + } + + code = slices.Concat(left, jump, right) + goto restart + } + + slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] + + switch pointer.Size { + case 1: + slice[0] = uint8(address) + case 2: + binary.LittleEndian.PutUint16(slice, uint16(address)) + case 4: + binary.LittleEndian.PutUint32(slice, uint32(address)) + case 8: + binary.LittleEndian.PutUint64(slice, uint64(address)) + } + } + + dataStart, _ := fs.Align(codeStart+Address(len(code)), config.Align) + + for _, pointer := range dataPointers { + address := config.BaseAddress + Address(dataStart) + pointer.Resolve() + slice := code[pointer.Position : pointer.Position+4] + binary.LittleEndian.PutUint32(slice, uint32(address)) + } + + if config.TargetOS == config.Windows { + if len(data) == 0 { + data = []byte{0} + } + + importsStart, _ := fs.Align(dataStart+Address(len(data)), config.Align) + + for _, pointer := range dllPointers { + destination := Address(importsStart) + pointer.Resolve() + delta := destination - Address(codeStart+pointer.Position+Address(pointer.Size)) + slice := code[pointer.Position : pointer.Position+4] + binary.LittleEndian.PutUint32(slice, uint32(delta)) + } + } + + return code +} diff --git a/src/asm/Optimizer.go b/src/asm/unnecessary.go similarity index 72% rename from src/asm/Optimizer.go rename to src/asm/unnecessary.go index 7bf6b99..c3a915a 100644 --- a/src/asm/Optimizer.go +++ b/src/asm/unnecessary.go @@ -11,17 +11,17 @@ func (a *Assembler) unnecessary(mnemonic Mnemonic, left cpu.Register, right cpu. last := a.Instructions[len(a.Instructions)-1] if mnemonic == MOVE && last.Mnemonic == MOVE { - data, isRegReg := last.Data.(*RegisterRegister) + lastData, isRegReg := last.Data.(*RegisterRegister) if !isRegReg { return false } - if data.Destination == left && data.Source == right { + if lastData.Destination == left && lastData.Source == right { return true } - if data.Destination == right && data.Source == left { + if lastData.Destination == right && lastData.Source == left { return true } } From 819a4a907b879c4d108fa8cb58f36138bcf74ef9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 2 Feb 2025 15:17:52 +0100 Subject: [PATCH 0615/1012] Added stack specific flags for thread stack allocation --- lib/thread/{thread.q => thread_linux.q} | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename lib/thread/{thread.q => thread_linux.q} (74%) diff --git a/lib/thread/thread.q b/lib/thread/thread_linux.q similarity index 74% rename from lib/thread/thread.q rename to lib/thread/thread_linux.q index d2b2b73..eaa873b 100644 --- a/lib/thread/thread.q +++ b/lib/thread/thread_linux.q @@ -1,9 +1,8 @@ -import mem import sys create(func Pointer) -> Int { size := 4096 - stack := mem.alloc(size) + stack := sys.mmap(0, size, 0x1|0x2, 0x02|0x20|0x100|0x20000) rip := stack + size - 8 store(rip, 8, func) return sys.clone(0x100|0x200|0x400|0x800|0x8000|0x10000|0x80000000, rip) From 271ae0fb18edcb0f8b7bbe8c496477438fd6ec95 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 2 Feb 2025 15:17:52 +0100 Subject: [PATCH 0616/1012] Added stack specific flags for thread stack allocation --- lib/thread/{thread.q => thread_linux.q} | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename lib/thread/{thread.q => thread_linux.q} (74%) diff --git a/lib/thread/thread.q b/lib/thread/thread_linux.q similarity index 74% rename from lib/thread/thread.q rename to lib/thread/thread_linux.q index d2b2b73..eaa873b 100644 --- a/lib/thread/thread.q +++ b/lib/thread/thread_linux.q @@ -1,9 +1,8 @@ -import mem import sys create(func Pointer) -> Int { size := 4096 - stack := mem.alloc(size) + stack := sys.mmap(0, size, 0x1|0x2, 0x02|0x20|0x100|0x20000) rip := stack + size - 8 store(rip, 8, func) return sys.clone(0x100|0x200|0x400|0x800|0x8000|0x10000|0x80000000, rip) From 0db4d4fc14fa091e65cb72de2405d987cde84839 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 2 Feb 2025 17:04:58 +0100 Subject: [PATCH 0617/1012] Added time.sleep function --- examples/thread/thread.q | 2 ++ lib/sys/sys_linux.q | 4 ++-- lib/time/time.q | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 lib/time/time.q diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 350168c..929ebbb 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -1,5 +1,6 @@ import sys import thread +import time main() { thread.create(work) @@ -10,6 +11,7 @@ main() { work() { sys.write(1, "[ ] start\n", 10) + time.sleep(10 * 1000 * 1000) sys.write(1, "[x] end\n", 8) sys.exit(0) } \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q index 73fcb76..c1207f7 100644 --- a/lib/sys/sys_linux.q +++ b/lib/sys/sys_linux.q @@ -22,8 +22,8 @@ munmap(address Pointer, length Int) -> Int { return syscall(11, address, length) } -nanosleep(requested Pointer, remaining Pointer) -> Int { - return syscall(35, requested, remaining) +nanosleep(timespec Pointer) -> Int { + return syscall(35, timespec, 0) } clone(flags Int, stack Pointer) -> Int { diff --git a/lib/time/time.q b/lib/time/time.q new file mode 100644 index 0000000..0d2d749 --- /dev/null +++ b/lib/time/time.q @@ -0,0 +1,21 @@ +import mem +import sys + +sleep(nanoseconds Int) { + seconds := 0 + + loop { + if nanoseconds >= 1000000000 { + nanoseconds = nanoseconds - 1000000000 + seconds += 1 + } else { + timespec := mem.alloc(16) + store(timespec, 8, seconds) + offset := timespec + 8 + store(offset, 8, nanoseconds) + sys.nanosleep(timespec) + mem.free(timespec, 16) + return + } + } +} \ No newline at end of file From 715635aaa7289f9aeb0a6877d894bc28cb55a3bc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 2 Feb 2025 17:04:58 +0100 Subject: [PATCH 0618/1012] Added time.sleep function --- examples/thread/thread.q | 2 ++ lib/sys/sys_linux.q | 4 ++-- lib/time/time.q | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 lib/time/time.q diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 350168c..929ebbb 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -1,5 +1,6 @@ import sys import thread +import time main() { thread.create(work) @@ -10,6 +11,7 @@ main() { work() { sys.write(1, "[ ] start\n", 10) + time.sleep(10 * 1000 * 1000) sys.write(1, "[x] end\n", 8) sys.exit(0) } \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q index 73fcb76..c1207f7 100644 --- a/lib/sys/sys_linux.q +++ b/lib/sys/sys_linux.q @@ -22,8 +22,8 @@ munmap(address Pointer, length Int) -> Int { return syscall(11, address, length) } -nanosleep(requested Pointer, remaining Pointer) -> Int { - return syscall(35, requested, remaining) +nanosleep(timespec Pointer) -> Int { + return syscall(35, timespec, 0) } clone(flags Int, stack Pointer) -> Int { diff --git a/lib/time/time.q b/lib/time/time.q new file mode 100644 index 0000000..0d2d749 --- /dev/null +++ b/lib/time/time.q @@ -0,0 +1,21 @@ +import mem +import sys + +sleep(nanoseconds Int) { + seconds := 0 + + loop { + if nanoseconds >= 1000000000 { + nanoseconds = nanoseconds - 1000000000 + seconds += 1 + } else { + timespec := mem.alloc(16) + store(timespec, 8, seconds) + offset := timespec + 8 + store(offset, 8, nanoseconds) + sys.nanosleep(timespec) + mem.free(timespec, 16) + return + } + } +} \ No newline at end of file From d3823e9cfe5451e7533b53db7b7a02be9ff8f3b8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 3 Feb 2025 13:13:17 +0100 Subject: [PATCH 0619/1012] Added return count mismatch error --- src/core/CompileReturn.go | 4 ++++ src/errors/ReturnCountMismatch.go | 26 ++++++++++++++++++++++++++ tests/errors/ReturnCountMismatch.q | 3 +++ tests/errors_test.go | 3 ++- 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/errors/ReturnCountMismatch.go create mode 100644 tests/errors/ReturnCountMismatch.q diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index ab901f2..8fa72c8 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -14,6 +14,10 @@ func (f *Function) CompileReturn(node *ast.Return) error { return nil } + if len(node.Values) != len(f.ReturnTypes) { + return errors.New(&errors.ReturnCountMismatch{Count: len(node.Values), ExpectedCount: len(f.ReturnTypes)}, f.File, node.Values[0].Token.Position) + } + for i := len(node.Values) - 1; i >= 0; i-- { typ, err := f.ExpressionToRegister(node.Values[i], f.CPU.Output[i]) diff --git a/src/errors/ReturnCountMismatch.go b/src/errors/ReturnCountMismatch.go new file mode 100644 index 0000000..726f63d --- /dev/null +++ b/src/errors/ReturnCountMismatch.go @@ -0,0 +1,26 @@ +package errors + +import "fmt" + +// ReturnCountMismatch error is created when the number of returned values doesn't match the return type. +type ReturnCountMismatch struct { + Count int + ExpectedCount int +} + +// Error generates the string representation. +func (err *ReturnCountMismatch) Error() string { + values := "values" + + if err.Count == 1 { + values = "value" + } + + types := "types" + + if err.ExpectedCount == 1 { + types = "type" + } + + return fmt.Sprintf("Returns %d %s in a function with %d return %s", err.Count, values, err.ExpectedCount, types) +} diff --git a/tests/errors/ReturnCountMismatch.q b/tests/errors/ReturnCountMismatch.q new file mode 100644 index 0000000..eea1ba5 --- /dev/null +++ b/tests/errors/ReturnCountMismatch.q @@ -0,0 +1,3 @@ +main() { + return 42 +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 7ecdddc..9de4094 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -37,7 +37,7 @@ var errs = []struct { {"MissingMainFunction.q", errors.MissingMainFunction}, {"MissingOperand.q", errors.MissingOperand}, {"MissingOperand2.q", errors.MissingOperand}, - {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, + {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, {"TypeMismatch.q", &errors.TypeMismatch{Expected: "Pointer", Encountered: "Int64", ParameterName: "p"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, @@ -47,6 +47,7 @@ var errs = []struct { {"UnknownPackage.q", &errors.UnknownPackage{Name: "sys"}}, {"UnusedImport.q", &errors.UnusedImport{Package: "sys"}}, {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, + {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, } func TestErrors(t *testing.T) { From 646dafd2168df594d874f374658fa82c7b7b0a12 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 3 Feb 2025 13:13:17 +0100 Subject: [PATCH 0620/1012] Added return count mismatch error --- src/core/CompileReturn.go | 4 ++++ src/errors/ReturnCountMismatch.go | 26 ++++++++++++++++++++++++++ tests/errors/ReturnCountMismatch.q | 3 +++ tests/errors_test.go | 3 ++- 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/errors/ReturnCountMismatch.go create mode 100644 tests/errors/ReturnCountMismatch.q diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index ab901f2..8fa72c8 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -14,6 +14,10 @@ func (f *Function) CompileReturn(node *ast.Return) error { return nil } + if len(node.Values) != len(f.ReturnTypes) { + return errors.New(&errors.ReturnCountMismatch{Count: len(node.Values), ExpectedCount: len(f.ReturnTypes)}, f.File, node.Values[0].Token.Position) + } + for i := len(node.Values) - 1; i >= 0; i-- { typ, err := f.ExpressionToRegister(node.Values[i], f.CPU.Output[i]) diff --git a/src/errors/ReturnCountMismatch.go b/src/errors/ReturnCountMismatch.go new file mode 100644 index 0000000..726f63d --- /dev/null +++ b/src/errors/ReturnCountMismatch.go @@ -0,0 +1,26 @@ +package errors + +import "fmt" + +// ReturnCountMismatch error is created when the number of returned values doesn't match the return type. +type ReturnCountMismatch struct { + Count int + ExpectedCount int +} + +// Error generates the string representation. +func (err *ReturnCountMismatch) Error() string { + values := "values" + + if err.Count == 1 { + values = "value" + } + + types := "types" + + if err.ExpectedCount == 1 { + types = "type" + } + + return fmt.Sprintf("Returns %d %s in a function with %d return %s", err.Count, values, err.ExpectedCount, types) +} diff --git a/tests/errors/ReturnCountMismatch.q b/tests/errors/ReturnCountMismatch.q new file mode 100644 index 0000000..eea1ba5 --- /dev/null +++ b/tests/errors/ReturnCountMismatch.q @@ -0,0 +1,3 @@ +main() { + return 42 +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 7ecdddc..9de4094 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -37,7 +37,7 @@ var errs = []struct { {"MissingMainFunction.q", errors.MissingMainFunction}, {"MissingOperand.q", errors.MissingOperand}, {"MissingOperand2.q", errors.MissingOperand}, - {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, + {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, {"TypeMismatch.q", &errors.TypeMismatch{Expected: "Pointer", Encountered: "Int64", ParameterName: "p"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, @@ -47,6 +47,7 @@ var errs = []struct { {"UnknownPackage.q", &errors.UnknownPackage{Name: "sys"}}, {"UnusedImport.q", &errors.UnusedImport{Package: "sys"}}, {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, + {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, } func TestErrors(t *testing.T) { From f2f0624638f8fdd64becf580d922886c53275055 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 3 Feb 2025 13:17:48 +0100 Subject: [PATCH 0621/1012] Fixed missing register move in if statements --- lib/time/time.q | 22 +++++++++------------- src/core/CompileAssign.go | 27 ++++++++++++++++++++++++++- src/core/CompileIf.go | 4 ++++ tests/programs/branch-save.q | 23 +++++++++++++++++++++++ tests/programs_test.go | 1 + 5 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 tests/programs/branch-save.q diff --git a/lib/time/time.q b/lib/time/time.q index 0d2d749..9890e67 100644 --- a/lib/time/time.q +++ b/lib/time/time.q @@ -4,18 +4,14 @@ import sys sleep(nanoseconds Int) { seconds := 0 - loop { - if nanoseconds >= 1000000000 { - nanoseconds = nanoseconds - 1000000000 - seconds += 1 - } else { - timespec := mem.alloc(16) - store(timespec, 8, seconds) - offset := timespec + 8 - store(offset, 8, nanoseconds) - sys.nanosleep(timespec) - mem.free(timespec, 16) - return - } + if nanoseconds >= 1000000000 { + seconds, nanoseconds = nanoseconds / 1000000000 } + + timespec := mem.alloc(16) + store(timespec, 8, seconds) + offset := timespec + 8 + store(offset, 8, nanoseconds) + sys.nanosleep(timespec) + mem.free(timespec, 16) } \ No newline at end of file diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index d048b80..8949cbc 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -1,8 +1,10 @@ package core import ( + "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/token" ) @@ -32,5 +34,28 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return f.CompileAssignDivision(node) } - return errors.New(errors.NotImplemented, f.File, left.Token.Position) + if !ast.IsFunctionCall(right) { + return errors.New(errors.NotImplemented, f.File, node.Expression.Token.Position) + } + + count := 0 + _, err := f.CompileCall(right) + + if err != nil { + return err + } + + return left.EachLeaf(func(leaf *expression.Expression) error { + name := leaf.Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) + } + + f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) + f.UseVariable(variable) + count++ + return nil + }) } diff --git a/src/core/CompileIf.go b/src/core/CompileIf.go index e7ce92c..029d7e1 100644 --- a/src/core/CompileIf.go +++ b/src/core/CompileIf.go @@ -9,6 +9,10 @@ import ( // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { + for _, register := range f.CPU.Input { + f.SaveRegister(register) + } + f.count.branch++ var ( diff --git a/tests/programs/branch-save.q b/tests/programs/branch-save.q new file mode 100644 index 0000000..75ad7bf --- /dev/null +++ b/tests/programs/branch-save.q @@ -0,0 +1,23 @@ +main() { + a, b := f(5) + assert a == 0 + assert b == 5 + + a, b = f(15) + assert a == 1 + assert b == 5 + + a, b = f(25) + assert a == 2 + assert b == 5 +} + +f(b Int) -> (Int, Int) { + a := 0 + + if b >= 10 { + a, b = b / 10 + } + + return a, b +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 23fc44a..8a6bbb3 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -54,6 +54,7 @@ var programs = []struct { {"branch-and", "", "", 0}, {"branch-or", "", "", 0}, {"branch-both", "", "", 0}, + {"branch-save", "", "", 0}, {"jump-near", "", "", 0}, {"switch", "", "", 0}, {"loop", "", "", 0}, From cb6b3a4cd0535702b2cf683a49e82730be3cb2c3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 3 Feb 2025 13:17:48 +0100 Subject: [PATCH 0622/1012] Fixed missing register move in if statements --- lib/time/time.q | 22 +++++++++------------- src/core/CompileAssign.go | 27 ++++++++++++++++++++++++++- src/core/CompileIf.go | 4 ++++ tests/programs/branch-save.q | 23 +++++++++++++++++++++++ tests/programs_test.go | 1 + 5 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 tests/programs/branch-save.q diff --git a/lib/time/time.q b/lib/time/time.q index 0d2d749..9890e67 100644 --- a/lib/time/time.q +++ b/lib/time/time.q @@ -4,18 +4,14 @@ import sys sleep(nanoseconds Int) { seconds := 0 - loop { - if nanoseconds >= 1000000000 { - nanoseconds = nanoseconds - 1000000000 - seconds += 1 - } else { - timespec := mem.alloc(16) - store(timespec, 8, seconds) - offset := timespec + 8 - store(offset, 8, nanoseconds) - sys.nanosleep(timespec) - mem.free(timespec, 16) - return - } + if nanoseconds >= 1000000000 { + seconds, nanoseconds = nanoseconds / 1000000000 } + + timespec := mem.alloc(16) + store(timespec, 8, seconds) + offset := timespec + 8 + store(offset, 8, nanoseconds) + sys.nanosleep(timespec) + mem.free(timespec, 16) } \ No newline at end of file diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index d048b80..8949cbc 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -1,8 +1,10 @@ package core import ( + "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/token" ) @@ -32,5 +34,28 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return f.CompileAssignDivision(node) } - return errors.New(errors.NotImplemented, f.File, left.Token.Position) + if !ast.IsFunctionCall(right) { + return errors.New(errors.NotImplemented, f.File, node.Expression.Token.Position) + } + + count := 0 + _, err := f.CompileCall(right) + + if err != nil { + return err + } + + return left.EachLeaf(func(leaf *expression.Expression) error { + name := leaf.Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) + } + + f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) + f.UseVariable(variable) + count++ + return nil + }) } diff --git a/src/core/CompileIf.go b/src/core/CompileIf.go index e7ce92c..029d7e1 100644 --- a/src/core/CompileIf.go +++ b/src/core/CompileIf.go @@ -9,6 +9,10 @@ import ( // CompileIf compiles a branch instruction. func (f *Function) CompileIf(branch *ast.If) error { + for _, register := range f.CPU.Input { + f.SaveRegister(register) + } + f.count.branch++ var ( diff --git a/tests/programs/branch-save.q b/tests/programs/branch-save.q new file mode 100644 index 0000000..75ad7bf --- /dev/null +++ b/tests/programs/branch-save.q @@ -0,0 +1,23 @@ +main() { + a, b := f(5) + assert a == 0 + assert b == 5 + + a, b = f(15) + assert a == 1 + assert b == 5 + + a, b = f(25) + assert a == 2 + assert b == 5 +} + +f(b Int) -> (Int, Int) { + a := 0 + + if b >= 10 { + a, b = b / 10 + } + + return a, b +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 23fc44a..8a6bbb3 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -54,6 +54,7 @@ var programs = []struct { {"branch-and", "", "", 0}, {"branch-or", "", "", 0}, {"branch-both", "", "", 0}, + {"branch-save", "", "", 0}, {"jump-near", "", "", 0}, {"switch", "", "", 0}, {"loop", "", "", 0}, From e962ac379c50509b64f04cbfaeb2e743c7ab64b3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 3 Feb 2025 14:23:23 +0100 Subject: [PATCH 0623/1012] Fixed incorrect section offsets on Windows --- src/asm/Finalize.go | 5 +++++ src/asm/resolvePointers.go | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index f5393b5..0915e6b 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -351,6 +351,11 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { } data, dataLabels = a.Data.Finalize() + + if config.TargetOS == config.Windows && len(data) == 0 { + data = []byte{0} + } + code = a.resolvePointers(code, data, codeStart, codeLabels, codePointers, dataPointers, dllPointers) return code, data } diff --git a/src/asm/resolvePointers.go b/src/asm/resolvePointers.go index 58ef114..40ecbb9 100644 --- a/src/asm/resolvePointers.go +++ b/src/asm/resolvePointers.go @@ -86,10 +86,6 @@ restart: } if config.TargetOS == config.Windows { - if len(data) == 0 { - data = []byte{0} - } - importsStart, _ := fs.Align(dataStart+Address(len(data)), config.Align) for _, pointer := range dllPointers { From 6af02d8fa38571457d14b14810e5b6cde4b99d89 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 3 Feb 2025 14:23:23 +0100 Subject: [PATCH 0624/1012] Fixed incorrect section offsets on Windows --- src/asm/Finalize.go | 5 +++++ src/asm/resolvePointers.go | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index f5393b5..0915e6b 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -351,6 +351,11 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { } data, dataLabels = a.Data.Finalize() + + if config.TargetOS == config.Windows && len(data) == 0 { + data = []byte{0} + } + code = a.resolvePointers(code, data, codeStart, codeLabels, codePointers, dataPointers, dllPointers) return code, data } diff --git a/src/asm/resolvePointers.go b/src/asm/resolvePointers.go index 58ef114..40ecbb9 100644 --- a/src/asm/resolvePointers.go +++ b/src/asm/resolvePointers.go @@ -86,10 +86,6 @@ restart: } if config.TargetOS == config.Windows { - if len(data) == 0 { - data = []byte{0} - } - importsStart, _ := fs.Align(dataStart+Address(len(data)), config.Align) for _, pointer := range dllPointers { From e439516059fb45a604a6bb8bcf6fa2ddc87e7667 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 3 Feb 2025 15:22:57 +0100 Subject: [PATCH 0625/1012] Simplified identifier lookup --- src/core/Define.go | 5 +++-- src/core/ExpressionToMemory.go | 11 +++-------- src/core/Identifier.go | 25 +++++++++++++++++++++++++ src/core/IdentifierExists.go | 13 ------------- src/core/TokenToRegister.go | 11 +++-------- 5 files changed, 34 insertions(+), 31 deletions(-) create mode 100644 src/core/Identifier.go delete mode 100644 src/core/IdentifierExists.go diff --git a/src/core/Define.go b/src/core/Define.go index ecd4178..94f7428 100644 --- a/src/core/Define.go +++ b/src/core/Define.go @@ -10,8 +10,9 @@ import ( // Define defines a new variable. func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) { name := leaf.Token.Text(f.File.Bytes) + variable, _ := f.Identifier(name) - if f.IdentifierExists(name) { + if variable != nil { return nil, errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, leaf.Token.Position) } @@ -21,7 +22,7 @@ func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) return nil, errors.New(&errors.UnusedVariable{Name: name}, f.File, leaf.Token.Position) } - variable := &scope.Variable{ + variable = &scope.Variable{ Name: name, Register: f.NewRegister(), Alive: uses, diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index bd84657..29ca917 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" @@ -16,7 +14,7 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me if node.IsLeaf() { if node.Token.Kind == token.Identifier { name := node.Token.Text(f.File.Bytes) - variable := f.VariableByName(name) + variable, function := f.Identifier(name) if variable != nil { f.UseVariable(variable) @@ -24,11 +22,8 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me return types.Pointer, nil } - uniqueName := fmt.Sprintf("%s.%s", f.Package, name) - _, exists := f.Functions[uniqueName] - - if exists { - f.MemoryLabel(asm.STORE, memory, uniqueName) + if function != nil { + f.MemoryLabel(asm.STORE, memory, function.UniqueName) return types.Pointer, nil } diff --git a/src/core/Identifier.go b/src/core/Identifier.go new file mode 100644 index 0000000..0150c56 --- /dev/null +++ b/src/core/Identifier.go @@ -0,0 +1,25 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/scope" +) + +// Identifier looks up an identifier which can be a variable or a function. +func (f *Function) Identifier(name string) (*scope.Variable, *Function) { + variable := f.VariableByName(name) + + if variable != nil { + return variable, nil + } + + uniqueName := fmt.Sprintf("%s.%s", f.Package, name) + function, exists := f.Functions[uniqueName] + + if exists { + return nil, function + } + + return nil, nil +} diff --git a/src/core/IdentifierExists.go b/src/core/IdentifierExists.go deleted file mode 100644 index efa3ed7..0000000 --- a/src/core/IdentifierExists.go +++ /dev/null @@ -1,13 +0,0 @@ -package core - -// IdentifierExists returns true if the identifier has been defined. -func (f *Function) IdentifierExists(name string) bool { - variable := f.VariableByName(name) - - if variable != nil { - return true - } - - _, exists := f.Functions[name] - return exists -} diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index 0b189e5..8f8d53a 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" @@ -16,7 +14,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (*types switch t.Kind { case token.Identifier: name := t.Text(f.File.Bytes) - variable := f.VariableByName(name) + variable, function := f.Identifier(name) if variable != nil { f.UseVariable(variable) @@ -25,12 +23,9 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (*types return variable.Type, nil } - uniqueName := fmt.Sprintf("%s.%s", f.Package, name) - _, exists := f.Functions[uniqueName] - - if exists { + if function != nil { f.SaveRegister(register) - f.RegisterLabel(asm.MOVE, register, uniqueName) + f.RegisterLabel(asm.MOVE, register, function.UniqueName) return types.Pointer, nil } From 6959379182af36ef3bfac36854dec0ce1269cafa Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 3 Feb 2025 15:22:57 +0100 Subject: [PATCH 0626/1012] Simplified identifier lookup --- src/core/Define.go | 5 +++-- src/core/ExpressionToMemory.go | 11 +++-------- src/core/Identifier.go | 25 +++++++++++++++++++++++++ src/core/IdentifierExists.go | 13 ------------- src/core/TokenToRegister.go | 11 +++-------- 5 files changed, 34 insertions(+), 31 deletions(-) create mode 100644 src/core/Identifier.go delete mode 100644 src/core/IdentifierExists.go diff --git a/src/core/Define.go b/src/core/Define.go index ecd4178..94f7428 100644 --- a/src/core/Define.go +++ b/src/core/Define.go @@ -10,8 +10,9 @@ import ( // Define defines a new variable. func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) { name := leaf.Token.Text(f.File.Bytes) + variable, _ := f.Identifier(name) - if f.IdentifierExists(name) { + if variable != nil { return nil, errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, leaf.Token.Position) } @@ -21,7 +22,7 @@ func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) return nil, errors.New(&errors.UnusedVariable{Name: name}, f.File, leaf.Token.Position) } - variable := &scope.Variable{ + variable = &scope.Variable{ Name: name, Register: f.NewRegister(), Alive: uses, diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index bd84657..29ca917 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" @@ -16,7 +14,7 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me if node.IsLeaf() { if node.Token.Kind == token.Identifier { name := node.Token.Text(f.File.Bytes) - variable := f.VariableByName(name) + variable, function := f.Identifier(name) if variable != nil { f.UseVariable(variable) @@ -24,11 +22,8 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me return types.Pointer, nil } - uniqueName := fmt.Sprintf("%s.%s", f.Package, name) - _, exists := f.Functions[uniqueName] - - if exists { - f.MemoryLabel(asm.STORE, memory, uniqueName) + if function != nil { + f.MemoryLabel(asm.STORE, memory, function.UniqueName) return types.Pointer, nil } diff --git a/src/core/Identifier.go b/src/core/Identifier.go new file mode 100644 index 0000000..0150c56 --- /dev/null +++ b/src/core/Identifier.go @@ -0,0 +1,25 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/scope" +) + +// Identifier looks up an identifier which can be a variable or a function. +func (f *Function) Identifier(name string) (*scope.Variable, *Function) { + variable := f.VariableByName(name) + + if variable != nil { + return variable, nil + } + + uniqueName := fmt.Sprintf("%s.%s", f.Package, name) + function, exists := f.Functions[uniqueName] + + if exists { + return nil, function + } + + return nil, nil +} diff --git a/src/core/IdentifierExists.go b/src/core/IdentifierExists.go deleted file mode 100644 index efa3ed7..0000000 --- a/src/core/IdentifierExists.go +++ /dev/null @@ -1,13 +0,0 @@ -package core - -// IdentifierExists returns true if the identifier has been defined. -func (f *Function) IdentifierExists(name string) bool { - variable := f.VariableByName(name) - - if variable != nil { - return true - } - - _, exists := f.Functions[name] - return exists -} diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index 0b189e5..8f8d53a 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" @@ -16,7 +14,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (*types switch t.Kind { case token.Identifier: name := t.Text(f.File.Bytes) - variable := f.VariableByName(name) + variable, function := f.Identifier(name) if variable != nil { f.UseVariable(variable) @@ -25,12 +23,9 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (*types return variable.Type, nil } - uniqueName := fmt.Sprintf("%s.%s", f.Package, name) - _, exists := f.Functions[uniqueName] - - if exists { + if function != nil { f.SaveRegister(register) - f.RegisterLabel(asm.MOVE, register, uniqueName) + f.RegisterLabel(asm.MOVE, register, function.UniqueName) return types.Pointer, nil } From 2d592169e4f77d03a9529223444b870d297fcebc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 3 Feb 2025 21:20:48 +0100 Subject: [PATCH 0627/1012] Simplified register usage visualization --- src/core/PrintInstructions.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/PrintInstructions.go b/src/core/PrintInstructions.go index fe47d3a..6020cf7 100644 --- a/src/core/PrintInstructions.go +++ b/src/core/PrintInstructions.go @@ -37,9 +37,9 @@ func (f *Function) PrintInstructions() { for _, reg := range f.CPU.All { if used&(1< Date: Mon, 3 Feb 2025 21:20:48 +0100 Subject: [PATCH 0628/1012] Simplified register usage visualization --- src/core/PrintInstructions.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/PrintInstructions.go b/src/core/PrintInstructions.go index fe47d3a..6020cf7 100644 --- a/src/core/PrintInstructions.go +++ b/src/core/PrintInstructions.go @@ -37,9 +37,9 @@ func (f *Function) PrintInstructions() { for _, reg := range f.CPU.All { if used&(1< Date: Tue, 4 Feb 2025 00:00:43 +0100 Subject: [PATCH 0629/1012] Forced the evaluation of symlinks --- src/config/init.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/config/init.go b/src/config/init.go index 8ef2be7..d1f9d75 100644 --- a/src/config/init.go +++ b/src/config/init.go @@ -25,6 +25,12 @@ func init() { panic(err) } + Executable, err = filepath.EvalSymlinks(Executable) + + if err != nil { + panic(err) + } + WorkingDirectory, err = os.Getwd() if err != nil { From fc1b970f7f461ca7b8dc86f3e29a945784cc06df Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 4 Feb 2025 00:00:43 +0100 Subject: [PATCH 0630/1012] Forced the evaluation of symlinks --- src/config/init.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/config/init.go b/src/config/init.go index 8ef2be7..d1f9d75 100644 --- a/src/config/init.go +++ b/src/config/init.go @@ -25,6 +25,12 @@ func init() { panic(err) } + Executable, err = filepath.EvalSymlinks(Executable) + + if err != nil { + panic(err) + } + WorkingDirectory, err = os.Getwd() if err != nil { From 30940f01005f49bc09d9c0818b5e65d82eefd92b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 4 Feb 2025 14:41:04 +0100 Subject: [PATCH 0631/1012] Implemented struct parser --- lib/time/timespec.q | 4 + src/build/Build.go | 4 +- src/compiler/Compile.go | 12 +- src/errors/Common.go | 1 + src/scanner/Scan.go | 7 +- src/scanner/Scanner.go | 2 + src/scanner/scanFile.go | 261 ++---------------- src/scanner/scanFunction.go | 222 +++++++++++++++ src/scanner/scanImport.go | 41 +++ src/scanner/scanStruct.go | 66 +++++ src/token/Kind.go | 1 + src/token/Tokenize_test.go | 3 +- src/token/identifier.go | 2 + tests/errors/ExpectedStructName.q | 1 + tests/errors/InvalidInstructionString.q | 3 + ...ionName.q => InvalidInstructionTopLevel.q} | 0 ...nName2.q => InvalidInstructionTopLevel2.q} | 0 tests/errors/InvalidInstructionTopLevel3.q | 1 + tests/errors_test.go | 9 +- 19 files changed, 388 insertions(+), 252 deletions(-) create mode 100644 lib/time/timespec.q create mode 100644 src/scanner/scanFunction.go create mode 100644 src/scanner/scanImport.go create mode 100644 src/scanner/scanStruct.go create mode 100644 tests/errors/ExpectedStructName.q create mode 100644 tests/errors/InvalidInstructionString.q rename tests/errors/{ExpectedFunctionName.q => InvalidInstructionTopLevel.q} (100%) rename tests/errors/{ExpectedFunctionName2.q => InvalidInstructionTopLevel2.q} (100%) create mode 100644 tests/errors/InvalidInstructionTopLevel3.q diff --git a/lib/time/timespec.q b/lib/time/timespec.q new file mode 100644 index 0000000..84d53d6 --- /dev/null +++ b/lib/time/timespec.q @@ -0,0 +1,4 @@ +struct timespec { + seconds Int + nanoseconds Int +} \ No newline at end of file diff --git a/src/build/Build.go b/src/build/Build.go index 52c9f5f..e3b620e 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -23,8 +23,8 @@ func New(files ...string) *Build { // Run compiles the input files. func (build *Build) Run() (compiler.Result, error) { - files, functions, errors := scanner.Scan(build.Files) - return compiler.Compile(files, functions, errors) + files, functions, types, errors := scanner.Scan(build.Files) + return compiler.Compile(files, functions, types, errors) } // Executable returns the path to the executable. diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 5d17cd5..dd35a48 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -6,13 +6,15 @@ import ( "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/types" ) // Compile waits for the scan to finish and compiles all functions. -func Compile(files <-chan *fs.File, functions <-chan *core.Function, errs <-chan error) (Result, error) { +func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Type, errs <-chan error) (Result, error) { result := Result{} allFiles := make([]*fs.File, 0, 8) allFunctions := map[string]*core.Function{} + allTypes := map[string]*types.Type{} for functions != nil || files != nil || errs != nil { select { @@ -25,6 +27,14 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, errs <-chan function.Functions = allFunctions allFunctions[function.UniqueName] = function + case typ, ok := <-structs: + if !ok { + structs = nil + continue + } + + allTypes[typ.Name] = typ + case file, ok := <-files: if !ok { files = nil diff --git a/src/errors/Common.go b/src/errors/Common.go index a4ebdc9..ab301d2 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -6,6 +6,7 @@ var ( ExpectedFunctionParameters = &Base{"Expected function parameters"} ExpectedFunctionDefinition = &Base{"Expected function definition"} ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} + ExpectedStructName = &Base{"Expected struct name"} InvalidNumber = &Base{"Invalid number"} InvalidExpression = &Base{"Invalid expression"} InvalidRune = &Base{"Invalid rune"} diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go index f18cbff..df17032 100644 --- a/src/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -3,13 +3,15 @@ package scanner import ( "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/types" ) // Scan scans the list of files. -func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan error) { +func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan *types.Type, <-chan error) { scanner := Scanner{ files: make(chan *fs.File), functions: make(chan *core.Function), + types: make(chan *types.Type), errors: make(chan error), } @@ -18,8 +20,9 @@ func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan error) scanner.group.Wait() close(scanner.files) close(scanner.functions) + close(scanner.types) close(scanner.errors) }() - return scanner.files, scanner.functions, scanner.errors + return scanner.files, scanner.functions, scanner.types, scanner.errors } diff --git a/src/scanner/Scanner.go b/src/scanner/Scanner.go index 434fe30..f1f0835 100644 --- a/src/scanner/Scanner.go +++ b/src/scanner/Scanner.go @@ -5,12 +5,14 @@ import ( "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/types" ) // Scanner is used to scan files before the actual compilation step. type Scanner struct { files chan *fs.File functions chan *core.Function + types chan *types.Type errors chan error queued sync.Map group sync.WaitGroup diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 6d57c0a..3dfa4ac 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -2,16 +2,10 @@ package scanner import ( "os" - "path/filepath" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/cli/q/src/x64" ) // scanFile scans a single file. @@ -32,250 +26,31 @@ func (s *Scanner) scanFile(path string, pkg string) error { } s.files <- file + i := 0 - var ( - i = 0 - groupLevel = 0 - blockLevel = 0 - nameStart = -1 - paramsStart = -1 - paramsEnd = -1 - bodyStart = -1 - typeStart = -1 - typeEnd = -1 - ) - - for { - for i < len(tokens) && tokens[i].Kind == token.Import { - i++ - - if tokens[i].Kind != token.Identifier { - panic("expected package name") - } - - packageName := tokens[i].Text(contents) - - if file.Imports == nil { - file.Imports = map[string]*fs.Import{} - } - - fullPath := filepath.Join(config.Library, packageName) - - file.Imports[packageName] = &fs.Import{ - Path: packageName, - FullPath: fullPath, - Position: tokens[i].Position, - } - - s.queueDirectory(fullPath, packageName) - i++ - - if tokens[i].Kind != token.NewLine && tokens[i].Kind != token.EOF { - panic("expected newline or eof") - } - - i++ - } - - // Function name - for i < len(tokens) { - if tokens[i].Kind == token.Identifier { - nameStart = i - i++ - break - } - - if tokens[i].Kind == token.NewLine || tokens[i].Kind == token.Comment { - i++ - continue - } - - if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(contents)}, file, tokens[i].Position) - } - - if tokens[i].Kind == token.EOF { - return nil - } - - return errors.New(errors.ExpectedFunctionName, file, tokens[i].Position) - } - - // Function parameters - for i < len(tokens) { - if tokens[i].Kind == token.GroupStart { - groupLevel++ - i++ - - if groupLevel == 1 { - paramsStart = i - } - - continue - } - - if tokens[i].Kind == token.GroupEnd { - groupLevel-- - - if groupLevel < 0 { - return errors.New(errors.MissingGroupStart, file, tokens[i].Position) - } - - if groupLevel == 0 { - paramsEnd = i - i++ - break - } - - i++ - continue - } - - if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(contents)}, file, tokens[i].Position) - } - - if tokens[i].Kind == token.EOF { - if groupLevel > 0 { - return errors.New(errors.MissingGroupEnd, file, tokens[i].Position) - } - - if paramsStart == -1 { - return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) - } - - return nil - } - - if groupLevel > 0 { - i++ - continue - } - - return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) - } - - // Return type - if i < len(tokens) && tokens[i].Kind == token.ReturnType { - typeStart = i + 1 - - for i < len(tokens) && tokens[i].Kind != token.BlockStart { - i++ - } - - typeEnd = i - } - - // Function definition - for i < len(tokens) { - if tokens[i].Kind == token.ReturnType { - i++ - continue - } - - if tokens[i].Kind == token.BlockStart { - blockLevel++ - i++ - - if blockLevel == 1 { - bodyStart = i - } - - continue - } - - if tokens[i].Kind == token.BlockEnd { - blockLevel-- - - if blockLevel < 0 { - return errors.New(errors.MissingBlockStart, file, tokens[i].Position) - } - - if blockLevel == 0 { - break - } - - i++ - continue - } - - if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(contents)}, file, tokens[i].Position) - } - - if tokens[i].Kind == token.EOF { - if blockLevel > 0 { - return errors.New(errors.MissingBlockEnd, file, tokens[i].Position) - } - - if bodyStart == -1 { - return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) - } - - return nil - } - - if blockLevel > 0 { - i++ - continue - } - - return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) - } - - name := tokens[nameStart].Text(contents) - body := tokens[bodyStart:i] - function := core.NewFunction(pkg, name, file, body) - - if typeStart != -1 { - if tokens[typeStart].Kind == token.GroupStart && tokens[typeEnd-1].Kind == token.GroupEnd { - typeStart++ - typeEnd-- - } - - function.ReturnTypes = types.ParseList(tokens[typeStart:typeEnd], contents) - } - - parameters := tokens[paramsStart:paramsEnd] - count := 0 - - err := parameters.Split(func(tokens token.List) error { - if len(tokens) < 2 { - return errors.New(errors.MissingType, file, tokens[0].End()) - } - - name := tokens[0].Text(contents) - dataType := types.Parse(tokens[1:].Text(contents)) - register := x64.InputRegisters[count] - uses := token.Count(function.Body, contents, token.Identifier, name) - - if uses == 0 && name != "_" { - return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) - } - - variable := &scope.Variable{ - Name: name, - Type: dataType, - Register: register, - Alive: uses, - } - - function.Parameters = append(function.Parameters, variable) - function.AddVariable(variable) - count++ + for i < len(tokens) { + switch tokens[i].Kind { + case token.NewLine: + case token.Import: + i, err = s.scanImport(file, tokens, i) + case token.Struct: + i, err = s.scanStruct(file, tokens, i) + case token.Identifier: + i, err = s.scanFunction(file, tokens, i) + case token.EOF: return nil - }) + case token.Invalid: + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position) + default: + return errors.New(&errors.InvalidInstruction{Instruction: tokens[i].Text(file.Bytes)}, file, tokens[i].Position) + } if err != nil { return err } - s.functions <- function - nameStart = -1 - paramsStart = -1 - bodyStart = -1 - typeStart = -1 - typeEnd = -1 i++ } + + return nil } diff --git a/src/scanner/scanFunction.go b/src/scanner/scanFunction.go new file mode 100644 index 0000000..9a51bdf --- /dev/null +++ b/src/scanner/scanFunction.go @@ -0,0 +1,222 @@ +package scanner + +import ( + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/scope" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" + "git.akyoto.dev/cli/q/src/x64" +) + +// scanFunction scans a function. +func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, error) { + var ( + groupLevel = 0 + blockLevel = 0 + nameStart = -1 + paramsStart = -1 + paramsEnd = -1 + bodyStart = -1 + typeStart = -1 + typeEnd = -1 + ) + + // Function name + for i < len(tokens) { + if tokens[i].Kind == token.Identifier { + nameStart = i + i++ + break + } + + if tokens[i].Kind == token.NewLine || tokens[i].Kind == token.Comment { + i++ + continue + } + + if tokens[i].Kind == token.Invalid { + return i, errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position) + } + + if tokens[i].Kind == token.EOF { + return i, nil + } + + return i, errors.New(errors.ExpectedFunctionName, file, tokens[i].Position) + } + + // Function parameters + for i < len(tokens) { + if tokens[i].Kind == token.GroupStart { + groupLevel++ + i++ + + if groupLevel == 1 { + paramsStart = i + } + + continue + } + + if tokens[i].Kind == token.GroupEnd { + groupLevel-- + + if groupLevel < 0 { + return i, errors.New(errors.MissingGroupStart, file, tokens[i].Position) + } + + if groupLevel == 0 { + paramsEnd = i + i++ + break + } + + i++ + continue + } + + if tokens[i].Kind == token.Invalid { + return i, errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position) + } + + if tokens[i].Kind == token.EOF { + if groupLevel > 0 { + return i, errors.New(errors.MissingGroupEnd, file, tokens[i].Position) + } + + if paramsStart == -1 { + return i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) + } + + return i, nil + } + + if groupLevel > 0 { + i++ + continue + } + + return i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) + } + + // Return type + if i < len(tokens) && tokens[i].Kind == token.ReturnType { + typeStart = i + 1 + + for i < len(tokens) && tokens[i].Kind != token.BlockStart { + i++ + } + + typeEnd = i + } + + // Function definition + for i < len(tokens) { + if tokens[i].Kind == token.ReturnType { + i++ + continue + } + + if tokens[i].Kind == token.BlockStart { + blockLevel++ + i++ + + if blockLevel == 1 { + bodyStart = i + } + + continue + } + + if tokens[i].Kind == token.BlockEnd { + blockLevel-- + + if blockLevel < 0 { + return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) + } + + if blockLevel == 0 { + break + } + + i++ + continue + } + + if tokens[i].Kind == token.Invalid { + return i, errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position) + } + + if tokens[i].Kind == token.EOF { + if blockLevel > 0 { + return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) + } + + if bodyStart == -1 { + return i, errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) + } + + return i, nil + } + + if blockLevel > 0 { + i++ + continue + } + + return i, errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) + } + + name := tokens[nameStart].Text(file.Bytes) + body := tokens[bodyStart:i] + function := core.NewFunction(file.Package, name, file, body) + + if typeStart != -1 { + if tokens[typeStart].Kind == token.GroupStart && tokens[typeEnd-1].Kind == token.GroupEnd { + typeStart++ + typeEnd-- + } + + function.ReturnTypes = types.ParseList(tokens[typeStart:typeEnd], file.Bytes) + } + + parameters := tokens[paramsStart:paramsEnd] + count := 0 + + err := parameters.Split(func(tokens token.List) error { + if len(tokens) < 2 { + return errors.New(errors.MissingType, file, tokens[0].End()) + } + + name := tokens[0].Text(file.Bytes) + dataType := types.Parse(tokens[1:].Text(file.Bytes)) + register := x64.InputRegisters[count] + uses := token.Count(function.Body, file.Bytes, token.Identifier, name) + + if uses == 0 && name != "_" { + return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) + } + + variable := &scope.Variable{ + Name: name, + Type: dataType, + Register: register, + Alive: uses, + } + + function.Parameters = append(function.Parameters, variable) + function.AddVariable(variable) + count++ + return nil + }) + + if err != nil { + return i, err + } + + s.functions <- function + i++ + return i, nil +} diff --git a/src/scanner/scanImport.go b/src/scanner/scanImport.go new file mode 100644 index 0000000..b430e2e --- /dev/null +++ b/src/scanner/scanImport.go @@ -0,0 +1,41 @@ +package scanner + +import ( + "path/filepath" + + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" +) + +// scanImport scans an import. +func (s *Scanner) scanImport(file *fs.File, tokens token.List, i int) (int, error) { + i++ + + if tokens[i].Kind != token.Identifier { + panic("expected package name") + } + + packageName := tokens[i].Text(file.Bytes) + + if file.Imports == nil { + file.Imports = map[string]*fs.Import{} + } + + fullPath := filepath.Join(config.Library, packageName) + + file.Imports[packageName] = &fs.Import{ + Path: packageName, + FullPath: fullPath, + Position: tokens[i].Position, + } + + s.queueDirectory(fullPath, packageName) + i++ + + if tokens[i].Kind != token.NewLine && tokens[i].Kind != token.EOF { + panic("expected newline or eof") + } + + return i, nil +} diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go new file mode 100644 index 0000000..dddf481 --- /dev/null +++ b/src/scanner/scanStruct.go @@ -0,0 +1,66 @@ +package scanner + +import ( + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" +) + +// scanStruct scans a struct. +func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, error) { + i++ + + if tokens[i].Kind != token.Identifier { + return i, errors.New(errors.ExpectedStructName, file, tokens[i].Position) + } + + structName := tokens[i].Text(file.Bytes) + + typ := &types.Type{ + Name: structName, + } + + i++ + + if tokens[i].Kind != token.BlockStart { + return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) + } + + i++ + closed := false + + for i < len(tokens) { + if tokens[i].Kind == token.Identifier { + fieldPosition := i + fieldName := tokens[i].Text(file.Bytes) + i++ + fieldTypeName := tokens[i].Text(file.Bytes) + fieldType := types.Parse(fieldTypeName) + i++ + + typ.Fields = append(typ.Fields, &types.Field{ + Type: fieldType, + Name: fieldName, + Position: token.Position(fieldPosition), + Offset: typ.Size, + }) + + typ.Size += fieldType.Size + } + + if tokens[i].Kind == token.BlockEnd { + closed = true + break + } + + i++ + } + + if !closed { + return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) + } + + s.types <- typ + return i, nil +} diff --git a/src/token/Kind.go b/src/token/Kind.go index 737ef55..1a652b2 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -70,6 +70,7 @@ const ( Import // import Loop // loop Return // return + Struct // struct Switch // switch ___END_KEYWORDS___ // ) diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index 267f736..e755f97 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -25,7 +25,7 @@ func TestFunction(t *testing.T) { } func TestKeyword(t *testing.T) { - tokens := token.Tokenize([]byte("assert if import else loop return switch")) + tokens := token.Tokenize([]byte("assert if import else loop return struct switch")) expected := []token.Kind{ token.Assert, @@ -34,6 +34,7 @@ func TestKeyword(t *testing.T) { token.Else, token.Loop, token.Return, + token.Struct, token.Switch, token.EOF, } diff --git a/src/token/identifier.go b/src/token/identifier.go index f44bcf6..fa9cc63 100644 --- a/src/token/identifier.go +++ b/src/token/identifier.go @@ -25,6 +25,8 @@ func identifier(tokens List, buffer []byte, i Position) (List, Position) { kind = Loop case "return": kind = Return + case "struct": + kind = Struct case "switch": kind = Switch } diff --git a/tests/errors/ExpectedStructName.q b/tests/errors/ExpectedStructName.q new file mode 100644 index 0000000..6c8a4ee --- /dev/null +++ b/tests/errors/ExpectedStructName.q @@ -0,0 +1 @@ +struct{} \ No newline at end of file diff --git a/tests/errors/InvalidInstructionString.q b/tests/errors/InvalidInstructionString.q new file mode 100644 index 0000000..09905c2 --- /dev/null +++ b/tests/errors/InvalidInstructionString.q @@ -0,0 +1,3 @@ +main() { + "Hello" +} \ No newline at end of file diff --git a/tests/errors/ExpectedFunctionName.q b/tests/errors/InvalidInstructionTopLevel.q similarity index 100% rename from tests/errors/ExpectedFunctionName.q rename to tests/errors/InvalidInstructionTopLevel.q diff --git a/tests/errors/ExpectedFunctionName2.q b/tests/errors/InvalidInstructionTopLevel2.q similarity index 100% rename from tests/errors/ExpectedFunctionName2.q rename to tests/errors/InvalidInstructionTopLevel2.q diff --git a/tests/errors/InvalidInstructionTopLevel3.q b/tests/errors/InvalidInstructionTopLevel3.q new file mode 100644 index 0000000..9b26e9b --- /dev/null +++ b/tests/errors/InvalidInstructionTopLevel3.q @@ -0,0 +1 @@ ++ \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 9de4094..7839f63 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -16,14 +16,17 @@ var errs = []struct { }{ {"EmptySwitch.q", errors.EmptySwitch}, {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, - {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, - {"ExpectedFunctionName2.q", errors.ExpectedFunctionName}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse}, {"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse}, + {"ExpectedStructName.q", errors.ExpectedStructName}, + {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, - {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}}, + {"InvalidInstructionString.q", &errors.InvalidInstruction{Instruction: "\"Hello\""}}, + {"InvalidInstructionTopLevel.q", &errors.InvalidInstruction{Instruction: "123"}}, + {"InvalidInstructionTopLevel2.q", &errors.InvalidInstruction{Instruction: "\"Hello\""}}, + {"InvalidInstructionTopLevel3.q", &errors.InvalidInstruction{Instruction: "+"}}, {"InvalidExpression.q", errors.InvalidExpression}, {"InvalidCharacter.q", &errors.InvalidCharacter{Character: "@"}}, {"InvalidCharacter2.q", &errors.InvalidCharacter{Character: "@"}}, From 51e3c1ba0e552cd77e3f70574dd8c31a707248ef Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 4 Feb 2025 14:41:04 +0100 Subject: [PATCH 0632/1012] Implemented struct parser --- lib/time/timespec.q | 4 + src/build/Build.go | 4 +- src/compiler/Compile.go | 12 +- src/errors/Common.go | 1 + src/scanner/Scan.go | 7 +- src/scanner/Scanner.go | 2 + src/scanner/scanFile.go | 261 ++---------------- src/scanner/scanFunction.go | 222 +++++++++++++++ src/scanner/scanImport.go | 41 +++ src/scanner/scanStruct.go | 66 +++++ src/token/Kind.go | 1 + src/token/Tokenize_test.go | 3 +- src/token/identifier.go | 2 + tests/errors/ExpectedStructName.q | 1 + tests/errors/InvalidInstructionString.q | 3 + ...ionName.q => InvalidInstructionTopLevel.q} | 0 ...nName2.q => InvalidInstructionTopLevel2.q} | 0 tests/errors/InvalidInstructionTopLevel3.q | 1 + tests/errors_test.go | 9 +- 19 files changed, 388 insertions(+), 252 deletions(-) create mode 100644 lib/time/timespec.q create mode 100644 src/scanner/scanFunction.go create mode 100644 src/scanner/scanImport.go create mode 100644 src/scanner/scanStruct.go create mode 100644 tests/errors/ExpectedStructName.q create mode 100644 tests/errors/InvalidInstructionString.q rename tests/errors/{ExpectedFunctionName.q => InvalidInstructionTopLevel.q} (100%) rename tests/errors/{ExpectedFunctionName2.q => InvalidInstructionTopLevel2.q} (100%) create mode 100644 tests/errors/InvalidInstructionTopLevel3.q diff --git a/lib/time/timespec.q b/lib/time/timespec.q new file mode 100644 index 0000000..84d53d6 --- /dev/null +++ b/lib/time/timespec.q @@ -0,0 +1,4 @@ +struct timespec { + seconds Int + nanoseconds Int +} \ No newline at end of file diff --git a/src/build/Build.go b/src/build/Build.go index 52c9f5f..e3b620e 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -23,8 +23,8 @@ func New(files ...string) *Build { // Run compiles the input files. func (build *Build) Run() (compiler.Result, error) { - files, functions, errors := scanner.Scan(build.Files) - return compiler.Compile(files, functions, errors) + files, functions, types, errors := scanner.Scan(build.Files) + return compiler.Compile(files, functions, types, errors) } // Executable returns the path to the executable. diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 5d17cd5..dd35a48 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -6,13 +6,15 @@ import ( "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/types" ) // Compile waits for the scan to finish and compiles all functions. -func Compile(files <-chan *fs.File, functions <-chan *core.Function, errs <-chan error) (Result, error) { +func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Type, errs <-chan error) (Result, error) { result := Result{} allFiles := make([]*fs.File, 0, 8) allFunctions := map[string]*core.Function{} + allTypes := map[string]*types.Type{} for functions != nil || files != nil || errs != nil { select { @@ -25,6 +27,14 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, errs <-chan function.Functions = allFunctions allFunctions[function.UniqueName] = function + case typ, ok := <-structs: + if !ok { + structs = nil + continue + } + + allTypes[typ.Name] = typ + case file, ok := <-files: if !ok { files = nil diff --git a/src/errors/Common.go b/src/errors/Common.go index a4ebdc9..ab301d2 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -6,6 +6,7 @@ var ( ExpectedFunctionParameters = &Base{"Expected function parameters"} ExpectedFunctionDefinition = &Base{"Expected function definition"} ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} + ExpectedStructName = &Base{"Expected struct name"} InvalidNumber = &Base{"Invalid number"} InvalidExpression = &Base{"Invalid expression"} InvalidRune = &Base{"Invalid rune"} diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go index f18cbff..df17032 100644 --- a/src/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -3,13 +3,15 @@ package scanner import ( "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/types" ) // Scan scans the list of files. -func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan error) { +func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan *types.Type, <-chan error) { scanner := Scanner{ files: make(chan *fs.File), functions: make(chan *core.Function), + types: make(chan *types.Type), errors: make(chan error), } @@ -18,8 +20,9 @@ func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan error) scanner.group.Wait() close(scanner.files) close(scanner.functions) + close(scanner.types) close(scanner.errors) }() - return scanner.files, scanner.functions, scanner.errors + return scanner.files, scanner.functions, scanner.types, scanner.errors } diff --git a/src/scanner/Scanner.go b/src/scanner/Scanner.go index 434fe30..f1f0835 100644 --- a/src/scanner/Scanner.go +++ b/src/scanner/Scanner.go @@ -5,12 +5,14 @@ import ( "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/types" ) // Scanner is used to scan files before the actual compilation step. type Scanner struct { files chan *fs.File functions chan *core.Function + types chan *types.Type errors chan error queued sync.Map group sync.WaitGroup diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 6d57c0a..3dfa4ac 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -2,16 +2,10 @@ package scanner import ( "os" - "path/filepath" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/cli/q/src/x64" ) // scanFile scans a single file. @@ -32,250 +26,31 @@ func (s *Scanner) scanFile(path string, pkg string) error { } s.files <- file + i := 0 - var ( - i = 0 - groupLevel = 0 - blockLevel = 0 - nameStart = -1 - paramsStart = -1 - paramsEnd = -1 - bodyStart = -1 - typeStart = -1 - typeEnd = -1 - ) - - for { - for i < len(tokens) && tokens[i].Kind == token.Import { - i++ - - if tokens[i].Kind != token.Identifier { - panic("expected package name") - } - - packageName := tokens[i].Text(contents) - - if file.Imports == nil { - file.Imports = map[string]*fs.Import{} - } - - fullPath := filepath.Join(config.Library, packageName) - - file.Imports[packageName] = &fs.Import{ - Path: packageName, - FullPath: fullPath, - Position: tokens[i].Position, - } - - s.queueDirectory(fullPath, packageName) - i++ - - if tokens[i].Kind != token.NewLine && tokens[i].Kind != token.EOF { - panic("expected newline or eof") - } - - i++ - } - - // Function name - for i < len(tokens) { - if tokens[i].Kind == token.Identifier { - nameStart = i - i++ - break - } - - if tokens[i].Kind == token.NewLine || tokens[i].Kind == token.Comment { - i++ - continue - } - - if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(contents)}, file, tokens[i].Position) - } - - if tokens[i].Kind == token.EOF { - return nil - } - - return errors.New(errors.ExpectedFunctionName, file, tokens[i].Position) - } - - // Function parameters - for i < len(tokens) { - if tokens[i].Kind == token.GroupStart { - groupLevel++ - i++ - - if groupLevel == 1 { - paramsStart = i - } - - continue - } - - if tokens[i].Kind == token.GroupEnd { - groupLevel-- - - if groupLevel < 0 { - return errors.New(errors.MissingGroupStart, file, tokens[i].Position) - } - - if groupLevel == 0 { - paramsEnd = i - i++ - break - } - - i++ - continue - } - - if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(contents)}, file, tokens[i].Position) - } - - if tokens[i].Kind == token.EOF { - if groupLevel > 0 { - return errors.New(errors.MissingGroupEnd, file, tokens[i].Position) - } - - if paramsStart == -1 { - return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) - } - - return nil - } - - if groupLevel > 0 { - i++ - continue - } - - return errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) - } - - // Return type - if i < len(tokens) && tokens[i].Kind == token.ReturnType { - typeStart = i + 1 - - for i < len(tokens) && tokens[i].Kind != token.BlockStart { - i++ - } - - typeEnd = i - } - - // Function definition - for i < len(tokens) { - if tokens[i].Kind == token.ReturnType { - i++ - continue - } - - if tokens[i].Kind == token.BlockStart { - blockLevel++ - i++ - - if blockLevel == 1 { - bodyStart = i - } - - continue - } - - if tokens[i].Kind == token.BlockEnd { - blockLevel-- - - if blockLevel < 0 { - return errors.New(errors.MissingBlockStart, file, tokens[i].Position) - } - - if blockLevel == 0 { - break - } - - i++ - continue - } - - if tokens[i].Kind == token.Invalid { - return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(contents)}, file, tokens[i].Position) - } - - if tokens[i].Kind == token.EOF { - if blockLevel > 0 { - return errors.New(errors.MissingBlockEnd, file, tokens[i].Position) - } - - if bodyStart == -1 { - return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) - } - - return nil - } - - if blockLevel > 0 { - i++ - continue - } - - return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) - } - - name := tokens[nameStart].Text(contents) - body := tokens[bodyStart:i] - function := core.NewFunction(pkg, name, file, body) - - if typeStart != -1 { - if tokens[typeStart].Kind == token.GroupStart && tokens[typeEnd-1].Kind == token.GroupEnd { - typeStart++ - typeEnd-- - } - - function.ReturnTypes = types.ParseList(tokens[typeStart:typeEnd], contents) - } - - parameters := tokens[paramsStart:paramsEnd] - count := 0 - - err := parameters.Split(func(tokens token.List) error { - if len(tokens) < 2 { - return errors.New(errors.MissingType, file, tokens[0].End()) - } - - name := tokens[0].Text(contents) - dataType := types.Parse(tokens[1:].Text(contents)) - register := x64.InputRegisters[count] - uses := token.Count(function.Body, contents, token.Identifier, name) - - if uses == 0 && name != "_" { - return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) - } - - variable := &scope.Variable{ - Name: name, - Type: dataType, - Register: register, - Alive: uses, - } - - function.Parameters = append(function.Parameters, variable) - function.AddVariable(variable) - count++ + for i < len(tokens) { + switch tokens[i].Kind { + case token.NewLine: + case token.Import: + i, err = s.scanImport(file, tokens, i) + case token.Struct: + i, err = s.scanStruct(file, tokens, i) + case token.Identifier: + i, err = s.scanFunction(file, tokens, i) + case token.EOF: return nil - }) + case token.Invalid: + return errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position) + default: + return errors.New(&errors.InvalidInstruction{Instruction: tokens[i].Text(file.Bytes)}, file, tokens[i].Position) + } if err != nil { return err } - s.functions <- function - nameStart = -1 - paramsStart = -1 - bodyStart = -1 - typeStart = -1 - typeEnd = -1 i++ } + + return nil } diff --git a/src/scanner/scanFunction.go b/src/scanner/scanFunction.go new file mode 100644 index 0000000..9a51bdf --- /dev/null +++ b/src/scanner/scanFunction.go @@ -0,0 +1,222 @@ +package scanner + +import ( + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/scope" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" + "git.akyoto.dev/cli/q/src/x64" +) + +// scanFunction scans a function. +func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, error) { + var ( + groupLevel = 0 + blockLevel = 0 + nameStart = -1 + paramsStart = -1 + paramsEnd = -1 + bodyStart = -1 + typeStart = -1 + typeEnd = -1 + ) + + // Function name + for i < len(tokens) { + if tokens[i].Kind == token.Identifier { + nameStart = i + i++ + break + } + + if tokens[i].Kind == token.NewLine || tokens[i].Kind == token.Comment { + i++ + continue + } + + if tokens[i].Kind == token.Invalid { + return i, errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position) + } + + if tokens[i].Kind == token.EOF { + return i, nil + } + + return i, errors.New(errors.ExpectedFunctionName, file, tokens[i].Position) + } + + // Function parameters + for i < len(tokens) { + if tokens[i].Kind == token.GroupStart { + groupLevel++ + i++ + + if groupLevel == 1 { + paramsStart = i + } + + continue + } + + if tokens[i].Kind == token.GroupEnd { + groupLevel-- + + if groupLevel < 0 { + return i, errors.New(errors.MissingGroupStart, file, tokens[i].Position) + } + + if groupLevel == 0 { + paramsEnd = i + i++ + break + } + + i++ + continue + } + + if tokens[i].Kind == token.Invalid { + return i, errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position) + } + + if tokens[i].Kind == token.EOF { + if groupLevel > 0 { + return i, errors.New(errors.MissingGroupEnd, file, tokens[i].Position) + } + + if paramsStart == -1 { + return i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) + } + + return i, nil + } + + if groupLevel > 0 { + i++ + continue + } + + return i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) + } + + // Return type + if i < len(tokens) && tokens[i].Kind == token.ReturnType { + typeStart = i + 1 + + for i < len(tokens) && tokens[i].Kind != token.BlockStart { + i++ + } + + typeEnd = i + } + + // Function definition + for i < len(tokens) { + if tokens[i].Kind == token.ReturnType { + i++ + continue + } + + if tokens[i].Kind == token.BlockStart { + blockLevel++ + i++ + + if blockLevel == 1 { + bodyStart = i + } + + continue + } + + if tokens[i].Kind == token.BlockEnd { + blockLevel-- + + if blockLevel < 0 { + return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) + } + + if blockLevel == 0 { + break + } + + i++ + continue + } + + if tokens[i].Kind == token.Invalid { + return i, errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position) + } + + if tokens[i].Kind == token.EOF { + if blockLevel > 0 { + return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) + } + + if bodyStart == -1 { + return i, errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) + } + + return i, nil + } + + if blockLevel > 0 { + i++ + continue + } + + return i, errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) + } + + name := tokens[nameStart].Text(file.Bytes) + body := tokens[bodyStart:i] + function := core.NewFunction(file.Package, name, file, body) + + if typeStart != -1 { + if tokens[typeStart].Kind == token.GroupStart && tokens[typeEnd-1].Kind == token.GroupEnd { + typeStart++ + typeEnd-- + } + + function.ReturnTypes = types.ParseList(tokens[typeStart:typeEnd], file.Bytes) + } + + parameters := tokens[paramsStart:paramsEnd] + count := 0 + + err := parameters.Split(func(tokens token.List) error { + if len(tokens) < 2 { + return errors.New(errors.MissingType, file, tokens[0].End()) + } + + name := tokens[0].Text(file.Bytes) + dataType := types.Parse(tokens[1:].Text(file.Bytes)) + register := x64.InputRegisters[count] + uses := token.Count(function.Body, file.Bytes, token.Identifier, name) + + if uses == 0 && name != "_" { + return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) + } + + variable := &scope.Variable{ + Name: name, + Type: dataType, + Register: register, + Alive: uses, + } + + function.Parameters = append(function.Parameters, variable) + function.AddVariable(variable) + count++ + return nil + }) + + if err != nil { + return i, err + } + + s.functions <- function + i++ + return i, nil +} diff --git a/src/scanner/scanImport.go b/src/scanner/scanImport.go new file mode 100644 index 0000000..b430e2e --- /dev/null +++ b/src/scanner/scanImport.go @@ -0,0 +1,41 @@ +package scanner + +import ( + "path/filepath" + + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" +) + +// scanImport scans an import. +func (s *Scanner) scanImport(file *fs.File, tokens token.List, i int) (int, error) { + i++ + + if tokens[i].Kind != token.Identifier { + panic("expected package name") + } + + packageName := tokens[i].Text(file.Bytes) + + if file.Imports == nil { + file.Imports = map[string]*fs.Import{} + } + + fullPath := filepath.Join(config.Library, packageName) + + file.Imports[packageName] = &fs.Import{ + Path: packageName, + FullPath: fullPath, + Position: tokens[i].Position, + } + + s.queueDirectory(fullPath, packageName) + i++ + + if tokens[i].Kind != token.NewLine && tokens[i].Kind != token.EOF { + panic("expected newline or eof") + } + + return i, nil +} diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go new file mode 100644 index 0000000..dddf481 --- /dev/null +++ b/src/scanner/scanStruct.go @@ -0,0 +1,66 @@ +package scanner + +import ( + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" +) + +// scanStruct scans a struct. +func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, error) { + i++ + + if tokens[i].Kind != token.Identifier { + return i, errors.New(errors.ExpectedStructName, file, tokens[i].Position) + } + + structName := tokens[i].Text(file.Bytes) + + typ := &types.Type{ + Name: structName, + } + + i++ + + if tokens[i].Kind != token.BlockStart { + return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) + } + + i++ + closed := false + + for i < len(tokens) { + if tokens[i].Kind == token.Identifier { + fieldPosition := i + fieldName := tokens[i].Text(file.Bytes) + i++ + fieldTypeName := tokens[i].Text(file.Bytes) + fieldType := types.Parse(fieldTypeName) + i++ + + typ.Fields = append(typ.Fields, &types.Field{ + Type: fieldType, + Name: fieldName, + Position: token.Position(fieldPosition), + Offset: typ.Size, + }) + + typ.Size += fieldType.Size + } + + if tokens[i].Kind == token.BlockEnd { + closed = true + break + } + + i++ + } + + if !closed { + return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) + } + + s.types <- typ + return i, nil +} diff --git a/src/token/Kind.go b/src/token/Kind.go index 737ef55..1a652b2 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -70,6 +70,7 @@ const ( Import // import Loop // loop Return // return + Struct // struct Switch // switch ___END_KEYWORDS___ // ) diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index 267f736..e755f97 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -25,7 +25,7 @@ func TestFunction(t *testing.T) { } func TestKeyword(t *testing.T) { - tokens := token.Tokenize([]byte("assert if import else loop return switch")) + tokens := token.Tokenize([]byte("assert if import else loop return struct switch")) expected := []token.Kind{ token.Assert, @@ -34,6 +34,7 @@ func TestKeyword(t *testing.T) { token.Else, token.Loop, token.Return, + token.Struct, token.Switch, token.EOF, } diff --git a/src/token/identifier.go b/src/token/identifier.go index f44bcf6..fa9cc63 100644 --- a/src/token/identifier.go +++ b/src/token/identifier.go @@ -25,6 +25,8 @@ func identifier(tokens List, buffer []byte, i Position) (List, Position) { kind = Loop case "return": kind = Return + case "struct": + kind = Struct case "switch": kind = Switch } diff --git a/tests/errors/ExpectedStructName.q b/tests/errors/ExpectedStructName.q new file mode 100644 index 0000000..6c8a4ee --- /dev/null +++ b/tests/errors/ExpectedStructName.q @@ -0,0 +1 @@ +struct{} \ No newline at end of file diff --git a/tests/errors/InvalidInstructionString.q b/tests/errors/InvalidInstructionString.q new file mode 100644 index 0000000..09905c2 --- /dev/null +++ b/tests/errors/InvalidInstructionString.q @@ -0,0 +1,3 @@ +main() { + "Hello" +} \ No newline at end of file diff --git a/tests/errors/ExpectedFunctionName.q b/tests/errors/InvalidInstructionTopLevel.q similarity index 100% rename from tests/errors/ExpectedFunctionName.q rename to tests/errors/InvalidInstructionTopLevel.q diff --git a/tests/errors/ExpectedFunctionName2.q b/tests/errors/InvalidInstructionTopLevel2.q similarity index 100% rename from tests/errors/ExpectedFunctionName2.q rename to tests/errors/InvalidInstructionTopLevel2.q diff --git a/tests/errors/InvalidInstructionTopLevel3.q b/tests/errors/InvalidInstructionTopLevel3.q new file mode 100644 index 0000000..9b26e9b --- /dev/null +++ b/tests/errors/InvalidInstructionTopLevel3.q @@ -0,0 +1 @@ ++ \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 9de4094..7839f63 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -16,14 +16,17 @@ var errs = []struct { }{ {"EmptySwitch.q", errors.EmptySwitch}, {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, - {"ExpectedFunctionName.q", errors.ExpectedFunctionName}, - {"ExpectedFunctionName2.q", errors.ExpectedFunctionName}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse}, {"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse}, + {"ExpectedStructName.q", errors.ExpectedStructName}, + {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, - {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}}, + {"InvalidInstructionString.q", &errors.InvalidInstruction{Instruction: "\"Hello\""}}, + {"InvalidInstructionTopLevel.q", &errors.InvalidInstruction{Instruction: "123"}}, + {"InvalidInstructionTopLevel2.q", &errors.InvalidInstruction{Instruction: "\"Hello\""}}, + {"InvalidInstructionTopLevel3.q", &errors.InvalidInstruction{Instruction: "+"}}, {"InvalidExpression.q", errors.InvalidExpression}, {"InvalidCharacter.q", &errors.InvalidCharacter{Character: "@"}}, {"InvalidCharacter2.q", &errors.InvalidCharacter{Character: "@"}}, From 50fc8c6249b66d589dd2e6f733c5dcf60eff971a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 4 Feb 2025 15:19:03 +0100 Subject: [PATCH 0633/1012] Removed unnecessary code --- src/scanner/scanFunction.go | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/src/scanner/scanFunction.go b/src/scanner/scanFunction.go index 9a51bdf..f66c1bb 100644 --- a/src/scanner/scanFunction.go +++ b/src/scanner/scanFunction.go @@ -15,7 +15,7 @@ func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, er var ( groupLevel = 0 blockLevel = 0 - nameStart = -1 + nameStart = i paramsStart = -1 paramsEnd = -1 bodyStart = -1 @@ -23,29 +23,7 @@ func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, er typeEnd = -1 ) - // Function name - for i < len(tokens) { - if tokens[i].Kind == token.Identifier { - nameStart = i - i++ - break - } - - if tokens[i].Kind == token.NewLine || tokens[i].Kind == token.Comment { - i++ - continue - } - - if tokens[i].Kind == token.Invalid { - return i, errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position) - } - - if tokens[i].Kind == token.EOF { - return i, nil - } - - return i, errors.New(errors.ExpectedFunctionName, file, tokens[i].Position) - } + i++ // Function parameters for i < len(tokens) { From 4609a814df9d36fbb8333deba99134dc270b9958 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 4 Feb 2025 15:19:03 +0100 Subject: [PATCH 0634/1012] Removed unnecessary code --- src/scanner/scanFunction.go | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/src/scanner/scanFunction.go b/src/scanner/scanFunction.go index 9a51bdf..f66c1bb 100644 --- a/src/scanner/scanFunction.go +++ b/src/scanner/scanFunction.go @@ -15,7 +15,7 @@ func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, er var ( groupLevel = 0 blockLevel = 0 - nameStart = -1 + nameStart = i paramsStart = -1 paramsEnd = -1 bodyStart = -1 @@ -23,29 +23,7 @@ func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, er typeEnd = -1 ) - // Function name - for i < len(tokens) { - if tokens[i].Kind == token.Identifier { - nameStart = i - i++ - break - } - - if tokens[i].Kind == token.NewLine || tokens[i].Kind == token.Comment { - i++ - continue - } - - if tokens[i].Kind == token.Invalid { - return i, errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position) - } - - if tokens[i].Kind == token.EOF { - return i, nil - } - - return i, errors.New(errors.ExpectedFunctionName, file, tokens[i].Position) - } + i++ // Function parameters for i < len(tokens) { From e36d9fade3c5e42efbb71cb05525cc8e0f50d77a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 4 Feb 2025 18:16:31 +0100 Subject: [PATCH 0635/1012] Implemented structs --- examples/point/point.q | 11 ++++++++ lib/sys/sys_linux.q | 4 +-- lib/time/time.q | 12 ++++----- src/asm/MemoryLabel.go | 5 ++++ src/asm/MemoryNumber.go | 5 ++++ src/asm/MemoryRegister.go | 5 ++++ src/compiler/Compile.go | 7 ++--- src/core/CompileAssign.go | 4 +++ src/core/CompileAssignField.go | 44 ++++++++++++++++++++++++++++++++ src/core/CompileCall.go | 21 ++++++++++----- src/core/CompileDelete.go | 37 +++++++++++++++++++++++++++ src/core/CompileMemoryStore.go | 2 +- src/core/CompileNew.go | 33 ++++++++++++++++++++++++ src/core/CompileReturn.go | 4 +-- src/core/Evaluate.go | 2 +- src/core/ExpressionToMemory.go | 10 ++++---- src/core/ExpressionToRegister.go | 4 +-- src/core/Function.go | 3 ++- src/core/TokenToRegister.go | 6 ++--- src/scanner/Scan.go | 9 +++++-- src/scanner/Scanner.go | 2 +- src/scanner/scanStruct.go | 10 ++++---- src/scope/Variable.go | 2 +- src/types/Base.go | 14 +++++----- src/types/Check.go | 21 ++++++++++++--- src/types/Field.go | 2 +- src/types/Parse.go | 4 +-- src/types/ParseList.go | 4 +-- src/types/Pointer.go | 17 ++++++++++++ src/types/Struct.go | 16 ++++++++++++ src/types/Type.go | 8 +++--- src/x64/Registers.go | 2 +- 32 files changed, 267 insertions(+), 63 deletions(-) create mode 100644 examples/point/point.q create mode 100644 src/core/CompileAssignField.go create mode 100644 src/core/CompileDelete.go create mode 100644 src/core/CompileNew.go create mode 100644 src/types/Pointer.go create mode 100644 src/types/Struct.go diff --git a/examples/point/point.q b/examples/point/point.q new file mode 100644 index 0000000..8c90e1f --- /dev/null +++ b/examples/point/point.q @@ -0,0 +1,11 @@ +struct Point { + x Int + y Int +} + +main() { + p := new(Point) + p.x = 4 + p.y = 2 + delete(p) +} \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q index c1207f7..a2f0e97 100644 --- a/lib/sys/sys_linux.q +++ b/lib/sys/sys_linux.q @@ -22,8 +22,8 @@ munmap(address Pointer, length Int) -> Int { return syscall(11, address, length) } -nanosleep(timespec Pointer) -> Int { - return syscall(35, timespec, 0) +nanosleep(duration Pointer) -> Int { + return syscall(35, duration, 0) } clone(flags Int, stack Pointer) -> Int { diff --git a/lib/time/time.q b/lib/time/time.q index 9890e67..bd22148 100644 --- a/lib/time/time.q +++ b/lib/time/time.q @@ -1,4 +1,3 @@ -import mem import sys sleep(nanoseconds Int) { @@ -8,10 +7,9 @@ sleep(nanoseconds Int) { seconds, nanoseconds = nanoseconds / 1000000000 } - timespec := mem.alloc(16) - store(timespec, 8, seconds) - offset := timespec + 8 - store(offset, 8, nanoseconds) - sys.nanosleep(timespec) - mem.free(timespec, 16) + duration := new(timespec) + duration.seconds = seconds + duration.nanoseconds = nanoseconds + sys.nanosleep(duration) + delete(duration) } \ No newline at end of file diff --git a/src/asm/MemoryLabel.go b/src/asm/MemoryLabel.go index 0d4ebf7..63108c8 100644 --- a/src/asm/MemoryLabel.go +++ b/src/asm/MemoryLabel.go @@ -2,6 +2,7 @@ package asm import ( "fmt" + "math" ) // MemoryLabel operates with a memory address and a number. @@ -12,6 +13,10 @@ type MemoryLabel struct { // String returns a human readable version. func (data *MemoryLabel) String() string { + if data.Address.OffsetRegister == math.MaxUint8 { + return fmt.Sprintf("%dB [%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.Offset, data.Label) + } + return fmt.Sprintf("%dB [%s+%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Label) } diff --git a/src/asm/MemoryNumber.go b/src/asm/MemoryNumber.go index 3e7a54a..1a255b3 100644 --- a/src/asm/MemoryNumber.go +++ b/src/asm/MemoryNumber.go @@ -2,6 +2,7 @@ package asm import ( "fmt" + "math" ) // MemoryNumber operates with a memory address and a number. @@ -12,6 +13,10 @@ type MemoryNumber struct { // String returns a human readable version. func (data *MemoryNumber) String() string { + if data.Address.OffsetRegister == math.MaxUint8 { + return fmt.Sprintf("%dB [%s+%d], %d", data.Address.Length, data.Address.Base, data.Address.Offset, data.Number) + } + return fmt.Sprintf("%dB [%s+%s+%d], %d", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Number) } diff --git a/src/asm/MemoryRegister.go b/src/asm/MemoryRegister.go index ae232e5..c35a3c8 100644 --- a/src/asm/MemoryRegister.go +++ b/src/asm/MemoryRegister.go @@ -2,6 +2,7 @@ package asm import ( "fmt" + "math" "git.akyoto.dev/cli/q/src/cpu" ) @@ -14,6 +15,10 @@ type MemoryRegister struct { // String returns a human readable version. func (data *MemoryRegister) String() string { + if data.Address.OffsetRegister == math.MaxUint8 { + return fmt.Sprintf("%dB [%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.Offset, data.Register) + } + return fmt.Sprintf("%dB [%s+%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Register) } diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index dd35a48..28ffb2e 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -10,11 +10,11 @@ import ( ) // Compile waits for the scan to finish and compiles all functions. -func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Type, errs <-chan error) (Result, error) { +func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-chan types.Type, errs <-chan error) (Result, error) { result := Result{} allFiles := make([]*fs.File, 0, 8) allFunctions := map[string]*core.Function{} - allTypes := map[string]*types.Type{} + allTypes := map[string]types.Type{} for functions != nil || files != nil || errs != nil { select { @@ -25,6 +25,7 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c } function.Functions = allFunctions + function.Types = allTypes allFunctions[function.UniqueName] = function case typ, ok := <-structs: @@ -33,7 +34,7 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c continue } - allTypes[typ.Name] = typ + allTypes[typ.UniqueName()] = typ case file, ok := <-files: if !ok { diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 8949cbc..6325e21 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -26,6 +26,10 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return f.Execute(operator, variable.Register, right) } + if left.Token.Kind == token.Period { + return f.CompileAssignField(node) + } + if left.Token.Kind == token.Array { return f.CompileAssignArray(node) } diff --git a/src/core/CompileAssignField.go b/src/core/CompileAssignField.go new file mode 100644 index 0000000..4b74e78 --- /dev/null +++ b/src/core/CompileAssignField.go @@ -0,0 +1,44 @@ +package core + +import ( + "math" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/types" +) + +// CompileAssignField compiles a memory write to a struct field. +func (f *Function) CompileAssignField(node *ast.Assign) error { + destination := node.Expression.Children[0] + value := node.Expression.Children[1] + name := destination.Children[0].Token.Text(f.File.Bytes) + fieldName := destination.Children[1].Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, destination.Children[0].Token.Position) + } + + defer f.UseVariable(variable) + pointer := variable.Type.(*types.Pointer) + structure := pointer.To.(*types.Struct) + + for _, field := range structure.Fields { + if field.Name == fieldName { + memory := asm.Memory{ + Base: variable.Register, + Offset: field.Offset, + OffsetRegister: math.MaxUint8, + Length: field.Type.TotalSize(), + } + + _, err := f.ExpressionToMemory(value, memory) + return err + } + } + + // TODO: unknown field error + return nil +} diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index a723240..f476d94 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -26,11 +26,20 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { if nameNode.IsLeaf() { name = nameNode.Token.Text(f.File.Bytes) - if name == "syscall" { + switch name { + case "syscall": return nil, f.CompileSyscall(root) - } - - if name == "store" { + case "new": + return &Function{ + ReturnTypes: []types.Type{ + &types.Pointer{ + To: f.Types[root.Children[1].Token.Text(f.File.Bytes)], + }, + }, + }, f.CompileNew(root) + case "delete": + return nil, f.CompileDelete(root) + case "store": return nil, f.CompileMemoryStore(root) } } else { @@ -85,8 +94,8 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { if !types.Check(typ, fn.Parameters[i].Type) { return nil, errors.New(&errors.TypeMismatch{ - Encountered: typ.Name, - Expected: fn.Parameters[i].Type.Name, + Encountered: typ.UniqueName(), + Expected: fn.Parameters[i].Type.UniqueName(), ParameterName: fn.Parameters[i].Name, }, f.File, parameters[i].Token.Position) } diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go new file mode 100644 index 0000000..1e1b475 --- /dev/null +++ b/src/core/CompileDelete.go @@ -0,0 +1,37 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" +) + +// CompileDelete compiles a `delete` function call which deallocates a struct. +func (f *Function) CompileDelete(root *expression.Expression) error { + parameters := root.Children[1:] + variableName := parameters[0].Token.Text(f.File.Bytes) + variable := f.VariableByName(variableName) + defer f.UseVariable(variable) + f.SaveRegister(f.CPU.Input[0]) + f.SaveRegister(f.CPU.Input[1]) + f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Register) + f.RegisterNumber(asm.MOVE, f.CPU.Input[1], int(variable.Type.(*types.Pointer).To.TotalSize())) + + for _, register := range f.CPU.General { + if f.RegisterIsUsed(register) { + f.Register(asm.PUSH, register) + } + } + + f.Call("mem.free") + + for i := len(f.CPU.General) - 1; i >= 0; i-- { + register := f.CPU.General[i] + + if f.RegisterIsUsed(register) { + f.Register(asm.POP, register) + } + } + + return nil +} diff --git a/src/core/CompileMemoryStore.go b/src/core/CompileMemoryStore.go index b0dd120..1c84e72 100644 --- a/src/core/CompileMemoryStore.go +++ b/src/core/CompileMemoryStore.go @@ -8,7 +8,7 @@ import ( "git.akyoto.dev/cli/q/src/expression" ) -// CompileMemoryStore ... +// CompileMemoryStore compiles a variable-width store to memory. func (f *Function) CompileMemoryStore(root *expression.Expression) error { parameters := root.Children[1:] name := parameters[0].Token.Text(f.File.Bytes) diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go new file mode 100644 index 0000000..04c6eb1 --- /dev/null +++ b/src/core/CompileNew.go @@ -0,0 +1,33 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/expression" +) + +// CompileNew compiles a `new` function call which allocates a struct. +func (f *Function) CompileNew(root *expression.Expression) error { + parameters := root.Children[1:] + structName := parameters[0].Token.Text(f.File.Bytes) + typ := f.Types[structName] + f.SaveRegister(f.CPU.Input[0]) + f.RegisterNumber(asm.MOVE, f.CPU.Input[0], int(typ.TotalSize())) + + for _, register := range f.CPU.General { + if f.RegisterIsUsed(register) { + f.Register(asm.PUSH, register) + } + } + + f.Call("mem.alloc") + + for i := len(f.CPU.General) - 1; i >= 0; i-- { + register := f.CPU.General[i] + + if f.RegisterIsUsed(register) { + f.Register(asm.POP, register) + } + } + + return nil +} diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 8fa72c8..023da02 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -27,8 +27,8 @@ func (f *Function) CompileReturn(node *ast.Return) error { if !types.Check(typ, f.ReturnTypes[i]) { return errors.New(&errors.TypeMismatch{ - Encountered: typ.Name, - Expected: f.ReturnTypes[i].Name, + Encountered: typ.UniqueName(), + Expected: f.ReturnTypes[i].UniqueName(), ParameterName: "", IsReturn: true, }, f.File, node.Values[i].Token.Position) diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 349c077..cd3f384 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -8,7 +8,7 @@ import ( ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. -func (f *Function) Evaluate(expr *expression.Expression) (*types.Type, cpu.Register, bool, error) { +func (f *Function) Evaluate(expr *expression.Expression) (types.Type, cpu.Register, bool, error) { if expr.Token.Kind == token.Identifier { name := expr.Token.Text(f.File.Bytes) variable := f.VariableByName(name) diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index 29ca917..e50262d 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -10,21 +10,21 @@ import ( ) // ExpressionToMemory puts the result of an expression into the specified memory address. -func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (*types.Type, error) { +func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (types.Type, error) { if node.IsLeaf() { if node.Token.Kind == token.Identifier { name := node.Token.Text(f.File.Bytes) variable, function := f.Identifier(name) if variable != nil { - f.UseVariable(variable) f.MemoryRegister(asm.STORE, memory, variable.Register) - return types.Pointer, nil + f.UseVariable(variable) + return types.PointerAny, nil } if function != nil { f.MemoryLabel(asm.STORE, memory, function.UniqueName) - return types.Pointer, nil + return types.PointerAny, nil } return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Token.Position) @@ -39,7 +39,7 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me size := byte(sizeof.Signed(int64(number))) - if size != memory.Length { + if size > memory.Length { return nil, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 7c5b31f..16d8e7c 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -11,7 +11,7 @@ import ( ) // ExpressionToRegister puts the result of an expression into the specified register. -func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (*types.Type, error) { +func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { f.SaveRegister(register) if node.IsFolded { @@ -72,7 +72,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return nil, err } - if typ == types.Pointer && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.Pointer { + if typ == types.PointerAny && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.PointerAny { typ = types.Int } diff --git a/src/core/Function.go b/src/core/Function.go index 4694800..966b00c 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -18,8 +18,9 @@ type Function struct { File *fs.File Body token.List Parameters []*scope.Variable - ReturnTypes []*types.Type + ReturnTypes []types.Type Functions map[string]*Function + Types map[string]types.Type DLLs dll.List Err error deferred []func() diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index 8f8d53a..af65813 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -10,7 +10,7 @@ import ( // TokenToRegister moves a token into a register. // It only works with identifiers, numbers and strings. -func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (*types.Type, error) { +func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types.Type, error) { switch t.Kind { case token.Identifier: name := t.Text(f.File.Bytes) @@ -26,7 +26,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (*types if function != nil { f.SaveRegister(register) f.RegisterLabel(asm.MOVE, register, function.UniqueName) - return types.Pointer, nil + return types.PointerAny, nil } return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) @@ -48,7 +48,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (*types label := f.AddBytes(data) f.SaveRegister(register) f.RegisterLabel(asm.MOVE, register, label) - return types.Pointer, nil + return types.PointerAny, nil default: return nil, errors.New(errors.InvalidExpression, f.File, t.Position) diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go index df17032..f2d3163 100644 --- a/src/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -1,20 +1,25 @@ package scanner import ( + "path/filepath" + + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/types" ) // Scan scans the list of files. -func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan *types.Type, <-chan error) { +func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan types.Type, <-chan error) { scanner := Scanner{ files: make(chan *fs.File), functions: make(chan *core.Function), - types: make(chan *types.Type), + types: make(chan types.Type), errors: make(chan error), } + scanner.queueDirectory(filepath.Join(config.Library, "mem"), "mem") + go func() { scanner.queue(files...) scanner.group.Wait() diff --git a/src/scanner/Scanner.go b/src/scanner/Scanner.go index f1f0835..13485f5 100644 --- a/src/scanner/Scanner.go +++ b/src/scanner/Scanner.go @@ -12,7 +12,7 @@ import ( type Scanner struct { files chan *fs.File functions chan *core.Function - types chan *types.Type + types chan types.Type errors chan error queued sync.Map group sync.WaitGroup diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index dddf481..40ecf36 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -17,7 +17,7 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro structName := tokens[i].Text(file.Bytes) - typ := &types.Type{ + structure := &types.Struct{ Name: structName, } @@ -39,14 +39,14 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro fieldType := types.Parse(fieldTypeName) i++ - typ.Fields = append(typ.Fields, &types.Field{ + structure.Fields = append(structure.Fields, &types.Field{ Type: fieldType, Name: fieldName, Position: token.Position(fieldPosition), - Offset: typ.Size, + Offset: structure.Size, }) - typ.Size += fieldType.Size + structure.Size += fieldType.TotalSize() } if tokens[i].Kind == token.BlockEnd { @@ -61,6 +61,6 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) } - s.types <- typ + s.types <- structure return i, nil } diff --git a/src/scope/Variable.go b/src/scope/Variable.go index d64ddca..7387ab6 100644 --- a/src/scope/Variable.go +++ b/src/scope/Variable.go @@ -7,7 +7,7 @@ import ( // Variable represents a named register. type Variable struct { - Type *types.Type + Type types.Type Name string Alive uint8 Register cpu.Register diff --git a/src/types/Base.go b/src/types/Base.go index f28b5dd..5f3d27b 100644 --- a/src/types/Base.go +++ b/src/types/Base.go @@ -1,13 +1,13 @@ package types var ( - Float64 = &Type{Name: "Float64", Size: 8} - Float32 = &Type{Name: "Float32", Size: 4} - Int64 = &Type{Name: "Int64", Size: 8} - Int32 = &Type{Name: "Int32", Size: 4} - Int16 = &Type{Name: "Int16", Size: 2} - Int8 = &Type{Name: "Int8", Size: 1} - Pointer = &Type{Name: "Pointer", Size: 8} + Float64 = &Struct{Name: "Float64", Size: 8} + Float32 = &Struct{Name: "Float32", Size: 4} + Int64 = &Struct{Name: "Int64", Size: 8} + Int32 = &Struct{Name: "Int32", Size: 4} + Int16 = &Struct{Name: "Int16", Size: 2} + Int8 = &Struct{Name: "Int8", Size: 1} + PointerAny = &Pointer{To: nil} ) var ( diff --git a/src/types/Check.go b/src/types/Check.go index dbf9808..fe1eb20 100644 --- a/src/types/Check.go +++ b/src/types/Check.go @@ -1,6 +1,21 @@ package types -// Check returns true if the first type can be converted into the second type. -func Check(a *Type, b *Type) bool { - return a == nil || a == b +// Check returns true if the encountered type `a` can be converted into the expected type `b`. +func Check(a Type, b Type) bool { + if a == nil { + return true + } + + if a == b { + return true + } + + aPointer, aIsPointer := a.(*Pointer) + bPointer, bIsPointer := b.(*Pointer) + + if aIsPointer && bIsPointer && (bPointer.To == nil || aPointer.To == bPointer.To) { + return true + } + + return false } diff --git a/src/types/Field.go b/src/types/Field.go index aa0d554..6e155ff 100644 --- a/src/types/Field.go +++ b/src/types/Field.go @@ -4,7 +4,7 @@ import "git.akyoto.dev/cli/q/src/token" // Field is a field in a data structure. type Field struct { - Type *Type + Type Type Name string Position token.Position Offset uint8 diff --git a/src/types/Parse.go b/src/types/Parse.go index 0cfc305..6fcc1f2 100644 --- a/src/types/Parse.go +++ b/src/types/Parse.go @@ -1,7 +1,7 @@ package types // Parse creates a new type from a list of tokens. -func Parse(name string) *Type { +func Parse(name string) Type { switch name { case "Int": return Int @@ -20,7 +20,7 @@ func Parse(name string) *Type { case "Float32": return Float32 case "Pointer": - return Pointer + return PointerAny default: panic("Unknown type " + name) } diff --git a/src/types/ParseList.go b/src/types/ParseList.go index 56a8592..137aec2 100644 --- a/src/types/ParseList.go +++ b/src/types/ParseList.go @@ -3,8 +3,8 @@ package types import "git.akyoto.dev/cli/q/src/token" // ParseList generates a list of types from comma separated tokens. -func ParseList(tokens token.List, source []byte) []*Type { - var list []*Type +func ParseList(tokens token.List, source []byte) []Type { + var list []Type tokens.Split(func(parameter token.List) error { typ := Parse(parameter.Text(source)) diff --git a/src/types/Pointer.go b/src/types/Pointer.go new file mode 100644 index 0000000..ccc92e7 --- /dev/null +++ b/src/types/Pointer.go @@ -0,0 +1,17 @@ +package types + +type Pointer struct { + To Type +} + +func (p *Pointer) UniqueName() string { + if p.To == nil { + return "Pointer" + } + + return "Pointer:" + p.To.UniqueName() +} + +func (p *Pointer) TotalSize() uint8 { + return 8 +} diff --git a/src/types/Struct.go b/src/types/Struct.go new file mode 100644 index 0000000..d6f7c7a --- /dev/null +++ b/src/types/Struct.go @@ -0,0 +1,16 @@ +package types + +// Struct is a structure in memory whose regions are addressable with fields. +type Struct struct { + Name string + Fields []*Field + Size uint8 +} + +func (s *Struct) UniqueName() string { + return s.Name +} + +func (s *Struct) TotalSize() uint8 { + return s.Size +} diff --git a/src/types/Type.go b/src/types/Type.go index d9ce347..d548867 100644 --- a/src/types/Type.go +++ b/src/types/Type.go @@ -1,8 +1,6 @@ package types -// Type represents a type in the type system. -type Type struct { - Name string - Fields []*Field - Size uint8 +type Type interface { + UniqueName() string + TotalSize() uint8 } diff --git a/src/x64/Registers.go b/src/x64/Registers.go index 0dc55b9..57d4e8b 100644 --- a/src/x64/Registers.go +++ b/src/x64/Registers.go @@ -25,7 +25,7 @@ var ( AllRegisters = []cpu.Register{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15} SyscallInputRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} SyscallOutputRegisters = []cpu.Register{RAX, RCX, R11} - GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} + GeneralRegisters = []cpu.Register{RCX, RBX, R11, R12, R13, R14, R15, RBP} InputRegisters = SyscallInputRegisters OutputRegisters = SyscallInputRegisters WindowsInputRegisters = []cpu.Register{RCX, RDX, R8, R9} From 03a3bd8f02d54890f7994e9833e6767516589ac5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 4 Feb 2025 18:16:31 +0100 Subject: [PATCH 0636/1012] Implemented structs --- examples/point/point.q | 11 ++++++++ lib/sys/sys_linux.q | 4 +-- lib/time/time.q | 12 ++++----- src/asm/MemoryLabel.go | 5 ++++ src/asm/MemoryNumber.go | 5 ++++ src/asm/MemoryRegister.go | 5 ++++ src/compiler/Compile.go | 7 ++--- src/core/CompileAssign.go | 4 +++ src/core/CompileAssignField.go | 44 ++++++++++++++++++++++++++++++++ src/core/CompileCall.go | 21 ++++++++++----- src/core/CompileDelete.go | 37 +++++++++++++++++++++++++++ src/core/CompileMemoryStore.go | 2 +- src/core/CompileNew.go | 33 ++++++++++++++++++++++++ src/core/CompileReturn.go | 4 +-- src/core/Evaluate.go | 2 +- src/core/ExpressionToMemory.go | 10 ++++---- src/core/ExpressionToRegister.go | 4 +-- src/core/Function.go | 3 ++- src/core/TokenToRegister.go | 6 ++--- src/scanner/Scan.go | 9 +++++-- src/scanner/Scanner.go | 2 +- src/scanner/scanStruct.go | 10 ++++---- src/scope/Variable.go | 2 +- src/types/Base.go | 14 +++++----- src/types/Check.go | 21 ++++++++++++--- src/types/Field.go | 2 +- src/types/Parse.go | 4 +-- src/types/ParseList.go | 4 +-- src/types/Pointer.go | 17 ++++++++++++ src/types/Struct.go | 16 ++++++++++++ src/types/Type.go | 8 +++--- src/x64/Registers.go | 2 +- 32 files changed, 267 insertions(+), 63 deletions(-) create mode 100644 examples/point/point.q create mode 100644 src/core/CompileAssignField.go create mode 100644 src/core/CompileDelete.go create mode 100644 src/core/CompileNew.go create mode 100644 src/types/Pointer.go create mode 100644 src/types/Struct.go diff --git a/examples/point/point.q b/examples/point/point.q new file mode 100644 index 0000000..8c90e1f --- /dev/null +++ b/examples/point/point.q @@ -0,0 +1,11 @@ +struct Point { + x Int + y Int +} + +main() { + p := new(Point) + p.x = 4 + p.y = 2 + delete(p) +} \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q index c1207f7..a2f0e97 100644 --- a/lib/sys/sys_linux.q +++ b/lib/sys/sys_linux.q @@ -22,8 +22,8 @@ munmap(address Pointer, length Int) -> Int { return syscall(11, address, length) } -nanosleep(timespec Pointer) -> Int { - return syscall(35, timespec, 0) +nanosleep(duration Pointer) -> Int { + return syscall(35, duration, 0) } clone(flags Int, stack Pointer) -> Int { diff --git a/lib/time/time.q b/lib/time/time.q index 9890e67..bd22148 100644 --- a/lib/time/time.q +++ b/lib/time/time.q @@ -1,4 +1,3 @@ -import mem import sys sleep(nanoseconds Int) { @@ -8,10 +7,9 @@ sleep(nanoseconds Int) { seconds, nanoseconds = nanoseconds / 1000000000 } - timespec := mem.alloc(16) - store(timespec, 8, seconds) - offset := timespec + 8 - store(offset, 8, nanoseconds) - sys.nanosleep(timespec) - mem.free(timespec, 16) + duration := new(timespec) + duration.seconds = seconds + duration.nanoseconds = nanoseconds + sys.nanosleep(duration) + delete(duration) } \ No newline at end of file diff --git a/src/asm/MemoryLabel.go b/src/asm/MemoryLabel.go index 0d4ebf7..63108c8 100644 --- a/src/asm/MemoryLabel.go +++ b/src/asm/MemoryLabel.go @@ -2,6 +2,7 @@ package asm import ( "fmt" + "math" ) // MemoryLabel operates with a memory address and a number. @@ -12,6 +13,10 @@ type MemoryLabel struct { // String returns a human readable version. func (data *MemoryLabel) String() string { + if data.Address.OffsetRegister == math.MaxUint8 { + return fmt.Sprintf("%dB [%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.Offset, data.Label) + } + return fmt.Sprintf("%dB [%s+%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Label) } diff --git a/src/asm/MemoryNumber.go b/src/asm/MemoryNumber.go index 3e7a54a..1a255b3 100644 --- a/src/asm/MemoryNumber.go +++ b/src/asm/MemoryNumber.go @@ -2,6 +2,7 @@ package asm import ( "fmt" + "math" ) // MemoryNumber operates with a memory address and a number. @@ -12,6 +13,10 @@ type MemoryNumber struct { // String returns a human readable version. func (data *MemoryNumber) String() string { + if data.Address.OffsetRegister == math.MaxUint8 { + return fmt.Sprintf("%dB [%s+%d], %d", data.Address.Length, data.Address.Base, data.Address.Offset, data.Number) + } + return fmt.Sprintf("%dB [%s+%s+%d], %d", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Number) } diff --git a/src/asm/MemoryRegister.go b/src/asm/MemoryRegister.go index ae232e5..c35a3c8 100644 --- a/src/asm/MemoryRegister.go +++ b/src/asm/MemoryRegister.go @@ -2,6 +2,7 @@ package asm import ( "fmt" + "math" "git.akyoto.dev/cli/q/src/cpu" ) @@ -14,6 +15,10 @@ type MemoryRegister struct { // String returns a human readable version. func (data *MemoryRegister) String() string { + if data.Address.OffsetRegister == math.MaxUint8 { + return fmt.Sprintf("%dB [%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.Offset, data.Register) + } + return fmt.Sprintf("%dB [%s+%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Register) } diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index dd35a48..28ffb2e 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -10,11 +10,11 @@ import ( ) // Compile waits for the scan to finish and compiles all functions. -func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Type, errs <-chan error) (Result, error) { +func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-chan types.Type, errs <-chan error) (Result, error) { result := Result{} allFiles := make([]*fs.File, 0, 8) allFunctions := map[string]*core.Function{} - allTypes := map[string]*types.Type{} + allTypes := map[string]types.Type{} for functions != nil || files != nil || errs != nil { select { @@ -25,6 +25,7 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c } function.Functions = allFunctions + function.Types = allTypes allFunctions[function.UniqueName] = function case typ, ok := <-structs: @@ -33,7 +34,7 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c continue } - allTypes[typ.Name] = typ + allTypes[typ.UniqueName()] = typ case file, ok := <-files: if !ok { diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 8949cbc..6325e21 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -26,6 +26,10 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return f.Execute(operator, variable.Register, right) } + if left.Token.Kind == token.Period { + return f.CompileAssignField(node) + } + if left.Token.Kind == token.Array { return f.CompileAssignArray(node) } diff --git a/src/core/CompileAssignField.go b/src/core/CompileAssignField.go new file mode 100644 index 0000000..4b74e78 --- /dev/null +++ b/src/core/CompileAssignField.go @@ -0,0 +1,44 @@ +package core + +import ( + "math" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/types" +) + +// CompileAssignField compiles a memory write to a struct field. +func (f *Function) CompileAssignField(node *ast.Assign) error { + destination := node.Expression.Children[0] + value := node.Expression.Children[1] + name := destination.Children[0].Token.Text(f.File.Bytes) + fieldName := destination.Children[1].Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, destination.Children[0].Token.Position) + } + + defer f.UseVariable(variable) + pointer := variable.Type.(*types.Pointer) + structure := pointer.To.(*types.Struct) + + for _, field := range structure.Fields { + if field.Name == fieldName { + memory := asm.Memory{ + Base: variable.Register, + Offset: field.Offset, + OffsetRegister: math.MaxUint8, + Length: field.Type.TotalSize(), + } + + _, err := f.ExpressionToMemory(value, memory) + return err + } + } + + // TODO: unknown field error + return nil +} diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index a723240..f476d94 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -26,11 +26,20 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { if nameNode.IsLeaf() { name = nameNode.Token.Text(f.File.Bytes) - if name == "syscall" { + switch name { + case "syscall": return nil, f.CompileSyscall(root) - } - - if name == "store" { + case "new": + return &Function{ + ReturnTypes: []types.Type{ + &types.Pointer{ + To: f.Types[root.Children[1].Token.Text(f.File.Bytes)], + }, + }, + }, f.CompileNew(root) + case "delete": + return nil, f.CompileDelete(root) + case "store": return nil, f.CompileMemoryStore(root) } } else { @@ -85,8 +94,8 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { if !types.Check(typ, fn.Parameters[i].Type) { return nil, errors.New(&errors.TypeMismatch{ - Encountered: typ.Name, - Expected: fn.Parameters[i].Type.Name, + Encountered: typ.UniqueName(), + Expected: fn.Parameters[i].Type.UniqueName(), ParameterName: fn.Parameters[i].Name, }, f.File, parameters[i].Token.Position) } diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go new file mode 100644 index 0000000..1e1b475 --- /dev/null +++ b/src/core/CompileDelete.go @@ -0,0 +1,37 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" +) + +// CompileDelete compiles a `delete` function call which deallocates a struct. +func (f *Function) CompileDelete(root *expression.Expression) error { + parameters := root.Children[1:] + variableName := parameters[0].Token.Text(f.File.Bytes) + variable := f.VariableByName(variableName) + defer f.UseVariable(variable) + f.SaveRegister(f.CPU.Input[0]) + f.SaveRegister(f.CPU.Input[1]) + f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Register) + f.RegisterNumber(asm.MOVE, f.CPU.Input[1], int(variable.Type.(*types.Pointer).To.TotalSize())) + + for _, register := range f.CPU.General { + if f.RegisterIsUsed(register) { + f.Register(asm.PUSH, register) + } + } + + f.Call("mem.free") + + for i := len(f.CPU.General) - 1; i >= 0; i-- { + register := f.CPU.General[i] + + if f.RegisterIsUsed(register) { + f.Register(asm.POP, register) + } + } + + return nil +} diff --git a/src/core/CompileMemoryStore.go b/src/core/CompileMemoryStore.go index b0dd120..1c84e72 100644 --- a/src/core/CompileMemoryStore.go +++ b/src/core/CompileMemoryStore.go @@ -8,7 +8,7 @@ import ( "git.akyoto.dev/cli/q/src/expression" ) -// CompileMemoryStore ... +// CompileMemoryStore compiles a variable-width store to memory. func (f *Function) CompileMemoryStore(root *expression.Expression) error { parameters := root.Children[1:] name := parameters[0].Token.Text(f.File.Bytes) diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go new file mode 100644 index 0000000..04c6eb1 --- /dev/null +++ b/src/core/CompileNew.go @@ -0,0 +1,33 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/expression" +) + +// CompileNew compiles a `new` function call which allocates a struct. +func (f *Function) CompileNew(root *expression.Expression) error { + parameters := root.Children[1:] + structName := parameters[0].Token.Text(f.File.Bytes) + typ := f.Types[structName] + f.SaveRegister(f.CPU.Input[0]) + f.RegisterNumber(asm.MOVE, f.CPU.Input[0], int(typ.TotalSize())) + + for _, register := range f.CPU.General { + if f.RegisterIsUsed(register) { + f.Register(asm.PUSH, register) + } + } + + f.Call("mem.alloc") + + for i := len(f.CPU.General) - 1; i >= 0; i-- { + register := f.CPU.General[i] + + if f.RegisterIsUsed(register) { + f.Register(asm.POP, register) + } + } + + return nil +} diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 8fa72c8..023da02 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -27,8 +27,8 @@ func (f *Function) CompileReturn(node *ast.Return) error { if !types.Check(typ, f.ReturnTypes[i]) { return errors.New(&errors.TypeMismatch{ - Encountered: typ.Name, - Expected: f.ReturnTypes[i].Name, + Encountered: typ.UniqueName(), + Expected: f.ReturnTypes[i].UniqueName(), ParameterName: "", IsReturn: true, }, f.File, node.Values[i].Token.Position) diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 349c077..cd3f384 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -8,7 +8,7 @@ import ( ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. -func (f *Function) Evaluate(expr *expression.Expression) (*types.Type, cpu.Register, bool, error) { +func (f *Function) Evaluate(expr *expression.Expression) (types.Type, cpu.Register, bool, error) { if expr.Token.Kind == token.Identifier { name := expr.Token.Text(f.File.Bytes) variable := f.VariableByName(name) diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index 29ca917..e50262d 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -10,21 +10,21 @@ import ( ) // ExpressionToMemory puts the result of an expression into the specified memory address. -func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (*types.Type, error) { +func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (types.Type, error) { if node.IsLeaf() { if node.Token.Kind == token.Identifier { name := node.Token.Text(f.File.Bytes) variable, function := f.Identifier(name) if variable != nil { - f.UseVariable(variable) f.MemoryRegister(asm.STORE, memory, variable.Register) - return types.Pointer, nil + f.UseVariable(variable) + return types.PointerAny, nil } if function != nil { f.MemoryLabel(asm.STORE, memory, function.UniqueName) - return types.Pointer, nil + return types.PointerAny, nil } return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Token.Position) @@ -39,7 +39,7 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me size := byte(sizeof.Signed(int64(number))) - if size != memory.Length { + if size > memory.Length { return nil, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 7c5b31f..16d8e7c 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -11,7 +11,7 @@ import ( ) // ExpressionToRegister puts the result of an expression into the specified register. -func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (*types.Type, error) { +func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { f.SaveRegister(register) if node.IsFolded { @@ -72,7 +72,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return nil, err } - if typ == types.Pointer && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.Pointer { + if typ == types.PointerAny && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.PointerAny { typ = types.Int } diff --git a/src/core/Function.go b/src/core/Function.go index 4694800..966b00c 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -18,8 +18,9 @@ type Function struct { File *fs.File Body token.List Parameters []*scope.Variable - ReturnTypes []*types.Type + ReturnTypes []types.Type Functions map[string]*Function + Types map[string]types.Type DLLs dll.List Err error deferred []func() diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index 8f8d53a..af65813 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -10,7 +10,7 @@ import ( // TokenToRegister moves a token into a register. // It only works with identifiers, numbers and strings. -func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (*types.Type, error) { +func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types.Type, error) { switch t.Kind { case token.Identifier: name := t.Text(f.File.Bytes) @@ -26,7 +26,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (*types if function != nil { f.SaveRegister(register) f.RegisterLabel(asm.MOVE, register, function.UniqueName) - return types.Pointer, nil + return types.PointerAny, nil } return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) @@ -48,7 +48,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (*types label := f.AddBytes(data) f.SaveRegister(register) f.RegisterLabel(asm.MOVE, register, label) - return types.Pointer, nil + return types.PointerAny, nil default: return nil, errors.New(errors.InvalidExpression, f.File, t.Position) diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go index df17032..f2d3163 100644 --- a/src/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -1,20 +1,25 @@ package scanner import ( + "path/filepath" + + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/types" ) // Scan scans the list of files. -func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan *types.Type, <-chan error) { +func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan types.Type, <-chan error) { scanner := Scanner{ files: make(chan *fs.File), functions: make(chan *core.Function), - types: make(chan *types.Type), + types: make(chan types.Type), errors: make(chan error), } + scanner.queueDirectory(filepath.Join(config.Library, "mem"), "mem") + go func() { scanner.queue(files...) scanner.group.Wait() diff --git a/src/scanner/Scanner.go b/src/scanner/Scanner.go index f1f0835..13485f5 100644 --- a/src/scanner/Scanner.go +++ b/src/scanner/Scanner.go @@ -12,7 +12,7 @@ import ( type Scanner struct { files chan *fs.File functions chan *core.Function - types chan *types.Type + types chan types.Type errors chan error queued sync.Map group sync.WaitGroup diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index dddf481..40ecf36 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -17,7 +17,7 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro structName := tokens[i].Text(file.Bytes) - typ := &types.Type{ + structure := &types.Struct{ Name: structName, } @@ -39,14 +39,14 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro fieldType := types.Parse(fieldTypeName) i++ - typ.Fields = append(typ.Fields, &types.Field{ + structure.Fields = append(structure.Fields, &types.Field{ Type: fieldType, Name: fieldName, Position: token.Position(fieldPosition), - Offset: typ.Size, + Offset: structure.Size, }) - typ.Size += fieldType.Size + structure.Size += fieldType.TotalSize() } if tokens[i].Kind == token.BlockEnd { @@ -61,6 +61,6 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) } - s.types <- typ + s.types <- structure return i, nil } diff --git a/src/scope/Variable.go b/src/scope/Variable.go index d64ddca..7387ab6 100644 --- a/src/scope/Variable.go +++ b/src/scope/Variable.go @@ -7,7 +7,7 @@ import ( // Variable represents a named register. type Variable struct { - Type *types.Type + Type types.Type Name string Alive uint8 Register cpu.Register diff --git a/src/types/Base.go b/src/types/Base.go index f28b5dd..5f3d27b 100644 --- a/src/types/Base.go +++ b/src/types/Base.go @@ -1,13 +1,13 @@ package types var ( - Float64 = &Type{Name: "Float64", Size: 8} - Float32 = &Type{Name: "Float32", Size: 4} - Int64 = &Type{Name: "Int64", Size: 8} - Int32 = &Type{Name: "Int32", Size: 4} - Int16 = &Type{Name: "Int16", Size: 2} - Int8 = &Type{Name: "Int8", Size: 1} - Pointer = &Type{Name: "Pointer", Size: 8} + Float64 = &Struct{Name: "Float64", Size: 8} + Float32 = &Struct{Name: "Float32", Size: 4} + Int64 = &Struct{Name: "Int64", Size: 8} + Int32 = &Struct{Name: "Int32", Size: 4} + Int16 = &Struct{Name: "Int16", Size: 2} + Int8 = &Struct{Name: "Int8", Size: 1} + PointerAny = &Pointer{To: nil} ) var ( diff --git a/src/types/Check.go b/src/types/Check.go index dbf9808..fe1eb20 100644 --- a/src/types/Check.go +++ b/src/types/Check.go @@ -1,6 +1,21 @@ package types -// Check returns true if the first type can be converted into the second type. -func Check(a *Type, b *Type) bool { - return a == nil || a == b +// Check returns true if the encountered type `a` can be converted into the expected type `b`. +func Check(a Type, b Type) bool { + if a == nil { + return true + } + + if a == b { + return true + } + + aPointer, aIsPointer := a.(*Pointer) + bPointer, bIsPointer := b.(*Pointer) + + if aIsPointer && bIsPointer && (bPointer.To == nil || aPointer.To == bPointer.To) { + return true + } + + return false } diff --git a/src/types/Field.go b/src/types/Field.go index aa0d554..6e155ff 100644 --- a/src/types/Field.go +++ b/src/types/Field.go @@ -4,7 +4,7 @@ import "git.akyoto.dev/cli/q/src/token" // Field is a field in a data structure. type Field struct { - Type *Type + Type Type Name string Position token.Position Offset uint8 diff --git a/src/types/Parse.go b/src/types/Parse.go index 0cfc305..6fcc1f2 100644 --- a/src/types/Parse.go +++ b/src/types/Parse.go @@ -1,7 +1,7 @@ package types // Parse creates a new type from a list of tokens. -func Parse(name string) *Type { +func Parse(name string) Type { switch name { case "Int": return Int @@ -20,7 +20,7 @@ func Parse(name string) *Type { case "Float32": return Float32 case "Pointer": - return Pointer + return PointerAny default: panic("Unknown type " + name) } diff --git a/src/types/ParseList.go b/src/types/ParseList.go index 56a8592..137aec2 100644 --- a/src/types/ParseList.go +++ b/src/types/ParseList.go @@ -3,8 +3,8 @@ package types import "git.akyoto.dev/cli/q/src/token" // ParseList generates a list of types from comma separated tokens. -func ParseList(tokens token.List, source []byte) []*Type { - var list []*Type +func ParseList(tokens token.List, source []byte) []Type { + var list []Type tokens.Split(func(parameter token.List) error { typ := Parse(parameter.Text(source)) diff --git a/src/types/Pointer.go b/src/types/Pointer.go new file mode 100644 index 0000000..ccc92e7 --- /dev/null +++ b/src/types/Pointer.go @@ -0,0 +1,17 @@ +package types + +type Pointer struct { + To Type +} + +func (p *Pointer) UniqueName() string { + if p.To == nil { + return "Pointer" + } + + return "Pointer:" + p.To.UniqueName() +} + +func (p *Pointer) TotalSize() uint8 { + return 8 +} diff --git a/src/types/Struct.go b/src/types/Struct.go new file mode 100644 index 0000000..d6f7c7a --- /dev/null +++ b/src/types/Struct.go @@ -0,0 +1,16 @@ +package types + +// Struct is a structure in memory whose regions are addressable with fields. +type Struct struct { + Name string + Fields []*Field + Size uint8 +} + +func (s *Struct) UniqueName() string { + return s.Name +} + +func (s *Struct) TotalSize() uint8 { + return s.Size +} diff --git a/src/types/Type.go b/src/types/Type.go index d9ce347..d548867 100644 --- a/src/types/Type.go +++ b/src/types/Type.go @@ -1,8 +1,6 @@ package types -// Type represents a type in the type system. -type Type struct { - Name string - Fields []*Field - Size uint8 +type Type interface { + UniqueName() string + TotalSize() uint8 } diff --git a/src/x64/Registers.go b/src/x64/Registers.go index 0dc55b9..57d4e8b 100644 --- a/src/x64/Registers.go +++ b/src/x64/Registers.go @@ -25,7 +25,7 @@ var ( AllRegisters = []cpu.Register{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15} SyscallInputRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} SyscallOutputRegisters = []cpu.Register{RAX, RCX, R11} - GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} + GeneralRegisters = []cpu.Register{RCX, RBX, R11, R12, R13, R14, R15, RBP} InputRegisters = SyscallInputRegisters OutputRegisters = SyscallInputRegisters WindowsInputRegisters = []cpu.Register{RCX, RDX, R8, R9} From 71d7cdf5b2293ac56088d8301da558c2535cad6f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 4 Feb 2025 20:43:15 +0100 Subject: [PATCH 0637/1012] Implemented reading from struct fields --- examples/point/point.q | 18 +++++++++++++++- src/core/CompileAssignField.go | 26 +++++++++++------------ src/core/ExpressionToRegister.go | 11 ++++++++++ src/errors/UnknownStructField.go | 19 +++++++++++++++++ src/types/Struct.go | 10 +++++++++ tests/errors/UnknownStructField.q | 6 ++++++ tests/errors_test.go | 1 + tests/examples_test.go | 1 + tests/programs/struct.q | 34 +++++++++++++++++++++++++++++++ tests/programs_test.go | 1 + 10 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 src/errors/UnknownStructField.go create mode 100644 tests/errors/UnknownStructField.q create mode 100644 tests/programs/struct.q diff --git a/examples/point/point.q b/examples/point/point.q index 8c90e1f..2898895 100644 --- a/examples/point/point.q +++ b/examples/point/point.q @@ -1,3 +1,6 @@ +import mem +import sys + struct Point { x Int y Int @@ -5,7 +8,20 @@ struct Point { main() { p := new(Point) - p.x = 4 + p.x = 1 p.y = 2 + + out := mem.alloc(8) + out[0] = 'x' + out[1] = ' ' + out[2] = '0' + p.x + out[3] = '\n' + out[4] = 'y' + out[5] = ' ' + out[6] = '0' + p.y + out[7] = '\n' + sys.write(1, out, 8) + mem.free(out) + delete(p) } \ No newline at end of file diff --git a/src/core/CompileAssignField.go b/src/core/CompileAssignField.go index 4b74e78..26d462f 100644 --- a/src/core/CompileAssignField.go +++ b/src/core/CompileAssignField.go @@ -24,21 +24,19 @@ func (f *Function) CompileAssignField(node *ast.Assign) error { defer f.UseVariable(variable) pointer := variable.Type.(*types.Pointer) structure := pointer.To.(*types.Struct) + field := structure.FieldByName(fieldName) - for _, field := range structure.Fields { - if field.Name == fieldName { - memory := asm.Memory{ - Base: variable.Register, - Offset: field.Offset, - OffsetRegister: math.MaxUint8, - Length: field.Type.TotalSize(), - } - - _, err := f.ExpressionToMemory(value, memory) - return err - } + if field == nil { + return errors.New(&errors.UnknownStructField{StructName: structure.Name, FieldName: fieldName}, f.File, destination.Children[1].Token.Position) } - // TODO: unknown field error - return nil + memory := asm.Memory{ + Base: variable.Register, + Offset: field.Offset, + OffsetRegister: math.MaxUint8, + Length: field.Type.TotalSize(), + } + + _, err := f.ExpressionToMemory(value, memory) + return err } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 16d8e7c..684ad38 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -44,6 +44,17 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return types.Int, err } + if node.Token.Kind == token.Period { + left := node.Children[0] + name := left.Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + right := node.Children[1] + name = right.Token.Text(f.File.Bytes) + field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(name) + f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: field.Offset, Length: field.Type.TotalSize()}, register) + return field.Type, nil + } + if len(node.Children) == 1 { if !node.Token.IsUnaryOperator() { return nil, errors.New(errors.MissingOperand, f.File, node.Token.End()) diff --git a/src/errors/UnknownStructField.go b/src/errors/UnknownStructField.go new file mode 100644 index 0000000..163d15c --- /dev/null +++ b/src/errors/UnknownStructField.go @@ -0,0 +1,19 @@ +package errors + +import "fmt" + +// UnknownStructField represents unknown struct fields. +type UnknownStructField struct { + StructName string + FieldName string + CorrectFieldName string +} + +// Error generates the string representation. +func (err *UnknownStructField) Error() string { + if err.CorrectFieldName != "" { + return fmt.Sprintf("Unknown struct field '%s' in '%s', did you mean '%s'?", err.FieldName, err.StructName, err.CorrectFieldName) + } + + return fmt.Sprintf("Unknown struct field '%s' in '%s'", err.FieldName, err.StructName) +} diff --git a/src/types/Struct.go b/src/types/Struct.go index d6f7c7a..d24bdb5 100644 --- a/src/types/Struct.go +++ b/src/types/Struct.go @@ -14,3 +14,13 @@ func (s *Struct) UniqueName() string { func (s *Struct) TotalSize() uint8 { return s.Size } + +func (s *Struct) FieldByName(name string) *Field { + for _, field := range s.Fields { + if field.Name == name { + return field + } + } + + return nil +} diff --git a/tests/errors/UnknownStructField.q b/tests/errors/UnknownStructField.q new file mode 100644 index 0000000..bea43e5 --- /dev/null +++ b/tests/errors/UnknownStructField.q @@ -0,0 +1,6 @@ +struct A {} + +main() { + a := new(A) + a.x = 1 +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 7839f63..7afb488 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -48,6 +48,7 @@ var errs = []struct { {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownPackage.q", &errors.UnknownPackage{Name: "sys"}}, + {"UnknownStructField.q", &errors.UnknownStructField{StructName: "A", FieldName: "x"}}, {"UnusedImport.q", &errors.UnusedImport{Package: "sys"}}, {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, diff --git a/tests/examples_test.go b/tests/examples_test.go index f889c1d..7373867 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -24,6 +24,7 @@ var examples = []struct { {"itoa", "", "9223372036854775807", 0}, {"collatz", "", "6 3 10 5 16 8 4 2 1", 0}, {"fizzbuzz", "", "1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz", 0}, + {"point", "", "x 1\ny 2\n", 0}, {"prime", "", "2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97", 0}, } diff --git a/tests/programs/struct.q b/tests/programs/struct.q new file mode 100644 index 0000000..514a863 --- /dev/null +++ b/tests/programs/struct.q @@ -0,0 +1,34 @@ +struct Point { + x Int + y Int +} + +main() { + p := new(Point) + assert p.x == 0 + assert p.y == 0 + assert p.x == p.y + + p.x = 1 + p.y = 2 + assert p.x == 1 + assert p.y == 2 + assert p.x != p.y + + p.x = p.y + assert p.x == 2 + assert p.y == 2 + assert p.x == p.y + + p.x = p.y + 1 + assert p.x == 3 + assert p.y == 2 + assert p.x != p.y + + p.y = p.x + assert p.x == 3 + assert p.y == 3 + assert p.x == p.y + + delete(p) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 8a6bbb3..f4b7d4d 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -60,6 +60,7 @@ var programs = []struct { {"loop", "", "", 0}, {"loop-lifetime", "", "", 0}, {"out-of-memory", "", "", 0}, + {"struct", "", "", 0}, } func TestPrograms(t *testing.T) { From bde68d4d6433da1f5690fc4a6a4fd38b1879ff7c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 4 Feb 2025 20:43:15 +0100 Subject: [PATCH 0638/1012] Implemented reading from struct fields --- examples/point/point.q | 18 +++++++++++++++- src/core/CompileAssignField.go | 26 +++++++++++------------ src/core/ExpressionToRegister.go | 11 ++++++++++ src/errors/UnknownStructField.go | 19 +++++++++++++++++ src/types/Struct.go | 10 +++++++++ tests/errors/UnknownStructField.q | 6 ++++++ tests/errors_test.go | 1 + tests/examples_test.go | 1 + tests/programs/struct.q | 34 +++++++++++++++++++++++++++++++ tests/programs_test.go | 1 + 10 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 src/errors/UnknownStructField.go create mode 100644 tests/errors/UnknownStructField.q create mode 100644 tests/programs/struct.q diff --git a/examples/point/point.q b/examples/point/point.q index 8c90e1f..2898895 100644 --- a/examples/point/point.q +++ b/examples/point/point.q @@ -1,3 +1,6 @@ +import mem +import sys + struct Point { x Int y Int @@ -5,7 +8,20 @@ struct Point { main() { p := new(Point) - p.x = 4 + p.x = 1 p.y = 2 + + out := mem.alloc(8) + out[0] = 'x' + out[1] = ' ' + out[2] = '0' + p.x + out[3] = '\n' + out[4] = 'y' + out[5] = ' ' + out[6] = '0' + p.y + out[7] = '\n' + sys.write(1, out, 8) + mem.free(out) + delete(p) } \ No newline at end of file diff --git a/src/core/CompileAssignField.go b/src/core/CompileAssignField.go index 4b74e78..26d462f 100644 --- a/src/core/CompileAssignField.go +++ b/src/core/CompileAssignField.go @@ -24,21 +24,19 @@ func (f *Function) CompileAssignField(node *ast.Assign) error { defer f.UseVariable(variable) pointer := variable.Type.(*types.Pointer) structure := pointer.To.(*types.Struct) + field := structure.FieldByName(fieldName) - for _, field := range structure.Fields { - if field.Name == fieldName { - memory := asm.Memory{ - Base: variable.Register, - Offset: field.Offset, - OffsetRegister: math.MaxUint8, - Length: field.Type.TotalSize(), - } - - _, err := f.ExpressionToMemory(value, memory) - return err - } + if field == nil { + return errors.New(&errors.UnknownStructField{StructName: structure.Name, FieldName: fieldName}, f.File, destination.Children[1].Token.Position) } - // TODO: unknown field error - return nil + memory := asm.Memory{ + Base: variable.Register, + Offset: field.Offset, + OffsetRegister: math.MaxUint8, + Length: field.Type.TotalSize(), + } + + _, err := f.ExpressionToMemory(value, memory) + return err } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 16d8e7c..684ad38 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -44,6 +44,17 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return types.Int, err } + if node.Token.Kind == token.Period { + left := node.Children[0] + name := left.Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + right := node.Children[1] + name = right.Token.Text(f.File.Bytes) + field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(name) + f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: field.Offset, Length: field.Type.TotalSize()}, register) + return field.Type, nil + } + if len(node.Children) == 1 { if !node.Token.IsUnaryOperator() { return nil, errors.New(errors.MissingOperand, f.File, node.Token.End()) diff --git a/src/errors/UnknownStructField.go b/src/errors/UnknownStructField.go new file mode 100644 index 0000000..163d15c --- /dev/null +++ b/src/errors/UnknownStructField.go @@ -0,0 +1,19 @@ +package errors + +import "fmt" + +// UnknownStructField represents unknown struct fields. +type UnknownStructField struct { + StructName string + FieldName string + CorrectFieldName string +} + +// Error generates the string representation. +func (err *UnknownStructField) Error() string { + if err.CorrectFieldName != "" { + return fmt.Sprintf("Unknown struct field '%s' in '%s', did you mean '%s'?", err.FieldName, err.StructName, err.CorrectFieldName) + } + + return fmt.Sprintf("Unknown struct field '%s' in '%s'", err.FieldName, err.StructName) +} diff --git a/src/types/Struct.go b/src/types/Struct.go index d6f7c7a..d24bdb5 100644 --- a/src/types/Struct.go +++ b/src/types/Struct.go @@ -14,3 +14,13 @@ func (s *Struct) UniqueName() string { func (s *Struct) TotalSize() uint8 { return s.Size } + +func (s *Struct) FieldByName(name string) *Field { + for _, field := range s.Fields { + if field.Name == name { + return field + } + } + + return nil +} diff --git a/tests/errors/UnknownStructField.q b/tests/errors/UnknownStructField.q new file mode 100644 index 0000000..bea43e5 --- /dev/null +++ b/tests/errors/UnknownStructField.q @@ -0,0 +1,6 @@ +struct A {} + +main() { + a := new(A) + a.x = 1 +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 7839f63..7afb488 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -48,6 +48,7 @@ var errs = []struct { {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownPackage.q", &errors.UnknownPackage{Name: "sys"}}, + {"UnknownStructField.q", &errors.UnknownStructField{StructName: "A", FieldName: "x"}}, {"UnusedImport.q", &errors.UnusedImport{Package: "sys"}}, {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, diff --git a/tests/examples_test.go b/tests/examples_test.go index f889c1d..7373867 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -24,6 +24,7 @@ var examples = []struct { {"itoa", "", "9223372036854775807", 0}, {"collatz", "", "6 3 10 5 16 8 4 2 1", 0}, {"fizzbuzz", "", "1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz", 0}, + {"point", "", "x 1\ny 2\n", 0}, {"prime", "", "2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97", 0}, } diff --git a/tests/programs/struct.q b/tests/programs/struct.q new file mode 100644 index 0000000..514a863 --- /dev/null +++ b/tests/programs/struct.q @@ -0,0 +1,34 @@ +struct Point { + x Int + y Int +} + +main() { + p := new(Point) + assert p.x == 0 + assert p.y == 0 + assert p.x == p.y + + p.x = 1 + p.y = 2 + assert p.x == 1 + assert p.y == 2 + assert p.x != p.y + + p.x = p.y + assert p.x == 2 + assert p.y == 2 + assert p.x == p.y + + p.x = p.y + 1 + assert p.x == 3 + assert p.y == 2 + assert p.x != p.y + + p.y = p.x + assert p.x == 3 + assert p.y == 3 + assert p.x == p.y + + delete(p) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 8a6bbb3..f4b7d4d 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -60,6 +60,7 @@ var programs = []struct { {"loop", "", "", 0}, {"loop-lifetime", "", "", 0}, {"out-of-memory", "", "", 0}, + {"struct", "", "", 0}, } func TestPrograms(t *testing.T) { From bd0a468282d383ea81ca2f593b181b30b680ef98 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 4 Feb 2025 23:58:01 +0100 Subject: [PATCH 0639/1012] Simplified type system --- src/compiler/Compile.go | 2 +- src/core/CompileAssignField.go | 6 +++--- src/core/CompileCall.go | 6 +++--- src/core/CompileDelete.go | 2 +- src/core/CompileNew.go | 2 +- src/core/CompileReturn.go | 6 +++--- src/core/ExpressionToRegister.go | 2 +- src/scanner/scanStruct.go | 11 +++-------- src/types/Base.go | 16 ---------------- src/types/Field.go | 4 ++-- src/types/Float.go | 7 +++++++ src/types/Int.go | 9 +++++++++ src/types/{Check.go => Is.go} | 4 ++-- src/types/Pointer.go | 11 ++++++++--- src/types/Struct.go | 32 +++++++++++++++++++++++--------- src/types/Type.go | 5 +++-- 16 files changed, 70 insertions(+), 55 deletions(-) delete mode 100644 src/types/Base.go create mode 100644 src/types/Float.go create mode 100644 src/types/Int.go rename src/types/{Check.go => Is.go} (68%) diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 28ffb2e..23960a1 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -34,7 +34,7 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c continue } - allTypes[typ.UniqueName()] = typ + allTypes[typ.Name()] = typ case file, ok := <-files: if !ok { diff --git a/src/core/CompileAssignField.go b/src/core/CompileAssignField.go index 26d462f..852ae2d 100644 --- a/src/core/CompileAssignField.go +++ b/src/core/CompileAssignField.go @@ -27,14 +27,14 @@ func (f *Function) CompileAssignField(node *ast.Assign) error { field := structure.FieldByName(fieldName) if field == nil { - return errors.New(&errors.UnknownStructField{StructName: structure.Name, FieldName: fieldName}, f.File, destination.Children[1].Token.Position) + return errors.New(&errors.UnknownStructField{StructName: structure.Name(), FieldName: fieldName}, f.File, destination.Children[1].Token.Position) } memory := asm.Memory{ Base: variable.Register, - Offset: field.Offset, + Offset: byte(field.Offset), OffsetRegister: math.MaxUint8, - Length: field.Type.TotalSize(), + Length: byte(field.Type.Size()), } _, err := f.ExpressionToMemory(value, memory) diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index f476d94..d65baec 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -92,10 +92,10 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { return nil, err } - if !types.Check(typ, fn.Parameters[i].Type) { + if !types.Is(typ, fn.Parameters[i].Type) { return nil, errors.New(&errors.TypeMismatch{ - Encountered: typ.UniqueName(), - Expected: fn.Parameters[i].Type.UniqueName(), + Encountered: typ.Name(), + Expected: fn.Parameters[i].Type.Name(), ParameterName: fn.Parameters[i].Name, }, f.File, parameters[i].Token.Position) } diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index 1e1b475..a8f46bd 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -15,7 +15,7 @@ func (f *Function) CompileDelete(root *expression.Expression) error { f.SaveRegister(f.CPU.Input[0]) f.SaveRegister(f.CPU.Input[1]) f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Register) - f.RegisterNumber(asm.MOVE, f.CPU.Input[1], int(variable.Type.(*types.Pointer).To.TotalSize())) + f.RegisterNumber(asm.MOVE, f.CPU.Input[1], int(variable.Type.(*types.Pointer).To.Size())) for _, register := range f.CPU.General { if f.RegisterIsUsed(register) { diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index 04c6eb1..cdbf76d 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -11,7 +11,7 @@ func (f *Function) CompileNew(root *expression.Expression) error { structName := parameters[0].Token.Text(f.File.Bytes) typ := f.Types[structName] f.SaveRegister(f.CPU.Input[0]) - f.RegisterNumber(asm.MOVE, f.CPU.Input[0], int(typ.TotalSize())) + f.RegisterNumber(asm.MOVE, f.CPU.Input[0], typ.Size()) for _, register := range f.CPU.General { if f.RegisterIsUsed(register) { diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 023da02..9b84b58 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -25,10 +25,10 @@ func (f *Function) CompileReturn(node *ast.Return) error { return err } - if !types.Check(typ, f.ReturnTypes[i]) { + if !types.Is(typ, f.ReturnTypes[i]) { return errors.New(&errors.TypeMismatch{ - Encountered: typ.UniqueName(), - Expected: f.ReturnTypes[i].UniqueName(), + Encountered: typ.Name(), + Expected: f.ReturnTypes[i].Name(), ParameterName: "", IsReturn: true, }, f.File, node.Values[i].Token.Position) diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 684ad38..aa6ad9b 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -51,7 +51,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp right := node.Children[1] name = right.Token.Text(f.File.Bytes) field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(name) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: field.Offset, Length: field.Type.TotalSize()}, register) + f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: byte(field.Offset), Length: byte(field.Type.Size())}, register) return field.Type, nil } diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index 40ecf36..e99165f 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -16,10 +16,7 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro } structName := tokens[i].Text(file.Bytes) - - structure := &types.Struct{ - Name: structName, - } + structure := types.NewStruct(structName) i++ @@ -39,14 +36,12 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro fieldType := types.Parse(fieldTypeName) i++ - structure.Fields = append(structure.Fields, &types.Field{ + structure.AddField(&types.Field{ Type: fieldType, Name: fieldName, Position: token.Position(fieldPosition), - Offset: structure.Size, + Offset: structure.Size(), }) - - structure.Size += fieldType.TotalSize() } if tokens[i].Kind == token.BlockEnd { diff --git a/src/types/Base.go b/src/types/Base.go deleted file mode 100644 index 5f3d27b..0000000 --- a/src/types/Base.go +++ /dev/null @@ -1,16 +0,0 @@ -package types - -var ( - Float64 = &Struct{Name: "Float64", Size: 8} - Float32 = &Struct{Name: "Float32", Size: 4} - Int64 = &Struct{Name: "Int64", Size: 8} - Int32 = &Struct{Name: "Int32", Size: 4} - Int16 = &Struct{Name: "Int16", Size: 2} - Int8 = &Struct{Name: "Int8", Size: 1} - PointerAny = &Pointer{To: nil} -) - -var ( - Float = Float64 - Int = Int64 -) diff --git a/src/types/Field.go b/src/types/Field.go index 6e155ff..e486878 100644 --- a/src/types/Field.go +++ b/src/types/Field.go @@ -2,10 +2,10 @@ package types import "git.akyoto.dev/cli/q/src/token" -// Field is a field in a data structure. +// Field is a memory region in a data structure. type Field struct { Type Type Name string Position token.Position - Offset uint8 + Offset int } diff --git a/src/types/Float.go b/src/types/Float.go new file mode 100644 index 0000000..1fedd30 --- /dev/null +++ b/src/types/Float.go @@ -0,0 +1,7 @@ +package types + +var ( + Float64 = &Struct{name: "Float64", size: 8} + Float32 = &Struct{name: "Float32", size: 4} + Float = Float64 +) diff --git a/src/types/Int.go b/src/types/Int.go new file mode 100644 index 0000000..73fa22c --- /dev/null +++ b/src/types/Int.go @@ -0,0 +1,9 @@ +package types + +var ( + Int64 = &Struct{name: "Int64", size: 8} + Int32 = &Struct{name: "Int32", size: 4} + Int16 = &Struct{name: "Int16", size: 2} + Int8 = &Struct{name: "Int8", size: 1} + Int = Int64 +) diff --git a/src/types/Check.go b/src/types/Is.go similarity index 68% rename from src/types/Check.go rename to src/types/Is.go index fe1eb20..9adbac5 100644 --- a/src/types/Check.go +++ b/src/types/Is.go @@ -1,7 +1,7 @@ package types -// Check returns true if the encountered type `a` can be converted into the expected type `b`. -func Check(a Type, b Type) bool { +// Is returns true if the encountered type `a` can be converted into the expected type `b`. +func Is(a Type, b Type) bool { if a == nil { return true } diff --git a/src/types/Pointer.go b/src/types/Pointer.go index ccc92e7..06a712e 100644 --- a/src/types/Pointer.go +++ b/src/types/Pointer.go @@ -1,17 +1,22 @@ package types +var PointerAny = &Pointer{To: nil} + +// Pointer is the address of an object. type Pointer struct { To Type } -func (p *Pointer) UniqueName() string { +// Name returns the type name. +func (p *Pointer) Name() string { if p.To == nil { return "Pointer" } - return "Pointer:" + p.To.UniqueName() + return "Pointer:" + p.To.Name() } -func (p *Pointer) TotalSize() uint8 { +// Size returns the total size in bytes. +func (p *Pointer) Size() int { return 8 } diff --git a/src/types/Struct.go b/src/types/Struct.go index d24bdb5..1e80517 100644 --- a/src/types/Struct.go +++ b/src/types/Struct.go @@ -1,22 +1,26 @@ package types -// Struct is a structure in memory whose regions are addressable with fields. +// Struct is a structure in memory whose regions are addressable with named fields. type Struct struct { - Name string - Fields []*Field - Size uint8 + name string + fields []*Field + size int } -func (s *Struct) UniqueName() string { - return s.Name +// NewStruct creates a new struct. +func NewStruct(name string) *Struct { + return &Struct{name: name} } -func (s *Struct) TotalSize() uint8 { - return s.Size +// AddField adds a new field to the end of the struct. +func (s *Struct) AddField(field *Field) { + s.fields = append(s.fields, field) + s.size += field.Type.Size() } +// FieldByName returns the field with the given name if it exists. func (s *Struct) FieldByName(name string) *Field { - for _, field := range s.Fields { + for _, field := range s.fields { if field.Name == name { return field } @@ -24,3 +28,13 @@ func (s *Struct) FieldByName(name string) *Field { return nil } + +// Name returns the name of the struct. +func (s *Struct) Name() string { + return s.name +} + +// Size returns the total size in bytes. +func (s *Struct) Size() int { + return s.size +} diff --git a/src/types/Type.go b/src/types/Type.go index d548867..ef55b60 100644 --- a/src/types/Type.go +++ b/src/types/Type.go @@ -1,6 +1,7 @@ package types +// Type is the generic interface for different data types. type Type interface { - UniqueName() string - TotalSize() uint8 + Name() string + Size() int } From 8421a21c9ad280133ccb1039b1d00dba7a796714 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 4 Feb 2025 23:58:01 +0100 Subject: [PATCH 0640/1012] Simplified type system --- src/compiler/Compile.go | 2 +- src/core/CompileAssignField.go | 6 +++--- src/core/CompileCall.go | 6 +++--- src/core/CompileDelete.go | 2 +- src/core/CompileNew.go | 2 +- src/core/CompileReturn.go | 6 +++--- src/core/ExpressionToRegister.go | 2 +- src/scanner/scanStruct.go | 11 +++-------- src/types/Base.go | 16 ---------------- src/types/Field.go | 4 ++-- src/types/Float.go | 7 +++++++ src/types/Int.go | 9 +++++++++ src/types/{Check.go => Is.go} | 4 ++-- src/types/Pointer.go | 11 ++++++++--- src/types/Struct.go | 32 +++++++++++++++++++++++--------- src/types/Type.go | 5 +++-- 16 files changed, 70 insertions(+), 55 deletions(-) delete mode 100644 src/types/Base.go create mode 100644 src/types/Float.go create mode 100644 src/types/Int.go rename src/types/{Check.go => Is.go} (68%) diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 28ffb2e..23960a1 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -34,7 +34,7 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c continue } - allTypes[typ.UniqueName()] = typ + allTypes[typ.Name()] = typ case file, ok := <-files: if !ok { diff --git a/src/core/CompileAssignField.go b/src/core/CompileAssignField.go index 26d462f..852ae2d 100644 --- a/src/core/CompileAssignField.go +++ b/src/core/CompileAssignField.go @@ -27,14 +27,14 @@ func (f *Function) CompileAssignField(node *ast.Assign) error { field := structure.FieldByName(fieldName) if field == nil { - return errors.New(&errors.UnknownStructField{StructName: structure.Name, FieldName: fieldName}, f.File, destination.Children[1].Token.Position) + return errors.New(&errors.UnknownStructField{StructName: structure.Name(), FieldName: fieldName}, f.File, destination.Children[1].Token.Position) } memory := asm.Memory{ Base: variable.Register, - Offset: field.Offset, + Offset: byte(field.Offset), OffsetRegister: math.MaxUint8, - Length: field.Type.TotalSize(), + Length: byte(field.Type.Size()), } _, err := f.ExpressionToMemory(value, memory) diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index f476d94..d65baec 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -92,10 +92,10 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { return nil, err } - if !types.Check(typ, fn.Parameters[i].Type) { + if !types.Is(typ, fn.Parameters[i].Type) { return nil, errors.New(&errors.TypeMismatch{ - Encountered: typ.UniqueName(), - Expected: fn.Parameters[i].Type.UniqueName(), + Encountered: typ.Name(), + Expected: fn.Parameters[i].Type.Name(), ParameterName: fn.Parameters[i].Name, }, f.File, parameters[i].Token.Position) } diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index 1e1b475..a8f46bd 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -15,7 +15,7 @@ func (f *Function) CompileDelete(root *expression.Expression) error { f.SaveRegister(f.CPU.Input[0]) f.SaveRegister(f.CPU.Input[1]) f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Register) - f.RegisterNumber(asm.MOVE, f.CPU.Input[1], int(variable.Type.(*types.Pointer).To.TotalSize())) + f.RegisterNumber(asm.MOVE, f.CPU.Input[1], int(variable.Type.(*types.Pointer).To.Size())) for _, register := range f.CPU.General { if f.RegisterIsUsed(register) { diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index 04c6eb1..cdbf76d 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -11,7 +11,7 @@ func (f *Function) CompileNew(root *expression.Expression) error { structName := parameters[0].Token.Text(f.File.Bytes) typ := f.Types[structName] f.SaveRegister(f.CPU.Input[0]) - f.RegisterNumber(asm.MOVE, f.CPU.Input[0], int(typ.TotalSize())) + f.RegisterNumber(asm.MOVE, f.CPU.Input[0], typ.Size()) for _, register := range f.CPU.General { if f.RegisterIsUsed(register) { diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 023da02..9b84b58 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -25,10 +25,10 @@ func (f *Function) CompileReturn(node *ast.Return) error { return err } - if !types.Check(typ, f.ReturnTypes[i]) { + if !types.Is(typ, f.ReturnTypes[i]) { return errors.New(&errors.TypeMismatch{ - Encountered: typ.UniqueName(), - Expected: f.ReturnTypes[i].UniqueName(), + Encountered: typ.Name(), + Expected: f.ReturnTypes[i].Name(), ParameterName: "", IsReturn: true, }, f.File, node.Values[i].Token.Position) diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 684ad38..aa6ad9b 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -51,7 +51,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp right := node.Children[1] name = right.Token.Text(f.File.Bytes) field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(name) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: field.Offset, Length: field.Type.TotalSize()}, register) + f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: byte(field.Offset), Length: byte(field.Type.Size())}, register) return field.Type, nil } diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index 40ecf36..e99165f 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -16,10 +16,7 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro } structName := tokens[i].Text(file.Bytes) - - structure := &types.Struct{ - Name: structName, - } + structure := types.NewStruct(structName) i++ @@ -39,14 +36,12 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro fieldType := types.Parse(fieldTypeName) i++ - structure.Fields = append(structure.Fields, &types.Field{ + structure.AddField(&types.Field{ Type: fieldType, Name: fieldName, Position: token.Position(fieldPosition), - Offset: structure.Size, + Offset: structure.Size(), }) - - structure.Size += fieldType.TotalSize() } if tokens[i].Kind == token.BlockEnd { diff --git a/src/types/Base.go b/src/types/Base.go deleted file mode 100644 index 5f3d27b..0000000 --- a/src/types/Base.go +++ /dev/null @@ -1,16 +0,0 @@ -package types - -var ( - Float64 = &Struct{Name: "Float64", Size: 8} - Float32 = &Struct{Name: "Float32", Size: 4} - Int64 = &Struct{Name: "Int64", Size: 8} - Int32 = &Struct{Name: "Int32", Size: 4} - Int16 = &Struct{Name: "Int16", Size: 2} - Int8 = &Struct{Name: "Int8", Size: 1} - PointerAny = &Pointer{To: nil} -) - -var ( - Float = Float64 - Int = Int64 -) diff --git a/src/types/Field.go b/src/types/Field.go index 6e155ff..e486878 100644 --- a/src/types/Field.go +++ b/src/types/Field.go @@ -2,10 +2,10 @@ package types import "git.akyoto.dev/cli/q/src/token" -// Field is a field in a data structure. +// Field is a memory region in a data structure. type Field struct { Type Type Name string Position token.Position - Offset uint8 + Offset int } diff --git a/src/types/Float.go b/src/types/Float.go new file mode 100644 index 0000000..1fedd30 --- /dev/null +++ b/src/types/Float.go @@ -0,0 +1,7 @@ +package types + +var ( + Float64 = &Struct{name: "Float64", size: 8} + Float32 = &Struct{name: "Float32", size: 4} + Float = Float64 +) diff --git a/src/types/Int.go b/src/types/Int.go new file mode 100644 index 0000000..73fa22c --- /dev/null +++ b/src/types/Int.go @@ -0,0 +1,9 @@ +package types + +var ( + Int64 = &Struct{name: "Int64", size: 8} + Int32 = &Struct{name: "Int32", size: 4} + Int16 = &Struct{name: "Int16", size: 2} + Int8 = &Struct{name: "Int8", size: 1} + Int = Int64 +) diff --git a/src/types/Check.go b/src/types/Is.go similarity index 68% rename from src/types/Check.go rename to src/types/Is.go index fe1eb20..9adbac5 100644 --- a/src/types/Check.go +++ b/src/types/Is.go @@ -1,7 +1,7 @@ package types -// Check returns true if the encountered type `a` can be converted into the expected type `b`. -func Check(a Type, b Type) bool { +// Is returns true if the encountered type `a` can be converted into the expected type `b`. +func Is(a Type, b Type) bool { if a == nil { return true } diff --git a/src/types/Pointer.go b/src/types/Pointer.go index ccc92e7..06a712e 100644 --- a/src/types/Pointer.go +++ b/src/types/Pointer.go @@ -1,17 +1,22 @@ package types +var PointerAny = &Pointer{To: nil} + +// Pointer is the address of an object. type Pointer struct { To Type } -func (p *Pointer) UniqueName() string { +// Name returns the type name. +func (p *Pointer) Name() string { if p.To == nil { return "Pointer" } - return "Pointer:" + p.To.UniqueName() + return "Pointer:" + p.To.Name() } -func (p *Pointer) TotalSize() uint8 { +// Size returns the total size in bytes. +func (p *Pointer) Size() int { return 8 } diff --git a/src/types/Struct.go b/src/types/Struct.go index d24bdb5..1e80517 100644 --- a/src/types/Struct.go +++ b/src/types/Struct.go @@ -1,22 +1,26 @@ package types -// Struct is a structure in memory whose regions are addressable with fields. +// Struct is a structure in memory whose regions are addressable with named fields. type Struct struct { - Name string - Fields []*Field - Size uint8 + name string + fields []*Field + size int } -func (s *Struct) UniqueName() string { - return s.Name +// NewStruct creates a new struct. +func NewStruct(name string) *Struct { + return &Struct{name: name} } -func (s *Struct) TotalSize() uint8 { - return s.Size +// AddField adds a new field to the end of the struct. +func (s *Struct) AddField(field *Field) { + s.fields = append(s.fields, field) + s.size += field.Type.Size() } +// FieldByName returns the field with the given name if it exists. func (s *Struct) FieldByName(name string) *Field { - for _, field := range s.Fields { + for _, field := range s.fields { if field.Name == name { return field } @@ -24,3 +28,13 @@ func (s *Struct) FieldByName(name string) *Field { return nil } + +// Name returns the name of the struct. +func (s *Struct) Name() string { + return s.name +} + +// Size returns the total size in bytes. +func (s *Struct) Size() int { + return s.size +} diff --git a/src/types/Type.go b/src/types/Type.go index d548867..ef55b60 100644 --- a/src/types/Type.go +++ b/src/types/Type.go @@ -1,6 +1,7 @@ package types +// Type is the generic interface for different data types. type Type interface { - UniqueName() string - TotalSize() uint8 + Name() string + Size() int } From f7bc903aa4c18dcdff1aefbae2f42abd613b88bd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 5 Feb 2025 11:11:15 +0100 Subject: [PATCH 0641/1012] Added more tests --- src/errors/Common.go | 1 + src/scanner/scanFunction.go | 6 +++++- src/token/List.go | 12 ++++++------ tests/errors/MissingParameter.q | 1 + tests/errors/MissingParameter2.q | 1 + tests/errors/MissingParameter3.q | 1 + tests/errors/MissingType.q | 1 + tests/errors_test.go | 4 ++++ 8 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 tests/errors/MissingParameter.q create mode 100644 tests/errors/MissingParameter2.q create mode 100644 tests/errors/MissingParameter3.q create mode 100644 tests/errors/MissingType.q diff --git a/src/errors/Common.go b/src/errors/Common.go index ab301d2..d395bdd 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -18,6 +18,7 @@ var ( MissingGroupEnd = &Base{"Missing ')'"} MissingMainFunction = &Base{"Missing main function"} MissingOperand = &Base{"Missing operand"} + MissingParameter = &Base{"Missing parameter"} MissingType = &Base{"Missing type"} NotImplemented = &Base{"Not implemented"} UnknownType = &Base{"Unknown type"} diff --git a/src/scanner/scanFunction.go b/src/scanner/scanFunction.go index f66c1bb..30cda18 100644 --- a/src/scanner/scanFunction.go +++ b/src/scanner/scanFunction.go @@ -164,7 +164,11 @@ func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, er count := 0 err := parameters.Split(func(tokens token.List) error { - if len(tokens) < 2 { + if len(tokens) == 0 { + return errors.New(errors.MissingParameter, file, parameters[0].Position) + } + + if len(tokens) == 1 { return errors.New(errors.MissingType, file, tokens[0].End()) } diff --git a/src/token/List.go b/src/token/List.go index c66dc27..218dcec 100644 --- a/src/token/List.go +++ b/src/token/List.go @@ -31,6 +31,10 @@ func (list List) LastIndexKind(kind Kind) int { // Split calls the callback function on each set of tokens in a comma separated list. func (list List) Split(call func(List) error) error { + if len(list) == 0 { + return nil + } + start := 0 groupLevel := 0 @@ -58,12 +62,8 @@ func (list List) Split(call func(List) error) error { } } - if start != len(list) { - parameter := list[start:] - return call(parameter) - } - - return nil + parameter := list[start:] + return call(parameter) } // Text returns the concatenated token text. diff --git a/tests/errors/MissingParameter.q b/tests/errors/MissingParameter.q new file mode 100644 index 0000000..a3247e3 --- /dev/null +++ b/tests/errors/MissingParameter.q @@ -0,0 +1 @@ +f(,) {} \ No newline at end of file diff --git a/tests/errors/MissingParameter2.q b/tests/errors/MissingParameter2.q new file mode 100644 index 0000000..fd8af14 --- /dev/null +++ b/tests/errors/MissingParameter2.q @@ -0,0 +1 @@ +f(a Int,) -> Int { return a } \ No newline at end of file diff --git a/tests/errors/MissingParameter3.q b/tests/errors/MissingParameter3.q new file mode 100644 index 0000000..6b4650b --- /dev/null +++ b/tests/errors/MissingParameter3.q @@ -0,0 +1 @@ +f(,a Int) {} \ No newline at end of file diff --git a/tests/errors/MissingType.q b/tests/errors/MissingType.q new file mode 100644 index 0000000..214c35e --- /dev/null +++ b/tests/errors/MissingType.q @@ -0,0 +1 @@ +f(a) {} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 7afb488..20178a0 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -40,6 +40,10 @@ var errs = []struct { {"MissingMainFunction.q", errors.MissingMainFunction}, {"MissingOperand.q", errors.MissingOperand}, {"MissingOperand2.q", errors.MissingOperand}, + {"MissingParameter.q", errors.MissingParameter}, + {"MissingParameter2.q", errors.MissingParameter}, + {"MissingParameter3.q", errors.MissingParameter}, + {"MissingType.q", errors.MissingType}, {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, {"TypeMismatch.q", &errors.TypeMismatch{Expected: "Pointer", Encountered: "Int64", ParameterName: "p"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, From 85568949a2488451b30a4fc64d69916b5463afe9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 5 Feb 2025 11:11:15 +0100 Subject: [PATCH 0642/1012] Added more tests --- src/errors/Common.go | 1 + src/scanner/scanFunction.go | 6 +++++- src/token/List.go | 12 ++++++------ tests/errors/MissingParameter.q | 1 + tests/errors/MissingParameter2.q | 1 + tests/errors/MissingParameter3.q | 1 + tests/errors/MissingType.q | 1 + tests/errors_test.go | 4 ++++ 8 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 tests/errors/MissingParameter.q create mode 100644 tests/errors/MissingParameter2.q create mode 100644 tests/errors/MissingParameter3.q create mode 100644 tests/errors/MissingType.q diff --git a/src/errors/Common.go b/src/errors/Common.go index ab301d2..d395bdd 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -18,6 +18,7 @@ var ( MissingGroupEnd = &Base{"Missing ')'"} MissingMainFunction = &Base{"Missing main function"} MissingOperand = &Base{"Missing operand"} + MissingParameter = &Base{"Missing parameter"} MissingType = &Base{"Missing type"} NotImplemented = &Base{"Not implemented"} UnknownType = &Base{"Unknown type"} diff --git a/src/scanner/scanFunction.go b/src/scanner/scanFunction.go index f66c1bb..30cda18 100644 --- a/src/scanner/scanFunction.go +++ b/src/scanner/scanFunction.go @@ -164,7 +164,11 @@ func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, er count := 0 err := parameters.Split(func(tokens token.List) error { - if len(tokens) < 2 { + if len(tokens) == 0 { + return errors.New(errors.MissingParameter, file, parameters[0].Position) + } + + if len(tokens) == 1 { return errors.New(errors.MissingType, file, tokens[0].End()) } diff --git a/src/token/List.go b/src/token/List.go index c66dc27..218dcec 100644 --- a/src/token/List.go +++ b/src/token/List.go @@ -31,6 +31,10 @@ func (list List) LastIndexKind(kind Kind) int { // Split calls the callback function on each set of tokens in a comma separated list. func (list List) Split(call func(List) error) error { + if len(list) == 0 { + return nil + } + start := 0 groupLevel := 0 @@ -58,12 +62,8 @@ func (list List) Split(call func(List) error) error { } } - if start != len(list) { - parameter := list[start:] - return call(parameter) - } - - return nil + parameter := list[start:] + return call(parameter) } // Text returns the concatenated token text. diff --git a/tests/errors/MissingParameter.q b/tests/errors/MissingParameter.q new file mode 100644 index 0000000..a3247e3 --- /dev/null +++ b/tests/errors/MissingParameter.q @@ -0,0 +1 @@ +f(,) {} \ No newline at end of file diff --git a/tests/errors/MissingParameter2.q b/tests/errors/MissingParameter2.q new file mode 100644 index 0000000..fd8af14 --- /dev/null +++ b/tests/errors/MissingParameter2.q @@ -0,0 +1 @@ +f(a Int,) -> Int { return a } \ No newline at end of file diff --git a/tests/errors/MissingParameter3.q b/tests/errors/MissingParameter3.q new file mode 100644 index 0000000..6b4650b --- /dev/null +++ b/tests/errors/MissingParameter3.q @@ -0,0 +1 @@ +f(,a Int) {} \ No newline at end of file diff --git a/tests/errors/MissingType.q b/tests/errors/MissingType.q new file mode 100644 index 0000000..214c35e --- /dev/null +++ b/tests/errors/MissingType.q @@ -0,0 +1 @@ +f(a) {} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 7afb488..20178a0 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -40,6 +40,10 @@ var errs = []struct { {"MissingMainFunction.q", errors.MissingMainFunction}, {"MissingOperand.q", errors.MissingOperand}, {"MissingOperand2.q", errors.MissingOperand}, + {"MissingParameter.q", errors.MissingParameter}, + {"MissingParameter2.q", errors.MissingParameter}, + {"MissingParameter3.q", errors.MissingParameter}, + {"MissingType.q", errors.MissingType}, {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, {"TypeMismatch.q", &errors.TypeMismatch{Expected: "Pointer", Encountered: "Int64", ParameterName: "p"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, From f76f0a1e4b1f4ebc9f2b22cad7ebd511dc0a5a0d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 5 Feb 2025 15:16:00 +0100 Subject: [PATCH 0643/1012] Implemented struct pointer types --- examples/point/point.q | 13 ++++++--- src/compiler/Compile.go | 22 +++++++++++++++- src/core/CompileCall.go | 16 +++++++----- src/core/CompileDefinition.go | 4 +-- src/core/CompileReturn.go | 8 +++--- src/core/ExpressionToRegister.go | 4 +-- src/core/Function.go | 27 +++++++++---------- src/core/Input.go | 16 ++++++++++++ src/core/Output.go | 15 +++++++++++ src/core/ResolveTypes.go | 45 ++++++++++++++++++++++++++++++++ src/core/TypeByName.go | 17 ++++++++++++ src/errors/Common.go | 2 +- src/errors/UnknownType.go | 18 +++++++++++++ src/scanner/scanFunction.go | 35 ++++++++----------------- src/scanner/scanStruct.go | 7 ++++- src/types/Parse.go | 27 ------------------- src/types/ParseList.go | 16 ------------ 17 files changed, 190 insertions(+), 102 deletions(-) create mode 100644 src/core/Input.go create mode 100644 src/core/Output.go create mode 100644 src/core/ResolveTypes.go create mode 100644 src/core/TypeByName.go create mode 100644 src/errors/UnknownType.go delete mode 100644 src/types/Parse.go delete mode 100644 src/types/ParseList.go diff --git a/examples/point/point.q b/examples/point/point.q index 2898895..bc21184 100644 --- a/examples/point/point.q +++ b/examples/point/point.q @@ -7,10 +7,19 @@ struct Point { } main() { + p := construct() + print(p) + delete(p) +} + +construct() -> *Point { p := new(Point) p.x = 1 p.y = 2 + return p +} +print(p *Point) { out := mem.alloc(8) out[0] = 'x' out[1] = ' ' @@ -21,7 +30,5 @@ main() { out[6] = '0' + p.y out[7] = '\n' sys.write(1, out, 8) - mem.free(out) - - delete(p) + mem.free(out, 8) } \ No newline at end of file diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 23960a1..2fdea38 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -14,7 +14,18 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c result := Result{} allFiles := make([]*fs.File, 0, 8) allFunctions := map[string]*core.Function{} - allTypes := map[string]types.Type{} + + allTypes := map[string]types.Type{ + "Int": types.Int, + "Int64": types.Int64, + "Int32": types.Int32, + "Int16": types.Int16, + "Int8": types.Int8, + "Float": types.Float, + "Float64": types.Float64, + "Float32": types.Float32, + "Pointer": types.PointerAny, + } for functions != nil || files != nil || errs != nil { select { @@ -54,6 +65,15 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c } } + // Resolve the types + for _, function := range allFunctions { + err := function.ResolveTypes() + + if err != nil { + return result, err + } + } + // Start parallel compilation CompileFunctions(allFunctions) diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index d65baec..2b3f4fa 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -31,9 +31,11 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { return nil, f.CompileSyscall(root) case "new": return &Function{ - ReturnTypes: []types.Type{ - &types.Pointer{ - To: f.Types[root.Children[1].Token.Text(f.File.Bytes)], + Output: []*Output{ + { + Type: &types.Pointer{ + To: f.Types[root.Children[1].Token.Text(f.File.Bytes)], + }, }, }, }, f.CompileNew(root) @@ -92,16 +94,16 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { return nil, err } - if !types.Is(typ, fn.Parameters[i].Type) { + if !types.Is(typ, fn.Input[i].Type) { return nil, errors.New(&errors.TypeMismatch{ Encountered: typ.Name(), - Expected: fn.Parameters[i].Type.Name(), - ParameterName: fn.Parameters[i].Name, + Expected: fn.Input[i].Type.Name(), + ParameterName: fn.Input[i].Name, }, f.File, parameters[i].Token.Position) } } - for _, register := range f.CPU.Output[:len(fn.ReturnTypes)] { + for _, register := range f.CPU.Output[:len(fn.Output)] { f.SaveRegister(register) } diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index 5782978..b50b3dd 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -28,7 +28,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { variable.Type = typ if variable.Type == nil { - return errors.New(errors.UnknownType, f.File, node.Expression.Token.End()) + return errors.New(errors.CouldNotInferType, f.File, node.Expression.Token.End()) } f.AddVariable(variable) @@ -54,7 +54,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { } if called != nil { - variable.Type = called.ReturnTypes[count] + variable.Type = called.Output[count].Type } f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 9b84b58..16180a8 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -14,8 +14,8 @@ func (f *Function) CompileReturn(node *ast.Return) error { return nil } - if len(node.Values) != len(f.ReturnTypes) { - return errors.New(&errors.ReturnCountMismatch{Count: len(node.Values), ExpectedCount: len(f.ReturnTypes)}, f.File, node.Values[0].Token.Position) + if len(node.Values) != len(f.Output) { + return errors.New(&errors.ReturnCountMismatch{Count: len(node.Values), ExpectedCount: len(f.Output)}, f.File, node.Values[0].Token.Position) } for i := len(node.Values) - 1; i >= 0; i-- { @@ -25,10 +25,10 @@ func (f *Function) CompileReturn(node *ast.Return) error { return err } - if !types.Is(typ, f.ReturnTypes[i]) { + if !types.Is(typ, f.Output[i].Type) { return errors.New(&errors.TypeMismatch{ Encountered: typ.Name(), - Expected: f.ReturnTypes[i].Name(), + Expected: f.Output[i].Type.Name(), ParameterName: "", IsReturn: true, }, f.File, node.Values[i].Token.Position) diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index aa6ad9b..17f3feb 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -30,11 +30,11 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) } - if fn == nil || len(fn.ReturnTypes) == 0 { + if fn == nil || len(fn.Output) == 0 { return nil, err } - return fn.ReturnTypes[0], err + return fn.Output[0].Type, err } if node.Token.Kind == token.Array { diff --git a/src/core/Function.go b/src/core/Function.go index 966b00c..7aeb189 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -4,7 +4,6 @@ import ( "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/register" - "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/types" ) @@ -12,19 +11,19 @@ import ( // Function represents the smallest unit of code. type Function struct { register.Machine - Package string - Name string - UniqueName string - File *fs.File - Body token.List - Parameters []*scope.Variable - ReturnTypes []types.Type - Functions map[string]*Function - Types map[string]types.Type - DLLs dll.List - Err error - deferred []func() - count counter + Package string + Name string + UniqueName string + File *fs.File + Body token.List + Input []*Input + Output []*Output + Functions map[string]*Function + Types map[string]types.Type + DLLs dll.List + Err error + deferred []func() + count counter } // counter stores how often a certain statement appeared so we can generate a unique label from it. diff --git a/src/core/Input.go b/src/core/Input.go new file mode 100644 index 0000000..2b9aee2 --- /dev/null +++ b/src/core/Input.go @@ -0,0 +1,16 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" +) + +type Input struct { + Name string + Type types.Type + tokens token.List +} + +func NewInput(tokens token.List) *Input { + return &Input{tokens: tokens} +} diff --git a/src/core/Output.go b/src/core/Output.go new file mode 100644 index 0000000..3b1b434 --- /dev/null +++ b/src/core/Output.go @@ -0,0 +1,15 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" +) + +type Output struct { + Type types.Type + tokens token.List +} + +func NewOutput(tokens token.List) *Output { + return &Output{tokens: tokens} +} diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go new file mode 100644 index 0000000..c508073 --- /dev/null +++ b/src/core/ResolveTypes.go @@ -0,0 +1,45 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/scope" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/x64" +) + +// ResolveTypes parses the input and output types. +func (f *Function) ResolveTypes() error { + for i, param := range f.Input { + param.Name = param.tokens[0].Text(f.File.Bytes) + typeName := param.tokens[1:].Text(f.File.Bytes) + param.Type = f.TypeByName(typeName) + + if param.Type == nil { + return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) + } + + uses := token.Count(f.Body, f.File.Bytes, token.Identifier, param.Name) + + if uses == 0 && param.Name != "_" { + return errors.New(&errors.UnusedVariable{Name: param.Name}, f.File, param.tokens[0].Position) + } + + f.AddVariable(&scope.Variable{ + Name: param.Name, + Type: param.Type, + Register: x64.InputRegisters[i], + Alive: uses, + }) + } + + for _, param := range f.Output { + typeName := param.tokens.Text(f.File.Bytes) + param.Type = f.TypeByName(typeName) + + if param.Type == nil { + return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) + } + } + + return nil +} diff --git a/src/core/TypeByName.go b/src/core/TypeByName.go new file mode 100644 index 0000000..fcad711 --- /dev/null +++ b/src/core/TypeByName.go @@ -0,0 +1,17 @@ +package core + +import ( + "strings" + + "git.akyoto.dev/cli/q/src/types" +) + +// TypeByName returns the type with the given name or `nil` if it doesn't exist. +func (f *Function) TypeByName(name string) types.Type { + if strings.HasPrefix(name, "*") { + to := strings.TrimPrefix(name, "*") + return &types.Pointer{To: f.TypeByName(to)} + } + + return f.Types[name] +} diff --git a/src/errors/Common.go b/src/errors/Common.go index d395bdd..107cb3c 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -1,6 +1,7 @@ package errors var ( + CouldNotInferType = &Base{"Couldn't infer type"} EmptySwitch = &Base{"Empty switch"} ExpectedFunctionName = &Base{"Expected function name"} ExpectedFunctionParameters = &Base{"Expected function parameters"} @@ -21,5 +22,4 @@ var ( MissingParameter = &Base{"Missing parameter"} MissingType = &Base{"Missing type"} NotImplemented = &Base{"Not implemented"} - UnknownType = &Base{"Unknown type"} ) diff --git a/src/errors/UnknownType.go b/src/errors/UnknownType.go new file mode 100644 index 0000000..77fc270 --- /dev/null +++ b/src/errors/UnknownType.go @@ -0,0 +1,18 @@ +package errors + +import "fmt" + +// UnknownType represents unknown types. +type UnknownType struct { + Name string + CorrectName string +} + +// Error generates the string representation. +func (err *UnknownType) Error() string { + if err.CorrectName != "" { + return fmt.Sprintf("Unknown type '%s', did you mean '%s'?", err.Name, err.CorrectName) + } + + return fmt.Sprintf("Unknown type '%s'", err.Name) +} diff --git a/src/scanner/scanFunction.go b/src/scanner/scanFunction.go index 30cda18..63fb880 100644 --- a/src/scanner/scanFunction.go +++ b/src/scanner/scanFunction.go @@ -4,10 +4,7 @@ import ( "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/cli/q/src/x64" ) // scanFunction scans a function. @@ -157,11 +154,19 @@ func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, er typeEnd-- } - function.ReturnTypes = types.ParseList(tokens[typeStart:typeEnd], file.Bytes) + outputTokens := tokens[typeStart:typeEnd] + + err := outputTokens.Split(func(tokens token.List) error { + function.Output = append(function.Output, core.NewOutput(tokens)) + return nil + }) + + if err != nil { + return i, err + } } parameters := tokens[paramsStart:paramsEnd] - count := 0 err := parameters.Split(func(tokens token.List) error { if len(tokens) == 0 { @@ -172,25 +177,7 @@ func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, er return errors.New(errors.MissingType, file, tokens[0].End()) } - name := tokens[0].Text(file.Bytes) - dataType := types.Parse(tokens[1:].Text(file.Bytes)) - register := x64.InputRegisters[count] - uses := token.Count(function.Body, file.Bytes, token.Identifier, name) - - if uses == 0 && name != "_" { - return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) - } - - variable := &scope.Variable{ - Name: name, - Type: dataType, - Register: register, - Alive: uses, - } - - function.Parameters = append(function.Parameters, variable) - function.AddVariable(variable) - count++ + function.Input = append(function.Input, core.NewInput(tokens)) return nil }) diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index e99165f..f2319e9 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -33,7 +33,12 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro fieldName := tokens[i].Text(file.Bytes) i++ fieldTypeName := tokens[i].Text(file.Bytes) - fieldType := types.Parse(fieldTypeName) + fieldType := types.Int + + if fieldTypeName != "Int" { + panic("not implemented") + } + i++ structure.AddField(&types.Field{ diff --git a/src/types/Parse.go b/src/types/Parse.go deleted file mode 100644 index 6fcc1f2..0000000 --- a/src/types/Parse.go +++ /dev/null @@ -1,27 +0,0 @@ -package types - -// Parse creates a new type from a list of tokens. -func Parse(name string) Type { - switch name { - case "Int": - return Int - case "Int64": - return Int64 - case "Int32": - return Int32 - case "Int16": - return Int16 - case "Int8": - return Int8 - case "Float": - return Float - case "Float64": - return Float64 - case "Float32": - return Float32 - case "Pointer": - return PointerAny - default: - panic("Unknown type " + name) - } -} diff --git a/src/types/ParseList.go b/src/types/ParseList.go deleted file mode 100644 index 137aec2..0000000 --- a/src/types/ParseList.go +++ /dev/null @@ -1,16 +0,0 @@ -package types - -import "git.akyoto.dev/cli/q/src/token" - -// ParseList generates a list of types from comma separated tokens. -func ParseList(tokens token.List, source []byte) []Type { - var list []Type - - tokens.Split(func(parameter token.List) error { - typ := Parse(parameter.Text(source)) - list = append(list, typ) - return nil - }) - - return list -} From 5d38a4980a1c3f10bbb4a470854fd9d7e4ce64dc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 5 Feb 2025 15:16:00 +0100 Subject: [PATCH 0644/1012] Implemented struct pointer types --- examples/point/point.q | 13 ++++++--- src/compiler/Compile.go | 22 +++++++++++++++- src/core/CompileCall.go | 16 +++++++----- src/core/CompileDefinition.go | 4 +-- src/core/CompileReturn.go | 8 +++--- src/core/ExpressionToRegister.go | 4 +-- src/core/Function.go | 27 +++++++++---------- src/core/Input.go | 16 ++++++++++++ src/core/Output.go | 15 +++++++++++ src/core/ResolveTypes.go | 45 ++++++++++++++++++++++++++++++++ src/core/TypeByName.go | 17 ++++++++++++ src/errors/Common.go | 2 +- src/errors/UnknownType.go | 18 +++++++++++++ src/scanner/scanFunction.go | 35 ++++++++----------------- src/scanner/scanStruct.go | 7 ++++- src/types/Parse.go | 27 ------------------- src/types/ParseList.go | 16 ------------ 17 files changed, 190 insertions(+), 102 deletions(-) create mode 100644 src/core/Input.go create mode 100644 src/core/Output.go create mode 100644 src/core/ResolveTypes.go create mode 100644 src/core/TypeByName.go create mode 100644 src/errors/UnknownType.go delete mode 100644 src/types/Parse.go delete mode 100644 src/types/ParseList.go diff --git a/examples/point/point.q b/examples/point/point.q index 2898895..bc21184 100644 --- a/examples/point/point.q +++ b/examples/point/point.q @@ -7,10 +7,19 @@ struct Point { } main() { + p := construct() + print(p) + delete(p) +} + +construct() -> *Point { p := new(Point) p.x = 1 p.y = 2 + return p +} +print(p *Point) { out := mem.alloc(8) out[0] = 'x' out[1] = ' ' @@ -21,7 +30,5 @@ main() { out[6] = '0' + p.y out[7] = '\n' sys.write(1, out, 8) - mem.free(out) - - delete(p) + mem.free(out, 8) } \ No newline at end of file diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 23960a1..2fdea38 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -14,7 +14,18 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c result := Result{} allFiles := make([]*fs.File, 0, 8) allFunctions := map[string]*core.Function{} - allTypes := map[string]types.Type{} + + allTypes := map[string]types.Type{ + "Int": types.Int, + "Int64": types.Int64, + "Int32": types.Int32, + "Int16": types.Int16, + "Int8": types.Int8, + "Float": types.Float, + "Float64": types.Float64, + "Float32": types.Float32, + "Pointer": types.PointerAny, + } for functions != nil || files != nil || errs != nil { select { @@ -54,6 +65,15 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c } } + // Resolve the types + for _, function := range allFunctions { + err := function.ResolveTypes() + + if err != nil { + return result, err + } + } + // Start parallel compilation CompileFunctions(allFunctions) diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index d65baec..2b3f4fa 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -31,9 +31,11 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { return nil, f.CompileSyscall(root) case "new": return &Function{ - ReturnTypes: []types.Type{ - &types.Pointer{ - To: f.Types[root.Children[1].Token.Text(f.File.Bytes)], + Output: []*Output{ + { + Type: &types.Pointer{ + To: f.Types[root.Children[1].Token.Text(f.File.Bytes)], + }, }, }, }, f.CompileNew(root) @@ -92,16 +94,16 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { return nil, err } - if !types.Is(typ, fn.Parameters[i].Type) { + if !types.Is(typ, fn.Input[i].Type) { return nil, errors.New(&errors.TypeMismatch{ Encountered: typ.Name(), - Expected: fn.Parameters[i].Type.Name(), - ParameterName: fn.Parameters[i].Name, + Expected: fn.Input[i].Type.Name(), + ParameterName: fn.Input[i].Name, }, f.File, parameters[i].Token.Position) } } - for _, register := range f.CPU.Output[:len(fn.ReturnTypes)] { + for _, register := range f.CPU.Output[:len(fn.Output)] { f.SaveRegister(register) } diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index 5782978..b50b3dd 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -28,7 +28,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { variable.Type = typ if variable.Type == nil { - return errors.New(errors.UnknownType, f.File, node.Expression.Token.End()) + return errors.New(errors.CouldNotInferType, f.File, node.Expression.Token.End()) } f.AddVariable(variable) @@ -54,7 +54,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { } if called != nil { - variable.Type = called.ReturnTypes[count] + variable.Type = called.Output[count].Type } f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 9b84b58..16180a8 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -14,8 +14,8 @@ func (f *Function) CompileReturn(node *ast.Return) error { return nil } - if len(node.Values) != len(f.ReturnTypes) { - return errors.New(&errors.ReturnCountMismatch{Count: len(node.Values), ExpectedCount: len(f.ReturnTypes)}, f.File, node.Values[0].Token.Position) + if len(node.Values) != len(f.Output) { + return errors.New(&errors.ReturnCountMismatch{Count: len(node.Values), ExpectedCount: len(f.Output)}, f.File, node.Values[0].Token.Position) } for i := len(node.Values) - 1; i >= 0; i-- { @@ -25,10 +25,10 @@ func (f *Function) CompileReturn(node *ast.Return) error { return err } - if !types.Is(typ, f.ReturnTypes[i]) { + if !types.Is(typ, f.Output[i].Type) { return errors.New(&errors.TypeMismatch{ Encountered: typ.Name(), - Expected: f.ReturnTypes[i].Name(), + Expected: f.Output[i].Type.Name(), ParameterName: "", IsReturn: true, }, f.File, node.Values[i].Token.Position) diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index aa6ad9b..17f3feb 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -30,11 +30,11 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) } - if fn == nil || len(fn.ReturnTypes) == 0 { + if fn == nil || len(fn.Output) == 0 { return nil, err } - return fn.ReturnTypes[0], err + return fn.Output[0].Type, err } if node.Token.Kind == token.Array { diff --git a/src/core/Function.go b/src/core/Function.go index 966b00c..7aeb189 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -4,7 +4,6 @@ import ( "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/register" - "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/types" ) @@ -12,19 +11,19 @@ import ( // Function represents the smallest unit of code. type Function struct { register.Machine - Package string - Name string - UniqueName string - File *fs.File - Body token.List - Parameters []*scope.Variable - ReturnTypes []types.Type - Functions map[string]*Function - Types map[string]types.Type - DLLs dll.List - Err error - deferred []func() - count counter + Package string + Name string + UniqueName string + File *fs.File + Body token.List + Input []*Input + Output []*Output + Functions map[string]*Function + Types map[string]types.Type + DLLs dll.List + Err error + deferred []func() + count counter } // counter stores how often a certain statement appeared so we can generate a unique label from it. diff --git a/src/core/Input.go b/src/core/Input.go new file mode 100644 index 0000000..2b9aee2 --- /dev/null +++ b/src/core/Input.go @@ -0,0 +1,16 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" +) + +type Input struct { + Name string + Type types.Type + tokens token.List +} + +func NewInput(tokens token.List) *Input { + return &Input{tokens: tokens} +} diff --git a/src/core/Output.go b/src/core/Output.go new file mode 100644 index 0000000..3b1b434 --- /dev/null +++ b/src/core/Output.go @@ -0,0 +1,15 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" +) + +type Output struct { + Type types.Type + tokens token.List +} + +func NewOutput(tokens token.List) *Output { + return &Output{tokens: tokens} +} diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go new file mode 100644 index 0000000..c508073 --- /dev/null +++ b/src/core/ResolveTypes.go @@ -0,0 +1,45 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/scope" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/x64" +) + +// ResolveTypes parses the input and output types. +func (f *Function) ResolveTypes() error { + for i, param := range f.Input { + param.Name = param.tokens[0].Text(f.File.Bytes) + typeName := param.tokens[1:].Text(f.File.Bytes) + param.Type = f.TypeByName(typeName) + + if param.Type == nil { + return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) + } + + uses := token.Count(f.Body, f.File.Bytes, token.Identifier, param.Name) + + if uses == 0 && param.Name != "_" { + return errors.New(&errors.UnusedVariable{Name: param.Name}, f.File, param.tokens[0].Position) + } + + f.AddVariable(&scope.Variable{ + Name: param.Name, + Type: param.Type, + Register: x64.InputRegisters[i], + Alive: uses, + }) + } + + for _, param := range f.Output { + typeName := param.tokens.Text(f.File.Bytes) + param.Type = f.TypeByName(typeName) + + if param.Type == nil { + return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) + } + } + + return nil +} diff --git a/src/core/TypeByName.go b/src/core/TypeByName.go new file mode 100644 index 0000000..fcad711 --- /dev/null +++ b/src/core/TypeByName.go @@ -0,0 +1,17 @@ +package core + +import ( + "strings" + + "git.akyoto.dev/cli/q/src/types" +) + +// TypeByName returns the type with the given name or `nil` if it doesn't exist. +func (f *Function) TypeByName(name string) types.Type { + if strings.HasPrefix(name, "*") { + to := strings.TrimPrefix(name, "*") + return &types.Pointer{To: f.TypeByName(to)} + } + + return f.Types[name] +} diff --git a/src/errors/Common.go b/src/errors/Common.go index d395bdd..107cb3c 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -1,6 +1,7 @@ package errors var ( + CouldNotInferType = &Base{"Couldn't infer type"} EmptySwitch = &Base{"Empty switch"} ExpectedFunctionName = &Base{"Expected function name"} ExpectedFunctionParameters = &Base{"Expected function parameters"} @@ -21,5 +22,4 @@ var ( MissingParameter = &Base{"Missing parameter"} MissingType = &Base{"Missing type"} NotImplemented = &Base{"Not implemented"} - UnknownType = &Base{"Unknown type"} ) diff --git a/src/errors/UnknownType.go b/src/errors/UnknownType.go new file mode 100644 index 0000000..77fc270 --- /dev/null +++ b/src/errors/UnknownType.go @@ -0,0 +1,18 @@ +package errors + +import "fmt" + +// UnknownType represents unknown types. +type UnknownType struct { + Name string + CorrectName string +} + +// Error generates the string representation. +func (err *UnknownType) Error() string { + if err.CorrectName != "" { + return fmt.Sprintf("Unknown type '%s', did you mean '%s'?", err.Name, err.CorrectName) + } + + return fmt.Sprintf("Unknown type '%s'", err.Name) +} diff --git a/src/scanner/scanFunction.go b/src/scanner/scanFunction.go index 30cda18..63fb880 100644 --- a/src/scanner/scanFunction.go +++ b/src/scanner/scanFunction.go @@ -4,10 +4,7 @@ import ( "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/cli/q/src/x64" ) // scanFunction scans a function. @@ -157,11 +154,19 @@ func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, er typeEnd-- } - function.ReturnTypes = types.ParseList(tokens[typeStart:typeEnd], file.Bytes) + outputTokens := tokens[typeStart:typeEnd] + + err := outputTokens.Split(func(tokens token.List) error { + function.Output = append(function.Output, core.NewOutput(tokens)) + return nil + }) + + if err != nil { + return i, err + } } parameters := tokens[paramsStart:paramsEnd] - count := 0 err := parameters.Split(func(tokens token.List) error { if len(tokens) == 0 { @@ -172,25 +177,7 @@ func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, er return errors.New(errors.MissingType, file, tokens[0].End()) } - name := tokens[0].Text(file.Bytes) - dataType := types.Parse(tokens[1:].Text(file.Bytes)) - register := x64.InputRegisters[count] - uses := token.Count(function.Body, file.Bytes, token.Identifier, name) - - if uses == 0 && name != "_" { - return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) - } - - variable := &scope.Variable{ - Name: name, - Type: dataType, - Register: register, - Alive: uses, - } - - function.Parameters = append(function.Parameters, variable) - function.AddVariable(variable) - count++ + function.Input = append(function.Input, core.NewInput(tokens)) return nil }) diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index e99165f..f2319e9 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -33,7 +33,12 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro fieldName := tokens[i].Text(file.Bytes) i++ fieldTypeName := tokens[i].Text(file.Bytes) - fieldType := types.Parse(fieldTypeName) + fieldType := types.Int + + if fieldTypeName != "Int" { + panic("not implemented") + } + i++ structure.AddField(&types.Field{ diff --git a/src/types/Parse.go b/src/types/Parse.go deleted file mode 100644 index 6fcc1f2..0000000 --- a/src/types/Parse.go +++ /dev/null @@ -1,27 +0,0 @@ -package types - -// Parse creates a new type from a list of tokens. -func Parse(name string) Type { - switch name { - case "Int": - return Int - case "Int64": - return Int64 - case "Int32": - return Int32 - case "Int16": - return Int16 - case "Int8": - return Int8 - case "Float": - return Float - case "Float64": - return Float64 - case "Float32": - return Float32 - case "Pointer": - return PointerAny - default: - panic("Unknown type " + name) - } -} diff --git a/src/types/ParseList.go b/src/types/ParseList.go deleted file mode 100644 index 137aec2..0000000 --- a/src/types/ParseList.go +++ /dev/null @@ -1,16 +0,0 @@ -package types - -import "git.akyoto.dev/cli/q/src/token" - -// ParseList generates a list of types from comma separated tokens. -func ParseList(tokens token.List, source []byte) []Type { - var list []Type - - tokens.Split(func(parameter token.List) error { - typ := Parse(parameter.Text(source)) - list = append(list, typ) - return nil - }) - - return list -} From 64bd2bae59f40d07ca5e179c1273543a9285999f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 5 Feb 2025 22:49:39 +0100 Subject: [PATCH 0645/1012] Fixed register allocation on function calls --- examples/point/point.q | 8 ++++---- src/core/CallSafe.go | 33 +++++++++++++++++++++++++++++++++ src/core/CompileCall.go | 30 +----------------------------- src/core/CompileDelete.go | 18 +----------------- src/core/CompileNew.go | 18 +----------------- 5 files changed, 40 insertions(+), 67 deletions(-) create mode 100644 src/core/CallSafe.go diff --git a/examples/point/point.q b/examples/point/point.q index bc21184..ccb57c9 100644 --- a/examples/point/point.q +++ b/examples/point/point.q @@ -7,15 +7,15 @@ struct Point { } main() { - p := construct() + p := construct(1, 2) print(p) delete(p) } -construct() -> *Point { +construct(x Int, y Int) -> *Point { p := new(Point) - p.x = 1 - p.y = 2 + p.x = x + p.y = y return p } diff --git a/src/core/CallSafe.go b/src/core/CallSafe.go new file mode 100644 index 0000000..f6756b4 --- /dev/null +++ b/src/core/CallSafe.go @@ -0,0 +1,33 @@ +package core + +import ( + "slices" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" +) + +// CallSafe pushes used registers to the stack, executes the call and restores the original register value. +func (f *Function) CallSafe(fn *Function, registers []cpu.Register) { + for _, register := range f.CPU.Output { + f.SaveRegister(register) + } + + for _, register := range f.CPU.General { + if f.RegisterIsUsed(register) { + f.Register(asm.PUSH, register) + } + } + + f.Call(fn.UniqueName) + + for _, register := range slices.Backward(f.CPU.General) { + if f.RegisterIsUsed(register) { + f.Register(asm.POP, register) + } + } + + for _, register := range registers { + f.FreeRegister(register) + } +} diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 2b3f4fa..c88ec87 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -3,7 +3,6 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/types" @@ -103,33 +102,6 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { } } - for _, register := range f.CPU.Output[:len(fn.Output)] { - f.SaveRegister(register) - } - - for _, register := range f.CPU.General { - if f.RegisterIsUsed(register) { - f.Register(asm.PUSH, register) - } - } - - f.Call(fn.UniqueName) - - for _, register := range registers { - if register == f.CPU.Output[0] && root.Parent != nil { - continue - } - - f.FreeRegister(register) - } - - for i := len(f.CPU.General) - 1; i >= 0; i-- { - register := f.CPU.General[i] - - if f.RegisterIsUsed(register) { - f.Register(asm.POP, register) - } - } - + f.CallSafe(fn, registers) return fn, nil } diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index a8f46bd..3d144f8 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -16,22 +16,6 @@ func (f *Function) CompileDelete(root *expression.Expression) error { f.SaveRegister(f.CPU.Input[1]) f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Register) f.RegisterNumber(asm.MOVE, f.CPU.Input[1], int(variable.Type.(*types.Pointer).To.Size())) - - for _, register := range f.CPU.General { - if f.RegisterIsUsed(register) { - f.Register(asm.PUSH, register) - } - } - - f.Call("mem.free") - - for i := len(f.CPU.General) - 1; i >= 0; i-- { - register := f.CPU.General[i] - - if f.RegisterIsUsed(register) { - f.Register(asm.POP, register) - } - } - + f.CallSafe(f.Functions["mem.free"], f.CPU.Input[:2]) return nil } diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index cdbf76d..d25818b 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -12,22 +12,6 @@ func (f *Function) CompileNew(root *expression.Expression) error { typ := f.Types[structName] f.SaveRegister(f.CPU.Input[0]) f.RegisterNumber(asm.MOVE, f.CPU.Input[0], typ.Size()) - - for _, register := range f.CPU.General { - if f.RegisterIsUsed(register) { - f.Register(asm.PUSH, register) - } - } - - f.Call("mem.alloc") - - for i := len(f.CPU.General) - 1; i >= 0; i-- { - register := f.CPU.General[i] - - if f.RegisterIsUsed(register) { - f.Register(asm.POP, register) - } - } - + f.CallSafe(f.Functions["mem.alloc"], f.CPU.Input[:1]) return nil } From 12894dbcc57288b7a01341ada3adbdf70edcc04d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 5 Feb 2025 22:49:39 +0100 Subject: [PATCH 0646/1012] Fixed register allocation on function calls --- examples/point/point.q | 8 ++++---- src/core/CallSafe.go | 33 +++++++++++++++++++++++++++++++++ src/core/CompileCall.go | 30 +----------------------------- src/core/CompileDelete.go | 18 +----------------- src/core/CompileNew.go | 18 +----------------- 5 files changed, 40 insertions(+), 67 deletions(-) create mode 100644 src/core/CallSafe.go diff --git a/examples/point/point.q b/examples/point/point.q index bc21184..ccb57c9 100644 --- a/examples/point/point.q +++ b/examples/point/point.q @@ -7,15 +7,15 @@ struct Point { } main() { - p := construct() + p := construct(1, 2) print(p) delete(p) } -construct() -> *Point { +construct(x Int, y Int) -> *Point { p := new(Point) - p.x = 1 - p.y = 2 + p.x = x + p.y = y return p } diff --git a/src/core/CallSafe.go b/src/core/CallSafe.go new file mode 100644 index 0000000..f6756b4 --- /dev/null +++ b/src/core/CallSafe.go @@ -0,0 +1,33 @@ +package core + +import ( + "slices" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" +) + +// CallSafe pushes used registers to the stack, executes the call and restores the original register value. +func (f *Function) CallSafe(fn *Function, registers []cpu.Register) { + for _, register := range f.CPU.Output { + f.SaveRegister(register) + } + + for _, register := range f.CPU.General { + if f.RegisterIsUsed(register) { + f.Register(asm.PUSH, register) + } + } + + f.Call(fn.UniqueName) + + for _, register := range slices.Backward(f.CPU.General) { + if f.RegisterIsUsed(register) { + f.Register(asm.POP, register) + } + } + + for _, register := range registers { + f.FreeRegister(register) + } +} diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 2b3f4fa..c88ec87 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -3,7 +3,6 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/types" @@ -103,33 +102,6 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { } } - for _, register := range f.CPU.Output[:len(fn.Output)] { - f.SaveRegister(register) - } - - for _, register := range f.CPU.General { - if f.RegisterIsUsed(register) { - f.Register(asm.PUSH, register) - } - } - - f.Call(fn.UniqueName) - - for _, register := range registers { - if register == f.CPU.Output[0] && root.Parent != nil { - continue - } - - f.FreeRegister(register) - } - - for i := len(f.CPU.General) - 1; i >= 0; i-- { - register := f.CPU.General[i] - - if f.RegisterIsUsed(register) { - f.Register(asm.POP, register) - } - } - + f.CallSafe(fn, registers) return fn, nil } diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index a8f46bd..3d144f8 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -16,22 +16,6 @@ func (f *Function) CompileDelete(root *expression.Expression) error { f.SaveRegister(f.CPU.Input[1]) f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Register) f.RegisterNumber(asm.MOVE, f.CPU.Input[1], int(variable.Type.(*types.Pointer).To.Size())) - - for _, register := range f.CPU.General { - if f.RegisterIsUsed(register) { - f.Register(asm.PUSH, register) - } - } - - f.Call("mem.free") - - for i := len(f.CPU.General) - 1; i >= 0; i-- { - register := f.CPU.General[i] - - if f.RegisterIsUsed(register) { - f.Register(asm.POP, register) - } - } - + f.CallSafe(f.Functions["mem.free"], f.CPU.Input[:2]) return nil } diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index cdbf76d..d25818b 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -12,22 +12,6 @@ func (f *Function) CompileNew(root *expression.Expression) error { typ := f.Types[structName] f.SaveRegister(f.CPU.Input[0]) f.RegisterNumber(asm.MOVE, f.CPU.Input[0], typ.Size()) - - for _, register := range f.CPU.General { - if f.RegisterIsUsed(register) { - f.Register(asm.PUSH, register) - } - } - - f.Call("mem.alloc") - - for i := len(f.CPU.General) - 1; i >= 0; i-- { - register := f.CPU.General[i] - - if f.RegisterIsUsed(register) { - f.Register(asm.POP, register) - } - } - + f.CallSafe(f.Functions["mem.alloc"], f.CPU.Input[:1]) return nil } From f558a693661ea56f95167f2bad6a20337dbe280c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 5 Feb 2025 23:10:50 +0100 Subject: [PATCH 0647/1012] Added build statistics --- src/cli/Build.go | 8 ++++++++ src/cli/Help.go | 23 +++++++++++++---------- src/compiler/Compile.go | 1 + src/compiler/Result.go | 25 ++++++++++++++++++------- src/config/config.go | 6 +++++- src/core/PrintInstructions.go | 9 +++++++++ 6 files changed, 54 insertions(+), 18 deletions(-) diff --git a/src/cli/Build.go b/src/cli/Build.go index 206ef7c..5f78ccd 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -42,8 +42,12 @@ func buildWithArgs(args []string) (*build.Build, error) { case "-d", "--dry": config.Dry = true + case "-s", "--statistics": + config.Statistics = true + case "-v", "--verbose": config.Assembler = true + config.Statistics = true case "--arch": i++ @@ -105,6 +109,10 @@ func makeExecutable(b *build.Build) error { result.PrintInstructions() } + if config.Statistics { + result.PrintStatistics() + } + if config.Dry { return nil } diff --git a/src/cli/Help.go b/src/cli/Help.go index 8d4f0ef..0543ad8 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -7,19 +7,22 @@ import ( // Help shows the command line argument usage. func Help(w io.Writer, code int) int { - fmt.Fprintln(w, `Usage: q [command] [options] + fmt.Fprintln(w, `Usage: - commands: + q [command] [options] - build [directory | file] - run [directory | file] - help - system +Commands: - build options: + build [directory | file] build an executable from a file or directory + --arch [arch] cross-compile for a different CPU architecture (x86, arm or riscv) + --assembler, -a show assembler instructions + --dry, -d skip writing the executable to disk + --os [os] cross-compile for a different OS (linux, mac or windows) + --statistics, -s show statistics + --verbose, -v show everything - --assembler, -a Show assembler instructions. - --comments, -c Show assembler comments. - --verbose, -v Show everything.`) + run [directory | file] build and run the executable + system show system information + help show this help`) return code } diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 2fdea38..ca3ef28 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -105,6 +105,7 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c result.Main = main result.Functions = allFunctions + result.finalize() return result, nil } diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 722ab99..c4d739f 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -2,6 +2,7 @@ package compiler import ( "bufio" + "fmt" "io" "os" @@ -13,6 +14,7 @@ import ( "git.akyoto.dev/cli/q/src/macho" "git.akyoto.dev/cli/q/src/pe" "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/go/color/ansi" ) // Result contains all the compiled functions in a build. @@ -21,10 +23,13 @@ type Result struct { Functions map[string]*core.Function InstructionCount int DataCount int + Code []byte + Data []byte + DLLs dll.List } // finalize generates the final machine code. -func (r *Result) finalize() ([]byte, []byte, dll.List) { +func (r *Result) finalize() { // This will be the entry point of the executable. // The only job of the entry function is to call `main` and exit cleanly. // The reason we call `main` instead of using `main` itself is to place @@ -50,7 +55,7 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { final.DLLCall("kernel32.ExitProcess") } - dlls := dll.List{ + r.DLLs = dll.List{ {Name: "kernel32", Functions: []string{"ExitProcess"}}, } @@ -61,7 +66,7 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { for _, library := range f.DLLs { for _, fn := range library.Functions { - dlls = dlls.Append(library.Name, fn) + r.DLLs = r.DLLs.Append(library.Name, fn) } } }) @@ -82,8 +87,7 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { final.DLLCall("kernel32.ExitProcess") } - code, data := final.Finalize(dlls) - return code, data, dlls + r.Code, r.Data = final.Finalize(r.DLLs) } // eachFunction recursively finds all the calls to external functions. @@ -119,10 +123,17 @@ func (r *Result) PrintInstructions() { }) } +// PrintStatistics shows the statistics. +func (r *Result) PrintStatistics() { + ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮") + ansi.Dim.Printf("│ %-44s%-32s │\n", "Code:", fmt.Sprintf("%d bytes", len(r.Code))) + ansi.Dim.Printf("│ %-44s%-32s │\n", "Data:", fmt.Sprintf("%d bytes", len(r.Data))) + ansi.Dim.Println("╰──────────────────────────────────────────────────────────────────────────────╯") +} + // Write writes the executable to the given writer. func (r *Result) Write(writer io.Writer) error { - code, data, dlls := r.finalize() - return write(writer, code, data, dlls) + return write(writer, r.Code, r.Data, r.DLLs) } // Write writes an executable file to disk. diff --git a/src/config/config.go b/src/config/config.go index b012e55..89644c8 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -14,9 +14,12 @@ const ( ) var ( - // Shows the assembly instructions at the end of the compilation. + // Shows the assembly instructions at the end. Assembler bool + // Shows statistics at the end. + Statistics bool + // Calculates the result of operations on constants at compile time. ConstantFold bool @@ -33,6 +36,7 @@ var ( // Reset resets the configuration to its default values. func Reset() { Assembler = false + Statistics = false ConstantFold = true Dry = false TargetArch = runtime.GOARCH diff --git a/src/core/PrintInstructions.go b/src/core/PrintInstructions.go index 6020cf7..d777ff0 100644 --- a/src/core/PrintInstructions.go +++ b/src/core/PrintInstructions.go @@ -12,6 +12,15 @@ import ( func (f *Function) PrintInstructions() { ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮") + if len(f.Input) > 0 { + for i, input := range f.Input { + ansi.Dim.Printf("│ %-44s%-32s", input.Name, f.CPU.Input[i]) + ansi.Dim.Print(" │\n") + } + + ansi.Dim.Println("├──────────────────────────────────────────────────────────────────────────────┤") + } + for i, x := range f.Assembler.Instructions { ansi.Dim.Print("│ ") From 37afbde0da229865b7a206e17c5e562ca5da9919 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 5 Feb 2025 23:10:50 +0100 Subject: [PATCH 0648/1012] Added build statistics --- src/cli/Build.go | 8 ++++++++ src/cli/Help.go | 23 +++++++++++++---------- src/compiler/Compile.go | 1 + src/compiler/Result.go | 25 ++++++++++++++++++------- src/config/config.go | 6 +++++- src/core/PrintInstructions.go | 9 +++++++++ 6 files changed, 54 insertions(+), 18 deletions(-) diff --git a/src/cli/Build.go b/src/cli/Build.go index 206ef7c..5f78ccd 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -42,8 +42,12 @@ func buildWithArgs(args []string) (*build.Build, error) { case "-d", "--dry": config.Dry = true + case "-s", "--statistics": + config.Statistics = true + case "-v", "--verbose": config.Assembler = true + config.Statistics = true case "--arch": i++ @@ -105,6 +109,10 @@ func makeExecutable(b *build.Build) error { result.PrintInstructions() } + if config.Statistics { + result.PrintStatistics() + } + if config.Dry { return nil } diff --git a/src/cli/Help.go b/src/cli/Help.go index 8d4f0ef..0543ad8 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -7,19 +7,22 @@ import ( // Help shows the command line argument usage. func Help(w io.Writer, code int) int { - fmt.Fprintln(w, `Usage: q [command] [options] + fmt.Fprintln(w, `Usage: - commands: + q [command] [options] - build [directory | file] - run [directory | file] - help - system +Commands: - build options: + build [directory | file] build an executable from a file or directory + --arch [arch] cross-compile for a different CPU architecture (x86, arm or riscv) + --assembler, -a show assembler instructions + --dry, -d skip writing the executable to disk + --os [os] cross-compile for a different OS (linux, mac or windows) + --statistics, -s show statistics + --verbose, -v show everything - --assembler, -a Show assembler instructions. - --comments, -c Show assembler comments. - --verbose, -v Show everything.`) + run [directory | file] build and run the executable + system show system information + help show this help`) return code } diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 2fdea38..ca3ef28 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -105,6 +105,7 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c result.Main = main result.Functions = allFunctions + result.finalize() return result, nil } diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 722ab99..c4d739f 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -2,6 +2,7 @@ package compiler import ( "bufio" + "fmt" "io" "os" @@ -13,6 +14,7 @@ import ( "git.akyoto.dev/cli/q/src/macho" "git.akyoto.dev/cli/q/src/pe" "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/go/color/ansi" ) // Result contains all the compiled functions in a build. @@ -21,10 +23,13 @@ type Result struct { Functions map[string]*core.Function InstructionCount int DataCount int + Code []byte + Data []byte + DLLs dll.List } // finalize generates the final machine code. -func (r *Result) finalize() ([]byte, []byte, dll.List) { +func (r *Result) finalize() { // This will be the entry point of the executable. // The only job of the entry function is to call `main` and exit cleanly. // The reason we call `main` instead of using `main` itself is to place @@ -50,7 +55,7 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { final.DLLCall("kernel32.ExitProcess") } - dlls := dll.List{ + r.DLLs = dll.List{ {Name: "kernel32", Functions: []string{"ExitProcess"}}, } @@ -61,7 +66,7 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { for _, library := range f.DLLs { for _, fn := range library.Functions { - dlls = dlls.Append(library.Name, fn) + r.DLLs = r.DLLs.Append(library.Name, fn) } } }) @@ -82,8 +87,7 @@ func (r *Result) finalize() ([]byte, []byte, dll.List) { final.DLLCall("kernel32.ExitProcess") } - code, data := final.Finalize(dlls) - return code, data, dlls + r.Code, r.Data = final.Finalize(r.DLLs) } // eachFunction recursively finds all the calls to external functions. @@ -119,10 +123,17 @@ func (r *Result) PrintInstructions() { }) } +// PrintStatistics shows the statistics. +func (r *Result) PrintStatistics() { + ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮") + ansi.Dim.Printf("│ %-44s%-32s │\n", "Code:", fmt.Sprintf("%d bytes", len(r.Code))) + ansi.Dim.Printf("│ %-44s%-32s │\n", "Data:", fmt.Sprintf("%d bytes", len(r.Data))) + ansi.Dim.Println("╰──────────────────────────────────────────────────────────────────────────────╯") +} + // Write writes the executable to the given writer. func (r *Result) Write(writer io.Writer) error { - code, data, dlls := r.finalize() - return write(writer, code, data, dlls) + return write(writer, r.Code, r.Data, r.DLLs) } // Write writes an executable file to disk. diff --git a/src/config/config.go b/src/config/config.go index b012e55..89644c8 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -14,9 +14,12 @@ const ( ) var ( - // Shows the assembly instructions at the end of the compilation. + // Shows the assembly instructions at the end. Assembler bool + // Shows statistics at the end. + Statistics bool + // Calculates the result of operations on constants at compile time. ConstantFold bool @@ -33,6 +36,7 @@ var ( // Reset resets the configuration to its default values. func Reset() { Assembler = false + Statistics = false ConstantFold = true Dry = false TargetArch = runtime.GOARCH diff --git a/src/core/PrintInstructions.go b/src/core/PrintInstructions.go index 6020cf7..d777ff0 100644 --- a/src/core/PrintInstructions.go +++ b/src/core/PrintInstructions.go @@ -12,6 +12,15 @@ import ( func (f *Function) PrintInstructions() { ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮") + if len(f.Input) > 0 { + for i, input := range f.Input { + ansi.Dim.Printf("│ %-44s%-32s", input.Name, f.CPU.Input[i]) + ansi.Dim.Print(" │\n") + } + + ansi.Dim.Println("├──────────────────────────────────────────────────────────────────────────────┤") + } + for i, x := range f.Assembler.Instructions { ansi.Dim.Print("│ ") From 9c53235e7e1b0440f358ac1af16724a8b45c7e94 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 5 Feb 2025 23:16:18 +0100 Subject: [PATCH 0649/1012] Renamed x64 to x86 --- docs/todo.md | 2 +- src/asm/Finalize.go | 114 ++++----- src/compiler/Result.go | 22 +- src/core/CompileAssignDivision.go | 6 +- src/core/CompileCall.go | 4 +- src/core/ExecuteRegisterNumber.go | 10 +- src/core/ExecuteRegisterRegister.go | 10 +- src/core/NewFunction.go | 14 +- src/core/ResolveTypes.go | 4 +- src/readme.md | 2 +- src/x64/Add_test.go | 88 ------- src/x64/And_test.go | 88 ------- src/x64/Compare_test.go | 88 ------- src/x64/Div_test.go | 39 --- src/x64/Load_test.go | 157 ------------ src/x64/Move_test.go | 108 --------- src/x64/Mul_test.go | 88 ------- src/x64/Negate_test.go | 39 --- src/x64/Or_test.go | 88 ------- src/x64/Pop_test.go | 39 --- src/x64/Push_test.go | 39 --- src/x64/Registers_test.go | 12 - src/x64/Shift_test.go | 71 ------ src/x64/StoreDynamic_test.go | 171 ------------- src/x64/Store_test.go | 305 ------------------------ src/x64/Sub_test.go | 88 ------- src/x64/Xor_test.go | 88 ------- src/x64/x64_test.go | 19 -- src/{x64 => x86}/Add.go | 2 +- src/x86/Add_test.go | 88 +++++++ src/{x64 => x86}/AlignStack.go | 2 +- src/{x64 => x86}/And.go | 2 +- src/x86/And_test.go | 88 +++++++ src/{x64 => x86}/Call.go | 2 +- src/{x64 => x86}/Compare.go | 2 +- src/x86/Compare_test.go | 88 +++++++ src/{x64 => x86}/Div.go | 2 +- src/x86/Div_test.go | 39 +++ src/{x64 => x86}/ExtendRAXToRDX.go | 2 +- src/{x64 => x86}/Jump.go | 2 +- src/{x64 => x86}/Jump_test.go | 18 +- src/{x64 => x86}/Load.go | 2 +- src/x86/Load_test.go | 157 ++++++++++++ src/{x64 => x86}/ModRM.go | 2 +- src/{x64 => x86}/ModRM_test.go | 6 +- src/{x64 => x86}/Move.go | 2 +- src/x86/Move_test.go | 108 +++++++++ src/{x64 => x86}/Mul.go | 2 +- src/x86/Mul_test.go | 88 +++++++ src/{x64 => x86}/Negate.go | 2 +- src/x86/Negate_test.go | 39 +++ src/{x64 => x86}/Or.go | 2 +- src/x86/Or_test.go | 88 +++++++ src/{x64 => x86}/Pop.go | 2 +- src/x86/Pop_test.go | 39 +++ src/{x64 => x86}/Push.go | 2 +- src/x86/Push_test.go | 39 +++ src/{x64 => x86}/REX.go | 2 +- src/{x64 => x86}/REX_test.go | 6 +- src/{x64 => x86}/Registers.go | 2 +- src/x86/Registers_test.go | 12 + src/{x64 => x86}/Return.go | 2 +- src/{x64 => x86}/SIB.go | 2 +- src/{x64 => x86}/SIB_test.go | 6 +- src/{x64 => x86}/Shift.go | 2 +- src/x86/Shift_test.go | 71 ++++++ src/{x64 => x86}/Store.go | 2 +- src/{x64 => x86}/StoreDynamic.go | 2 +- src/x86/StoreDynamic_test.go | 171 +++++++++++++ src/x86/Store_test.go | 305 ++++++++++++++++++++++++ src/{x64 => x86}/Sub.go | 2 +- src/x86/Sub_test.go | 88 +++++++ src/{x64 => x86}/Syscall.go | 2 +- src/{x64 => x86}/Xor.go | 2 +- src/x86/Xor_test.go | 88 +++++++ src/{x64 => x86}/encode.go | 2 +- src/{x64 => x86}/encodeNum.go | 2 +- src/{x64 => x86}/memoryAccess.go | 2 +- src/{x64 => x86}/memoryAccessDynamic.go | 2 +- src/x86/x64_test.go | 19 ++ 80 files changed, 1757 insertions(+), 1757 deletions(-) delete mode 100644 src/x64/Add_test.go delete mode 100644 src/x64/And_test.go delete mode 100644 src/x64/Compare_test.go delete mode 100644 src/x64/Div_test.go delete mode 100644 src/x64/Load_test.go delete mode 100644 src/x64/Move_test.go delete mode 100644 src/x64/Mul_test.go delete mode 100644 src/x64/Negate_test.go delete mode 100644 src/x64/Or_test.go delete mode 100644 src/x64/Pop_test.go delete mode 100644 src/x64/Push_test.go delete mode 100644 src/x64/Registers_test.go delete mode 100644 src/x64/Shift_test.go delete mode 100644 src/x64/StoreDynamic_test.go delete mode 100644 src/x64/Store_test.go delete mode 100644 src/x64/Sub_test.go delete mode 100644 src/x64/Xor_test.go delete mode 100644 src/x64/x64_test.go rename src/{x64 => x86}/Add.go (97%) create mode 100644 src/x86/Add_test.go rename src/{x64 => x86}/AlignStack.go (91%) rename src/{x64 => x86}/And.go (97%) create mode 100644 src/x86/And_test.go rename src/{x64 => x86}/Call.go (98%) rename src/{x64 => x86}/Compare.go (97%) create mode 100644 src/x86/Compare_test.go rename src/{x64 => x86}/Div.go (96%) create mode 100644 src/x86/Div_test.go rename src/{x64 => x86}/ExtendRAXToRDX.go (93%) rename src/{x64 => x86}/Jump.go (98%) rename src/{x64 => x86}/Jump_test.go (56%) rename src/{x64 => x86}/Load.go (95%) create mode 100644 src/x86/Load_test.go rename src/{x64 => x86}/ModRM.go (97%) rename src/{x64 => x86}/ModRM_test.go (90%) rename src/{x64 => x86}/Move.go (99%) create mode 100644 src/x86/Move_test.go rename src/{x64 => x86}/Mul.go (97%) create mode 100644 src/x86/Mul_test.go rename src/{x64 => x86}/Negate.go (94%) create mode 100644 src/x86/Negate_test.go rename src/{x64 => x86}/Or.go (97%) create mode 100644 src/x86/Or_test.go rename src/{x64 => x86}/Pop.go (96%) create mode 100644 src/x86/Pop_test.go rename src/{x64 => x86}/Push.go (96%) create mode 100644 src/x86/Push_test.go rename src/{x64 => x86}/REX.go (93%) rename src/{x64 => x86}/REX_test.go (87%) rename src/{x64 => x86}/Registers.go (98%) create mode 100644 src/x86/Registers_test.go rename src/{x64 => x86}/Return.go (94%) rename src/{x64 => x86}/SIB.go (97%) rename src/{x64 => x86}/SIB_test.go (89%) rename src/{x64 => x86}/Shift.go (97%) create mode 100644 src/x86/Shift_test.go rename src/{x64 => x86}/Store.go (98%) rename src/{x64 => x86}/StoreDynamic.go (98%) create mode 100644 src/x86/StoreDynamic_test.go create mode 100644 src/x86/Store_test.go rename src/{x64 => x86}/Sub.go (97%) create mode 100644 src/x86/Sub_test.go rename src/{x64 => x86}/Syscall.go (91%) rename src/{x64 => x86}/Xor.go (97%) create mode 100644 src/x86/Xor_test.go rename src/{x64 => x86}/encode.go (98%) rename src/{x64 => x86}/encodeNum.go (97%) rename src/{x64 => x86}/memoryAccess.go (98%) rename src/{x64 => x86}/memoryAccessDynamic.go (98%) create mode 100644 src/x86/x64_test.go diff --git a/docs/todo.md b/docs/todo.md index b8655f1..19132e0 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -71,7 +71,7 @@ - [ ] arm64 - [ ] riscv -- [x] x64 +- [x] x86 ### Platform diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 0915e6b..5ea281c 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -6,7 +6,7 @@ import ( "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" ) // Finalize generates the final machine code. @@ -27,67 +27,67 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { case ADD: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.AddRegisterNumber(code, operands.Register, operands.Number) + code = x86.AddRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.AddRegisterRegister(code, operands.Destination, operands.Source) + code = x86.AddRegisterRegister(code, operands.Destination, operands.Source) } case AND: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.AndRegisterNumber(code, operands.Register, operands.Number) + code = x86.AndRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.AndRegisterRegister(code, operands.Destination, operands.Source) + code = x86.AndRegisterRegister(code, operands.Destination, operands.Source) } case SUB: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.SubRegisterNumber(code, operands.Register, operands.Number) + code = x86.SubRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.SubRegisterRegister(code, operands.Destination, operands.Source) + code = x86.SubRegisterRegister(code, operands.Destination, operands.Source) } case MUL: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.MulRegisterNumber(code, operands.Register, operands.Number) + code = x86.MulRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.MulRegisterRegister(code, operands.Destination, operands.Source) + code = x86.MulRegisterRegister(code, operands.Destination, operands.Source) } case DIV: switch operands := x.Data.(type) { case *RegisterRegister: - if operands.Destination != x64.RAX { - code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination) + if operands.Destination != x86.RAX { + code = x86.MoveRegisterRegister(code, x86.RAX, operands.Destination) } - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Source) + code = x86.ExtendRAXToRDX(code) + code = x86.DivRegister(code, operands.Source) - if operands.Destination != x64.RAX { - code = x64.MoveRegisterRegister(code, operands.Destination, x64.RAX) + if operands.Destination != x86.RAX { + code = x86.MoveRegisterRegister(code, operands.Destination, x86.RAX) } } case MODULO: switch operands := x.Data.(type) { case *RegisterRegister: - if operands.Destination != x64.RAX { - code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination) + if operands.Destination != x86.RAX { + code = x86.MoveRegisterRegister(code, x86.RAX, operands.Destination) } - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Source) + code = x86.ExtendRAXToRDX(code) + code = x86.DivRegister(code, operands.Source) - if operands.Destination != x64.RDX { - code = x64.MoveRegisterRegister(code, operands.Destination, x64.RDX) + if operands.Destination != x86.RDX { + code = x86.MoveRegisterRegister(code, operands.Destination, x86.RDX) } } case CALL: - code = x64.Call(code, 0x00_00_00_00) + code = x86.Call(code, 0x00_00_00_00) size := 4 label := x.Data.(*Label) @@ -116,20 +116,20 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { case COMPARE: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.CompareRegisterNumber(code, operands.Register, operands.Number) + code = x86.CompareRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source) + code = x86.CompareRegisterRegister(code, operands.Destination, operands.Source) } case DLLCALL: size := 4 // TODO: R15 could be in use. - code = x64.MoveRegisterRegister(code, x64.R15, x64.RSP) - code = x64.AlignStack(code) - code = x64.SubRegisterNumber(code, x64.RSP, 32) - code = x64.CallAtAddress(code, 0x00_00_00_00) + code = x86.MoveRegisterRegister(code, x86.R15, x86.RSP) + code = x86.AlignStack(code) + code = x86.SubRegisterNumber(code, x86.RSP, 32) + code = x86.CallAtAddress(code, 0x00_00_00_00) position := len(code) - size - code = x64.MoveRegisterRegister(code, x64.RSP, x64.R15) + code = x86.MoveRegisterRegister(code, x86.RSP, x86.R15) label := x.Data.(*Label) pointer := &pointer{ @@ -156,19 +156,19 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { case JE, JNE, JG, JGE, JL, JLE, JUMP: switch x.Mnemonic { case JE: - code = x64.Jump8IfEqual(code, 0x00) + code = x86.Jump8IfEqual(code, 0x00) case JNE: - code = x64.Jump8IfNotEqual(code, 0x00) + code = x86.Jump8IfNotEqual(code, 0x00) case JG: - code = x64.Jump8IfGreater(code, 0x00) + code = x86.Jump8IfGreater(code, 0x00) case JGE: - code = x64.Jump8IfGreaterOrEqual(code, 0x00) + code = x86.Jump8IfGreaterOrEqual(code, 0x00) case JL: - code = x64.Jump8IfLess(code, 0x00) + code = x86.Jump8IfLess(code, 0x00) case JLE: - code = x64.Jump8IfLessOrEqual(code, 0x00) + code = x86.Jump8IfLessOrEqual(code, 0x00) case JUMP: - code = x64.Jump8(code, 0x00) + code = x86.Jump8(code, 0x00) } size := 1 @@ -199,20 +199,20 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { case LOAD: switch operands := x.Data.(type) { case *MemoryRegister: - code = x64.LoadRegister(code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) + code = x86.LoadRegister(code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) } case MOVE: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.MoveRegisterNumber(code, operands.Register, operands.Number) + code = x86.MoveRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.MoveRegisterRegister(code, operands.Destination, operands.Source) + code = x86.MoveRegisterRegister(code, operands.Destination, operands.Source) case *RegisterLabel: start := len(code) - code = x64.MoveRegisterNumber(code, operands.Register, 0x00_00_00_00) + code = x86.MoveRegisterNumber(code, operands.Register, 0x00_00_00_00) size := 4 opSize := len(code) - size - start regLabel := x.Data.(*RegisterLabel) @@ -253,59 +253,59 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { case NEGATE: switch operands := x.Data.(type) { case *Register: - code = x64.NegateRegister(code, operands.Register) + code = x86.NegateRegister(code, operands.Register) } case OR: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.OrRegisterNumber(code, operands.Register, operands.Number) + code = x86.OrRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.OrRegisterRegister(code, operands.Destination, operands.Source) + code = x86.OrRegisterRegister(code, operands.Destination, operands.Source) } case POP: switch operands := x.Data.(type) { case *Register: - code = x64.PopRegister(code, operands.Register) + code = x86.PopRegister(code, operands.Register) } case PUSH: switch operands := x.Data.(type) { case *Register: - code = x64.PushRegister(code, operands.Register) + code = x86.PushRegister(code, operands.Register) } case RETURN: - code = x64.Return(code) + code = x86.Return(code) case SHIFTL: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.ShiftLeftNumber(code, operands.Register, byte(operands.Number)&0b111111) + code = x86.ShiftLeftNumber(code, operands.Register, byte(operands.Number)&0b111111) } case SHIFTRS: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.ShiftRightSignedNumber(code, operands.Register, byte(operands.Number)&0b111111) + code = x86.ShiftRightSignedNumber(code, operands.Register, byte(operands.Number)&0b111111) } case STORE: switch operands := x.Data.(type) { case *MemoryNumber: if operands.Address.OffsetRegister == math.MaxUint8 { - code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) + code = x86.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) } else { - code = x64.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) + code = x86.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) } case *MemoryLabel: start := len(code) if operands.Address.OffsetRegister == math.MaxUint8 { - code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, 0b00_00_00_00) + code = x86.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, 0b00_00_00_00) } else { - code = x64.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, 0b00_00_00_00) + code = x86.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, 0b00_00_00_00) } size := 4 @@ -328,21 +328,21 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { }) case *MemoryRegister: if operands.Address.OffsetRegister == math.MaxUint8 { - code = x64.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) + code = x86.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) } else { - code = x64.StoreDynamicRegister(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) + code = x86.StoreDynamicRegister(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) } } case SYSCALL: - code = x64.Syscall(code) + code = x86.Syscall(code) case XOR: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.XorRegisterNumber(code, operands.Register, operands.Number) + code = x86.XorRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.XorRegisterRegister(code, operands.Destination, operands.Source) + code = x86.XorRegisterRegister(code, operands.Destination, operands.Source) } default: diff --git a/src/compiler/Result.go b/src/compiler/Result.go index c4d739f..6d01851 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -13,7 +13,7 @@ import ( "git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/macho" "git.akyoto.dev/cli/q/src/pe" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" "git.akyoto.dev/go/color/ansi" ) @@ -43,15 +43,15 @@ func (r *Result) finalize() { switch config.TargetOS { case config.Linux: - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], LinuxExit) - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], LinuxExit) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 0) final.Syscall() case config.Mac: - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], MacExit) - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], MacExit) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 0) final.Syscall() case config.Windows: - final.RegisterNumber(asm.MOVE, x64.WindowsInputRegisters[0], 0) + final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 0) final.DLLCall("kernel32.ExitProcess") } @@ -75,15 +75,15 @@ func (r *Result) finalize() { switch config.TargetOS { case config.Linux: - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], LinuxExit) - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], LinuxExit) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 1) final.Syscall() case config.Mac: - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], MacExit) - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], MacExit) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 1) final.Syscall() case config.Windows: - final.RegisterNumber(asm.MOVE, x64.WindowsInputRegisters[0], 1) + final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 1) final.DLLCall("kernel32.ExitProcess") } diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index e169827..d9f5f5c 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -4,7 +4,7 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" ) // CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right. @@ -37,8 +37,8 @@ func (f *Function) CompileAssignDivision(node *ast.Assign) error { divisor := right.Children[1] err = f.Execute(right.Token, dividendRegister, divisor) - f.RegisterRegister(asm.MOVE, quotientVariable.Register, x64.RAX) - f.RegisterRegister(asm.MOVE, remainderVariable.Register, x64.RDX) + f.RegisterRegister(asm.MOVE, quotientVariable.Register, x86.RAX) + f.RegisterRegister(asm.MOVE, remainderVariable.Register, x86.RDX) if isTemporary { f.FreeRegister(dividendRegister) diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index c88ec87..5ce42f1 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -6,7 +6,7 @@ import ( "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" ) // CompileCall executes a function call. @@ -50,7 +50,7 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { if pkg == "kernel32" || pkg == "user32" || pkg == "gdi32" || pkg == "comctl32" { parameters := root.Children[1:] - registers := x64.WindowsInputRegisters[:len(parameters)] + registers := x86.WindowsInputRegisters[:len(parameters)] for i := len(parameters) - 1; i >= 0; i-- { _, err := f.ExpressionToRegister(parameters[i], registers[i]) diff --git a/src/core/ExecuteRegisterNumber.go b/src/core/ExecuteRegisterNumber.go index 0714751..f26f1a3 100644 --- a/src/core/ExecuteRegisterNumber.go +++ b/src/core/ExecuteRegisterNumber.go @@ -5,7 +5,7 @@ import ( "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" ) // ExecuteRegisterNumber performs an operation on a register and a number. @@ -25,16 +25,16 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg f.RegisterNumber(asm.MUL, register, number) case token.Div, token.DivAssign: - f.SaveRegister(x64.RAX) - f.SaveRegister(x64.RDX) + f.SaveRegister(x86.RAX) + f.SaveRegister(x86.RDX) tmp := f.NewRegister() f.RegisterNumber(asm.MOVE, tmp, number) f.RegisterRegister(asm.DIV, register, tmp) f.FreeRegister(tmp) case token.Mod, token.ModAssign: - f.SaveRegister(x64.RAX) - f.SaveRegister(x64.RDX) + f.SaveRegister(x86.RAX) + f.SaveRegister(x86.RDX) tmp := f.NewRegister() f.RegisterNumber(asm.MOVE, tmp, number) f.RegisterRegister(asm.MODULO, register, tmp) diff --git a/src/core/ExecuteRegisterRegister.go b/src/core/ExecuteRegisterRegister.go index 3add7ef..ebb6811 100644 --- a/src/core/ExecuteRegisterRegister.go +++ b/src/core/ExecuteRegisterRegister.go @@ -5,7 +5,7 @@ import ( "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" ) // ExecuteRegisterRegister performs an operation on two registers. @@ -25,13 +25,13 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.R f.RegisterRegister(asm.MUL, register, operand) case token.Div, token.DivAssign: - f.SaveRegister(x64.RAX) - f.SaveRegister(x64.RDX) + f.SaveRegister(x86.RAX) + f.SaveRegister(x86.RDX) f.RegisterRegister(asm.DIV, register, operand) case token.Mod, token.ModAssign: - f.SaveRegister(x64.RAX) - f.SaveRegister(x64.RDX) + f.SaveRegister(x86.RAX) + f.SaveRegister(x86.RDX) f.RegisterRegister(asm.MODULO, register, operand) case token.And, token.AndAssign: diff --git a/src/core/NewFunction.go b/src/core/NewFunction.go index 5e5a4ae..e2d93d2 100644 --- a/src/core/NewFunction.go +++ b/src/core/NewFunction.go @@ -7,7 +7,7 @@ import ( "git.akyoto.dev/cli/q/src/register" "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" ) // NewFunction creates a new function. @@ -26,12 +26,12 @@ func NewFunction(pkg string, name string, file *fs.File, body []token.Token) *Fu Scopes: []*scope.Scope{{}}, }, CPU: cpu.CPU{ - All: x64.AllRegisters, - General: x64.GeneralRegisters, - Input: x64.InputRegisters, - Output: x64.OutputRegisters, - SyscallInput: x64.SyscallInputRegisters, - SyscallOutput: x64.SyscallOutputRegisters, + All: x86.AllRegisters, + General: x86.GeneralRegisters, + Input: x86.InputRegisters, + Output: x86.OutputRegisters, + SyscallInput: x86.SyscallInputRegisters, + SyscallOutput: x86.SyscallOutputRegisters, }, }, } diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index c508073..c779c13 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -4,7 +4,7 @@ import ( "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" ) // ResolveTypes parses the input and output types. @@ -27,7 +27,7 @@ func (f *Function) ResolveTypes() error { f.AddVariable(&scope.Variable{ Name: param.Name, Type: param.Type, - Register: x64.InputRegisters[i], + Register: x86.InputRegisters[i], Alive: uses, }) } diff --git a/src/readme.md b/src/readme.md index 6483efc..4ee22a5 100644 --- a/src/readme.md +++ b/src/readme.md @@ -24,4 +24,4 @@ - [sizeof](sizeof) - Calculates the byte size of numbers - [token](token) - Converts a file to tokens with the `Tokenize` function - [types](types) - Type system (w.i.p.) -- [x64](x64) - x86-64 implementation +- [x86](x86) - x86-64 implementation diff --git a/src/x64/Add_test.go b/src/x64/Add_test.go deleted file mode 100644 index 134e09e..0000000 --- a/src/x64/Add_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestAddRegisterNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0x83, 0xC0, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0x83, 0xC1, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0x83, 0xC2, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0x83, 0xC3, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0x83, 0xC4, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0x83, 0xC5, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0x83, 0xC6, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0x83, 0xC7, 0x01}}, - {x64.R8, 1, []byte{0x49, 0x83, 0xC0, 0x01}}, - {x64.R9, 1, []byte{0x49, 0x83, 0xC1, 0x01}}, - {x64.R10, 1, []byte{0x49, 0x83, 0xC2, 0x01}}, - {x64.R11, 1, []byte{0x49, 0x83, 0xC3, 0x01}}, - {x64.R12, 1, []byte{0x49, 0x83, 0xC4, 0x01}}, - {x64.R13, 1, []byte{0x49, 0x83, 0xC5, 0x01}}, - {x64.R14, 1, []byte{0x49, 0x83, 0xC6, 0x01}}, - {x64.R15, 1, []byte{0x49, 0x83, 0xC7, 0x01}}, - - {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("add %s, %x", pattern.Register, pattern.Number) - code := x64.AddRegisterNumber(nil, pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestAddRegisterRegister(t *testing.T) { - usagePatterns := []struct { - Left cpu.Register - Right cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, []byte{0x4C, 0x01, 0xF8}}, - {x64.RCX, x64.R14, []byte{0x4C, 0x01, 0xF1}}, - {x64.RDX, x64.R13, []byte{0x4C, 0x01, 0xEA}}, - {x64.RBX, x64.R12, []byte{0x4C, 0x01, 0xE3}}, - {x64.RSP, x64.R11, []byte{0x4C, 0x01, 0xDC}}, - {x64.RBP, x64.R10, []byte{0x4C, 0x01, 0xD5}}, - {x64.RSI, x64.R9, []byte{0x4C, 0x01, 0xCE}}, - {x64.RDI, x64.R8, []byte{0x4C, 0x01, 0xC7}}, - {x64.R8, x64.RDI, []byte{0x49, 0x01, 0xF8}}, - {x64.R9, x64.RSI, []byte{0x49, 0x01, 0xF1}}, - {x64.R10, x64.RBP, []byte{0x49, 0x01, 0xEA}}, - {x64.R11, x64.RSP, []byte{0x49, 0x01, 0xE3}}, - {x64.R12, x64.RBX, []byte{0x49, 0x01, 0xDC}}, - {x64.R13, x64.RDX, []byte{0x49, 0x01, 0xD5}}, - {x64.R14, x64.RCX, []byte{0x49, 0x01, 0xCE}}, - {x64.R15, x64.RAX, []byte{0x49, 0x01, 0xC7}}, - } - - for _, pattern := range usagePatterns { - t.Logf("add %s, %s", pattern.Left, pattern.Right) - code := x64.AddRegisterRegister(nil, pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/And_test.go b/src/x64/And_test.go deleted file mode 100644 index 249d7b8..0000000 --- a/src/x64/And_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestAndRegisterNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0x83, 0xE0, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0x83, 0xE1, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0x83, 0xE2, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0x83, 0xE3, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0x83, 0xE4, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0x83, 0xE5, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0x83, 0xE6, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0x83, 0xE7, 0x01}}, - {x64.R8, 1, []byte{0x49, 0x83, 0xE0, 0x01}}, - {x64.R9, 1, []byte{0x49, 0x83, 0xE1, 0x01}}, - {x64.R10, 1, []byte{0x49, 0x83, 0xE2, 0x01}}, - {x64.R11, 1, []byte{0x49, 0x83, 0xE3, 0x01}}, - {x64.R12, 1, []byte{0x49, 0x83, 0xE4, 0x01}}, - {x64.R13, 1, []byte{0x49, 0x83, 0xE5, 0x01}}, - {x64.R14, 1, []byte{0x49, 0x83, 0xE6, 0x01}}, - {x64.R15, 1, []byte{0x49, 0x83, 0xE7, 0x01}}, - - {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE7, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE7, 0xFF, 0xFF, 0xFF, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("and %s, %x", pattern.Register, pattern.Number) - code := x64.AndRegisterNumber(nil, pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestAndRegisterRegister(t *testing.T) { - usagePatterns := []struct { - Left cpu.Register - Right cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, []byte{0x4C, 0x21, 0xF8}}, - {x64.RCX, x64.R14, []byte{0x4C, 0x21, 0xF1}}, - {x64.RDX, x64.R13, []byte{0x4C, 0x21, 0xEA}}, - {x64.RBX, x64.R12, []byte{0x4C, 0x21, 0xE3}}, - {x64.RSP, x64.R11, []byte{0x4C, 0x21, 0xDC}}, - {x64.RBP, x64.R10, []byte{0x4C, 0x21, 0xD5}}, - {x64.RSI, x64.R9, []byte{0x4C, 0x21, 0xCE}}, - {x64.RDI, x64.R8, []byte{0x4C, 0x21, 0xC7}}, - {x64.R8, x64.RDI, []byte{0x49, 0x21, 0xF8}}, - {x64.R9, x64.RSI, []byte{0x49, 0x21, 0xF1}}, - {x64.R10, x64.RBP, []byte{0x49, 0x21, 0xEA}}, - {x64.R11, x64.RSP, []byte{0x49, 0x21, 0xE3}}, - {x64.R12, x64.RBX, []byte{0x49, 0x21, 0xDC}}, - {x64.R13, x64.RDX, []byte{0x49, 0x21, 0xD5}}, - {x64.R14, x64.RCX, []byte{0x49, 0x21, 0xCE}}, - {x64.R15, x64.RAX, []byte{0x49, 0x21, 0xC7}}, - } - - for _, pattern := range usagePatterns { - t.Logf("and %s, %s", pattern.Left, pattern.Right) - code := x64.AndRegisterRegister(nil, pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Compare_test.go b/src/x64/Compare_test.go deleted file mode 100644 index 0105ed0..0000000 --- a/src/x64/Compare_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestCompareRegisterNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0x83, 0xF8, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0x83, 0xF9, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0x83, 0xFA, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0x83, 0xFB, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0x83, 0xFC, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0x83, 0xFD, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0x83, 0xFE, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0x83, 0xFF, 0x01}}, - {x64.R8, 1, []byte{0x49, 0x83, 0xF8, 0x01}}, - {x64.R9, 1, []byte{0x49, 0x83, 0xF9, 0x01}}, - {x64.R10, 1, []byte{0x49, 0x83, 0xFA, 0x01}}, - {x64.R11, 1, []byte{0x49, 0x83, 0xFB, 0x01}}, - {x64.R12, 1, []byte{0x49, 0x83, 0xFC, 0x01}}, - {x64.R13, 1, []byte{0x49, 0x83, 0xFD, 0x01}}, - {x64.R14, 1, []byte{0x49, 0x83, 0xFE, 0x01}}, - {x64.R15, 1, []byte{0x49, 0x83, 0xFF, 0x01}}, - - {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("cmp %s, %x", pattern.Register, pattern.Number) - code := x64.CompareRegisterNumber(nil, pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestCompareRegisterRegister(t *testing.T) { - usagePatterns := []struct { - Left cpu.Register - Right cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, []byte{0x4C, 0x39, 0xF8}}, - {x64.RCX, x64.R14, []byte{0x4C, 0x39, 0xF1}}, - {x64.RDX, x64.R13, []byte{0x4C, 0x39, 0xEA}}, - {x64.RBX, x64.R12, []byte{0x4C, 0x39, 0xE3}}, - {x64.RSP, x64.R11, []byte{0x4C, 0x39, 0xDC}}, - {x64.RBP, x64.R10, []byte{0x4C, 0x39, 0xD5}}, - {x64.RSI, x64.R9, []byte{0x4C, 0x39, 0xCE}}, - {x64.RDI, x64.R8, []byte{0x4C, 0x39, 0xC7}}, - {x64.R8, x64.RDI, []byte{0x49, 0x39, 0xF8}}, - {x64.R9, x64.RSI, []byte{0x49, 0x39, 0xF1}}, - {x64.R10, x64.RBP, []byte{0x49, 0x39, 0xEA}}, - {x64.R11, x64.RSP, []byte{0x49, 0x39, 0xE3}}, - {x64.R12, x64.RBX, []byte{0x49, 0x39, 0xDC}}, - {x64.R13, x64.RDX, []byte{0x49, 0x39, 0xD5}}, - {x64.R14, x64.RCX, []byte{0x49, 0x39, 0xCE}}, - {x64.R15, x64.RAX, []byte{0x49, 0x39, 0xC7}}, - } - - for _, pattern := range usagePatterns { - t.Logf("cmp %s, %s", pattern.Left, pattern.Right) - code := x64.CompareRegisterRegister(nil, pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Div_test.go b/src/x64/Div_test.go deleted file mode 100644 index de684b4..0000000 --- a/src/x64/Div_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestDivRegister(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Code []byte - }{ - {x64.RAX, []byte{0x48, 0xF7, 0xF8}}, - {x64.RCX, []byte{0x48, 0xF7, 0xF9}}, - {x64.RDX, []byte{0x48, 0xF7, 0xFA}}, - {x64.RBX, []byte{0x48, 0xF7, 0xFB}}, - {x64.RSP, []byte{0x48, 0xF7, 0xFC}}, - {x64.RBP, []byte{0x48, 0xF7, 0xFD}}, - {x64.RSI, []byte{0x48, 0xF7, 0xFE}}, - {x64.RDI, []byte{0x48, 0xF7, 0xFF}}, - {x64.R8, []byte{0x49, 0xF7, 0xF8}}, - {x64.R9, []byte{0x49, 0xF7, 0xF9}}, - {x64.R10, []byte{0x49, 0xF7, 0xFA}}, - {x64.R11, []byte{0x49, 0xF7, 0xFB}}, - {x64.R12, []byte{0x49, 0xF7, 0xFC}}, - {x64.R13, []byte{0x49, 0xF7, 0xFD}}, - {x64.R14, []byte{0x49, 0xF7, 0xFE}}, - {x64.R15, []byte{0x49, 0xF7, 0xFF}}, - } - - for _, pattern := range usagePatterns { - t.Logf("idiv %s", pattern.Register) - code := x64.DivRegister(nil, pattern.Register) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Load_test.go b/src/x64/Load_test.go deleted file mode 100644 index 9585f15..0000000 --- a/src/x64/Load_test.go +++ /dev/null @@ -1,157 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestLoadRegister(t *testing.T) { - usagePatterns := []struct { - Destination cpu.Register - Source cpu.Register - Offset byte - Length byte - Code []byte - }{ - // No offset - {x64.RAX, x64.R15, 0, 8, []byte{0x49, 0x8B, 0x07}}, - {x64.RAX, x64.R15, 0, 4, []byte{0x41, 0x8B, 0x07}}, - {x64.RAX, x64.R15, 0, 2, []byte{0x66, 0x41, 0x8B, 0x07}}, - {x64.RAX, x64.R15, 0, 1, []byte{0x41, 0x8A, 0x07}}, - {x64.RCX, x64.R14, 0, 8, []byte{0x49, 0x8B, 0x0E}}, - {x64.RCX, x64.R14, 0, 4, []byte{0x41, 0x8B, 0x0E}}, - {x64.RCX, x64.R14, 0, 2, []byte{0x66, 0x41, 0x8B, 0x0E}}, - {x64.RCX, x64.R14, 0, 1, []byte{0x41, 0x8A, 0x0E}}, - {x64.RDX, x64.R13, 0, 8, []byte{0x49, 0x8B, 0x55, 0x00}}, - {x64.RDX, x64.R13, 0, 4, []byte{0x41, 0x8B, 0x55, 0x00}}, - {x64.RDX, x64.R13, 0, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x00}}, - {x64.RDX, x64.R13, 0, 1, []byte{0x41, 0x8A, 0x55, 0x00}}, - {x64.RBX, x64.R12, 0, 8, []byte{0x49, 0x8B, 0x1C, 0x24}}, - {x64.RBX, x64.R12, 0, 4, []byte{0x41, 0x8B, 0x1C, 0x24}}, - {x64.RBX, x64.R12, 0, 2, []byte{0x66, 0x41, 0x8B, 0x1C, 0x24}}, - {x64.RBX, x64.R12, 0, 1, []byte{0x41, 0x8A, 0x1C, 0x24}}, - {x64.RSP, x64.R11, 0, 8, []byte{0x49, 0x8B, 0x23}}, - {x64.RSP, x64.R11, 0, 4, []byte{0x41, 0x8B, 0x23}}, - {x64.RSP, x64.R11, 0, 2, []byte{0x66, 0x41, 0x8B, 0x23}}, - {x64.RSP, x64.R11, 0, 1, []byte{0x41, 0x8A, 0x23}}, - {x64.RBP, x64.R10, 0, 8, []byte{0x49, 0x8B, 0x2A}}, - {x64.RBP, x64.R10, 0, 4, []byte{0x41, 0x8B, 0x2A}}, - {x64.RBP, x64.R10, 0, 2, []byte{0x66, 0x41, 0x8B, 0x2A}}, - {x64.RBP, x64.R10, 0, 1, []byte{0x41, 0x8A, 0x2A}}, - {x64.RSI, x64.R9, 0, 8, []byte{0x49, 0x8B, 0x31}}, - {x64.RSI, x64.R9, 0, 4, []byte{0x41, 0x8B, 0x31}}, - {x64.RSI, x64.R9, 0, 2, []byte{0x66, 0x41, 0x8B, 0x31}}, - {x64.RSI, x64.R9, 0, 1, []byte{0x41, 0x8A, 0x31}}, - {x64.RDI, x64.R8, 0, 8, []byte{0x49, 0x8B, 0x38}}, - {x64.RDI, x64.R8, 0, 4, []byte{0x41, 0x8B, 0x38}}, - {x64.RDI, x64.R8, 0, 2, []byte{0x66, 0x41, 0x8B, 0x38}}, - {x64.RDI, x64.R8, 0, 1, []byte{0x41, 0x8A, 0x38}}, - {x64.R8, x64.RDI, 0, 8, []byte{0x4C, 0x8B, 0x07}}, - {x64.R8, x64.RDI, 0, 4, []byte{0x44, 0x8B, 0x07}}, - {x64.R8, x64.RDI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x07}}, - {x64.R8, x64.RDI, 0, 1, []byte{0x44, 0x8A, 0x07}}, - {x64.R9, x64.RSI, 0, 8, []byte{0x4C, 0x8B, 0x0E}}, - {x64.R9, x64.RSI, 0, 4, []byte{0x44, 0x8B, 0x0E}}, - {x64.R9, x64.RSI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x0E}}, - {x64.R9, x64.RSI, 0, 1, []byte{0x44, 0x8A, 0x0E}}, - {x64.R10, x64.RBP, 0, 8, []byte{0x4C, 0x8B, 0x55, 0x00}}, - {x64.R10, x64.RBP, 0, 4, []byte{0x44, 0x8B, 0x55, 0x00}}, - {x64.R10, x64.RBP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x00}}, - {x64.R10, x64.RBP, 0, 1, []byte{0x44, 0x8A, 0x55, 0x00}}, - {x64.R11, x64.RSP, 0, 8, []byte{0x4C, 0x8B, 0x1C, 0x24}}, - {x64.R11, x64.RSP, 0, 4, []byte{0x44, 0x8B, 0x1C, 0x24}}, - {x64.R11, x64.RSP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x1C, 0x24}}, - {x64.R11, x64.RSP, 0, 1, []byte{0x44, 0x8A, 0x1C, 0x24}}, - {x64.R12, x64.RBX, 0, 8, []byte{0x4C, 0x8B, 0x23}}, - {x64.R12, x64.RBX, 0, 4, []byte{0x44, 0x8B, 0x23}}, - {x64.R12, x64.RBX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x23}}, - {x64.R12, x64.RBX, 0, 1, []byte{0x44, 0x8A, 0x23}}, - {x64.R13, x64.RDX, 0, 8, []byte{0x4C, 0x8B, 0x2A}}, - {x64.R13, x64.RDX, 0, 4, []byte{0x44, 0x8B, 0x2A}}, - {x64.R13, x64.RDX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x2A}}, - {x64.R13, x64.RDX, 0, 1, []byte{0x44, 0x8A, 0x2A}}, - {x64.R14, x64.RCX, 0, 8, []byte{0x4C, 0x8B, 0x31}}, - {x64.R14, x64.RCX, 0, 4, []byte{0x44, 0x8B, 0x31}}, - {x64.R14, x64.RCX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x31}}, - {x64.R14, x64.RCX, 0, 1, []byte{0x44, 0x8A, 0x31}}, - {x64.R15, x64.RAX, 0, 8, []byte{0x4C, 0x8B, 0x38}}, - {x64.R15, x64.RAX, 0, 4, []byte{0x44, 0x8B, 0x38}}, - {x64.R15, x64.RAX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x38}}, - {x64.R15, x64.RAX, 0, 1, []byte{0x44, 0x8A, 0x38}}, - - // Offset of 1 - {x64.RAX, x64.R15, 1, 8, []byte{0x49, 0x8B, 0x47, 0x01}}, - {x64.RAX, x64.R15, 1, 4, []byte{0x41, 0x8B, 0x47, 0x01}}, - {x64.RAX, x64.R15, 1, 2, []byte{0x66, 0x41, 0x8B, 0x47, 0x01}}, - {x64.RAX, x64.R15, 1, 1, []byte{0x41, 0x8A, 0x47, 0x01}}, - {x64.RCX, x64.R14, 1, 8, []byte{0x49, 0x8B, 0x4E, 0x01}}, - {x64.RCX, x64.R14, 1, 4, []byte{0x41, 0x8B, 0x4E, 0x01}}, - {x64.RCX, x64.R14, 1, 2, []byte{0x66, 0x41, 0x8B, 0x4E, 0x01}}, - {x64.RCX, x64.R14, 1, 1, []byte{0x41, 0x8A, 0x4E, 0x01}}, - {x64.RDX, x64.R13, 1, 8, []byte{0x49, 0x8B, 0x55, 0x01}}, - {x64.RDX, x64.R13, 1, 4, []byte{0x41, 0x8B, 0x55, 0x01}}, - {x64.RDX, x64.R13, 1, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x01}}, - {x64.RDX, x64.R13, 1, 1, []byte{0x41, 0x8A, 0x55, 0x01}}, - {x64.RBX, x64.R12, 1, 8, []byte{0x49, 0x8B, 0x5C, 0x24, 0x01}}, - {x64.RBX, x64.R12, 1, 4, []byte{0x41, 0x8B, 0x5C, 0x24, 0x01}}, - {x64.RBX, x64.R12, 1, 2, []byte{0x66, 0x41, 0x8B, 0x5C, 0x24, 0x01}}, - {x64.RBX, x64.R12, 1, 1, []byte{0x41, 0x8A, 0x5C, 0x24, 0x01}}, - {x64.RSP, x64.R11, 1, 8, []byte{0x49, 0x8B, 0x63, 0x01}}, - {x64.RSP, x64.R11, 1, 4, []byte{0x41, 0x8B, 0x63, 0x01}}, - {x64.RSP, x64.R11, 1, 2, []byte{0x66, 0x41, 0x8B, 0x63, 0x01}}, - {x64.RSP, x64.R11, 1, 1, []byte{0x41, 0x8A, 0x63, 0x01}}, - {x64.RBP, x64.R10, 1, 8, []byte{0x49, 0x8B, 0x6A, 0x01}}, - {x64.RBP, x64.R10, 1, 4, []byte{0x41, 0x8B, 0x6A, 0x01}}, - {x64.RBP, x64.R10, 1, 2, []byte{0x66, 0x41, 0x8B, 0x6A, 0x01}}, - {x64.RBP, x64.R10, 1, 1, []byte{0x41, 0x8A, 0x6A, 0x01}}, - {x64.RSI, x64.R9, 1, 8, []byte{0x49, 0x8B, 0x71, 0x01}}, - {x64.RSI, x64.R9, 1, 4, []byte{0x41, 0x8B, 0x71, 0x01}}, - {x64.RSI, x64.R9, 1, 2, []byte{0x66, 0x41, 0x8B, 0x71, 0x01}}, - {x64.RSI, x64.R9, 1, 1, []byte{0x41, 0x8A, 0x71, 0x01}}, - {x64.RDI, x64.R8, 1, 8, []byte{0x49, 0x8B, 0x78, 0x01}}, - {x64.RDI, x64.R8, 1, 4, []byte{0x41, 0x8B, 0x78, 0x01}}, - {x64.RDI, x64.R8, 1, 2, []byte{0x66, 0x41, 0x8B, 0x78, 0x01}}, - {x64.RDI, x64.R8, 1, 1, []byte{0x41, 0x8A, 0x78, 0x01}}, - {x64.R8, x64.RDI, 1, 8, []byte{0x4C, 0x8B, 0x47, 0x01}}, - {x64.R8, x64.RDI, 1, 4, []byte{0x44, 0x8B, 0x47, 0x01}}, - {x64.R8, x64.RDI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x47, 0x01}}, - {x64.R8, x64.RDI, 1, 1, []byte{0x44, 0x8A, 0x47, 0x01}}, - {x64.R9, x64.RSI, 1, 8, []byte{0x4C, 0x8B, 0x4E, 0x01}}, - {x64.R9, x64.RSI, 1, 4, []byte{0x44, 0x8B, 0x4E, 0x01}}, - {x64.R9, x64.RSI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x4E, 0x01}}, - {x64.R9, x64.RSI, 1, 1, []byte{0x44, 0x8A, 0x4E, 0x01}}, - {x64.R10, x64.RBP, 1, 8, []byte{0x4C, 0x8B, 0x55, 0x01}}, - {x64.R10, x64.RBP, 1, 4, []byte{0x44, 0x8B, 0x55, 0x01}}, - {x64.R10, x64.RBP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x01}}, - {x64.R10, x64.RBP, 1, 1, []byte{0x44, 0x8A, 0x55, 0x01}}, - {x64.R11, x64.RSP, 1, 8, []byte{0x4C, 0x8B, 0x5C, 0x24, 0x01}}, - {x64.R11, x64.RSP, 1, 4, []byte{0x44, 0x8B, 0x5C, 0x24, 0x01}}, - {x64.R11, x64.RSP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x5C, 0x24, 0x01}}, - {x64.R11, x64.RSP, 1, 1, []byte{0x44, 0x8A, 0x5C, 0x24, 0x01}}, - {x64.R12, x64.RBX, 1, 8, []byte{0x4C, 0x8B, 0x63, 0x01}}, - {x64.R12, x64.RBX, 1, 4, []byte{0x44, 0x8B, 0x63, 0x01}}, - {x64.R12, x64.RBX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x63, 0x01}}, - {x64.R12, x64.RBX, 1, 1, []byte{0x44, 0x8A, 0x63, 0x01}}, - {x64.R13, x64.RDX, 1, 8, []byte{0x4C, 0x8B, 0x6A, 0x01}}, - {x64.R13, x64.RDX, 1, 4, []byte{0x44, 0x8B, 0x6A, 0x01}}, - {x64.R13, x64.RDX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x6A, 0x01}}, - {x64.R13, x64.RDX, 1, 1, []byte{0x44, 0x8A, 0x6A, 0x01}}, - {x64.R14, x64.RCX, 1, 8, []byte{0x4C, 0x8B, 0x71, 0x01}}, - {x64.R14, x64.RCX, 1, 4, []byte{0x44, 0x8B, 0x71, 0x01}}, - {x64.R14, x64.RCX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x71, 0x01}}, - {x64.R14, x64.RCX, 1, 1, []byte{0x44, 0x8A, 0x71, 0x01}}, - {x64.R15, x64.RAX, 1, 8, []byte{0x4C, 0x8B, 0x78, 0x01}}, - {x64.R15, x64.RAX, 1, 4, []byte{0x44, 0x8B, 0x78, 0x01}}, - {x64.R15, x64.RAX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x78, 0x01}}, - {x64.R15, x64.RAX, 1, 1, []byte{0x44, 0x8A, 0x78, 0x01}}, - } - - for _, pattern := range usagePatterns { - t.Logf("load %dB %s, [%s+%d]", pattern.Length, pattern.Destination, pattern.Source, pattern.Offset) - code := x64.LoadRegister(nil, pattern.Destination, pattern.Offset, pattern.Length, pattern.Source) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Move_test.go b/src/x64/Move_test.go deleted file mode 100644 index 071e05b..0000000 --- a/src/x64/Move_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestMoveRegisterNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - // 32 bits - {x64.RAX, 0x7FFFFFFF, []byte{0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFF, []byte{0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFF, []byte{0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFF, []byte{0xBB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFF, []byte{0xBC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFF, []byte{0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFF, []byte{0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFF, []byte{0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFF, []byte{0x41, 0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFF, []byte{0x41, 0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFF, []byte{0x41, 0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFF, []byte{0x41, 0xBB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFF, []byte{0x41, 0xBC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFF, []byte{0x41, 0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFF, []byte{0x41, 0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFF, []byte{0x41, 0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, - - // 64 bits - {x64.RAX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - - // Negative numbers - {x64.RAX, -1, []byte{0x48, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.RCX, -1, []byte{0x48, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.RDX, -1, []byte{0x48, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.RBX, -1, []byte{0x48, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.RSP, -1, []byte{0x48, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.RBP, -1, []byte{0x48, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.RSI, -1, []byte{0x48, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.RDI, -1, []byte{0x48, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.R8, -1, []byte{0x49, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.R9, -1, []byte{0x49, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.R10, -1, []byte{0x49, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.R11, -1, []byte{0x49, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.R12, -1, []byte{0x49, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.R13, -1, []byte{0x49, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.R14, -1, []byte{0x49, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.R15, -1, []byte{0x49, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}}, - } - - for _, pattern := range usagePatterns { - t.Logf("mov %s, %x", pattern.Register, pattern.Number) - code := x64.MoveRegisterNumber(nil, pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestMoveRegisterRegister(t *testing.T) { - usagePatterns := []struct { - Left cpu.Register - Right cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, []byte{0x4C, 0x89, 0xF8}}, - {x64.RCX, x64.R14, []byte{0x4C, 0x89, 0xF1}}, - {x64.RDX, x64.R13, []byte{0x4C, 0x89, 0xEA}}, - {x64.RBX, x64.R12, []byte{0x4C, 0x89, 0xE3}}, - {x64.RSP, x64.R11, []byte{0x4C, 0x89, 0xDC}}, - {x64.RBP, x64.R10, []byte{0x4C, 0x89, 0xD5}}, - {x64.RSI, x64.R9, []byte{0x4C, 0x89, 0xCE}}, - {x64.RDI, x64.R8, []byte{0x4C, 0x89, 0xC7}}, - {x64.R8, x64.RDI, []byte{0x49, 0x89, 0xF8}}, - {x64.R9, x64.RSI, []byte{0x49, 0x89, 0xF1}}, - {x64.R10, x64.RBP, []byte{0x49, 0x89, 0xEA}}, - {x64.R11, x64.RSP, []byte{0x49, 0x89, 0xE3}}, - {x64.R12, x64.RBX, []byte{0x49, 0x89, 0xDC}}, - {x64.R13, x64.RDX, []byte{0x49, 0x89, 0xD5}}, - {x64.R14, x64.RCX, []byte{0x49, 0x89, 0xCE}}, - {x64.R15, x64.RAX, []byte{0x49, 0x89, 0xC7}}, - } - - for _, pattern := range usagePatterns { - t.Logf("mov %s, %s", pattern.Left, pattern.Right) - code := x64.MoveRegisterRegister(nil, pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Mul_test.go b/src/x64/Mul_test.go deleted file mode 100644 index 03ffae1..0000000 --- a/src/x64/Mul_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestMulRegisterNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0x6B, 0xC0, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0x6B, 0xC9, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0x6B, 0xD2, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0x6B, 0xDB, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0x6B, 0xE4, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0x6B, 0xED, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0x6B, 0xF6, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0x6B, 0xFF, 0x01}}, - {x64.R8, 1, []byte{0x4D, 0x6B, 0xC0, 0x01}}, - {x64.R9, 1, []byte{0x4D, 0x6B, 0xC9, 0x01}}, - {x64.R10, 1, []byte{0x4D, 0x6B, 0xD2, 0x01}}, - {x64.R11, 1, []byte{0x4D, 0x6B, 0xDB, 0x01}}, - {x64.R12, 1, []byte{0x4D, 0x6B, 0xE4, 0x01}}, - {x64.R13, 1, []byte{0x4D, 0x6B, 0xED, 0x01}}, - {x64.R14, 1, []byte{0x4D, 0x6B, 0xF6, 0x01}}, - {x64.R15, 1, []byte{0x4D, 0x6B, 0xFF, 0x01}}, - - {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("mul %s, %x", pattern.Register, pattern.Number) - code := x64.MulRegisterNumber(nil, pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestMulRegisterRegister(t *testing.T) { - usagePatterns := []struct { - Left cpu.Register - Right cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, []byte{0x49, 0x0F, 0xAF, 0xC7}}, - {x64.RCX, x64.R14, []byte{0x49, 0x0F, 0xAF, 0xCE}}, - {x64.RDX, x64.R13, []byte{0x49, 0x0F, 0xAF, 0xD5}}, - {x64.RBX, x64.R12, []byte{0x49, 0x0F, 0xAF, 0xDC}}, - {x64.RSP, x64.R11, []byte{0x49, 0x0F, 0xAF, 0xE3}}, - {x64.RBP, x64.R10, []byte{0x49, 0x0F, 0xAF, 0xEA}}, - {x64.RSI, x64.R9, []byte{0x49, 0x0F, 0xAF, 0xF1}}, - {x64.RDI, x64.R8, []byte{0x49, 0x0F, 0xAF, 0xF8}}, - {x64.R8, x64.RDI, []byte{0x4C, 0x0F, 0xAF, 0xC7}}, - {x64.R9, x64.RSI, []byte{0x4C, 0x0F, 0xAF, 0xCE}}, - {x64.R10, x64.RBP, []byte{0x4C, 0x0F, 0xAF, 0xD5}}, - {x64.R11, x64.RSP, []byte{0x4C, 0x0F, 0xAF, 0xDC}}, - {x64.R12, x64.RBX, []byte{0x4C, 0x0F, 0xAF, 0xE3}}, - {x64.R13, x64.RDX, []byte{0x4C, 0x0F, 0xAF, 0xEA}}, - {x64.R14, x64.RCX, []byte{0x4C, 0x0F, 0xAF, 0xF1}}, - {x64.R15, x64.RAX, []byte{0x4C, 0x0F, 0xAF, 0xF8}}, - } - - for _, pattern := range usagePatterns { - t.Logf("mul %s, %s", pattern.Left, pattern.Right) - code := x64.MulRegisterRegister(nil, pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Negate_test.go b/src/x64/Negate_test.go deleted file mode 100644 index 625e601..0000000 --- a/src/x64/Negate_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestNegateRegister(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Code []byte - }{ - {x64.RAX, []byte{0x48, 0xF7, 0xD8}}, - {x64.RCX, []byte{0x48, 0xF7, 0xD9}}, - {x64.RDX, []byte{0x48, 0xF7, 0xDA}}, - {x64.RBX, []byte{0x48, 0xF7, 0xDB}}, - {x64.RSP, []byte{0x48, 0xF7, 0xDC}}, - {x64.RBP, []byte{0x48, 0xF7, 0xDD}}, - {x64.RSI, []byte{0x48, 0xF7, 0xDE}}, - {x64.RDI, []byte{0x48, 0xF7, 0xDF}}, - {x64.R8, []byte{0x49, 0xF7, 0xD8}}, - {x64.R9, []byte{0x49, 0xF7, 0xD9}}, - {x64.R10, []byte{0x49, 0xF7, 0xDA}}, - {x64.R11, []byte{0x49, 0xF7, 0xDB}}, - {x64.R12, []byte{0x49, 0xF7, 0xDC}}, - {x64.R13, []byte{0x49, 0xF7, 0xDD}}, - {x64.R14, []byte{0x49, 0xF7, 0xDE}}, - {x64.R15, []byte{0x49, 0xF7, 0xDF}}, - } - - for _, pattern := range usagePatterns { - t.Logf("neg %s", pattern.Register) - code := x64.NegateRegister(nil, pattern.Register) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Or_test.go b/src/x64/Or_test.go deleted file mode 100644 index d85065a..0000000 --- a/src/x64/Or_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestOrRegisterNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0x83, 0xC8, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0x83, 0xC9, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0x83, 0xCA, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0x83, 0xCB, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0x83, 0xCC, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0x83, 0xCD, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0x83, 0xCE, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0x83, 0xCF, 0x01}}, - {x64.R8, 1, []byte{0x49, 0x83, 0xC8, 0x01}}, - {x64.R9, 1, []byte{0x49, 0x83, 0xC9, 0x01}}, - {x64.R10, 1, []byte{0x49, 0x83, 0xCA, 0x01}}, - {x64.R11, 1, []byte{0x49, 0x83, 0xCB, 0x01}}, - {x64.R12, 1, []byte{0x49, 0x83, 0xCC, 0x01}}, - {x64.R13, 1, []byte{0x49, 0x83, 0xCD, 0x01}}, - {x64.R14, 1, []byte{0x49, 0x83, 0xCE, 0x01}}, - {x64.R15, 1, []byte{0x49, 0x83, 0xCF, 0x01}}, - - {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCF, 0xFF, 0xFF, 0xFF, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("or %s, %x", pattern.Register, pattern.Number) - code := x64.OrRegisterNumber(nil, pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestOrRegisterRegister(t *testing.T) { - usagePatterns := []struct { - Left cpu.Register - Right cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, []byte{0x4C, 0x09, 0xF8}}, - {x64.RCX, x64.R14, []byte{0x4C, 0x09, 0xF1}}, - {x64.RDX, x64.R13, []byte{0x4C, 0x09, 0xEA}}, - {x64.RBX, x64.R12, []byte{0x4C, 0x09, 0xE3}}, - {x64.RSP, x64.R11, []byte{0x4C, 0x09, 0xDC}}, - {x64.RBP, x64.R10, []byte{0x4C, 0x09, 0xD5}}, - {x64.RSI, x64.R9, []byte{0x4C, 0x09, 0xCE}}, - {x64.RDI, x64.R8, []byte{0x4C, 0x09, 0xC7}}, - {x64.R8, x64.RDI, []byte{0x49, 0x09, 0xF8}}, - {x64.R9, x64.RSI, []byte{0x49, 0x09, 0xF1}}, - {x64.R10, x64.RBP, []byte{0x49, 0x09, 0xEA}}, - {x64.R11, x64.RSP, []byte{0x49, 0x09, 0xE3}}, - {x64.R12, x64.RBX, []byte{0x49, 0x09, 0xDC}}, - {x64.R13, x64.RDX, []byte{0x49, 0x09, 0xD5}}, - {x64.R14, x64.RCX, []byte{0x49, 0x09, 0xCE}}, - {x64.R15, x64.RAX, []byte{0x49, 0x09, 0xC7}}, - } - - for _, pattern := range usagePatterns { - t.Logf("or %s, %s", pattern.Left, pattern.Right) - code := x64.OrRegisterRegister(nil, pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Pop_test.go b/src/x64/Pop_test.go deleted file mode 100644 index 446eebe..0000000 --- a/src/x64/Pop_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestPopRegister(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Code []byte - }{ - {x64.RAX, []byte{0x58}}, - {x64.RCX, []byte{0x59}}, - {x64.RDX, []byte{0x5A}}, - {x64.RBX, []byte{0x5B}}, - {x64.RSP, []byte{0x5C}}, - {x64.RBP, []byte{0x5D}}, - {x64.RSI, []byte{0x5E}}, - {x64.RDI, []byte{0x5F}}, - {x64.R8, []byte{0x41, 0x58}}, - {x64.R9, []byte{0x41, 0x59}}, - {x64.R10, []byte{0x41, 0x5A}}, - {x64.R11, []byte{0x41, 0x5B}}, - {x64.R12, []byte{0x41, 0x5C}}, - {x64.R13, []byte{0x41, 0x5D}}, - {x64.R14, []byte{0x41, 0x5E}}, - {x64.R15, []byte{0x41, 0x5F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("pop %s", pattern.Register) - code := x64.PopRegister(nil, pattern.Register) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Push_test.go b/src/x64/Push_test.go deleted file mode 100644 index a49095d..0000000 --- a/src/x64/Push_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestPushRegister(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Code []byte - }{ - {x64.RAX, []byte{0x50}}, - {x64.RCX, []byte{0x51}}, - {x64.RDX, []byte{0x52}}, - {x64.RBX, []byte{0x53}}, - {x64.RSP, []byte{0x54}}, - {x64.RBP, []byte{0x55}}, - {x64.RSI, []byte{0x56}}, - {x64.RDI, []byte{0x57}}, - {x64.R8, []byte{0x41, 0x50}}, - {x64.R9, []byte{0x41, 0x51}}, - {x64.R10, []byte{0x41, 0x52}}, - {x64.R11, []byte{0x41, 0x53}}, - {x64.R12, []byte{0x41, 0x54}}, - {x64.R13, []byte{0x41, 0x55}}, - {x64.R14, []byte{0x41, 0x56}}, - {x64.R15, []byte{0x41, 0x57}}, - } - - for _, pattern := range usagePatterns { - t.Logf("push %s", pattern.Register) - code := x64.PushRegister(nil, pattern.Register) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Registers_test.go b/src/x64/Registers_test.go deleted file mode 100644 index 626bb84..0000000 --- a/src/x64/Registers_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestRegisters(t *testing.T) { - assert.NotContains(t, x64.GeneralRegisters, x64.RSP) -} diff --git a/src/x64/Shift_test.go b/src/x64/Shift_test.go deleted file mode 100644 index dc95cf2..0000000 --- a/src/x64/Shift_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestShiftLeftNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0xC1, 0xE0, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0xC1, 0xE1, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0xC1, 0xE2, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0xC1, 0xE3, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0xC1, 0xE4, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0xC1, 0xE5, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0xC1, 0xE6, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0xC1, 0xE7, 0x01}}, - {x64.R8, 1, []byte{0x49, 0xC1, 0xE0, 0x01}}, - {x64.R9, 1, []byte{0x49, 0xC1, 0xE1, 0x01}}, - {x64.R10, 1, []byte{0x49, 0xC1, 0xE2, 0x01}}, - {x64.R11, 1, []byte{0x49, 0xC1, 0xE3, 0x01}}, - {x64.R12, 1, []byte{0x49, 0xC1, 0xE4, 0x01}}, - {x64.R13, 1, []byte{0x49, 0xC1, 0xE5, 0x01}}, - {x64.R14, 1, []byte{0x49, 0xC1, 0xE6, 0x01}}, - {x64.R15, 1, []byte{0x49, 0xC1, 0xE7, 0x01}}, - } - - for _, pattern := range usagePatterns { - t.Logf("shl %s, %x", pattern.Register, pattern.Number) - code := x64.ShiftLeftNumber(nil, pattern.Register, byte(pattern.Number)) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestShiftRightSignedNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0xC1, 0xF8, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0xC1, 0xF9, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0xC1, 0xFA, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0xC1, 0xFB, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0xC1, 0xFC, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0xC1, 0xFD, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0xC1, 0xFE, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0xC1, 0xFF, 0x01}}, - {x64.R8, 1, []byte{0x49, 0xC1, 0xF8, 0x01}}, - {x64.R9, 1, []byte{0x49, 0xC1, 0xF9, 0x01}}, - {x64.R10, 1, []byte{0x49, 0xC1, 0xFA, 0x01}}, - {x64.R11, 1, []byte{0x49, 0xC1, 0xFB, 0x01}}, - {x64.R12, 1, []byte{0x49, 0xC1, 0xFC, 0x01}}, - {x64.R13, 1, []byte{0x49, 0xC1, 0xFD, 0x01}}, - {x64.R14, 1, []byte{0x49, 0xC1, 0xFE, 0x01}}, - {x64.R15, 1, []byte{0x49, 0xC1, 0xFF, 0x01}}, - } - - for _, pattern := range usagePatterns { - t.Logf("sar %s, %x", pattern.Register, pattern.Number) - code := x64.ShiftRightSignedNumber(nil, pattern.Register, byte(pattern.Number)) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/StoreDynamic_test.go b/src/x64/StoreDynamic_test.go deleted file mode 100644 index 0ff1f82..0000000 --- a/src/x64/StoreDynamic_test.go +++ /dev/null @@ -1,171 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestStoreDynamicNumber(t *testing.T) { - usagePatterns := []struct { - RegisterTo cpu.Register - Offset cpu.Register - Length byte - Number int - Code []byte - }{ - {x64.RAX, x64.R15, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RAX, x64.R15, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RAX, x64.R15, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, - {x64.RAX, x64.R15, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x38, 0x7F}}, - {x64.RCX, x64.R14, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RCX, x64.R14, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RCX, x64.R14, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, - {x64.RCX, x64.R14, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x31, 0x7F}}, - {x64.RDX, x64.R13, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDX, x64.R13, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDX, x64.R13, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, - {x64.RDX, x64.R13, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x2A, 0x7F}}, - {x64.RBX, x64.R12, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBX, x64.R12, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBX, x64.R12, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00}}, - {x64.RBX, x64.R12, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x23, 0x7F}}, - {x64.RSP, x64.R11, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSP, x64.R11, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSP, x64.R11, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, - {x64.RSP, x64.R11, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, - {x64.RBP, x64.R10, 8, 0x7F, []byte{0x4A, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBP, x64.R10, 4, 0x7F, []byte{0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBP, x64.R10, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, - {x64.RBP, x64.R10, 1, 0x7F, []byte{0x42, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, - {x64.RSI, x64.R9, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSI, x64.R9, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSI, x64.R9, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, - {x64.RSI, x64.R9, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x0E, 0x7F}}, - {x64.RDI, x64.R8, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDI, x64.R8, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDI, x64.R8, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, - {x64.RDI, x64.R8, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x07, 0x7F}}, - {x64.R8, x64.RDI, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R8, x64.RDI, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R8, x64.RDI, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, - {x64.R8, x64.RDI, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x38, 0x7F}}, - {x64.R9, x64.RSI, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R9, x64.RSI, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R9, x64.RSI, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, - {x64.R9, x64.RSI, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x31, 0x7F}}, - {x64.R10, x64.RBP, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R10, x64.RBP, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R10, x64.RBP, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, - {x64.R10, x64.RBP, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x2A, 0x7F}}, - {x64.R11, x64.RSP, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R11, x64.RSP, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R11, x64.RSP, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, - {x64.R11, x64.RSP, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, - {x64.R12, x64.RBX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R12, x64.RBX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R12, x64.RBX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, - {x64.R12, x64.RBX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x1C, 0x7F}}, - {x64.R13, x64.RDX, 8, 0x7F, []byte{0x49, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R13, x64.RDX, 4, 0x7F, []byte{0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R13, x64.RDX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, - {x64.R13, x64.RDX, 1, 0x7F, []byte{0x41, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, - {x64.R14, x64.RCX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R14, x64.RCX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R14, x64.RCX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, - {x64.R14, x64.RCX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x0E, 0x7F}}, - {x64.R15, x64.RAX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R15, x64.RAX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R15, x64.RAX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, - {x64.R15, x64.RAX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x07, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%s], %d", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.Number) - code := x64.StoreDynamicNumber(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestStoreDynamicRegister(t *testing.T) { - usagePatterns := []struct { - RegisterTo cpu.Register - Offset cpu.Register - Length byte - RegisterFrom cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, 8, x64.R15, []byte{0x4E, 0x89, 0x3C, 0x38}}, - {x64.RAX, x64.R15, 4, x64.R15, []byte{0x46, 0x89, 0x3C, 0x38}}, - {x64.RAX, x64.R15, 2, x64.R15, []byte{0x66, 0x46, 0x89, 0x3C, 0x38}}, - {x64.RAX, x64.R15, 1, x64.R15, []byte{0x46, 0x88, 0x3C, 0x38}}, - {x64.RCX, x64.R14, 8, x64.R14, []byte{0x4E, 0x89, 0x34, 0x31}}, - {x64.RCX, x64.R14, 4, x64.R14, []byte{0x46, 0x89, 0x34, 0x31}}, - {x64.RCX, x64.R14, 2, x64.R14, []byte{0x66, 0x46, 0x89, 0x34, 0x31}}, - {x64.RCX, x64.R14, 1, x64.R14, []byte{0x46, 0x88, 0x34, 0x31}}, - {x64.RDX, x64.R13, 8, x64.R13, []byte{0x4E, 0x89, 0x2C, 0x2A}}, - {x64.RDX, x64.R13, 4, x64.R13, []byte{0x46, 0x89, 0x2C, 0x2A}}, - {x64.RDX, x64.R13, 2, x64.R13, []byte{0x66, 0x46, 0x89, 0x2C, 0x2A}}, - {x64.RDX, x64.R13, 1, x64.R13, []byte{0x46, 0x88, 0x2C, 0x2A}}, - {x64.RBX, x64.R12, 8, x64.R12, []byte{0x4E, 0x89, 0x24, 0x23}}, - {x64.RBX, x64.R12, 4, x64.R12, []byte{0x46, 0x89, 0x24, 0x23}}, - {x64.RBX, x64.R12, 2, x64.R12, []byte{0x66, 0x46, 0x89, 0x24, 0x23}}, - {x64.RBX, x64.R12, 1, x64.R12, []byte{0x46, 0x88, 0x24, 0x23}}, - {x64.RSP, x64.R11, 8, x64.R11, []byte{0x4E, 0x89, 0x1C, 0x1C}}, - {x64.RSP, x64.R11, 4, x64.R11, []byte{0x46, 0x89, 0x1C, 0x1C}}, - {x64.RSP, x64.R11, 2, x64.R11, []byte{0x66, 0x46, 0x89, 0x1C, 0x1C}}, - {x64.RSP, x64.R11, 1, x64.R11, []byte{0x46, 0x88, 0x1C, 0x1C}}, - {x64.RBP, x64.R10, 8, x64.R10, []byte{0x4E, 0x89, 0x54, 0x15, 0x00}}, - {x64.RBP, x64.R10, 4, x64.R10, []byte{0x46, 0x89, 0x54, 0x15, 0x00}}, - {x64.RBP, x64.R10, 2, x64.R10, []byte{0x66, 0x46, 0x89, 0x54, 0x15, 0x00}}, - {x64.RBP, x64.R10, 1, x64.R10, []byte{0x46, 0x88, 0x54, 0x15, 0x00}}, - {x64.RSI, x64.R9, 8, x64.R9, []byte{0x4E, 0x89, 0x0C, 0x0E}}, - {x64.RSI, x64.R9, 4, x64.R9, []byte{0x46, 0x89, 0x0C, 0x0E}}, - {x64.RSI, x64.R9, 2, x64.R9, []byte{0x66, 0x46, 0x89, 0x0C, 0x0E}}, - {x64.RSI, x64.R9, 1, x64.R9, []byte{0x46, 0x88, 0x0C, 0x0E}}, - {x64.RDI, x64.R8, 8, x64.R8, []byte{0x4E, 0x89, 0x04, 0x07}}, - {x64.RDI, x64.R8, 4, x64.R8, []byte{0x46, 0x89, 0x04, 0x07}}, - {x64.RDI, x64.R8, 2, x64.R8, []byte{0x66, 0x46, 0x89, 0x04, 0x07}}, - {x64.RDI, x64.R8, 1, x64.R8, []byte{0x46, 0x88, 0x04, 0x07}}, - {x64.R8, x64.RDI, 8, x64.RDI, []byte{0x49, 0x89, 0x3C, 0x38}}, - {x64.R8, x64.RDI, 4, x64.RDI, []byte{0x41, 0x89, 0x3C, 0x38}}, - {x64.R8, x64.RDI, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x3C, 0x38}}, - {x64.R8, x64.RDI, 1, x64.RDI, []byte{0x41, 0x88, 0x3C, 0x38}}, - {x64.R9, x64.RSI, 8, x64.RSI, []byte{0x49, 0x89, 0x34, 0x31}}, - {x64.R9, x64.RSI, 4, x64.RSI, []byte{0x41, 0x89, 0x34, 0x31}}, - {x64.R9, x64.RSI, 2, x64.RSI, []byte{0x66, 0x41, 0x89, 0x34, 0x31}}, - {x64.R9, x64.RSI, 1, x64.RSI, []byte{0x41, 0x88, 0x34, 0x31}}, - {x64.R10, x64.RBP, 8, x64.RBP, []byte{0x49, 0x89, 0x2C, 0x2A}}, - {x64.R10, x64.RBP, 4, x64.RBP, []byte{0x41, 0x89, 0x2C, 0x2A}}, - {x64.R10, x64.RBP, 2, x64.RBP, []byte{0x66, 0x41, 0x89, 0x2C, 0x2A}}, - {x64.R10, x64.RBP, 1, x64.RBP, []byte{0x41, 0x88, 0x2C, 0x2A}}, - {x64.R11, x64.RSP, 8, x64.RSP, []byte{0x4A, 0x89, 0x24, 0x1C}}, - {x64.R11, x64.RSP, 4, x64.RSP, []byte{0x42, 0x89, 0x24, 0x1C}}, - {x64.R11, x64.RSP, 2, x64.RSP, []byte{0x66, 0x42, 0x89, 0x24, 0x1C}}, - {x64.R11, x64.RSP, 1, x64.RSP, []byte{0x42, 0x88, 0x24, 0x1C}}, - {x64.R12, x64.RBX, 8, x64.RBX, []byte{0x49, 0x89, 0x1C, 0x1C}}, - {x64.R12, x64.RBX, 4, x64.RBX, []byte{0x41, 0x89, 0x1C, 0x1C}}, - {x64.R12, x64.RBX, 2, x64.RBX, []byte{0x66, 0x41, 0x89, 0x1C, 0x1C}}, - {x64.R12, x64.RBX, 1, x64.RBX, []byte{0x41, 0x88, 0x1C, 0x1C}}, - {x64.R13, x64.RDX, 8, x64.RDX, []byte{0x49, 0x89, 0x54, 0x15, 0x00}}, - {x64.R13, x64.RDX, 4, x64.RDX, []byte{0x41, 0x89, 0x54, 0x15, 0x00}}, - {x64.R13, x64.RDX, 2, x64.RDX, []byte{0x66, 0x41, 0x89, 0x54, 0x15, 0x00}}, - {x64.R13, x64.RDX, 1, x64.RDX, []byte{0x41, 0x88, 0x54, 0x15, 0x00}}, - {x64.R14, x64.RCX, 8, x64.RCX, []byte{0x49, 0x89, 0x0C, 0x0E}}, - {x64.R14, x64.RCX, 4, x64.RCX, []byte{0x41, 0x89, 0x0C, 0x0E}}, - {x64.R14, x64.RCX, 2, x64.RCX, []byte{0x66, 0x41, 0x89, 0x0C, 0x0E}}, - {x64.R14, x64.RCX, 1, x64.RCX, []byte{0x41, 0x88, 0x0C, 0x0E}}, - {x64.R15, x64.RAX, 8, x64.RAX, []byte{0x49, 0x89, 0x04, 0x07}}, - {x64.R15, x64.RAX, 4, x64.RAX, []byte{0x41, 0x89, 0x04, 0x07}}, - {x64.R15, x64.RAX, 2, x64.RAX, []byte{0x66, 0x41, 0x89, 0x04, 0x07}}, - {x64.R15, x64.RAX, 1, x64.RAX, []byte{0x41, 0x88, 0x04, 0x07}}, - } - - for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%s], %s", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) - code := x64.StoreDynamicRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Store_test.go b/src/x64/Store_test.go deleted file mode 100644 index 3f4bd05..0000000 --- a/src/x64/Store_test.go +++ /dev/null @@ -1,305 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestStoreNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Offset byte - Length byte - Number int - Code []byte - }{ - // No offset - {x64.RAX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RAX, 0, 4, 0x7F, []byte{0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RAX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x00, 0x7F, 0x00}}, - {x64.RAX, 0, 1, 0x7F, []byte{0xC6, 0x00, 0x7F}}, - {x64.RCX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RCX, 0, 4, 0x7F, []byte{0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RCX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x01, 0x7F, 0x00}}, - {x64.RCX, 0, 1, 0x7F, []byte{0xC6, 0x01, 0x7F}}, - {x64.RDX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDX, 0, 4, 0x7F, []byte{0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x02, 0x7F, 0x00}}, - {x64.RDX, 0, 1, 0x7F, []byte{0xC6, 0x02, 0x7F}}, - {x64.RBX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBX, 0, 4, 0x7F, []byte{0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x03, 0x7F, 0x00}}, - {x64.RBX, 0, 1, 0x7F, []byte{0xC6, 0x03, 0x7F}}, - {x64.RSP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSP, 0, 4, 0x7F, []byte{0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x04, 0x24, 0x7F, 0x00}}, - {x64.RSP, 0, 1, 0x7F, []byte{0xC6, 0x04, 0x24, 0x7F}}, - {x64.RBP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBP, 0, 4, 0x7F, []byte{0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x00, 0x7F, 0x00}}, - {x64.RBP, 0, 1, 0x7F, []byte{0xC6, 0x45, 0x00, 0x7F}}, - {x64.RSI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSI, 0, 4, 0x7F, []byte{0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x06, 0x7F, 0x00}}, - {x64.RSI, 0, 1, 0x7F, []byte{0xC6, 0x06, 0x7F}}, - {x64.RDI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDI, 0, 4, 0x7F, []byte{0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x07, 0x7F, 0x00}}, - {x64.RDI, 0, 1, 0x7F, []byte{0xC6, 0x07, 0x7F}}, - {x64.R8, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R8, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R8, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x00, 0x7F, 0x00}}, - {x64.R8, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x00, 0x7F}}, - {x64.R9, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R9, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R9, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x01, 0x7F, 0x00}}, - {x64.R9, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x01, 0x7F}}, - {x64.R10, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R10, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R10, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x02, 0x7F, 0x00}}, - {x64.R10, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x02, 0x7F}}, - {x64.R11, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R11, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R11, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x03, 0x7F, 0x00}}, - {x64.R11, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x03, 0x7F}}, - {x64.R12, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R12, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R12, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x24, 0x7F, 0x00}}, - {x64.R12, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x24, 0x7F}}, - {x64.R13, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R13, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R13, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x45, 0x00, 0x7F, 0x00}}, - {x64.R13, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x45, 0x00, 0x7F}}, - {x64.R14, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R14, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R14, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x06, 0x7F, 0x00}}, - {x64.R14, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x06, 0x7F}}, - {x64.R15, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R15, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R15, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x07, 0x7F, 0x00}}, - {x64.R15, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x07, 0x7F}}, - - // Offset of 1 - {x64.RAX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RAX, 1, 4, 0x7F, []byte{0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RAX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, - {x64.RAX, 1, 1, 0x7F, []byte{0xC6, 0x40, 0x01, 0x7F}}, - {x64.RCX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RCX, 1, 4, 0x7F, []byte{0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RCX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x41, 0x01, 0x7F, 0x00}}, - {x64.RCX, 1, 1, 0x7F, []byte{0xC6, 0x41, 0x01, 0x7F}}, - {x64.RDX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDX, 1, 4, 0x7F, []byte{0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x42, 0x01, 0x7F, 0x00}}, - {x64.RDX, 1, 1, 0x7F, []byte{0xC6, 0x42, 0x01, 0x7F}}, - {x64.RBX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBX, 1, 4, 0x7F, []byte{0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, - {x64.RBX, 1, 1, 0x7F, []byte{0xC6, 0x43, 0x01, 0x7F}}, - {x64.RSP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSP, 1, 4, 0x7F, []byte{0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, - {x64.RSP, 1, 1, 0x7F, []byte{0xC6, 0x44, 0x24, 0x01, 0x7F}}, - {x64.RBP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBP, 1, 4, 0x7F, []byte{0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, - {x64.RBP, 1, 1, 0x7F, []byte{0xC6, 0x45, 0x01, 0x7F}}, - {x64.RSI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSI, 1, 4, 0x7F, []byte{0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, - {x64.RSI, 1, 1, 0x7F, []byte{0xC6, 0x46, 0x01, 0x7F}}, - {x64.RDI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDI, 1, 4, 0x7F, []byte{0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, - {x64.RDI, 1, 1, 0x7F, []byte{0xC6, 0x47, 0x01, 0x7F}}, - {x64.R8, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R8, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R8, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, - {x64.R8, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x40, 0x01, 0x7F}}, - {x64.R9, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R9, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R9, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x41, 0x01, 0x7F, 0x00}}, - {x64.R9, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x41, 0x01, 0x7F}}, - {x64.R10, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R10, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R10, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x42, 0x01, 0x7F, 0x00}}, - {x64.R10, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x42, 0x01, 0x7F}}, - {x64.R11, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R11, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R11, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, - {x64.R11, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x43, 0x01, 0x7F}}, - {x64.R12, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R12, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R12, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, - {x64.R12, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x44, 0x24, 0x01, 0x7F}}, - {x64.R13, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R13, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R13, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, - {x64.R13, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x45, 0x01, 0x7F}}, - {x64.R14, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R14, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R14, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, - {x64.R14, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x46, 0x01, 0x7F}}, - {x64.R15, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R15, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R15, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, - {x64.R15, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x47, 0x01, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%d], %d", pattern.Length, pattern.Register, pattern.Offset, pattern.Number) - code := x64.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.Length, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestStoreRegister(t *testing.T) { - usagePatterns := []struct { - RegisterTo cpu.Register - Offset byte - Length byte - RegisterFrom cpu.Register - Code []byte - }{ - // No offset - {x64.RAX, 0, 8, x64.R15, []byte{0x4C, 0x89, 0x38}}, - {x64.RAX, 0, 4, x64.R15, []byte{0x44, 0x89, 0x38}}, - {x64.RAX, 0, 2, x64.R15, []byte{0x66, 0x44, 0x89, 0x38}}, - {x64.RAX, 0, 1, x64.R15, []byte{0x44, 0x88, 0x38}}, - {x64.RCX, 0, 8, x64.R14, []byte{0x4C, 0x89, 0x31}}, - {x64.RCX, 0, 4, x64.R14, []byte{0x44, 0x89, 0x31}}, - {x64.RCX, 0, 2, x64.R14, []byte{0x66, 0x44, 0x89, 0x31}}, - {x64.RCX, 0, 1, x64.R14, []byte{0x44, 0x88, 0x31}}, - {x64.RDX, 0, 8, x64.R13, []byte{0x4C, 0x89, 0x2A}}, - {x64.RDX, 0, 4, x64.R13, []byte{0x44, 0x89, 0x2A}}, - {x64.RDX, 0, 2, x64.R13, []byte{0x66, 0x44, 0x89, 0x2A}}, - {x64.RDX, 0, 1, x64.R13, []byte{0x44, 0x88, 0x2A}}, - {x64.RBX, 0, 8, x64.R12, []byte{0x4C, 0x89, 0x23}}, - {x64.RBX, 0, 4, x64.R12, []byte{0x44, 0x89, 0x23}}, - {x64.RBX, 0, 2, x64.R12, []byte{0x66, 0x44, 0x89, 0x23}}, - {x64.RBX, 0, 1, x64.R12, []byte{0x44, 0x88, 0x23}}, - {x64.RSP, 0, 8, x64.R11, []byte{0x4C, 0x89, 0x1C, 0x24}}, - {x64.RSP, 0, 4, x64.R11, []byte{0x44, 0x89, 0x1C, 0x24}}, - {x64.RSP, 0, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x1C, 0x24}}, - {x64.RSP, 0, 1, x64.R11, []byte{0x44, 0x88, 0x1C, 0x24}}, - {x64.RBP, 0, 8, x64.R10, []byte{0x4C, 0x89, 0x55, 0x00}}, - {x64.RBP, 0, 4, x64.R10, []byte{0x44, 0x89, 0x55, 0x00}}, - {x64.RBP, 0, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x00}}, - {x64.RBP, 0, 1, x64.R10, []byte{0x44, 0x88, 0x55, 0x00}}, - {x64.RSI, 0, 8, x64.R9, []byte{0x4C, 0x89, 0x0E}}, - {x64.RSI, 0, 4, x64.R9, []byte{0x44, 0x89, 0x0E}}, - {x64.RSI, 0, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x0E}}, - {x64.RSI, 0, 1, x64.R9, []byte{0x44, 0x88, 0x0E}}, - {x64.RDI, 0, 8, x64.R8, []byte{0x4C, 0x89, 0x07}}, - {x64.RDI, 0, 4, x64.R8, []byte{0x44, 0x89, 0x07}}, - {x64.RDI, 0, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x07}}, - {x64.RDI, 0, 1, x64.R8, []byte{0x44, 0x88, 0x07}}, - {x64.R8, 0, 8, x64.RDI, []byte{0x49, 0x89, 0x38}}, - {x64.R8, 0, 4, x64.RDI, []byte{0x41, 0x89, 0x38}}, - {x64.R8, 0, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x38}}, - {x64.R8, 0, 1, x64.RDI, []byte{0x41, 0x88, 0x38}}, - {x64.R9, 0, 8, x64.RSI, []byte{0x49, 0x89, 0x31}}, - {x64.R9, 0, 4, x64.RSI, []byte{0x41, 0x89, 0x31}}, - {x64.R9, 0, 2, x64.RSI, []byte{0x66, 0x41, 0x89, 0x31}}, - {x64.R9, 0, 1, x64.RSI, []byte{0x41, 0x88, 0x31}}, - {x64.R10, 0, 8, x64.RBP, []byte{0x49, 0x89, 0x2A}}, - {x64.R10, 0, 4, x64.RBP, []byte{0x41, 0x89, 0x2A}}, - {x64.R10, 0, 2, x64.RBP, []byte{0x66, 0x41, 0x89, 0x2A}}, - {x64.R10, 0, 1, x64.RBP, []byte{0x41, 0x88, 0x2A}}, - {x64.R11, 0, 8, x64.RSP, []byte{0x49, 0x89, 0x23}}, - {x64.R11, 0, 4, x64.RSP, []byte{0x41, 0x89, 0x23}}, - {x64.R11, 0, 2, x64.RSP, []byte{0x66, 0x41, 0x89, 0x23}}, - {x64.R11, 0, 1, x64.RSP, []byte{0x41, 0x88, 0x23}}, - {x64.R12, 0, 8, x64.RBX, []byte{0x49, 0x89, 0x1C, 0x24}}, - {x64.R12, 0, 4, x64.RBX, []byte{0x41, 0x89, 0x1C, 0x24}}, - {x64.R12, 0, 2, x64.RBX, []byte{0x66, 0x41, 0x89, 0x1C, 0x24}}, - {x64.R12, 0, 1, x64.RBX, []byte{0x41, 0x88, 0x1C, 0x24}}, - {x64.R13, 0, 8, x64.RDX, []byte{0x49, 0x89, 0x55, 0x00}}, - {x64.R13, 0, 4, x64.RDX, []byte{0x41, 0x89, 0x55, 0x00}}, - {x64.R13, 0, 2, x64.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x00}}, - {x64.R13, 0, 1, x64.RDX, []byte{0x41, 0x88, 0x55, 0x00}}, - {x64.R14, 0, 8, x64.RCX, []byte{0x49, 0x89, 0x0E}}, - {x64.R14, 0, 4, x64.RCX, []byte{0x41, 0x89, 0x0E}}, - {x64.R14, 0, 2, x64.RCX, []byte{0x66, 0x41, 0x89, 0x0E}}, - {x64.R14, 0, 1, x64.RCX, []byte{0x41, 0x88, 0x0E}}, - {x64.R15, 0, 8, x64.RAX, []byte{0x49, 0x89, 0x07}}, - {x64.R15, 0, 4, x64.RAX, []byte{0x41, 0x89, 0x07}}, - {x64.R15, 0, 2, x64.RAX, []byte{0x66, 0x41, 0x89, 0x07}}, - {x64.R15, 0, 1, x64.RAX, []byte{0x41, 0x88, 0x07}}, - - // Offset of 1 - {x64.RAX, 1, 8, x64.R15, []byte{0x4C, 0x89, 0x78, 0x01}}, - {x64.RAX, 1, 4, x64.R15, []byte{0x44, 0x89, 0x78, 0x01}}, - {x64.RAX, 1, 2, x64.R15, []byte{0x66, 0x44, 0x89, 0x78, 0x01}}, - {x64.RAX, 1, 1, x64.R15, []byte{0x44, 0x88, 0x78, 0x01}}, - {x64.RCX, 1, 8, x64.R14, []byte{0x4C, 0x89, 0x71, 0x01}}, - {x64.RCX, 1, 4, x64.R14, []byte{0x44, 0x89, 0x71, 0x01}}, - {x64.RCX, 1, 2, x64.R14, []byte{0x66, 0x44, 0x89, 0x71, 0x01}}, - {x64.RCX, 1, 1, x64.R14, []byte{0x44, 0x88, 0x71, 0x01}}, - {x64.RDX, 1, 8, x64.R13, []byte{0x4C, 0x89, 0x6A, 0x01}}, - {x64.RDX, 1, 4, x64.R13, []byte{0x44, 0x89, 0x6A, 0x01}}, - {x64.RDX, 1, 2, x64.R13, []byte{0x66, 0x44, 0x89, 0x6A, 0x01}}, - {x64.RDX, 1, 1, x64.R13, []byte{0x44, 0x88, 0x6A, 0x01}}, - {x64.RBX, 1, 8, x64.R12, []byte{0x4C, 0x89, 0x63, 0x01}}, - {x64.RBX, 1, 4, x64.R12, []byte{0x44, 0x89, 0x63, 0x01}}, - {x64.RBX, 1, 2, x64.R12, []byte{0x66, 0x44, 0x89, 0x63, 0x01}}, - {x64.RBX, 1, 1, x64.R12, []byte{0x44, 0x88, 0x63, 0x01}}, - {x64.RSP, 1, 8, x64.R11, []byte{0x4C, 0x89, 0x5C, 0x24, 0x01}}, - {x64.RSP, 1, 4, x64.R11, []byte{0x44, 0x89, 0x5C, 0x24, 0x01}}, - {x64.RSP, 1, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x5C, 0x24, 0x01}}, - {x64.RSP, 1, 1, x64.R11, []byte{0x44, 0x88, 0x5C, 0x24, 0x01}}, - {x64.RBP, 1, 8, x64.R10, []byte{0x4C, 0x89, 0x55, 0x01}}, - {x64.RBP, 1, 4, x64.R10, []byte{0x44, 0x89, 0x55, 0x01}}, - {x64.RBP, 1, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x01}}, - {x64.RBP, 1, 1, x64.R10, []byte{0x44, 0x88, 0x55, 0x01}}, - {x64.RSI, 1, 8, x64.R9, []byte{0x4C, 0x89, 0x4E, 0x01}}, - {x64.RSI, 1, 4, x64.R9, []byte{0x44, 0x89, 0x4E, 0x01}}, - {x64.RSI, 1, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x4E, 0x01}}, - {x64.RSI, 1, 1, x64.R9, []byte{0x44, 0x88, 0x4E, 0x01}}, - {x64.RDI, 1, 8, x64.R8, []byte{0x4C, 0x89, 0x47, 0x01}}, - {x64.RDI, 1, 4, x64.R8, []byte{0x44, 0x89, 0x47, 0x01}}, - {x64.RDI, 1, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x47, 0x01}}, - {x64.RDI, 1, 1, x64.R8, []byte{0x44, 0x88, 0x47, 0x01}}, - {x64.R8, 1, 8, x64.RDI, []byte{0x49, 0x89, 0x78, 0x01}}, - {x64.R8, 1, 4, x64.RDI, []byte{0x41, 0x89, 0x78, 0x01}}, - {x64.R8, 1, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x78, 0x01}}, - {x64.R8, 1, 1, x64.RDI, []byte{0x41, 0x88, 0x78, 0x01}}, - {x64.R9, 1, 8, x64.RSI, []byte{0x49, 0x89, 0x71, 0x01}}, - {x64.R9, 1, 4, x64.RSI, []byte{0x41, 0x89, 0x71, 0x01}}, - {x64.R9, 1, 2, x64.RSI, []byte{0x66, 0x41, 0x89, 0x71, 0x01}}, - {x64.R9, 1, 1, x64.RSI, []byte{0x41, 0x88, 0x71, 0x01}}, - {x64.R10, 1, 8, x64.RBP, []byte{0x49, 0x89, 0x6A, 0x01}}, - {x64.R10, 1, 4, x64.RBP, []byte{0x41, 0x89, 0x6A, 0x01}}, - {x64.R10, 1, 2, x64.RBP, []byte{0x66, 0x41, 0x89, 0x6A, 0x01}}, - {x64.R10, 1, 1, x64.RBP, []byte{0x41, 0x88, 0x6A, 0x01}}, - {x64.R11, 1, 8, x64.RSP, []byte{0x49, 0x89, 0x63, 0x01}}, - {x64.R11, 1, 4, x64.RSP, []byte{0x41, 0x89, 0x63, 0x01}}, - {x64.R11, 1, 2, x64.RSP, []byte{0x66, 0x41, 0x89, 0x63, 0x01}}, - {x64.R11, 1, 1, x64.RSP, []byte{0x41, 0x88, 0x63, 0x01}}, - {x64.R12, 1, 8, x64.RBX, []byte{0x49, 0x89, 0x5C, 0x24, 0x01}}, - {x64.R12, 1, 4, x64.RBX, []byte{0x41, 0x89, 0x5C, 0x24, 0x01}}, - {x64.R12, 1, 2, x64.RBX, []byte{0x66, 0x41, 0x89, 0x5C, 0x24, 0x01}}, - {x64.R12, 1, 1, x64.RBX, []byte{0x41, 0x88, 0x5C, 0x24, 01}}, - {x64.R13, 1, 8, x64.RDX, []byte{0x49, 0x89, 0x55, 0x01}}, - {x64.R13, 1, 4, x64.RDX, []byte{0x41, 0x89, 0x55, 0x01}}, - {x64.R13, 1, 2, x64.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x01}}, - {x64.R13, 1, 1, x64.RDX, []byte{0x41, 0x88, 0x55, 0x01}}, - {x64.R14, 1, 8, x64.RCX, []byte{0x49, 0x89, 0x4E, 0x01}}, - {x64.R14, 1, 4, x64.RCX, []byte{0x41, 0x89, 0x4E, 0x01}}, - {x64.R14, 1, 2, x64.RCX, []byte{0x66, 0x41, 0x89, 0x4E, 0x01}}, - {x64.R14, 1, 1, x64.RCX, []byte{0x41, 0x88, 0x4E, 0x01}}, - {x64.R15, 1, 8, x64.RAX, []byte{0x49, 0x89, 0x47, 0x01}}, - {x64.R15, 1, 4, x64.RAX, []byte{0x41, 0x89, 0x47, 0x01}}, - {x64.R15, 1, 2, x64.RAX, []byte{0x66, 0x41, 0x89, 0x47, 0x01}}, - {x64.R15, 1, 1, x64.RAX, []byte{0x41, 0x88, 0x47, 0x01}}, - } - - for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%d], %s", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) - code := x64.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Sub_test.go b/src/x64/Sub_test.go deleted file mode 100644 index 7f03b53..0000000 --- a/src/x64/Sub_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestSubRegisterNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0x83, 0xE8, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0x83, 0xE9, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0x83, 0xEA, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0x83, 0xEB, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0x83, 0xEC, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0x83, 0xED, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0x83, 0xEE, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0x83, 0xEF, 0x01}}, - {x64.R8, 1, []byte{0x49, 0x83, 0xE8, 0x01}}, - {x64.R9, 1, []byte{0x49, 0x83, 0xE9, 0x01}}, - {x64.R10, 1, []byte{0x49, 0x83, 0xEA, 0x01}}, - {x64.R11, 1, []byte{0x49, 0x83, 0xEB, 0x01}}, - {x64.R12, 1, []byte{0x49, 0x83, 0xEC, 0x01}}, - {x64.R13, 1, []byte{0x49, 0x83, 0xED, 0x01}}, - {x64.R14, 1, []byte{0x49, 0x83, 0xEE, 0x01}}, - {x64.R15, 1, []byte{0x49, 0x83, 0xEF, 0x01}}, - - {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("sub %s, %x", pattern.Register, pattern.Number) - code := x64.SubRegisterNumber(nil, pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestSubRegisterRegister(t *testing.T) { - usagePatterns := []struct { - Left cpu.Register - Right cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, []byte{0x4C, 0x29, 0xF8}}, - {x64.RCX, x64.R14, []byte{0x4C, 0x29, 0xF1}}, - {x64.RDX, x64.R13, []byte{0x4C, 0x29, 0xEA}}, - {x64.RBX, x64.R12, []byte{0x4C, 0x29, 0xE3}}, - {x64.RSP, x64.R11, []byte{0x4C, 0x29, 0xDC}}, - {x64.RBP, x64.R10, []byte{0x4C, 0x29, 0xD5}}, - {x64.RSI, x64.R9, []byte{0x4C, 0x29, 0xCE}}, - {x64.RDI, x64.R8, []byte{0x4C, 0x29, 0xC7}}, - {x64.R8, x64.RDI, []byte{0x49, 0x29, 0xF8}}, - {x64.R9, x64.RSI, []byte{0x49, 0x29, 0xF1}}, - {x64.R10, x64.RBP, []byte{0x49, 0x29, 0xEA}}, - {x64.R11, x64.RSP, []byte{0x49, 0x29, 0xE3}}, - {x64.R12, x64.RBX, []byte{0x49, 0x29, 0xDC}}, - {x64.R13, x64.RDX, []byte{0x49, 0x29, 0xD5}}, - {x64.R14, x64.RCX, []byte{0x49, 0x29, 0xCE}}, - {x64.R15, x64.RAX, []byte{0x49, 0x29, 0xC7}}, - } - - for _, pattern := range usagePatterns { - t.Logf("sub %s, %s", pattern.Left, pattern.Right) - code := x64.SubRegisterRegister(nil, pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Xor_test.go b/src/x64/Xor_test.go deleted file mode 100644 index 1a2b629..0000000 --- a/src/x64/Xor_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestXorRegisterNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0x83, 0xF0, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0x83, 0xF1, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0x83, 0xF2, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0x83, 0xF3, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0x83, 0xF4, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0x83, 0xF5, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0x83, 0xF6, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0x83, 0xF7, 0x01}}, - {x64.R8, 1, []byte{0x49, 0x83, 0xF0, 0x01}}, - {x64.R9, 1, []byte{0x49, 0x83, 0xF1, 0x01}}, - {x64.R10, 1, []byte{0x49, 0x83, 0xF2, 0x01}}, - {x64.R11, 1, []byte{0x49, 0x83, 0xF3, 0x01}}, - {x64.R12, 1, []byte{0x49, 0x83, 0xF4, 0x01}}, - {x64.R13, 1, []byte{0x49, 0x83, 0xF5, 0x01}}, - {x64.R14, 1, []byte{0x49, 0x83, 0xF6, 0x01}}, - {x64.R15, 1, []byte{0x49, 0x83, 0xF7, 0x01}}, - - {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF7, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF7, 0xFF, 0xFF, 0xFF, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("xor %s, %x", pattern.Register, pattern.Number) - code := x64.XorRegisterNumber(nil, pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestXorRegisterRegister(t *testing.T) { - usagePatterns := []struct { - Left cpu.Register - Right cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, []byte{0x4C, 0x31, 0xF8}}, - {x64.RCX, x64.R14, []byte{0x4C, 0x31, 0xF1}}, - {x64.RDX, x64.R13, []byte{0x4C, 0x31, 0xEA}}, - {x64.RBX, x64.R12, []byte{0x4C, 0x31, 0xE3}}, - {x64.RSP, x64.R11, []byte{0x4C, 0x31, 0xDC}}, - {x64.RBP, x64.R10, []byte{0x4C, 0x31, 0xD5}}, - {x64.RSI, x64.R9, []byte{0x4C, 0x31, 0xCE}}, - {x64.RDI, x64.R8, []byte{0x4C, 0x31, 0xC7}}, - {x64.R8, x64.RDI, []byte{0x49, 0x31, 0xF8}}, - {x64.R9, x64.RSI, []byte{0x49, 0x31, 0xF1}}, - {x64.R10, x64.RBP, []byte{0x49, 0x31, 0xEA}}, - {x64.R11, x64.RSP, []byte{0x49, 0x31, 0xE3}}, - {x64.R12, x64.RBX, []byte{0x49, 0x31, 0xDC}}, - {x64.R13, x64.RDX, []byte{0x49, 0x31, 0xD5}}, - {x64.R14, x64.RCX, []byte{0x49, 0x31, 0xCE}}, - {x64.R15, x64.RAX, []byte{0x49, 0x31, 0xC7}}, - } - - for _, pattern := range usagePatterns { - t.Logf("xor %s, %s", pattern.Left, pattern.Right) - code := x64.XorRegisterRegister(nil, pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/x64_test.go b/src/x64/x64_test.go deleted file mode 100644 index 8216f1a..0000000 --- a/src/x64/x64_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestX64(t *testing.T) { - assert.DeepEqual(t, x64.AlignStack(nil), []byte{0x48, 0x83, 0xE4, 0xF0}) - assert.DeepEqual(t, x64.Call(nil, 1), []byte{0xE8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.CallAtAddress(nil, 1), []byte{0xFF, 0x15, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.ExtendRAXToRDX(nil), []byte{0x48, 0x99}) - assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 0, 1), []byte{0xB8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 1, 1), []byte{0xB9, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.Return(nil), []byte{0xC3}) - assert.DeepEqual(t, x64.Syscall(nil), []byte{0x0F, 0x05}) -} diff --git a/src/x64/Add.go b/src/x86/Add.go similarity index 97% rename from src/x64/Add.go rename to src/x86/Add.go index fb70735..9569a56 100644 --- a/src/x64/Add.go +++ b/src/x86/Add.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Add_test.go b/src/x86/Add_test.go new file mode 100644 index 0000000..4aa285e --- /dev/null +++ b/src/x86/Add_test.go @@ -0,0 +1,88 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestAddRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0x83, 0xC0, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0x83, 0xC1, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0x83, 0xC2, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0x83, 0xC3, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0x83, 0xC4, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0x83, 0xC5, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0x83, 0xC6, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0x83, 0xC7, 0x01}}, + {x86.R8, 1, []byte{0x49, 0x83, 0xC0, 0x01}}, + {x86.R9, 1, []byte{0x49, 0x83, 0xC1, 0x01}}, + {x86.R10, 1, []byte{0x49, 0x83, 0xC2, 0x01}}, + {x86.R11, 1, []byte{0x49, 0x83, 0xC3, 0x01}}, + {x86.R12, 1, []byte{0x49, 0x83, 0xC4, 0x01}}, + {x86.R13, 1, []byte{0x49, 0x83, 0xC5, 0x01}}, + {x86.R14, 1, []byte{0x49, 0x83, 0xC6, 0x01}}, + {x86.R15, 1, []byte{0x49, 0x83, 0xC7, 0x01}}, + + {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("add %s, %x", pattern.Register, pattern.Number) + code := x86.AddRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestAddRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, []byte{0x4C, 0x01, 0xF8}}, + {x86.RCX, x86.R14, []byte{0x4C, 0x01, 0xF1}}, + {x86.RDX, x86.R13, []byte{0x4C, 0x01, 0xEA}}, + {x86.RBX, x86.R12, []byte{0x4C, 0x01, 0xE3}}, + {x86.RSP, x86.R11, []byte{0x4C, 0x01, 0xDC}}, + {x86.RBP, x86.R10, []byte{0x4C, 0x01, 0xD5}}, + {x86.RSI, x86.R9, []byte{0x4C, 0x01, 0xCE}}, + {x86.RDI, x86.R8, []byte{0x4C, 0x01, 0xC7}}, + {x86.R8, x86.RDI, []byte{0x49, 0x01, 0xF8}}, + {x86.R9, x86.RSI, []byte{0x49, 0x01, 0xF1}}, + {x86.R10, x86.RBP, []byte{0x49, 0x01, 0xEA}}, + {x86.R11, x86.RSP, []byte{0x49, 0x01, 0xE3}}, + {x86.R12, x86.RBX, []byte{0x49, 0x01, 0xDC}}, + {x86.R13, x86.RDX, []byte{0x49, 0x01, 0xD5}}, + {x86.R14, x86.RCX, []byte{0x49, 0x01, 0xCE}}, + {x86.R15, x86.RAX, []byte{0x49, 0x01, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("add %s, %s", pattern.Left, pattern.Right) + code := x86.AddRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/AlignStack.go b/src/x86/AlignStack.go similarity index 91% rename from src/x64/AlignStack.go rename to src/x86/AlignStack.go index d3199e1..b2ce237 100644 --- a/src/x64/AlignStack.go +++ b/src/x86/AlignStack.go @@ -1,4 +1,4 @@ -package x64 +package x86 // AlignStack aligns RSP on a 16-byte boundary. func AlignStack(code []byte) []byte { diff --git a/src/x64/And.go b/src/x86/And.go similarity index 97% rename from src/x64/And.go rename to src/x86/And.go index 8556507..39da3a8 100644 --- a/src/x64/And.go +++ b/src/x86/And.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/And_test.go b/src/x86/And_test.go new file mode 100644 index 0000000..a4dbd58 --- /dev/null +++ b/src/x86/And_test.go @@ -0,0 +1,88 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestAndRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0x83, 0xE0, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0x83, 0xE1, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0x83, 0xE2, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0x83, 0xE3, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0x83, 0xE4, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0x83, 0xE5, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0x83, 0xE6, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0x83, 0xE7, 0x01}}, + {x86.R8, 1, []byte{0x49, 0x83, 0xE0, 0x01}}, + {x86.R9, 1, []byte{0x49, 0x83, 0xE1, 0x01}}, + {x86.R10, 1, []byte{0x49, 0x83, 0xE2, 0x01}}, + {x86.R11, 1, []byte{0x49, 0x83, 0xE3, 0x01}}, + {x86.R12, 1, []byte{0x49, 0x83, 0xE4, 0x01}}, + {x86.R13, 1, []byte{0x49, 0x83, 0xE5, 0x01}}, + {x86.R14, 1, []byte{0x49, 0x83, 0xE6, 0x01}}, + {x86.R15, 1, []byte{0x49, 0x83, 0xE7, 0x01}}, + + {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE7, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("and %s, %x", pattern.Register, pattern.Number) + code := x86.AndRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestAndRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, []byte{0x4C, 0x21, 0xF8}}, + {x86.RCX, x86.R14, []byte{0x4C, 0x21, 0xF1}}, + {x86.RDX, x86.R13, []byte{0x4C, 0x21, 0xEA}}, + {x86.RBX, x86.R12, []byte{0x4C, 0x21, 0xE3}}, + {x86.RSP, x86.R11, []byte{0x4C, 0x21, 0xDC}}, + {x86.RBP, x86.R10, []byte{0x4C, 0x21, 0xD5}}, + {x86.RSI, x86.R9, []byte{0x4C, 0x21, 0xCE}}, + {x86.RDI, x86.R8, []byte{0x4C, 0x21, 0xC7}}, + {x86.R8, x86.RDI, []byte{0x49, 0x21, 0xF8}}, + {x86.R9, x86.RSI, []byte{0x49, 0x21, 0xF1}}, + {x86.R10, x86.RBP, []byte{0x49, 0x21, 0xEA}}, + {x86.R11, x86.RSP, []byte{0x49, 0x21, 0xE3}}, + {x86.R12, x86.RBX, []byte{0x49, 0x21, 0xDC}}, + {x86.R13, x86.RDX, []byte{0x49, 0x21, 0xD5}}, + {x86.R14, x86.RCX, []byte{0x49, 0x21, 0xCE}}, + {x86.R15, x86.RAX, []byte{0x49, 0x21, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("and %s, %s", pattern.Left, pattern.Right) + code := x86.AndRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Call.go b/src/x86/Call.go similarity index 98% rename from src/x64/Call.go rename to src/x86/Call.go index 6c37d7b..812b484 100644 --- a/src/x64/Call.go +++ b/src/x86/Call.go @@ -1,4 +1,4 @@ -package x64 +package x86 // Call places the return address on the top of the stack and continues // program flow at the new address. diff --git a/src/x64/Compare.go b/src/x86/Compare.go similarity index 97% rename from src/x64/Compare.go rename to src/x86/Compare.go index 3c48dcf..6b67aaa 100644 --- a/src/x64/Compare.go +++ b/src/x86/Compare.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Compare_test.go b/src/x86/Compare_test.go new file mode 100644 index 0000000..dbf6795 --- /dev/null +++ b/src/x86/Compare_test.go @@ -0,0 +1,88 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestCompareRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0x83, 0xF8, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0x83, 0xF9, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0x83, 0xFA, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0x83, 0xFB, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0x83, 0xFC, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0x83, 0xFD, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0x83, 0xFE, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0x83, 0xFF, 0x01}}, + {x86.R8, 1, []byte{0x49, 0x83, 0xF8, 0x01}}, + {x86.R9, 1, []byte{0x49, 0x83, 0xF9, 0x01}}, + {x86.R10, 1, []byte{0x49, 0x83, 0xFA, 0x01}}, + {x86.R11, 1, []byte{0x49, 0x83, 0xFB, 0x01}}, + {x86.R12, 1, []byte{0x49, 0x83, 0xFC, 0x01}}, + {x86.R13, 1, []byte{0x49, 0x83, 0xFD, 0x01}}, + {x86.R14, 1, []byte{0x49, 0x83, 0xFE, 0x01}}, + {x86.R15, 1, []byte{0x49, 0x83, 0xFF, 0x01}}, + + {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("cmp %s, %x", pattern.Register, pattern.Number) + code := x86.CompareRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestCompareRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, []byte{0x4C, 0x39, 0xF8}}, + {x86.RCX, x86.R14, []byte{0x4C, 0x39, 0xF1}}, + {x86.RDX, x86.R13, []byte{0x4C, 0x39, 0xEA}}, + {x86.RBX, x86.R12, []byte{0x4C, 0x39, 0xE3}}, + {x86.RSP, x86.R11, []byte{0x4C, 0x39, 0xDC}}, + {x86.RBP, x86.R10, []byte{0x4C, 0x39, 0xD5}}, + {x86.RSI, x86.R9, []byte{0x4C, 0x39, 0xCE}}, + {x86.RDI, x86.R8, []byte{0x4C, 0x39, 0xC7}}, + {x86.R8, x86.RDI, []byte{0x49, 0x39, 0xF8}}, + {x86.R9, x86.RSI, []byte{0x49, 0x39, 0xF1}}, + {x86.R10, x86.RBP, []byte{0x49, 0x39, 0xEA}}, + {x86.R11, x86.RSP, []byte{0x49, 0x39, 0xE3}}, + {x86.R12, x86.RBX, []byte{0x49, 0x39, 0xDC}}, + {x86.R13, x86.RDX, []byte{0x49, 0x39, 0xD5}}, + {x86.R14, x86.RCX, []byte{0x49, 0x39, 0xCE}}, + {x86.R15, x86.RAX, []byte{0x49, 0x39, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("cmp %s, %s", pattern.Left, pattern.Right) + code := x86.CompareRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Div.go b/src/x86/Div.go similarity index 96% rename from src/x64/Div.go rename to src/x86/Div.go index 26439d1..b6c5ebf 100644 --- a/src/x64/Div.go +++ b/src/x86/Div.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Div_test.go b/src/x86/Div_test.go new file mode 100644 index 0000000..2af2bda --- /dev/null +++ b/src/x86/Div_test.go @@ -0,0 +1,39 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestDivRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x86.RAX, []byte{0x48, 0xF7, 0xF8}}, + {x86.RCX, []byte{0x48, 0xF7, 0xF9}}, + {x86.RDX, []byte{0x48, 0xF7, 0xFA}}, + {x86.RBX, []byte{0x48, 0xF7, 0xFB}}, + {x86.RSP, []byte{0x48, 0xF7, 0xFC}}, + {x86.RBP, []byte{0x48, 0xF7, 0xFD}}, + {x86.RSI, []byte{0x48, 0xF7, 0xFE}}, + {x86.RDI, []byte{0x48, 0xF7, 0xFF}}, + {x86.R8, []byte{0x49, 0xF7, 0xF8}}, + {x86.R9, []byte{0x49, 0xF7, 0xF9}}, + {x86.R10, []byte{0x49, 0xF7, 0xFA}}, + {x86.R11, []byte{0x49, 0xF7, 0xFB}}, + {x86.R12, []byte{0x49, 0xF7, 0xFC}}, + {x86.R13, []byte{0x49, 0xF7, 0xFD}}, + {x86.R14, []byte{0x49, 0xF7, 0xFE}}, + {x86.R15, []byte{0x49, 0xF7, 0xFF}}, + } + + for _, pattern := range usagePatterns { + t.Logf("idiv %s", pattern.Register) + code := x86.DivRegister(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/ExtendRAXToRDX.go b/src/x86/ExtendRAXToRDX.go similarity index 93% rename from src/x64/ExtendRAXToRDX.go rename to src/x86/ExtendRAXToRDX.go index 2f03963..7d97a86 100644 --- a/src/x64/ExtendRAXToRDX.go +++ b/src/x86/ExtendRAXToRDX.go @@ -1,4 +1,4 @@ -package x64 +package x86 // ExtendRAXToRDX doubles the size of RAX by sign-extending it to RDX. // This is also known as CQO. diff --git a/src/x64/Jump.go b/src/x86/Jump.go similarity index 98% rename from src/x64/Jump.go rename to src/x86/Jump.go index 1174f58..27b1ef4 100644 --- a/src/x64/Jump.go +++ b/src/x86/Jump.go @@ -1,4 +1,4 @@ -package x64 +package x86 // Jump continues program flow at the new address. // The address is relative to the next instruction. diff --git a/src/x64/Jump_test.go b/src/x86/Jump_test.go similarity index 56% rename from src/x64/Jump_test.go rename to src/x86/Jump_test.go index 4dde162..b2df363 100644 --- a/src/x64/Jump_test.go +++ b/src/x86/Jump_test.go @@ -1,9 +1,9 @@ -package x64_test +package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" "git.akyoto.dev/go/assert" ) @@ -25,16 +25,16 @@ func TestJump(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("jmp %x", pattern.Offset) - code := x64.Jump8(nil, pattern.Offset) + code := x86.Jump8(nil, pattern.Offset) assert.DeepEqual(t, code, pattern.Code) } } func TestConditionalJump(t *testing.T) { - assert.DeepEqual(t, x64.Jump8IfEqual(nil, 1), []byte{0x74, 0x01}) - assert.DeepEqual(t, x64.Jump8IfNotEqual(nil, 1), []byte{0x75, 0x01}) - assert.DeepEqual(t, x64.Jump8IfLess(nil, 1), []byte{0x7C, 0x01}) - assert.DeepEqual(t, x64.Jump8IfGreaterOrEqual(nil, 1), []byte{0x7D, 0x01}) - assert.DeepEqual(t, x64.Jump8IfLessOrEqual(nil, 1), []byte{0x7E, 0x01}) - assert.DeepEqual(t, x64.Jump8IfGreater(nil, 1), []byte{0x7F, 0x01}) + assert.DeepEqual(t, x86.Jump8IfEqual(nil, 1), []byte{0x74, 0x01}) + assert.DeepEqual(t, x86.Jump8IfNotEqual(nil, 1), []byte{0x75, 0x01}) + assert.DeepEqual(t, x86.Jump8IfLess(nil, 1), []byte{0x7C, 0x01}) + assert.DeepEqual(t, x86.Jump8IfGreaterOrEqual(nil, 1), []byte{0x7D, 0x01}) + assert.DeepEqual(t, x86.Jump8IfLessOrEqual(nil, 1), []byte{0x7E, 0x01}) + assert.DeepEqual(t, x86.Jump8IfGreater(nil, 1), []byte{0x7F, 0x01}) } diff --git a/src/x64/Load.go b/src/x86/Load.go similarity index 95% rename from src/x64/Load.go rename to src/x86/Load.go index eb5ac51..ae32daa 100644 --- a/src/x64/Load.go +++ b/src/x86/Load.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Load_test.go b/src/x86/Load_test.go new file mode 100644 index 0000000..2633e09 --- /dev/null +++ b/src/x86/Load_test.go @@ -0,0 +1,157 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestLoadRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Offset byte + Length byte + Code []byte + }{ + // No offset + {x86.RAX, x86.R15, 0, 8, []byte{0x49, 0x8B, 0x07}}, + {x86.RAX, x86.R15, 0, 4, []byte{0x41, 0x8B, 0x07}}, + {x86.RAX, x86.R15, 0, 2, []byte{0x66, 0x41, 0x8B, 0x07}}, + {x86.RAX, x86.R15, 0, 1, []byte{0x41, 0x8A, 0x07}}, + {x86.RCX, x86.R14, 0, 8, []byte{0x49, 0x8B, 0x0E}}, + {x86.RCX, x86.R14, 0, 4, []byte{0x41, 0x8B, 0x0E}}, + {x86.RCX, x86.R14, 0, 2, []byte{0x66, 0x41, 0x8B, 0x0E}}, + {x86.RCX, x86.R14, 0, 1, []byte{0x41, 0x8A, 0x0E}}, + {x86.RDX, x86.R13, 0, 8, []byte{0x49, 0x8B, 0x55, 0x00}}, + {x86.RDX, x86.R13, 0, 4, []byte{0x41, 0x8B, 0x55, 0x00}}, + {x86.RDX, x86.R13, 0, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x00}}, + {x86.RDX, x86.R13, 0, 1, []byte{0x41, 0x8A, 0x55, 0x00}}, + {x86.RBX, x86.R12, 0, 8, []byte{0x49, 0x8B, 0x1C, 0x24}}, + {x86.RBX, x86.R12, 0, 4, []byte{0x41, 0x8B, 0x1C, 0x24}}, + {x86.RBX, x86.R12, 0, 2, []byte{0x66, 0x41, 0x8B, 0x1C, 0x24}}, + {x86.RBX, x86.R12, 0, 1, []byte{0x41, 0x8A, 0x1C, 0x24}}, + {x86.RSP, x86.R11, 0, 8, []byte{0x49, 0x8B, 0x23}}, + {x86.RSP, x86.R11, 0, 4, []byte{0x41, 0x8B, 0x23}}, + {x86.RSP, x86.R11, 0, 2, []byte{0x66, 0x41, 0x8B, 0x23}}, + {x86.RSP, x86.R11, 0, 1, []byte{0x41, 0x8A, 0x23}}, + {x86.RBP, x86.R10, 0, 8, []byte{0x49, 0x8B, 0x2A}}, + {x86.RBP, x86.R10, 0, 4, []byte{0x41, 0x8B, 0x2A}}, + {x86.RBP, x86.R10, 0, 2, []byte{0x66, 0x41, 0x8B, 0x2A}}, + {x86.RBP, x86.R10, 0, 1, []byte{0x41, 0x8A, 0x2A}}, + {x86.RSI, x86.R9, 0, 8, []byte{0x49, 0x8B, 0x31}}, + {x86.RSI, x86.R9, 0, 4, []byte{0x41, 0x8B, 0x31}}, + {x86.RSI, x86.R9, 0, 2, []byte{0x66, 0x41, 0x8B, 0x31}}, + {x86.RSI, x86.R9, 0, 1, []byte{0x41, 0x8A, 0x31}}, + {x86.RDI, x86.R8, 0, 8, []byte{0x49, 0x8B, 0x38}}, + {x86.RDI, x86.R8, 0, 4, []byte{0x41, 0x8B, 0x38}}, + {x86.RDI, x86.R8, 0, 2, []byte{0x66, 0x41, 0x8B, 0x38}}, + {x86.RDI, x86.R8, 0, 1, []byte{0x41, 0x8A, 0x38}}, + {x86.R8, x86.RDI, 0, 8, []byte{0x4C, 0x8B, 0x07}}, + {x86.R8, x86.RDI, 0, 4, []byte{0x44, 0x8B, 0x07}}, + {x86.R8, x86.RDI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x07}}, + {x86.R8, x86.RDI, 0, 1, []byte{0x44, 0x8A, 0x07}}, + {x86.R9, x86.RSI, 0, 8, []byte{0x4C, 0x8B, 0x0E}}, + {x86.R9, x86.RSI, 0, 4, []byte{0x44, 0x8B, 0x0E}}, + {x86.R9, x86.RSI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x0E}}, + {x86.R9, x86.RSI, 0, 1, []byte{0x44, 0x8A, 0x0E}}, + {x86.R10, x86.RBP, 0, 8, []byte{0x4C, 0x8B, 0x55, 0x00}}, + {x86.R10, x86.RBP, 0, 4, []byte{0x44, 0x8B, 0x55, 0x00}}, + {x86.R10, x86.RBP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x00}}, + {x86.R10, x86.RBP, 0, 1, []byte{0x44, 0x8A, 0x55, 0x00}}, + {x86.R11, x86.RSP, 0, 8, []byte{0x4C, 0x8B, 0x1C, 0x24}}, + {x86.R11, x86.RSP, 0, 4, []byte{0x44, 0x8B, 0x1C, 0x24}}, + {x86.R11, x86.RSP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x1C, 0x24}}, + {x86.R11, x86.RSP, 0, 1, []byte{0x44, 0x8A, 0x1C, 0x24}}, + {x86.R12, x86.RBX, 0, 8, []byte{0x4C, 0x8B, 0x23}}, + {x86.R12, x86.RBX, 0, 4, []byte{0x44, 0x8B, 0x23}}, + {x86.R12, x86.RBX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x23}}, + {x86.R12, x86.RBX, 0, 1, []byte{0x44, 0x8A, 0x23}}, + {x86.R13, x86.RDX, 0, 8, []byte{0x4C, 0x8B, 0x2A}}, + {x86.R13, x86.RDX, 0, 4, []byte{0x44, 0x8B, 0x2A}}, + {x86.R13, x86.RDX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x2A}}, + {x86.R13, x86.RDX, 0, 1, []byte{0x44, 0x8A, 0x2A}}, + {x86.R14, x86.RCX, 0, 8, []byte{0x4C, 0x8B, 0x31}}, + {x86.R14, x86.RCX, 0, 4, []byte{0x44, 0x8B, 0x31}}, + {x86.R14, x86.RCX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x31}}, + {x86.R14, x86.RCX, 0, 1, []byte{0x44, 0x8A, 0x31}}, + {x86.R15, x86.RAX, 0, 8, []byte{0x4C, 0x8B, 0x38}}, + {x86.R15, x86.RAX, 0, 4, []byte{0x44, 0x8B, 0x38}}, + {x86.R15, x86.RAX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x38}}, + {x86.R15, x86.RAX, 0, 1, []byte{0x44, 0x8A, 0x38}}, + + // Offset of 1 + {x86.RAX, x86.R15, 1, 8, []byte{0x49, 0x8B, 0x47, 0x01}}, + {x86.RAX, x86.R15, 1, 4, []byte{0x41, 0x8B, 0x47, 0x01}}, + {x86.RAX, x86.R15, 1, 2, []byte{0x66, 0x41, 0x8B, 0x47, 0x01}}, + {x86.RAX, x86.R15, 1, 1, []byte{0x41, 0x8A, 0x47, 0x01}}, + {x86.RCX, x86.R14, 1, 8, []byte{0x49, 0x8B, 0x4E, 0x01}}, + {x86.RCX, x86.R14, 1, 4, []byte{0x41, 0x8B, 0x4E, 0x01}}, + {x86.RCX, x86.R14, 1, 2, []byte{0x66, 0x41, 0x8B, 0x4E, 0x01}}, + {x86.RCX, x86.R14, 1, 1, []byte{0x41, 0x8A, 0x4E, 0x01}}, + {x86.RDX, x86.R13, 1, 8, []byte{0x49, 0x8B, 0x55, 0x01}}, + {x86.RDX, x86.R13, 1, 4, []byte{0x41, 0x8B, 0x55, 0x01}}, + {x86.RDX, x86.R13, 1, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x01}}, + {x86.RDX, x86.R13, 1, 1, []byte{0x41, 0x8A, 0x55, 0x01}}, + {x86.RBX, x86.R12, 1, 8, []byte{0x49, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.RBX, x86.R12, 1, 4, []byte{0x41, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.RBX, x86.R12, 1, 2, []byte{0x66, 0x41, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.RBX, x86.R12, 1, 1, []byte{0x41, 0x8A, 0x5C, 0x24, 0x01}}, + {x86.RSP, x86.R11, 1, 8, []byte{0x49, 0x8B, 0x63, 0x01}}, + {x86.RSP, x86.R11, 1, 4, []byte{0x41, 0x8B, 0x63, 0x01}}, + {x86.RSP, x86.R11, 1, 2, []byte{0x66, 0x41, 0x8B, 0x63, 0x01}}, + {x86.RSP, x86.R11, 1, 1, []byte{0x41, 0x8A, 0x63, 0x01}}, + {x86.RBP, x86.R10, 1, 8, []byte{0x49, 0x8B, 0x6A, 0x01}}, + {x86.RBP, x86.R10, 1, 4, []byte{0x41, 0x8B, 0x6A, 0x01}}, + {x86.RBP, x86.R10, 1, 2, []byte{0x66, 0x41, 0x8B, 0x6A, 0x01}}, + {x86.RBP, x86.R10, 1, 1, []byte{0x41, 0x8A, 0x6A, 0x01}}, + {x86.RSI, x86.R9, 1, 8, []byte{0x49, 0x8B, 0x71, 0x01}}, + {x86.RSI, x86.R9, 1, 4, []byte{0x41, 0x8B, 0x71, 0x01}}, + {x86.RSI, x86.R9, 1, 2, []byte{0x66, 0x41, 0x8B, 0x71, 0x01}}, + {x86.RSI, x86.R9, 1, 1, []byte{0x41, 0x8A, 0x71, 0x01}}, + {x86.RDI, x86.R8, 1, 8, []byte{0x49, 0x8B, 0x78, 0x01}}, + {x86.RDI, x86.R8, 1, 4, []byte{0x41, 0x8B, 0x78, 0x01}}, + {x86.RDI, x86.R8, 1, 2, []byte{0x66, 0x41, 0x8B, 0x78, 0x01}}, + {x86.RDI, x86.R8, 1, 1, []byte{0x41, 0x8A, 0x78, 0x01}}, + {x86.R8, x86.RDI, 1, 8, []byte{0x4C, 0x8B, 0x47, 0x01}}, + {x86.R8, x86.RDI, 1, 4, []byte{0x44, 0x8B, 0x47, 0x01}}, + {x86.R8, x86.RDI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x47, 0x01}}, + {x86.R8, x86.RDI, 1, 1, []byte{0x44, 0x8A, 0x47, 0x01}}, + {x86.R9, x86.RSI, 1, 8, []byte{0x4C, 0x8B, 0x4E, 0x01}}, + {x86.R9, x86.RSI, 1, 4, []byte{0x44, 0x8B, 0x4E, 0x01}}, + {x86.R9, x86.RSI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x4E, 0x01}}, + {x86.R9, x86.RSI, 1, 1, []byte{0x44, 0x8A, 0x4E, 0x01}}, + {x86.R10, x86.RBP, 1, 8, []byte{0x4C, 0x8B, 0x55, 0x01}}, + {x86.R10, x86.RBP, 1, 4, []byte{0x44, 0x8B, 0x55, 0x01}}, + {x86.R10, x86.RBP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x01}}, + {x86.R10, x86.RBP, 1, 1, []byte{0x44, 0x8A, 0x55, 0x01}}, + {x86.R11, x86.RSP, 1, 8, []byte{0x4C, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R11, x86.RSP, 1, 4, []byte{0x44, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R11, x86.RSP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R11, x86.RSP, 1, 1, []byte{0x44, 0x8A, 0x5C, 0x24, 0x01}}, + {x86.R12, x86.RBX, 1, 8, []byte{0x4C, 0x8B, 0x63, 0x01}}, + {x86.R12, x86.RBX, 1, 4, []byte{0x44, 0x8B, 0x63, 0x01}}, + {x86.R12, x86.RBX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x63, 0x01}}, + {x86.R12, x86.RBX, 1, 1, []byte{0x44, 0x8A, 0x63, 0x01}}, + {x86.R13, x86.RDX, 1, 8, []byte{0x4C, 0x8B, 0x6A, 0x01}}, + {x86.R13, x86.RDX, 1, 4, []byte{0x44, 0x8B, 0x6A, 0x01}}, + {x86.R13, x86.RDX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x6A, 0x01}}, + {x86.R13, x86.RDX, 1, 1, []byte{0x44, 0x8A, 0x6A, 0x01}}, + {x86.R14, x86.RCX, 1, 8, []byte{0x4C, 0x8B, 0x71, 0x01}}, + {x86.R14, x86.RCX, 1, 4, []byte{0x44, 0x8B, 0x71, 0x01}}, + {x86.R14, x86.RCX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x71, 0x01}}, + {x86.R14, x86.RCX, 1, 1, []byte{0x44, 0x8A, 0x71, 0x01}}, + {x86.R15, x86.RAX, 1, 8, []byte{0x4C, 0x8B, 0x78, 0x01}}, + {x86.R15, x86.RAX, 1, 4, []byte{0x44, 0x8B, 0x78, 0x01}}, + {x86.R15, x86.RAX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x78, 0x01}}, + {x86.R15, x86.RAX, 1, 1, []byte{0x44, 0x8A, 0x78, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("load %dB %s, [%s+%d]", pattern.Length, pattern.Destination, pattern.Source, pattern.Offset) + code := x86.LoadRegister(nil, pattern.Destination, pattern.Offset, pattern.Length, pattern.Source) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/ModRM.go b/src/x86/ModRM.go similarity index 97% rename from src/x64/ModRM.go rename to src/x86/ModRM.go index bb6852e..1ab4393 100644 --- a/src/x64/ModRM.go +++ b/src/x86/ModRM.go @@ -1,4 +1,4 @@ -package x64 +package x86 // AddressMode encodes the addressing mode. type AddressMode = byte diff --git a/src/x64/ModRM_test.go b/src/x86/ModRM_test.go similarity index 90% rename from src/x64/ModRM_test.go rename to src/x86/ModRM_test.go index 09f72eb..fd1151a 100644 --- a/src/x64/ModRM_test.go +++ b/src/x86/ModRM_test.go @@ -1,9 +1,9 @@ -package x64_test +package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" "git.akyoto.dev/go/assert" ) @@ -28,7 +28,7 @@ func TestModRM(t *testing.T) { } for _, test := range testData { - modRM := x64.ModRM(test.mod, test.reg, test.rm) + modRM := x86.ModRM(test.mod, test.reg, test.rm) assert.Equal(t, modRM, test.expected) } } diff --git a/src/x64/Move.go b/src/x86/Move.go similarity index 99% rename from src/x64/Move.go rename to src/x86/Move.go index 48edc79..2c38ca5 100644 --- a/src/x64/Move.go +++ b/src/x86/Move.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "encoding/binary" diff --git a/src/x86/Move_test.go b/src/x86/Move_test.go new file mode 100644 index 0000000..5c074ae --- /dev/null +++ b/src/x86/Move_test.go @@ -0,0 +1,108 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestMoveRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + // 32 bits + {x86.RAX, 0x7FFFFFFF, []byte{0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFF, []byte{0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFF, []byte{0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFF, []byte{0xBB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFF, []byte{0xBC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFF, []byte{0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFF, []byte{0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFF, []byte{0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFF, []byte{0x41, 0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFF, []byte{0x41, 0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFF, []byte{0x41, 0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFF, []byte{0x41, 0xBB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFF, []byte{0x41, 0xBC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFF, []byte{0x41, 0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFF, []byte{0x41, 0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFF, []byte{0x41, 0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, + + // 64 bits + {x86.RAX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + + // Negative numbers + {x86.RAX, -1, []byte{0x48, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.RCX, -1, []byte{0x48, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.RDX, -1, []byte{0x48, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.RBX, -1, []byte{0x48, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.RSP, -1, []byte{0x48, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.RBP, -1, []byte{0x48, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.RSI, -1, []byte{0x48, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.RDI, -1, []byte{0x48, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R8, -1, []byte{0x49, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R9, -1, []byte{0x49, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R10, -1, []byte{0x49, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R11, -1, []byte{0x49, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R12, -1, []byte{0x49, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R13, -1, []byte{0x49, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R14, -1, []byte{0x49, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R15, -1, []byte{0x49, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, %x", pattern.Register, pattern.Number) + code := x86.MoveRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestMoveRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, []byte{0x4C, 0x89, 0xF8}}, + {x86.RCX, x86.R14, []byte{0x4C, 0x89, 0xF1}}, + {x86.RDX, x86.R13, []byte{0x4C, 0x89, 0xEA}}, + {x86.RBX, x86.R12, []byte{0x4C, 0x89, 0xE3}}, + {x86.RSP, x86.R11, []byte{0x4C, 0x89, 0xDC}}, + {x86.RBP, x86.R10, []byte{0x4C, 0x89, 0xD5}}, + {x86.RSI, x86.R9, []byte{0x4C, 0x89, 0xCE}}, + {x86.RDI, x86.R8, []byte{0x4C, 0x89, 0xC7}}, + {x86.R8, x86.RDI, []byte{0x49, 0x89, 0xF8}}, + {x86.R9, x86.RSI, []byte{0x49, 0x89, 0xF1}}, + {x86.R10, x86.RBP, []byte{0x49, 0x89, 0xEA}}, + {x86.R11, x86.RSP, []byte{0x49, 0x89, 0xE3}}, + {x86.R12, x86.RBX, []byte{0x49, 0x89, 0xDC}}, + {x86.R13, x86.RDX, []byte{0x49, 0x89, 0xD5}}, + {x86.R14, x86.RCX, []byte{0x49, 0x89, 0xCE}}, + {x86.R15, x86.RAX, []byte{0x49, 0x89, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, %s", pattern.Left, pattern.Right) + code := x86.MoveRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Mul.go b/src/x86/Mul.go similarity index 97% rename from src/x64/Mul.go rename to src/x86/Mul.go index 1d11da3..f2b4dbb 100644 --- a/src/x64/Mul.go +++ b/src/x86/Mul.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Mul_test.go b/src/x86/Mul_test.go new file mode 100644 index 0000000..46618fe --- /dev/null +++ b/src/x86/Mul_test.go @@ -0,0 +1,88 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestMulRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0x6B, 0xC0, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0x6B, 0xC9, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0x6B, 0xD2, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0x6B, 0xDB, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0x6B, 0xE4, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0x6B, 0xED, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0x6B, 0xF6, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0x6B, 0xFF, 0x01}}, + {x86.R8, 1, []byte{0x4D, 0x6B, 0xC0, 0x01}}, + {x86.R9, 1, []byte{0x4D, 0x6B, 0xC9, 0x01}}, + {x86.R10, 1, []byte{0x4D, 0x6B, 0xD2, 0x01}}, + {x86.R11, 1, []byte{0x4D, 0x6B, 0xDB, 0x01}}, + {x86.R12, 1, []byte{0x4D, 0x6B, 0xE4, 0x01}}, + {x86.R13, 1, []byte{0x4D, 0x6B, 0xED, 0x01}}, + {x86.R14, 1, []byte{0x4D, 0x6B, 0xF6, 0x01}}, + {x86.R15, 1, []byte{0x4D, 0x6B, 0xFF, 0x01}}, + + {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mul %s, %x", pattern.Register, pattern.Number) + code := x86.MulRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestMulRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, []byte{0x49, 0x0F, 0xAF, 0xC7}}, + {x86.RCX, x86.R14, []byte{0x49, 0x0F, 0xAF, 0xCE}}, + {x86.RDX, x86.R13, []byte{0x49, 0x0F, 0xAF, 0xD5}}, + {x86.RBX, x86.R12, []byte{0x49, 0x0F, 0xAF, 0xDC}}, + {x86.RSP, x86.R11, []byte{0x49, 0x0F, 0xAF, 0xE3}}, + {x86.RBP, x86.R10, []byte{0x49, 0x0F, 0xAF, 0xEA}}, + {x86.RSI, x86.R9, []byte{0x49, 0x0F, 0xAF, 0xF1}}, + {x86.RDI, x86.R8, []byte{0x49, 0x0F, 0xAF, 0xF8}}, + {x86.R8, x86.RDI, []byte{0x4C, 0x0F, 0xAF, 0xC7}}, + {x86.R9, x86.RSI, []byte{0x4C, 0x0F, 0xAF, 0xCE}}, + {x86.R10, x86.RBP, []byte{0x4C, 0x0F, 0xAF, 0xD5}}, + {x86.R11, x86.RSP, []byte{0x4C, 0x0F, 0xAF, 0xDC}}, + {x86.R12, x86.RBX, []byte{0x4C, 0x0F, 0xAF, 0xE3}}, + {x86.R13, x86.RDX, []byte{0x4C, 0x0F, 0xAF, 0xEA}}, + {x86.R14, x86.RCX, []byte{0x4C, 0x0F, 0xAF, 0xF1}}, + {x86.R15, x86.RAX, []byte{0x4C, 0x0F, 0xAF, 0xF8}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mul %s, %s", pattern.Left, pattern.Right) + code := x86.MulRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Negate.go b/src/x86/Negate.go similarity index 94% rename from src/x64/Negate.go rename to src/x86/Negate.go index 479c143..141d105 100644 --- a/src/x64/Negate.go +++ b/src/x86/Negate.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Negate_test.go b/src/x86/Negate_test.go new file mode 100644 index 0000000..0fcaeea --- /dev/null +++ b/src/x86/Negate_test.go @@ -0,0 +1,39 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestNegateRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x86.RAX, []byte{0x48, 0xF7, 0xD8}}, + {x86.RCX, []byte{0x48, 0xF7, 0xD9}}, + {x86.RDX, []byte{0x48, 0xF7, 0xDA}}, + {x86.RBX, []byte{0x48, 0xF7, 0xDB}}, + {x86.RSP, []byte{0x48, 0xF7, 0xDC}}, + {x86.RBP, []byte{0x48, 0xF7, 0xDD}}, + {x86.RSI, []byte{0x48, 0xF7, 0xDE}}, + {x86.RDI, []byte{0x48, 0xF7, 0xDF}}, + {x86.R8, []byte{0x49, 0xF7, 0xD8}}, + {x86.R9, []byte{0x49, 0xF7, 0xD9}}, + {x86.R10, []byte{0x49, 0xF7, 0xDA}}, + {x86.R11, []byte{0x49, 0xF7, 0xDB}}, + {x86.R12, []byte{0x49, 0xF7, 0xDC}}, + {x86.R13, []byte{0x49, 0xF7, 0xDD}}, + {x86.R14, []byte{0x49, 0xF7, 0xDE}}, + {x86.R15, []byte{0x49, 0xF7, 0xDF}}, + } + + for _, pattern := range usagePatterns { + t.Logf("neg %s", pattern.Register) + code := x86.NegateRegister(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Or.go b/src/x86/Or.go similarity index 97% rename from src/x64/Or.go rename to src/x86/Or.go index fa5b440..9ed61c4 100644 --- a/src/x64/Or.go +++ b/src/x86/Or.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Or_test.go b/src/x86/Or_test.go new file mode 100644 index 0000000..f26e4e7 --- /dev/null +++ b/src/x86/Or_test.go @@ -0,0 +1,88 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestOrRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0x83, 0xC8, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0x83, 0xC9, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0x83, 0xCA, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0x83, 0xCB, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0x83, 0xCC, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0x83, 0xCD, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0x83, 0xCE, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0x83, 0xCF, 0x01}}, + {x86.R8, 1, []byte{0x49, 0x83, 0xC8, 0x01}}, + {x86.R9, 1, []byte{0x49, 0x83, 0xC9, 0x01}}, + {x86.R10, 1, []byte{0x49, 0x83, 0xCA, 0x01}}, + {x86.R11, 1, []byte{0x49, 0x83, 0xCB, 0x01}}, + {x86.R12, 1, []byte{0x49, 0x83, 0xCC, 0x01}}, + {x86.R13, 1, []byte{0x49, 0x83, 0xCD, 0x01}}, + {x86.R14, 1, []byte{0x49, 0x83, 0xCE, 0x01}}, + {x86.R15, 1, []byte{0x49, 0x83, 0xCF, 0x01}}, + + {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("or %s, %x", pattern.Register, pattern.Number) + code := x86.OrRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestOrRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, []byte{0x4C, 0x09, 0xF8}}, + {x86.RCX, x86.R14, []byte{0x4C, 0x09, 0xF1}}, + {x86.RDX, x86.R13, []byte{0x4C, 0x09, 0xEA}}, + {x86.RBX, x86.R12, []byte{0x4C, 0x09, 0xE3}}, + {x86.RSP, x86.R11, []byte{0x4C, 0x09, 0xDC}}, + {x86.RBP, x86.R10, []byte{0x4C, 0x09, 0xD5}}, + {x86.RSI, x86.R9, []byte{0x4C, 0x09, 0xCE}}, + {x86.RDI, x86.R8, []byte{0x4C, 0x09, 0xC7}}, + {x86.R8, x86.RDI, []byte{0x49, 0x09, 0xF8}}, + {x86.R9, x86.RSI, []byte{0x49, 0x09, 0xF1}}, + {x86.R10, x86.RBP, []byte{0x49, 0x09, 0xEA}}, + {x86.R11, x86.RSP, []byte{0x49, 0x09, 0xE3}}, + {x86.R12, x86.RBX, []byte{0x49, 0x09, 0xDC}}, + {x86.R13, x86.RDX, []byte{0x49, 0x09, 0xD5}}, + {x86.R14, x86.RCX, []byte{0x49, 0x09, 0xCE}}, + {x86.R15, x86.RAX, []byte{0x49, 0x09, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("or %s, %s", pattern.Left, pattern.Right) + code := x86.OrRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Pop.go b/src/x86/Pop.go similarity index 96% rename from src/x64/Pop.go rename to src/x86/Pop.go index b97182f..5b23719 100644 --- a/src/x64/Pop.go +++ b/src/x86/Pop.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Pop_test.go b/src/x86/Pop_test.go new file mode 100644 index 0000000..268dcfc --- /dev/null +++ b/src/x86/Pop_test.go @@ -0,0 +1,39 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestPopRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x86.RAX, []byte{0x58}}, + {x86.RCX, []byte{0x59}}, + {x86.RDX, []byte{0x5A}}, + {x86.RBX, []byte{0x5B}}, + {x86.RSP, []byte{0x5C}}, + {x86.RBP, []byte{0x5D}}, + {x86.RSI, []byte{0x5E}}, + {x86.RDI, []byte{0x5F}}, + {x86.R8, []byte{0x41, 0x58}}, + {x86.R9, []byte{0x41, 0x59}}, + {x86.R10, []byte{0x41, 0x5A}}, + {x86.R11, []byte{0x41, 0x5B}}, + {x86.R12, []byte{0x41, 0x5C}}, + {x86.R13, []byte{0x41, 0x5D}}, + {x86.R14, []byte{0x41, 0x5E}}, + {x86.R15, []byte{0x41, 0x5F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("pop %s", pattern.Register) + code := x86.PopRegister(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Push.go b/src/x86/Push.go similarity index 96% rename from src/x64/Push.go rename to src/x86/Push.go index bdae334..b9c82b0 100644 --- a/src/x64/Push.go +++ b/src/x86/Push.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Push_test.go b/src/x86/Push_test.go new file mode 100644 index 0000000..29cedac --- /dev/null +++ b/src/x86/Push_test.go @@ -0,0 +1,39 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestPushRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x86.RAX, []byte{0x50}}, + {x86.RCX, []byte{0x51}}, + {x86.RDX, []byte{0x52}}, + {x86.RBX, []byte{0x53}}, + {x86.RSP, []byte{0x54}}, + {x86.RBP, []byte{0x55}}, + {x86.RSI, []byte{0x56}}, + {x86.RDI, []byte{0x57}}, + {x86.R8, []byte{0x41, 0x50}}, + {x86.R9, []byte{0x41, 0x51}}, + {x86.R10, []byte{0x41, 0x52}}, + {x86.R11, []byte{0x41, 0x53}}, + {x86.R12, []byte{0x41, 0x54}}, + {x86.R13, []byte{0x41, 0x55}}, + {x86.R14, []byte{0x41, 0x56}}, + {x86.R15, []byte{0x41, 0x57}}, + } + + for _, pattern := range usagePatterns { + t.Logf("push %s", pattern.Register) + code := x86.PushRegister(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/REX.go b/src/x86/REX.go similarity index 93% rename from src/x64/REX.go rename to src/x86/REX.go index 36d7e58..554140e 100644 --- a/src/x64/REX.go +++ b/src/x86/REX.go @@ -1,4 +1,4 @@ -package x64 +package x86 // REX is used to generate a REX prefix. // w, r, x and b can only be set to either 0 or 1. diff --git a/src/x64/REX_test.go b/src/x86/REX_test.go similarity index 87% rename from src/x64/REX_test.go rename to src/x86/REX_test.go index c754354..73f2290 100644 --- a/src/x64/REX_test.go +++ b/src/x86/REX_test.go @@ -1,9 +1,9 @@ -package x64_test +package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" "git.akyoto.dev/go/assert" ) @@ -28,7 +28,7 @@ func TestREX(t *testing.T) { } for _, test := range testData { - rex := x64.REX(test.w, test.r, test.x, test.b) + rex := x86.REX(test.w, test.r, test.x, test.b) assert.Equal(t, rex, test.expected) } } diff --git a/src/x64/Registers.go b/src/x86/Registers.go similarity index 98% rename from src/x64/Registers.go rename to src/x86/Registers.go index 57d4e8b..0ed7c5f 100644 --- a/src/x64/Registers.go +++ b/src/x86/Registers.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Registers_test.go b/src/x86/Registers_test.go new file mode 100644 index 0000000..54eb4d2 --- /dev/null +++ b/src/x86/Registers_test.go @@ -0,0 +1,12 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestRegisters(t *testing.T) { + assert.NotContains(t, x86.GeneralRegisters, x86.RSP) +} diff --git a/src/x64/Return.go b/src/x86/Return.go similarity index 94% rename from src/x64/Return.go rename to src/x86/Return.go index a64d39f..4ac2301 100644 --- a/src/x64/Return.go +++ b/src/x86/Return.go @@ -1,4 +1,4 @@ -package x64 +package x86 // Return transfers program control to a return address located on the top of the stack. // The address is usually placed on the stack by a Call instruction. diff --git a/src/x64/SIB.go b/src/x86/SIB.go similarity index 97% rename from src/x64/SIB.go rename to src/x86/SIB.go index 438024d..4b75714 100644 --- a/src/x64/SIB.go +++ b/src/x86/SIB.go @@ -1,4 +1,4 @@ -package x64 +package x86 // ScaleFactor encodes the scale factor. type ScaleFactor = byte diff --git a/src/x64/SIB_test.go b/src/x86/SIB_test.go similarity index 89% rename from src/x64/SIB_test.go rename to src/x86/SIB_test.go index 911b133..c6dcb4b 100644 --- a/src/x64/SIB_test.go +++ b/src/x86/SIB_test.go @@ -1,9 +1,9 @@ -package x64_test +package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" "git.akyoto.dev/go/assert" ) @@ -28,7 +28,7 @@ func TestSIB(t *testing.T) { } for _, test := range testData { - sib := x64.SIB(test.scale, test.index, test.base) + sib := x86.SIB(test.scale, test.index, test.base) assert.Equal(t, sib, test.expected) } } diff --git a/src/x64/Shift.go b/src/x86/Shift.go similarity index 97% rename from src/x64/Shift.go rename to src/x86/Shift.go index 6fa2251..428e7aa 100644 --- a/src/x64/Shift.go +++ b/src/x86/Shift.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Shift_test.go b/src/x86/Shift_test.go new file mode 100644 index 0000000..2e80741 --- /dev/null +++ b/src/x86/Shift_test.go @@ -0,0 +1,71 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestShiftLeftNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0xC1, 0xE0, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0xC1, 0xE1, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0xC1, 0xE2, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0xC1, 0xE3, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0xC1, 0xE4, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0xC1, 0xE5, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0xC1, 0xE6, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0xC1, 0xE7, 0x01}}, + {x86.R8, 1, []byte{0x49, 0xC1, 0xE0, 0x01}}, + {x86.R9, 1, []byte{0x49, 0xC1, 0xE1, 0x01}}, + {x86.R10, 1, []byte{0x49, 0xC1, 0xE2, 0x01}}, + {x86.R11, 1, []byte{0x49, 0xC1, 0xE3, 0x01}}, + {x86.R12, 1, []byte{0x49, 0xC1, 0xE4, 0x01}}, + {x86.R13, 1, []byte{0x49, 0xC1, 0xE5, 0x01}}, + {x86.R14, 1, []byte{0x49, 0xC1, 0xE6, 0x01}}, + {x86.R15, 1, []byte{0x49, 0xC1, 0xE7, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("shl %s, %x", pattern.Register, pattern.Number) + code := x86.ShiftLeftNumber(nil, pattern.Register, byte(pattern.Number)) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestShiftRightSignedNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0xC1, 0xF8, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0xC1, 0xF9, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0xC1, 0xFA, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0xC1, 0xFB, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0xC1, 0xFC, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0xC1, 0xFD, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0xC1, 0xFE, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0xC1, 0xFF, 0x01}}, + {x86.R8, 1, []byte{0x49, 0xC1, 0xF8, 0x01}}, + {x86.R9, 1, []byte{0x49, 0xC1, 0xF9, 0x01}}, + {x86.R10, 1, []byte{0x49, 0xC1, 0xFA, 0x01}}, + {x86.R11, 1, []byte{0x49, 0xC1, 0xFB, 0x01}}, + {x86.R12, 1, []byte{0x49, 0xC1, 0xFC, 0x01}}, + {x86.R13, 1, []byte{0x49, 0xC1, 0xFD, 0x01}}, + {x86.R14, 1, []byte{0x49, 0xC1, 0xFE, 0x01}}, + {x86.R15, 1, []byte{0x49, 0xC1, 0xFF, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("sar %s, %x", pattern.Register, pattern.Number) + code := x86.ShiftRightSignedNumber(nil, pattern.Register, byte(pattern.Number)) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Store.go b/src/x86/Store.go similarity index 98% rename from src/x64/Store.go rename to src/x86/Store.go index 01bdfc6..0bf06e0 100644 --- a/src/x64/Store.go +++ b/src/x86/Store.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "encoding/binary" diff --git a/src/x64/StoreDynamic.go b/src/x86/StoreDynamic.go similarity index 98% rename from src/x64/StoreDynamic.go rename to src/x86/StoreDynamic.go index 3a214c9..2cedf7e 100644 --- a/src/x64/StoreDynamic.go +++ b/src/x86/StoreDynamic.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "encoding/binary" diff --git a/src/x86/StoreDynamic_test.go b/src/x86/StoreDynamic_test.go new file mode 100644 index 0000000..f5a957f --- /dev/null +++ b/src/x86/StoreDynamic_test.go @@ -0,0 +1,171 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestStoreDynamicNumber(t *testing.T) { + usagePatterns := []struct { + RegisterTo cpu.Register + Offset cpu.Register + Length byte + Number int + Code []byte + }{ + {x86.RAX, x86.R15, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RAX, x86.R15, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RAX, x86.R15, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, + {x86.RAX, x86.R15, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x38, 0x7F}}, + {x86.RCX, x86.R14, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RCX, x86.R14, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RCX, x86.R14, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, + {x86.RCX, x86.R14, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x31, 0x7F}}, + {x86.RDX, x86.R13, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDX, x86.R13, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDX, x86.R13, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, + {x86.RDX, x86.R13, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x2A, 0x7F}}, + {x86.RBX, x86.R12, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBX, x86.R12, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBX, x86.R12, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00}}, + {x86.RBX, x86.R12, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x23, 0x7F}}, + {x86.RSP, x86.R11, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSP, x86.R11, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSP, x86.R11, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x86.RSP, x86.R11, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, + {x86.RBP, x86.R10, 8, 0x7F, []byte{0x4A, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBP, x86.R10, 4, 0x7F, []byte{0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBP, x86.R10, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, + {x86.RBP, x86.R10, 1, 0x7F, []byte{0x42, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, + {x86.RSI, x86.R9, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSI, x86.R9, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSI, x86.R9, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, + {x86.RSI, x86.R9, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x0E, 0x7F}}, + {x86.RDI, x86.R8, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDI, x86.R8, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDI, x86.R8, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, + {x86.RDI, x86.R8, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x07, 0x7F}}, + {x86.R8, x86.RDI, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R8, x86.RDI, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R8, x86.RDI, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, + {x86.R8, x86.RDI, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x38, 0x7F}}, + {x86.R9, x86.RSI, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R9, x86.RSI, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R9, x86.RSI, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, + {x86.R9, x86.RSI, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x31, 0x7F}}, + {x86.R10, x86.RBP, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R10, x86.RBP, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R10, x86.RBP, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, + {x86.R10, x86.RBP, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x2A, 0x7F}}, + {x86.R11, x86.RSP, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R11, x86.RSP, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R11, x86.RSP, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x86.R11, x86.RSP, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, + {x86.R12, x86.RBX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R12, x86.RBX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R12, x86.RBX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x86.R12, x86.RBX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x1C, 0x7F}}, + {x86.R13, x86.RDX, 8, 0x7F, []byte{0x49, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R13, x86.RDX, 4, 0x7F, []byte{0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R13, x86.RDX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, + {x86.R13, x86.RDX, 1, 0x7F, []byte{0x41, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, + {x86.R14, x86.RCX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R14, x86.RCX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R14, x86.RCX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, + {x86.R14, x86.RCX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x0E, 0x7F}}, + {x86.R15, x86.RAX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R15, x86.RAX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R15, x86.RAX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, + {x86.R15, x86.RAX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x07, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("store %dB [%s+%s], %d", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.Number) + code := x86.StoreDynamicNumber(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestStoreDynamicRegister(t *testing.T) { + usagePatterns := []struct { + RegisterTo cpu.Register + Offset cpu.Register + Length byte + RegisterFrom cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, 8, x86.R15, []byte{0x4E, 0x89, 0x3C, 0x38}}, + {x86.RAX, x86.R15, 4, x86.R15, []byte{0x46, 0x89, 0x3C, 0x38}}, + {x86.RAX, x86.R15, 2, x86.R15, []byte{0x66, 0x46, 0x89, 0x3C, 0x38}}, + {x86.RAX, x86.R15, 1, x86.R15, []byte{0x46, 0x88, 0x3C, 0x38}}, + {x86.RCX, x86.R14, 8, x86.R14, []byte{0x4E, 0x89, 0x34, 0x31}}, + {x86.RCX, x86.R14, 4, x86.R14, []byte{0x46, 0x89, 0x34, 0x31}}, + {x86.RCX, x86.R14, 2, x86.R14, []byte{0x66, 0x46, 0x89, 0x34, 0x31}}, + {x86.RCX, x86.R14, 1, x86.R14, []byte{0x46, 0x88, 0x34, 0x31}}, + {x86.RDX, x86.R13, 8, x86.R13, []byte{0x4E, 0x89, 0x2C, 0x2A}}, + {x86.RDX, x86.R13, 4, x86.R13, []byte{0x46, 0x89, 0x2C, 0x2A}}, + {x86.RDX, x86.R13, 2, x86.R13, []byte{0x66, 0x46, 0x89, 0x2C, 0x2A}}, + {x86.RDX, x86.R13, 1, x86.R13, []byte{0x46, 0x88, 0x2C, 0x2A}}, + {x86.RBX, x86.R12, 8, x86.R12, []byte{0x4E, 0x89, 0x24, 0x23}}, + {x86.RBX, x86.R12, 4, x86.R12, []byte{0x46, 0x89, 0x24, 0x23}}, + {x86.RBX, x86.R12, 2, x86.R12, []byte{0x66, 0x46, 0x89, 0x24, 0x23}}, + {x86.RBX, x86.R12, 1, x86.R12, []byte{0x46, 0x88, 0x24, 0x23}}, + {x86.RSP, x86.R11, 8, x86.R11, []byte{0x4E, 0x89, 0x1C, 0x1C}}, + {x86.RSP, x86.R11, 4, x86.R11, []byte{0x46, 0x89, 0x1C, 0x1C}}, + {x86.RSP, x86.R11, 2, x86.R11, []byte{0x66, 0x46, 0x89, 0x1C, 0x1C}}, + {x86.RSP, x86.R11, 1, x86.R11, []byte{0x46, 0x88, 0x1C, 0x1C}}, + {x86.RBP, x86.R10, 8, x86.R10, []byte{0x4E, 0x89, 0x54, 0x15, 0x00}}, + {x86.RBP, x86.R10, 4, x86.R10, []byte{0x46, 0x89, 0x54, 0x15, 0x00}}, + {x86.RBP, x86.R10, 2, x86.R10, []byte{0x66, 0x46, 0x89, 0x54, 0x15, 0x00}}, + {x86.RBP, x86.R10, 1, x86.R10, []byte{0x46, 0x88, 0x54, 0x15, 0x00}}, + {x86.RSI, x86.R9, 8, x86.R9, []byte{0x4E, 0x89, 0x0C, 0x0E}}, + {x86.RSI, x86.R9, 4, x86.R9, []byte{0x46, 0x89, 0x0C, 0x0E}}, + {x86.RSI, x86.R9, 2, x86.R9, []byte{0x66, 0x46, 0x89, 0x0C, 0x0E}}, + {x86.RSI, x86.R9, 1, x86.R9, []byte{0x46, 0x88, 0x0C, 0x0E}}, + {x86.RDI, x86.R8, 8, x86.R8, []byte{0x4E, 0x89, 0x04, 0x07}}, + {x86.RDI, x86.R8, 4, x86.R8, []byte{0x46, 0x89, 0x04, 0x07}}, + {x86.RDI, x86.R8, 2, x86.R8, []byte{0x66, 0x46, 0x89, 0x04, 0x07}}, + {x86.RDI, x86.R8, 1, x86.R8, []byte{0x46, 0x88, 0x04, 0x07}}, + {x86.R8, x86.RDI, 8, x86.RDI, []byte{0x49, 0x89, 0x3C, 0x38}}, + {x86.R8, x86.RDI, 4, x86.RDI, []byte{0x41, 0x89, 0x3C, 0x38}}, + {x86.R8, x86.RDI, 2, x86.RDI, []byte{0x66, 0x41, 0x89, 0x3C, 0x38}}, + {x86.R8, x86.RDI, 1, x86.RDI, []byte{0x41, 0x88, 0x3C, 0x38}}, + {x86.R9, x86.RSI, 8, x86.RSI, []byte{0x49, 0x89, 0x34, 0x31}}, + {x86.R9, x86.RSI, 4, x86.RSI, []byte{0x41, 0x89, 0x34, 0x31}}, + {x86.R9, x86.RSI, 2, x86.RSI, []byte{0x66, 0x41, 0x89, 0x34, 0x31}}, + {x86.R9, x86.RSI, 1, x86.RSI, []byte{0x41, 0x88, 0x34, 0x31}}, + {x86.R10, x86.RBP, 8, x86.RBP, []byte{0x49, 0x89, 0x2C, 0x2A}}, + {x86.R10, x86.RBP, 4, x86.RBP, []byte{0x41, 0x89, 0x2C, 0x2A}}, + {x86.R10, x86.RBP, 2, x86.RBP, []byte{0x66, 0x41, 0x89, 0x2C, 0x2A}}, + {x86.R10, x86.RBP, 1, x86.RBP, []byte{0x41, 0x88, 0x2C, 0x2A}}, + {x86.R11, x86.RSP, 8, x86.RSP, []byte{0x4A, 0x89, 0x24, 0x1C}}, + {x86.R11, x86.RSP, 4, x86.RSP, []byte{0x42, 0x89, 0x24, 0x1C}}, + {x86.R11, x86.RSP, 2, x86.RSP, []byte{0x66, 0x42, 0x89, 0x24, 0x1C}}, + {x86.R11, x86.RSP, 1, x86.RSP, []byte{0x42, 0x88, 0x24, 0x1C}}, + {x86.R12, x86.RBX, 8, x86.RBX, []byte{0x49, 0x89, 0x1C, 0x1C}}, + {x86.R12, x86.RBX, 4, x86.RBX, []byte{0x41, 0x89, 0x1C, 0x1C}}, + {x86.R12, x86.RBX, 2, x86.RBX, []byte{0x66, 0x41, 0x89, 0x1C, 0x1C}}, + {x86.R12, x86.RBX, 1, x86.RBX, []byte{0x41, 0x88, 0x1C, 0x1C}}, + {x86.R13, x86.RDX, 8, x86.RDX, []byte{0x49, 0x89, 0x54, 0x15, 0x00}}, + {x86.R13, x86.RDX, 4, x86.RDX, []byte{0x41, 0x89, 0x54, 0x15, 0x00}}, + {x86.R13, x86.RDX, 2, x86.RDX, []byte{0x66, 0x41, 0x89, 0x54, 0x15, 0x00}}, + {x86.R13, x86.RDX, 1, x86.RDX, []byte{0x41, 0x88, 0x54, 0x15, 0x00}}, + {x86.R14, x86.RCX, 8, x86.RCX, []byte{0x49, 0x89, 0x0C, 0x0E}}, + {x86.R14, x86.RCX, 4, x86.RCX, []byte{0x41, 0x89, 0x0C, 0x0E}}, + {x86.R14, x86.RCX, 2, x86.RCX, []byte{0x66, 0x41, 0x89, 0x0C, 0x0E}}, + {x86.R14, x86.RCX, 1, x86.RCX, []byte{0x41, 0x88, 0x0C, 0x0E}}, + {x86.R15, x86.RAX, 8, x86.RAX, []byte{0x49, 0x89, 0x04, 0x07}}, + {x86.R15, x86.RAX, 4, x86.RAX, []byte{0x41, 0x89, 0x04, 0x07}}, + {x86.R15, x86.RAX, 2, x86.RAX, []byte{0x66, 0x41, 0x89, 0x04, 0x07}}, + {x86.R15, x86.RAX, 1, x86.RAX, []byte{0x41, 0x88, 0x04, 0x07}}, + } + + for _, pattern := range usagePatterns { + t.Logf("store %dB [%s+%s], %s", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) + code := x86.StoreDynamicRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x86/Store_test.go b/src/x86/Store_test.go new file mode 100644 index 0000000..e9e1e26 --- /dev/null +++ b/src/x86/Store_test.go @@ -0,0 +1,305 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestStoreNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Offset byte + Length byte + Number int + Code []byte + }{ + // No offset + {x86.RAX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RAX, 0, 4, 0x7F, []byte{0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RAX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x00, 0x7F, 0x00}}, + {x86.RAX, 0, 1, 0x7F, []byte{0xC6, 0x00, 0x7F}}, + {x86.RCX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RCX, 0, 4, 0x7F, []byte{0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RCX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x01, 0x7F, 0x00}}, + {x86.RCX, 0, 1, 0x7F, []byte{0xC6, 0x01, 0x7F}}, + {x86.RDX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDX, 0, 4, 0x7F, []byte{0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x02, 0x7F, 0x00}}, + {x86.RDX, 0, 1, 0x7F, []byte{0xC6, 0x02, 0x7F}}, + {x86.RBX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBX, 0, 4, 0x7F, []byte{0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x03, 0x7F, 0x00}}, + {x86.RBX, 0, 1, 0x7F, []byte{0xC6, 0x03, 0x7F}}, + {x86.RSP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSP, 0, 4, 0x7F, []byte{0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x04, 0x24, 0x7F, 0x00}}, + {x86.RSP, 0, 1, 0x7F, []byte{0xC6, 0x04, 0x24, 0x7F}}, + {x86.RBP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBP, 0, 4, 0x7F, []byte{0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x00, 0x7F, 0x00}}, + {x86.RBP, 0, 1, 0x7F, []byte{0xC6, 0x45, 0x00, 0x7F}}, + {x86.RSI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSI, 0, 4, 0x7F, []byte{0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x06, 0x7F, 0x00}}, + {x86.RSI, 0, 1, 0x7F, []byte{0xC6, 0x06, 0x7F}}, + {x86.RDI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDI, 0, 4, 0x7F, []byte{0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x07, 0x7F, 0x00}}, + {x86.RDI, 0, 1, 0x7F, []byte{0xC6, 0x07, 0x7F}}, + {x86.R8, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R8, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R8, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x00, 0x7F, 0x00}}, + {x86.R8, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x00, 0x7F}}, + {x86.R9, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R9, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R9, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x01, 0x7F, 0x00}}, + {x86.R9, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x01, 0x7F}}, + {x86.R10, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R10, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R10, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x02, 0x7F, 0x00}}, + {x86.R10, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x02, 0x7F}}, + {x86.R11, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R11, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R11, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x03, 0x7F, 0x00}}, + {x86.R11, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x03, 0x7F}}, + {x86.R12, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R12, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R12, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x24, 0x7F, 0x00}}, + {x86.R12, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x24, 0x7F}}, + {x86.R13, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R13, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R13, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x45, 0x00, 0x7F, 0x00}}, + {x86.R13, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x45, 0x00, 0x7F}}, + {x86.R14, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R14, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R14, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x06, 0x7F, 0x00}}, + {x86.R14, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x06, 0x7F}}, + {x86.R15, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R15, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R15, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x07, 0x7F, 0x00}}, + {x86.R15, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x07, 0x7F}}, + + // Offset of 1 + {x86.RAX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RAX, 1, 4, 0x7F, []byte{0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RAX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, + {x86.RAX, 1, 1, 0x7F, []byte{0xC6, 0x40, 0x01, 0x7F}}, + {x86.RCX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RCX, 1, 4, 0x7F, []byte{0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RCX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x41, 0x01, 0x7F, 0x00}}, + {x86.RCX, 1, 1, 0x7F, []byte{0xC6, 0x41, 0x01, 0x7F}}, + {x86.RDX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDX, 1, 4, 0x7F, []byte{0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x42, 0x01, 0x7F, 0x00}}, + {x86.RDX, 1, 1, 0x7F, []byte{0xC6, 0x42, 0x01, 0x7F}}, + {x86.RBX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBX, 1, 4, 0x7F, []byte{0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, + {x86.RBX, 1, 1, 0x7F, []byte{0xC6, 0x43, 0x01, 0x7F}}, + {x86.RSP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSP, 1, 4, 0x7F, []byte{0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, + {x86.RSP, 1, 1, 0x7F, []byte{0xC6, 0x44, 0x24, 0x01, 0x7F}}, + {x86.RBP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBP, 1, 4, 0x7F, []byte{0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, + {x86.RBP, 1, 1, 0x7F, []byte{0xC6, 0x45, 0x01, 0x7F}}, + {x86.RSI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSI, 1, 4, 0x7F, []byte{0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, + {x86.RSI, 1, 1, 0x7F, []byte{0xC6, 0x46, 0x01, 0x7F}}, + {x86.RDI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDI, 1, 4, 0x7F, []byte{0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, + {x86.RDI, 1, 1, 0x7F, []byte{0xC6, 0x47, 0x01, 0x7F}}, + {x86.R8, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R8, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R8, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, + {x86.R8, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x40, 0x01, 0x7F}}, + {x86.R9, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R9, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R9, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x41, 0x01, 0x7F, 0x00}}, + {x86.R9, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x41, 0x01, 0x7F}}, + {x86.R10, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R10, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R10, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x42, 0x01, 0x7F, 0x00}}, + {x86.R10, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x42, 0x01, 0x7F}}, + {x86.R11, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R11, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R11, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, + {x86.R11, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x43, 0x01, 0x7F}}, + {x86.R12, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R12, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R12, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, + {x86.R12, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x44, 0x24, 0x01, 0x7F}}, + {x86.R13, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R13, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R13, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, + {x86.R13, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x45, 0x01, 0x7F}}, + {x86.R14, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R14, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R14, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, + {x86.R14, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x46, 0x01, 0x7F}}, + {x86.R15, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R15, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R15, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, + {x86.R15, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x47, 0x01, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("store %dB [%s+%d], %d", pattern.Length, pattern.Register, pattern.Offset, pattern.Number) + code := x86.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.Length, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestStoreRegister(t *testing.T) { + usagePatterns := []struct { + RegisterTo cpu.Register + Offset byte + Length byte + RegisterFrom cpu.Register + Code []byte + }{ + // No offset + {x86.RAX, 0, 8, x86.R15, []byte{0x4C, 0x89, 0x38}}, + {x86.RAX, 0, 4, x86.R15, []byte{0x44, 0x89, 0x38}}, + {x86.RAX, 0, 2, x86.R15, []byte{0x66, 0x44, 0x89, 0x38}}, + {x86.RAX, 0, 1, x86.R15, []byte{0x44, 0x88, 0x38}}, + {x86.RCX, 0, 8, x86.R14, []byte{0x4C, 0x89, 0x31}}, + {x86.RCX, 0, 4, x86.R14, []byte{0x44, 0x89, 0x31}}, + {x86.RCX, 0, 2, x86.R14, []byte{0x66, 0x44, 0x89, 0x31}}, + {x86.RCX, 0, 1, x86.R14, []byte{0x44, 0x88, 0x31}}, + {x86.RDX, 0, 8, x86.R13, []byte{0x4C, 0x89, 0x2A}}, + {x86.RDX, 0, 4, x86.R13, []byte{0x44, 0x89, 0x2A}}, + {x86.RDX, 0, 2, x86.R13, []byte{0x66, 0x44, 0x89, 0x2A}}, + {x86.RDX, 0, 1, x86.R13, []byte{0x44, 0x88, 0x2A}}, + {x86.RBX, 0, 8, x86.R12, []byte{0x4C, 0x89, 0x23}}, + {x86.RBX, 0, 4, x86.R12, []byte{0x44, 0x89, 0x23}}, + {x86.RBX, 0, 2, x86.R12, []byte{0x66, 0x44, 0x89, 0x23}}, + {x86.RBX, 0, 1, x86.R12, []byte{0x44, 0x88, 0x23}}, + {x86.RSP, 0, 8, x86.R11, []byte{0x4C, 0x89, 0x1C, 0x24}}, + {x86.RSP, 0, 4, x86.R11, []byte{0x44, 0x89, 0x1C, 0x24}}, + {x86.RSP, 0, 2, x86.R11, []byte{0x66, 0x44, 0x89, 0x1C, 0x24}}, + {x86.RSP, 0, 1, x86.R11, []byte{0x44, 0x88, 0x1C, 0x24}}, + {x86.RBP, 0, 8, x86.R10, []byte{0x4C, 0x89, 0x55, 0x00}}, + {x86.RBP, 0, 4, x86.R10, []byte{0x44, 0x89, 0x55, 0x00}}, + {x86.RBP, 0, 2, x86.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x00}}, + {x86.RBP, 0, 1, x86.R10, []byte{0x44, 0x88, 0x55, 0x00}}, + {x86.RSI, 0, 8, x86.R9, []byte{0x4C, 0x89, 0x0E}}, + {x86.RSI, 0, 4, x86.R9, []byte{0x44, 0x89, 0x0E}}, + {x86.RSI, 0, 2, x86.R9, []byte{0x66, 0x44, 0x89, 0x0E}}, + {x86.RSI, 0, 1, x86.R9, []byte{0x44, 0x88, 0x0E}}, + {x86.RDI, 0, 8, x86.R8, []byte{0x4C, 0x89, 0x07}}, + {x86.RDI, 0, 4, x86.R8, []byte{0x44, 0x89, 0x07}}, + {x86.RDI, 0, 2, x86.R8, []byte{0x66, 0x44, 0x89, 0x07}}, + {x86.RDI, 0, 1, x86.R8, []byte{0x44, 0x88, 0x07}}, + {x86.R8, 0, 8, x86.RDI, []byte{0x49, 0x89, 0x38}}, + {x86.R8, 0, 4, x86.RDI, []byte{0x41, 0x89, 0x38}}, + {x86.R8, 0, 2, x86.RDI, []byte{0x66, 0x41, 0x89, 0x38}}, + {x86.R8, 0, 1, x86.RDI, []byte{0x41, 0x88, 0x38}}, + {x86.R9, 0, 8, x86.RSI, []byte{0x49, 0x89, 0x31}}, + {x86.R9, 0, 4, x86.RSI, []byte{0x41, 0x89, 0x31}}, + {x86.R9, 0, 2, x86.RSI, []byte{0x66, 0x41, 0x89, 0x31}}, + {x86.R9, 0, 1, x86.RSI, []byte{0x41, 0x88, 0x31}}, + {x86.R10, 0, 8, x86.RBP, []byte{0x49, 0x89, 0x2A}}, + {x86.R10, 0, 4, x86.RBP, []byte{0x41, 0x89, 0x2A}}, + {x86.R10, 0, 2, x86.RBP, []byte{0x66, 0x41, 0x89, 0x2A}}, + {x86.R10, 0, 1, x86.RBP, []byte{0x41, 0x88, 0x2A}}, + {x86.R11, 0, 8, x86.RSP, []byte{0x49, 0x89, 0x23}}, + {x86.R11, 0, 4, x86.RSP, []byte{0x41, 0x89, 0x23}}, + {x86.R11, 0, 2, x86.RSP, []byte{0x66, 0x41, 0x89, 0x23}}, + {x86.R11, 0, 1, x86.RSP, []byte{0x41, 0x88, 0x23}}, + {x86.R12, 0, 8, x86.RBX, []byte{0x49, 0x89, 0x1C, 0x24}}, + {x86.R12, 0, 4, x86.RBX, []byte{0x41, 0x89, 0x1C, 0x24}}, + {x86.R12, 0, 2, x86.RBX, []byte{0x66, 0x41, 0x89, 0x1C, 0x24}}, + {x86.R12, 0, 1, x86.RBX, []byte{0x41, 0x88, 0x1C, 0x24}}, + {x86.R13, 0, 8, x86.RDX, []byte{0x49, 0x89, 0x55, 0x00}}, + {x86.R13, 0, 4, x86.RDX, []byte{0x41, 0x89, 0x55, 0x00}}, + {x86.R13, 0, 2, x86.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x00}}, + {x86.R13, 0, 1, x86.RDX, []byte{0x41, 0x88, 0x55, 0x00}}, + {x86.R14, 0, 8, x86.RCX, []byte{0x49, 0x89, 0x0E}}, + {x86.R14, 0, 4, x86.RCX, []byte{0x41, 0x89, 0x0E}}, + {x86.R14, 0, 2, x86.RCX, []byte{0x66, 0x41, 0x89, 0x0E}}, + {x86.R14, 0, 1, x86.RCX, []byte{0x41, 0x88, 0x0E}}, + {x86.R15, 0, 8, x86.RAX, []byte{0x49, 0x89, 0x07}}, + {x86.R15, 0, 4, x86.RAX, []byte{0x41, 0x89, 0x07}}, + {x86.R15, 0, 2, x86.RAX, []byte{0x66, 0x41, 0x89, 0x07}}, + {x86.R15, 0, 1, x86.RAX, []byte{0x41, 0x88, 0x07}}, + + // Offset of 1 + {x86.RAX, 1, 8, x86.R15, []byte{0x4C, 0x89, 0x78, 0x01}}, + {x86.RAX, 1, 4, x86.R15, []byte{0x44, 0x89, 0x78, 0x01}}, + {x86.RAX, 1, 2, x86.R15, []byte{0x66, 0x44, 0x89, 0x78, 0x01}}, + {x86.RAX, 1, 1, x86.R15, []byte{0x44, 0x88, 0x78, 0x01}}, + {x86.RCX, 1, 8, x86.R14, []byte{0x4C, 0x89, 0x71, 0x01}}, + {x86.RCX, 1, 4, x86.R14, []byte{0x44, 0x89, 0x71, 0x01}}, + {x86.RCX, 1, 2, x86.R14, []byte{0x66, 0x44, 0x89, 0x71, 0x01}}, + {x86.RCX, 1, 1, x86.R14, []byte{0x44, 0x88, 0x71, 0x01}}, + {x86.RDX, 1, 8, x86.R13, []byte{0x4C, 0x89, 0x6A, 0x01}}, + {x86.RDX, 1, 4, x86.R13, []byte{0x44, 0x89, 0x6A, 0x01}}, + {x86.RDX, 1, 2, x86.R13, []byte{0x66, 0x44, 0x89, 0x6A, 0x01}}, + {x86.RDX, 1, 1, x86.R13, []byte{0x44, 0x88, 0x6A, 0x01}}, + {x86.RBX, 1, 8, x86.R12, []byte{0x4C, 0x89, 0x63, 0x01}}, + {x86.RBX, 1, 4, x86.R12, []byte{0x44, 0x89, 0x63, 0x01}}, + {x86.RBX, 1, 2, x86.R12, []byte{0x66, 0x44, 0x89, 0x63, 0x01}}, + {x86.RBX, 1, 1, x86.R12, []byte{0x44, 0x88, 0x63, 0x01}}, + {x86.RSP, 1, 8, x86.R11, []byte{0x4C, 0x89, 0x5C, 0x24, 0x01}}, + {x86.RSP, 1, 4, x86.R11, []byte{0x44, 0x89, 0x5C, 0x24, 0x01}}, + {x86.RSP, 1, 2, x86.R11, []byte{0x66, 0x44, 0x89, 0x5C, 0x24, 0x01}}, + {x86.RSP, 1, 1, x86.R11, []byte{0x44, 0x88, 0x5C, 0x24, 0x01}}, + {x86.RBP, 1, 8, x86.R10, []byte{0x4C, 0x89, 0x55, 0x01}}, + {x86.RBP, 1, 4, x86.R10, []byte{0x44, 0x89, 0x55, 0x01}}, + {x86.RBP, 1, 2, x86.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x01}}, + {x86.RBP, 1, 1, x86.R10, []byte{0x44, 0x88, 0x55, 0x01}}, + {x86.RSI, 1, 8, x86.R9, []byte{0x4C, 0x89, 0x4E, 0x01}}, + {x86.RSI, 1, 4, x86.R9, []byte{0x44, 0x89, 0x4E, 0x01}}, + {x86.RSI, 1, 2, x86.R9, []byte{0x66, 0x44, 0x89, 0x4E, 0x01}}, + {x86.RSI, 1, 1, x86.R9, []byte{0x44, 0x88, 0x4E, 0x01}}, + {x86.RDI, 1, 8, x86.R8, []byte{0x4C, 0x89, 0x47, 0x01}}, + {x86.RDI, 1, 4, x86.R8, []byte{0x44, 0x89, 0x47, 0x01}}, + {x86.RDI, 1, 2, x86.R8, []byte{0x66, 0x44, 0x89, 0x47, 0x01}}, + {x86.RDI, 1, 1, x86.R8, []byte{0x44, 0x88, 0x47, 0x01}}, + {x86.R8, 1, 8, x86.RDI, []byte{0x49, 0x89, 0x78, 0x01}}, + {x86.R8, 1, 4, x86.RDI, []byte{0x41, 0x89, 0x78, 0x01}}, + {x86.R8, 1, 2, x86.RDI, []byte{0x66, 0x41, 0x89, 0x78, 0x01}}, + {x86.R8, 1, 1, x86.RDI, []byte{0x41, 0x88, 0x78, 0x01}}, + {x86.R9, 1, 8, x86.RSI, []byte{0x49, 0x89, 0x71, 0x01}}, + {x86.R9, 1, 4, x86.RSI, []byte{0x41, 0x89, 0x71, 0x01}}, + {x86.R9, 1, 2, x86.RSI, []byte{0x66, 0x41, 0x89, 0x71, 0x01}}, + {x86.R9, 1, 1, x86.RSI, []byte{0x41, 0x88, 0x71, 0x01}}, + {x86.R10, 1, 8, x86.RBP, []byte{0x49, 0x89, 0x6A, 0x01}}, + {x86.R10, 1, 4, x86.RBP, []byte{0x41, 0x89, 0x6A, 0x01}}, + {x86.R10, 1, 2, x86.RBP, []byte{0x66, 0x41, 0x89, 0x6A, 0x01}}, + {x86.R10, 1, 1, x86.RBP, []byte{0x41, 0x88, 0x6A, 0x01}}, + {x86.R11, 1, 8, x86.RSP, []byte{0x49, 0x89, 0x63, 0x01}}, + {x86.R11, 1, 4, x86.RSP, []byte{0x41, 0x89, 0x63, 0x01}}, + {x86.R11, 1, 2, x86.RSP, []byte{0x66, 0x41, 0x89, 0x63, 0x01}}, + {x86.R11, 1, 1, x86.RSP, []byte{0x41, 0x88, 0x63, 0x01}}, + {x86.R12, 1, 8, x86.RBX, []byte{0x49, 0x89, 0x5C, 0x24, 0x01}}, + {x86.R12, 1, 4, x86.RBX, []byte{0x41, 0x89, 0x5C, 0x24, 0x01}}, + {x86.R12, 1, 2, x86.RBX, []byte{0x66, 0x41, 0x89, 0x5C, 0x24, 0x01}}, + {x86.R12, 1, 1, x86.RBX, []byte{0x41, 0x88, 0x5C, 0x24, 01}}, + {x86.R13, 1, 8, x86.RDX, []byte{0x49, 0x89, 0x55, 0x01}}, + {x86.R13, 1, 4, x86.RDX, []byte{0x41, 0x89, 0x55, 0x01}}, + {x86.R13, 1, 2, x86.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x01}}, + {x86.R13, 1, 1, x86.RDX, []byte{0x41, 0x88, 0x55, 0x01}}, + {x86.R14, 1, 8, x86.RCX, []byte{0x49, 0x89, 0x4E, 0x01}}, + {x86.R14, 1, 4, x86.RCX, []byte{0x41, 0x89, 0x4E, 0x01}}, + {x86.R14, 1, 2, x86.RCX, []byte{0x66, 0x41, 0x89, 0x4E, 0x01}}, + {x86.R14, 1, 1, x86.RCX, []byte{0x41, 0x88, 0x4E, 0x01}}, + {x86.R15, 1, 8, x86.RAX, []byte{0x49, 0x89, 0x47, 0x01}}, + {x86.R15, 1, 4, x86.RAX, []byte{0x41, 0x89, 0x47, 0x01}}, + {x86.R15, 1, 2, x86.RAX, []byte{0x66, 0x41, 0x89, 0x47, 0x01}}, + {x86.R15, 1, 1, x86.RAX, []byte{0x41, 0x88, 0x47, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("store %dB [%s+%d], %s", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) + code := x86.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Sub.go b/src/x86/Sub.go similarity index 97% rename from src/x64/Sub.go rename to src/x86/Sub.go index 57332fd..40bb4ac 100644 --- a/src/x64/Sub.go +++ b/src/x86/Sub.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Sub_test.go b/src/x86/Sub_test.go new file mode 100644 index 0000000..7577b65 --- /dev/null +++ b/src/x86/Sub_test.go @@ -0,0 +1,88 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestSubRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0x83, 0xE8, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0x83, 0xE9, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0x83, 0xEA, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0x83, 0xEB, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0x83, 0xEC, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0x83, 0xED, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0x83, 0xEE, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0x83, 0xEF, 0x01}}, + {x86.R8, 1, []byte{0x49, 0x83, 0xE8, 0x01}}, + {x86.R9, 1, []byte{0x49, 0x83, 0xE9, 0x01}}, + {x86.R10, 1, []byte{0x49, 0x83, 0xEA, 0x01}}, + {x86.R11, 1, []byte{0x49, 0x83, 0xEB, 0x01}}, + {x86.R12, 1, []byte{0x49, 0x83, 0xEC, 0x01}}, + {x86.R13, 1, []byte{0x49, 0x83, 0xED, 0x01}}, + {x86.R14, 1, []byte{0x49, 0x83, 0xEE, 0x01}}, + {x86.R15, 1, []byte{0x49, 0x83, 0xEF, 0x01}}, + + {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("sub %s, %x", pattern.Register, pattern.Number) + code := x86.SubRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestSubRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, []byte{0x4C, 0x29, 0xF8}}, + {x86.RCX, x86.R14, []byte{0x4C, 0x29, 0xF1}}, + {x86.RDX, x86.R13, []byte{0x4C, 0x29, 0xEA}}, + {x86.RBX, x86.R12, []byte{0x4C, 0x29, 0xE3}}, + {x86.RSP, x86.R11, []byte{0x4C, 0x29, 0xDC}}, + {x86.RBP, x86.R10, []byte{0x4C, 0x29, 0xD5}}, + {x86.RSI, x86.R9, []byte{0x4C, 0x29, 0xCE}}, + {x86.RDI, x86.R8, []byte{0x4C, 0x29, 0xC7}}, + {x86.R8, x86.RDI, []byte{0x49, 0x29, 0xF8}}, + {x86.R9, x86.RSI, []byte{0x49, 0x29, 0xF1}}, + {x86.R10, x86.RBP, []byte{0x49, 0x29, 0xEA}}, + {x86.R11, x86.RSP, []byte{0x49, 0x29, 0xE3}}, + {x86.R12, x86.RBX, []byte{0x49, 0x29, 0xDC}}, + {x86.R13, x86.RDX, []byte{0x49, 0x29, 0xD5}}, + {x86.R14, x86.RCX, []byte{0x49, 0x29, 0xCE}}, + {x86.R15, x86.RAX, []byte{0x49, 0x29, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("sub %s, %s", pattern.Left, pattern.Right) + code := x86.SubRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Syscall.go b/src/x86/Syscall.go similarity index 91% rename from src/x64/Syscall.go rename to src/x86/Syscall.go index 31e2b96..2e2005a 100644 --- a/src/x64/Syscall.go +++ b/src/x86/Syscall.go @@ -1,4 +1,4 @@ -package x64 +package x86 // Syscall is the primary way to communicate with the OS kernel. func Syscall(code []byte) []byte { diff --git a/src/x64/Xor.go b/src/x86/Xor.go similarity index 97% rename from src/x64/Xor.go rename to src/x86/Xor.go index c36a805..5c50ee6 100644 --- a/src/x64/Xor.go +++ b/src/x86/Xor.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Xor_test.go b/src/x86/Xor_test.go new file mode 100644 index 0000000..dcf6afa --- /dev/null +++ b/src/x86/Xor_test.go @@ -0,0 +1,88 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestXorRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0x83, 0xF0, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0x83, 0xF1, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0x83, 0xF2, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0x83, 0xF3, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0x83, 0xF4, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0x83, 0xF5, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0x83, 0xF6, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0x83, 0xF7, 0x01}}, + {x86.R8, 1, []byte{0x49, 0x83, 0xF0, 0x01}}, + {x86.R9, 1, []byte{0x49, 0x83, 0xF1, 0x01}}, + {x86.R10, 1, []byte{0x49, 0x83, 0xF2, 0x01}}, + {x86.R11, 1, []byte{0x49, 0x83, 0xF3, 0x01}}, + {x86.R12, 1, []byte{0x49, 0x83, 0xF4, 0x01}}, + {x86.R13, 1, []byte{0x49, 0x83, 0xF5, 0x01}}, + {x86.R14, 1, []byte{0x49, 0x83, 0xF6, 0x01}}, + {x86.R15, 1, []byte{0x49, 0x83, 0xF7, 0x01}}, + + {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF7, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("xor %s, %x", pattern.Register, pattern.Number) + code := x86.XorRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestXorRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, []byte{0x4C, 0x31, 0xF8}}, + {x86.RCX, x86.R14, []byte{0x4C, 0x31, 0xF1}}, + {x86.RDX, x86.R13, []byte{0x4C, 0x31, 0xEA}}, + {x86.RBX, x86.R12, []byte{0x4C, 0x31, 0xE3}}, + {x86.RSP, x86.R11, []byte{0x4C, 0x31, 0xDC}}, + {x86.RBP, x86.R10, []byte{0x4C, 0x31, 0xD5}}, + {x86.RSI, x86.R9, []byte{0x4C, 0x31, 0xCE}}, + {x86.RDI, x86.R8, []byte{0x4C, 0x31, 0xC7}}, + {x86.R8, x86.RDI, []byte{0x49, 0x31, 0xF8}}, + {x86.R9, x86.RSI, []byte{0x49, 0x31, 0xF1}}, + {x86.R10, x86.RBP, []byte{0x49, 0x31, 0xEA}}, + {x86.R11, x86.RSP, []byte{0x49, 0x31, 0xE3}}, + {x86.R12, x86.RBX, []byte{0x49, 0x31, 0xDC}}, + {x86.R13, x86.RDX, []byte{0x49, 0x31, 0xD5}}, + {x86.R14, x86.RCX, []byte{0x49, 0x31, 0xCE}}, + {x86.R15, x86.RAX, []byte{0x49, 0x31, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("xor %s, %s", pattern.Left, pattern.Right) + code := x86.XorRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/encode.go b/src/x86/encode.go similarity index 98% rename from src/x64/encode.go rename to src/x86/encode.go index 6e872b5..1306d52 100644 --- a/src/x64/encode.go +++ b/src/x86/encode.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x64/encodeNum.go b/src/x86/encodeNum.go similarity index 97% rename from src/x64/encodeNum.go rename to src/x86/encodeNum.go index c6db324..1611604 100644 --- a/src/x64/encodeNum.go +++ b/src/x86/encodeNum.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "encoding/binary" diff --git a/src/x64/memoryAccess.go b/src/x86/memoryAccess.go similarity index 98% rename from src/x64/memoryAccess.go rename to src/x86/memoryAccess.go index c1c658c..b1dd605 100644 --- a/src/x64/memoryAccess.go +++ b/src/x86/memoryAccess.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x64/memoryAccessDynamic.go b/src/x86/memoryAccessDynamic.go similarity index 98% rename from src/x64/memoryAccessDynamic.go rename to src/x86/memoryAccessDynamic.go index c9e323c..d47988e 100644 --- a/src/x64/memoryAccessDynamic.go +++ b/src/x86/memoryAccessDynamic.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/x64_test.go b/src/x86/x64_test.go new file mode 100644 index 0000000..f9e88e4 --- /dev/null +++ b/src/x86/x64_test.go @@ -0,0 +1,19 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestX64(t *testing.T) { + assert.DeepEqual(t, x86.AlignStack(nil), []byte{0x48, 0x83, 0xE4, 0xF0}) + assert.DeepEqual(t, x86.Call(nil, 1), []byte{0xE8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x86.CallAtAddress(nil, 1), []byte{0xFF, 0x15, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x86.ExtendRAXToRDX(nil), []byte{0x48, 0x99}) + assert.DeepEqual(t, x86.MoveRegisterNumber(nil, 0, 1), []byte{0xB8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x86.MoveRegisterNumber(nil, 1, 1), []byte{0xB9, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x86.Return(nil), []byte{0xC3}) + assert.DeepEqual(t, x86.Syscall(nil), []byte{0x0F, 0x05}) +} From d4f9071ee45cb1a01a840456c474bf7420b8a6ff Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 5 Feb 2025 23:16:18 +0100 Subject: [PATCH 0650/1012] Renamed x64 to x86 --- docs/todo.md | 2 +- src/asm/Finalize.go | 114 ++++----- src/compiler/Result.go | 22 +- src/core/CompileAssignDivision.go | 6 +- src/core/CompileCall.go | 4 +- src/core/ExecuteRegisterNumber.go | 10 +- src/core/ExecuteRegisterRegister.go | 10 +- src/core/NewFunction.go | 14 +- src/core/ResolveTypes.go | 4 +- src/readme.md | 2 +- src/x64/Add_test.go | 88 ------- src/x64/And_test.go | 88 ------- src/x64/Compare_test.go | 88 ------- src/x64/Div_test.go | 39 --- src/x64/Load_test.go | 157 ------------ src/x64/Move_test.go | 108 --------- src/x64/Mul_test.go | 88 ------- src/x64/Negate_test.go | 39 --- src/x64/Or_test.go | 88 ------- src/x64/Pop_test.go | 39 --- src/x64/Push_test.go | 39 --- src/x64/Registers_test.go | 12 - src/x64/Shift_test.go | 71 ------ src/x64/StoreDynamic_test.go | 171 ------------- src/x64/Store_test.go | 305 ------------------------ src/x64/Sub_test.go | 88 ------- src/x64/Xor_test.go | 88 ------- src/x64/x64_test.go | 19 -- src/{x64 => x86}/Add.go | 2 +- src/x86/Add_test.go | 88 +++++++ src/{x64 => x86}/AlignStack.go | 2 +- src/{x64 => x86}/And.go | 2 +- src/x86/And_test.go | 88 +++++++ src/{x64 => x86}/Call.go | 2 +- src/{x64 => x86}/Compare.go | 2 +- src/x86/Compare_test.go | 88 +++++++ src/{x64 => x86}/Div.go | 2 +- src/x86/Div_test.go | 39 +++ src/{x64 => x86}/ExtendRAXToRDX.go | 2 +- src/{x64 => x86}/Jump.go | 2 +- src/{x64 => x86}/Jump_test.go | 18 +- src/{x64 => x86}/Load.go | 2 +- src/x86/Load_test.go | 157 ++++++++++++ src/{x64 => x86}/ModRM.go | 2 +- src/{x64 => x86}/ModRM_test.go | 6 +- src/{x64 => x86}/Move.go | 2 +- src/x86/Move_test.go | 108 +++++++++ src/{x64 => x86}/Mul.go | 2 +- src/x86/Mul_test.go | 88 +++++++ src/{x64 => x86}/Negate.go | 2 +- src/x86/Negate_test.go | 39 +++ src/{x64 => x86}/Or.go | 2 +- src/x86/Or_test.go | 88 +++++++ src/{x64 => x86}/Pop.go | 2 +- src/x86/Pop_test.go | 39 +++ src/{x64 => x86}/Push.go | 2 +- src/x86/Push_test.go | 39 +++ src/{x64 => x86}/REX.go | 2 +- src/{x64 => x86}/REX_test.go | 6 +- src/{x64 => x86}/Registers.go | 2 +- src/x86/Registers_test.go | 12 + src/{x64 => x86}/Return.go | 2 +- src/{x64 => x86}/SIB.go | 2 +- src/{x64 => x86}/SIB_test.go | 6 +- src/{x64 => x86}/Shift.go | 2 +- src/x86/Shift_test.go | 71 ++++++ src/{x64 => x86}/Store.go | 2 +- src/{x64 => x86}/StoreDynamic.go | 2 +- src/x86/StoreDynamic_test.go | 171 +++++++++++++ src/x86/Store_test.go | 305 ++++++++++++++++++++++++ src/{x64 => x86}/Sub.go | 2 +- src/x86/Sub_test.go | 88 +++++++ src/{x64 => x86}/Syscall.go | 2 +- src/{x64 => x86}/Xor.go | 2 +- src/x86/Xor_test.go | 88 +++++++ src/{x64 => x86}/encode.go | 2 +- src/{x64 => x86}/encodeNum.go | 2 +- src/{x64 => x86}/memoryAccess.go | 2 +- src/{x64 => x86}/memoryAccessDynamic.go | 2 +- src/x86/x64_test.go | 19 ++ 80 files changed, 1757 insertions(+), 1757 deletions(-) delete mode 100644 src/x64/Add_test.go delete mode 100644 src/x64/And_test.go delete mode 100644 src/x64/Compare_test.go delete mode 100644 src/x64/Div_test.go delete mode 100644 src/x64/Load_test.go delete mode 100644 src/x64/Move_test.go delete mode 100644 src/x64/Mul_test.go delete mode 100644 src/x64/Negate_test.go delete mode 100644 src/x64/Or_test.go delete mode 100644 src/x64/Pop_test.go delete mode 100644 src/x64/Push_test.go delete mode 100644 src/x64/Registers_test.go delete mode 100644 src/x64/Shift_test.go delete mode 100644 src/x64/StoreDynamic_test.go delete mode 100644 src/x64/Store_test.go delete mode 100644 src/x64/Sub_test.go delete mode 100644 src/x64/Xor_test.go delete mode 100644 src/x64/x64_test.go rename src/{x64 => x86}/Add.go (97%) create mode 100644 src/x86/Add_test.go rename src/{x64 => x86}/AlignStack.go (91%) rename src/{x64 => x86}/And.go (97%) create mode 100644 src/x86/And_test.go rename src/{x64 => x86}/Call.go (98%) rename src/{x64 => x86}/Compare.go (97%) create mode 100644 src/x86/Compare_test.go rename src/{x64 => x86}/Div.go (96%) create mode 100644 src/x86/Div_test.go rename src/{x64 => x86}/ExtendRAXToRDX.go (93%) rename src/{x64 => x86}/Jump.go (98%) rename src/{x64 => x86}/Jump_test.go (56%) rename src/{x64 => x86}/Load.go (95%) create mode 100644 src/x86/Load_test.go rename src/{x64 => x86}/ModRM.go (97%) rename src/{x64 => x86}/ModRM_test.go (90%) rename src/{x64 => x86}/Move.go (99%) create mode 100644 src/x86/Move_test.go rename src/{x64 => x86}/Mul.go (97%) create mode 100644 src/x86/Mul_test.go rename src/{x64 => x86}/Negate.go (94%) create mode 100644 src/x86/Negate_test.go rename src/{x64 => x86}/Or.go (97%) create mode 100644 src/x86/Or_test.go rename src/{x64 => x86}/Pop.go (96%) create mode 100644 src/x86/Pop_test.go rename src/{x64 => x86}/Push.go (96%) create mode 100644 src/x86/Push_test.go rename src/{x64 => x86}/REX.go (93%) rename src/{x64 => x86}/REX_test.go (87%) rename src/{x64 => x86}/Registers.go (98%) create mode 100644 src/x86/Registers_test.go rename src/{x64 => x86}/Return.go (94%) rename src/{x64 => x86}/SIB.go (97%) rename src/{x64 => x86}/SIB_test.go (89%) rename src/{x64 => x86}/Shift.go (97%) create mode 100644 src/x86/Shift_test.go rename src/{x64 => x86}/Store.go (98%) rename src/{x64 => x86}/StoreDynamic.go (98%) create mode 100644 src/x86/StoreDynamic_test.go create mode 100644 src/x86/Store_test.go rename src/{x64 => x86}/Sub.go (97%) create mode 100644 src/x86/Sub_test.go rename src/{x64 => x86}/Syscall.go (91%) rename src/{x64 => x86}/Xor.go (97%) create mode 100644 src/x86/Xor_test.go rename src/{x64 => x86}/encode.go (98%) rename src/{x64 => x86}/encodeNum.go (97%) rename src/{x64 => x86}/memoryAccess.go (98%) rename src/{x64 => x86}/memoryAccessDynamic.go (98%) create mode 100644 src/x86/x64_test.go diff --git a/docs/todo.md b/docs/todo.md index b8655f1..19132e0 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -71,7 +71,7 @@ - [ ] arm64 - [ ] riscv -- [x] x64 +- [x] x86 ### Platform diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index 0915e6b..5ea281c 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -6,7 +6,7 @@ import ( "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" ) // Finalize generates the final machine code. @@ -27,67 +27,67 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { case ADD: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.AddRegisterNumber(code, operands.Register, operands.Number) + code = x86.AddRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.AddRegisterRegister(code, operands.Destination, operands.Source) + code = x86.AddRegisterRegister(code, operands.Destination, operands.Source) } case AND: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.AndRegisterNumber(code, operands.Register, operands.Number) + code = x86.AndRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.AndRegisterRegister(code, operands.Destination, operands.Source) + code = x86.AndRegisterRegister(code, operands.Destination, operands.Source) } case SUB: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.SubRegisterNumber(code, operands.Register, operands.Number) + code = x86.SubRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.SubRegisterRegister(code, operands.Destination, operands.Source) + code = x86.SubRegisterRegister(code, operands.Destination, operands.Source) } case MUL: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.MulRegisterNumber(code, operands.Register, operands.Number) + code = x86.MulRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.MulRegisterRegister(code, operands.Destination, operands.Source) + code = x86.MulRegisterRegister(code, operands.Destination, operands.Source) } case DIV: switch operands := x.Data.(type) { case *RegisterRegister: - if operands.Destination != x64.RAX { - code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination) + if operands.Destination != x86.RAX { + code = x86.MoveRegisterRegister(code, x86.RAX, operands.Destination) } - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Source) + code = x86.ExtendRAXToRDX(code) + code = x86.DivRegister(code, operands.Source) - if operands.Destination != x64.RAX { - code = x64.MoveRegisterRegister(code, operands.Destination, x64.RAX) + if operands.Destination != x86.RAX { + code = x86.MoveRegisterRegister(code, operands.Destination, x86.RAX) } } case MODULO: switch operands := x.Data.(type) { case *RegisterRegister: - if operands.Destination != x64.RAX { - code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination) + if operands.Destination != x86.RAX { + code = x86.MoveRegisterRegister(code, x86.RAX, operands.Destination) } - code = x64.ExtendRAXToRDX(code) - code = x64.DivRegister(code, operands.Source) + code = x86.ExtendRAXToRDX(code) + code = x86.DivRegister(code, operands.Source) - if operands.Destination != x64.RDX { - code = x64.MoveRegisterRegister(code, operands.Destination, x64.RDX) + if operands.Destination != x86.RDX { + code = x86.MoveRegisterRegister(code, operands.Destination, x86.RDX) } } case CALL: - code = x64.Call(code, 0x00_00_00_00) + code = x86.Call(code, 0x00_00_00_00) size := 4 label := x.Data.(*Label) @@ -116,20 +116,20 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { case COMPARE: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.CompareRegisterNumber(code, operands.Register, operands.Number) + code = x86.CompareRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source) + code = x86.CompareRegisterRegister(code, operands.Destination, operands.Source) } case DLLCALL: size := 4 // TODO: R15 could be in use. - code = x64.MoveRegisterRegister(code, x64.R15, x64.RSP) - code = x64.AlignStack(code) - code = x64.SubRegisterNumber(code, x64.RSP, 32) - code = x64.CallAtAddress(code, 0x00_00_00_00) + code = x86.MoveRegisterRegister(code, x86.R15, x86.RSP) + code = x86.AlignStack(code) + code = x86.SubRegisterNumber(code, x86.RSP, 32) + code = x86.CallAtAddress(code, 0x00_00_00_00) position := len(code) - size - code = x64.MoveRegisterRegister(code, x64.RSP, x64.R15) + code = x86.MoveRegisterRegister(code, x86.RSP, x86.R15) label := x.Data.(*Label) pointer := &pointer{ @@ -156,19 +156,19 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { case JE, JNE, JG, JGE, JL, JLE, JUMP: switch x.Mnemonic { case JE: - code = x64.Jump8IfEqual(code, 0x00) + code = x86.Jump8IfEqual(code, 0x00) case JNE: - code = x64.Jump8IfNotEqual(code, 0x00) + code = x86.Jump8IfNotEqual(code, 0x00) case JG: - code = x64.Jump8IfGreater(code, 0x00) + code = x86.Jump8IfGreater(code, 0x00) case JGE: - code = x64.Jump8IfGreaterOrEqual(code, 0x00) + code = x86.Jump8IfGreaterOrEqual(code, 0x00) case JL: - code = x64.Jump8IfLess(code, 0x00) + code = x86.Jump8IfLess(code, 0x00) case JLE: - code = x64.Jump8IfLessOrEqual(code, 0x00) + code = x86.Jump8IfLessOrEqual(code, 0x00) case JUMP: - code = x64.Jump8(code, 0x00) + code = x86.Jump8(code, 0x00) } size := 1 @@ -199,20 +199,20 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { case LOAD: switch operands := x.Data.(type) { case *MemoryRegister: - code = x64.LoadRegister(code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) + code = x86.LoadRegister(code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) } case MOVE: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.MoveRegisterNumber(code, operands.Register, operands.Number) + code = x86.MoveRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.MoveRegisterRegister(code, operands.Destination, operands.Source) + code = x86.MoveRegisterRegister(code, operands.Destination, operands.Source) case *RegisterLabel: start := len(code) - code = x64.MoveRegisterNumber(code, operands.Register, 0x00_00_00_00) + code = x86.MoveRegisterNumber(code, operands.Register, 0x00_00_00_00) size := 4 opSize := len(code) - size - start regLabel := x.Data.(*RegisterLabel) @@ -253,59 +253,59 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { case NEGATE: switch operands := x.Data.(type) { case *Register: - code = x64.NegateRegister(code, operands.Register) + code = x86.NegateRegister(code, operands.Register) } case OR: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.OrRegisterNumber(code, operands.Register, operands.Number) + code = x86.OrRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.OrRegisterRegister(code, operands.Destination, operands.Source) + code = x86.OrRegisterRegister(code, operands.Destination, operands.Source) } case POP: switch operands := x.Data.(type) { case *Register: - code = x64.PopRegister(code, operands.Register) + code = x86.PopRegister(code, operands.Register) } case PUSH: switch operands := x.Data.(type) { case *Register: - code = x64.PushRegister(code, operands.Register) + code = x86.PushRegister(code, operands.Register) } case RETURN: - code = x64.Return(code) + code = x86.Return(code) case SHIFTL: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.ShiftLeftNumber(code, operands.Register, byte(operands.Number)&0b111111) + code = x86.ShiftLeftNumber(code, operands.Register, byte(operands.Number)&0b111111) } case SHIFTRS: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.ShiftRightSignedNumber(code, operands.Register, byte(operands.Number)&0b111111) + code = x86.ShiftRightSignedNumber(code, operands.Register, byte(operands.Number)&0b111111) } case STORE: switch operands := x.Data.(type) { case *MemoryNumber: if operands.Address.OffsetRegister == math.MaxUint8 { - code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) + code = x86.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) } else { - code = x64.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) + code = x86.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) } case *MemoryLabel: start := len(code) if operands.Address.OffsetRegister == math.MaxUint8 { - code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, 0b00_00_00_00) + code = x86.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, 0b00_00_00_00) } else { - code = x64.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, 0b00_00_00_00) + code = x86.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, 0b00_00_00_00) } size := 4 @@ -328,21 +328,21 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { }) case *MemoryRegister: if operands.Address.OffsetRegister == math.MaxUint8 { - code = x64.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) + code = x86.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) } else { - code = x64.StoreDynamicRegister(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) + code = x86.StoreDynamicRegister(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) } } case SYSCALL: - code = x64.Syscall(code) + code = x86.Syscall(code) case XOR: switch operands := x.Data.(type) { case *RegisterNumber: - code = x64.XorRegisterNumber(code, operands.Register, operands.Number) + code = x86.XorRegisterNumber(code, operands.Register, operands.Number) case *RegisterRegister: - code = x64.XorRegisterRegister(code, operands.Destination, operands.Source) + code = x86.XorRegisterRegister(code, operands.Destination, operands.Source) } default: diff --git a/src/compiler/Result.go b/src/compiler/Result.go index c4d739f..6d01851 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -13,7 +13,7 @@ import ( "git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/macho" "git.akyoto.dev/cli/q/src/pe" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" "git.akyoto.dev/go/color/ansi" ) @@ -43,15 +43,15 @@ func (r *Result) finalize() { switch config.TargetOS { case config.Linux: - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], LinuxExit) - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], LinuxExit) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 0) final.Syscall() case config.Mac: - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], MacExit) - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], MacExit) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 0) final.Syscall() case config.Windows: - final.RegisterNumber(asm.MOVE, x64.WindowsInputRegisters[0], 0) + final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 0) final.DLLCall("kernel32.ExitProcess") } @@ -75,15 +75,15 @@ func (r *Result) finalize() { switch config.TargetOS { case config.Linux: - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], LinuxExit) - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], LinuxExit) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 1) final.Syscall() case config.Mac: - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], MacExit) - final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], MacExit) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 1) final.Syscall() case config.Windows: - final.RegisterNumber(asm.MOVE, x64.WindowsInputRegisters[0], 1) + final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 1) final.DLLCall("kernel32.ExitProcess") } diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index e169827..d9f5f5c 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -4,7 +4,7 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" ) // CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right. @@ -37,8 +37,8 @@ func (f *Function) CompileAssignDivision(node *ast.Assign) error { divisor := right.Children[1] err = f.Execute(right.Token, dividendRegister, divisor) - f.RegisterRegister(asm.MOVE, quotientVariable.Register, x64.RAX) - f.RegisterRegister(asm.MOVE, remainderVariable.Register, x64.RDX) + f.RegisterRegister(asm.MOVE, quotientVariable.Register, x86.RAX) + f.RegisterRegister(asm.MOVE, remainderVariable.Register, x86.RDX) if isTemporary { f.FreeRegister(dividendRegister) diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index c88ec87..5ce42f1 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -6,7 +6,7 @@ import ( "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" ) // CompileCall executes a function call. @@ -50,7 +50,7 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { if pkg == "kernel32" || pkg == "user32" || pkg == "gdi32" || pkg == "comctl32" { parameters := root.Children[1:] - registers := x64.WindowsInputRegisters[:len(parameters)] + registers := x86.WindowsInputRegisters[:len(parameters)] for i := len(parameters) - 1; i >= 0; i-- { _, err := f.ExpressionToRegister(parameters[i], registers[i]) diff --git a/src/core/ExecuteRegisterNumber.go b/src/core/ExecuteRegisterNumber.go index 0714751..f26f1a3 100644 --- a/src/core/ExecuteRegisterNumber.go +++ b/src/core/ExecuteRegisterNumber.go @@ -5,7 +5,7 @@ import ( "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" ) // ExecuteRegisterNumber performs an operation on a register and a number. @@ -25,16 +25,16 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg f.RegisterNumber(asm.MUL, register, number) case token.Div, token.DivAssign: - f.SaveRegister(x64.RAX) - f.SaveRegister(x64.RDX) + f.SaveRegister(x86.RAX) + f.SaveRegister(x86.RDX) tmp := f.NewRegister() f.RegisterNumber(asm.MOVE, tmp, number) f.RegisterRegister(asm.DIV, register, tmp) f.FreeRegister(tmp) case token.Mod, token.ModAssign: - f.SaveRegister(x64.RAX) - f.SaveRegister(x64.RDX) + f.SaveRegister(x86.RAX) + f.SaveRegister(x86.RDX) tmp := f.NewRegister() f.RegisterNumber(asm.MOVE, tmp, number) f.RegisterRegister(asm.MODULO, register, tmp) diff --git a/src/core/ExecuteRegisterRegister.go b/src/core/ExecuteRegisterRegister.go index 3add7ef..ebb6811 100644 --- a/src/core/ExecuteRegisterRegister.go +++ b/src/core/ExecuteRegisterRegister.go @@ -5,7 +5,7 @@ import ( "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" ) // ExecuteRegisterRegister performs an operation on two registers. @@ -25,13 +25,13 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.R f.RegisterRegister(asm.MUL, register, operand) case token.Div, token.DivAssign: - f.SaveRegister(x64.RAX) - f.SaveRegister(x64.RDX) + f.SaveRegister(x86.RAX) + f.SaveRegister(x86.RDX) f.RegisterRegister(asm.DIV, register, operand) case token.Mod, token.ModAssign: - f.SaveRegister(x64.RAX) - f.SaveRegister(x64.RDX) + f.SaveRegister(x86.RAX) + f.SaveRegister(x86.RDX) f.RegisterRegister(asm.MODULO, register, operand) case token.And, token.AndAssign: diff --git a/src/core/NewFunction.go b/src/core/NewFunction.go index 5e5a4ae..e2d93d2 100644 --- a/src/core/NewFunction.go +++ b/src/core/NewFunction.go @@ -7,7 +7,7 @@ import ( "git.akyoto.dev/cli/q/src/register" "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" ) // NewFunction creates a new function. @@ -26,12 +26,12 @@ func NewFunction(pkg string, name string, file *fs.File, body []token.Token) *Fu Scopes: []*scope.Scope{{}}, }, CPU: cpu.CPU{ - All: x64.AllRegisters, - General: x64.GeneralRegisters, - Input: x64.InputRegisters, - Output: x64.OutputRegisters, - SyscallInput: x64.SyscallInputRegisters, - SyscallOutput: x64.SyscallOutputRegisters, + All: x86.AllRegisters, + General: x86.GeneralRegisters, + Input: x86.InputRegisters, + Output: x86.OutputRegisters, + SyscallInput: x86.SyscallInputRegisters, + SyscallOutput: x86.SyscallOutputRegisters, }, }, } diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index c508073..c779c13 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -4,7 +4,7 @@ import ( "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" ) // ResolveTypes parses the input and output types. @@ -27,7 +27,7 @@ func (f *Function) ResolveTypes() error { f.AddVariable(&scope.Variable{ Name: param.Name, Type: param.Type, - Register: x64.InputRegisters[i], + Register: x86.InputRegisters[i], Alive: uses, }) } diff --git a/src/readme.md b/src/readme.md index 6483efc..4ee22a5 100644 --- a/src/readme.md +++ b/src/readme.md @@ -24,4 +24,4 @@ - [sizeof](sizeof) - Calculates the byte size of numbers - [token](token) - Converts a file to tokens with the `Tokenize` function - [types](types) - Type system (w.i.p.) -- [x64](x64) - x86-64 implementation +- [x86](x86) - x86-64 implementation diff --git a/src/x64/Add_test.go b/src/x64/Add_test.go deleted file mode 100644 index 134e09e..0000000 --- a/src/x64/Add_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestAddRegisterNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0x83, 0xC0, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0x83, 0xC1, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0x83, 0xC2, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0x83, 0xC3, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0x83, 0xC4, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0x83, 0xC5, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0x83, 0xC6, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0x83, 0xC7, 0x01}}, - {x64.R8, 1, []byte{0x49, 0x83, 0xC0, 0x01}}, - {x64.R9, 1, []byte{0x49, 0x83, 0xC1, 0x01}}, - {x64.R10, 1, []byte{0x49, 0x83, 0xC2, 0x01}}, - {x64.R11, 1, []byte{0x49, 0x83, 0xC3, 0x01}}, - {x64.R12, 1, []byte{0x49, 0x83, 0xC4, 0x01}}, - {x64.R13, 1, []byte{0x49, 0x83, 0xC5, 0x01}}, - {x64.R14, 1, []byte{0x49, 0x83, 0xC6, 0x01}}, - {x64.R15, 1, []byte{0x49, 0x83, 0xC7, 0x01}}, - - {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("add %s, %x", pattern.Register, pattern.Number) - code := x64.AddRegisterNumber(nil, pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestAddRegisterRegister(t *testing.T) { - usagePatterns := []struct { - Left cpu.Register - Right cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, []byte{0x4C, 0x01, 0xF8}}, - {x64.RCX, x64.R14, []byte{0x4C, 0x01, 0xF1}}, - {x64.RDX, x64.R13, []byte{0x4C, 0x01, 0xEA}}, - {x64.RBX, x64.R12, []byte{0x4C, 0x01, 0xE3}}, - {x64.RSP, x64.R11, []byte{0x4C, 0x01, 0xDC}}, - {x64.RBP, x64.R10, []byte{0x4C, 0x01, 0xD5}}, - {x64.RSI, x64.R9, []byte{0x4C, 0x01, 0xCE}}, - {x64.RDI, x64.R8, []byte{0x4C, 0x01, 0xC7}}, - {x64.R8, x64.RDI, []byte{0x49, 0x01, 0xF8}}, - {x64.R9, x64.RSI, []byte{0x49, 0x01, 0xF1}}, - {x64.R10, x64.RBP, []byte{0x49, 0x01, 0xEA}}, - {x64.R11, x64.RSP, []byte{0x49, 0x01, 0xE3}}, - {x64.R12, x64.RBX, []byte{0x49, 0x01, 0xDC}}, - {x64.R13, x64.RDX, []byte{0x49, 0x01, 0xD5}}, - {x64.R14, x64.RCX, []byte{0x49, 0x01, 0xCE}}, - {x64.R15, x64.RAX, []byte{0x49, 0x01, 0xC7}}, - } - - for _, pattern := range usagePatterns { - t.Logf("add %s, %s", pattern.Left, pattern.Right) - code := x64.AddRegisterRegister(nil, pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/And_test.go b/src/x64/And_test.go deleted file mode 100644 index 249d7b8..0000000 --- a/src/x64/And_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestAndRegisterNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0x83, 0xE0, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0x83, 0xE1, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0x83, 0xE2, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0x83, 0xE3, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0x83, 0xE4, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0x83, 0xE5, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0x83, 0xE6, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0x83, 0xE7, 0x01}}, - {x64.R8, 1, []byte{0x49, 0x83, 0xE0, 0x01}}, - {x64.R9, 1, []byte{0x49, 0x83, 0xE1, 0x01}}, - {x64.R10, 1, []byte{0x49, 0x83, 0xE2, 0x01}}, - {x64.R11, 1, []byte{0x49, 0x83, 0xE3, 0x01}}, - {x64.R12, 1, []byte{0x49, 0x83, 0xE4, 0x01}}, - {x64.R13, 1, []byte{0x49, 0x83, 0xE5, 0x01}}, - {x64.R14, 1, []byte{0x49, 0x83, 0xE6, 0x01}}, - {x64.R15, 1, []byte{0x49, 0x83, 0xE7, 0x01}}, - - {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE7, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE7, 0xFF, 0xFF, 0xFF, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("and %s, %x", pattern.Register, pattern.Number) - code := x64.AndRegisterNumber(nil, pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestAndRegisterRegister(t *testing.T) { - usagePatterns := []struct { - Left cpu.Register - Right cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, []byte{0x4C, 0x21, 0xF8}}, - {x64.RCX, x64.R14, []byte{0x4C, 0x21, 0xF1}}, - {x64.RDX, x64.R13, []byte{0x4C, 0x21, 0xEA}}, - {x64.RBX, x64.R12, []byte{0x4C, 0x21, 0xE3}}, - {x64.RSP, x64.R11, []byte{0x4C, 0x21, 0xDC}}, - {x64.RBP, x64.R10, []byte{0x4C, 0x21, 0xD5}}, - {x64.RSI, x64.R9, []byte{0x4C, 0x21, 0xCE}}, - {x64.RDI, x64.R8, []byte{0x4C, 0x21, 0xC7}}, - {x64.R8, x64.RDI, []byte{0x49, 0x21, 0xF8}}, - {x64.R9, x64.RSI, []byte{0x49, 0x21, 0xF1}}, - {x64.R10, x64.RBP, []byte{0x49, 0x21, 0xEA}}, - {x64.R11, x64.RSP, []byte{0x49, 0x21, 0xE3}}, - {x64.R12, x64.RBX, []byte{0x49, 0x21, 0xDC}}, - {x64.R13, x64.RDX, []byte{0x49, 0x21, 0xD5}}, - {x64.R14, x64.RCX, []byte{0x49, 0x21, 0xCE}}, - {x64.R15, x64.RAX, []byte{0x49, 0x21, 0xC7}}, - } - - for _, pattern := range usagePatterns { - t.Logf("and %s, %s", pattern.Left, pattern.Right) - code := x64.AndRegisterRegister(nil, pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Compare_test.go b/src/x64/Compare_test.go deleted file mode 100644 index 0105ed0..0000000 --- a/src/x64/Compare_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestCompareRegisterNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0x83, 0xF8, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0x83, 0xF9, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0x83, 0xFA, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0x83, 0xFB, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0x83, 0xFC, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0x83, 0xFD, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0x83, 0xFE, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0x83, 0xFF, 0x01}}, - {x64.R8, 1, []byte{0x49, 0x83, 0xF8, 0x01}}, - {x64.R9, 1, []byte{0x49, 0x83, 0xF9, 0x01}}, - {x64.R10, 1, []byte{0x49, 0x83, 0xFA, 0x01}}, - {x64.R11, 1, []byte{0x49, 0x83, 0xFB, 0x01}}, - {x64.R12, 1, []byte{0x49, 0x83, 0xFC, 0x01}}, - {x64.R13, 1, []byte{0x49, 0x83, 0xFD, 0x01}}, - {x64.R14, 1, []byte{0x49, 0x83, 0xFE, 0x01}}, - {x64.R15, 1, []byte{0x49, 0x83, 0xFF, 0x01}}, - - {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("cmp %s, %x", pattern.Register, pattern.Number) - code := x64.CompareRegisterNumber(nil, pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestCompareRegisterRegister(t *testing.T) { - usagePatterns := []struct { - Left cpu.Register - Right cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, []byte{0x4C, 0x39, 0xF8}}, - {x64.RCX, x64.R14, []byte{0x4C, 0x39, 0xF1}}, - {x64.RDX, x64.R13, []byte{0x4C, 0x39, 0xEA}}, - {x64.RBX, x64.R12, []byte{0x4C, 0x39, 0xE3}}, - {x64.RSP, x64.R11, []byte{0x4C, 0x39, 0xDC}}, - {x64.RBP, x64.R10, []byte{0x4C, 0x39, 0xD5}}, - {x64.RSI, x64.R9, []byte{0x4C, 0x39, 0xCE}}, - {x64.RDI, x64.R8, []byte{0x4C, 0x39, 0xC7}}, - {x64.R8, x64.RDI, []byte{0x49, 0x39, 0xF8}}, - {x64.R9, x64.RSI, []byte{0x49, 0x39, 0xF1}}, - {x64.R10, x64.RBP, []byte{0x49, 0x39, 0xEA}}, - {x64.R11, x64.RSP, []byte{0x49, 0x39, 0xE3}}, - {x64.R12, x64.RBX, []byte{0x49, 0x39, 0xDC}}, - {x64.R13, x64.RDX, []byte{0x49, 0x39, 0xD5}}, - {x64.R14, x64.RCX, []byte{0x49, 0x39, 0xCE}}, - {x64.R15, x64.RAX, []byte{0x49, 0x39, 0xC7}}, - } - - for _, pattern := range usagePatterns { - t.Logf("cmp %s, %s", pattern.Left, pattern.Right) - code := x64.CompareRegisterRegister(nil, pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Div_test.go b/src/x64/Div_test.go deleted file mode 100644 index de684b4..0000000 --- a/src/x64/Div_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestDivRegister(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Code []byte - }{ - {x64.RAX, []byte{0x48, 0xF7, 0xF8}}, - {x64.RCX, []byte{0x48, 0xF7, 0xF9}}, - {x64.RDX, []byte{0x48, 0xF7, 0xFA}}, - {x64.RBX, []byte{0x48, 0xF7, 0xFB}}, - {x64.RSP, []byte{0x48, 0xF7, 0xFC}}, - {x64.RBP, []byte{0x48, 0xF7, 0xFD}}, - {x64.RSI, []byte{0x48, 0xF7, 0xFE}}, - {x64.RDI, []byte{0x48, 0xF7, 0xFF}}, - {x64.R8, []byte{0x49, 0xF7, 0xF8}}, - {x64.R9, []byte{0x49, 0xF7, 0xF9}}, - {x64.R10, []byte{0x49, 0xF7, 0xFA}}, - {x64.R11, []byte{0x49, 0xF7, 0xFB}}, - {x64.R12, []byte{0x49, 0xF7, 0xFC}}, - {x64.R13, []byte{0x49, 0xF7, 0xFD}}, - {x64.R14, []byte{0x49, 0xF7, 0xFE}}, - {x64.R15, []byte{0x49, 0xF7, 0xFF}}, - } - - for _, pattern := range usagePatterns { - t.Logf("idiv %s", pattern.Register) - code := x64.DivRegister(nil, pattern.Register) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Load_test.go b/src/x64/Load_test.go deleted file mode 100644 index 9585f15..0000000 --- a/src/x64/Load_test.go +++ /dev/null @@ -1,157 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestLoadRegister(t *testing.T) { - usagePatterns := []struct { - Destination cpu.Register - Source cpu.Register - Offset byte - Length byte - Code []byte - }{ - // No offset - {x64.RAX, x64.R15, 0, 8, []byte{0x49, 0x8B, 0x07}}, - {x64.RAX, x64.R15, 0, 4, []byte{0x41, 0x8B, 0x07}}, - {x64.RAX, x64.R15, 0, 2, []byte{0x66, 0x41, 0x8B, 0x07}}, - {x64.RAX, x64.R15, 0, 1, []byte{0x41, 0x8A, 0x07}}, - {x64.RCX, x64.R14, 0, 8, []byte{0x49, 0x8B, 0x0E}}, - {x64.RCX, x64.R14, 0, 4, []byte{0x41, 0x8B, 0x0E}}, - {x64.RCX, x64.R14, 0, 2, []byte{0x66, 0x41, 0x8B, 0x0E}}, - {x64.RCX, x64.R14, 0, 1, []byte{0x41, 0x8A, 0x0E}}, - {x64.RDX, x64.R13, 0, 8, []byte{0x49, 0x8B, 0x55, 0x00}}, - {x64.RDX, x64.R13, 0, 4, []byte{0x41, 0x8B, 0x55, 0x00}}, - {x64.RDX, x64.R13, 0, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x00}}, - {x64.RDX, x64.R13, 0, 1, []byte{0x41, 0x8A, 0x55, 0x00}}, - {x64.RBX, x64.R12, 0, 8, []byte{0x49, 0x8B, 0x1C, 0x24}}, - {x64.RBX, x64.R12, 0, 4, []byte{0x41, 0x8B, 0x1C, 0x24}}, - {x64.RBX, x64.R12, 0, 2, []byte{0x66, 0x41, 0x8B, 0x1C, 0x24}}, - {x64.RBX, x64.R12, 0, 1, []byte{0x41, 0x8A, 0x1C, 0x24}}, - {x64.RSP, x64.R11, 0, 8, []byte{0x49, 0x8B, 0x23}}, - {x64.RSP, x64.R11, 0, 4, []byte{0x41, 0x8B, 0x23}}, - {x64.RSP, x64.R11, 0, 2, []byte{0x66, 0x41, 0x8B, 0x23}}, - {x64.RSP, x64.R11, 0, 1, []byte{0x41, 0x8A, 0x23}}, - {x64.RBP, x64.R10, 0, 8, []byte{0x49, 0x8B, 0x2A}}, - {x64.RBP, x64.R10, 0, 4, []byte{0x41, 0x8B, 0x2A}}, - {x64.RBP, x64.R10, 0, 2, []byte{0x66, 0x41, 0x8B, 0x2A}}, - {x64.RBP, x64.R10, 0, 1, []byte{0x41, 0x8A, 0x2A}}, - {x64.RSI, x64.R9, 0, 8, []byte{0x49, 0x8B, 0x31}}, - {x64.RSI, x64.R9, 0, 4, []byte{0x41, 0x8B, 0x31}}, - {x64.RSI, x64.R9, 0, 2, []byte{0x66, 0x41, 0x8B, 0x31}}, - {x64.RSI, x64.R9, 0, 1, []byte{0x41, 0x8A, 0x31}}, - {x64.RDI, x64.R8, 0, 8, []byte{0x49, 0x8B, 0x38}}, - {x64.RDI, x64.R8, 0, 4, []byte{0x41, 0x8B, 0x38}}, - {x64.RDI, x64.R8, 0, 2, []byte{0x66, 0x41, 0x8B, 0x38}}, - {x64.RDI, x64.R8, 0, 1, []byte{0x41, 0x8A, 0x38}}, - {x64.R8, x64.RDI, 0, 8, []byte{0x4C, 0x8B, 0x07}}, - {x64.R8, x64.RDI, 0, 4, []byte{0x44, 0x8B, 0x07}}, - {x64.R8, x64.RDI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x07}}, - {x64.R8, x64.RDI, 0, 1, []byte{0x44, 0x8A, 0x07}}, - {x64.R9, x64.RSI, 0, 8, []byte{0x4C, 0x8B, 0x0E}}, - {x64.R9, x64.RSI, 0, 4, []byte{0x44, 0x8B, 0x0E}}, - {x64.R9, x64.RSI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x0E}}, - {x64.R9, x64.RSI, 0, 1, []byte{0x44, 0x8A, 0x0E}}, - {x64.R10, x64.RBP, 0, 8, []byte{0x4C, 0x8B, 0x55, 0x00}}, - {x64.R10, x64.RBP, 0, 4, []byte{0x44, 0x8B, 0x55, 0x00}}, - {x64.R10, x64.RBP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x00}}, - {x64.R10, x64.RBP, 0, 1, []byte{0x44, 0x8A, 0x55, 0x00}}, - {x64.R11, x64.RSP, 0, 8, []byte{0x4C, 0x8B, 0x1C, 0x24}}, - {x64.R11, x64.RSP, 0, 4, []byte{0x44, 0x8B, 0x1C, 0x24}}, - {x64.R11, x64.RSP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x1C, 0x24}}, - {x64.R11, x64.RSP, 0, 1, []byte{0x44, 0x8A, 0x1C, 0x24}}, - {x64.R12, x64.RBX, 0, 8, []byte{0x4C, 0x8B, 0x23}}, - {x64.R12, x64.RBX, 0, 4, []byte{0x44, 0x8B, 0x23}}, - {x64.R12, x64.RBX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x23}}, - {x64.R12, x64.RBX, 0, 1, []byte{0x44, 0x8A, 0x23}}, - {x64.R13, x64.RDX, 0, 8, []byte{0x4C, 0x8B, 0x2A}}, - {x64.R13, x64.RDX, 0, 4, []byte{0x44, 0x8B, 0x2A}}, - {x64.R13, x64.RDX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x2A}}, - {x64.R13, x64.RDX, 0, 1, []byte{0x44, 0x8A, 0x2A}}, - {x64.R14, x64.RCX, 0, 8, []byte{0x4C, 0x8B, 0x31}}, - {x64.R14, x64.RCX, 0, 4, []byte{0x44, 0x8B, 0x31}}, - {x64.R14, x64.RCX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x31}}, - {x64.R14, x64.RCX, 0, 1, []byte{0x44, 0x8A, 0x31}}, - {x64.R15, x64.RAX, 0, 8, []byte{0x4C, 0x8B, 0x38}}, - {x64.R15, x64.RAX, 0, 4, []byte{0x44, 0x8B, 0x38}}, - {x64.R15, x64.RAX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x38}}, - {x64.R15, x64.RAX, 0, 1, []byte{0x44, 0x8A, 0x38}}, - - // Offset of 1 - {x64.RAX, x64.R15, 1, 8, []byte{0x49, 0x8B, 0x47, 0x01}}, - {x64.RAX, x64.R15, 1, 4, []byte{0x41, 0x8B, 0x47, 0x01}}, - {x64.RAX, x64.R15, 1, 2, []byte{0x66, 0x41, 0x8B, 0x47, 0x01}}, - {x64.RAX, x64.R15, 1, 1, []byte{0x41, 0x8A, 0x47, 0x01}}, - {x64.RCX, x64.R14, 1, 8, []byte{0x49, 0x8B, 0x4E, 0x01}}, - {x64.RCX, x64.R14, 1, 4, []byte{0x41, 0x8B, 0x4E, 0x01}}, - {x64.RCX, x64.R14, 1, 2, []byte{0x66, 0x41, 0x8B, 0x4E, 0x01}}, - {x64.RCX, x64.R14, 1, 1, []byte{0x41, 0x8A, 0x4E, 0x01}}, - {x64.RDX, x64.R13, 1, 8, []byte{0x49, 0x8B, 0x55, 0x01}}, - {x64.RDX, x64.R13, 1, 4, []byte{0x41, 0x8B, 0x55, 0x01}}, - {x64.RDX, x64.R13, 1, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x01}}, - {x64.RDX, x64.R13, 1, 1, []byte{0x41, 0x8A, 0x55, 0x01}}, - {x64.RBX, x64.R12, 1, 8, []byte{0x49, 0x8B, 0x5C, 0x24, 0x01}}, - {x64.RBX, x64.R12, 1, 4, []byte{0x41, 0x8B, 0x5C, 0x24, 0x01}}, - {x64.RBX, x64.R12, 1, 2, []byte{0x66, 0x41, 0x8B, 0x5C, 0x24, 0x01}}, - {x64.RBX, x64.R12, 1, 1, []byte{0x41, 0x8A, 0x5C, 0x24, 0x01}}, - {x64.RSP, x64.R11, 1, 8, []byte{0x49, 0x8B, 0x63, 0x01}}, - {x64.RSP, x64.R11, 1, 4, []byte{0x41, 0x8B, 0x63, 0x01}}, - {x64.RSP, x64.R11, 1, 2, []byte{0x66, 0x41, 0x8B, 0x63, 0x01}}, - {x64.RSP, x64.R11, 1, 1, []byte{0x41, 0x8A, 0x63, 0x01}}, - {x64.RBP, x64.R10, 1, 8, []byte{0x49, 0x8B, 0x6A, 0x01}}, - {x64.RBP, x64.R10, 1, 4, []byte{0x41, 0x8B, 0x6A, 0x01}}, - {x64.RBP, x64.R10, 1, 2, []byte{0x66, 0x41, 0x8B, 0x6A, 0x01}}, - {x64.RBP, x64.R10, 1, 1, []byte{0x41, 0x8A, 0x6A, 0x01}}, - {x64.RSI, x64.R9, 1, 8, []byte{0x49, 0x8B, 0x71, 0x01}}, - {x64.RSI, x64.R9, 1, 4, []byte{0x41, 0x8B, 0x71, 0x01}}, - {x64.RSI, x64.R9, 1, 2, []byte{0x66, 0x41, 0x8B, 0x71, 0x01}}, - {x64.RSI, x64.R9, 1, 1, []byte{0x41, 0x8A, 0x71, 0x01}}, - {x64.RDI, x64.R8, 1, 8, []byte{0x49, 0x8B, 0x78, 0x01}}, - {x64.RDI, x64.R8, 1, 4, []byte{0x41, 0x8B, 0x78, 0x01}}, - {x64.RDI, x64.R8, 1, 2, []byte{0x66, 0x41, 0x8B, 0x78, 0x01}}, - {x64.RDI, x64.R8, 1, 1, []byte{0x41, 0x8A, 0x78, 0x01}}, - {x64.R8, x64.RDI, 1, 8, []byte{0x4C, 0x8B, 0x47, 0x01}}, - {x64.R8, x64.RDI, 1, 4, []byte{0x44, 0x8B, 0x47, 0x01}}, - {x64.R8, x64.RDI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x47, 0x01}}, - {x64.R8, x64.RDI, 1, 1, []byte{0x44, 0x8A, 0x47, 0x01}}, - {x64.R9, x64.RSI, 1, 8, []byte{0x4C, 0x8B, 0x4E, 0x01}}, - {x64.R9, x64.RSI, 1, 4, []byte{0x44, 0x8B, 0x4E, 0x01}}, - {x64.R9, x64.RSI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x4E, 0x01}}, - {x64.R9, x64.RSI, 1, 1, []byte{0x44, 0x8A, 0x4E, 0x01}}, - {x64.R10, x64.RBP, 1, 8, []byte{0x4C, 0x8B, 0x55, 0x01}}, - {x64.R10, x64.RBP, 1, 4, []byte{0x44, 0x8B, 0x55, 0x01}}, - {x64.R10, x64.RBP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x01}}, - {x64.R10, x64.RBP, 1, 1, []byte{0x44, 0x8A, 0x55, 0x01}}, - {x64.R11, x64.RSP, 1, 8, []byte{0x4C, 0x8B, 0x5C, 0x24, 0x01}}, - {x64.R11, x64.RSP, 1, 4, []byte{0x44, 0x8B, 0x5C, 0x24, 0x01}}, - {x64.R11, x64.RSP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x5C, 0x24, 0x01}}, - {x64.R11, x64.RSP, 1, 1, []byte{0x44, 0x8A, 0x5C, 0x24, 0x01}}, - {x64.R12, x64.RBX, 1, 8, []byte{0x4C, 0x8B, 0x63, 0x01}}, - {x64.R12, x64.RBX, 1, 4, []byte{0x44, 0x8B, 0x63, 0x01}}, - {x64.R12, x64.RBX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x63, 0x01}}, - {x64.R12, x64.RBX, 1, 1, []byte{0x44, 0x8A, 0x63, 0x01}}, - {x64.R13, x64.RDX, 1, 8, []byte{0x4C, 0x8B, 0x6A, 0x01}}, - {x64.R13, x64.RDX, 1, 4, []byte{0x44, 0x8B, 0x6A, 0x01}}, - {x64.R13, x64.RDX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x6A, 0x01}}, - {x64.R13, x64.RDX, 1, 1, []byte{0x44, 0x8A, 0x6A, 0x01}}, - {x64.R14, x64.RCX, 1, 8, []byte{0x4C, 0x8B, 0x71, 0x01}}, - {x64.R14, x64.RCX, 1, 4, []byte{0x44, 0x8B, 0x71, 0x01}}, - {x64.R14, x64.RCX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x71, 0x01}}, - {x64.R14, x64.RCX, 1, 1, []byte{0x44, 0x8A, 0x71, 0x01}}, - {x64.R15, x64.RAX, 1, 8, []byte{0x4C, 0x8B, 0x78, 0x01}}, - {x64.R15, x64.RAX, 1, 4, []byte{0x44, 0x8B, 0x78, 0x01}}, - {x64.R15, x64.RAX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x78, 0x01}}, - {x64.R15, x64.RAX, 1, 1, []byte{0x44, 0x8A, 0x78, 0x01}}, - } - - for _, pattern := range usagePatterns { - t.Logf("load %dB %s, [%s+%d]", pattern.Length, pattern.Destination, pattern.Source, pattern.Offset) - code := x64.LoadRegister(nil, pattern.Destination, pattern.Offset, pattern.Length, pattern.Source) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Move_test.go b/src/x64/Move_test.go deleted file mode 100644 index 071e05b..0000000 --- a/src/x64/Move_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestMoveRegisterNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - // 32 bits - {x64.RAX, 0x7FFFFFFF, []byte{0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFF, []byte{0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFF, []byte{0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFF, []byte{0xBB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFF, []byte{0xBC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFF, []byte{0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFF, []byte{0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFF, []byte{0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFF, []byte{0x41, 0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFF, []byte{0x41, 0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFF, []byte{0x41, 0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFF, []byte{0x41, 0xBB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFF, []byte{0x41, 0xBC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFF, []byte{0x41, 0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFF, []byte{0x41, 0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFF, []byte{0x41, 0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, - - // 64 bits - {x64.RAX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - - // Negative numbers - {x64.RAX, -1, []byte{0x48, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.RCX, -1, []byte{0x48, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.RDX, -1, []byte{0x48, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.RBX, -1, []byte{0x48, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.RSP, -1, []byte{0x48, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.RBP, -1, []byte{0x48, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.RSI, -1, []byte{0x48, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.RDI, -1, []byte{0x48, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.R8, -1, []byte{0x49, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.R9, -1, []byte{0x49, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.R10, -1, []byte{0x49, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.R11, -1, []byte{0x49, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.R12, -1, []byte{0x49, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.R13, -1, []byte{0x49, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.R14, -1, []byte{0x49, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x64.R15, -1, []byte{0x49, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}}, - } - - for _, pattern := range usagePatterns { - t.Logf("mov %s, %x", pattern.Register, pattern.Number) - code := x64.MoveRegisterNumber(nil, pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestMoveRegisterRegister(t *testing.T) { - usagePatterns := []struct { - Left cpu.Register - Right cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, []byte{0x4C, 0x89, 0xF8}}, - {x64.RCX, x64.R14, []byte{0x4C, 0x89, 0xF1}}, - {x64.RDX, x64.R13, []byte{0x4C, 0x89, 0xEA}}, - {x64.RBX, x64.R12, []byte{0x4C, 0x89, 0xE3}}, - {x64.RSP, x64.R11, []byte{0x4C, 0x89, 0xDC}}, - {x64.RBP, x64.R10, []byte{0x4C, 0x89, 0xD5}}, - {x64.RSI, x64.R9, []byte{0x4C, 0x89, 0xCE}}, - {x64.RDI, x64.R8, []byte{0x4C, 0x89, 0xC7}}, - {x64.R8, x64.RDI, []byte{0x49, 0x89, 0xF8}}, - {x64.R9, x64.RSI, []byte{0x49, 0x89, 0xF1}}, - {x64.R10, x64.RBP, []byte{0x49, 0x89, 0xEA}}, - {x64.R11, x64.RSP, []byte{0x49, 0x89, 0xE3}}, - {x64.R12, x64.RBX, []byte{0x49, 0x89, 0xDC}}, - {x64.R13, x64.RDX, []byte{0x49, 0x89, 0xD5}}, - {x64.R14, x64.RCX, []byte{0x49, 0x89, 0xCE}}, - {x64.R15, x64.RAX, []byte{0x49, 0x89, 0xC7}}, - } - - for _, pattern := range usagePatterns { - t.Logf("mov %s, %s", pattern.Left, pattern.Right) - code := x64.MoveRegisterRegister(nil, pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Mul_test.go b/src/x64/Mul_test.go deleted file mode 100644 index 03ffae1..0000000 --- a/src/x64/Mul_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestMulRegisterNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0x6B, 0xC0, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0x6B, 0xC9, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0x6B, 0xD2, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0x6B, 0xDB, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0x6B, 0xE4, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0x6B, 0xED, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0x6B, 0xF6, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0x6B, 0xFF, 0x01}}, - {x64.R8, 1, []byte{0x4D, 0x6B, 0xC0, 0x01}}, - {x64.R9, 1, []byte{0x4D, 0x6B, 0xC9, 0x01}}, - {x64.R10, 1, []byte{0x4D, 0x6B, 0xD2, 0x01}}, - {x64.R11, 1, []byte{0x4D, 0x6B, 0xDB, 0x01}}, - {x64.R12, 1, []byte{0x4D, 0x6B, 0xE4, 0x01}}, - {x64.R13, 1, []byte{0x4D, 0x6B, 0xED, 0x01}}, - {x64.R14, 1, []byte{0x4D, 0x6B, 0xF6, 0x01}}, - {x64.R15, 1, []byte{0x4D, 0x6B, 0xFF, 0x01}}, - - {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("mul %s, %x", pattern.Register, pattern.Number) - code := x64.MulRegisterNumber(nil, pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestMulRegisterRegister(t *testing.T) { - usagePatterns := []struct { - Left cpu.Register - Right cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, []byte{0x49, 0x0F, 0xAF, 0xC7}}, - {x64.RCX, x64.R14, []byte{0x49, 0x0F, 0xAF, 0xCE}}, - {x64.RDX, x64.R13, []byte{0x49, 0x0F, 0xAF, 0xD5}}, - {x64.RBX, x64.R12, []byte{0x49, 0x0F, 0xAF, 0xDC}}, - {x64.RSP, x64.R11, []byte{0x49, 0x0F, 0xAF, 0xE3}}, - {x64.RBP, x64.R10, []byte{0x49, 0x0F, 0xAF, 0xEA}}, - {x64.RSI, x64.R9, []byte{0x49, 0x0F, 0xAF, 0xF1}}, - {x64.RDI, x64.R8, []byte{0x49, 0x0F, 0xAF, 0xF8}}, - {x64.R8, x64.RDI, []byte{0x4C, 0x0F, 0xAF, 0xC7}}, - {x64.R9, x64.RSI, []byte{0x4C, 0x0F, 0xAF, 0xCE}}, - {x64.R10, x64.RBP, []byte{0x4C, 0x0F, 0xAF, 0xD5}}, - {x64.R11, x64.RSP, []byte{0x4C, 0x0F, 0xAF, 0xDC}}, - {x64.R12, x64.RBX, []byte{0x4C, 0x0F, 0xAF, 0xE3}}, - {x64.R13, x64.RDX, []byte{0x4C, 0x0F, 0xAF, 0xEA}}, - {x64.R14, x64.RCX, []byte{0x4C, 0x0F, 0xAF, 0xF1}}, - {x64.R15, x64.RAX, []byte{0x4C, 0x0F, 0xAF, 0xF8}}, - } - - for _, pattern := range usagePatterns { - t.Logf("mul %s, %s", pattern.Left, pattern.Right) - code := x64.MulRegisterRegister(nil, pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Negate_test.go b/src/x64/Negate_test.go deleted file mode 100644 index 625e601..0000000 --- a/src/x64/Negate_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestNegateRegister(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Code []byte - }{ - {x64.RAX, []byte{0x48, 0xF7, 0xD8}}, - {x64.RCX, []byte{0x48, 0xF7, 0xD9}}, - {x64.RDX, []byte{0x48, 0xF7, 0xDA}}, - {x64.RBX, []byte{0x48, 0xF7, 0xDB}}, - {x64.RSP, []byte{0x48, 0xF7, 0xDC}}, - {x64.RBP, []byte{0x48, 0xF7, 0xDD}}, - {x64.RSI, []byte{0x48, 0xF7, 0xDE}}, - {x64.RDI, []byte{0x48, 0xF7, 0xDF}}, - {x64.R8, []byte{0x49, 0xF7, 0xD8}}, - {x64.R9, []byte{0x49, 0xF7, 0xD9}}, - {x64.R10, []byte{0x49, 0xF7, 0xDA}}, - {x64.R11, []byte{0x49, 0xF7, 0xDB}}, - {x64.R12, []byte{0x49, 0xF7, 0xDC}}, - {x64.R13, []byte{0x49, 0xF7, 0xDD}}, - {x64.R14, []byte{0x49, 0xF7, 0xDE}}, - {x64.R15, []byte{0x49, 0xF7, 0xDF}}, - } - - for _, pattern := range usagePatterns { - t.Logf("neg %s", pattern.Register) - code := x64.NegateRegister(nil, pattern.Register) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Or_test.go b/src/x64/Or_test.go deleted file mode 100644 index d85065a..0000000 --- a/src/x64/Or_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestOrRegisterNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0x83, 0xC8, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0x83, 0xC9, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0x83, 0xCA, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0x83, 0xCB, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0x83, 0xCC, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0x83, 0xCD, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0x83, 0xCE, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0x83, 0xCF, 0x01}}, - {x64.R8, 1, []byte{0x49, 0x83, 0xC8, 0x01}}, - {x64.R9, 1, []byte{0x49, 0x83, 0xC9, 0x01}}, - {x64.R10, 1, []byte{0x49, 0x83, 0xCA, 0x01}}, - {x64.R11, 1, []byte{0x49, 0x83, 0xCB, 0x01}}, - {x64.R12, 1, []byte{0x49, 0x83, 0xCC, 0x01}}, - {x64.R13, 1, []byte{0x49, 0x83, 0xCD, 0x01}}, - {x64.R14, 1, []byte{0x49, 0x83, 0xCE, 0x01}}, - {x64.R15, 1, []byte{0x49, 0x83, 0xCF, 0x01}}, - - {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCF, 0xFF, 0xFF, 0xFF, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("or %s, %x", pattern.Register, pattern.Number) - code := x64.OrRegisterNumber(nil, pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestOrRegisterRegister(t *testing.T) { - usagePatterns := []struct { - Left cpu.Register - Right cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, []byte{0x4C, 0x09, 0xF8}}, - {x64.RCX, x64.R14, []byte{0x4C, 0x09, 0xF1}}, - {x64.RDX, x64.R13, []byte{0x4C, 0x09, 0xEA}}, - {x64.RBX, x64.R12, []byte{0x4C, 0x09, 0xE3}}, - {x64.RSP, x64.R11, []byte{0x4C, 0x09, 0xDC}}, - {x64.RBP, x64.R10, []byte{0x4C, 0x09, 0xD5}}, - {x64.RSI, x64.R9, []byte{0x4C, 0x09, 0xCE}}, - {x64.RDI, x64.R8, []byte{0x4C, 0x09, 0xC7}}, - {x64.R8, x64.RDI, []byte{0x49, 0x09, 0xF8}}, - {x64.R9, x64.RSI, []byte{0x49, 0x09, 0xF1}}, - {x64.R10, x64.RBP, []byte{0x49, 0x09, 0xEA}}, - {x64.R11, x64.RSP, []byte{0x49, 0x09, 0xE3}}, - {x64.R12, x64.RBX, []byte{0x49, 0x09, 0xDC}}, - {x64.R13, x64.RDX, []byte{0x49, 0x09, 0xD5}}, - {x64.R14, x64.RCX, []byte{0x49, 0x09, 0xCE}}, - {x64.R15, x64.RAX, []byte{0x49, 0x09, 0xC7}}, - } - - for _, pattern := range usagePatterns { - t.Logf("or %s, %s", pattern.Left, pattern.Right) - code := x64.OrRegisterRegister(nil, pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Pop_test.go b/src/x64/Pop_test.go deleted file mode 100644 index 446eebe..0000000 --- a/src/x64/Pop_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestPopRegister(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Code []byte - }{ - {x64.RAX, []byte{0x58}}, - {x64.RCX, []byte{0x59}}, - {x64.RDX, []byte{0x5A}}, - {x64.RBX, []byte{0x5B}}, - {x64.RSP, []byte{0x5C}}, - {x64.RBP, []byte{0x5D}}, - {x64.RSI, []byte{0x5E}}, - {x64.RDI, []byte{0x5F}}, - {x64.R8, []byte{0x41, 0x58}}, - {x64.R9, []byte{0x41, 0x59}}, - {x64.R10, []byte{0x41, 0x5A}}, - {x64.R11, []byte{0x41, 0x5B}}, - {x64.R12, []byte{0x41, 0x5C}}, - {x64.R13, []byte{0x41, 0x5D}}, - {x64.R14, []byte{0x41, 0x5E}}, - {x64.R15, []byte{0x41, 0x5F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("pop %s", pattern.Register) - code := x64.PopRegister(nil, pattern.Register) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Push_test.go b/src/x64/Push_test.go deleted file mode 100644 index a49095d..0000000 --- a/src/x64/Push_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestPushRegister(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Code []byte - }{ - {x64.RAX, []byte{0x50}}, - {x64.RCX, []byte{0x51}}, - {x64.RDX, []byte{0x52}}, - {x64.RBX, []byte{0x53}}, - {x64.RSP, []byte{0x54}}, - {x64.RBP, []byte{0x55}}, - {x64.RSI, []byte{0x56}}, - {x64.RDI, []byte{0x57}}, - {x64.R8, []byte{0x41, 0x50}}, - {x64.R9, []byte{0x41, 0x51}}, - {x64.R10, []byte{0x41, 0x52}}, - {x64.R11, []byte{0x41, 0x53}}, - {x64.R12, []byte{0x41, 0x54}}, - {x64.R13, []byte{0x41, 0x55}}, - {x64.R14, []byte{0x41, 0x56}}, - {x64.R15, []byte{0x41, 0x57}}, - } - - for _, pattern := range usagePatterns { - t.Logf("push %s", pattern.Register) - code := x64.PushRegister(nil, pattern.Register) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Registers_test.go b/src/x64/Registers_test.go deleted file mode 100644 index 626bb84..0000000 --- a/src/x64/Registers_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestRegisters(t *testing.T) { - assert.NotContains(t, x64.GeneralRegisters, x64.RSP) -} diff --git a/src/x64/Shift_test.go b/src/x64/Shift_test.go deleted file mode 100644 index dc95cf2..0000000 --- a/src/x64/Shift_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestShiftLeftNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0xC1, 0xE0, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0xC1, 0xE1, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0xC1, 0xE2, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0xC1, 0xE3, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0xC1, 0xE4, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0xC1, 0xE5, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0xC1, 0xE6, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0xC1, 0xE7, 0x01}}, - {x64.R8, 1, []byte{0x49, 0xC1, 0xE0, 0x01}}, - {x64.R9, 1, []byte{0x49, 0xC1, 0xE1, 0x01}}, - {x64.R10, 1, []byte{0x49, 0xC1, 0xE2, 0x01}}, - {x64.R11, 1, []byte{0x49, 0xC1, 0xE3, 0x01}}, - {x64.R12, 1, []byte{0x49, 0xC1, 0xE4, 0x01}}, - {x64.R13, 1, []byte{0x49, 0xC1, 0xE5, 0x01}}, - {x64.R14, 1, []byte{0x49, 0xC1, 0xE6, 0x01}}, - {x64.R15, 1, []byte{0x49, 0xC1, 0xE7, 0x01}}, - } - - for _, pattern := range usagePatterns { - t.Logf("shl %s, %x", pattern.Register, pattern.Number) - code := x64.ShiftLeftNumber(nil, pattern.Register, byte(pattern.Number)) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestShiftRightSignedNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0xC1, 0xF8, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0xC1, 0xF9, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0xC1, 0xFA, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0xC1, 0xFB, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0xC1, 0xFC, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0xC1, 0xFD, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0xC1, 0xFE, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0xC1, 0xFF, 0x01}}, - {x64.R8, 1, []byte{0x49, 0xC1, 0xF8, 0x01}}, - {x64.R9, 1, []byte{0x49, 0xC1, 0xF9, 0x01}}, - {x64.R10, 1, []byte{0x49, 0xC1, 0xFA, 0x01}}, - {x64.R11, 1, []byte{0x49, 0xC1, 0xFB, 0x01}}, - {x64.R12, 1, []byte{0x49, 0xC1, 0xFC, 0x01}}, - {x64.R13, 1, []byte{0x49, 0xC1, 0xFD, 0x01}}, - {x64.R14, 1, []byte{0x49, 0xC1, 0xFE, 0x01}}, - {x64.R15, 1, []byte{0x49, 0xC1, 0xFF, 0x01}}, - } - - for _, pattern := range usagePatterns { - t.Logf("sar %s, %x", pattern.Register, pattern.Number) - code := x64.ShiftRightSignedNumber(nil, pattern.Register, byte(pattern.Number)) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/StoreDynamic_test.go b/src/x64/StoreDynamic_test.go deleted file mode 100644 index 0ff1f82..0000000 --- a/src/x64/StoreDynamic_test.go +++ /dev/null @@ -1,171 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestStoreDynamicNumber(t *testing.T) { - usagePatterns := []struct { - RegisterTo cpu.Register - Offset cpu.Register - Length byte - Number int - Code []byte - }{ - {x64.RAX, x64.R15, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RAX, x64.R15, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RAX, x64.R15, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, - {x64.RAX, x64.R15, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x38, 0x7F}}, - {x64.RCX, x64.R14, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RCX, x64.R14, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RCX, x64.R14, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, - {x64.RCX, x64.R14, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x31, 0x7F}}, - {x64.RDX, x64.R13, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDX, x64.R13, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDX, x64.R13, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, - {x64.RDX, x64.R13, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x2A, 0x7F}}, - {x64.RBX, x64.R12, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBX, x64.R12, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBX, x64.R12, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00}}, - {x64.RBX, x64.R12, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x23, 0x7F}}, - {x64.RSP, x64.R11, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSP, x64.R11, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSP, x64.R11, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, - {x64.RSP, x64.R11, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, - {x64.RBP, x64.R10, 8, 0x7F, []byte{0x4A, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBP, x64.R10, 4, 0x7F, []byte{0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBP, x64.R10, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, - {x64.RBP, x64.R10, 1, 0x7F, []byte{0x42, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, - {x64.RSI, x64.R9, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSI, x64.R9, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSI, x64.R9, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, - {x64.RSI, x64.R9, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x0E, 0x7F}}, - {x64.RDI, x64.R8, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDI, x64.R8, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDI, x64.R8, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, - {x64.RDI, x64.R8, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x07, 0x7F}}, - {x64.R8, x64.RDI, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R8, x64.RDI, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R8, x64.RDI, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, - {x64.R8, x64.RDI, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x38, 0x7F}}, - {x64.R9, x64.RSI, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R9, x64.RSI, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R9, x64.RSI, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, - {x64.R9, x64.RSI, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x31, 0x7F}}, - {x64.R10, x64.RBP, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R10, x64.RBP, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R10, x64.RBP, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, - {x64.R10, x64.RBP, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x2A, 0x7F}}, - {x64.R11, x64.RSP, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R11, x64.RSP, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R11, x64.RSP, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, - {x64.R11, x64.RSP, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, - {x64.R12, x64.RBX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R12, x64.RBX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R12, x64.RBX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, - {x64.R12, x64.RBX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x1C, 0x7F}}, - {x64.R13, x64.RDX, 8, 0x7F, []byte{0x49, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R13, x64.RDX, 4, 0x7F, []byte{0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R13, x64.RDX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, - {x64.R13, x64.RDX, 1, 0x7F, []byte{0x41, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, - {x64.R14, x64.RCX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R14, x64.RCX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R14, x64.RCX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, - {x64.R14, x64.RCX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x0E, 0x7F}}, - {x64.R15, x64.RAX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R15, x64.RAX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R15, x64.RAX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, - {x64.R15, x64.RAX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x07, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%s], %d", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.Number) - code := x64.StoreDynamicNumber(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestStoreDynamicRegister(t *testing.T) { - usagePatterns := []struct { - RegisterTo cpu.Register - Offset cpu.Register - Length byte - RegisterFrom cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, 8, x64.R15, []byte{0x4E, 0x89, 0x3C, 0x38}}, - {x64.RAX, x64.R15, 4, x64.R15, []byte{0x46, 0x89, 0x3C, 0x38}}, - {x64.RAX, x64.R15, 2, x64.R15, []byte{0x66, 0x46, 0x89, 0x3C, 0x38}}, - {x64.RAX, x64.R15, 1, x64.R15, []byte{0x46, 0x88, 0x3C, 0x38}}, - {x64.RCX, x64.R14, 8, x64.R14, []byte{0x4E, 0x89, 0x34, 0x31}}, - {x64.RCX, x64.R14, 4, x64.R14, []byte{0x46, 0x89, 0x34, 0x31}}, - {x64.RCX, x64.R14, 2, x64.R14, []byte{0x66, 0x46, 0x89, 0x34, 0x31}}, - {x64.RCX, x64.R14, 1, x64.R14, []byte{0x46, 0x88, 0x34, 0x31}}, - {x64.RDX, x64.R13, 8, x64.R13, []byte{0x4E, 0x89, 0x2C, 0x2A}}, - {x64.RDX, x64.R13, 4, x64.R13, []byte{0x46, 0x89, 0x2C, 0x2A}}, - {x64.RDX, x64.R13, 2, x64.R13, []byte{0x66, 0x46, 0x89, 0x2C, 0x2A}}, - {x64.RDX, x64.R13, 1, x64.R13, []byte{0x46, 0x88, 0x2C, 0x2A}}, - {x64.RBX, x64.R12, 8, x64.R12, []byte{0x4E, 0x89, 0x24, 0x23}}, - {x64.RBX, x64.R12, 4, x64.R12, []byte{0x46, 0x89, 0x24, 0x23}}, - {x64.RBX, x64.R12, 2, x64.R12, []byte{0x66, 0x46, 0x89, 0x24, 0x23}}, - {x64.RBX, x64.R12, 1, x64.R12, []byte{0x46, 0x88, 0x24, 0x23}}, - {x64.RSP, x64.R11, 8, x64.R11, []byte{0x4E, 0x89, 0x1C, 0x1C}}, - {x64.RSP, x64.R11, 4, x64.R11, []byte{0x46, 0x89, 0x1C, 0x1C}}, - {x64.RSP, x64.R11, 2, x64.R11, []byte{0x66, 0x46, 0x89, 0x1C, 0x1C}}, - {x64.RSP, x64.R11, 1, x64.R11, []byte{0x46, 0x88, 0x1C, 0x1C}}, - {x64.RBP, x64.R10, 8, x64.R10, []byte{0x4E, 0x89, 0x54, 0x15, 0x00}}, - {x64.RBP, x64.R10, 4, x64.R10, []byte{0x46, 0x89, 0x54, 0x15, 0x00}}, - {x64.RBP, x64.R10, 2, x64.R10, []byte{0x66, 0x46, 0x89, 0x54, 0x15, 0x00}}, - {x64.RBP, x64.R10, 1, x64.R10, []byte{0x46, 0x88, 0x54, 0x15, 0x00}}, - {x64.RSI, x64.R9, 8, x64.R9, []byte{0x4E, 0x89, 0x0C, 0x0E}}, - {x64.RSI, x64.R9, 4, x64.R9, []byte{0x46, 0x89, 0x0C, 0x0E}}, - {x64.RSI, x64.R9, 2, x64.R9, []byte{0x66, 0x46, 0x89, 0x0C, 0x0E}}, - {x64.RSI, x64.R9, 1, x64.R9, []byte{0x46, 0x88, 0x0C, 0x0E}}, - {x64.RDI, x64.R8, 8, x64.R8, []byte{0x4E, 0x89, 0x04, 0x07}}, - {x64.RDI, x64.R8, 4, x64.R8, []byte{0x46, 0x89, 0x04, 0x07}}, - {x64.RDI, x64.R8, 2, x64.R8, []byte{0x66, 0x46, 0x89, 0x04, 0x07}}, - {x64.RDI, x64.R8, 1, x64.R8, []byte{0x46, 0x88, 0x04, 0x07}}, - {x64.R8, x64.RDI, 8, x64.RDI, []byte{0x49, 0x89, 0x3C, 0x38}}, - {x64.R8, x64.RDI, 4, x64.RDI, []byte{0x41, 0x89, 0x3C, 0x38}}, - {x64.R8, x64.RDI, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x3C, 0x38}}, - {x64.R8, x64.RDI, 1, x64.RDI, []byte{0x41, 0x88, 0x3C, 0x38}}, - {x64.R9, x64.RSI, 8, x64.RSI, []byte{0x49, 0x89, 0x34, 0x31}}, - {x64.R9, x64.RSI, 4, x64.RSI, []byte{0x41, 0x89, 0x34, 0x31}}, - {x64.R9, x64.RSI, 2, x64.RSI, []byte{0x66, 0x41, 0x89, 0x34, 0x31}}, - {x64.R9, x64.RSI, 1, x64.RSI, []byte{0x41, 0x88, 0x34, 0x31}}, - {x64.R10, x64.RBP, 8, x64.RBP, []byte{0x49, 0x89, 0x2C, 0x2A}}, - {x64.R10, x64.RBP, 4, x64.RBP, []byte{0x41, 0x89, 0x2C, 0x2A}}, - {x64.R10, x64.RBP, 2, x64.RBP, []byte{0x66, 0x41, 0x89, 0x2C, 0x2A}}, - {x64.R10, x64.RBP, 1, x64.RBP, []byte{0x41, 0x88, 0x2C, 0x2A}}, - {x64.R11, x64.RSP, 8, x64.RSP, []byte{0x4A, 0x89, 0x24, 0x1C}}, - {x64.R11, x64.RSP, 4, x64.RSP, []byte{0x42, 0x89, 0x24, 0x1C}}, - {x64.R11, x64.RSP, 2, x64.RSP, []byte{0x66, 0x42, 0x89, 0x24, 0x1C}}, - {x64.R11, x64.RSP, 1, x64.RSP, []byte{0x42, 0x88, 0x24, 0x1C}}, - {x64.R12, x64.RBX, 8, x64.RBX, []byte{0x49, 0x89, 0x1C, 0x1C}}, - {x64.R12, x64.RBX, 4, x64.RBX, []byte{0x41, 0x89, 0x1C, 0x1C}}, - {x64.R12, x64.RBX, 2, x64.RBX, []byte{0x66, 0x41, 0x89, 0x1C, 0x1C}}, - {x64.R12, x64.RBX, 1, x64.RBX, []byte{0x41, 0x88, 0x1C, 0x1C}}, - {x64.R13, x64.RDX, 8, x64.RDX, []byte{0x49, 0x89, 0x54, 0x15, 0x00}}, - {x64.R13, x64.RDX, 4, x64.RDX, []byte{0x41, 0x89, 0x54, 0x15, 0x00}}, - {x64.R13, x64.RDX, 2, x64.RDX, []byte{0x66, 0x41, 0x89, 0x54, 0x15, 0x00}}, - {x64.R13, x64.RDX, 1, x64.RDX, []byte{0x41, 0x88, 0x54, 0x15, 0x00}}, - {x64.R14, x64.RCX, 8, x64.RCX, []byte{0x49, 0x89, 0x0C, 0x0E}}, - {x64.R14, x64.RCX, 4, x64.RCX, []byte{0x41, 0x89, 0x0C, 0x0E}}, - {x64.R14, x64.RCX, 2, x64.RCX, []byte{0x66, 0x41, 0x89, 0x0C, 0x0E}}, - {x64.R14, x64.RCX, 1, x64.RCX, []byte{0x41, 0x88, 0x0C, 0x0E}}, - {x64.R15, x64.RAX, 8, x64.RAX, []byte{0x49, 0x89, 0x04, 0x07}}, - {x64.R15, x64.RAX, 4, x64.RAX, []byte{0x41, 0x89, 0x04, 0x07}}, - {x64.R15, x64.RAX, 2, x64.RAX, []byte{0x66, 0x41, 0x89, 0x04, 0x07}}, - {x64.R15, x64.RAX, 1, x64.RAX, []byte{0x41, 0x88, 0x04, 0x07}}, - } - - for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%s], %s", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) - code := x64.StoreDynamicRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Store_test.go b/src/x64/Store_test.go deleted file mode 100644 index 3f4bd05..0000000 --- a/src/x64/Store_test.go +++ /dev/null @@ -1,305 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestStoreNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Offset byte - Length byte - Number int - Code []byte - }{ - // No offset - {x64.RAX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RAX, 0, 4, 0x7F, []byte{0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RAX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x00, 0x7F, 0x00}}, - {x64.RAX, 0, 1, 0x7F, []byte{0xC6, 0x00, 0x7F}}, - {x64.RCX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RCX, 0, 4, 0x7F, []byte{0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RCX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x01, 0x7F, 0x00}}, - {x64.RCX, 0, 1, 0x7F, []byte{0xC6, 0x01, 0x7F}}, - {x64.RDX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDX, 0, 4, 0x7F, []byte{0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x02, 0x7F, 0x00}}, - {x64.RDX, 0, 1, 0x7F, []byte{0xC6, 0x02, 0x7F}}, - {x64.RBX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBX, 0, 4, 0x7F, []byte{0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x03, 0x7F, 0x00}}, - {x64.RBX, 0, 1, 0x7F, []byte{0xC6, 0x03, 0x7F}}, - {x64.RSP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSP, 0, 4, 0x7F, []byte{0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x04, 0x24, 0x7F, 0x00}}, - {x64.RSP, 0, 1, 0x7F, []byte{0xC6, 0x04, 0x24, 0x7F}}, - {x64.RBP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBP, 0, 4, 0x7F, []byte{0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x00, 0x7F, 0x00}}, - {x64.RBP, 0, 1, 0x7F, []byte{0xC6, 0x45, 0x00, 0x7F}}, - {x64.RSI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSI, 0, 4, 0x7F, []byte{0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x06, 0x7F, 0x00}}, - {x64.RSI, 0, 1, 0x7F, []byte{0xC6, 0x06, 0x7F}}, - {x64.RDI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDI, 0, 4, 0x7F, []byte{0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x07, 0x7F, 0x00}}, - {x64.RDI, 0, 1, 0x7F, []byte{0xC6, 0x07, 0x7F}}, - {x64.R8, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R8, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R8, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x00, 0x7F, 0x00}}, - {x64.R8, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x00, 0x7F}}, - {x64.R9, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R9, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R9, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x01, 0x7F, 0x00}}, - {x64.R9, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x01, 0x7F}}, - {x64.R10, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R10, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R10, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x02, 0x7F, 0x00}}, - {x64.R10, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x02, 0x7F}}, - {x64.R11, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R11, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R11, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x03, 0x7F, 0x00}}, - {x64.R11, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x03, 0x7F}}, - {x64.R12, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R12, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R12, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x24, 0x7F, 0x00}}, - {x64.R12, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x24, 0x7F}}, - {x64.R13, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R13, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R13, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x45, 0x00, 0x7F, 0x00}}, - {x64.R13, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x45, 0x00, 0x7F}}, - {x64.R14, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R14, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R14, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x06, 0x7F, 0x00}}, - {x64.R14, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x06, 0x7F}}, - {x64.R15, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R15, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R15, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x07, 0x7F, 0x00}}, - {x64.R15, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x07, 0x7F}}, - - // Offset of 1 - {x64.RAX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RAX, 1, 4, 0x7F, []byte{0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RAX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, - {x64.RAX, 1, 1, 0x7F, []byte{0xC6, 0x40, 0x01, 0x7F}}, - {x64.RCX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RCX, 1, 4, 0x7F, []byte{0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RCX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x41, 0x01, 0x7F, 0x00}}, - {x64.RCX, 1, 1, 0x7F, []byte{0xC6, 0x41, 0x01, 0x7F}}, - {x64.RDX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDX, 1, 4, 0x7F, []byte{0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x42, 0x01, 0x7F, 0x00}}, - {x64.RDX, 1, 1, 0x7F, []byte{0xC6, 0x42, 0x01, 0x7F}}, - {x64.RBX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBX, 1, 4, 0x7F, []byte{0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, - {x64.RBX, 1, 1, 0x7F, []byte{0xC6, 0x43, 0x01, 0x7F}}, - {x64.RSP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSP, 1, 4, 0x7F, []byte{0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, - {x64.RSP, 1, 1, 0x7F, []byte{0xC6, 0x44, 0x24, 0x01, 0x7F}}, - {x64.RBP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBP, 1, 4, 0x7F, []byte{0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RBP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, - {x64.RBP, 1, 1, 0x7F, []byte{0xC6, 0x45, 0x01, 0x7F}}, - {x64.RSI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSI, 1, 4, 0x7F, []byte{0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RSI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, - {x64.RSI, 1, 1, 0x7F, []byte{0xC6, 0x46, 0x01, 0x7F}}, - {x64.RDI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDI, 1, 4, 0x7F, []byte{0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.RDI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, - {x64.RDI, 1, 1, 0x7F, []byte{0xC6, 0x47, 0x01, 0x7F}}, - {x64.R8, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R8, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R8, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, - {x64.R8, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x40, 0x01, 0x7F}}, - {x64.R9, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R9, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R9, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x41, 0x01, 0x7F, 0x00}}, - {x64.R9, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x41, 0x01, 0x7F}}, - {x64.R10, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R10, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R10, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x42, 0x01, 0x7F, 0x00}}, - {x64.R10, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x42, 0x01, 0x7F}}, - {x64.R11, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R11, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R11, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, - {x64.R11, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x43, 0x01, 0x7F}}, - {x64.R12, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R12, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R12, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, - {x64.R12, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x44, 0x24, 0x01, 0x7F}}, - {x64.R13, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R13, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R13, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, - {x64.R13, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x45, 0x01, 0x7F}}, - {x64.R14, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R14, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R14, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, - {x64.R14, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x46, 0x01, 0x7F}}, - {x64.R15, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R15, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x64.R15, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, - {x64.R15, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x47, 0x01, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%d], %d", pattern.Length, pattern.Register, pattern.Offset, pattern.Number) - code := x64.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.Length, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestStoreRegister(t *testing.T) { - usagePatterns := []struct { - RegisterTo cpu.Register - Offset byte - Length byte - RegisterFrom cpu.Register - Code []byte - }{ - // No offset - {x64.RAX, 0, 8, x64.R15, []byte{0x4C, 0x89, 0x38}}, - {x64.RAX, 0, 4, x64.R15, []byte{0x44, 0x89, 0x38}}, - {x64.RAX, 0, 2, x64.R15, []byte{0x66, 0x44, 0x89, 0x38}}, - {x64.RAX, 0, 1, x64.R15, []byte{0x44, 0x88, 0x38}}, - {x64.RCX, 0, 8, x64.R14, []byte{0x4C, 0x89, 0x31}}, - {x64.RCX, 0, 4, x64.R14, []byte{0x44, 0x89, 0x31}}, - {x64.RCX, 0, 2, x64.R14, []byte{0x66, 0x44, 0x89, 0x31}}, - {x64.RCX, 0, 1, x64.R14, []byte{0x44, 0x88, 0x31}}, - {x64.RDX, 0, 8, x64.R13, []byte{0x4C, 0x89, 0x2A}}, - {x64.RDX, 0, 4, x64.R13, []byte{0x44, 0x89, 0x2A}}, - {x64.RDX, 0, 2, x64.R13, []byte{0x66, 0x44, 0x89, 0x2A}}, - {x64.RDX, 0, 1, x64.R13, []byte{0x44, 0x88, 0x2A}}, - {x64.RBX, 0, 8, x64.R12, []byte{0x4C, 0x89, 0x23}}, - {x64.RBX, 0, 4, x64.R12, []byte{0x44, 0x89, 0x23}}, - {x64.RBX, 0, 2, x64.R12, []byte{0x66, 0x44, 0x89, 0x23}}, - {x64.RBX, 0, 1, x64.R12, []byte{0x44, 0x88, 0x23}}, - {x64.RSP, 0, 8, x64.R11, []byte{0x4C, 0x89, 0x1C, 0x24}}, - {x64.RSP, 0, 4, x64.R11, []byte{0x44, 0x89, 0x1C, 0x24}}, - {x64.RSP, 0, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x1C, 0x24}}, - {x64.RSP, 0, 1, x64.R11, []byte{0x44, 0x88, 0x1C, 0x24}}, - {x64.RBP, 0, 8, x64.R10, []byte{0x4C, 0x89, 0x55, 0x00}}, - {x64.RBP, 0, 4, x64.R10, []byte{0x44, 0x89, 0x55, 0x00}}, - {x64.RBP, 0, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x00}}, - {x64.RBP, 0, 1, x64.R10, []byte{0x44, 0x88, 0x55, 0x00}}, - {x64.RSI, 0, 8, x64.R9, []byte{0x4C, 0x89, 0x0E}}, - {x64.RSI, 0, 4, x64.R9, []byte{0x44, 0x89, 0x0E}}, - {x64.RSI, 0, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x0E}}, - {x64.RSI, 0, 1, x64.R9, []byte{0x44, 0x88, 0x0E}}, - {x64.RDI, 0, 8, x64.R8, []byte{0x4C, 0x89, 0x07}}, - {x64.RDI, 0, 4, x64.R8, []byte{0x44, 0x89, 0x07}}, - {x64.RDI, 0, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x07}}, - {x64.RDI, 0, 1, x64.R8, []byte{0x44, 0x88, 0x07}}, - {x64.R8, 0, 8, x64.RDI, []byte{0x49, 0x89, 0x38}}, - {x64.R8, 0, 4, x64.RDI, []byte{0x41, 0x89, 0x38}}, - {x64.R8, 0, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x38}}, - {x64.R8, 0, 1, x64.RDI, []byte{0x41, 0x88, 0x38}}, - {x64.R9, 0, 8, x64.RSI, []byte{0x49, 0x89, 0x31}}, - {x64.R9, 0, 4, x64.RSI, []byte{0x41, 0x89, 0x31}}, - {x64.R9, 0, 2, x64.RSI, []byte{0x66, 0x41, 0x89, 0x31}}, - {x64.R9, 0, 1, x64.RSI, []byte{0x41, 0x88, 0x31}}, - {x64.R10, 0, 8, x64.RBP, []byte{0x49, 0x89, 0x2A}}, - {x64.R10, 0, 4, x64.RBP, []byte{0x41, 0x89, 0x2A}}, - {x64.R10, 0, 2, x64.RBP, []byte{0x66, 0x41, 0x89, 0x2A}}, - {x64.R10, 0, 1, x64.RBP, []byte{0x41, 0x88, 0x2A}}, - {x64.R11, 0, 8, x64.RSP, []byte{0x49, 0x89, 0x23}}, - {x64.R11, 0, 4, x64.RSP, []byte{0x41, 0x89, 0x23}}, - {x64.R11, 0, 2, x64.RSP, []byte{0x66, 0x41, 0x89, 0x23}}, - {x64.R11, 0, 1, x64.RSP, []byte{0x41, 0x88, 0x23}}, - {x64.R12, 0, 8, x64.RBX, []byte{0x49, 0x89, 0x1C, 0x24}}, - {x64.R12, 0, 4, x64.RBX, []byte{0x41, 0x89, 0x1C, 0x24}}, - {x64.R12, 0, 2, x64.RBX, []byte{0x66, 0x41, 0x89, 0x1C, 0x24}}, - {x64.R12, 0, 1, x64.RBX, []byte{0x41, 0x88, 0x1C, 0x24}}, - {x64.R13, 0, 8, x64.RDX, []byte{0x49, 0x89, 0x55, 0x00}}, - {x64.R13, 0, 4, x64.RDX, []byte{0x41, 0x89, 0x55, 0x00}}, - {x64.R13, 0, 2, x64.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x00}}, - {x64.R13, 0, 1, x64.RDX, []byte{0x41, 0x88, 0x55, 0x00}}, - {x64.R14, 0, 8, x64.RCX, []byte{0x49, 0x89, 0x0E}}, - {x64.R14, 0, 4, x64.RCX, []byte{0x41, 0x89, 0x0E}}, - {x64.R14, 0, 2, x64.RCX, []byte{0x66, 0x41, 0x89, 0x0E}}, - {x64.R14, 0, 1, x64.RCX, []byte{0x41, 0x88, 0x0E}}, - {x64.R15, 0, 8, x64.RAX, []byte{0x49, 0x89, 0x07}}, - {x64.R15, 0, 4, x64.RAX, []byte{0x41, 0x89, 0x07}}, - {x64.R15, 0, 2, x64.RAX, []byte{0x66, 0x41, 0x89, 0x07}}, - {x64.R15, 0, 1, x64.RAX, []byte{0x41, 0x88, 0x07}}, - - // Offset of 1 - {x64.RAX, 1, 8, x64.R15, []byte{0x4C, 0x89, 0x78, 0x01}}, - {x64.RAX, 1, 4, x64.R15, []byte{0x44, 0x89, 0x78, 0x01}}, - {x64.RAX, 1, 2, x64.R15, []byte{0x66, 0x44, 0x89, 0x78, 0x01}}, - {x64.RAX, 1, 1, x64.R15, []byte{0x44, 0x88, 0x78, 0x01}}, - {x64.RCX, 1, 8, x64.R14, []byte{0x4C, 0x89, 0x71, 0x01}}, - {x64.RCX, 1, 4, x64.R14, []byte{0x44, 0x89, 0x71, 0x01}}, - {x64.RCX, 1, 2, x64.R14, []byte{0x66, 0x44, 0x89, 0x71, 0x01}}, - {x64.RCX, 1, 1, x64.R14, []byte{0x44, 0x88, 0x71, 0x01}}, - {x64.RDX, 1, 8, x64.R13, []byte{0x4C, 0x89, 0x6A, 0x01}}, - {x64.RDX, 1, 4, x64.R13, []byte{0x44, 0x89, 0x6A, 0x01}}, - {x64.RDX, 1, 2, x64.R13, []byte{0x66, 0x44, 0x89, 0x6A, 0x01}}, - {x64.RDX, 1, 1, x64.R13, []byte{0x44, 0x88, 0x6A, 0x01}}, - {x64.RBX, 1, 8, x64.R12, []byte{0x4C, 0x89, 0x63, 0x01}}, - {x64.RBX, 1, 4, x64.R12, []byte{0x44, 0x89, 0x63, 0x01}}, - {x64.RBX, 1, 2, x64.R12, []byte{0x66, 0x44, 0x89, 0x63, 0x01}}, - {x64.RBX, 1, 1, x64.R12, []byte{0x44, 0x88, 0x63, 0x01}}, - {x64.RSP, 1, 8, x64.R11, []byte{0x4C, 0x89, 0x5C, 0x24, 0x01}}, - {x64.RSP, 1, 4, x64.R11, []byte{0x44, 0x89, 0x5C, 0x24, 0x01}}, - {x64.RSP, 1, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x5C, 0x24, 0x01}}, - {x64.RSP, 1, 1, x64.R11, []byte{0x44, 0x88, 0x5C, 0x24, 0x01}}, - {x64.RBP, 1, 8, x64.R10, []byte{0x4C, 0x89, 0x55, 0x01}}, - {x64.RBP, 1, 4, x64.R10, []byte{0x44, 0x89, 0x55, 0x01}}, - {x64.RBP, 1, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x01}}, - {x64.RBP, 1, 1, x64.R10, []byte{0x44, 0x88, 0x55, 0x01}}, - {x64.RSI, 1, 8, x64.R9, []byte{0x4C, 0x89, 0x4E, 0x01}}, - {x64.RSI, 1, 4, x64.R9, []byte{0x44, 0x89, 0x4E, 0x01}}, - {x64.RSI, 1, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x4E, 0x01}}, - {x64.RSI, 1, 1, x64.R9, []byte{0x44, 0x88, 0x4E, 0x01}}, - {x64.RDI, 1, 8, x64.R8, []byte{0x4C, 0x89, 0x47, 0x01}}, - {x64.RDI, 1, 4, x64.R8, []byte{0x44, 0x89, 0x47, 0x01}}, - {x64.RDI, 1, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x47, 0x01}}, - {x64.RDI, 1, 1, x64.R8, []byte{0x44, 0x88, 0x47, 0x01}}, - {x64.R8, 1, 8, x64.RDI, []byte{0x49, 0x89, 0x78, 0x01}}, - {x64.R8, 1, 4, x64.RDI, []byte{0x41, 0x89, 0x78, 0x01}}, - {x64.R8, 1, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x78, 0x01}}, - {x64.R8, 1, 1, x64.RDI, []byte{0x41, 0x88, 0x78, 0x01}}, - {x64.R9, 1, 8, x64.RSI, []byte{0x49, 0x89, 0x71, 0x01}}, - {x64.R9, 1, 4, x64.RSI, []byte{0x41, 0x89, 0x71, 0x01}}, - {x64.R9, 1, 2, x64.RSI, []byte{0x66, 0x41, 0x89, 0x71, 0x01}}, - {x64.R9, 1, 1, x64.RSI, []byte{0x41, 0x88, 0x71, 0x01}}, - {x64.R10, 1, 8, x64.RBP, []byte{0x49, 0x89, 0x6A, 0x01}}, - {x64.R10, 1, 4, x64.RBP, []byte{0x41, 0x89, 0x6A, 0x01}}, - {x64.R10, 1, 2, x64.RBP, []byte{0x66, 0x41, 0x89, 0x6A, 0x01}}, - {x64.R10, 1, 1, x64.RBP, []byte{0x41, 0x88, 0x6A, 0x01}}, - {x64.R11, 1, 8, x64.RSP, []byte{0x49, 0x89, 0x63, 0x01}}, - {x64.R11, 1, 4, x64.RSP, []byte{0x41, 0x89, 0x63, 0x01}}, - {x64.R11, 1, 2, x64.RSP, []byte{0x66, 0x41, 0x89, 0x63, 0x01}}, - {x64.R11, 1, 1, x64.RSP, []byte{0x41, 0x88, 0x63, 0x01}}, - {x64.R12, 1, 8, x64.RBX, []byte{0x49, 0x89, 0x5C, 0x24, 0x01}}, - {x64.R12, 1, 4, x64.RBX, []byte{0x41, 0x89, 0x5C, 0x24, 0x01}}, - {x64.R12, 1, 2, x64.RBX, []byte{0x66, 0x41, 0x89, 0x5C, 0x24, 0x01}}, - {x64.R12, 1, 1, x64.RBX, []byte{0x41, 0x88, 0x5C, 0x24, 01}}, - {x64.R13, 1, 8, x64.RDX, []byte{0x49, 0x89, 0x55, 0x01}}, - {x64.R13, 1, 4, x64.RDX, []byte{0x41, 0x89, 0x55, 0x01}}, - {x64.R13, 1, 2, x64.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x01}}, - {x64.R13, 1, 1, x64.RDX, []byte{0x41, 0x88, 0x55, 0x01}}, - {x64.R14, 1, 8, x64.RCX, []byte{0x49, 0x89, 0x4E, 0x01}}, - {x64.R14, 1, 4, x64.RCX, []byte{0x41, 0x89, 0x4E, 0x01}}, - {x64.R14, 1, 2, x64.RCX, []byte{0x66, 0x41, 0x89, 0x4E, 0x01}}, - {x64.R14, 1, 1, x64.RCX, []byte{0x41, 0x88, 0x4E, 0x01}}, - {x64.R15, 1, 8, x64.RAX, []byte{0x49, 0x89, 0x47, 0x01}}, - {x64.R15, 1, 4, x64.RAX, []byte{0x41, 0x89, 0x47, 0x01}}, - {x64.R15, 1, 2, x64.RAX, []byte{0x66, 0x41, 0x89, 0x47, 0x01}}, - {x64.R15, 1, 1, x64.RAX, []byte{0x41, 0x88, 0x47, 0x01}}, - } - - for _, pattern := range usagePatterns { - t.Logf("store %dB [%s+%d], %s", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) - code := x64.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Sub_test.go b/src/x64/Sub_test.go deleted file mode 100644 index 7f03b53..0000000 --- a/src/x64/Sub_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestSubRegisterNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0x83, 0xE8, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0x83, 0xE9, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0x83, 0xEA, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0x83, 0xEB, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0x83, 0xEC, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0x83, 0xED, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0x83, 0xEE, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0x83, 0xEF, 0x01}}, - {x64.R8, 1, []byte{0x49, 0x83, 0xE8, 0x01}}, - {x64.R9, 1, []byte{0x49, 0x83, 0xE9, 0x01}}, - {x64.R10, 1, []byte{0x49, 0x83, 0xEA, 0x01}}, - {x64.R11, 1, []byte{0x49, 0x83, 0xEB, 0x01}}, - {x64.R12, 1, []byte{0x49, 0x83, 0xEC, 0x01}}, - {x64.R13, 1, []byte{0x49, 0x83, 0xED, 0x01}}, - {x64.R14, 1, []byte{0x49, 0x83, 0xEE, 0x01}}, - {x64.R15, 1, []byte{0x49, 0x83, 0xEF, 0x01}}, - - {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("sub %s, %x", pattern.Register, pattern.Number) - code := x64.SubRegisterNumber(nil, pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestSubRegisterRegister(t *testing.T) { - usagePatterns := []struct { - Left cpu.Register - Right cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, []byte{0x4C, 0x29, 0xF8}}, - {x64.RCX, x64.R14, []byte{0x4C, 0x29, 0xF1}}, - {x64.RDX, x64.R13, []byte{0x4C, 0x29, 0xEA}}, - {x64.RBX, x64.R12, []byte{0x4C, 0x29, 0xE3}}, - {x64.RSP, x64.R11, []byte{0x4C, 0x29, 0xDC}}, - {x64.RBP, x64.R10, []byte{0x4C, 0x29, 0xD5}}, - {x64.RSI, x64.R9, []byte{0x4C, 0x29, 0xCE}}, - {x64.RDI, x64.R8, []byte{0x4C, 0x29, 0xC7}}, - {x64.R8, x64.RDI, []byte{0x49, 0x29, 0xF8}}, - {x64.R9, x64.RSI, []byte{0x49, 0x29, 0xF1}}, - {x64.R10, x64.RBP, []byte{0x49, 0x29, 0xEA}}, - {x64.R11, x64.RSP, []byte{0x49, 0x29, 0xE3}}, - {x64.R12, x64.RBX, []byte{0x49, 0x29, 0xDC}}, - {x64.R13, x64.RDX, []byte{0x49, 0x29, 0xD5}}, - {x64.R14, x64.RCX, []byte{0x49, 0x29, 0xCE}}, - {x64.R15, x64.RAX, []byte{0x49, 0x29, 0xC7}}, - } - - for _, pattern := range usagePatterns { - t.Logf("sub %s, %s", pattern.Left, pattern.Right) - code := x64.SubRegisterRegister(nil, pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/Xor_test.go b/src/x64/Xor_test.go deleted file mode 100644 index 1a2b629..0000000 --- a/src/x64/Xor_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestXorRegisterNumber(t *testing.T) { - usagePatterns := []struct { - Register cpu.Register - Number int - Code []byte - }{ - {x64.RAX, 1, []byte{0x48, 0x83, 0xF0, 0x01}}, - {x64.RCX, 1, []byte{0x48, 0x83, 0xF1, 0x01}}, - {x64.RDX, 1, []byte{0x48, 0x83, 0xF2, 0x01}}, - {x64.RBX, 1, []byte{0x48, 0x83, 0xF3, 0x01}}, - {x64.RSP, 1, []byte{0x48, 0x83, 0xF4, 0x01}}, - {x64.RBP, 1, []byte{0x48, 0x83, 0xF5, 0x01}}, - {x64.RSI, 1, []byte{0x48, 0x83, 0xF6, 0x01}}, - {x64.RDI, 1, []byte{0x48, 0x83, 0xF7, 0x01}}, - {x64.R8, 1, []byte{0x49, 0x83, 0xF0, 0x01}}, - {x64.R9, 1, []byte{0x49, 0x83, 0xF1, 0x01}}, - {x64.R10, 1, []byte{0x49, 0x83, 0xF2, 0x01}}, - {x64.R11, 1, []byte{0x49, 0x83, 0xF3, 0x01}}, - {x64.R12, 1, []byte{0x49, 0x83, 0xF4, 0x01}}, - {x64.R13, 1, []byte{0x49, 0x83, 0xF5, 0x01}}, - {x64.R14, 1, []byte{0x49, 0x83, 0xF6, 0x01}}, - {x64.R15, 1, []byte{0x49, 0x83, 0xF7, 0x01}}, - - {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF7, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF7, 0xFF, 0xFF, 0xFF, 0x7F}}, - } - - for _, pattern := range usagePatterns { - t.Logf("xor %s, %x", pattern.Register, pattern.Number) - code := x64.XorRegisterNumber(nil, pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) - } -} - -func TestXorRegisterRegister(t *testing.T) { - usagePatterns := []struct { - Left cpu.Register - Right cpu.Register - Code []byte - }{ - {x64.RAX, x64.R15, []byte{0x4C, 0x31, 0xF8}}, - {x64.RCX, x64.R14, []byte{0x4C, 0x31, 0xF1}}, - {x64.RDX, x64.R13, []byte{0x4C, 0x31, 0xEA}}, - {x64.RBX, x64.R12, []byte{0x4C, 0x31, 0xE3}}, - {x64.RSP, x64.R11, []byte{0x4C, 0x31, 0xDC}}, - {x64.RBP, x64.R10, []byte{0x4C, 0x31, 0xD5}}, - {x64.RSI, x64.R9, []byte{0x4C, 0x31, 0xCE}}, - {x64.RDI, x64.R8, []byte{0x4C, 0x31, 0xC7}}, - {x64.R8, x64.RDI, []byte{0x49, 0x31, 0xF8}}, - {x64.R9, x64.RSI, []byte{0x49, 0x31, 0xF1}}, - {x64.R10, x64.RBP, []byte{0x49, 0x31, 0xEA}}, - {x64.R11, x64.RSP, []byte{0x49, 0x31, 0xE3}}, - {x64.R12, x64.RBX, []byte{0x49, 0x31, 0xDC}}, - {x64.R13, x64.RDX, []byte{0x49, 0x31, 0xD5}}, - {x64.R14, x64.RCX, []byte{0x49, 0x31, 0xCE}}, - {x64.R15, x64.RAX, []byte{0x49, 0x31, 0xC7}}, - } - - for _, pattern := range usagePatterns { - t.Logf("xor %s, %s", pattern.Left, pattern.Right) - code := x64.XorRegisterRegister(nil, pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) - } -} diff --git a/src/x64/x64_test.go b/src/x64/x64_test.go deleted file mode 100644 index 8216f1a..0000000 --- a/src/x64/x64_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package x64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/x64" - "git.akyoto.dev/go/assert" -) - -func TestX64(t *testing.T) { - assert.DeepEqual(t, x64.AlignStack(nil), []byte{0x48, 0x83, 0xE4, 0xF0}) - assert.DeepEqual(t, x64.Call(nil, 1), []byte{0xE8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.CallAtAddress(nil, 1), []byte{0xFF, 0x15, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.ExtendRAXToRDX(nil), []byte{0x48, 0x99}) - assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 0, 1), []byte{0xB8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 1, 1), []byte{0xB9, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x64.Return(nil), []byte{0xC3}) - assert.DeepEqual(t, x64.Syscall(nil), []byte{0x0F, 0x05}) -} diff --git a/src/x64/Add.go b/src/x86/Add.go similarity index 97% rename from src/x64/Add.go rename to src/x86/Add.go index fb70735..9569a56 100644 --- a/src/x64/Add.go +++ b/src/x86/Add.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Add_test.go b/src/x86/Add_test.go new file mode 100644 index 0000000..4aa285e --- /dev/null +++ b/src/x86/Add_test.go @@ -0,0 +1,88 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestAddRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0x83, 0xC0, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0x83, 0xC1, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0x83, 0xC2, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0x83, 0xC3, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0x83, 0xC4, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0x83, 0xC5, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0x83, 0xC6, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0x83, 0xC7, 0x01}}, + {x86.R8, 1, []byte{0x49, 0x83, 0xC0, 0x01}}, + {x86.R9, 1, []byte{0x49, 0x83, 0xC1, 0x01}}, + {x86.R10, 1, []byte{0x49, 0x83, 0xC2, 0x01}}, + {x86.R11, 1, []byte{0x49, 0x83, 0xC3, 0x01}}, + {x86.R12, 1, []byte{0x49, 0x83, 0xC4, 0x01}}, + {x86.R13, 1, []byte{0x49, 0x83, 0xC5, 0x01}}, + {x86.R14, 1, []byte{0x49, 0x83, 0xC6, 0x01}}, + {x86.R15, 1, []byte{0x49, 0x83, 0xC7, 0x01}}, + + {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("add %s, %x", pattern.Register, pattern.Number) + code := x86.AddRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestAddRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, []byte{0x4C, 0x01, 0xF8}}, + {x86.RCX, x86.R14, []byte{0x4C, 0x01, 0xF1}}, + {x86.RDX, x86.R13, []byte{0x4C, 0x01, 0xEA}}, + {x86.RBX, x86.R12, []byte{0x4C, 0x01, 0xE3}}, + {x86.RSP, x86.R11, []byte{0x4C, 0x01, 0xDC}}, + {x86.RBP, x86.R10, []byte{0x4C, 0x01, 0xD5}}, + {x86.RSI, x86.R9, []byte{0x4C, 0x01, 0xCE}}, + {x86.RDI, x86.R8, []byte{0x4C, 0x01, 0xC7}}, + {x86.R8, x86.RDI, []byte{0x49, 0x01, 0xF8}}, + {x86.R9, x86.RSI, []byte{0x49, 0x01, 0xF1}}, + {x86.R10, x86.RBP, []byte{0x49, 0x01, 0xEA}}, + {x86.R11, x86.RSP, []byte{0x49, 0x01, 0xE3}}, + {x86.R12, x86.RBX, []byte{0x49, 0x01, 0xDC}}, + {x86.R13, x86.RDX, []byte{0x49, 0x01, 0xD5}}, + {x86.R14, x86.RCX, []byte{0x49, 0x01, 0xCE}}, + {x86.R15, x86.RAX, []byte{0x49, 0x01, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("add %s, %s", pattern.Left, pattern.Right) + code := x86.AddRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/AlignStack.go b/src/x86/AlignStack.go similarity index 91% rename from src/x64/AlignStack.go rename to src/x86/AlignStack.go index d3199e1..b2ce237 100644 --- a/src/x64/AlignStack.go +++ b/src/x86/AlignStack.go @@ -1,4 +1,4 @@ -package x64 +package x86 // AlignStack aligns RSP on a 16-byte boundary. func AlignStack(code []byte) []byte { diff --git a/src/x64/And.go b/src/x86/And.go similarity index 97% rename from src/x64/And.go rename to src/x86/And.go index 8556507..39da3a8 100644 --- a/src/x64/And.go +++ b/src/x86/And.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/And_test.go b/src/x86/And_test.go new file mode 100644 index 0000000..a4dbd58 --- /dev/null +++ b/src/x86/And_test.go @@ -0,0 +1,88 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestAndRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0x83, 0xE0, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0x83, 0xE1, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0x83, 0xE2, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0x83, 0xE3, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0x83, 0xE4, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0x83, 0xE5, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0x83, 0xE6, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0x83, 0xE7, 0x01}}, + {x86.R8, 1, []byte{0x49, 0x83, 0xE0, 0x01}}, + {x86.R9, 1, []byte{0x49, 0x83, 0xE1, 0x01}}, + {x86.R10, 1, []byte{0x49, 0x83, 0xE2, 0x01}}, + {x86.R11, 1, []byte{0x49, 0x83, 0xE3, 0x01}}, + {x86.R12, 1, []byte{0x49, 0x83, 0xE4, 0x01}}, + {x86.R13, 1, []byte{0x49, 0x83, 0xE5, 0x01}}, + {x86.R14, 1, []byte{0x49, 0x83, 0xE6, 0x01}}, + {x86.R15, 1, []byte{0x49, 0x83, 0xE7, 0x01}}, + + {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE7, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("and %s, %x", pattern.Register, pattern.Number) + code := x86.AndRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestAndRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, []byte{0x4C, 0x21, 0xF8}}, + {x86.RCX, x86.R14, []byte{0x4C, 0x21, 0xF1}}, + {x86.RDX, x86.R13, []byte{0x4C, 0x21, 0xEA}}, + {x86.RBX, x86.R12, []byte{0x4C, 0x21, 0xE3}}, + {x86.RSP, x86.R11, []byte{0x4C, 0x21, 0xDC}}, + {x86.RBP, x86.R10, []byte{0x4C, 0x21, 0xD5}}, + {x86.RSI, x86.R9, []byte{0x4C, 0x21, 0xCE}}, + {x86.RDI, x86.R8, []byte{0x4C, 0x21, 0xC7}}, + {x86.R8, x86.RDI, []byte{0x49, 0x21, 0xF8}}, + {x86.R9, x86.RSI, []byte{0x49, 0x21, 0xF1}}, + {x86.R10, x86.RBP, []byte{0x49, 0x21, 0xEA}}, + {x86.R11, x86.RSP, []byte{0x49, 0x21, 0xE3}}, + {x86.R12, x86.RBX, []byte{0x49, 0x21, 0xDC}}, + {x86.R13, x86.RDX, []byte{0x49, 0x21, 0xD5}}, + {x86.R14, x86.RCX, []byte{0x49, 0x21, 0xCE}}, + {x86.R15, x86.RAX, []byte{0x49, 0x21, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("and %s, %s", pattern.Left, pattern.Right) + code := x86.AndRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Call.go b/src/x86/Call.go similarity index 98% rename from src/x64/Call.go rename to src/x86/Call.go index 6c37d7b..812b484 100644 --- a/src/x64/Call.go +++ b/src/x86/Call.go @@ -1,4 +1,4 @@ -package x64 +package x86 // Call places the return address on the top of the stack and continues // program flow at the new address. diff --git a/src/x64/Compare.go b/src/x86/Compare.go similarity index 97% rename from src/x64/Compare.go rename to src/x86/Compare.go index 3c48dcf..6b67aaa 100644 --- a/src/x64/Compare.go +++ b/src/x86/Compare.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Compare_test.go b/src/x86/Compare_test.go new file mode 100644 index 0000000..dbf6795 --- /dev/null +++ b/src/x86/Compare_test.go @@ -0,0 +1,88 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestCompareRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0x83, 0xF8, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0x83, 0xF9, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0x83, 0xFA, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0x83, 0xFB, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0x83, 0xFC, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0x83, 0xFD, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0x83, 0xFE, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0x83, 0xFF, 0x01}}, + {x86.R8, 1, []byte{0x49, 0x83, 0xF8, 0x01}}, + {x86.R9, 1, []byte{0x49, 0x83, 0xF9, 0x01}}, + {x86.R10, 1, []byte{0x49, 0x83, 0xFA, 0x01}}, + {x86.R11, 1, []byte{0x49, 0x83, 0xFB, 0x01}}, + {x86.R12, 1, []byte{0x49, 0x83, 0xFC, 0x01}}, + {x86.R13, 1, []byte{0x49, 0x83, 0xFD, 0x01}}, + {x86.R14, 1, []byte{0x49, 0x83, 0xFE, 0x01}}, + {x86.R15, 1, []byte{0x49, 0x83, 0xFF, 0x01}}, + + {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("cmp %s, %x", pattern.Register, pattern.Number) + code := x86.CompareRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestCompareRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, []byte{0x4C, 0x39, 0xF8}}, + {x86.RCX, x86.R14, []byte{0x4C, 0x39, 0xF1}}, + {x86.RDX, x86.R13, []byte{0x4C, 0x39, 0xEA}}, + {x86.RBX, x86.R12, []byte{0x4C, 0x39, 0xE3}}, + {x86.RSP, x86.R11, []byte{0x4C, 0x39, 0xDC}}, + {x86.RBP, x86.R10, []byte{0x4C, 0x39, 0xD5}}, + {x86.RSI, x86.R9, []byte{0x4C, 0x39, 0xCE}}, + {x86.RDI, x86.R8, []byte{0x4C, 0x39, 0xC7}}, + {x86.R8, x86.RDI, []byte{0x49, 0x39, 0xF8}}, + {x86.R9, x86.RSI, []byte{0x49, 0x39, 0xF1}}, + {x86.R10, x86.RBP, []byte{0x49, 0x39, 0xEA}}, + {x86.R11, x86.RSP, []byte{0x49, 0x39, 0xE3}}, + {x86.R12, x86.RBX, []byte{0x49, 0x39, 0xDC}}, + {x86.R13, x86.RDX, []byte{0x49, 0x39, 0xD5}}, + {x86.R14, x86.RCX, []byte{0x49, 0x39, 0xCE}}, + {x86.R15, x86.RAX, []byte{0x49, 0x39, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("cmp %s, %s", pattern.Left, pattern.Right) + code := x86.CompareRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Div.go b/src/x86/Div.go similarity index 96% rename from src/x64/Div.go rename to src/x86/Div.go index 26439d1..b6c5ebf 100644 --- a/src/x64/Div.go +++ b/src/x86/Div.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Div_test.go b/src/x86/Div_test.go new file mode 100644 index 0000000..2af2bda --- /dev/null +++ b/src/x86/Div_test.go @@ -0,0 +1,39 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestDivRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x86.RAX, []byte{0x48, 0xF7, 0xF8}}, + {x86.RCX, []byte{0x48, 0xF7, 0xF9}}, + {x86.RDX, []byte{0x48, 0xF7, 0xFA}}, + {x86.RBX, []byte{0x48, 0xF7, 0xFB}}, + {x86.RSP, []byte{0x48, 0xF7, 0xFC}}, + {x86.RBP, []byte{0x48, 0xF7, 0xFD}}, + {x86.RSI, []byte{0x48, 0xF7, 0xFE}}, + {x86.RDI, []byte{0x48, 0xF7, 0xFF}}, + {x86.R8, []byte{0x49, 0xF7, 0xF8}}, + {x86.R9, []byte{0x49, 0xF7, 0xF9}}, + {x86.R10, []byte{0x49, 0xF7, 0xFA}}, + {x86.R11, []byte{0x49, 0xF7, 0xFB}}, + {x86.R12, []byte{0x49, 0xF7, 0xFC}}, + {x86.R13, []byte{0x49, 0xF7, 0xFD}}, + {x86.R14, []byte{0x49, 0xF7, 0xFE}}, + {x86.R15, []byte{0x49, 0xF7, 0xFF}}, + } + + for _, pattern := range usagePatterns { + t.Logf("idiv %s", pattern.Register) + code := x86.DivRegister(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/ExtendRAXToRDX.go b/src/x86/ExtendRAXToRDX.go similarity index 93% rename from src/x64/ExtendRAXToRDX.go rename to src/x86/ExtendRAXToRDX.go index 2f03963..7d97a86 100644 --- a/src/x64/ExtendRAXToRDX.go +++ b/src/x86/ExtendRAXToRDX.go @@ -1,4 +1,4 @@ -package x64 +package x86 // ExtendRAXToRDX doubles the size of RAX by sign-extending it to RDX. // This is also known as CQO. diff --git a/src/x64/Jump.go b/src/x86/Jump.go similarity index 98% rename from src/x64/Jump.go rename to src/x86/Jump.go index 1174f58..27b1ef4 100644 --- a/src/x64/Jump.go +++ b/src/x86/Jump.go @@ -1,4 +1,4 @@ -package x64 +package x86 // Jump continues program flow at the new address. // The address is relative to the next instruction. diff --git a/src/x64/Jump_test.go b/src/x86/Jump_test.go similarity index 56% rename from src/x64/Jump_test.go rename to src/x86/Jump_test.go index 4dde162..b2df363 100644 --- a/src/x64/Jump_test.go +++ b/src/x86/Jump_test.go @@ -1,9 +1,9 @@ -package x64_test +package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" "git.akyoto.dev/go/assert" ) @@ -25,16 +25,16 @@ func TestJump(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("jmp %x", pattern.Offset) - code := x64.Jump8(nil, pattern.Offset) + code := x86.Jump8(nil, pattern.Offset) assert.DeepEqual(t, code, pattern.Code) } } func TestConditionalJump(t *testing.T) { - assert.DeepEqual(t, x64.Jump8IfEqual(nil, 1), []byte{0x74, 0x01}) - assert.DeepEqual(t, x64.Jump8IfNotEqual(nil, 1), []byte{0x75, 0x01}) - assert.DeepEqual(t, x64.Jump8IfLess(nil, 1), []byte{0x7C, 0x01}) - assert.DeepEqual(t, x64.Jump8IfGreaterOrEqual(nil, 1), []byte{0x7D, 0x01}) - assert.DeepEqual(t, x64.Jump8IfLessOrEqual(nil, 1), []byte{0x7E, 0x01}) - assert.DeepEqual(t, x64.Jump8IfGreater(nil, 1), []byte{0x7F, 0x01}) + assert.DeepEqual(t, x86.Jump8IfEqual(nil, 1), []byte{0x74, 0x01}) + assert.DeepEqual(t, x86.Jump8IfNotEqual(nil, 1), []byte{0x75, 0x01}) + assert.DeepEqual(t, x86.Jump8IfLess(nil, 1), []byte{0x7C, 0x01}) + assert.DeepEqual(t, x86.Jump8IfGreaterOrEqual(nil, 1), []byte{0x7D, 0x01}) + assert.DeepEqual(t, x86.Jump8IfLessOrEqual(nil, 1), []byte{0x7E, 0x01}) + assert.DeepEqual(t, x86.Jump8IfGreater(nil, 1), []byte{0x7F, 0x01}) } diff --git a/src/x64/Load.go b/src/x86/Load.go similarity index 95% rename from src/x64/Load.go rename to src/x86/Load.go index eb5ac51..ae32daa 100644 --- a/src/x64/Load.go +++ b/src/x86/Load.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Load_test.go b/src/x86/Load_test.go new file mode 100644 index 0000000..2633e09 --- /dev/null +++ b/src/x86/Load_test.go @@ -0,0 +1,157 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestLoadRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Offset byte + Length byte + Code []byte + }{ + // No offset + {x86.RAX, x86.R15, 0, 8, []byte{0x49, 0x8B, 0x07}}, + {x86.RAX, x86.R15, 0, 4, []byte{0x41, 0x8B, 0x07}}, + {x86.RAX, x86.R15, 0, 2, []byte{0x66, 0x41, 0x8B, 0x07}}, + {x86.RAX, x86.R15, 0, 1, []byte{0x41, 0x8A, 0x07}}, + {x86.RCX, x86.R14, 0, 8, []byte{0x49, 0x8B, 0x0E}}, + {x86.RCX, x86.R14, 0, 4, []byte{0x41, 0x8B, 0x0E}}, + {x86.RCX, x86.R14, 0, 2, []byte{0x66, 0x41, 0x8B, 0x0E}}, + {x86.RCX, x86.R14, 0, 1, []byte{0x41, 0x8A, 0x0E}}, + {x86.RDX, x86.R13, 0, 8, []byte{0x49, 0x8B, 0x55, 0x00}}, + {x86.RDX, x86.R13, 0, 4, []byte{0x41, 0x8B, 0x55, 0x00}}, + {x86.RDX, x86.R13, 0, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x00}}, + {x86.RDX, x86.R13, 0, 1, []byte{0x41, 0x8A, 0x55, 0x00}}, + {x86.RBX, x86.R12, 0, 8, []byte{0x49, 0x8B, 0x1C, 0x24}}, + {x86.RBX, x86.R12, 0, 4, []byte{0x41, 0x8B, 0x1C, 0x24}}, + {x86.RBX, x86.R12, 0, 2, []byte{0x66, 0x41, 0x8B, 0x1C, 0x24}}, + {x86.RBX, x86.R12, 0, 1, []byte{0x41, 0x8A, 0x1C, 0x24}}, + {x86.RSP, x86.R11, 0, 8, []byte{0x49, 0x8B, 0x23}}, + {x86.RSP, x86.R11, 0, 4, []byte{0x41, 0x8B, 0x23}}, + {x86.RSP, x86.R11, 0, 2, []byte{0x66, 0x41, 0x8B, 0x23}}, + {x86.RSP, x86.R11, 0, 1, []byte{0x41, 0x8A, 0x23}}, + {x86.RBP, x86.R10, 0, 8, []byte{0x49, 0x8B, 0x2A}}, + {x86.RBP, x86.R10, 0, 4, []byte{0x41, 0x8B, 0x2A}}, + {x86.RBP, x86.R10, 0, 2, []byte{0x66, 0x41, 0x8B, 0x2A}}, + {x86.RBP, x86.R10, 0, 1, []byte{0x41, 0x8A, 0x2A}}, + {x86.RSI, x86.R9, 0, 8, []byte{0x49, 0x8B, 0x31}}, + {x86.RSI, x86.R9, 0, 4, []byte{0x41, 0x8B, 0x31}}, + {x86.RSI, x86.R9, 0, 2, []byte{0x66, 0x41, 0x8B, 0x31}}, + {x86.RSI, x86.R9, 0, 1, []byte{0x41, 0x8A, 0x31}}, + {x86.RDI, x86.R8, 0, 8, []byte{0x49, 0x8B, 0x38}}, + {x86.RDI, x86.R8, 0, 4, []byte{0x41, 0x8B, 0x38}}, + {x86.RDI, x86.R8, 0, 2, []byte{0x66, 0x41, 0x8B, 0x38}}, + {x86.RDI, x86.R8, 0, 1, []byte{0x41, 0x8A, 0x38}}, + {x86.R8, x86.RDI, 0, 8, []byte{0x4C, 0x8B, 0x07}}, + {x86.R8, x86.RDI, 0, 4, []byte{0x44, 0x8B, 0x07}}, + {x86.R8, x86.RDI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x07}}, + {x86.R8, x86.RDI, 0, 1, []byte{0x44, 0x8A, 0x07}}, + {x86.R9, x86.RSI, 0, 8, []byte{0x4C, 0x8B, 0x0E}}, + {x86.R9, x86.RSI, 0, 4, []byte{0x44, 0x8B, 0x0E}}, + {x86.R9, x86.RSI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x0E}}, + {x86.R9, x86.RSI, 0, 1, []byte{0x44, 0x8A, 0x0E}}, + {x86.R10, x86.RBP, 0, 8, []byte{0x4C, 0x8B, 0x55, 0x00}}, + {x86.R10, x86.RBP, 0, 4, []byte{0x44, 0x8B, 0x55, 0x00}}, + {x86.R10, x86.RBP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x00}}, + {x86.R10, x86.RBP, 0, 1, []byte{0x44, 0x8A, 0x55, 0x00}}, + {x86.R11, x86.RSP, 0, 8, []byte{0x4C, 0x8B, 0x1C, 0x24}}, + {x86.R11, x86.RSP, 0, 4, []byte{0x44, 0x8B, 0x1C, 0x24}}, + {x86.R11, x86.RSP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x1C, 0x24}}, + {x86.R11, x86.RSP, 0, 1, []byte{0x44, 0x8A, 0x1C, 0x24}}, + {x86.R12, x86.RBX, 0, 8, []byte{0x4C, 0x8B, 0x23}}, + {x86.R12, x86.RBX, 0, 4, []byte{0x44, 0x8B, 0x23}}, + {x86.R12, x86.RBX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x23}}, + {x86.R12, x86.RBX, 0, 1, []byte{0x44, 0x8A, 0x23}}, + {x86.R13, x86.RDX, 0, 8, []byte{0x4C, 0x8B, 0x2A}}, + {x86.R13, x86.RDX, 0, 4, []byte{0x44, 0x8B, 0x2A}}, + {x86.R13, x86.RDX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x2A}}, + {x86.R13, x86.RDX, 0, 1, []byte{0x44, 0x8A, 0x2A}}, + {x86.R14, x86.RCX, 0, 8, []byte{0x4C, 0x8B, 0x31}}, + {x86.R14, x86.RCX, 0, 4, []byte{0x44, 0x8B, 0x31}}, + {x86.R14, x86.RCX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x31}}, + {x86.R14, x86.RCX, 0, 1, []byte{0x44, 0x8A, 0x31}}, + {x86.R15, x86.RAX, 0, 8, []byte{0x4C, 0x8B, 0x38}}, + {x86.R15, x86.RAX, 0, 4, []byte{0x44, 0x8B, 0x38}}, + {x86.R15, x86.RAX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x38}}, + {x86.R15, x86.RAX, 0, 1, []byte{0x44, 0x8A, 0x38}}, + + // Offset of 1 + {x86.RAX, x86.R15, 1, 8, []byte{0x49, 0x8B, 0x47, 0x01}}, + {x86.RAX, x86.R15, 1, 4, []byte{0x41, 0x8B, 0x47, 0x01}}, + {x86.RAX, x86.R15, 1, 2, []byte{0x66, 0x41, 0x8B, 0x47, 0x01}}, + {x86.RAX, x86.R15, 1, 1, []byte{0x41, 0x8A, 0x47, 0x01}}, + {x86.RCX, x86.R14, 1, 8, []byte{0x49, 0x8B, 0x4E, 0x01}}, + {x86.RCX, x86.R14, 1, 4, []byte{0x41, 0x8B, 0x4E, 0x01}}, + {x86.RCX, x86.R14, 1, 2, []byte{0x66, 0x41, 0x8B, 0x4E, 0x01}}, + {x86.RCX, x86.R14, 1, 1, []byte{0x41, 0x8A, 0x4E, 0x01}}, + {x86.RDX, x86.R13, 1, 8, []byte{0x49, 0x8B, 0x55, 0x01}}, + {x86.RDX, x86.R13, 1, 4, []byte{0x41, 0x8B, 0x55, 0x01}}, + {x86.RDX, x86.R13, 1, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x01}}, + {x86.RDX, x86.R13, 1, 1, []byte{0x41, 0x8A, 0x55, 0x01}}, + {x86.RBX, x86.R12, 1, 8, []byte{0x49, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.RBX, x86.R12, 1, 4, []byte{0x41, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.RBX, x86.R12, 1, 2, []byte{0x66, 0x41, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.RBX, x86.R12, 1, 1, []byte{0x41, 0x8A, 0x5C, 0x24, 0x01}}, + {x86.RSP, x86.R11, 1, 8, []byte{0x49, 0x8B, 0x63, 0x01}}, + {x86.RSP, x86.R11, 1, 4, []byte{0x41, 0x8B, 0x63, 0x01}}, + {x86.RSP, x86.R11, 1, 2, []byte{0x66, 0x41, 0x8B, 0x63, 0x01}}, + {x86.RSP, x86.R11, 1, 1, []byte{0x41, 0x8A, 0x63, 0x01}}, + {x86.RBP, x86.R10, 1, 8, []byte{0x49, 0x8B, 0x6A, 0x01}}, + {x86.RBP, x86.R10, 1, 4, []byte{0x41, 0x8B, 0x6A, 0x01}}, + {x86.RBP, x86.R10, 1, 2, []byte{0x66, 0x41, 0x8B, 0x6A, 0x01}}, + {x86.RBP, x86.R10, 1, 1, []byte{0x41, 0x8A, 0x6A, 0x01}}, + {x86.RSI, x86.R9, 1, 8, []byte{0x49, 0x8B, 0x71, 0x01}}, + {x86.RSI, x86.R9, 1, 4, []byte{0x41, 0x8B, 0x71, 0x01}}, + {x86.RSI, x86.R9, 1, 2, []byte{0x66, 0x41, 0x8B, 0x71, 0x01}}, + {x86.RSI, x86.R9, 1, 1, []byte{0x41, 0x8A, 0x71, 0x01}}, + {x86.RDI, x86.R8, 1, 8, []byte{0x49, 0x8B, 0x78, 0x01}}, + {x86.RDI, x86.R8, 1, 4, []byte{0x41, 0x8B, 0x78, 0x01}}, + {x86.RDI, x86.R8, 1, 2, []byte{0x66, 0x41, 0x8B, 0x78, 0x01}}, + {x86.RDI, x86.R8, 1, 1, []byte{0x41, 0x8A, 0x78, 0x01}}, + {x86.R8, x86.RDI, 1, 8, []byte{0x4C, 0x8B, 0x47, 0x01}}, + {x86.R8, x86.RDI, 1, 4, []byte{0x44, 0x8B, 0x47, 0x01}}, + {x86.R8, x86.RDI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x47, 0x01}}, + {x86.R8, x86.RDI, 1, 1, []byte{0x44, 0x8A, 0x47, 0x01}}, + {x86.R9, x86.RSI, 1, 8, []byte{0x4C, 0x8B, 0x4E, 0x01}}, + {x86.R9, x86.RSI, 1, 4, []byte{0x44, 0x8B, 0x4E, 0x01}}, + {x86.R9, x86.RSI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x4E, 0x01}}, + {x86.R9, x86.RSI, 1, 1, []byte{0x44, 0x8A, 0x4E, 0x01}}, + {x86.R10, x86.RBP, 1, 8, []byte{0x4C, 0x8B, 0x55, 0x01}}, + {x86.R10, x86.RBP, 1, 4, []byte{0x44, 0x8B, 0x55, 0x01}}, + {x86.R10, x86.RBP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x01}}, + {x86.R10, x86.RBP, 1, 1, []byte{0x44, 0x8A, 0x55, 0x01}}, + {x86.R11, x86.RSP, 1, 8, []byte{0x4C, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R11, x86.RSP, 1, 4, []byte{0x44, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R11, x86.RSP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R11, x86.RSP, 1, 1, []byte{0x44, 0x8A, 0x5C, 0x24, 0x01}}, + {x86.R12, x86.RBX, 1, 8, []byte{0x4C, 0x8B, 0x63, 0x01}}, + {x86.R12, x86.RBX, 1, 4, []byte{0x44, 0x8B, 0x63, 0x01}}, + {x86.R12, x86.RBX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x63, 0x01}}, + {x86.R12, x86.RBX, 1, 1, []byte{0x44, 0x8A, 0x63, 0x01}}, + {x86.R13, x86.RDX, 1, 8, []byte{0x4C, 0x8B, 0x6A, 0x01}}, + {x86.R13, x86.RDX, 1, 4, []byte{0x44, 0x8B, 0x6A, 0x01}}, + {x86.R13, x86.RDX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x6A, 0x01}}, + {x86.R13, x86.RDX, 1, 1, []byte{0x44, 0x8A, 0x6A, 0x01}}, + {x86.R14, x86.RCX, 1, 8, []byte{0x4C, 0x8B, 0x71, 0x01}}, + {x86.R14, x86.RCX, 1, 4, []byte{0x44, 0x8B, 0x71, 0x01}}, + {x86.R14, x86.RCX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x71, 0x01}}, + {x86.R14, x86.RCX, 1, 1, []byte{0x44, 0x8A, 0x71, 0x01}}, + {x86.R15, x86.RAX, 1, 8, []byte{0x4C, 0x8B, 0x78, 0x01}}, + {x86.R15, x86.RAX, 1, 4, []byte{0x44, 0x8B, 0x78, 0x01}}, + {x86.R15, x86.RAX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x78, 0x01}}, + {x86.R15, x86.RAX, 1, 1, []byte{0x44, 0x8A, 0x78, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("load %dB %s, [%s+%d]", pattern.Length, pattern.Destination, pattern.Source, pattern.Offset) + code := x86.LoadRegister(nil, pattern.Destination, pattern.Offset, pattern.Length, pattern.Source) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/ModRM.go b/src/x86/ModRM.go similarity index 97% rename from src/x64/ModRM.go rename to src/x86/ModRM.go index bb6852e..1ab4393 100644 --- a/src/x64/ModRM.go +++ b/src/x86/ModRM.go @@ -1,4 +1,4 @@ -package x64 +package x86 // AddressMode encodes the addressing mode. type AddressMode = byte diff --git a/src/x64/ModRM_test.go b/src/x86/ModRM_test.go similarity index 90% rename from src/x64/ModRM_test.go rename to src/x86/ModRM_test.go index 09f72eb..fd1151a 100644 --- a/src/x64/ModRM_test.go +++ b/src/x86/ModRM_test.go @@ -1,9 +1,9 @@ -package x64_test +package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" "git.akyoto.dev/go/assert" ) @@ -28,7 +28,7 @@ func TestModRM(t *testing.T) { } for _, test := range testData { - modRM := x64.ModRM(test.mod, test.reg, test.rm) + modRM := x86.ModRM(test.mod, test.reg, test.rm) assert.Equal(t, modRM, test.expected) } } diff --git a/src/x64/Move.go b/src/x86/Move.go similarity index 99% rename from src/x64/Move.go rename to src/x86/Move.go index 48edc79..2c38ca5 100644 --- a/src/x64/Move.go +++ b/src/x86/Move.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "encoding/binary" diff --git a/src/x86/Move_test.go b/src/x86/Move_test.go new file mode 100644 index 0000000..5c074ae --- /dev/null +++ b/src/x86/Move_test.go @@ -0,0 +1,108 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestMoveRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + // 32 bits + {x86.RAX, 0x7FFFFFFF, []byte{0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFF, []byte{0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFF, []byte{0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFF, []byte{0xBB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFF, []byte{0xBC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFF, []byte{0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFF, []byte{0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFF, []byte{0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFF, []byte{0x41, 0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFF, []byte{0x41, 0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFF, []byte{0x41, 0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFF, []byte{0x41, 0xBB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFF, []byte{0x41, 0xBC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFF, []byte{0x41, 0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFF, []byte{0x41, 0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFF, []byte{0x41, 0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, + + // 64 bits + {x86.RAX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + + // Negative numbers + {x86.RAX, -1, []byte{0x48, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.RCX, -1, []byte{0x48, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.RDX, -1, []byte{0x48, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.RBX, -1, []byte{0x48, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.RSP, -1, []byte{0x48, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.RBP, -1, []byte{0x48, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.RSI, -1, []byte{0x48, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.RDI, -1, []byte{0x48, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R8, -1, []byte{0x49, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R9, -1, []byte{0x49, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R10, -1, []byte{0x49, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R11, -1, []byte{0x49, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R12, -1, []byte{0x49, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R13, -1, []byte{0x49, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R14, -1, []byte{0x49, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R15, -1, []byte{0x49, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, %x", pattern.Register, pattern.Number) + code := x86.MoveRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestMoveRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, []byte{0x4C, 0x89, 0xF8}}, + {x86.RCX, x86.R14, []byte{0x4C, 0x89, 0xF1}}, + {x86.RDX, x86.R13, []byte{0x4C, 0x89, 0xEA}}, + {x86.RBX, x86.R12, []byte{0x4C, 0x89, 0xE3}}, + {x86.RSP, x86.R11, []byte{0x4C, 0x89, 0xDC}}, + {x86.RBP, x86.R10, []byte{0x4C, 0x89, 0xD5}}, + {x86.RSI, x86.R9, []byte{0x4C, 0x89, 0xCE}}, + {x86.RDI, x86.R8, []byte{0x4C, 0x89, 0xC7}}, + {x86.R8, x86.RDI, []byte{0x49, 0x89, 0xF8}}, + {x86.R9, x86.RSI, []byte{0x49, 0x89, 0xF1}}, + {x86.R10, x86.RBP, []byte{0x49, 0x89, 0xEA}}, + {x86.R11, x86.RSP, []byte{0x49, 0x89, 0xE3}}, + {x86.R12, x86.RBX, []byte{0x49, 0x89, 0xDC}}, + {x86.R13, x86.RDX, []byte{0x49, 0x89, 0xD5}}, + {x86.R14, x86.RCX, []byte{0x49, 0x89, 0xCE}}, + {x86.R15, x86.RAX, []byte{0x49, 0x89, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, %s", pattern.Left, pattern.Right) + code := x86.MoveRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Mul.go b/src/x86/Mul.go similarity index 97% rename from src/x64/Mul.go rename to src/x86/Mul.go index 1d11da3..f2b4dbb 100644 --- a/src/x64/Mul.go +++ b/src/x86/Mul.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Mul_test.go b/src/x86/Mul_test.go new file mode 100644 index 0000000..46618fe --- /dev/null +++ b/src/x86/Mul_test.go @@ -0,0 +1,88 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestMulRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0x6B, 0xC0, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0x6B, 0xC9, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0x6B, 0xD2, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0x6B, 0xDB, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0x6B, 0xE4, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0x6B, 0xED, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0x6B, 0xF6, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0x6B, 0xFF, 0x01}}, + {x86.R8, 1, []byte{0x4D, 0x6B, 0xC0, 0x01}}, + {x86.R9, 1, []byte{0x4D, 0x6B, 0xC9, 0x01}}, + {x86.R10, 1, []byte{0x4D, 0x6B, 0xD2, 0x01}}, + {x86.R11, 1, []byte{0x4D, 0x6B, 0xDB, 0x01}}, + {x86.R12, 1, []byte{0x4D, 0x6B, 0xE4, 0x01}}, + {x86.R13, 1, []byte{0x4D, 0x6B, 0xED, 0x01}}, + {x86.R14, 1, []byte{0x4D, 0x6B, 0xF6, 0x01}}, + {x86.R15, 1, []byte{0x4D, 0x6B, 0xFF, 0x01}}, + + {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mul %s, %x", pattern.Register, pattern.Number) + code := x86.MulRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestMulRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, []byte{0x49, 0x0F, 0xAF, 0xC7}}, + {x86.RCX, x86.R14, []byte{0x49, 0x0F, 0xAF, 0xCE}}, + {x86.RDX, x86.R13, []byte{0x49, 0x0F, 0xAF, 0xD5}}, + {x86.RBX, x86.R12, []byte{0x49, 0x0F, 0xAF, 0xDC}}, + {x86.RSP, x86.R11, []byte{0x49, 0x0F, 0xAF, 0xE3}}, + {x86.RBP, x86.R10, []byte{0x49, 0x0F, 0xAF, 0xEA}}, + {x86.RSI, x86.R9, []byte{0x49, 0x0F, 0xAF, 0xF1}}, + {x86.RDI, x86.R8, []byte{0x49, 0x0F, 0xAF, 0xF8}}, + {x86.R8, x86.RDI, []byte{0x4C, 0x0F, 0xAF, 0xC7}}, + {x86.R9, x86.RSI, []byte{0x4C, 0x0F, 0xAF, 0xCE}}, + {x86.R10, x86.RBP, []byte{0x4C, 0x0F, 0xAF, 0xD5}}, + {x86.R11, x86.RSP, []byte{0x4C, 0x0F, 0xAF, 0xDC}}, + {x86.R12, x86.RBX, []byte{0x4C, 0x0F, 0xAF, 0xE3}}, + {x86.R13, x86.RDX, []byte{0x4C, 0x0F, 0xAF, 0xEA}}, + {x86.R14, x86.RCX, []byte{0x4C, 0x0F, 0xAF, 0xF1}}, + {x86.R15, x86.RAX, []byte{0x4C, 0x0F, 0xAF, 0xF8}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mul %s, %s", pattern.Left, pattern.Right) + code := x86.MulRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Negate.go b/src/x86/Negate.go similarity index 94% rename from src/x64/Negate.go rename to src/x86/Negate.go index 479c143..141d105 100644 --- a/src/x64/Negate.go +++ b/src/x86/Negate.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Negate_test.go b/src/x86/Negate_test.go new file mode 100644 index 0000000..0fcaeea --- /dev/null +++ b/src/x86/Negate_test.go @@ -0,0 +1,39 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestNegateRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x86.RAX, []byte{0x48, 0xF7, 0xD8}}, + {x86.RCX, []byte{0x48, 0xF7, 0xD9}}, + {x86.RDX, []byte{0x48, 0xF7, 0xDA}}, + {x86.RBX, []byte{0x48, 0xF7, 0xDB}}, + {x86.RSP, []byte{0x48, 0xF7, 0xDC}}, + {x86.RBP, []byte{0x48, 0xF7, 0xDD}}, + {x86.RSI, []byte{0x48, 0xF7, 0xDE}}, + {x86.RDI, []byte{0x48, 0xF7, 0xDF}}, + {x86.R8, []byte{0x49, 0xF7, 0xD8}}, + {x86.R9, []byte{0x49, 0xF7, 0xD9}}, + {x86.R10, []byte{0x49, 0xF7, 0xDA}}, + {x86.R11, []byte{0x49, 0xF7, 0xDB}}, + {x86.R12, []byte{0x49, 0xF7, 0xDC}}, + {x86.R13, []byte{0x49, 0xF7, 0xDD}}, + {x86.R14, []byte{0x49, 0xF7, 0xDE}}, + {x86.R15, []byte{0x49, 0xF7, 0xDF}}, + } + + for _, pattern := range usagePatterns { + t.Logf("neg %s", pattern.Register) + code := x86.NegateRegister(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Or.go b/src/x86/Or.go similarity index 97% rename from src/x64/Or.go rename to src/x86/Or.go index fa5b440..9ed61c4 100644 --- a/src/x64/Or.go +++ b/src/x86/Or.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Or_test.go b/src/x86/Or_test.go new file mode 100644 index 0000000..f26e4e7 --- /dev/null +++ b/src/x86/Or_test.go @@ -0,0 +1,88 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestOrRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0x83, 0xC8, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0x83, 0xC9, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0x83, 0xCA, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0x83, 0xCB, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0x83, 0xCC, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0x83, 0xCD, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0x83, 0xCE, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0x83, 0xCF, 0x01}}, + {x86.R8, 1, []byte{0x49, 0x83, 0xC8, 0x01}}, + {x86.R9, 1, []byte{0x49, 0x83, 0xC9, 0x01}}, + {x86.R10, 1, []byte{0x49, 0x83, 0xCA, 0x01}}, + {x86.R11, 1, []byte{0x49, 0x83, 0xCB, 0x01}}, + {x86.R12, 1, []byte{0x49, 0x83, 0xCC, 0x01}}, + {x86.R13, 1, []byte{0x49, 0x83, 0xCD, 0x01}}, + {x86.R14, 1, []byte{0x49, 0x83, 0xCE, 0x01}}, + {x86.R15, 1, []byte{0x49, 0x83, 0xCF, 0x01}}, + + {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("or %s, %x", pattern.Register, pattern.Number) + code := x86.OrRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestOrRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, []byte{0x4C, 0x09, 0xF8}}, + {x86.RCX, x86.R14, []byte{0x4C, 0x09, 0xF1}}, + {x86.RDX, x86.R13, []byte{0x4C, 0x09, 0xEA}}, + {x86.RBX, x86.R12, []byte{0x4C, 0x09, 0xE3}}, + {x86.RSP, x86.R11, []byte{0x4C, 0x09, 0xDC}}, + {x86.RBP, x86.R10, []byte{0x4C, 0x09, 0xD5}}, + {x86.RSI, x86.R9, []byte{0x4C, 0x09, 0xCE}}, + {x86.RDI, x86.R8, []byte{0x4C, 0x09, 0xC7}}, + {x86.R8, x86.RDI, []byte{0x49, 0x09, 0xF8}}, + {x86.R9, x86.RSI, []byte{0x49, 0x09, 0xF1}}, + {x86.R10, x86.RBP, []byte{0x49, 0x09, 0xEA}}, + {x86.R11, x86.RSP, []byte{0x49, 0x09, 0xE3}}, + {x86.R12, x86.RBX, []byte{0x49, 0x09, 0xDC}}, + {x86.R13, x86.RDX, []byte{0x49, 0x09, 0xD5}}, + {x86.R14, x86.RCX, []byte{0x49, 0x09, 0xCE}}, + {x86.R15, x86.RAX, []byte{0x49, 0x09, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("or %s, %s", pattern.Left, pattern.Right) + code := x86.OrRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Pop.go b/src/x86/Pop.go similarity index 96% rename from src/x64/Pop.go rename to src/x86/Pop.go index b97182f..5b23719 100644 --- a/src/x64/Pop.go +++ b/src/x86/Pop.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Pop_test.go b/src/x86/Pop_test.go new file mode 100644 index 0000000..268dcfc --- /dev/null +++ b/src/x86/Pop_test.go @@ -0,0 +1,39 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestPopRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x86.RAX, []byte{0x58}}, + {x86.RCX, []byte{0x59}}, + {x86.RDX, []byte{0x5A}}, + {x86.RBX, []byte{0x5B}}, + {x86.RSP, []byte{0x5C}}, + {x86.RBP, []byte{0x5D}}, + {x86.RSI, []byte{0x5E}}, + {x86.RDI, []byte{0x5F}}, + {x86.R8, []byte{0x41, 0x58}}, + {x86.R9, []byte{0x41, 0x59}}, + {x86.R10, []byte{0x41, 0x5A}}, + {x86.R11, []byte{0x41, 0x5B}}, + {x86.R12, []byte{0x41, 0x5C}}, + {x86.R13, []byte{0x41, 0x5D}}, + {x86.R14, []byte{0x41, 0x5E}}, + {x86.R15, []byte{0x41, 0x5F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("pop %s", pattern.Register) + code := x86.PopRegister(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Push.go b/src/x86/Push.go similarity index 96% rename from src/x64/Push.go rename to src/x86/Push.go index bdae334..b9c82b0 100644 --- a/src/x64/Push.go +++ b/src/x86/Push.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Push_test.go b/src/x86/Push_test.go new file mode 100644 index 0000000..29cedac --- /dev/null +++ b/src/x86/Push_test.go @@ -0,0 +1,39 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestPushRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x86.RAX, []byte{0x50}}, + {x86.RCX, []byte{0x51}}, + {x86.RDX, []byte{0x52}}, + {x86.RBX, []byte{0x53}}, + {x86.RSP, []byte{0x54}}, + {x86.RBP, []byte{0x55}}, + {x86.RSI, []byte{0x56}}, + {x86.RDI, []byte{0x57}}, + {x86.R8, []byte{0x41, 0x50}}, + {x86.R9, []byte{0x41, 0x51}}, + {x86.R10, []byte{0x41, 0x52}}, + {x86.R11, []byte{0x41, 0x53}}, + {x86.R12, []byte{0x41, 0x54}}, + {x86.R13, []byte{0x41, 0x55}}, + {x86.R14, []byte{0x41, 0x56}}, + {x86.R15, []byte{0x41, 0x57}}, + } + + for _, pattern := range usagePatterns { + t.Logf("push %s", pattern.Register) + code := x86.PushRegister(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/REX.go b/src/x86/REX.go similarity index 93% rename from src/x64/REX.go rename to src/x86/REX.go index 36d7e58..554140e 100644 --- a/src/x64/REX.go +++ b/src/x86/REX.go @@ -1,4 +1,4 @@ -package x64 +package x86 // REX is used to generate a REX prefix. // w, r, x and b can only be set to either 0 or 1. diff --git a/src/x64/REX_test.go b/src/x86/REX_test.go similarity index 87% rename from src/x64/REX_test.go rename to src/x86/REX_test.go index c754354..73f2290 100644 --- a/src/x64/REX_test.go +++ b/src/x86/REX_test.go @@ -1,9 +1,9 @@ -package x64_test +package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" "git.akyoto.dev/go/assert" ) @@ -28,7 +28,7 @@ func TestREX(t *testing.T) { } for _, test := range testData { - rex := x64.REX(test.w, test.r, test.x, test.b) + rex := x86.REX(test.w, test.r, test.x, test.b) assert.Equal(t, rex, test.expected) } } diff --git a/src/x64/Registers.go b/src/x86/Registers.go similarity index 98% rename from src/x64/Registers.go rename to src/x86/Registers.go index 57d4e8b..0ed7c5f 100644 --- a/src/x64/Registers.go +++ b/src/x86/Registers.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Registers_test.go b/src/x86/Registers_test.go new file mode 100644 index 0000000..54eb4d2 --- /dev/null +++ b/src/x86/Registers_test.go @@ -0,0 +1,12 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestRegisters(t *testing.T) { + assert.NotContains(t, x86.GeneralRegisters, x86.RSP) +} diff --git a/src/x64/Return.go b/src/x86/Return.go similarity index 94% rename from src/x64/Return.go rename to src/x86/Return.go index a64d39f..4ac2301 100644 --- a/src/x64/Return.go +++ b/src/x86/Return.go @@ -1,4 +1,4 @@ -package x64 +package x86 // Return transfers program control to a return address located on the top of the stack. // The address is usually placed on the stack by a Call instruction. diff --git a/src/x64/SIB.go b/src/x86/SIB.go similarity index 97% rename from src/x64/SIB.go rename to src/x86/SIB.go index 438024d..4b75714 100644 --- a/src/x64/SIB.go +++ b/src/x86/SIB.go @@ -1,4 +1,4 @@ -package x64 +package x86 // ScaleFactor encodes the scale factor. type ScaleFactor = byte diff --git a/src/x64/SIB_test.go b/src/x86/SIB_test.go similarity index 89% rename from src/x64/SIB_test.go rename to src/x86/SIB_test.go index 911b133..c6dcb4b 100644 --- a/src/x64/SIB_test.go +++ b/src/x86/SIB_test.go @@ -1,9 +1,9 @@ -package x64_test +package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x64" + "git.akyoto.dev/cli/q/src/x86" "git.akyoto.dev/go/assert" ) @@ -28,7 +28,7 @@ func TestSIB(t *testing.T) { } for _, test := range testData { - sib := x64.SIB(test.scale, test.index, test.base) + sib := x86.SIB(test.scale, test.index, test.base) assert.Equal(t, sib, test.expected) } } diff --git a/src/x64/Shift.go b/src/x86/Shift.go similarity index 97% rename from src/x64/Shift.go rename to src/x86/Shift.go index 6fa2251..428e7aa 100644 --- a/src/x64/Shift.go +++ b/src/x86/Shift.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Shift_test.go b/src/x86/Shift_test.go new file mode 100644 index 0000000..2e80741 --- /dev/null +++ b/src/x86/Shift_test.go @@ -0,0 +1,71 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestShiftLeftNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0xC1, 0xE0, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0xC1, 0xE1, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0xC1, 0xE2, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0xC1, 0xE3, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0xC1, 0xE4, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0xC1, 0xE5, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0xC1, 0xE6, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0xC1, 0xE7, 0x01}}, + {x86.R8, 1, []byte{0x49, 0xC1, 0xE0, 0x01}}, + {x86.R9, 1, []byte{0x49, 0xC1, 0xE1, 0x01}}, + {x86.R10, 1, []byte{0x49, 0xC1, 0xE2, 0x01}}, + {x86.R11, 1, []byte{0x49, 0xC1, 0xE3, 0x01}}, + {x86.R12, 1, []byte{0x49, 0xC1, 0xE4, 0x01}}, + {x86.R13, 1, []byte{0x49, 0xC1, 0xE5, 0x01}}, + {x86.R14, 1, []byte{0x49, 0xC1, 0xE6, 0x01}}, + {x86.R15, 1, []byte{0x49, 0xC1, 0xE7, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("shl %s, %x", pattern.Register, pattern.Number) + code := x86.ShiftLeftNumber(nil, pattern.Register, byte(pattern.Number)) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestShiftRightSignedNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0xC1, 0xF8, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0xC1, 0xF9, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0xC1, 0xFA, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0xC1, 0xFB, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0xC1, 0xFC, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0xC1, 0xFD, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0xC1, 0xFE, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0xC1, 0xFF, 0x01}}, + {x86.R8, 1, []byte{0x49, 0xC1, 0xF8, 0x01}}, + {x86.R9, 1, []byte{0x49, 0xC1, 0xF9, 0x01}}, + {x86.R10, 1, []byte{0x49, 0xC1, 0xFA, 0x01}}, + {x86.R11, 1, []byte{0x49, 0xC1, 0xFB, 0x01}}, + {x86.R12, 1, []byte{0x49, 0xC1, 0xFC, 0x01}}, + {x86.R13, 1, []byte{0x49, 0xC1, 0xFD, 0x01}}, + {x86.R14, 1, []byte{0x49, 0xC1, 0xFE, 0x01}}, + {x86.R15, 1, []byte{0x49, 0xC1, 0xFF, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("sar %s, %x", pattern.Register, pattern.Number) + code := x86.ShiftRightSignedNumber(nil, pattern.Register, byte(pattern.Number)) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Store.go b/src/x86/Store.go similarity index 98% rename from src/x64/Store.go rename to src/x86/Store.go index 01bdfc6..0bf06e0 100644 --- a/src/x64/Store.go +++ b/src/x86/Store.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "encoding/binary" diff --git a/src/x64/StoreDynamic.go b/src/x86/StoreDynamic.go similarity index 98% rename from src/x64/StoreDynamic.go rename to src/x86/StoreDynamic.go index 3a214c9..2cedf7e 100644 --- a/src/x64/StoreDynamic.go +++ b/src/x86/StoreDynamic.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "encoding/binary" diff --git a/src/x86/StoreDynamic_test.go b/src/x86/StoreDynamic_test.go new file mode 100644 index 0000000..f5a957f --- /dev/null +++ b/src/x86/StoreDynamic_test.go @@ -0,0 +1,171 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestStoreDynamicNumber(t *testing.T) { + usagePatterns := []struct { + RegisterTo cpu.Register + Offset cpu.Register + Length byte + Number int + Code []byte + }{ + {x86.RAX, x86.R15, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RAX, x86.R15, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RAX, x86.R15, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, + {x86.RAX, x86.R15, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x38, 0x7F}}, + {x86.RCX, x86.R14, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RCX, x86.R14, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RCX, x86.R14, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, + {x86.RCX, x86.R14, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x31, 0x7F}}, + {x86.RDX, x86.R13, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDX, x86.R13, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDX, x86.R13, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, + {x86.RDX, x86.R13, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x2A, 0x7F}}, + {x86.RBX, x86.R12, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBX, x86.R12, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBX, x86.R12, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00}}, + {x86.RBX, x86.R12, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x23, 0x7F}}, + {x86.RSP, x86.R11, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSP, x86.R11, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSP, x86.R11, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x86.RSP, x86.R11, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, + {x86.RBP, x86.R10, 8, 0x7F, []byte{0x4A, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBP, x86.R10, 4, 0x7F, []byte{0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBP, x86.R10, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, + {x86.RBP, x86.R10, 1, 0x7F, []byte{0x42, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, + {x86.RSI, x86.R9, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSI, x86.R9, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSI, x86.R9, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, + {x86.RSI, x86.R9, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x0E, 0x7F}}, + {x86.RDI, x86.R8, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDI, x86.R8, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDI, x86.R8, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, + {x86.RDI, x86.R8, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x07, 0x7F}}, + {x86.R8, x86.RDI, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R8, x86.RDI, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R8, x86.RDI, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, + {x86.R8, x86.RDI, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x38, 0x7F}}, + {x86.R9, x86.RSI, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R9, x86.RSI, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R9, x86.RSI, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, + {x86.R9, x86.RSI, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x31, 0x7F}}, + {x86.R10, x86.RBP, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R10, x86.RBP, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R10, x86.RBP, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, + {x86.R10, x86.RBP, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x2A, 0x7F}}, + {x86.R11, x86.RSP, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R11, x86.RSP, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R11, x86.RSP, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x86.R11, x86.RSP, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, + {x86.R12, x86.RBX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R12, x86.RBX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R12, x86.RBX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x86.R12, x86.RBX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x1C, 0x7F}}, + {x86.R13, x86.RDX, 8, 0x7F, []byte{0x49, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R13, x86.RDX, 4, 0x7F, []byte{0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R13, x86.RDX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, + {x86.R13, x86.RDX, 1, 0x7F, []byte{0x41, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, + {x86.R14, x86.RCX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R14, x86.RCX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R14, x86.RCX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, + {x86.R14, x86.RCX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x0E, 0x7F}}, + {x86.R15, x86.RAX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R15, x86.RAX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R15, x86.RAX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, + {x86.R15, x86.RAX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x07, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("store %dB [%s+%s], %d", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.Number) + code := x86.StoreDynamicNumber(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestStoreDynamicRegister(t *testing.T) { + usagePatterns := []struct { + RegisterTo cpu.Register + Offset cpu.Register + Length byte + RegisterFrom cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, 8, x86.R15, []byte{0x4E, 0x89, 0x3C, 0x38}}, + {x86.RAX, x86.R15, 4, x86.R15, []byte{0x46, 0x89, 0x3C, 0x38}}, + {x86.RAX, x86.R15, 2, x86.R15, []byte{0x66, 0x46, 0x89, 0x3C, 0x38}}, + {x86.RAX, x86.R15, 1, x86.R15, []byte{0x46, 0x88, 0x3C, 0x38}}, + {x86.RCX, x86.R14, 8, x86.R14, []byte{0x4E, 0x89, 0x34, 0x31}}, + {x86.RCX, x86.R14, 4, x86.R14, []byte{0x46, 0x89, 0x34, 0x31}}, + {x86.RCX, x86.R14, 2, x86.R14, []byte{0x66, 0x46, 0x89, 0x34, 0x31}}, + {x86.RCX, x86.R14, 1, x86.R14, []byte{0x46, 0x88, 0x34, 0x31}}, + {x86.RDX, x86.R13, 8, x86.R13, []byte{0x4E, 0x89, 0x2C, 0x2A}}, + {x86.RDX, x86.R13, 4, x86.R13, []byte{0x46, 0x89, 0x2C, 0x2A}}, + {x86.RDX, x86.R13, 2, x86.R13, []byte{0x66, 0x46, 0x89, 0x2C, 0x2A}}, + {x86.RDX, x86.R13, 1, x86.R13, []byte{0x46, 0x88, 0x2C, 0x2A}}, + {x86.RBX, x86.R12, 8, x86.R12, []byte{0x4E, 0x89, 0x24, 0x23}}, + {x86.RBX, x86.R12, 4, x86.R12, []byte{0x46, 0x89, 0x24, 0x23}}, + {x86.RBX, x86.R12, 2, x86.R12, []byte{0x66, 0x46, 0x89, 0x24, 0x23}}, + {x86.RBX, x86.R12, 1, x86.R12, []byte{0x46, 0x88, 0x24, 0x23}}, + {x86.RSP, x86.R11, 8, x86.R11, []byte{0x4E, 0x89, 0x1C, 0x1C}}, + {x86.RSP, x86.R11, 4, x86.R11, []byte{0x46, 0x89, 0x1C, 0x1C}}, + {x86.RSP, x86.R11, 2, x86.R11, []byte{0x66, 0x46, 0x89, 0x1C, 0x1C}}, + {x86.RSP, x86.R11, 1, x86.R11, []byte{0x46, 0x88, 0x1C, 0x1C}}, + {x86.RBP, x86.R10, 8, x86.R10, []byte{0x4E, 0x89, 0x54, 0x15, 0x00}}, + {x86.RBP, x86.R10, 4, x86.R10, []byte{0x46, 0x89, 0x54, 0x15, 0x00}}, + {x86.RBP, x86.R10, 2, x86.R10, []byte{0x66, 0x46, 0x89, 0x54, 0x15, 0x00}}, + {x86.RBP, x86.R10, 1, x86.R10, []byte{0x46, 0x88, 0x54, 0x15, 0x00}}, + {x86.RSI, x86.R9, 8, x86.R9, []byte{0x4E, 0x89, 0x0C, 0x0E}}, + {x86.RSI, x86.R9, 4, x86.R9, []byte{0x46, 0x89, 0x0C, 0x0E}}, + {x86.RSI, x86.R9, 2, x86.R9, []byte{0x66, 0x46, 0x89, 0x0C, 0x0E}}, + {x86.RSI, x86.R9, 1, x86.R9, []byte{0x46, 0x88, 0x0C, 0x0E}}, + {x86.RDI, x86.R8, 8, x86.R8, []byte{0x4E, 0x89, 0x04, 0x07}}, + {x86.RDI, x86.R8, 4, x86.R8, []byte{0x46, 0x89, 0x04, 0x07}}, + {x86.RDI, x86.R8, 2, x86.R8, []byte{0x66, 0x46, 0x89, 0x04, 0x07}}, + {x86.RDI, x86.R8, 1, x86.R8, []byte{0x46, 0x88, 0x04, 0x07}}, + {x86.R8, x86.RDI, 8, x86.RDI, []byte{0x49, 0x89, 0x3C, 0x38}}, + {x86.R8, x86.RDI, 4, x86.RDI, []byte{0x41, 0x89, 0x3C, 0x38}}, + {x86.R8, x86.RDI, 2, x86.RDI, []byte{0x66, 0x41, 0x89, 0x3C, 0x38}}, + {x86.R8, x86.RDI, 1, x86.RDI, []byte{0x41, 0x88, 0x3C, 0x38}}, + {x86.R9, x86.RSI, 8, x86.RSI, []byte{0x49, 0x89, 0x34, 0x31}}, + {x86.R9, x86.RSI, 4, x86.RSI, []byte{0x41, 0x89, 0x34, 0x31}}, + {x86.R9, x86.RSI, 2, x86.RSI, []byte{0x66, 0x41, 0x89, 0x34, 0x31}}, + {x86.R9, x86.RSI, 1, x86.RSI, []byte{0x41, 0x88, 0x34, 0x31}}, + {x86.R10, x86.RBP, 8, x86.RBP, []byte{0x49, 0x89, 0x2C, 0x2A}}, + {x86.R10, x86.RBP, 4, x86.RBP, []byte{0x41, 0x89, 0x2C, 0x2A}}, + {x86.R10, x86.RBP, 2, x86.RBP, []byte{0x66, 0x41, 0x89, 0x2C, 0x2A}}, + {x86.R10, x86.RBP, 1, x86.RBP, []byte{0x41, 0x88, 0x2C, 0x2A}}, + {x86.R11, x86.RSP, 8, x86.RSP, []byte{0x4A, 0x89, 0x24, 0x1C}}, + {x86.R11, x86.RSP, 4, x86.RSP, []byte{0x42, 0x89, 0x24, 0x1C}}, + {x86.R11, x86.RSP, 2, x86.RSP, []byte{0x66, 0x42, 0x89, 0x24, 0x1C}}, + {x86.R11, x86.RSP, 1, x86.RSP, []byte{0x42, 0x88, 0x24, 0x1C}}, + {x86.R12, x86.RBX, 8, x86.RBX, []byte{0x49, 0x89, 0x1C, 0x1C}}, + {x86.R12, x86.RBX, 4, x86.RBX, []byte{0x41, 0x89, 0x1C, 0x1C}}, + {x86.R12, x86.RBX, 2, x86.RBX, []byte{0x66, 0x41, 0x89, 0x1C, 0x1C}}, + {x86.R12, x86.RBX, 1, x86.RBX, []byte{0x41, 0x88, 0x1C, 0x1C}}, + {x86.R13, x86.RDX, 8, x86.RDX, []byte{0x49, 0x89, 0x54, 0x15, 0x00}}, + {x86.R13, x86.RDX, 4, x86.RDX, []byte{0x41, 0x89, 0x54, 0x15, 0x00}}, + {x86.R13, x86.RDX, 2, x86.RDX, []byte{0x66, 0x41, 0x89, 0x54, 0x15, 0x00}}, + {x86.R13, x86.RDX, 1, x86.RDX, []byte{0x41, 0x88, 0x54, 0x15, 0x00}}, + {x86.R14, x86.RCX, 8, x86.RCX, []byte{0x49, 0x89, 0x0C, 0x0E}}, + {x86.R14, x86.RCX, 4, x86.RCX, []byte{0x41, 0x89, 0x0C, 0x0E}}, + {x86.R14, x86.RCX, 2, x86.RCX, []byte{0x66, 0x41, 0x89, 0x0C, 0x0E}}, + {x86.R14, x86.RCX, 1, x86.RCX, []byte{0x41, 0x88, 0x0C, 0x0E}}, + {x86.R15, x86.RAX, 8, x86.RAX, []byte{0x49, 0x89, 0x04, 0x07}}, + {x86.R15, x86.RAX, 4, x86.RAX, []byte{0x41, 0x89, 0x04, 0x07}}, + {x86.R15, x86.RAX, 2, x86.RAX, []byte{0x66, 0x41, 0x89, 0x04, 0x07}}, + {x86.R15, x86.RAX, 1, x86.RAX, []byte{0x41, 0x88, 0x04, 0x07}}, + } + + for _, pattern := range usagePatterns { + t.Logf("store %dB [%s+%s], %s", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) + code := x86.StoreDynamicRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x86/Store_test.go b/src/x86/Store_test.go new file mode 100644 index 0000000..e9e1e26 --- /dev/null +++ b/src/x86/Store_test.go @@ -0,0 +1,305 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestStoreNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Offset byte + Length byte + Number int + Code []byte + }{ + // No offset + {x86.RAX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RAX, 0, 4, 0x7F, []byte{0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RAX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x00, 0x7F, 0x00}}, + {x86.RAX, 0, 1, 0x7F, []byte{0xC6, 0x00, 0x7F}}, + {x86.RCX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RCX, 0, 4, 0x7F, []byte{0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RCX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x01, 0x7F, 0x00}}, + {x86.RCX, 0, 1, 0x7F, []byte{0xC6, 0x01, 0x7F}}, + {x86.RDX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDX, 0, 4, 0x7F, []byte{0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x02, 0x7F, 0x00}}, + {x86.RDX, 0, 1, 0x7F, []byte{0xC6, 0x02, 0x7F}}, + {x86.RBX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBX, 0, 4, 0x7F, []byte{0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x03, 0x7F, 0x00}}, + {x86.RBX, 0, 1, 0x7F, []byte{0xC6, 0x03, 0x7F}}, + {x86.RSP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSP, 0, 4, 0x7F, []byte{0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x04, 0x24, 0x7F, 0x00}}, + {x86.RSP, 0, 1, 0x7F, []byte{0xC6, 0x04, 0x24, 0x7F}}, + {x86.RBP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBP, 0, 4, 0x7F, []byte{0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x00, 0x7F, 0x00}}, + {x86.RBP, 0, 1, 0x7F, []byte{0xC6, 0x45, 0x00, 0x7F}}, + {x86.RSI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSI, 0, 4, 0x7F, []byte{0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x06, 0x7F, 0x00}}, + {x86.RSI, 0, 1, 0x7F, []byte{0xC6, 0x06, 0x7F}}, + {x86.RDI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDI, 0, 4, 0x7F, []byte{0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x07, 0x7F, 0x00}}, + {x86.RDI, 0, 1, 0x7F, []byte{0xC6, 0x07, 0x7F}}, + {x86.R8, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R8, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R8, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x00, 0x7F, 0x00}}, + {x86.R8, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x00, 0x7F}}, + {x86.R9, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R9, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R9, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x01, 0x7F, 0x00}}, + {x86.R9, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x01, 0x7F}}, + {x86.R10, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R10, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R10, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x02, 0x7F, 0x00}}, + {x86.R10, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x02, 0x7F}}, + {x86.R11, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R11, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R11, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x03, 0x7F, 0x00}}, + {x86.R11, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x03, 0x7F}}, + {x86.R12, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R12, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R12, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x24, 0x7F, 0x00}}, + {x86.R12, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x24, 0x7F}}, + {x86.R13, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R13, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R13, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x45, 0x00, 0x7F, 0x00}}, + {x86.R13, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x45, 0x00, 0x7F}}, + {x86.R14, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R14, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R14, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x06, 0x7F, 0x00}}, + {x86.R14, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x06, 0x7F}}, + {x86.R15, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R15, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R15, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x07, 0x7F, 0x00}}, + {x86.R15, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x07, 0x7F}}, + + // Offset of 1 + {x86.RAX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RAX, 1, 4, 0x7F, []byte{0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RAX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, + {x86.RAX, 1, 1, 0x7F, []byte{0xC6, 0x40, 0x01, 0x7F}}, + {x86.RCX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RCX, 1, 4, 0x7F, []byte{0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RCX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x41, 0x01, 0x7F, 0x00}}, + {x86.RCX, 1, 1, 0x7F, []byte{0xC6, 0x41, 0x01, 0x7F}}, + {x86.RDX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDX, 1, 4, 0x7F, []byte{0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x42, 0x01, 0x7F, 0x00}}, + {x86.RDX, 1, 1, 0x7F, []byte{0xC6, 0x42, 0x01, 0x7F}}, + {x86.RBX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBX, 1, 4, 0x7F, []byte{0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, + {x86.RBX, 1, 1, 0x7F, []byte{0xC6, 0x43, 0x01, 0x7F}}, + {x86.RSP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSP, 1, 4, 0x7F, []byte{0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, + {x86.RSP, 1, 1, 0x7F, []byte{0xC6, 0x44, 0x24, 0x01, 0x7F}}, + {x86.RBP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBP, 1, 4, 0x7F, []byte{0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RBP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, + {x86.RBP, 1, 1, 0x7F, []byte{0xC6, 0x45, 0x01, 0x7F}}, + {x86.RSI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSI, 1, 4, 0x7F, []byte{0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RSI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, + {x86.RSI, 1, 1, 0x7F, []byte{0xC6, 0x46, 0x01, 0x7F}}, + {x86.RDI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDI, 1, 4, 0x7F, []byte{0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.RDI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, + {x86.RDI, 1, 1, 0x7F, []byte{0xC6, 0x47, 0x01, 0x7F}}, + {x86.R8, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R8, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R8, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, + {x86.R8, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x40, 0x01, 0x7F}}, + {x86.R9, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R9, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R9, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x41, 0x01, 0x7F, 0x00}}, + {x86.R9, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x41, 0x01, 0x7F}}, + {x86.R10, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R10, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R10, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x42, 0x01, 0x7F, 0x00}}, + {x86.R10, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x42, 0x01, 0x7F}}, + {x86.R11, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R11, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R11, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, + {x86.R11, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x43, 0x01, 0x7F}}, + {x86.R12, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R12, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R12, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, + {x86.R12, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x44, 0x24, 0x01, 0x7F}}, + {x86.R13, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R13, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R13, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, + {x86.R13, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x45, 0x01, 0x7F}}, + {x86.R14, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R14, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R14, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, + {x86.R14, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x46, 0x01, 0x7F}}, + {x86.R15, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R15, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R15, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, + {x86.R15, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x47, 0x01, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("store %dB [%s+%d], %d", pattern.Length, pattern.Register, pattern.Offset, pattern.Number) + code := x86.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.Length, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestStoreRegister(t *testing.T) { + usagePatterns := []struct { + RegisterTo cpu.Register + Offset byte + Length byte + RegisterFrom cpu.Register + Code []byte + }{ + // No offset + {x86.RAX, 0, 8, x86.R15, []byte{0x4C, 0x89, 0x38}}, + {x86.RAX, 0, 4, x86.R15, []byte{0x44, 0x89, 0x38}}, + {x86.RAX, 0, 2, x86.R15, []byte{0x66, 0x44, 0x89, 0x38}}, + {x86.RAX, 0, 1, x86.R15, []byte{0x44, 0x88, 0x38}}, + {x86.RCX, 0, 8, x86.R14, []byte{0x4C, 0x89, 0x31}}, + {x86.RCX, 0, 4, x86.R14, []byte{0x44, 0x89, 0x31}}, + {x86.RCX, 0, 2, x86.R14, []byte{0x66, 0x44, 0x89, 0x31}}, + {x86.RCX, 0, 1, x86.R14, []byte{0x44, 0x88, 0x31}}, + {x86.RDX, 0, 8, x86.R13, []byte{0x4C, 0x89, 0x2A}}, + {x86.RDX, 0, 4, x86.R13, []byte{0x44, 0x89, 0x2A}}, + {x86.RDX, 0, 2, x86.R13, []byte{0x66, 0x44, 0x89, 0x2A}}, + {x86.RDX, 0, 1, x86.R13, []byte{0x44, 0x88, 0x2A}}, + {x86.RBX, 0, 8, x86.R12, []byte{0x4C, 0x89, 0x23}}, + {x86.RBX, 0, 4, x86.R12, []byte{0x44, 0x89, 0x23}}, + {x86.RBX, 0, 2, x86.R12, []byte{0x66, 0x44, 0x89, 0x23}}, + {x86.RBX, 0, 1, x86.R12, []byte{0x44, 0x88, 0x23}}, + {x86.RSP, 0, 8, x86.R11, []byte{0x4C, 0x89, 0x1C, 0x24}}, + {x86.RSP, 0, 4, x86.R11, []byte{0x44, 0x89, 0x1C, 0x24}}, + {x86.RSP, 0, 2, x86.R11, []byte{0x66, 0x44, 0x89, 0x1C, 0x24}}, + {x86.RSP, 0, 1, x86.R11, []byte{0x44, 0x88, 0x1C, 0x24}}, + {x86.RBP, 0, 8, x86.R10, []byte{0x4C, 0x89, 0x55, 0x00}}, + {x86.RBP, 0, 4, x86.R10, []byte{0x44, 0x89, 0x55, 0x00}}, + {x86.RBP, 0, 2, x86.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x00}}, + {x86.RBP, 0, 1, x86.R10, []byte{0x44, 0x88, 0x55, 0x00}}, + {x86.RSI, 0, 8, x86.R9, []byte{0x4C, 0x89, 0x0E}}, + {x86.RSI, 0, 4, x86.R9, []byte{0x44, 0x89, 0x0E}}, + {x86.RSI, 0, 2, x86.R9, []byte{0x66, 0x44, 0x89, 0x0E}}, + {x86.RSI, 0, 1, x86.R9, []byte{0x44, 0x88, 0x0E}}, + {x86.RDI, 0, 8, x86.R8, []byte{0x4C, 0x89, 0x07}}, + {x86.RDI, 0, 4, x86.R8, []byte{0x44, 0x89, 0x07}}, + {x86.RDI, 0, 2, x86.R8, []byte{0x66, 0x44, 0x89, 0x07}}, + {x86.RDI, 0, 1, x86.R8, []byte{0x44, 0x88, 0x07}}, + {x86.R8, 0, 8, x86.RDI, []byte{0x49, 0x89, 0x38}}, + {x86.R8, 0, 4, x86.RDI, []byte{0x41, 0x89, 0x38}}, + {x86.R8, 0, 2, x86.RDI, []byte{0x66, 0x41, 0x89, 0x38}}, + {x86.R8, 0, 1, x86.RDI, []byte{0x41, 0x88, 0x38}}, + {x86.R9, 0, 8, x86.RSI, []byte{0x49, 0x89, 0x31}}, + {x86.R9, 0, 4, x86.RSI, []byte{0x41, 0x89, 0x31}}, + {x86.R9, 0, 2, x86.RSI, []byte{0x66, 0x41, 0x89, 0x31}}, + {x86.R9, 0, 1, x86.RSI, []byte{0x41, 0x88, 0x31}}, + {x86.R10, 0, 8, x86.RBP, []byte{0x49, 0x89, 0x2A}}, + {x86.R10, 0, 4, x86.RBP, []byte{0x41, 0x89, 0x2A}}, + {x86.R10, 0, 2, x86.RBP, []byte{0x66, 0x41, 0x89, 0x2A}}, + {x86.R10, 0, 1, x86.RBP, []byte{0x41, 0x88, 0x2A}}, + {x86.R11, 0, 8, x86.RSP, []byte{0x49, 0x89, 0x23}}, + {x86.R11, 0, 4, x86.RSP, []byte{0x41, 0x89, 0x23}}, + {x86.R11, 0, 2, x86.RSP, []byte{0x66, 0x41, 0x89, 0x23}}, + {x86.R11, 0, 1, x86.RSP, []byte{0x41, 0x88, 0x23}}, + {x86.R12, 0, 8, x86.RBX, []byte{0x49, 0x89, 0x1C, 0x24}}, + {x86.R12, 0, 4, x86.RBX, []byte{0x41, 0x89, 0x1C, 0x24}}, + {x86.R12, 0, 2, x86.RBX, []byte{0x66, 0x41, 0x89, 0x1C, 0x24}}, + {x86.R12, 0, 1, x86.RBX, []byte{0x41, 0x88, 0x1C, 0x24}}, + {x86.R13, 0, 8, x86.RDX, []byte{0x49, 0x89, 0x55, 0x00}}, + {x86.R13, 0, 4, x86.RDX, []byte{0x41, 0x89, 0x55, 0x00}}, + {x86.R13, 0, 2, x86.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x00}}, + {x86.R13, 0, 1, x86.RDX, []byte{0x41, 0x88, 0x55, 0x00}}, + {x86.R14, 0, 8, x86.RCX, []byte{0x49, 0x89, 0x0E}}, + {x86.R14, 0, 4, x86.RCX, []byte{0x41, 0x89, 0x0E}}, + {x86.R14, 0, 2, x86.RCX, []byte{0x66, 0x41, 0x89, 0x0E}}, + {x86.R14, 0, 1, x86.RCX, []byte{0x41, 0x88, 0x0E}}, + {x86.R15, 0, 8, x86.RAX, []byte{0x49, 0x89, 0x07}}, + {x86.R15, 0, 4, x86.RAX, []byte{0x41, 0x89, 0x07}}, + {x86.R15, 0, 2, x86.RAX, []byte{0x66, 0x41, 0x89, 0x07}}, + {x86.R15, 0, 1, x86.RAX, []byte{0x41, 0x88, 0x07}}, + + // Offset of 1 + {x86.RAX, 1, 8, x86.R15, []byte{0x4C, 0x89, 0x78, 0x01}}, + {x86.RAX, 1, 4, x86.R15, []byte{0x44, 0x89, 0x78, 0x01}}, + {x86.RAX, 1, 2, x86.R15, []byte{0x66, 0x44, 0x89, 0x78, 0x01}}, + {x86.RAX, 1, 1, x86.R15, []byte{0x44, 0x88, 0x78, 0x01}}, + {x86.RCX, 1, 8, x86.R14, []byte{0x4C, 0x89, 0x71, 0x01}}, + {x86.RCX, 1, 4, x86.R14, []byte{0x44, 0x89, 0x71, 0x01}}, + {x86.RCX, 1, 2, x86.R14, []byte{0x66, 0x44, 0x89, 0x71, 0x01}}, + {x86.RCX, 1, 1, x86.R14, []byte{0x44, 0x88, 0x71, 0x01}}, + {x86.RDX, 1, 8, x86.R13, []byte{0x4C, 0x89, 0x6A, 0x01}}, + {x86.RDX, 1, 4, x86.R13, []byte{0x44, 0x89, 0x6A, 0x01}}, + {x86.RDX, 1, 2, x86.R13, []byte{0x66, 0x44, 0x89, 0x6A, 0x01}}, + {x86.RDX, 1, 1, x86.R13, []byte{0x44, 0x88, 0x6A, 0x01}}, + {x86.RBX, 1, 8, x86.R12, []byte{0x4C, 0x89, 0x63, 0x01}}, + {x86.RBX, 1, 4, x86.R12, []byte{0x44, 0x89, 0x63, 0x01}}, + {x86.RBX, 1, 2, x86.R12, []byte{0x66, 0x44, 0x89, 0x63, 0x01}}, + {x86.RBX, 1, 1, x86.R12, []byte{0x44, 0x88, 0x63, 0x01}}, + {x86.RSP, 1, 8, x86.R11, []byte{0x4C, 0x89, 0x5C, 0x24, 0x01}}, + {x86.RSP, 1, 4, x86.R11, []byte{0x44, 0x89, 0x5C, 0x24, 0x01}}, + {x86.RSP, 1, 2, x86.R11, []byte{0x66, 0x44, 0x89, 0x5C, 0x24, 0x01}}, + {x86.RSP, 1, 1, x86.R11, []byte{0x44, 0x88, 0x5C, 0x24, 0x01}}, + {x86.RBP, 1, 8, x86.R10, []byte{0x4C, 0x89, 0x55, 0x01}}, + {x86.RBP, 1, 4, x86.R10, []byte{0x44, 0x89, 0x55, 0x01}}, + {x86.RBP, 1, 2, x86.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x01}}, + {x86.RBP, 1, 1, x86.R10, []byte{0x44, 0x88, 0x55, 0x01}}, + {x86.RSI, 1, 8, x86.R9, []byte{0x4C, 0x89, 0x4E, 0x01}}, + {x86.RSI, 1, 4, x86.R9, []byte{0x44, 0x89, 0x4E, 0x01}}, + {x86.RSI, 1, 2, x86.R9, []byte{0x66, 0x44, 0x89, 0x4E, 0x01}}, + {x86.RSI, 1, 1, x86.R9, []byte{0x44, 0x88, 0x4E, 0x01}}, + {x86.RDI, 1, 8, x86.R8, []byte{0x4C, 0x89, 0x47, 0x01}}, + {x86.RDI, 1, 4, x86.R8, []byte{0x44, 0x89, 0x47, 0x01}}, + {x86.RDI, 1, 2, x86.R8, []byte{0x66, 0x44, 0x89, 0x47, 0x01}}, + {x86.RDI, 1, 1, x86.R8, []byte{0x44, 0x88, 0x47, 0x01}}, + {x86.R8, 1, 8, x86.RDI, []byte{0x49, 0x89, 0x78, 0x01}}, + {x86.R8, 1, 4, x86.RDI, []byte{0x41, 0x89, 0x78, 0x01}}, + {x86.R8, 1, 2, x86.RDI, []byte{0x66, 0x41, 0x89, 0x78, 0x01}}, + {x86.R8, 1, 1, x86.RDI, []byte{0x41, 0x88, 0x78, 0x01}}, + {x86.R9, 1, 8, x86.RSI, []byte{0x49, 0x89, 0x71, 0x01}}, + {x86.R9, 1, 4, x86.RSI, []byte{0x41, 0x89, 0x71, 0x01}}, + {x86.R9, 1, 2, x86.RSI, []byte{0x66, 0x41, 0x89, 0x71, 0x01}}, + {x86.R9, 1, 1, x86.RSI, []byte{0x41, 0x88, 0x71, 0x01}}, + {x86.R10, 1, 8, x86.RBP, []byte{0x49, 0x89, 0x6A, 0x01}}, + {x86.R10, 1, 4, x86.RBP, []byte{0x41, 0x89, 0x6A, 0x01}}, + {x86.R10, 1, 2, x86.RBP, []byte{0x66, 0x41, 0x89, 0x6A, 0x01}}, + {x86.R10, 1, 1, x86.RBP, []byte{0x41, 0x88, 0x6A, 0x01}}, + {x86.R11, 1, 8, x86.RSP, []byte{0x49, 0x89, 0x63, 0x01}}, + {x86.R11, 1, 4, x86.RSP, []byte{0x41, 0x89, 0x63, 0x01}}, + {x86.R11, 1, 2, x86.RSP, []byte{0x66, 0x41, 0x89, 0x63, 0x01}}, + {x86.R11, 1, 1, x86.RSP, []byte{0x41, 0x88, 0x63, 0x01}}, + {x86.R12, 1, 8, x86.RBX, []byte{0x49, 0x89, 0x5C, 0x24, 0x01}}, + {x86.R12, 1, 4, x86.RBX, []byte{0x41, 0x89, 0x5C, 0x24, 0x01}}, + {x86.R12, 1, 2, x86.RBX, []byte{0x66, 0x41, 0x89, 0x5C, 0x24, 0x01}}, + {x86.R12, 1, 1, x86.RBX, []byte{0x41, 0x88, 0x5C, 0x24, 01}}, + {x86.R13, 1, 8, x86.RDX, []byte{0x49, 0x89, 0x55, 0x01}}, + {x86.R13, 1, 4, x86.RDX, []byte{0x41, 0x89, 0x55, 0x01}}, + {x86.R13, 1, 2, x86.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x01}}, + {x86.R13, 1, 1, x86.RDX, []byte{0x41, 0x88, 0x55, 0x01}}, + {x86.R14, 1, 8, x86.RCX, []byte{0x49, 0x89, 0x4E, 0x01}}, + {x86.R14, 1, 4, x86.RCX, []byte{0x41, 0x89, 0x4E, 0x01}}, + {x86.R14, 1, 2, x86.RCX, []byte{0x66, 0x41, 0x89, 0x4E, 0x01}}, + {x86.R14, 1, 1, x86.RCX, []byte{0x41, 0x88, 0x4E, 0x01}}, + {x86.R15, 1, 8, x86.RAX, []byte{0x49, 0x89, 0x47, 0x01}}, + {x86.R15, 1, 4, x86.RAX, []byte{0x41, 0x89, 0x47, 0x01}}, + {x86.R15, 1, 2, x86.RAX, []byte{0x66, 0x41, 0x89, 0x47, 0x01}}, + {x86.R15, 1, 1, x86.RAX, []byte{0x41, 0x88, 0x47, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("store %dB [%s+%d], %s", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom) + code := x86.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Sub.go b/src/x86/Sub.go similarity index 97% rename from src/x64/Sub.go rename to src/x86/Sub.go index 57332fd..40bb4ac 100644 --- a/src/x64/Sub.go +++ b/src/x86/Sub.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Sub_test.go b/src/x86/Sub_test.go new file mode 100644 index 0000000..7577b65 --- /dev/null +++ b/src/x86/Sub_test.go @@ -0,0 +1,88 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestSubRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0x83, 0xE8, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0x83, 0xE9, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0x83, 0xEA, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0x83, 0xEB, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0x83, 0xEC, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0x83, 0xED, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0x83, 0xEE, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0x83, 0xEF, 0x01}}, + {x86.R8, 1, []byte{0x49, 0x83, 0xE8, 0x01}}, + {x86.R9, 1, []byte{0x49, 0x83, 0xE9, 0x01}}, + {x86.R10, 1, []byte{0x49, 0x83, 0xEA, 0x01}}, + {x86.R11, 1, []byte{0x49, 0x83, 0xEB, 0x01}}, + {x86.R12, 1, []byte{0x49, 0x83, 0xEC, 0x01}}, + {x86.R13, 1, []byte{0x49, 0x83, 0xED, 0x01}}, + {x86.R14, 1, []byte{0x49, 0x83, 0xEE, 0x01}}, + {x86.R15, 1, []byte{0x49, 0x83, 0xEF, 0x01}}, + + {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("sub %s, %x", pattern.Register, pattern.Number) + code := x86.SubRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestSubRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, []byte{0x4C, 0x29, 0xF8}}, + {x86.RCX, x86.R14, []byte{0x4C, 0x29, 0xF1}}, + {x86.RDX, x86.R13, []byte{0x4C, 0x29, 0xEA}}, + {x86.RBX, x86.R12, []byte{0x4C, 0x29, 0xE3}}, + {x86.RSP, x86.R11, []byte{0x4C, 0x29, 0xDC}}, + {x86.RBP, x86.R10, []byte{0x4C, 0x29, 0xD5}}, + {x86.RSI, x86.R9, []byte{0x4C, 0x29, 0xCE}}, + {x86.RDI, x86.R8, []byte{0x4C, 0x29, 0xC7}}, + {x86.R8, x86.RDI, []byte{0x49, 0x29, 0xF8}}, + {x86.R9, x86.RSI, []byte{0x49, 0x29, 0xF1}}, + {x86.R10, x86.RBP, []byte{0x49, 0x29, 0xEA}}, + {x86.R11, x86.RSP, []byte{0x49, 0x29, 0xE3}}, + {x86.R12, x86.RBX, []byte{0x49, 0x29, 0xDC}}, + {x86.R13, x86.RDX, []byte{0x49, 0x29, 0xD5}}, + {x86.R14, x86.RCX, []byte{0x49, 0x29, 0xCE}}, + {x86.R15, x86.RAX, []byte{0x49, 0x29, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("sub %s, %s", pattern.Left, pattern.Right) + code := x86.SubRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/Syscall.go b/src/x86/Syscall.go similarity index 91% rename from src/x64/Syscall.go rename to src/x86/Syscall.go index 31e2b96..2e2005a 100644 --- a/src/x64/Syscall.go +++ b/src/x86/Syscall.go @@ -1,4 +1,4 @@ -package x64 +package x86 // Syscall is the primary way to communicate with the OS kernel. func Syscall(code []byte) []byte { diff --git a/src/x64/Xor.go b/src/x86/Xor.go similarity index 97% rename from src/x64/Xor.go rename to src/x86/Xor.go index c36a805..5c50ee6 100644 --- a/src/x64/Xor.go +++ b/src/x86/Xor.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/Xor_test.go b/src/x86/Xor_test.go new file mode 100644 index 0000000..dcf6afa --- /dev/null +++ b/src/x86/Xor_test.go @@ -0,0 +1,88 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestXorRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x86.RAX, 1, []byte{0x48, 0x83, 0xF0, 0x01}}, + {x86.RCX, 1, []byte{0x48, 0x83, 0xF1, 0x01}}, + {x86.RDX, 1, []byte{0x48, 0x83, 0xF2, 0x01}}, + {x86.RBX, 1, []byte{0x48, 0x83, 0xF3, 0x01}}, + {x86.RSP, 1, []byte{0x48, 0x83, 0xF4, 0x01}}, + {x86.RBP, 1, []byte{0x48, 0x83, 0xF5, 0x01}}, + {x86.RSI, 1, []byte{0x48, 0x83, 0xF6, 0x01}}, + {x86.RDI, 1, []byte{0x48, 0x83, 0xF7, 0x01}}, + {x86.R8, 1, []byte{0x49, 0x83, 0xF0, 0x01}}, + {x86.R9, 1, []byte{0x49, 0x83, 0xF1, 0x01}}, + {x86.R10, 1, []byte{0x49, 0x83, 0xF2, 0x01}}, + {x86.R11, 1, []byte{0x49, 0x83, 0xF3, 0x01}}, + {x86.R12, 1, []byte{0x49, 0x83, 0xF4, 0x01}}, + {x86.R13, 1, []byte{0x49, 0x83, 0xF5, 0x01}}, + {x86.R14, 1, []byte{0x49, 0x83, 0xF6, 0x01}}, + {x86.R15, 1, []byte{0x49, 0x83, 0xF7, 0x01}}, + + {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF7, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("xor %s, %x", pattern.Register, pattern.Number) + code := x86.XorRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestXorRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x86.RAX, x86.R15, []byte{0x4C, 0x31, 0xF8}}, + {x86.RCX, x86.R14, []byte{0x4C, 0x31, 0xF1}}, + {x86.RDX, x86.R13, []byte{0x4C, 0x31, 0xEA}}, + {x86.RBX, x86.R12, []byte{0x4C, 0x31, 0xE3}}, + {x86.RSP, x86.R11, []byte{0x4C, 0x31, 0xDC}}, + {x86.RBP, x86.R10, []byte{0x4C, 0x31, 0xD5}}, + {x86.RSI, x86.R9, []byte{0x4C, 0x31, 0xCE}}, + {x86.RDI, x86.R8, []byte{0x4C, 0x31, 0xC7}}, + {x86.R8, x86.RDI, []byte{0x49, 0x31, 0xF8}}, + {x86.R9, x86.RSI, []byte{0x49, 0x31, 0xF1}}, + {x86.R10, x86.RBP, []byte{0x49, 0x31, 0xEA}}, + {x86.R11, x86.RSP, []byte{0x49, 0x31, 0xE3}}, + {x86.R12, x86.RBX, []byte{0x49, 0x31, 0xDC}}, + {x86.R13, x86.RDX, []byte{0x49, 0x31, 0xD5}}, + {x86.R14, x86.RCX, []byte{0x49, 0x31, 0xCE}}, + {x86.R15, x86.RAX, []byte{0x49, 0x31, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("xor %s, %s", pattern.Left, pattern.Right) + code := x86.XorRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x64/encode.go b/src/x86/encode.go similarity index 98% rename from src/x64/encode.go rename to src/x86/encode.go index 6e872b5..1306d52 100644 --- a/src/x64/encode.go +++ b/src/x86/encode.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x64/encodeNum.go b/src/x86/encodeNum.go similarity index 97% rename from src/x64/encodeNum.go rename to src/x86/encodeNum.go index c6db324..1611604 100644 --- a/src/x64/encodeNum.go +++ b/src/x86/encodeNum.go @@ -1,4 +1,4 @@ -package x64 +package x86 import ( "encoding/binary" diff --git a/src/x64/memoryAccess.go b/src/x86/memoryAccess.go similarity index 98% rename from src/x64/memoryAccess.go rename to src/x86/memoryAccess.go index c1c658c..b1dd605 100644 --- a/src/x64/memoryAccess.go +++ b/src/x86/memoryAccess.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x64/memoryAccessDynamic.go b/src/x86/memoryAccessDynamic.go similarity index 98% rename from src/x64/memoryAccessDynamic.go rename to src/x86/memoryAccessDynamic.go index c9e323c..d47988e 100644 --- a/src/x64/memoryAccessDynamic.go +++ b/src/x86/memoryAccessDynamic.go @@ -1,4 +1,4 @@ -package x64 +package x86 import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/x86/x64_test.go b/src/x86/x64_test.go new file mode 100644 index 0000000..f9e88e4 --- /dev/null +++ b/src/x86/x64_test.go @@ -0,0 +1,19 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestX64(t *testing.T) { + assert.DeepEqual(t, x86.AlignStack(nil), []byte{0x48, 0x83, 0xE4, 0xF0}) + assert.DeepEqual(t, x86.Call(nil, 1), []byte{0xE8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x86.CallAtAddress(nil, 1), []byte{0xFF, 0x15, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x86.ExtendRAXToRDX(nil), []byte{0x48, 0x99}) + assert.DeepEqual(t, x86.MoveRegisterNumber(nil, 0, 1), []byte{0xB8, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x86.MoveRegisterNumber(nil, 1, 1), []byte{0xB9, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x86.Return(nil), []byte{0xC3}) + assert.DeepEqual(t, x86.Syscall(nil), []byte{0x0F, 0x05}) +} From 559df424d32e636c7b14454204b27e7ee3563ed1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Feb 2025 12:49:17 +0100 Subject: [PATCH 0651/1012] Added server example --- examples/server/server.q | 52 ++++++++++++++++++++++++++++++++++ src/core/CompileCall.go | 5 ++++ src/core/ExpressionToMemory.go | 9 +++--- src/scanner/scanStruct.go | 10 ++++++- tests/errors/TypeMismatch.q | 2 +- 5 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 examples/server/server.q diff --git a/examples/server/server.q b/examples/server/server.q new file mode 100644 index 0000000..b27d049 --- /dev/null +++ b/examples/server/server.q @@ -0,0 +1,52 @@ +import sys + +struct sockaddr_in { + sin_family Int16 + sin_port Int16 + sin_addr Int64 + sin_zero Int64 +} + +// Open server and client in 2 terminals: +// [1] q run examples/server +// [2] netcat 127.0.0.1 8080 +main() { + socket := sys.socket(2, 1, 0) + + if socket < 0 { + sys.write(1, "socket error\n", 13) + sys.exit(1) + } + + addr := new(sockaddr_in) + addr.sin_family = 2 + addr.sin_port = 0x901F + addr.sin_addr = 0 + addr.sin_zero = 0 + + if sys.bind(socket, addr, 20) != 0 { + sys.write(1, "bind error\n", 11) + sys.exit(1) + } + + delete(addr) + + if sys.listen(socket, 10) != 0 { + sys.write(1, "listen error\n", 13) + sys.exit(1) + } + + sys.write(1, "listening...\n", 13) + + loop { + conn := sys.accept(socket, 0, 0) + + if conn != -1 { + sys.write(1, "accepted\n", 9) + sys.write(conn, "Hello\n", 6) + sys.close(conn) + } else { + sys.write(1, "error\n", 6) + } + } +} \ No newline at end of file diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 5ce42f1..9ac1b93 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -5,6 +5,7 @@ import ( "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/types" "git.akyoto.dev/cli/q/src/x86" ) @@ -94,6 +95,10 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { } if !types.Is(typ, fn.Input[i].Type) { + if parameters[i].Token.Kind == token.Number && parameters[i].Token.Text(f.File.Bytes) == "0" { + continue + } + return nil, errors.New(&errors.TypeMismatch{ Encountered: typ.Name(), Expected: fn.Input[i].Type.Name(), diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index e50262d..0176701 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -4,7 +4,6 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/sizeof" "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/types" ) @@ -37,11 +36,11 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me return nil, err } - size := byte(sizeof.Signed(int64(number))) + // size := byte(sizeof.Signed(int64(number))) - if size > memory.Length { - return nil, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) - } + // if size > memory.Length { + // return nil, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) + // } f.MemoryNumber(asm.STORE, memory, number) return types.Int, nil diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index f2319e9..3f9e74d 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -35,7 +35,15 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro fieldTypeName := tokens[i].Text(file.Bytes) fieldType := types.Int - if fieldTypeName != "Int" { + switch fieldTypeName { + case "Int", "Int64": + case "Int32": + fieldType = types.Int32 + case "Int16": + fieldType = types.Int16 + case "Int8": + fieldType = types.Int8 + default: panic("not implemented") } diff --git a/tests/errors/TypeMismatch.q b/tests/errors/TypeMismatch.q index d86c306..4da2421 100644 --- a/tests/errors/TypeMismatch.q +++ b/tests/errors/TypeMismatch.q @@ -1,5 +1,5 @@ main() { - writeToMemory(0) + writeToMemory(42) } writeToMemory(p Pointer) { From f36b1f6c7c9046f1917b5c92f680d86c94380a87 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Feb 2025 12:49:17 +0100 Subject: [PATCH 0652/1012] Added server example --- examples/server/server.q | 52 ++++++++++++++++++++++++++++++++++ src/core/CompileCall.go | 5 ++++ src/core/ExpressionToMemory.go | 9 +++--- src/scanner/scanStruct.go | 10 ++++++- tests/errors/TypeMismatch.q | 2 +- 5 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 examples/server/server.q diff --git a/examples/server/server.q b/examples/server/server.q new file mode 100644 index 0000000..b27d049 --- /dev/null +++ b/examples/server/server.q @@ -0,0 +1,52 @@ +import sys + +struct sockaddr_in { + sin_family Int16 + sin_port Int16 + sin_addr Int64 + sin_zero Int64 +} + +// Open server and client in 2 terminals: +// [1] q run examples/server +// [2] netcat 127.0.0.1 8080 +main() { + socket := sys.socket(2, 1, 0) + + if socket < 0 { + sys.write(1, "socket error\n", 13) + sys.exit(1) + } + + addr := new(sockaddr_in) + addr.sin_family = 2 + addr.sin_port = 0x901F + addr.sin_addr = 0 + addr.sin_zero = 0 + + if sys.bind(socket, addr, 20) != 0 { + sys.write(1, "bind error\n", 11) + sys.exit(1) + } + + delete(addr) + + if sys.listen(socket, 10) != 0 { + sys.write(1, "listen error\n", 13) + sys.exit(1) + } + + sys.write(1, "listening...\n", 13) + + loop { + conn := sys.accept(socket, 0, 0) + + if conn != -1 { + sys.write(1, "accepted\n", 9) + sys.write(conn, "Hello\n", 6) + sys.close(conn) + } else { + sys.write(1, "error\n", 6) + } + } +} \ No newline at end of file diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 5ce42f1..9ac1b93 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -5,6 +5,7 @@ import ( "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/types" "git.akyoto.dev/cli/q/src/x86" ) @@ -94,6 +95,10 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { } if !types.Is(typ, fn.Input[i].Type) { + if parameters[i].Token.Kind == token.Number && parameters[i].Token.Text(f.File.Bytes) == "0" { + continue + } + return nil, errors.New(&errors.TypeMismatch{ Encountered: typ.Name(), Expected: fn.Input[i].Type.Name(), diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index e50262d..0176701 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -4,7 +4,6 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/sizeof" "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/types" ) @@ -37,11 +36,11 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me return nil, err } - size := byte(sizeof.Signed(int64(number))) + // size := byte(sizeof.Signed(int64(number))) - if size > memory.Length { - return nil, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) - } + // if size > memory.Length { + // return nil, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) + // } f.MemoryNumber(asm.STORE, memory, number) return types.Int, nil diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index f2319e9..3f9e74d 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -35,7 +35,15 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro fieldTypeName := tokens[i].Text(file.Bytes) fieldType := types.Int - if fieldTypeName != "Int" { + switch fieldTypeName { + case "Int", "Int64": + case "Int32": + fieldType = types.Int32 + case "Int16": + fieldType = types.Int16 + case "Int8": + fieldType = types.Int8 + default: panic("not implemented") } diff --git a/tests/errors/TypeMismatch.q b/tests/errors/TypeMismatch.q index d86c306..4da2421 100644 --- a/tests/errors/TypeMismatch.q +++ b/tests/errors/TypeMismatch.q @@ -1,5 +1,5 @@ main() { - writeToMemory(0) + writeToMemory(42) } writeToMemory(p Pointer) { From 1b63ac73cf9ebe1913c412372b9de96c76ee5a70 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Feb 2025 13:10:44 +0100 Subject: [PATCH 0653/1012] Allowed comments in the scanner --- src/scanner/scanFile.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 3dfa4ac..79cf791 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -31,6 +31,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { for i < len(tokens) { switch tokens[i].Kind { case token.NewLine: + case token.Comment: case token.Import: i, err = s.scanImport(file, tokens, i) case token.Struct: From ace2f3c4a57498986810cc393d7bfed466136c1a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Feb 2025 13:10:44 +0100 Subject: [PATCH 0654/1012] Allowed comments in the scanner --- src/scanner/scanFile.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 3dfa4ac..79cf791 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -31,6 +31,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { for i < len(tokens) { switch tokens[i].Kind { case token.NewLine: + case token.Comment: case token.Import: i, err = s.scanImport(file, tokens, i) case token.Struct: From 9908066c203112f781b65ed500e8154b4a856f4c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Feb 2025 13:19:04 +0100 Subject: [PATCH 0655/1012] Added socket syscalls for Mac --- lib/sys/network_linux.q | 15 +++++++++++++++ lib/sys/network_mac.q | 15 +++++++++++++++ lib/sys/sys_linux.q | 16 ---------------- 3 files changed, 30 insertions(+), 16 deletions(-) create mode 100644 lib/sys/network_linux.q create mode 100644 lib/sys/network_mac.q diff --git a/lib/sys/network_linux.q b/lib/sys/network_linux.q new file mode 100644 index 0000000..5368e47 --- /dev/null +++ b/lib/sys/network_linux.q @@ -0,0 +1,15 @@ +socket(family Int, type Int, protocol Int) -> Int { + return syscall(41, family, type, protocol) +} + +accept(fd Int, address Pointer, length Int) -> Int { + return syscall(43, fd, address, length) +} + +bind(fd Int, address Pointer, length Int) -> Int { + return syscall(49, fd, address, length) +} + +listen(fd Int, backlog Int) -> Int { + return syscall(50, fd, backlog) +} \ No newline at end of file diff --git a/lib/sys/network_mac.q b/lib/sys/network_mac.q new file mode 100644 index 0000000..667c9ac --- /dev/null +++ b/lib/sys/network_mac.q @@ -0,0 +1,15 @@ +socket(family Int, type Int, protocol Int) -> Int { + return syscall(0x2000061, family, type, protocol) +} + +accept(fd Int, address Pointer, length Int) -> Int { + return syscall(0x200001E, fd, address, length) +} + +bind(fd Int, address Pointer, length Int) -> Int { + return syscall(0x2000068, fd, address, length) +} + +listen(fd Int, backlog Int) -> Int { + return syscall(0x200006A, fd, backlog) +} \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q index a2f0e97..36fad2c 100644 --- a/lib/sys/sys_linux.q +++ b/lib/sys/sys_linux.q @@ -42,22 +42,6 @@ exit(status Int) { syscall(60, status) } -socket(family Int, type Int, protocol Int) -> Int { - return syscall(41, family, type, protocol) -} - -accept(fd Int, address Pointer, length Int) -> Int { - return syscall(43, fd, address, length) -} - -bind(fd Int, address Pointer, length Int) -> Int { - return syscall(49, fd, address, length) -} - -listen(fd Int, backlog Int) -> Int { - return syscall(50, fd, backlog) -} - getcwd(buffer Pointer, length Int) -> Int { return syscall(79, buffer, length) } From 170931cf5d83e9f06767a9f6049b03bdf14a1e72 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Feb 2025 13:19:04 +0100 Subject: [PATCH 0656/1012] Added socket syscalls for Mac --- lib/sys/network_linux.q | 15 +++++++++++++++ lib/sys/network_mac.q | 15 +++++++++++++++ lib/sys/sys_linux.q | 16 ---------------- 3 files changed, 30 insertions(+), 16 deletions(-) create mode 100644 lib/sys/network_linux.q create mode 100644 lib/sys/network_mac.q diff --git a/lib/sys/network_linux.q b/lib/sys/network_linux.q new file mode 100644 index 0000000..5368e47 --- /dev/null +++ b/lib/sys/network_linux.q @@ -0,0 +1,15 @@ +socket(family Int, type Int, protocol Int) -> Int { + return syscall(41, family, type, protocol) +} + +accept(fd Int, address Pointer, length Int) -> Int { + return syscall(43, fd, address, length) +} + +bind(fd Int, address Pointer, length Int) -> Int { + return syscall(49, fd, address, length) +} + +listen(fd Int, backlog Int) -> Int { + return syscall(50, fd, backlog) +} \ No newline at end of file diff --git a/lib/sys/network_mac.q b/lib/sys/network_mac.q new file mode 100644 index 0000000..667c9ac --- /dev/null +++ b/lib/sys/network_mac.q @@ -0,0 +1,15 @@ +socket(family Int, type Int, protocol Int) -> Int { + return syscall(0x2000061, family, type, protocol) +} + +accept(fd Int, address Pointer, length Int) -> Int { + return syscall(0x200001E, fd, address, length) +} + +bind(fd Int, address Pointer, length Int) -> Int { + return syscall(0x2000068, fd, address, length) +} + +listen(fd Int, backlog Int) -> Int { + return syscall(0x200006A, fd, backlog) +} \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q index a2f0e97..36fad2c 100644 --- a/lib/sys/sys_linux.q +++ b/lib/sys/sys_linux.q @@ -42,22 +42,6 @@ exit(status Int) { syscall(60, status) } -socket(family Int, type Int, protocol Int) -> Int { - return syscall(41, family, type, protocol) -} - -accept(fd Int, address Pointer, length Int) -> Int { - return syscall(43, fd, address, length) -} - -bind(fd Int, address Pointer, length Int) -> Int { - return syscall(49, fd, address, length) -} - -listen(fd Int, backlog Int) -> Int { - return syscall(50, fd, backlog) -} - getcwd(buffer Pointer, length Int) -> Int { return syscall(79, buffer, length) } From c352778c6d9b0440c8639a5e21106e9d624d7f5c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Feb 2025 13:46:37 +0100 Subject: [PATCH 0657/1012] Reorganized sys functions --- lib/sys/fs_linux.q | 23 ++++++++ lib/sys/io_linux.q | 15 +++++ lib/sys/io_mac.q | 15 +++++ lib/sys/io_windows.q | 3 + lib/sys/mem_linux.q | 7 +++ lib/sys/mem_mac.q | 7 +++ lib/sys/{sys_windows.q => mem_windows.q} | 10 ---- lib/sys/{network_linux.q => net_linux.q} | 0 lib/sys/{network_mac.q => net_mac.q} | 0 lib/sys/proc_linux.q | 19 +++++++ lib/sys/proc_mac.q | 15 +++++ lib/sys/proc_windows.q | 3 + lib/sys/sys_linux.q | 71 ------------------------ lib/sys/sys_mac.q | 39 ------------- lib/sys/time_linux.q | 3 + 15 files changed, 110 insertions(+), 120 deletions(-) create mode 100644 lib/sys/fs_linux.q create mode 100644 lib/sys/io_linux.q create mode 100644 lib/sys/io_mac.q create mode 100644 lib/sys/io_windows.q create mode 100644 lib/sys/mem_linux.q create mode 100644 lib/sys/mem_mac.q rename lib/sys/{sys_windows.q => mem_windows.q} (54%) rename lib/sys/{network_linux.q => net_linux.q} (100%) rename lib/sys/{network_mac.q => net_mac.q} (100%) create mode 100644 lib/sys/proc_linux.q create mode 100644 lib/sys/proc_mac.q create mode 100644 lib/sys/proc_windows.q delete mode 100644 lib/sys/sys_linux.q delete mode 100644 lib/sys/sys_mac.q create mode 100644 lib/sys/time_linux.q diff --git a/lib/sys/fs_linux.q b/lib/sys/fs_linux.q new file mode 100644 index 0000000..dc1bd11 --- /dev/null +++ b/lib/sys/fs_linux.q @@ -0,0 +1,23 @@ +getcwd(buffer Pointer, length Int) -> Int { + return syscall(79, buffer, length) +} + +chdir(path Pointer) -> Int { + return syscall(80, path) +} + +rename(old Pointer, new Pointer) -> Int { + return syscall(82, old, new) +} + +mkdir(path Pointer, mode Int) -> Int { + return syscall(83, path, mode) +} + +rmdir(path Pointer) -> Int { + return syscall(84, path) +} + +unlink(file Pointer) -> Int { + return syscall(87, file) +} \ No newline at end of file diff --git a/lib/sys/io_linux.q b/lib/sys/io_linux.q new file mode 100644 index 0000000..0bfae7e --- /dev/null +++ b/lib/sys/io_linux.q @@ -0,0 +1,15 @@ +read(fd Int, address Pointer, length Int) -> Int { + return syscall(0, fd, address, length) +} + +write(fd Int, address Pointer, length Int) -> Int { + return syscall(1, fd, address, length) +} + +open(file Pointer, flags Int, mode Int) -> Int { + return syscall(2, file, flags, mode) +} + +close(fd Int) -> Int { + return syscall(3, fd) +} \ No newline at end of file diff --git a/lib/sys/io_mac.q b/lib/sys/io_mac.q new file mode 100644 index 0000000..17b07aa --- /dev/null +++ b/lib/sys/io_mac.q @@ -0,0 +1,15 @@ +read(fd Int, address Pointer, length Int) -> Int { + return syscall(0x2000003, fd, address, length) +} + +write(fd Int, address Pointer, length Int) -> Int { + return syscall(0x2000004, fd, address, length) +} + +open(file Pointer, flags Int, mode Int) -> Int { + return syscall(0x2000005, file, flags, mode) +} + +close(fd Int) -> Int { + return syscall(0x2000006, fd) +} \ No newline at end of file diff --git a/lib/sys/io_windows.q b/lib/sys/io_windows.q new file mode 100644 index 0000000..0bf7b09 --- /dev/null +++ b/lib/sys/io_windows.q @@ -0,0 +1,3 @@ +write(fd Int, address Pointer, length Int) -> Int { + return kernel32.WriteFile(fd, address, length) +} \ No newline at end of file diff --git a/lib/sys/mem_linux.q b/lib/sys/mem_linux.q new file mode 100644 index 0000000..5d8bb7c --- /dev/null +++ b/lib/sys/mem_linux.q @@ -0,0 +1,7 @@ +mmap(address Int, length Int, protection Int, flags Int) -> Pointer { + return syscall(9, address, length, protection, flags) +} + +munmap(address Pointer, length Int) -> Int { + return syscall(11, address, length) +} \ No newline at end of file diff --git a/lib/sys/mem_mac.q b/lib/sys/mem_mac.q new file mode 100644 index 0000000..cbae713 --- /dev/null +++ b/lib/sys/mem_mac.q @@ -0,0 +1,7 @@ +mmap(address Int, length Int, protection Int, flags Int) -> Pointer { + return syscall(0x20000C5, address, length, protection, flags) +} + +munmap(address Pointer, length Int) -> Int { + return syscall(0x2000049, address, length) +} \ No newline at end of file diff --git a/lib/sys/sys_windows.q b/lib/sys/mem_windows.q similarity index 54% rename from lib/sys/sys_windows.q rename to lib/sys/mem_windows.q index 3c9c82f..c9ada81 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/mem_windows.q @@ -1,17 +1,7 @@ -write(fd Int, address Pointer, length Int) -> Int { - // out numberOfBytesWritten - // out overlapped - return kernel32.WriteFile(fd, address, length) -} - mmap(address Int, length Int, protection Int, flags Int) -> Pointer { return kernel32.VirtualAlloc(address, length, flags, protection) } munmap(address Pointer, length Int) -> Int { return kernel32.VirtualFree(address, length, 0x4000) -} - -exit(code Int) { - kernel32.ExitProcess(code) } \ No newline at end of file diff --git a/lib/sys/network_linux.q b/lib/sys/net_linux.q similarity index 100% rename from lib/sys/network_linux.q rename to lib/sys/net_linux.q diff --git a/lib/sys/network_mac.q b/lib/sys/net_mac.q similarity index 100% rename from lib/sys/network_mac.q rename to lib/sys/net_mac.q diff --git a/lib/sys/proc_linux.q b/lib/sys/proc_linux.q new file mode 100644 index 0000000..2ef2ec7 --- /dev/null +++ b/lib/sys/proc_linux.q @@ -0,0 +1,19 @@ +clone(flags Int, stack Pointer) -> Int { + return syscall(56, flags, stack) +} + +fork() -> Int { + return syscall(57) +} + +execve(path Pointer, argv Pointer, envp Pointer) -> Int { + return syscall(59, path, argv, envp) +} + +exit(status Int) { + syscall(60, status) +} + +waitid(type Int, id Int, info Pointer, options Int) -> Int { + return syscall(247, type, id, info, options) +} \ No newline at end of file diff --git a/lib/sys/proc_mac.q b/lib/sys/proc_mac.q new file mode 100644 index 0000000..bf70e55 --- /dev/null +++ b/lib/sys/proc_mac.q @@ -0,0 +1,15 @@ +exit(status Int) { + syscall(0x2000001, status) +} + +fork() -> Int { + return syscall(0x2000002) +} + +execve(path Pointer, argv Pointer, envp Pointer) -> Int { + return syscall(0x200003B, path, argv, envp) +} + +waitid(type Int, id Int, info Pointer, options Int) -> Int { + return syscall(0x20000AD, type, id, info, options) +} \ No newline at end of file diff --git a/lib/sys/proc_windows.q b/lib/sys/proc_windows.q new file mode 100644 index 0000000..7819bca --- /dev/null +++ b/lib/sys/proc_windows.q @@ -0,0 +1,3 @@ +exit(code Int) { + kernel32.ExitProcess(code) +} \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q deleted file mode 100644 index 36fad2c..0000000 --- a/lib/sys/sys_linux.q +++ /dev/null @@ -1,71 +0,0 @@ -read(fd Int, address Pointer, length Int) -> Int { - return syscall(0, fd, address, length) -} - -write(fd Int, address Pointer, length Int) -> Int { - return syscall(1, fd, address, length) -} - -open(file Pointer, flags Int, mode Int) -> Int { - return syscall(2, file, flags, mode) -} - -close(fd Int) -> Int { - return syscall(3, fd) -} - -mmap(address Int, length Int, protection Int, flags Int) -> Pointer { - return syscall(9, address, length, protection, flags) -} - -munmap(address Pointer, length Int) -> Int { - return syscall(11, address, length) -} - -nanosleep(duration Pointer) -> Int { - return syscall(35, duration, 0) -} - -clone(flags Int, stack Pointer) -> Int { - return syscall(56, flags, stack) -} - -fork() -> Int { - return syscall(57) -} - -execve(path Pointer, argv Pointer, envp Pointer) -> Int { - return syscall(59, path, argv, envp) -} - -exit(status Int) { - syscall(60, status) -} - -getcwd(buffer Pointer, length Int) -> Int { - return syscall(79, buffer, length) -} - -chdir(path Pointer) -> Int { - return syscall(80, path) -} - -rename(old Pointer, new Pointer) -> Int { - return syscall(82, old, new) -} - -mkdir(path Pointer, mode Int) -> Int { - return syscall(83, path, mode) -} - -rmdir(path Pointer) -> Int { - return syscall(84, path) -} - -unlink(file Pointer) -> Int { - return syscall(87, file) -} - -waitid(type Int, id Int, info Pointer, options Int) -> Int { - return syscall(247, type, id, info, options) -} diff --git a/lib/sys/sys_mac.q b/lib/sys/sys_mac.q deleted file mode 100644 index 24fd36d..0000000 --- a/lib/sys/sys_mac.q +++ /dev/null @@ -1,39 +0,0 @@ -exit(status Int) { - syscall(0x2000001, status) -} - -fork() -> Int { - return syscall(0x2000002) -} - -read(fd Int, address Pointer, length Int) -> Int { - return syscall(0x2000003, fd, address, length) -} - -write(fd Int, address Pointer, length Int) -> Int { - return syscall(0x2000004, fd, address, length) -} - -open(file Pointer, flags Int, mode Int) -> Int { - return syscall(0x2000005, file, flags, mode) -} - -close(fd Int) -> Int { - return syscall(0x2000006, fd) -} - -mmap(address Int, length Int, protection Int, flags Int) -> Pointer { - return syscall(0x20000C5, address, length, protection, flags) -} - -munmap(address Pointer, length Int) -> Int { - return syscall(0x2000049, address, length) -} - -execve(path Pointer, argv Pointer, envp Pointer) -> Int { - return syscall(0x200003B, path, argv, envp) -} - -waitid(type Int, id Int, info Pointer, options Int) -> Int { - return syscall(0x20000AD, type, id, info, options) -} \ No newline at end of file diff --git a/lib/sys/time_linux.q b/lib/sys/time_linux.q new file mode 100644 index 0000000..6727476 --- /dev/null +++ b/lib/sys/time_linux.q @@ -0,0 +1,3 @@ +nanosleep(duration Pointer) -> Int { + return syscall(35, duration, 0) +} \ No newline at end of file From 9dcd43be4628cad375b641e0b0ffa4c7927ac2a7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Feb 2025 13:46:37 +0100 Subject: [PATCH 0658/1012] Reorganized sys functions --- lib/sys/fs_linux.q | 23 ++++++++ lib/sys/io_linux.q | 15 +++++ lib/sys/io_mac.q | 15 +++++ lib/sys/io_windows.q | 3 + lib/sys/mem_linux.q | 7 +++ lib/sys/mem_mac.q | 7 +++ lib/sys/{sys_windows.q => mem_windows.q} | 10 ---- lib/sys/{network_linux.q => net_linux.q} | 0 lib/sys/{network_mac.q => net_mac.q} | 0 lib/sys/proc_linux.q | 19 +++++++ lib/sys/proc_mac.q | 15 +++++ lib/sys/proc_windows.q | 3 + lib/sys/sys_linux.q | 71 ------------------------ lib/sys/sys_mac.q | 39 ------------- lib/sys/time_linux.q | 3 + 15 files changed, 110 insertions(+), 120 deletions(-) create mode 100644 lib/sys/fs_linux.q create mode 100644 lib/sys/io_linux.q create mode 100644 lib/sys/io_mac.q create mode 100644 lib/sys/io_windows.q create mode 100644 lib/sys/mem_linux.q create mode 100644 lib/sys/mem_mac.q rename lib/sys/{sys_windows.q => mem_windows.q} (54%) rename lib/sys/{network_linux.q => net_linux.q} (100%) rename lib/sys/{network_mac.q => net_mac.q} (100%) create mode 100644 lib/sys/proc_linux.q create mode 100644 lib/sys/proc_mac.q create mode 100644 lib/sys/proc_windows.q delete mode 100644 lib/sys/sys_linux.q delete mode 100644 lib/sys/sys_mac.q create mode 100644 lib/sys/time_linux.q diff --git a/lib/sys/fs_linux.q b/lib/sys/fs_linux.q new file mode 100644 index 0000000..dc1bd11 --- /dev/null +++ b/lib/sys/fs_linux.q @@ -0,0 +1,23 @@ +getcwd(buffer Pointer, length Int) -> Int { + return syscall(79, buffer, length) +} + +chdir(path Pointer) -> Int { + return syscall(80, path) +} + +rename(old Pointer, new Pointer) -> Int { + return syscall(82, old, new) +} + +mkdir(path Pointer, mode Int) -> Int { + return syscall(83, path, mode) +} + +rmdir(path Pointer) -> Int { + return syscall(84, path) +} + +unlink(file Pointer) -> Int { + return syscall(87, file) +} \ No newline at end of file diff --git a/lib/sys/io_linux.q b/lib/sys/io_linux.q new file mode 100644 index 0000000..0bfae7e --- /dev/null +++ b/lib/sys/io_linux.q @@ -0,0 +1,15 @@ +read(fd Int, address Pointer, length Int) -> Int { + return syscall(0, fd, address, length) +} + +write(fd Int, address Pointer, length Int) -> Int { + return syscall(1, fd, address, length) +} + +open(file Pointer, flags Int, mode Int) -> Int { + return syscall(2, file, flags, mode) +} + +close(fd Int) -> Int { + return syscall(3, fd) +} \ No newline at end of file diff --git a/lib/sys/io_mac.q b/lib/sys/io_mac.q new file mode 100644 index 0000000..17b07aa --- /dev/null +++ b/lib/sys/io_mac.q @@ -0,0 +1,15 @@ +read(fd Int, address Pointer, length Int) -> Int { + return syscall(0x2000003, fd, address, length) +} + +write(fd Int, address Pointer, length Int) -> Int { + return syscall(0x2000004, fd, address, length) +} + +open(file Pointer, flags Int, mode Int) -> Int { + return syscall(0x2000005, file, flags, mode) +} + +close(fd Int) -> Int { + return syscall(0x2000006, fd) +} \ No newline at end of file diff --git a/lib/sys/io_windows.q b/lib/sys/io_windows.q new file mode 100644 index 0000000..0bf7b09 --- /dev/null +++ b/lib/sys/io_windows.q @@ -0,0 +1,3 @@ +write(fd Int, address Pointer, length Int) -> Int { + return kernel32.WriteFile(fd, address, length) +} \ No newline at end of file diff --git a/lib/sys/mem_linux.q b/lib/sys/mem_linux.q new file mode 100644 index 0000000..5d8bb7c --- /dev/null +++ b/lib/sys/mem_linux.q @@ -0,0 +1,7 @@ +mmap(address Int, length Int, protection Int, flags Int) -> Pointer { + return syscall(9, address, length, protection, flags) +} + +munmap(address Pointer, length Int) -> Int { + return syscall(11, address, length) +} \ No newline at end of file diff --git a/lib/sys/mem_mac.q b/lib/sys/mem_mac.q new file mode 100644 index 0000000..cbae713 --- /dev/null +++ b/lib/sys/mem_mac.q @@ -0,0 +1,7 @@ +mmap(address Int, length Int, protection Int, flags Int) -> Pointer { + return syscall(0x20000C5, address, length, protection, flags) +} + +munmap(address Pointer, length Int) -> Int { + return syscall(0x2000049, address, length) +} \ No newline at end of file diff --git a/lib/sys/sys_windows.q b/lib/sys/mem_windows.q similarity index 54% rename from lib/sys/sys_windows.q rename to lib/sys/mem_windows.q index 3c9c82f..c9ada81 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/mem_windows.q @@ -1,17 +1,7 @@ -write(fd Int, address Pointer, length Int) -> Int { - // out numberOfBytesWritten - // out overlapped - return kernel32.WriteFile(fd, address, length) -} - mmap(address Int, length Int, protection Int, flags Int) -> Pointer { return kernel32.VirtualAlloc(address, length, flags, protection) } munmap(address Pointer, length Int) -> Int { return kernel32.VirtualFree(address, length, 0x4000) -} - -exit(code Int) { - kernel32.ExitProcess(code) } \ No newline at end of file diff --git a/lib/sys/network_linux.q b/lib/sys/net_linux.q similarity index 100% rename from lib/sys/network_linux.q rename to lib/sys/net_linux.q diff --git a/lib/sys/network_mac.q b/lib/sys/net_mac.q similarity index 100% rename from lib/sys/network_mac.q rename to lib/sys/net_mac.q diff --git a/lib/sys/proc_linux.q b/lib/sys/proc_linux.q new file mode 100644 index 0000000..2ef2ec7 --- /dev/null +++ b/lib/sys/proc_linux.q @@ -0,0 +1,19 @@ +clone(flags Int, stack Pointer) -> Int { + return syscall(56, flags, stack) +} + +fork() -> Int { + return syscall(57) +} + +execve(path Pointer, argv Pointer, envp Pointer) -> Int { + return syscall(59, path, argv, envp) +} + +exit(status Int) { + syscall(60, status) +} + +waitid(type Int, id Int, info Pointer, options Int) -> Int { + return syscall(247, type, id, info, options) +} \ No newline at end of file diff --git a/lib/sys/proc_mac.q b/lib/sys/proc_mac.q new file mode 100644 index 0000000..bf70e55 --- /dev/null +++ b/lib/sys/proc_mac.q @@ -0,0 +1,15 @@ +exit(status Int) { + syscall(0x2000001, status) +} + +fork() -> Int { + return syscall(0x2000002) +} + +execve(path Pointer, argv Pointer, envp Pointer) -> Int { + return syscall(0x200003B, path, argv, envp) +} + +waitid(type Int, id Int, info Pointer, options Int) -> Int { + return syscall(0x20000AD, type, id, info, options) +} \ No newline at end of file diff --git a/lib/sys/proc_windows.q b/lib/sys/proc_windows.q new file mode 100644 index 0000000..7819bca --- /dev/null +++ b/lib/sys/proc_windows.q @@ -0,0 +1,3 @@ +exit(code Int) { + kernel32.ExitProcess(code) +} \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q deleted file mode 100644 index 36fad2c..0000000 --- a/lib/sys/sys_linux.q +++ /dev/null @@ -1,71 +0,0 @@ -read(fd Int, address Pointer, length Int) -> Int { - return syscall(0, fd, address, length) -} - -write(fd Int, address Pointer, length Int) -> Int { - return syscall(1, fd, address, length) -} - -open(file Pointer, flags Int, mode Int) -> Int { - return syscall(2, file, flags, mode) -} - -close(fd Int) -> Int { - return syscall(3, fd) -} - -mmap(address Int, length Int, protection Int, flags Int) -> Pointer { - return syscall(9, address, length, protection, flags) -} - -munmap(address Pointer, length Int) -> Int { - return syscall(11, address, length) -} - -nanosleep(duration Pointer) -> Int { - return syscall(35, duration, 0) -} - -clone(flags Int, stack Pointer) -> Int { - return syscall(56, flags, stack) -} - -fork() -> Int { - return syscall(57) -} - -execve(path Pointer, argv Pointer, envp Pointer) -> Int { - return syscall(59, path, argv, envp) -} - -exit(status Int) { - syscall(60, status) -} - -getcwd(buffer Pointer, length Int) -> Int { - return syscall(79, buffer, length) -} - -chdir(path Pointer) -> Int { - return syscall(80, path) -} - -rename(old Pointer, new Pointer) -> Int { - return syscall(82, old, new) -} - -mkdir(path Pointer, mode Int) -> Int { - return syscall(83, path, mode) -} - -rmdir(path Pointer) -> Int { - return syscall(84, path) -} - -unlink(file Pointer) -> Int { - return syscall(87, file) -} - -waitid(type Int, id Int, info Pointer, options Int) -> Int { - return syscall(247, type, id, info, options) -} diff --git a/lib/sys/sys_mac.q b/lib/sys/sys_mac.q deleted file mode 100644 index 24fd36d..0000000 --- a/lib/sys/sys_mac.q +++ /dev/null @@ -1,39 +0,0 @@ -exit(status Int) { - syscall(0x2000001, status) -} - -fork() -> Int { - return syscall(0x2000002) -} - -read(fd Int, address Pointer, length Int) -> Int { - return syscall(0x2000003, fd, address, length) -} - -write(fd Int, address Pointer, length Int) -> Int { - return syscall(0x2000004, fd, address, length) -} - -open(file Pointer, flags Int, mode Int) -> Int { - return syscall(0x2000005, file, flags, mode) -} - -close(fd Int) -> Int { - return syscall(0x2000006, fd) -} - -mmap(address Int, length Int, protection Int, flags Int) -> Pointer { - return syscall(0x20000C5, address, length, protection, flags) -} - -munmap(address Pointer, length Int) -> Int { - return syscall(0x2000049, address, length) -} - -execve(path Pointer, argv Pointer, envp Pointer) -> Int { - return syscall(0x200003B, path, argv, envp) -} - -waitid(type Int, id Int, info Pointer, options Int) -> Int { - return syscall(0x20000AD, type, id, info, options) -} \ No newline at end of file diff --git a/lib/sys/time_linux.q b/lib/sys/time_linux.q new file mode 100644 index 0000000..6727476 --- /dev/null +++ b/lib/sys/time_linux.q @@ -0,0 +1,3 @@ +nanosleep(duration Pointer) -> Int { + return syscall(35, duration, 0) +} \ No newline at end of file From 463d993c28f67ebe40e62a1e9d324c76d1ebb4db Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Feb 2025 14:46:33 +0100 Subject: [PATCH 0659/1012] Added HTTP server example --- examples/server/server.q | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/server/server.q b/examples/server/server.q index b27d049..6b2eb0d 100644 --- a/examples/server/server.q +++ b/examples/server/server.q @@ -9,7 +9,7 @@ struct sockaddr_in { // Open server and client in 2 terminals: // [1] q run examples/server -// [2] netcat 127.0.0.1 8080 +// [2] curl http://127.0.0.1:8080 main() { socket := sys.socket(2, 1, 0) @@ -31,7 +31,7 @@ main() { delete(addr) - if sys.listen(socket, 10) != 0 { + if sys.listen(socket, 128) != 0 { sys.write(1, "listen error\n", 13) sys.exit(1) } @@ -41,9 +41,8 @@ main() { loop { conn := sys.accept(socket, 0, 0) - if conn != -1 { - sys.write(1, "accepted\n", 9) - sys.write(conn, "Hello\n", 6) + if conn >= 0 { + sys.write(conn, "HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nHello\n", 44) sys.close(conn) } else { sys.write(1, "error\n", 6) From 94a0e819201715464864f98dc29172b80f9c6319 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Feb 2025 14:46:33 +0100 Subject: [PATCH 0660/1012] Added HTTP server example --- examples/server/server.q | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/server/server.q b/examples/server/server.q index b27d049..6b2eb0d 100644 --- a/examples/server/server.q +++ b/examples/server/server.q @@ -9,7 +9,7 @@ struct sockaddr_in { // Open server and client in 2 terminals: // [1] q run examples/server -// [2] netcat 127.0.0.1 8080 +// [2] curl http://127.0.0.1:8080 main() { socket := sys.socket(2, 1, 0) @@ -31,7 +31,7 @@ main() { delete(addr) - if sys.listen(socket, 10) != 0 { + if sys.listen(socket, 128) != 0 { sys.write(1, "listen error\n", 13) sys.exit(1) } @@ -41,9 +41,8 @@ main() { loop { conn := sys.accept(socket, 0, 0) - if conn != -1 { - sys.write(1, "accepted\n", 9) - sys.write(conn, "Hello\n", 6) + if conn >= 0 { + sys.write(conn, "HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nHello\n", 44) sys.close(conn) } else { sys.write(1, "error\n", 6) From 5bbc730ed45ce749f18bba7cd0c349d29da3dbe4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Feb 2025 17:11:57 +0100 Subject: [PATCH 0661/1012] Improved server example --- examples/server/server.q | 20 ++++++++++---------- lib/sys/net_linux.q | 4 ++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/examples/server/server.q b/examples/server/server.q index 6b2eb0d..852ae50 100644 --- a/examples/server/server.q +++ b/examples/server/server.q @@ -1,15 +1,15 @@ -import sys - -struct sockaddr_in { - sin_family Int16 - sin_port Int16 - sin_addr Int64 - sin_zero Int64 -} - // Open server and client in 2 terminals: // [1] q run examples/server // [2] curl http://127.0.0.1:8080 +import sys + +struct sockaddr_in { + sin_family Int16 + sin_port Int16 + sin_addr Int64 + sin_zero Int64 +} + main() { socket := sys.socket(2, 1, 0) @@ -42,7 +42,7 @@ main() { conn := sys.accept(socket, 0, 0) if conn >= 0 { - sys.write(conn, "HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nHello\n", 44) + sys.write(conn, "HTTP/1.0 200 OK\r\nContent-Length: 6\r\n\r\nHello\n", 44) sys.close(conn) } else { sys.write(1, "error\n", 6) diff --git a/lib/sys/net_linux.q b/lib/sys/net_linux.q index 5368e47..00ed81c 100644 --- a/lib/sys/net_linux.q +++ b/lib/sys/net_linux.q @@ -12,4 +12,8 @@ bind(fd Int, address Pointer, length Int) -> Int { listen(fd Int, backlog Int) -> Int { return syscall(50, fd, backlog) +} + +setsockopt(fd Int, level Int, optname Int, optval Pointer, optlen Int) -> Int { + return syscall(54, fd, level, optname, optval, optlen) } \ No newline at end of file From cae3092df7600ff5e62789f9dae780af70fa16b1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Feb 2025 17:11:57 +0100 Subject: [PATCH 0662/1012] Improved server example --- examples/server/server.q | 20 ++++++++++---------- lib/sys/net_linux.q | 4 ++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/examples/server/server.q b/examples/server/server.q index 6b2eb0d..852ae50 100644 --- a/examples/server/server.q +++ b/examples/server/server.q @@ -1,15 +1,15 @@ -import sys - -struct sockaddr_in { - sin_family Int16 - sin_port Int16 - sin_addr Int64 - sin_zero Int64 -} - // Open server and client in 2 terminals: // [1] q run examples/server // [2] curl http://127.0.0.1:8080 +import sys + +struct sockaddr_in { + sin_family Int16 + sin_port Int16 + sin_addr Int64 + sin_zero Int64 +} + main() { socket := sys.socket(2, 1, 0) @@ -42,7 +42,7 @@ main() { conn := sys.accept(socket, 0, 0) if conn >= 0 { - sys.write(conn, "HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nHello\n", 44) + sys.write(conn, "HTTP/1.0 200 OK\r\nContent-Length: 6\r\n\r\nHello\n", 44) sys.close(conn) } else { sys.write(1, "error\n", 6) diff --git a/lib/sys/net_linux.q b/lib/sys/net_linux.q index 5368e47..00ed81c 100644 --- a/lib/sys/net_linux.q +++ b/lib/sys/net_linux.q @@ -12,4 +12,8 @@ bind(fd Int, address Pointer, length Int) -> Int { listen(fd Int, backlog Int) -> Int { return syscall(50, fd, backlog) +} + +setsockopt(fd Int, level Int, optname Int, optval Pointer, optlen Int) -> Int { + return syscall(54, fd, level, optname, optval, optlen) } \ No newline at end of file From 05f6a015dcd5f2f9b3dfafe9f18d5f85bf10cdd2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Feb 2025 23:26:10 +0100 Subject: [PATCH 0663/1012] Added asmc package --- src/asm/Finalize.go | 361 ------------------ src/asmc/Finalize.go | 32 ++ src/asmc/call.go | 31 ++ src/{asm/CodeOffset.go => asmc/codeOffset.go} | 6 +- src/asmc/compile.go | 162 ++++++++ src/asmc/compiler.go | 15 + src/asmc/dllCall.go | 41 ++ src/asmc/jump.go | 47 +++ src/asmc/move.go | 58 +++ src/{asm => asmc}/pointer.go | 2 +- src/{asm => asmc}/resolvePointers.go | 38 +- src/asmc/store.go | 53 +++ src/compiler/Result.go | 3 +- src/readme.md | 1 + 14 files changed, 464 insertions(+), 386 deletions(-) delete mode 100644 src/asm/Finalize.go create mode 100644 src/asmc/Finalize.go create mode 100644 src/asmc/call.go rename src/{asm/CodeOffset.go => asmc/codeOffset.go} (81%) create mode 100644 src/asmc/compile.go create mode 100644 src/asmc/compiler.go create mode 100644 src/asmc/dllCall.go create mode 100644 src/asmc/jump.go create mode 100644 src/asmc/move.go rename src/{asm => asmc}/pointer.go (96%) rename src/{asm => asmc}/resolvePointers.go (62%) create mode 100644 src/asmc/store.go diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go deleted file mode 100644 index 5ea281c..0000000 --- a/src/asm/Finalize.go +++ /dev/null @@ -1,361 +0,0 @@ -package asm - -import ( - "math" - "strings" - - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/x86" -) - -// Finalize generates the final machine code. -func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { - var ( - code = make([]byte, 0, len(a.Instructions)*8) - data []byte - codeLabels = map[string]Address{} - dataLabels map[string]Address - codePointers []*pointer - dataPointers []*pointer - dllPointers []*pointer - codeStart = CodeOffset() - ) - - for _, x := range a.Instructions { - switch x.Mnemonic { - case ADD: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.AddRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x86.AddRegisterRegister(code, operands.Destination, operands.Source) - } - - case AND: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.AndRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x86.AndRegisterRegister(code, operands.Destination, operands.Source) - } - - case SUB: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.SubRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x86.SubRegisterRegister(code, operands.Destination, operands.Source) - } - - case MUL: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.MulRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x86.MulRegisterRegister(code, operands.Destination, operands.Source) - } - - case DIV: - switch operands := x.Data.(type) { - case *RegisterRegister: - if operands.Destination != x86.RAX { - code = x86.MoveRegisterRegister(code, x86.RAX, operands.Destination) - } - - code = x86.ExtendRAXToRDX(code) - code = x86.DivRegister(code, operands.Source) - - if operands.Destination != x86.RAX { - code = x86.MoveRegisterRegister(code, operands.Destination, x86.RAX) - } - } - - case MODULO: - switch operands := x.Data.(type) { - case *RegisterRegister: - if operands.Destination != x86.RAX { - code = x86.MoveRegisterRegister(code, x86.RAX, operands.Destination) - } - - code = x86.ExtendRAXToRDX(code) - code = x86.DivRegister(code, operands.Source) - - if operands.Destination != x86.RDX { - code = x86.MoveRegisterRegister(code, operands.Destination, x86.RDX) - } - } - - case CALL: - code = x86.Call(code, 0x00_00_00_00) - size := 4 - label := x.Data.(*Label) - - pointer := &pointer{ - Position: Address(len(code) - size), - OpSize: 1, - Size: uint8(size), - } - - pointer.Resolve = func() Address { - destination, exists := codeLabels[label.Name] - - if !exists { - panic("unknown jump label") - } - - distance := destination - (pointer.Position + Address(pointer.Size)) - return Address(distance) - } - - codePointers = append(codePointers, pointer) - - case COMMENT: - continue - - case COMPARE: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.CompareRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x86.CompareRegisterRegister(code, operands.Destination, operands.Source) - } - - case DLLCALL: - size := 4 - // TODO: R15 could be in use. - code = x86.MoveRegisterRegister(code, x86.R15, x86.RSP) - code = x86.AlignStack(code) - code = x86.SubRegisterNumber(code, x86.RSP, 32) - code = x86.CallAtAddress(code, 0x00_00_00_00) - position := len(code) - size - code = x86.MoveRegisterRegister(code, x86.RSP, x86.R15) - - label := x.Data.(*Label) - pointer := &pointer{ - Position: Address(position), - OpSize: 2, - Size: uint8(size), - } - - pointer.Resolve = func() Address { - dot := strings.Index(label.Name, ".") - library := label.Name[:dot] - funcName := label.Name[dot+1:] - index := dlls.Index(library, funcName) - - if index == -1 { - panic("unknown DLL function " + label.Name) - } - - return Address(index * 8) - } - - dllPointers = append(dllPointers, pointer) - - case JE, JNE, JG, JGE, JL, JLE, JUMP: - switch x.Mnemonic { - case JE: - code = x86.Jump8IfEqual(code, 0x00) - case JNE: - code = x86.Jump8IfNotEqual(code, 0x00) - case JG: - code = x86.Jump8IfGreater(code, 0x00) - case JGE: - code = x86.Jump8IfGreaterOrEqual(code, 0x00) - case JL: - code = x86.Jump8IfLess(code, 0x00) - case JLE: - code = x86.Jump8IfLessOrEqual(code, 0x00) - case JUMP: - code = x86.Jump8(code, 0x00) - } - - size := 1 - label := x.Data.(*Label) - - pointer := &pointer{ - Position: Address(len(code) - size), - OpSize: 1, - Size: uint8(size), - } - - pointer.Resolve = func() Address { - destination, exists := codeLabels[label.Name] - - if !exists { - panic("unknown jump label") - } - - distance := destination - (pointer.Position + Address(pointer.Size)) - return Address(distance) - } - - codePointers = append(codePointers, pointer) - - case LABEL: - codeLabels[x.Data.(*Label).Name] = Address(len(code)) - - case LOAD: - switch operands := x.Data.(type) { - case *MemoryRegister: - code = x86.LoadRegister(code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) - } - - case MOVE: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.MoveRegisterNumber(code, operands.Register, operands.Number) - - case *RegisterRegister: - code = x86.MoveRegisterRegister(code, operands.Destination, operands.Source) - - case *RegisterLabel: - start := len(code) - code = x86.MoveRegisterNumber(code, operands.Register, 0x00_00_00_00) - size := 4 - opSize := len(code) - size - start - regLabel := x.Data.(*RegisterLabel) - - if strings.HasPrefix(regLabel.Label, "data_") { - dataPointers = append(dataPointers, &pointer{ - Position: Address(len(code) - size), - OpSize: uint8(opSize), - Size: uint8(size), - Resolve: func() Address { - destination, exists := dataLabels[regLabel.Label] - - if !exists { - panic("unknown label") - } - - return Address(destination) - }, - }) - } else { - codePointers = append(codePointers, &pointer{ - Position: Address(len(code) - size), - OpSize: uint8(opSize), - Size: uint8(size), - Resolve: func() Address { - destination, exists := codeLabels[regLabel.Label] - - if !exists { - panic("unknown label") - } - - return config.BaseAddress + codeStart + destination - }, - }) - } - } - - case NEGATE: - switch operands := x.Data.(type) { - case *Register: - code = x86.NegateRegister(code, operands.Register) - } - - case OR: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.OrRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x86.OrRegisterRegister(code, operands.Destination, operands.Source) - } - - case POP: - switch operands := x.Data.(type) { - case *Register: - code = x86.PopRegister(code, operands.Register) - } - - case PUSH: - switch operands := x.Data.(type) { - case *Register: - code = x86.PushRegister(code, operands.Register) - } - - case RETURN: - code = x86.Return(code) - - case SHIFTL: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.ShiftLeftNumber(code, operands.Register, byte(operands.Number)&0b111111) - } - - case SHIFTRS: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.ShiftRightSignedNumber(code, operands.Register, byte(operands.Number)&0b111111) - } - - case STORE: - switch operands := x.Data.(type) { - case *MemoryNumber: - if operands.Address.OffsetRegister == math.MaxUint8 { - code = x86.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) - } else { - code = x86.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) - } - case *MemoryLabel: - start := len(code) - - if operands.Address.OffsetRegister == math.MaxUint8 { - code = x86.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, 0b00_00_00_00) - } else { - code = x86.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, 0b00_00_00_00) - } - - size := 4 - opSize := len(code) - size - start - memLabel := x.Data.(*MemoryLabel) - - codePointers = append(codePointers, &pointer{ - Position: Address(len(code) - size), - OpSize: uint8(opSize), - Size: uint8(size), - Resolve: func() Address { - destination, exists := codeLabels[memLabel.Label] - - if !exists { - panic("unknown label") - } - - return config.BaseAddress + codeStart + destination - }, - }) - case *MemoryRegister: - if operands.Address.OffsetRegister == math.MaxUint8 { - code = x86.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) - } else { - code = x86.StoreDynamicRegister(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) - } - } - - case SYSCALL: - code = x86.Syscall(code) - - case XOR: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.XorRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x86.XorRegisterRegister(code, operands.Destination, operands.Source) - } - - default: - panic("unknown mnemonic: " + x.Mnemonic.String()) - } - } - - data, dataLabels = a.Data.Finalize() - - if config.TargetOS == config.Windows && len(data) == 0 { - data = []byte{0} - } - - code = a.resolvePointers(code, data, codeStart, codeLabels, codePointers, dataPointers, dllPointers) - return code, data -} diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go new file mode 100644 index 0000000..ff47fb3 --- /dev/null +++ b/src/asmc/Finalize.go @@ -0,0 +1,32 @@ +package asmc + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/dll" +) + +// Finalize generates the final machine code. +func Finalize(a asm.Assembler, dlls dll.List) ([]byte, []byte) { + data, dataLabels := a.Data.Finalize() + + if config.TargetOS == config.Windows && len(data) == 0 { + data = []byte{0} + } + + c := compiler{ + code: make([]byte, 0, len(a.Instructions)*8), + codeLabels: map[string]Address{}, + codeStart: codeOffset(), + data: data, + dataLabels: dataLabels, + dlls: dlls, + } + + for _, x := range a.Instructions { + c.compile(x) + } + + c.resolvePointers() + return c.code, c.data +} diff --git a/src/asmc/call.go b/src/asmc/call.go new file mode 100644 index 0000000..940c0da --- /dev/null +++ b/src/asmc/call.go @@ -0,0 +1,31 @@ +package asmc + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/x86" +) + +func (c *compiler) call(x asm.Instruction) { + c.code = x86.Call(c.code, 0x00_00_00_00) + size := 4 + label := x.Data.(*asm.Label) + + pointer := &pointer{ + Position: Address(len(c.code) - size), + OpSize: 1, + Size: uint8(size), + } + + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[label.Name] + + if !exists { + panic("unknown jump label") + } + + distance := destination - (pointer.Position + Address(pointer.Size)) + return Address(distance) + } + + c.codePointers = append(c.codePointers, pointer) +} diff --git a/src/asm/CodeOffset.go b/src/asmc/codeOffset.go similarity index 81% rename from src/asm/CodeOffset.go rename to src/asmc/codeOffset.go index 4b8d4ba..306adc0 100644 --- a/src/asm/CodeOffset.go +++ b/src/asmc/codeOffset.go @@ -1,4 +1,4 @@ -package asm +package asmc import ( "git.akyoto.dev/cli/q/src/config" @@ -8,8 +8,8 @@ import ( "git.akyoto.dev/cli/q/src/pe" ) -// CodeOffset returns the file offset of the code section. -func CodeOffset() Address { +// codeOffset returns the file offset of the code section. +func codeOffset() Address { headerEnd := Address(0) switch config.TargetOS { diff --git a/src/asmc/compile.go b/src/asmc/compile.go new file mode 100644 index 0000000..e671884 --- /dev/null +++ b/src/asmc/compile.go @@ -0,0 +1,162 @@ +package asmc + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/x86" +) + +func (c *compiler) compile(x asm.Instruction) { + switch x.Mnemonic { + case asm.ADD: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.AddRegisterNumber(c.code, operands.Register, operands.Number) + case *asm.RegisterRegister: + c.code = x86.AddRegisterRegister(c.code, operands.Destination, operands.Source) + } + + case asm.AND: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.AndRegisterNumber(c.code, operands.Register, operands.Number) + case *asm.RegisterRegister: + c.code = x86.AndRegisterRegister(c.code, operands.Destination, operands.Source) + } + + case asm.SUB: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.SubRegisterNumber(c.code, operands.Register, operands.Number) + case *asm.RegisterRegister: + c.code = x86.SubRegisterRegister(c.code, operands.Destination, operands.Source) + } + + case asm.MUL: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.MulRegisterNumber(c.code, operands.Register, operands.Number) + case *asm.RegisterRegister: + c.code = x86.MulRegisterRegister(c.code, operands.Destination, operands.Source) + } + + case asm.DIV: + switch operands := x.Data.(type) { + case *asm.RegisterRegister: + if operands.Destination != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) + } + + c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.DivRegister(c.code, operands.Source) + + if operands.Destination != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.RAX) + } + } + + case asm.MODULO: + switch operands := x.Data.(type) { + case *asm.RegisterRegister: + if operands.Destination != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) + } + + c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.DivRegister(c.code, operands.Source) + + if operands.Destination != x86.RDX { + c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.RDX) + } + } + + case asm.CALL: + c.call(x) + + case asm.COMMENT: + return + + case asm.COMPARE: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.CompareRegisterNumber(c.code, operands.Register, operands.Number) + case *asm.RegisterRegister: + c.code = x86.CompareRegisterRegister(c.code, operands.Destination, operands.Source) + } + + case asm.DLLCALL: + c.dllCall(x) + + case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: + c.jump(x) + + case asm.LABEL: + c.codeLabels[x.Data.(*asm.Label).Name] = Address(len(c.code)) + + case asm.LOAD: + switch operands := x.Data.(type) { + case *asm.MemoryRegister: + c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) + } + + case asm.MOVE: + c.move(x) + + case asm.NEGATE: + switch operands := x.Data.(type) { + case *asm.Register: + c.code = x86.NegateRegister(c.code, operands.Register) + } + + case asm.OR: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.OrRegisterNumber(c.code, operands.Register, operands.Number) + case *asm.RegisterRegister: + c.code = x86.OrRegisterRegister(c.code, operands.Destination, operands.Source) + } + + case asm.POP: + switch operands := x.Data.(type) { + case *asm.Register: + c.code = x86.PopRegister(c.code, operands.Register) + } + + case asm.PUSH: + switch operands := x.Data.(type) { + case *asm.Register: + c.code = x86.PushRegister(c.code, operands.Register) + } + + case asm.RETURN: + c.code = x86.Return(c.code) + + case asm.SHIFTL: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.ShiftLeftNumber(c.code, operands.Register, byte(operands.Number)&0b111111) + } + + case asm.SHIFTRS: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.ShiftRightSignedNumber(c.code, operands.Register, byte(operands.Number)&0b111111) + } + + case asm.STORE: + c.store(x) + + case asm.SYSCALL: + c.code = x86.Syscall(c.code) + + case asm.XOR: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.XorRegisterNumber(c.code, operands.Register, operands.Number) + case *asm.RegisterRegister: + c.code = x86.XorRegisterRegister(c.code, operands.Destination, operands.Source) + } + + default: + panic("unknown mnemonic: " + x.Mnemonic.String()) + } +} diff --git a/src/asmc/compiler.go b/src/asmc/compiler.go new file mode 100644 index 0000000..0847707 --- /dev/null +++ b/src/asmc/compiler.go @@ -0,0 +1,15 @@ +package asmc + +import "git.akyoto.dev/cli/q/src/dll" + +type compiler struct { + code []byte + data []byte + codeLabels map[string]Address + dataLabels map[string]Address + codePointers []*pointer + dataPointers []*pointer + codeStart Address + dlls dll.List + dllPointers []*pointer +} diff --git a/src/asmc/dllCall.go b/src/asmc/dllCall.go new file mode 100644 index 0000000..8c9856e --- /dev/null +++ b/src/asmc/dllCall.go @@ -0,0 +1,41 @@ +package asmc + +import ( + "strings" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/x86" +) + +func (c *compiler) dllCall(x asm.Instruction) { + size := 4 + // TODO: R15 could be in use. + c.code = x86.MoveRegisterRegister(c.code, x86.R15, x86.RSP) + c.code = x86.AlignStack(c.code) + c.code = x86.SubRegisterNumber(c.code, x86.RSP, 32) + c.code = x86.CallAtAddress(c.code, 0x00_00_00_00) + position := len(c.code) - size + c.code = x86.MoveRegisterRegister(c.code, x86.RSP, x86.R15) + + label := x.Data.(*asm.Label) + pointer := &pointer{ + Position: Address(position), + OpSize: 2, + Size: uint8(size), + } + + pointer.Resolve = func() Address { + dot := strings.Index(label.Name, ".") + library := label.Name[:dot] + funcName := label.Name[dot+1:] + index := c.dlls.Index(library, funcName) + + if index == -1 { + panic("unknown DLL function " + label.Name) + } + + return Address(index * 8) + } + + c.dllPointers = append(c.dllPointers, pointer) +} diff --git a/src/asmc/jump.go b/src/asmc/jump.go new file mode 100644 index 0000000..e6d8f65 --- /dev/null +++ b/src/asmc/jump.go @@ -0,0 +1,47 @@ +package asmc + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/x86" +) + +func (c *compiler) jump(x asm.Instruction) { + switch x.Mnemonic { + case asm.JE: + c.code = x86.Jump8IfEqual(c.code, 0x00) + case asm.JNE: + c.code = x86.Jump8IfNotEqual(c.code, 0x00) + case asm.JG: + c.code = x86.Jump8IfGreater(c.code, 0x00) + case asm.JGE: + c.code = x86.Jump8IfGreaterOrEqual(c.code, 0x00) + case asm.JL: + c.code = x86.Jump8IfLess(c.code, 0x00) + case asm.JLE: + c.code = x86.Jump8IfLessOrEqual(c.code, 0x00) + case asm.JUMP: + c.code = x86.Jump8(c.code, 0x00) + } + + size := 1 + label := x.Data.(*asm.Label) + + pointer := &pointer{ + Position: Address(len(c.code) - size), + OpSize: 1, + Size: uint8(size), + } + + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[label.Name] + + if !exists { + panic("unknown jump label") + } + + distance := destination - (pointer.Position + Address(pointer.Size)) + return Address(distance) + } + + c.codePointers = append(c.codePointers, pointer) +} diff --git a/src/asmc/move.go b/src/asmc/move.go new file mode 100644 index 0000000..8d5a12d --- /dev/null +++ b/src/asmc/move.go @@ -0,0 +1,58 @@ +package asmc + +import ( + "strings" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/x86" +) + +func (c *compiler) move(x asm.Instruction) { + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.MoveRegisterNumber(c.code, operands.Register, operands.Number) + + case *asm.RegisterRegister: + c.code = x86.MoveRegisterRegister(c.code, operands.Destination, operands.Source) + + case *asm.RegisterLabel: + start := len(c.code) + c.code = x86.MoveRegisterNumber(c.code, operands.Register, 0x00_00_00_00) + size := 4 + opSize := len(c.code) - size - start + regLabel := x.Data.(*asm.RegisterLabel) + + if strings.HasPrefix(regLabel.Label, "data_") { + c.dataPointers = append(c.dataPointers, &pointer{ + Position: Address(len(c.code) - size), + OpSize: uint8(opSize), + Size: uint8(size), + Resolve: func() Address { + destination, exists := c.dataLabels[regLabel.Label] + + if !exists { + panic("unknown label") + } + + return Address(destination) + }, + }) + } else { + c.codePointers = append(c.codePointers, &pointer{ + Position: Address(len(c.code) - size), + OpSize: uint8(opSize), + Size: uint8(size), + Resolve: func() Address { + destination, exists := c.codeLabels[regLabel.Label] + + if !exists { + panic("unknown label") + } + + return config.BaseAddress + c.codeStart + destination + }, + }) + } + } +} diff --git a/src/asm/pointer.go b/src/asmc/pointer.go similarity index 96% rename from src/asm/pointer.go rename to src/asmc/pointer.go index 1d4880b..f299758 100644 --- a/src/asm/pointer.go +++ b/src/asmc/pointer.go @@ -1,4 +1,4 @@ -package asm +package asmc // Address represents a memory address. type Address = int32 diff --git a/src/asm/resolvePointers.go b/src/asmc/resolvePointers.go similarity index 62% rename from src/asm/resolvePointers.go rename to src/asmc/resolvePointers.go index 40ecbb9..a437555 100644 --- a/src/asm/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -1,4 +1,4 @@ -package asm +package asmc import ( "encoding/binary" @@ -11,16 +11,16 @@ import ( ) // resolvePointers resolves the addresses of all pointers within the code and writes the correct addresses to the code slice. -func (a Assembler) resolvePointers(code []byte, data []byte, codeStart Address, codeLabels map[string]Address, codePointers []*pointer, dataPointers []*pointer, dllPointers []*pointer) []byte { +func (c *compiler) resolvePointers() { restart: - for i, pointer := range codePointers { + for i, pointer := range c.codePointers { address := pointer.Resolve() if sizeof.Signed(int64(address)) > int(pointer.Size) { - left := code[:pointer.Position-Address(pointer.OpSize)] - right := code[pointer.Position+Address(pointer.Size):] + left := c.code[:pointer.Position-Address(pointer.OpSize)] + right := c.code[pointer.Position+Address(pointer.Size):] size := pointer.Size + pointer.OpSize - opCode := code[pointer.Position-Address(pointer.OpSize)] + opCode := c.code[pointer.Position-Address(pointer.OpSize)] var jump []byte @@ -49,21 +49,21 @@ restart: jump = binary.LittleEndian.AppendUint32(jump, uint32(address)) offset := Address(len(jump)) - Address(size) - for _, following := range codePointers[i+1:] { + for _, following := range c.codePointers[i+1:] { following.Position += offset } - for key, address := range codeLabels { + for key, address := range c.codeLabels { if address > pointer.Position { - codeLabels[key] += offset + c.codeLabels[key] += offset } } - code = slices.Concat(left, jump, right) + c.code = slices.Concat(left, jump, right) goto restart } - slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] + slice := c.code[pointer.Position : pointer.Position+Address(pointer.Size)] switch pointer.Size { case 1: @@ -77,24 +77,22 @@ restart: } } - dataStart, _ := fs.Align(codeStart+Address(len(code)), config.Align) + dataStart, _ := fs.Align(c.codeStart+Address(len(c.code)), config.Align) - for _, pointer := range dataPointers { + for _, pointer := range c.dataPointers { address := config.BaseAddress + Address(dataStart) + pointer.Resolve() - slice := code[pointer.Position : pointer.Position+4] + slice := c.code[pointer.Position : pointer.Position+4] binary.LittleEndian.PutUint32(slice, uint32(address)) } if config.TargetOS == config.Windows { - importsStart, _ := fs.Align(dataStart+Address(len(data)), config.Align) + importsStart, _ := fs.Align(dataStart+Address(len(c.data)), config.Align) - for _, pointer := range dllPointers { + for _, pointer := range c.dllPointers { destination := Address(importsStart) + pointer.Resolve() - delta := destination - Address(codeStart+pointer.Position+Address(pointer.Size)) - slice := code[pointer.Position : pointer.Position+4] + delta := destination - Address(c.codeStart+pointer.Position+Address(pointer.Size)) + slice := c.code[pointer.Position : pointer.Position+4] binary.LittleEndian.PutUint32(slice, uint32(delta)) } } - - return code } diff --git a/src/asmc/store.go b/src/asmc/store.go new file mode 100644 index 0000000..b5e8f0d --- /dev/null +++ b/src/asmc/store.go @@ -0,0 +1,53 @@ +package asmc + +import ( + "math" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/x86" +) + +func (c *compiler) store(x asm.Instruction) { + switch operands := x.Data.(type) { + case *asm.MemoryNumber: + if operands.Address.OffsetRegister == math.MaxUint8 { + c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) + } else { + c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) + } + case *asm.MemoryLabel: + start := len(c.code) + + if operands.Address.OffsetRegister == math.MaxUint8 { + c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, 0b00_00_00_00) + } else { + c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, 0b00_00_00_00) + } + + size := 4 + opSize := len(c.code) - size - start + memLabel := x.Data.(*asm.MemoryLabel) + + c.codePointers = append(c.codePointers, &pointer{ + Position: Address(len(c.code) - size), + OpSize: uint8(opSize), + Size: uint8(size), + Resolve: func() Address { + destination, exists := c.codeLabels[memLabel.Label] + + if !exists { + panic("unknown label") + } + + return config.BaseAddress + c.codeStart + destination + }, + }) + case *asm.MemoryRegister: + if operands.Address.OffsetRegister == math.MaxUint8 { + c.code = x86.StoreRegister(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) + } else { + c.code = x86.StoreDynamicRegister(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) + } + } +} diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 6d01851..0788a36 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -7,6 +7,7 @@ import ( "os" "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/asmc" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/dll" @@ -87,7 +88,7 @@ func (r *Result) finalize() { final.DLLCall("kernel32.ExitProcess") } - r.Code, r.Data = final.Finalize(r.DLLs) + r.Code, r.Data = asmc.Finalize(final, r.DLLs) } // eachFunction recursively finds all the calls to external functions. diff --git a/src/readme.md b/src/readme.md index 4ee22a5..c793954 100644 --- a/src/readme.md +++ b/src/readme.md @@ -2,6 +2,7 @@ - [arm64](arm64) - ARM64 implementation (w.i.p.) - [asm](asm) - Pseudo-assembler stage +- [asmc](asmc) - Compiles assembler to actual machine code - [ast](ast) - Abstract syntax tree generation with the `Parse` function - [build](build) - Build command - [cli](cli) - Command line interface From 78aee7999b1308a011a1624ac8d4c2b8ada90c60 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Feb 2025 23:26:10 +0100 Subject: [PATCH 0664/1012] Added asmc package --- src/asm/Finalize.go | 361 ------------------ src/asmc/Finalize.go | 32 ++ src/asmc/call.go | 31 ++ src/{asm/CodeOffset.go => asmc/codeOffset.go} | 6 +- src/asmc/compile.go | 162 ++++++++ src/asmc/compiler.go | 15 + src/asmc/dllCall.go | 41 ++ src/asmc/jump.go | 47 +++ src/asmc/move.go | 58 +++ src/{asm => asmc}/pointer.go | 2 +- src/{asm => asmc}/resolvePointers.go | 38 +- src/asmc/store.go | 53 +++ src/compiler/Result.go | 3 +- src/readme.md | 1 + 14 files changed, 464 insertions(+), 386 deletions(-) delete mode 100644 src/asm/Finalize.go create mode 100644 src/asmc/Finalize.go create mode 100644 src/asmc/call.go rename src/{asm/CodeOffset.go => asmc/codeOffset.go} (81%) create mode 100644 src/asmc/compile.go create mode 100644 src/asmc/compiler.go create mode 100644 src/asmc/dllCall.go create mode 100644 src/asmc/jump.go create mode 100644 src/asmc/move.go rename src/{asm => asmc}/pointer.go (96%) rename src/{asm => asmc}/resolvePointers.go (62%) create mode 100644 src/asmc/store.go diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go deleted file mode 100644 index 5ea281c..0000000 --- a/src/asm/Finalize.go +++ /dev/null @@ -1,361 +0,0 @@ -package asm - -import ( - "math" - "strings" - - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/x86" -) - -// Finalize generates the final machine code. -func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { - var ( - code = make([]byte, 0, len(a.Instructions)*8) - data []byte - codeLabels = map[string]Address{} - dataLabels map[string]Address - codePointers []*pointer - dataPointers []*pointer - dllPointers []*pointer - codeStart = CodeOffset() - ) - - for _, x := range a.Instructions { - switch x.Mnemonic { - case ADD: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.AddRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x86.AddRegisterRegister(code, operands.Destination, operands.Source) - } - - case AND: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.AndRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x86.AndRegisterRegister(code, operands.Destination, operands.Source) - } - - case SUB: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.SubRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x86.SubRegisterRegister(code, operands.Destination, operands.Source) - } - - case MUL: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.MulRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x86.MulRegisterRegister(code, operands.Destination, operands.Source) - } - - case DIV: - switch operands := x.Data.(type) { - case *RegisterRegister: - if operands.Destination != x86.RAX { - code = x86.MoveRegisterRegister(code, x86.RAX, operands.Destination) - } - - code = x86.ExtendRAXToRDX(code) - code = x86.DivRegister(code, operands.Source) - - if operands.Destination != x86.RAX { - code = x86.MoveRegisterRegister(code, operands.Destination, x86.RAX) - } - } - - case MODULO: - switch operands := x.Data.(type) { - case *RegisterRegister: - if operands.Destination != x86.RAX { - code = x86.MoveRegisterRegister(code, x86.RAX, operands.Destination) - } - - code = x86.ExtendRAXToRDX(code) - code = x86.DivRegister(code, operands.Source) - - if operands.Destination != x86.RDX { - code = x86.MoveRegisterRegister(code, operands.Destination, x86.RDX) - } - } - - case CALL: - code = x86.Call(code, 0x00_00_00_00) - size := 4 - label := x.Data.(*Label) - - pointer := &pointer{ - Position: Address(len(code) - size), - OpSize: 1, - Size: uint8(size), - } - - pointer.Resolve = func() Address { - destination, exists := codeLabels[label.Name] - - if !exists { - panic("unknown jump label") - } - - distance := destination - (pointer.Position + Address(pointer.Size)) - return Address(distance) - } - - codePointers = append(codePointers, pointer) - - case COMMENT: - continue - - case COMPARE: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.CompareRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x86.CompareRegisterRegister(code, operands.Destination, operands.Source) - } - - case DLLCALL: - size := 4 - // TODO: R15 could be in use. - code = x86.MoveRegisterRegister(code, x86.R15, x86.RSP) - code = x86.AlignStack(code) - code = x86.SubRegisterNumber(code, x86.RSP, 32) - code = x86.CallAtAddress(code, 0x00_00_00_00) - position := len(code) - size - code = x86.MoveRegisterRegister(code, x86.RSP, x86.R15) - - label := x.Data.(*Label) - pointer := &pointer{ - Position: Address(position), - OpSize: 2, - Size: uint8(size), - } - - pointer.Resolve = func() Address { - dot := strings.Index(label.Name, ".") - library := label.Name[:dot] - funcName := label.Name[dot+1:] - index := dlls.Index(library, funcName) - - if index == -1 { - panic("unknown DLL function " + label.Name) - } - - return Address(index * 8) - } - - dllPointers = append(dllPointers, pointer) - - case JE, JNE, JG, JGE, JL, JLE, JUMP: - switch x.Mnemonic { - case JE: - code = x86.Jump8IfEqual(code, 0x00) - case JNE: - code = x86.Jump8IfNotEqual(code, 0x00) - case JG: - code = x86.Jump8IfGreater(code, 0x00) - case JGE: - code = x86.Jump8IfGreaterOrEqual(code, 0x00) - case JL: - code = x86.Jump8IfLess(code, 0x00) - case JLE: - code = x86.Jump8IfLessOrEqual(code, 0x00) - case JUMP: - code = x86.Jump8(code, 0x00) - } - - size := 1 - label := x.Data.(*Label) - - pointer := &pointer{ - Position: Address(len(code) - size), - OpSize: 1, - Size: uint8(size), - } - - pointer.Resolve = func() Address { - destination, exists := codeLabels[label.Name] - - if !exists { - panic("unknown jump label") - } - - distance := destination - (pointer.Position + Address(pointer.Size)) - return Address(distance) - } - - codePointers = append(codePointers, pointer) - - case LABEL: - codeLabels[x.Data.(*Label).Name] = Address(len(code)) - - case LOAD: - switch operands := x.Data.(type) { - case *MemoryRegister: - code = x86.LoadRegister(code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) - } - - case MOVE: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.MoveRegisterNumber(code, operands.Register, operands.Number) - - case *RegisterRegister: - code = x86.MoveRegisterRegister(code, operands.Destination, operands.Source) - - case *RegisterLabel: - start := len(code) - code = x86.MoveRegisterNumber(code, operands.Register, 0x00_00_00_00) - size := 4 - opSize := len(code) - size - start - regLabel := x.Data.(*RegisterLabel) - - if strings.HasPrefix(regLabel.Label, "data_") { - dataPointers = append(dataPointers, &pointer{ - Position: Address(len(code) - size), - OpSize: uint8(opSize), - Size: uint8(size), - Resolve: func() Address { - destination, exists := dataLabels[regLabel.Label] - - if !exists { - panic("unknown label") - } - - return Address(destination) - }, - }) - } else { - codePointers = append(codePointers, &pointer{ - Position: Address(len(code) - size), - OpSize: uint8(opSize), - Size: uint8(size), - Resolve: func() Address { - destination, exists := codeLabels[regLabel.Label] - - if !exists { - panic("unknown label") - } - - return config.BaseAddress + codeStart + destination - }, - }) - } - } - - case NEGATE: - switch operands := x.Data.(type) { - case *Register: - code = x86.NegateRegister(code, operands.Register) - } - - case OR: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.OrRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x86.OrRegisterRegister(code, operands.Destination, operands.Source) - } - - case POP: - switch operands := x.Data.(type) { - case *Register: - code = x86.PopRegister(code, operands.Register) - } - - case PUSH: - switch operands := x.Data.(type) { - case *Register: - code = x86.PushRegister(code, operands.Register) - } - - case RETURN: - code = x86.Return(code) - - case SHIFTL: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.ShiftLeftNumber(code, operands.Register, byte(operands.Number)&0b111111) - } - - case SHIFTRS: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.ShiftRightSignedNumber(code, operands.Register, byte(operands.Number)&0b111111) - } - - case STORE: - switch operands := x.Data.(type) { - case *MemoryNumber: - if operands.Address.OffsetRegister == math.MaxUint8 { - code = x86.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) - } else { - code = x86.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) - } - case *MemoryLabel: - start := len(code) - - if operands.Address.OffsetRegister == math.MaxUint8 { - code = x86.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, 0b00_00_00_00) - } else { - code = x86.StoreDynamicNumber(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, 0b00_00_00_00) - } - - size := 4 - opSize := len(code) - size - start - memLabel := x.Data.(*MemoryLabel) - - codePointers = append(codePointers, &pointer{ - Position: Address(len(code) - size), - OpSize: uint8(opSize), - Size: uint8(size), - Resolve: func() Address { - destination, exists := codeLabels[memLabel.Label] - - if !exists { - panic("unknown label") - } - - return config.BaseAddress + codeStart + destination - }, - }) - case *MemoryRegister: - if operands.Address.OffsetRegister == math.MaxUint8 { - code = x86.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) - } else { - code = x86.StoreDynamicRegister(code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) - } - } - - case SYSCALL: - code = x86.Syscall(code) - - case XOR: - switch operands := x.Data.(type) { - case *RegisterNumber: - code = x86.XorRegisterNumber(code, operands.Register, operands.Number) - case *RegisterRegister: - code = x86.XorRegisterRegister(code, operands.Destination, operands.Source) - } - - default: - panic("unknown mnemonic: " + x.Mnemonic.String()) - } - } - - data, dataLabels = a.Data.Finalize() - - if config.TargetOS == config.Windows && len(data) == 0 { - data = []byte{0} - } - - code = a.resolvePointers(code, data, codeStart, codeLabels, codePointers, dataPointers, dllPointers) - return code, data -} diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go new file mode 100644 index 0000000..ff47fb3 --- /dev/null +++ b/src/asmc/Finalize.go @@ -0,0 +1,32 @@ +package asmc + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/dll" +) + +// Finalize generates the final machine code. +func Finalize(a asm.Assembler, dlls dll.List) ([]byte, []byte) { + data, dataLabels := a.Data.Finalize() + + if config.TargetOS == config.Windows && len(data) == 0 { + data = []byte{0} + } + + c := compiler{ + code: make([]byte, 0, len(a.Instructions)*8), + codeLabels: map[string]Address{}, + codeStart: codeOffset(), + data: data, + dataLabels: dataLabels, + dlls: dlls, + } + + for _, x := range a.Instructions { + c.compile(x) + } + + c.resolvePointers() + return c.code, c.data +} diff --git a/src/asmc/call.go b/src/asmc/call.go new file mode 100644 index 0000000..940c0da --- /dev/null +++ b/src/asmc/call.go @@ -0,0 +1,31 @@ +package asmc + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/x86" +) + +func (c *compiler) call(x asm.Instruction) { + c.code = x86.Call(c.code, 0x00_00_00_00) + size := 4 + label := x.Data.(*asm.Label) + + pointer := &pointer{ + Position: Address(len(c.code) - size), + OpSize: 1, + Size: uint8(size), + } + + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[label.Name] + + if !exists { + panic("unknown jump label") + } + + distance := destination - (pointer.Position + Address(pointer.Size)) + return Address(distance) + } + + c.codePointers = append(c.codePointers, pointer) +} diff --git a/src/asm/CodeOffset.go b/src/asmc/codeOffset.go similarity index 81% rename from src/asm/CodeOffset.go rename to src/asmc/codeOffset.go index 4b8d4ba..306adc0 100644 --- a/src/asm/CodeOffset.go +++ b/src/asmc/codeOffset.go @@ -1,4 +1,4 @@ -package asm +package asmc import ( "git.akyoto.dev/cli/q/src/config" @@ -8,8 +8,8 @@ import ( "git.akyoto.dev/cli/q/src/pe" ) -// CodeOffset returns the file offset of the code section. -func CodeOffset() Address { +// codeOffset returns the file offset of the code section. +func codeOffset() Address { headerEnd := Address(0) switch config.TargetOS { diff --git a/src/asmc/compile.go b/src/asmc/compile.go new file mode 100644 index 0000000..e671884 --- /dev/null +++ b/src/asmc/compile.go @@ -0,0 +1,162 @@ +package asmc + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/x86" +) + +func (c *compiler) compile(x asm.Instruction) { + switch x.Mnemonic { + case asm.ADD: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.AddRegisterNumber(c.code, operands.Register, operands.Number) + case *asm.RegisterRegister: + c.code = x86.AddRegisterRegister(c.code, operands.Destination, operands.Source) + } + + case asm.AND: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.AndRegisterNumber(c.code, operands.Register, operands.Number) + case *asm.RegisterRegister: + c.code = x86.AndRegisterRegister(c.code, operands.Destination, operands.Source) + } + + case asm.SUB: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.SubRegisterNumber(c.code, operands.Register, operands.Number) + case *asm.RegisterRegister: + c.code = x86.SubRegisterRegister(c.code, operands.Destination, operands.Source) + } + + case asm.MUL: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.MulRegisterNumber(c.code, operands.Register, operands.Number) + case *asm.RegisterRegister: + c.code = x86.MulRegisterRegister(c.code, operands.Destination, operands.Source) + } + + case asm.DIV: + switch operands := x.Data.(type) { + case *asm.RegisterRegister: + if operands.Destination != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) + } + + c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.DivRegister(c.code, operands.Source) + + if operands.Destination != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.RAX) + } + } + + case asm.MODULO: + switch operands := x.Data.(type) { + case *asm.RegisterRegister: + if operands.Destination != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) + } + + c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.DivRegister(c.code, operands.Source) + + if operands.Destination != x86.RDX { + c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.RDX) + } + } + + case asm.CALL: + c.call(x) + + case asm.COMMENT: + return + + case asm.COMPARE: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.CompareRegisterNumber(c.code, operands.Register, operands.Number) + case *asm.RegisterRegister: + c.code = x86.CompareRegisterRegister(c.code, operands.Destination, operands.Source) + } + + case asm.DLLCALL: + c.dllCall(x) + + case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: + c.jump(x) + + case asm.LABEL: + c.codeLabels[x.Data.(*asm.Label).Name] = Address(len(c.code)) + + case asm.LOAD: + switch operands := x.Data.(type) { + case *asm.MemoryRegister: + c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) + } + + case asm.MOVE: + c.move(x) + + case asm.NEGATE: + switch operands := x.Data.(type) { + case *asm.Register: + c.code = x86.NegateRegister(c.code, operands.Register) + } + + case asm.OR: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.OrRegisterNumber(c.code, operands.Register, operands.Number) + case *asm.RegisterRegister: + c.code = x86.OrRegisterRegister(c.code, operands.Destination, operands.Source) + } + + case asm.POP: + switch operands := x.Data.(type) { + case *asm.Register: + c.code = x86.PopRegister(c.code, operands.Register) + } + + case asm.PUSH: + switch operands := x.Data.(type) { + case *asm.Register: + c.code = x86.PushRegister(c.code, operands.Register) + } + + case asm.RETURN: + c.code = x86.Return(c.code) + + case asm.SHIFTL: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.ShiftLeftNumber(c.code, operands.Register, byte(operands.Number)&0b111111) + } + + case asm.SHIFTRS: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.ShiftRightSignedNumber(c.code, operands.Register, byte(operands.Number)&0b111111) + } + + case asm.STORE: + c.store(x) + + case asm.SYSCALL: + c.code = x86.Syscall(c.code) + + case asm.XOR: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.XorRegisterNumber(c.code, operands.Register, operands.Number) + case *asm.RegisterRegister: + c.code = x86.XorRegisterRegister(c.code, operands.Destination, operands.Source) + } + + default: + panic("unknown mnemonic: " + x.Mnemonic.String()) + } +} diff --git a/src/asmc/compiler.go b/src/asmc/compiler.go new file mode 100644 index 0000000..0847707 --- /dev/null +++ b/src/asmc/compiler.go @@ -0,0 +1,15 @@ +package asmc + +import "git.akyoto.dev/cli/q/src/dll" + +type compiler struct { + code []byte + data []byte + codeLabels map[string]Address + dataLabels map[string]Address + codePointers []*pointer + dataPointers []*pointer + codeStart Address + dlls dll.List + dllPointers []*pointer +} diff --git a/src/asmc/dllCall.go b/src/asmc/dllCall.go new file mode 100644 index 0000000..8c9856e --- /dev/null +++ b/src/asmc/dllCall.go @@ -0,0 +1,41 @@ +package asmc + +import ( + "strings" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/x86" +) + +func (c *compiler) dllCall(x asm.Instruction) { + size := 4 + // TODO: R15 could be in use. + c.code = x86.MoveRegisterRegister(c.code, x86.R15, x86.RSP) + c.code = x86.AlignStack(c.code) + c.code = x86.SubRegisterNumber(c.code, x86.RSP, 32) + c.code = x86.CallAtAddress(c.code, 0x00_00_00_00) + position := len(c.code) - size + c.code = x86.MoveRegisterRegister(c.code, x86.RSP, x86.R15) + + label := x.Data.(*asm.Label) + pointer := &pointer{ + Position: Address(position), + OpSize: 2, + Size: uint8(size), + } + + pointer.Resolve = func() Address { + dot := strings.Index(label.Name, ".") + library := label.Name[:dot] + funcName := label.Name[dot+1:] + index := c.dlls.Index(library, funcName) + + if index == -1 { + panic("unknown DLL function " + label.Name) + } + + return Address(index * 8) + } + + c.dllPointers = append(c.dllPointers, pointer) +} diff --git a/src/asmc/jump.go b/src/asmc/jump.go new file mode 100644 index 0000000..e6d8f65 --- /dev/null +++ b/src/asmc/jump.go @@ -0,0 +1,47 @@ +package asmc + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/x86" +) + +func (c *compiler) jump(x asm.Instruction) { + switch x.Mnemonic { + case asm.JE: + c.code = x86.Jump8IfEqual(c.code, 0x00) + case asm.JNE: + c.code = x86.Jump8IfNotEqual(c.code, 0x00) + case asm.JG: + c.code = x86.Jump8IfGreater(c.code, 0x00) + case asm.JGE: + c.code = x86.Jump8IfGreaterOrEqual(c.code, 0x00) + case asm.JL: + c.code = x86.Jump8IfLess(c.code, 0x00) + case asm.JLE: + c.code = x86.Jump8IfLessOrEqual(c.code, 0x00) + case asm.JUMP: + c.code = x86.Jump8(c.code, 0x00) + } + + size := 1 + label := x.Data.(*asm.Label) + + pointer := &pointer{ + Position: Address(len(c.code) - size), + OpSize: 1, + Size: uint8(size), + } + + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[label.Name] + + if !exists { + panic("unknown jump label") + } + + distance := destination - (pointer.Position + Address(pointer.Size)) + return Address(distance) + } + + c.codePointers = append(c.codePointers, pointer) +} diff --git a/src/asmc/move.go b/src/asmc/move.go new file mode 100644 index 0000000..8d5a12d --- /dev/null +++ b/src/asmc/move.go @@ -0,0 +1,58 @@ +package asmc + +import ( + "strings" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/x86" +) + +func (c *compiler) move(x asm.Instruction) { + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = x86.MoveRegisterNumber(c.code, operands.Register, operands.Number) + + case *asm.RegisterRegister: + c.code = x86.MoveRegisterRegister(c.code, operands.Destination, operands.Source) + + case *asm.RegisterLabel: + start := len(c.code) + c.code = x86.MoveRegisterNumber(c.code, operands.Register, 0x00_00_00_00) + size := 4 + opSize := len(c.code) - size - start + regLabel := x.Data.(*asm.RegisterLabel) + + if strings.HasPrefix(regLabel.Label, "data_") { + c.dataPointers = append(c.dataPointers, &pointer{ + Position: Address(len(c.code) - size), + OpSize: uint8(opSize), + Size: uint8(size), + Resolve: func() Address { + destination, exists := c.dataLabels[regLabel.Label] + + if !exists { + panic("unknown label") + } + + return Address(destination) + }, + }) + } else { + c.codePointers = append(c.codePointers, &pointer{ + Position: Address(len(c.code) - size), + OpSize: uint8(opSize), + Size: uint8(size), + Resolve: func() Address { + destination, exists := c.codeLabels[regLabel.Label] + + if !exists { + panic("unknown label") + } + + return config.BaseAddress + c.codeStart + destination + }, + }) + } + } +} diff --git a/src/asm/pointer.go b/src/asmc/pointer.go similarity index 96% rename from src/asm/pointer.go rename to src/asmc/pointer.go index 1d4880b..f299758 100644 --- a/src/asm/pointer.go +++ b/src/asmc/pointer.go @@ -1,4 +1,4 @@ -package asm +package asmc // Address represents a memory address. type Address = int32 diff --git a/src/asm/resolvePointers.go b/src/asmc/resolvePointers.go similarity index 62% rename from src/asm/resolvePointers.go rename to src/asmc/resolvePointers.go index 40ecbb9..a437555 100644 --- a/src/asm/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -1,4 +1,4 @@ -package asm +package asmc import ( "encoding/binary" @@ -11,16 +11,16 @@ import ( ) // resolvePointers resolves the addresses of all pointers within the code and writes the correct addresses to the code slice. -func (a Assembler) resolvePointers(code []byte, data []byte, codeStart Address, codeLabels map[string]Address, codePointers []*pointer, dataPointers []*pointer, dllPointers []*pointer) []byte { +func (c *compiler) resolvePointers() { restart: - for i, pointer := range codePointers { + for i, pointer := range c.codePointers { address := pointer.Resolve() if sizeof.Signed(int64(address)) > int(pointer.Size) { - left := code[:pointer.Position-Address(pointer.OpSize)] - right := code[pointer.Position+Address(pointer.Size):] + left := c.code[:pointer.Position-Address(pointer.OpSize)] + right := c.code[pointer.Position+Address(pointer.Size):] size := pointer.Size + pointer.OpSize - opCode := code[pointer.Position-Address(pointer.OpSize)] + opCode := c.code[pointer.Position-Address(pointer.OpSize)] var jump []byte @@ -49,21 +49,21 @@ restart: jump = binary.LittleEndian.AppendUint32(jump, uint32(address)) offset := Address(len(jump)) - Address(size) - for _, following := range codePointers[i+1:] { + for _, following := range c.codePointers[i+1:] { following.Position += offset } - for key, address := range codeLabels { + for key, address := range c.codeLabels { if address > pointer.Position { - codeLabels[key] += offset + c.codeLabels[key] += offset } } - code = slices.Concat(left, jump, right) + c.code = slices.Concat(left, jump, right) goto restart } - slice := code[pointer.Position : pointer.Position+Address(pointer.Size)] + slice := c.code[pointer.Position : pointer.Position+Address(pointer.Size)] switch pointer.Size { case 1: @@ -77,24 +77,22 @@ restart: } } - dataStart, _ := fs.Align(codeStart+Address(len(code)), config.Align) + dataStart, _ := fs.Align(c.codeStart+Address(len(c.code)), config.Align) - for _, pointer := range dataPointers { + for _, pointer := range c.dataPointers { address := config.BaseAddress + Address(dataStart) + pointer.Resolve() - slice := code[pointer.Position : pointer.Position+4] + slice := c.code[pointer.Position : pointer.Position+4] binary.LittleEndian.PutUint32(slice, uint32(address)) } if config.TargetOS == config.Windows { - importsStart, _ := fs.Align(dataStart+Address(len(data)), config.Align) + importsStart, _ := fs.Align(dataStart+Address(len(c.data)), config.Align) - for _, pointer := range dllPointers { + for _, pointer := range c.dllPointers { destination := Address(importsStart) + pointer.Resolve() - delta := destination - Address(codeStart+pointer.Position+Address(pointer.Size)) - slice := code[pointer.Position : pointer.Position+4] + delta := destination - Address(c.codeStart+pointer.Position+Address(pointer.Size)) + slice := c.code[pointer.Position : pointer.Position+4] binary.LittleEndian.PutUint32(slice, uint32(delta)) } } - - return code } diff --git a/src/asmc/store.go b/src/asmc/store.go new file mode 100644 index 0000000..b5e8f0d --- /dev/null +++ b/src/asmc/store.go @@ -0,0 +1,53 @@ +package asmc + +import ( + "math" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/x86" +) + +func (c *compiler) store(x asm.Instruction) { + switch operands := x.Data.(type) { + case *asm.MemoryNumber: + if operands.Address.OffsetRegister == math.MaxUint8 { + c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) + } else { + c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) + } + case *asm.MemoryLabel: + start := len(c.code) + + if operands.Address.OffsetRegister == math.MaxUint8 { + c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, 0b00_00_00_00) + } else { + c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, 0b00_00_00_00) + } + + size := 4 + opSize := len(c.code) - size - start + memLabel := x.Data.(*asm.MemoryLabel) + + c.codePointers = append(c.codePointers, &pointer{ + Position: Address(len(c.code) - size), + OpSize: uint8(opSize), + Size: uint8(size), + Resolve: func() Address { + destination, exists := c.codeLabels[memLabel.Label] + + if !exists { + panic("unknown label") + } + + return config.BaseAddress + c.codeStart + destination + }, + }) + case *asm.MemoryRegister: + if operands.Address.OffsetRegister == math.MaxUint8 { + c.code = x86.StoreRegister(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) + } else { + c.code = x86.StoreDynamicRegister(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) + } + } +} diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 6d01851..0788a36 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -7,6 +7,7 @@ import ( "os" "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/asmc" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/dll" @@ -87,7 +88,7 @@ func (r *Result) finalize() { final.DLLCall("kernel32.ExitProcess") } - r.Code, r.Data = final.Finalize(r.DLLs) + r.Code, r.Data = asmc.Finalize(final, r.DLLs) } // eachFunction recursively finds all the calls to external functions. diff --git a/src/readme.md b/src/readme.md index 4ee22a5..c793954 100644 --- a/src/readme.md +++ b/src/readme.md @@ -2,6 +2,7 @@ - [arm64](arm64) - ARM64 implementation (w.i.p.) - [asm](asm) - Pseudo-assembler stage +- [asmc](asmc) - Compiles assembler to actual machine code - [ast](ast) - Abstract syntax tree generation with the `Parse` function - [build](build) - Build command - [cli](cli) - Command line interface From 8fe663ece1ecb95f7a2681ca52c1b675bce02a40 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 7 Feb 2025 11:38:21 +0100 Subject: [PATCH 0665/1012] Renamed arm64 to arm --- src/{arm64 => arm}/Registers.go | 2 +- src/arm/Registers_test.go | 12 ++++++++++++ src/arm64/Registers_test.go | 12 ------------ src/readme.md | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) rename src/{arm64 => arm}/Registers.go (97%) create mode 100644 src/arm/Registers_test.go delete mode 100644 src/arm64/Registers_test.go diff --git a/src/arm64/Registers.go b/src/arm/Registers.go similarity index 97% rename from src/arm64/Registers.go rename to src/arm/Registers.go index 7cee1b7..f9ca6b6 100644 --- a/src/arm64/Registers.go +++ b/src/arm/Registers.go @@ -1,4 +1,4 @@ -package arm64 +package arm import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/arm/Registers_test.go b/src/arm/Registers_test.go new file mode 100644 index 0000000..5c44c94 --- /dev/null +++ b/src/arm/Registers_test.go @@ -0,0 +1,12 @@ +package arm_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arm" + "git.akyoto.dev/go/assert" +) + +func TestRegisters(t *testing.T) { + assert.NotNil(t, arm.SyscallInputRegisters) +} diff --git a/src/arm64/Registers_test.go b/src/arm64/Registers_test.go deleted file mode 100644 index 14ce983..0000000 --- a/src/arm64/Registers_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package arm64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/arm64" - "git.akyoto.dev/go/assert" -) - -func TestRegisters(t *testing.T) { - assert.NotNil(t, arm64.SyscallInputRegisters) -} diff --git a/src/readme.md b/src/readme.md index c793954..1716ab7 100644 --- a/src/readme.md +++ b/src/readme.md @@ -1,6 +1,6 @@ # Overview -- [arm64](arm64) - ARM64 implementation (w.i.p.) +- [arm](arm) - ARM64 implementation (w.i.p.) - [asm](asm) - Pseudo-assembler stage - [asmc](asmc) - Compiles assembler to actual machine code - [ast](ast) - Abstract syntax tree generation with the `Parse` function @@ -24,5 +24,5 @@ - [scope](scope) - Defines a `Scope` used for code blocks - [sizeof](sizeof) - Calculates the byte size of numbers - [token](token) - Converts a file to tokens with the `Tokenize` function -- [types](types) - Type system (w.i.p.) +- [types](types) - Type system - [x86](x86) - x86-64 implementation From c28eace9668e7913251633753bce34edb2c132af Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 7 Feb 2025 11:38:21 +0100 Subject: [PATCH 0666/1012] Renamed arm64 to arm --- src/{arm64 => arm}/Registers.go | 2 +- src/arm/Registers_test.go | 12 ++++++++++++ src/arm64/Registers_test.go | 12 ------------ src/readme.md | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) rename src/{arm64 => arm}/Registers.go (97%) create mode 100644 src/arm/Registers_test.go delete mode 100644 src/arm64/Registers_test.go diff --git a/src/arm64/Registers.go b/src/arm/Registers.go similarity index 97% rename from src/arm64/Registers.go rename to src/arm/Registers.go index 7cee1b7..f9ca6b6 100644 --- a/src/arm64/Registers.go +++ b/src/arm/Registers.go @@ -1,4 +1,4 @@ -package arm64 +package arm import "git.akyoto.dev/cli/q/src/cpu" diff --git a/src/arm/Registers_test.go b/src/arm/Registers_test.go new file mode 100644 index 0000000..5c44c94 --- /dev/null +++ b/src/arm/Registers_test.go @@ -0,0 +1,12 @@ +package arm_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/arm" + "git.akyoto.dev/go/assert" +) + +func TestRegisters(t *testing.T) { + assert.NotNil(t, arm.SyscallInputRegisters) +} diff --git a/src/arm64/Registers_test.go b/src/arm64/Registers_test.go deleted file mode 100644 index 14ce983..0000000 --- a/src/arm64/Registers_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package arm64_test - -import ( - "testing" - - "git.akyoto.dev/cli/q/src/arm64" - "git.akyoto.dev/go/assert" -) - -func TestRegisters(t *testing.T) { - assert.NotNil(t, arm64.SyscallInputRegisters) -} diff --git a/src/readme.md b/src/readme.md index c793954..1716ab7 100644 --- a/src/readme.md +++ b/src/readme.md @@ -1,6 +1,6 @@ # Overview -- [arm64](arm64) - ARM64 implementation (w.i.p.) +- [arm](arm) - ARM64 implementation (w.i.p.) - [asm](asm) - Pseudo-assembler stage - [asmc](asmc) - Compiles assembler to actual machine code - [ast](ast) - Abstract syntax tree generation with the `Parse` function @@ -24,5 +24,5 @@ - [scope](scope) - Defines a `Scope` used for code blocks - [sizeof](sizeof) - Calculates the byte size of numbers - [token](token) - Converts a file to tokens with the `Tokenize` function -- [types](types) - Type system (w.i.p.) +- [types](types) - Type system - [x86](x86) - x86-64 implementation From 162ddbefef08e2b7e6d7d37e8fdcc899b73a990f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 7 Feb 2025 16:26:24 +0100 Subject: [PATCH 0667/1012] Implemented struct size calculation after a scan --- src/compiler/Compile.go | 11 +++++++++++ src/scanner/scanStruct.go | 17 +---------------- src/types/Base.go | 17 +++++++++++++++++ src/types/Field.go | 3 ++- src/types/Float.go | 4 ++-- src/types/Int.go | 8 ++++---- src/types/Struct.go | 12 +++++++++++- 7 files changed, 48 insertions(+), 24 deletions(-) create mode 100644 src/types/Base.go diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index ca3ef28..328c9f2 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -65,6 +65,17 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c } } + // Calculate size of structs + for _, typ := range allTypes { + structure, isStruct := typ.(*types.Struct) + + if !isStruct { + continue + } + + structure.Update(allTypes) + } + // Resolve the types for _, function := range allFunctions { err := function.ResolveTypes() diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index 3f9e74d..d5fe98f 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -33,27 +33,12 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro fieldName := tokens[i].Text(file.Bytes) i++ fieldTypeName := tokens[i].Text(file.Bytes) - fieldType := types.Int - - switch fieldTypeName { - case "Int", "Int64": - case "Int32": - fieldType = types.Int32 - case "Int16": - fieldType = types.Int16 - case "Int8": - fieldType = types.Int8 - default: - panic("not implemented") - } - i++ structure.AddField(&types.Field{ - Type: fieldType, Name: fieldName, + TypeName: fieldTypeName, Position: token.Position(fieldPosition), - Offset: structure.Size(), }) } diff --git a/src/types/Base.go b/src/types/Base.go new file mode 100644 index 0000000..fb9e4c8 --- /dev/null +++ b/src/types/Base.go @@ -0,0 +1,17 @@ +package types + +// Base is used to describe basic types like integers and floats. +type Base struct { + name string + size int +} + +// Name returns the name of the type. +func (s *Base) Name() string { + return s.name +} + +// Size returns the total size in bytes. +func (s *Base) Size() int { + return s.size +} diff --git a/src/types/Field.go b/src/types/Field.go index e486878..c8d4fce 100644 --- a/src/types/Field.go +++ b/src/types/Field.go @@ -4,8 +4,9 @@ import "git.akyoto.dev/cli/q/src/token" // Field is a memory region in a data structure. type Field struct { - Type Type Name string + Type Type + TypeName string Position token.Position Offset int } diff --git a/src/types/Float.go b/src/types/Float.go index 1fedd30..cbfc638 100644 --- a/src/types/Float.go +++ b/src/types/Float.go @@ -1,7 +1,7 @@ package types var ( - Float64 = &Struct{name: "Float64", size: 8} - Float32 = &Struct{name: "Float32", size: 4} + Float64 = &Base{name: "Float64", size: 8} + Float32 = &Base{name: "Float32", size: 4} Float = Float64 ) diff --git a/src/types/Int.go b/src/types/Int.go index 73fa22c..f07d7c6 100644 --- a/src/types/Int.go +++ b/src/types/Int.go @@ -1,9 +1,9 @@ package types var ( - Int64 = &Struct{name: "Int64", size: 8} - Int32 = &Struct{name: "Int32", size: 4} - Int16 = &Struct{name: "Int16", size: 2} - Int8 = &Struct{name: "Int8", size: 1} + Int64 = &Base{name: "Int64", size: 8} + Int32 = &Base{name: "Int32", size: 4} + Int16 = &Base{name: "Int16", size: 2} + Int8 = &Base{name: "Int8", size: 1} Int = Int64 ) diff --git a/src/types/Struct.go b/src/types/Struct.go index 1e80517..ac3d20e 100644 --- a/src/types/Struct.go +++ b/src/types/Struct.go @@ -15,7 +15,6 @@ func NewStruct(name string) *Struct { // AddField adds a new field to the end of the struct. func (s *Struct) AddField(field *Field) { s.fields = append(s.fields, field) - s.size += field.Type.Size() } // FieldByName returns the field with the given name if it exists. @@ -38,3 +37,14 @@ func (s *Struct) Name() string { func (s *Struct) Size() int { return s.size } + +// Update updates the offsets and structure size. +func (s *Struct) Update(types map[string]Type) { + s.size = 0 + + for _, field := range s.fields { + field.Type = types[field.TypeName] + field.Offset = s.size + s.size += field.Type.Size() + } +} From 63a2752c56ce0d2619931aa7ba59e1b1753e0456 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 7 Feb 2025 16:26:24 +0100 Subject: [PATCH 0668/1012] Implemented struct size calculation after a scan --- src/compiler/Compile.go | 11 +++++++++++ src/scanner/scanStruct.go | 17 +---------------- src/types/Base.go | 17 +++++++++++++++++ src/types/Field.go | 3 ++- src/types/Float.go | 4 ++-- src/types/Int.go | 8 ++++---- src/types/Struct.go | 12 +++++++++++- 7 files changed, 48 insertions(+), 24 deletions(-) create mode 100644 src/types/Base.go diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index ca3ef28..328c9f2 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -65,6 +65,17 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c } } + // Calculate size of structs + for _, typ := range allTypes { + structure, isStruct := typ.(*types.Struct) + + if !isStruct { + continue + } + + structure.Update(allTypes) + } + // Resolve the types for _, function := range allFunctions { err := function.ResolveTypes() diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index 3f9e74d..d5fe98f 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -33,27 +33,12 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro fieldName := tokens[i].Text(file.Bytes) i++ fieldTypeName := tokens[i].Text(file.Bytes) - fieldType := types.Int - - switch fieldTypeName { - case "Int", "Int64": - case "Int32": - fieldType = types.Int32 - case "Int16": - fieldType = types.Int16 - case "Int8": - fieldType = types.Int8 - default: - panic("not implemented") - } - i++ structure.AddField(&types.Field{ - Type: fieldType, Name: fieldName, + TypeName: fieldTypeName, Position: token.Position(fieldPosition), - Offset: structure.Size(), }) } diff --git a/src/types/Base.go b/src/types/Base.go new file mode 100644 index 0000000..fb9e4c8 --- /dev/null +++ b/src/types/Base.go @@ -0,0 +1,17 @@ +package types + +// Base is used to describe basic types like integers and floats. +type Base struct { + name string + size int +} + +// Name returns the name of the type. +func (s *Base) Name() string { + return s.name +} + +// Size returns the total size in bytes. +func (s *Base) Size() int { + return s.size +} diff --git a/src/types/Field.go b/src/types/Field.go index e486878..c8d4fce 100644 --- a/src/types/Field.go +++ b/src/types/Field.go @@ -4,8 +4,9 @@ import "git.akyoto.dev/cli/q/src/token" // Field is a memory region in a data structure. type Field struct { - Type Type Name string + Type Type + TypeName string Position token.Position Offset int } diff --git a/src/types/Float.go b/src/types/Float.go index 1fedd30..cbfc638 100644 --- a/src/types/Float.go +++ b/src/types/Float.go @@ -1,7 +1,7 @@ package types var ( - Float64 = &Struct{name: "Float64", size: 8} - Float32 = &Struct{name: "Float32", size: 4} + Float64 = &Base{name: "Float64", size: 8} + Float32 = &Base{name: "Float32", size: 4} Float = Float64 ) diff --git a/src/types/Int.go b/src/types/Int.go index 73fa22c..f07d7c6 100644 --- a/src/types/Int.go +++ b/src/types/Int.go @@ -1,9 +1,9 @@ package types var ( - Int64 = &Struct{name: "Int64", size: 8} - Int32 = &Struct{name: "Int32", size: 4} - Int16 = &Struct{name: "Int16", size: 2} - Int8 = &Struct{name: "Int8", size: 1} + Int64 = &Base{name: "Int64", size: 8} + Int32 = &Base{name: "Int32", size: 4} + Int16 = &Base{name: "Int16", size: 2} + Int8 = &Base{name: "Int8", size: 1} Int = Int64 ) diff --git a/src/types/Struct.go b/src/types/Struct.go index 1e80517..ac3d20e 100644 --- a/src/types/Struct.go +++ b/src/types/Struct.go @@ -15,7 +15,6 @@ func NewStruct(name string) *Struct { // AddField adds a new field to the end of the struct. func (s *Struct) AddField(field *Field) { s.fields = append(s.fields, field) - s.size += field.Type.Size() } // FieldByName returns the field with the given name if it exists. @@ -38,3 +37,14 @@ func (s *Struct) Name() string { func (s *Struct) Size() int { return s.size } + +// Update updates the offsets and structure size. +func (s *Struct) Update(types map[string]Type) { + s.size = 0 + + for _, field := range s.fields { + field.Type = types[field.TypeName] + field.Offset = s.size + s.size += field.Type.Size() + } +} From a296ee8c94015a5ac3cfda4441319da4f0fab494 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 7 Feb 2025 19:41:54 +0100 Subject: [PATCH 0669/1012] Simplified code --- src/cli/Build.go | 21 +++++++-------------- src/cli/Run.go | 22 +++------------------- src/cli/exit.go | 27 +++++++++++++++++++++++++++ src/cli/readme.md | 4 ++-- src/x86/Move.go | 4 ++-- 5 files changed, 41 insertions(+), 37 deletions(-) create mode 100644 src/cli/exit.go diff --git a/src/cli/Build.go b/src/cli/Build.go index 5f78ccd..28510cd 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -13,25 +13,18 @@ import ( // Build parses the arguments and creates a build. func Build(args []string) int { - _, err := buildWithArgs(args) + _, err := buildExecutable(args) if err != nil { fmt.Fprintln(os.Stderr, err) - - switch err.(type) { - case *errors.ExpectedCLIParameter, *errors.UnknownCLIParameter: - return 2 - - default: - return 1 - } + return exit(err) } return 0 } -// buildWithArgs creates a new build with the given arguments. -func buildWithArgs(args []string) (*build.Build, error) { +// buildExecutable creates a new build with the given arguments. +func buildExecutable(args []string) (*build.Build, error) { b := build.New() for i := 0; i < len(args); i++ { @@ -93,12 +86,12 @@ func buildWithArgs(args []string) (*build.Build, error) { b.Files = append(b.Files, ".") } - err := makeExecutable(b) + err := start(b) return b, err } -// makeExecutable starts the build by running the compiler and then writing the result to disk. -func makeExecutable(b *build.Build) error { +// start starts the build by running the compiler and then writing the result to disk. +func start(b *build.Build) error { result, err := b.Run() if err != nil { diff --git a/src/cli/Run.go b/src/cli/Run.go index 11a5969..ce22b06 100644 --- a/src/cli/Run.go +++ b/src/cli/Run.go @@ -4,24 +4,15 @@ import ( "fmt" "os" "os/exec" - - "git.akyoto.dev/cli/q/src/errors" ) // Run builds and runs the executable. func Run(args []string) int { - b, err := buildWithArgs(args) + b, err := buildExecutable(args) if err != nil { fmt.Fprintln(os.Stderr, err) - - switch err.(type) { - case *errors.ExpectedCLIParameter, *errors.UnknownCLIParameter: - return 2 - - default: - return 1 - } + return exit(err) } cmd := exec.Command(b.Executable()) @@ -32,14 +23,7 @@ func Run(args []string) int { if err != nil { fmt.Fprintln(os.Stderr, err) - - switch err := err.(type) { - case *exec.ExitError: - return err.ExitCode() - - default: - return 1 - } + return exit(err) } return 0 diff --git a/src/cli/exit.go b/src/cli/exit.go new file mode 100644 index 0000000..69f8460 --- /dev/null +++ b/src/cli/exit.go @@ -0,0 +1,27 @@ +package cli + +import ( + "errors" + "os/exec" + + xerrors "git.akyoto.dev/cli/q/src/errors" +) + +// exit returns the exit code depending on the error type. +func exit(err error) int { + var ( + exit *exec.ExitError + expectedParameter *xerrors.ExpectedCLIParameter + unknownParameter *xerrors.UnknownCLIParameter + ) + + if errors.As(err, &exit) { + return exit.ExitCode() + } + + if errors.As(err, &expectedParameter) || errors.As(err, &unknownParameter) { + return 2 + } + + return 1 +} diff --git a/src/cli/readme.md b/src/cli/readme.md index 262701d..3c23b53 100644 --- a/src/cli/readme.md +++ b/src/cli/readme.md @@ -21,13 +21,13 @@ q build examples/hello q build examples/hello --dry ``` -Adding the `-a` or `--assembler` flag shows the generated assembly instructions: +Adding `-a` or `--assembler` shows the generated assembly instructions: ```shell q build examples/hello -a ``` -Adding the `-v` or `--verbose` flag shows verbose compiler information: +Adding `-v` or `--verbose` activates all flags that show additional information: ```shell q build examples/hello -v diff --git a/src/x86/Move.go b/src/x86/Move.go index 2c38ca5..fee16e1 100644 --- a/src/x86/Move.go +++ b/src/x86/Move.go @@ -34,9 +34,9 @@ func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byt if w == 1 { return binary.LittleEndian.AppendUint64(code, uint64(number)) - } else { - return binary.LittleEndian.AppendUint32(code, uint32(number)) } + + return binary.LittleEndian.AppendUint32(code, uint32(number)) } // MoveRegisterNumber32 moves an integer into the given register and sign-extends the register. From c2d108443412b93654684b040d2a4115d5b76928 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 7 Feb 2025 19:41:54 +0100 Subject: [PATCH 0670/1012] Simplified code --- src/cli/Build.go | 21 +++++++-------------- src/cli/Run.go | 22 +++------------------- src/cli/exit.go | 27 +++++++++++++++++++++++++++ src/cli/readme.md | 4 ++-- src/x86/Move.go | 4 ++-- 5 files changed, 41 insertions(+), 37 deletions(-) create mode 100644 src/cli/exit.go diff --git a/src/cli/Build.go b/src/cli/Build.go index 5f78ccd..28510cd 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -13,25 +13,18 @@ import ( // Build parses the arguments and creates a build. func Build(args []string) int { - _, err := buildWithArgs(args) + _, err := buildExecutable(args) if err != nil { fmt.Fprintln(os.Stderr, err) - - switch err.(type) { - case *errors.ExpectedCLIParameter, *errors.UnknownCLIParameter: - return 2 - - default: - return 1 - } + return exit(err) } return 0 } -// buildWithArgs creates a new build with the given arguments. -func buildWithArgs(args []string) (*build.Build, error) { +// buildExecutable creates a new build with the given arguments. +func buildExecutable(args []string) (*build.Build, error) { b := build.New() for i := 0; i < len(args); i++ { @@ -93,12 +86,12 @@ func buildWithArgs(args []string) (*build.Build, error) { b.Files = append(b.Files, ".") } - err := makeExecutable(b) + err := start(b) return b, err } -// makeExecutable starts the build by running the compiler and then writing the result to disk. -func makeExecutable(b *build.Build) error { +// start starts the build by running the compiler and then writing the result to disk. +func start(b *build.Build) error { result, err := b.Run() if err != nil { diff --git a/src/cli/Run.go b/src/cli/Run.go index 11a5969..ce22b06 100644 --- a/src/cli/Run.go +++ b/src/cli/Run.go @@ -4,24 +4,15 @@ import ( "fmt" "os" "os/exec" - - "git.akyoto.dev/cli/q/src/errors" ) // Run builds and runs the executable. func Run(args []string) int { - b, err := buildWithArgs(args) + b, err := buildExecutable(args) if err != nil { fmt.Fprintln(os.Stderr, err) - - switch err.(type) { - case *errors.ExpectedCLIParameter, *errors.UnknownCLIParameter: - return 2 - - default: - return 1 - } + return exit(err) } cmd := exec.Command(b.Executable()) @@ -32,14 +23,7 @@ func Run(args []string) int { if err != nil { fmt.Fprintln(os.Stderr, err) - - switch err := err.(type) { - case *exec.ExitError: - return err.ExitCode() - - default: - return 1 - } + return exit(err) } return 0 diff --git a/src/cli/exit.go b/src/cli/exit.go new file mode 100644 index 0000000..69f8460 --- /dev/null +++ b/src/cli/exit.go @@ -0,0 +1,27 @@ +package cli + +import ( + "errors" + "os/exec" + + xerrors "git.akyoto.dev/cli/q/src/errors" +) + +// exit returns the exit code depending on the error type. +func exit(err error) int { + var ( + exit *exec.ExitError + expectedParameter *xerrors.ExpectedCLIParameter + unknownParameter *xerrors.UnknownCLIParameter + ) + + if errors.As(err, &exit) { + return exit.ExitCode() + } + + if errors.As(err, &expectedParameter) || errors.As(err, &unknownParameter) { + return 2 + } + + return 1 +} diff --git a/src/cli/readme.md b/src/cli/readme.md index 262701d..3c23b53 100644 --- a/src/cli/readme.md +++ b/src/cli/readme.md @@ -21,13 +21,13 @@ q build examples/hello q build examples/hello --dry ``` -Adding the `-a` or `--assembler` flag shows the generated assembly instructions: +Adding `-a` or `--assembler` shows the generated assembly instructions: ```shell q build examples/hello -a ``` -Adding the `-v` or `--verbose` flag shows verbose compiler information: +Adding `-v` or `--verbose` activates all flags that show additional information: ```shell q build examples/hello -v diff --git a/src/x86/Move.go b/src/x86/Move.go index 2c38ca5..fee16e1 100644 --- a/src/x86/Move.go +++ b/src/x86/Move.go @@ -34,9 +34,9 @@ func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byt if w == 1 { return binary.LittleEndian.AppendUint64(code, uint64(number)) - } else { - return binary.LittleEndian.AppendUint32(code, uint32(number)) } + + return binary.LittleEndian.AppendUint32(code, uint32(number)) } // MoveRegisterNumber32 moves an integer into the given register and sign-extends the register. From a562f521b6e1f65b065ece6810b95af43366dbd3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 7 Feb 2025 20:08:49 +0100 Subject: [PATCH 0671/1012] Simplified CLI errors --- src/cli/Build.go | 11 +++++----- src/cli/errors.go | 34 +++++++++++++++++++++++++++++ src/cli/exit.go | 9 ++++---- src/errors/ExpectedCLIParameter.go | 13 ----------- src/errors/InvalidParameterValue.go | 14 ------------ src/errors/UnknownCLIParameter.go | 13 ----------- 6 files changed, 43 insertions(+), 51 deletions(-) create mode 100644 src/cli/errors.go delete mode 100644 src/errors/ExpectedCLIParameter.go delete mode 100644 src/errors/InvalidParameterValue.go delete mode 100644 src/errors/UnknownCLIParameter.go diff --git a/src/cli/Build.go b/src/cli/Build.go index 28510cd..bfb7e37 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -8,7 +8,6 @@ import ( "git.akyoto.dev/cli/q/src/build" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/errors" ) // Build parses the arguments and creates a build. @@ -46,7 +45,7 @@ func buildExecutable(args []string) (*build.Build, error) { i++ if i >= len(args) { - return b, &errors.ExpectedCLIParameter{Parameter: "arch"} + return b, &ExpectedParameterError{Parameter: "arch"} } config.TargetArch = args[i] @@ -55,7 +54,7 @@ func buildExecutable(args []string) (*build.Build, error) { i++ if i >= len(args) { - return b, &errors.ExpectedCLIParameter{Parameter: "os"} + return b, &ExpectedParameterError{Parameter: "os"} } switch args[i] { @@ -66,12 +65,12 @@ func buildExecutable(args []string) (*build.Build, error) { case "windows": config.TargetOS = config.Windows default: - return b, &errors.InvalidParameterValue{Value: args[i], Parameter: "os"} + return b, &InvalidValueError{Value: args[i], Parameter: "os"} } default: if strings.HasPrefix(args[i], "-") { - return b, &errors.UnknownCLIParameter{Parameter: args[i]} + return b, &UnknownParameterError{Parameter: args[i]} } b.Files = append(b.Files, args[i]) @@ -79,7 +78,7 @@ func buildExecutable(args []string) (*build.Build, error) { } if config.TargetOS == config.Unknown { - return b, &errors.InvalidParameterValue{Value: runtime.GOOS, Parameter: "os"} + return b, &InvalidValueError{Value: runtime.GOOS, Parameter: "os"} } if len(b.Files) == 0 { diff --git a/src/cli/errors.go b/src/cli/errors.go new file mode 100644 index 0000000..5f675d6 --- /dev/null +++ b/src/cli/errors.go @@ -0,0 +1,34 @@ +package cli + +import "fmt" + +// ExpectedParameterError is created when a command line parameter is missing. +type ExpectedParameterError struct { + Parameter string +} + +// UnknownParameterError is created when a command line parameter is not recognized. +type UnknownParameterError struct { + Parameter string +} + +// InvalidValueError is created when a parameter has an invalid value. +type InvalidValueError struct { + Value string + Parameter string +} + +// Error generates the string representation. +func (err *ExpectedParameterError) Error() string { + return fmt.Sprintf("Expected parameter '%s'", err.Parameter) +} + +// Error generates the string representation. +func (err *UnknownParameterError) Error() string { + return fmt.Sprintf("Unknown parameter '%s'", err.Parameter) +} + +// Error generates the string representation. +func (err *InvalidValueError) Error() string { + return fmt.Sprintf("Invalid value '%s' for parameter '%s'", err.Value, err.Parameter) +} diff --git a/src/cli/exit.go b/src/cli/exit.go index 69f8460..aefa74e 100644 --- a/src/cli/exit.go +++ b/src/cli/exit.go @@ -3,23 +3,22 @@ package cli import ( "errors" "os/exec" - - xerrors "git.akyoto.dev/cli/q/src/errors" ) // exit returns the exit code depending on the error type. func exit(err error) int { var ( exit *exec.ExitError - expectedParameter *xerrors.ExpectedCLIParameter - unknownParameter *xerrors.UnknownCLIParameter + expectedParameter *ExpectedParameterError + unknownParameter *UnknownParameterError + invalidValue *InvalidValueError ) if errors.As(err, &exit) { return exit.ExitCode() } - if errors.As(err, &expectedParameter) || errors.As(err, &unknownParameter) { + if errors.As(err, &expectedParameter) || errors.As(err, &unknownParameter) || errors.As(err, &invalidValue) { return 2 } diff --git a/src/errors/ExpectedCLIParameter.go b/src/errors/ExpectedCLIParameter.go deleted file mode 100644 index 0c25c6b..0000000 --- a/src/errors/ExpectedCLIParameter.go +++ /dev/null @@ -1,13 +0,0 @@ -package errors - -import "fmt" - -// ExpectedCLIParameter error is created when a command line parameter is missing. -type ExpectedCLIParameter struct { - Parameter string -} - -// Error generates the string representation. -func (err *ExpectedCLIParameter) Error() string { - return fmt.Sprintf("Expected parameter '%s'", err.Parameter) -} diff --git a/src/errors/InvalidParameterValue.go b/src/errors/InvalidParameterValue.go deleted file mode 100644 index cd2989e..0000000 --- a/src/errors/InvalidParameterValue.go +++ /dev/null @@ -1,14 +0,0 @@ -package errors - -import "fmt" - -// InvalidParameterValue error is created when a parameter has an invalid value. -type InvalidParameterValue struct { - Value string - Parameter string -} - -// Error generates the string representation. -func (err *InvalidParameterValue) Error() string { - return fmt.Sprintf("Invalid value '%s' for parameter '%s'", err.Value, err.Parameter) -} diff --git a/src/errors/UnknownCLIParameter.go b/src/errors/UnknownCLIParameter.go deleted file mode 100644 index 136864e..0000000 --- a/src/errors/UnknownCLIParameter.go +++ /dev/null @@ -1,13 +0,0 @@ -package errors - -import "fmt" - -// UnknownCLIParameter error is created when a command line parameter is not recognized. -type UnknownCLIParameter struct { - Parameter string -} - -// Error generates the string representation. -func (err *UnknownCLIParameter) Error() string { - return fmt.Sprintf("Unknown parameter '%s'", err.Parameter) -} From 5fc9cc4dff3265bf7a72b034a2b8d1d15082e286 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 7 Feb 2025 20:08:49 +0100 Subject: [PATCH 0672/1012] Simplified CLI errors --- src/cli/Build.go | 11 +++++----- src/cli/errors.go | 34 +++++++++++++++++++++++++++++ src/cli/exit.go | 9 ++++---- src/errors/ExpectedCLIParameter.go | 13 ----------- src/errors/InvalidParameterValue.go | 14 ------------ src/errors/UnknownCLIParameter.go | 13 ----------- 6 files changed, 43 insertions(+), 51 deletions(-) create mode 100644 src/cli/errors.go delete mode 100644 src/errors/ExpectedCLIParameter.go delete mode 100644 src/errors/InvalidParameterValue.go delete mode 100644 src/errors/UnknownCLIParameter.go diff --git a/src/cli/Build.go b/src/cli/Build.go index 28510cd..bfb7e37 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -8,7 +8,6 @@ import ( "git.akyoto.dev/cli/q/src/build" "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/errors" ) // Build parses the arguments and creates a build. @@ -46,7 +45,7 @@ func buildExecutable(args []string) (*build.Build, error) { i++ if i >= len(args) { - return b, &errors.ExpectedCLIParameter{Parameter: "arch"} + return b, &ExpectedParameterError{Parameter: "arch"} } config.TargetArch = args[i] @@ -55,7 +54,7 @@ func buildExecutable(args []string) (*build.Build, error) { i++ if i >= len(args) { - return b, &errors.ExpectedCLIParameter{Parameter: "os"} + return b, &ExpectedParameterError{Parameter: "os"} } switch args[i] { @@ -66,12 +65,12 @@ func buildExecutable(args []string) (*build.Build, error) { case "windows": config.TargetOS = config.Windows default: - return b, &errors.InvalidParameterValue{Value: args[i], Parameter: "os"} + return b, &InvalidValueError{Value: args[i], Parameter: "os"} } default: if strings.HasPrefix(args[i], "-") { - return b, &errors.UnknownCLIParameter{Parameter: args[i]} + return b, &UnknownParameterError{Parameter: args[i]} } b.Files = append(b.Files, args[i]) @@ -79,7 +78,7 @@ func buildExecutable(args []string) (*build.Build, error) { } if config.TargetOS == config.Unknown { - return b, &errors.InvalidParameterValue{Value: runtime.GOOS, Parameter: "os"} + return b, &InvalidValueError{Value: runtime.GOOS, Parameter: "os"} } if len(b.Files) == 0 { diff --git a/src/cli/errors.go b/src/cli/errors.go new file mode 100644 index 0000000..5f675d6 --- /dev/null +++ b/src/cli/errors.go @@ -0,0 +1,34 @@ +package cli + +import "fmt" + +// ExpectedParameterError is created when a command line parameter is missing. +type ExpectedParameterError struct { + Parameter string +} + +// UnknownParameterError is created when a command line parameter is not recognized. +type UnknownParameterError struct { + Parameter string +} + +// InvalidValueError is created when a parameter has an invalid value. +type InvalidValueError struct { + Value string + Parameter string +} + +// Error generates the string representation. +func (err *ExpectedParameterError) Error() string { + return fmt.Sprintf("Expected parameter '%s'", err.Parameter) +} + +// Error generates the string representation. +func (err *UnknownParameterError) Error() string { + return fmt.Sprintf("Unknown parameter '%s'", err.Parameter) +} + +// Error generates the string representation. +func (err *InvalidValueError) Error() string { + return fmt.Sprintf("Invalid value '%s' for parameter '%s'", err.Value, err.Parameter) +} diff --git a/src/cli/exit.go b/src/cli/exit.go index 69f8460..aefa74e 100644 --- a/src/cli/exit.go +++ b/src/cli/exit.go @@ -3,23 +3,22 @@ package cli import ( "errors" "os/exec" - - xerrors "git.akyoto.dev/cli/q/src/errors" ) // exit returns the exit code depending on the error type. func exit(err error) int { var ( exit *exec.ExitError - expectedParameter *xerrors.ExpectedCLIParameter - unknownParameter *xerrors.UnknownCLIParameter + expectedParameter *ExpectedParameterError + unknownParameter *UnknownParameterError + invalidValue *InvalidValueError ) if errors.As(err, &exit) { return exit.ExitCode() } - if errors.As(err, &expectedParameter) || errors.As(err, &unknownParameter) { + if errors.As(err, &expectedParameter) || errors.As(err, &unknownParameter) || errors.As(err, &invalidValue) { return 2 } diff --git a/src/errors/ExpectedCLIParameter.go b/src/errors/ExpectedCLIParameter.go deleted file mode 100644 index 0c25c6b..0000000 --- a/src/errors/ExpectedCLIParameter.go +++ /dev/null @@ -1,13 +0,0 @@ -package errors - -import "fmt" - -// ExpectedCLIParameter error is created when a command line parameter is missing. -type ExpectedCLIParameter struct { - Parameter string -} - -// Error generates the string representation. -func (err *ExpectedCLIParameter) Error() string { - return fmt.Sprintf("Expected parameter '%s'", err.Parameter) -} diff --git a/src/errors/InvalidParameterValue.go b/src/errors/InvalidParameterValue.go deleted file mode 100644 index cd2989e..0000000 --- a/src/errors/InvalidParameterValue.go +++ /dev/null @@ -1,14 +0,0 @@ -package errors - -import "fmt" - -// InvalidParameterValue error is created when a parameter has an invalid value. -type InvalidParameterValue struct { - Value string - Parameter string -} - -// Error generates the string representation. -func (err *InvalidParameterValue) Error() string { - return fmt.Sprintf("Invalid value '%s' for parameter '%s'", err.Value, err.Parameter) -} diff --git a/src/errors/UnknownCLIParameter.go b/src/errors/UnknownCLIParameter.go deleted file mode 100644 index 136864e..0000000 --- a/src/errors/UnknownCLIParameter.go +++ /dev/null @@ -1,13 +0,0 @@ -package errors - -import "fmt" - -// UnknownCLIParameter error is created when a command line parameter is not recognized. -type UnknownCLIParameter struct { - Parameter string -} - -// Error generates the string representation. -func (err *UnknownCLIParameter) Error() string { - return fmt.Sprintf("Unknown parameter '%s'", err.Parameter) -} From 6ebd1829fb5533c3f0f72e8278633ddfb1e22707 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 7 Feb 2025 21:25:29 +0100 Subject: [PATCH 0673/1012] Added more tests --- src/errors/Common.go | 1 + src/scanner/Scan.go | 3 +-- src/scanner/scanImport.go | 9 ++------- tests/errors/ExpectedPackageName.q | 1 + tests/errors_test.go | 1 + 5 files changed, 6 insertions(+), 9 deletions(-) create mode 100644 tests/errors/ExpectedPackageName.q diff --git a/src/errors/Common.go b/src/errors/Common.go index 107cb3c..e78af1d 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -7,6 +7,7 @@ var ( ExpectedFunctionParameters = &Base{"Expected function parameters"} ExpectedFunctionDefinition = &Base{"Expected function definition"} ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} + ExpectedPackageName = &Base{"Expected package name"} ExpectedStructName = &Base{"Expected struct name"} InvalidNumber = &Base{"Invalid number"} InvalidExpression = &Base{"Invalid expression"} diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go index f2d3163..a0ca92d 100644 --- a/src/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -18,9 +18,8 @@ func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan types. errors: make(chan error), } - scanner.queueDirectory(filepath.Join(config.Library, "mem"), "mem") - go func() { + scanner.queueDirectory(filepath.Join(config.Library, "mem"), "mem") scanner.queue(files...) scanner.group.Wait() close(scanner.files) diff --git a/src/scanner/scanImport.go b/src/scanner/scanImport.go index b430e2e..710a76a 100644 --- a/src/scanner/scanImport.go +++ b/src/scanner/scanImport.go @@ -4,6 +4,7 @@ import ( "path/filepath" "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/token" ) @@ -13,7 +14,7 @@ func (s *Scanner) scanImport(file *fs.File, tokens token.List, i int) (int, erro i++ if tokens[i].Kind != token.Identifier { - panic("expected package name") + return i, errors.New(errors.ExpectedPackageName, file, tokens[i].Position) } packageName := tokens[i].Text(file.Bytes) @@ -31,11 +32,5 @@ func (s *Scanner) scanImport(file *fs.File, tokens token.List, i int) (int, erro } s.queueDirectory(fullPath, packageName) - i++ - - if tokens[i].Kind != token.NewLine && tokens[i].Kind != token.EOF { - panic("expected newline or eof") - } - return i, nil } diff --git a/tests/errors/ExpectedPackageName.q b/tests/errors/ExpectedPackageName.q new file mode 100644 index 0000000..efca996 --- /dev/null +++ b/tests/errors/ExpectedPackageName.q @@ -0,0 +1 @@ +import \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 20178a0..24c6b52 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -20,6 +20,7 @@ var errs = []struct { {"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse}, {"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse}, {"ExpectedStructName.q", errors.ExpectedStructName}, + {"ExpectedPackageName.q", errors.ExpectedPackageName}, {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, From cba26d8154feef8d9bee7f6cf7fd922d804066be Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 7 Feb 2025 21:25:29 +0100 Subject: [PATCH 0674/1012] Added more tests --- src/errors/Common.go | 1 + src/scanner/Scan.go | 3 +-- src/scanner/scanImport.go | 9 ++------- tests/errors/ExpectedPackageName.q | 1 + tests/errors_test.go | 1 + 5 files changed, 6 insertions(+), 9 deletions(-) create mode 100644 tests/errors/ExpectedPackageName.q diff --git a/src/errors/Common.go b/src/errors/Common.go index 107cb3c..e78af1d 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -7,6 +7,7 @@ var ( ExpectedFunctionParameters = &Base{"Expected function parameters"} ExpectedFunctionDefinition = &Base{"Expected function definition"} ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} + ExpectedPackageName = &Base{"Expected package name"} ExpectedStructName = &Base{"Expected struct name"} InvalidNumber = &Base{"Invalid number"} InvalidExpression = &Base{"Invalid expression"} diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go index f2d3163..a0ca92d 100644 --- a/src/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -18,9 +18,8 @@ func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan types. errors: make(chan error), } - scanner.queueDirectory(filepath.Join(config.Library, "mem"), "mem") - go func() { + scanner.queueDirectory(filepath.Join(config.Library, "mem"), "mem") scanner.queue(files...) scanner.group.Wait() close(scanner.files) diff --git a/src/scanner/scanImport.go b/src/scanner/scanImport.go index b430e2e..710a76a 100644 --- a/src/scanner/scanImport.go +++ b/src/scanner/scanImport.go @@ -4,6 +4,7 @@ import ( "path/filepath" "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/token" ) @@ -13,7 +14,7 @@ func (s *Scanner) scanImport(file *fs.File, tokens token.List, i int) (int, erro i++ if tokens[i].Kind != token.Identifier { - panic("expected package name") + return i, errors.New(errors.ExpectedPackageName, file, tokens[i].Position) } packageName := tokens[i].Text(file.Bytes) @@ -31,11 +32,5 @@ func (s *Scanner) scanImport(file *fs.File, tokens token.List, i int) (int, erro } s.queueDirectory(fullPath, packageName) - i++ - - if tokens[i].Kind != token.NewLine && tokens[i].Kind != token.EOF { - panic("expected newline or eof") - } - return i, nil } diff --git a/tests/errors/ExpectedPackageName.q b/tests/errors/ExpectedPackageName.q new file mode 100644 index 0000000..efca996 --- /dev/null +++ b/tests/errors/ExpectedPackageName.q @@ -0,0 +1 @@ +import \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 20178a0..24c6b52 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -20,6 +20,7 @@ var errs = []struct { {"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse}, {"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse}, {"ExpectedStructName.q", errors.ExpectedStructName}, + {"ExpectedPackageName.q", errors.ExpectedPackageName}, {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, From 05688e9e799e1ac405d9a09a38e67326c185f065 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 00:26:50 +0100 Subject: [PATCH 0675/1012] Updated dependencies --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 29d0c49..a911fe7 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,4 @@ require ( git.akyoto.dev/go/color v0.1.1 ) -require golang.org/x/sys v0.29.0 // indirect +require golang.org/x/sys v0.30.0 // indirect diff --git a/go.sum b/go.sum index 5b3d719..11abd11 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,5 @@ git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= git.akyoto.dev/go/color v0.1.1 h1:mMAoMIwLBPNy7ocRSxdsCFs7onPC3GfDEiJErCneqRE= git.akyoto.dev/go/color v0.1.1/go.mod h1:ywOjoD0O0sk6bIn92uAJf7mErlEFCuQInL84y4Lqi3Q= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= From 1d50720c288336c186ea21c66415302d0065afe8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 00:26:50 +0100 Subject: [PATCH 0676/1012] Updated dependencies --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 29d0c49..a911fe7 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,4 @@ require ( git.akyoto.dev/go/color v0.1.1 ) -require golang.org/x/sys v0.29.0 // indirect +require golang.org/x/sys v0.30.0 // indirect diff --git a/go.sum b/go.sum index 5b3d719..11abd11 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,5 @@ git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= git.akyoto.dev/go/color v0.1.1 h1:mMAoMIwLBPNy7ocRSxdsCFs7onPC3GfDEiJErCneqRE= git.akyoto.dev/go/color v0.1.1/go.mod h1:ywOjoD0O0sk6bIn92uAJf7mErlEFCuQInL84y4Lqi3Q= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= From c6ef9013a88aedcb24b010eb01722fc3b351986b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 00:40:41 +0100 Subject: [PATCH 0677/1012] Added more tests --- src/scanner/scanFile.go | 5 +-- src/types/types_test.go | 55 ++++++++++++++++++++++++++++ src/x86/{x64_test.go => x86_test.go} | 2 +- 3 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 src/types/types_test.go rename src/x86/{x64_test.go => x86_test.go} (96%) diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 79cf791..3b1bff2 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -26,9 +26,8 @@ func (s *Scanner) scanFile(path string, pkg string) error { } s.files <- file - i := 0 - for i < len(tokens) { + for i := 0; i < len(tokens); i++ { switch tokens[i].Kind { case token.NewLine: case token.Comment: @@ -49,8 +48,6 @@ func (s *Scanner) scanFile(path string, pkg string) error { if err != nil { return err } - - i++ } return nil diff --git a/src/types/types_test.go b/src/types/types_test.go new file mode 100644 index 0000000..d5b6ba8 --- /dev/null +++ b/src/types/types_test.go @@ -0,0 +1,55 @@ +package types_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/types" + "git.akyoto.dev/go/assert" +) + +func TestName(t *testing.T) { + assert.Equal(t, types.Int.Name(), "Int64") + assert.Equal(t, types.PointerAny.Name(), "Pointer") + assert.Equal(t, (&types.Pointer{To: types.Int}).Name(), "Pointer:Int64") +} + +func TestSize(t *testing.T) { + assert.Equal(t, types.Int.Size(), 8) + assert.Equal(t, types.Int8.Size(), 1) + assert.Equal(t, types.Int16.Size(), 2) + assert.Equal(t, types.Int32.Size(), 4) + assert.Equal(t, types.Int64.Size(), 8) + assert.Equal(t, types.PointerAny.Size(), 8) + assert.Equal(t, (&types.Pointer{To: types.Int}).Size(), 8) +} + +func TestStruct(t *testing.T) { + s := types.NewStruct("Test") + assert.Equal(t, s.Name(), "Test") + assert.Equal(t, s.Size(), 0) + field := &types.Field{Name: "TestField", TypeName: "Int8"} + s.AddField(field) + s.Update(map[string]types.Type{"Int8": types.Int8}) + assert.Equal(t, s.Size(), 1) + assert.Equal(t, s.FieldByName("TestField"), field) + assert.Nil(t, s.FieldByName("does-not-exist")) +} + +func TestBasics(t *testing.T) { + assert.True(t, types.Is(types.Int, types.Int)) + assert.True(t, types.Is(types.PointerAny, types.PointerAny)) + assert.False(t, types.Is(types.Int, types.Float)) + assert.False(t, types.Is(&types.Pointer{To: types.Int}, &types.Pointer{To: types.Float})) +} + +func TestSpecialCases(t *testing.T) { + // Case #1: + // For syscalls whose return type is `nil` we currently allow casting them to anything. + assert.True(t, types.Is(nil, types.Int)) + assert.True(t, types.Is(nil, types.Float)) + + // Case #2: + // A pointer pointing to a known type fulfills the requirement of a pointer to anything. + assert.True(t, types.Is(&types.Pointer{To: types.Int}, &types.Pointer{To: nil})) + assert.True(t, types.Is(&types.Pointer{To: types.Float}, &types.Pointer{To: nil})) +} diff --git a/src/x86/x64_test.go b/src/x86/x86_test.go similarity index 96% rename from src/x86/x64_test.go rename to src/x86/x86_test.go index f9e88e4..12efd4d 100644 --- a/src/x86/x64_test.go +++ b/src/x86/x86_test.go @@ -7,7 +7,7 @@ import ( "git.akyoto.dev/go/assert" ) -func TestX64(t *testing.T) { +func TestX86(t *testing.T) { assert.DeepEqual(t, x86.AlignStack(nil), []byte{0x48, 0x83, 0xE4, 0xF0}) assert.DeepEqual(t, x86.Call(nil, 1), []byte{0xE8, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x86.CallAtAddress(nil, 1), []byte{0xFF, 0x15, 0x01, 0x00, 0x00, 0x00}) From feacc27f495f61783ce138c9382af0cc861dbf6c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 00:40:41 +0100 Subject: [PATCH 0678/1012] Added more tests --- src/scanner/scanFile.go | 5 +-- src/types/types_test.go | 55 ++++++++++++++++++++++++++++ src/x86/{x64_test.go => x86_test.go} | 2 +- 3 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 src/types/types_test.go rename src/x86/{x64_test.go => x86_test.go} (96%) diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 79cf791..3b1bff2 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -26,9 +26,8 @@ func (s *Scanner) scanFile(path string, pkg string) error { } s.files <- file - i := 0 - for i < len(tokens) { + for i := 0; i < len(tokens); i++ { switch tokens[i].Kind { case token.NewLine: case token.Comment: @@ -49,8 +48,6 @@ func (s *Scanner) scanFile(path string, pkg string) error { if err != nil { return err } - - i++ } return nil diff --git a/src/types/types_test.go b/src/types/types_test.go new file mode 100644 index 0000000..d5b6ba8 --- /dev/null +++ b/src/types/types_test.go @@ -0,0 +1,55 @@ +package types_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/types" + "git.akyoto.dev/go/assert" +) + +func TestName(t *testing.T) { + assert.Equal(t, types.Int.Name(), "Int64") + assert.Equal(t, types.PointerAny.Name(), "Pointer") + assert.Equal(t, (&types.Pointer{To: types.Int}).Name(), "Pointer:Int64") +} + +func TestSize(t *testing.T) { + assert.Equal(t, types.Int.Size(), 8) + assert.Equal(t, types.Int8.Size(), 1) + assert.Equal(t, types.Int16.Size(), 2) + assert.Equal(t, types.Int32.Size(), 4) + assert.Equal(t, types.Int64.Size(), 8) + assert.Equal(t, types.PointerAny.Size(), 8) + assert.Equal(t, (&types.Pointer{To: types.Int}).Size(), 8) +} + +func TestStruct(t *testing.T) { + s := types.NewStruct("Test") + assert.Equal(t, s.Name(), "Test") + assert.Equal(t, s.Size(), 0) + field := &types.Field{Name: "TestField", TypeName: "Int8"} + s.AddField(field) + s.Update(map[string]types.Type{"Int8": types.Int8}) + assert.Equal(t, s.Size(), 1) + assert.Equal(t, s.FieldByName("TestField"), field) + assert.Nil(t, s.FieldByName("does-not-exist")) +} + +func TestBasics(t *testing.T) { + assert.True(t, types.Is(types.Int, types.Int)) + assert.True(t, types.Is(types.PointerAny, types.PointerAny)) + assert.False(t, types.Is(types.Int, types.Float)) + assert.False(t, types.Is(&types.Pointer{To: types.Int}, &types.Pointer{To: types.Float})) +} + +func TestSpecialCases(t *testing.T) { + // Case #1: + // For syscalls whose return type is `nil` we currently allow casting them to anything. + assert.True(t, types.Is(nil, types.Int)) + assert.True(t, types.Is(nil, types.Float)) + + // Case #2: + // A pointer pointing to a known type fulfills the requirement of a pointer to anything. + assert.True(t, types.Is(&types.Pointer{To: types.Int}, &types.Pointer{To: nil})) + assert.True(t, types.Is(&types.Pointer{To: types.Float}, &types.Pointer{To: nil})) +} diff --git a/src/x86/x64_test.go b/src/x86/x86_test.go similarity index 96% rename from src/x86/x64_test.go rename to src/x86/x86_test.go index f9e88e4..12efd4d 100644 --- a/src/x86/x64_test.go +++ b/src/x86/x86_test.go @@ -7,7 +7,7 @@ import ( "git.akyoto.dev/go/assert" ) -func TestX64(t *testing.T) { +func TestX86(t *testing.T) { assert.DeepEqual(t, x86.AlignStack(nil), []byte{0x48, 0x83, 0xE4, 0xF0}) assert.DeepEqual(t, x86.Call(nil, 1), []byte{0xE8, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x86.CallAtAddress(nil, 1), []byte{0xFF, 0x15, 0x01, 0x00, 0x00, 0x00}) From b02b72254205d66b5650aafa9cc16da8c0b1e0d2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 13:02:18 +0100 Subject: [PATCH 0679/1012] Removed unnecessary type conversions --- src/asmc/move.go | 2 +- src/asmc/resolvePointers.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/asmc/move.go b/src/asmc/move.go index 8d5a12d..11742b3 100644 --- a/src/asmc/move.go +++ b/src/asmc/move.go @@ -35,7 +35,7 @@ func (c *compiler) move(x asm.Instruction) { panic("unknown label") } - return Address(destination) + return destination }, }) } else { diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index a437555..9f62e54 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -80,7 +80,7 @@ restart: dataStart, _ := fs.Align(c.codeStart+Address(len(c.code)), config.Align) for _, pointer := range c.dataPointers { - address := config.BaseAddress + Address(dataStart) + pointer.Resolve() + address := config.BaseAddress + dataStart + pointer.Resolve() slice := c.code[pointer.Position : pointer.Position+4] binary.LittleEndian.PutUint32(slice, uint32(address)) } @@ -89,7 +89,7 @@ restart: importsStart, _ := fs.Align(dataStart+Address(len(c.data)), config.Align) for _, pointer := range c.dllPointers { - destination := Address(importsStart) + pointer.Resolve() + destination := importsStart + pointer.Resolve() delta := destination - Address(c.codeStart+pointer.Position+Address(pointer.Size)) slice := c.code[pointer.Position : pointer.Position+4] binary.LittleEndian.PutUint32(slice, uint32(delta)) From 01342f53187da2467c27a98329a97b8356244311 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 13:02:18 +0100 Subject: [PATCH 0680/1012] Removed unnecessary type conversions --- src/asmc/move.go | 2 +- src/asmc/resolvePointers.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/asmc/move.go b/src/asmc/move.go index 8d5a12d..11742b3 100644 --- a/src/asmc/move.go +++ b/src/asmc/move.go @@ -35,7 +35,7 @@ func (c *compiler) move(x asm.Instruction) { panic("unknown label") } - return Address(destination) + return destination }, }) } else { diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index a437555..9f62e54 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -80,7 +80,7 @@ restart: dataStart, _ := fs.Align(c.codeStart+Address(len(c.code)), config.Align) for _, pointer := range c.dataPointers { - address := config.BaseAddress + Address(dataStart) + pointer.Resolve() + address := config.BaseAddress + dataStart + pointer.Resolve() slice := c.code[pointer.Position : pointer.Position+4] binary.LittleEndian.PutUint32(slice, uint32(address)) } @@ -89,7 +89,7 @@ restart: importsStart, _ := fs.Align(dataStart+Address(len(c.data)), config.Align) for _, pointer := range c.dllPointers { - destination := Address(importsStart) + pointer.Resolve() + destination := importsStart + pointer.Resolve() delta := destination - Address(c.codeStart+pointer.Position+Address(pointer.Size)) slice := c.code[pointer.Position : pointer.Position+4] binary.LittleEndian.PutUint32(slice, uint32(delta)) From d71bbd51cf8fdfe5eb70360be55cb6e1e796bec7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 14:44:13 +0100 Subject: [PATCH 0681/1012] Added generic types to sizeof --- src/asmc/resolvePointers.go | 2 +- src/register/RegisterNumber.go | 2 +- src/sizeof/Signed.go | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index 9f62e54..748af18 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -16,7 +16,7 @@ restart: for i, pointer := range c.codePointers { address := pointer.Resolve() - if sizeof.Signed(int64(address)) > int(pointer.Size) { + if sizeof.Signed(address) > int(pointer.Size) { left := c.code[:pointer.Position-Address(pointer.OpSize)] right := c.code[pointer.Position+Address(pointer.Size):] size := pointer.Size + pointer.OpSize diff --git a/src/register/RegisterNumber.go b/src/register/RegisterNumber.go index a97dd8b..23aa23f 100644 --- a/src/register/RegisterNumber.go +++ b/src/register/RegisterNumber.go @@ -16,7 +16,7 @@ func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { } // If the number only needs 32 bits, we can encode the instruction. - if sizeof.Signed(int64(b)) <= 4 { + if sizeof.Signed(b) <= 4 { f.Assembler.RegisterNumber(mnemonic, a, b) f.postInstruction() return diff --git a/src/sizeof/Signed.go b/src/sizeof/Signed.go index a0fee4a..c3049a2 100644 --- a/src/sizeof/Signed.go +++ b/src/sizeof/Signed.go @@ -3,15 +3,17 @@ package sizeof import "math" // Signed tells you how many bytes are needed to encode this signed number. -func Signed(number int64) int { +func Signed[T int | int8 | int16 | int32 | int64](number T) int { + x := int64(number) + switch { - case number >= math.MinInt8 && number <= math.MaxInt8: + case x >= math.MinInt8 && x <= math.MaxInt8: return 1 - case number >= math.MinInt16 && number <= math.MaxInt16: + case x >= math.MinInt16 && x <= math.MaxInt16: return 2 - case number >= math.MinInt32 && number <= math.MaxInt32: + case x >= math.MinInt32 && x <= math.MaxInt32: return 4 default: From 91bafc0867efbd5176655cb942a6b9ff5edc654d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 14:44:13 +0100 Subject: [PATCH 0682/1012] Added generic types to sizeof --- src/asmc/resolvePointers.go | 2 +- src/register/RegisterNumber.go | 2 +- src/sizeof/Signed.go | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index 9f62e54..748af18 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -16,7 +16,7 @@ restart: for i, pointer := range c.codePointers { address := pointer.Resolve() - if sizeof.Signed(int64(address)) > int(pointer.Size) { + if sizeof.Signed(address) > int(pointer.Size) { left := c.code[:pointer.Position-Address(pointer.OpSize)] right := c.code[pointer.Position+Address(pointer.Size):] size := pointer.Size + pointer.OpSize diff --git a/src/register/RegisterNumber.go b/src/register/RegisterNumber.go index a97dd8b..23aa23f 100644 --- a/src/register/RegisterNumber.go +++ b/src/register/RegisterNumber.go @@ -16,7 +16,7 @@ func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { } // If the number only needs 32 bits, we can encode the instruction. - if sizeof.Signed(int64(b)) <= 4 { + if sizeof.Signed(b) <= 4 { f.Assembler.RegisterNumber(mnemonic, a, b) f.postInstruction() return diff --git a/src/sizeof/Signed.go b/src/sizeof/Signed.go index a0fee4a..c3049a2 100644 --- a/src/sizeof/Signed.go +++ b/src/sizeof/Signed.go @@ -3,15 +3,17 @@ package sizeof import "math" // Signed tells you how many bytes are needed to encode this signed number. -func Signed(number int64) int { +func Signed[T int | int8 | int16 | int32 | int64](number T) int { + x := int64(number) + switch { - case number >= math.MinInt8 && number <= math.MaxInt8: + case x >= math.MinInt8 && x <= math.MaxInt8: return 1 - case number >= math.MinInt16 && number <= math.MaxInt16: + case x >= math.MinInt16 && x <= math.MaxInt16: return 2 - case number >= math.MinInt32 && number <= math.MaxInt32: + case x >= math.MinInt32 && x <= math.MaxInt32: return 4 default: From 8357aefc01196c1652193a9d177464c0a6344c3c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 16:06:39 +0100 Subject: [PATCH 0683/1012] Implemented package specific structs --- examples/server/server.q | 9 +------- lib/sys/net_linux.q | 7 +++++++ src/build/Build.go | 4 ++-- src/compiler/Compile.go | 31 +++++++--------------------- src/core/CompileCall.go | 8 ++++---- src/core/CompileNew.go | 43 ++++++++++++++++++++++++++++++++++----- src/core/Function.go | 2 +- src/core/ResolveTypes.go | 5 +++-- src/core/TypeByName.go | 16 --------------- src/scanner/Scan.go | 8 ++++---- src/scanner/Scanner.go | 2 +- src/scanner/scanStruct.go | 4 ++-- src/types/ByName.go | 36 ++++++++++++++++++++++++++++++++ src/types/Struct.go | 20 +++++++++++------- src/types/types_test.go | 4 ++-- 15 files changed, 121 insertions(+), 78 deletions(-) create mode 100644 src/types/ByName.go diff --git a/examples/server/server.q b/examples/server/server.q index 852ae50..e9146c7 100644 --- a/examples/server/server.q +++ b/examples/server/server.q @@ -3,13 +3,6 @@ // [2] curl http://127.0.0.1:8080 import sys -struct sockaddr_in { - sin_family Int16 - sin_port Int16 - sin_addr Int64 - sin_zero Int64 -} - main() { socket := sys.socket(2, 1, 0) @@ -18,7 +11,7 @@ main() { sys.exit(1) } - addr := new(sockaddr_in) + addr := new(sys.sockaddr_in) addr.sin_family = 2 addr.sin_port = 0x901F addr.sin_addr = 0 diff --git a/lib/sys/net_linux.q b/lib/sys/net_linux.q index 00ed81c..91b0da6 100644 --- a/lib/sys/net_linux.q +++ b/lib/sys/net_linux.q @@ -1,3 +1,10 @@ +struct sockaddr_in { + sin_family Int16 + sin_port Int16 + sin_addr Int64 + sin_zero Int64 +} + socket(family Int, type Int, protocol Int) -> Int { return syscall(41, family, type, protocol) } diff --git a/src/build/Build.go b/src/build/Build.go index e3b620e..9784a97 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -23,8 +23,8 @@ func New(files ...string) *Build { // Run compiles the input files. func (build *Build) Run() (compiler.Result, error) { - files, functions, types, errors := scanner.Scan(build.Files) - return compiler.Compile(files, functions, types, errors) + files, functions, structs, errors := scanner.Scan(build.Files) + return compiler.Compile(files, functions, structs, errors) } // Executable returns the path to the executable. diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 328c9f2..57ccb04 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -10,22 +10,11 @@ import ( ) // Compile waits for the scan to finish and compiles all functions. -func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-chan types.Type, errs <-chan error) (Result, error) { +func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Struct, errs <-chan error) (Result, error) { result := Result{} allFiles := make([]*fs.File, 0, 8) allFunctions := map[string]*core.Function{} - - allTypes := map[string]types.Type{ - "Int": types.Int, - "Int64": types.Int64, - "Int32": types.Int32, - "Int16": types.Int16, - "Int8": types.Int8, - "Float": types.Float, - "Float64": types.Float64, - "Float32": types.Float32, - "Pointer": types.PointerAny, - } + allStructs := map[string]*types.Struct{} for functions != nil || files != nil || errs != nil { select { @@ -36,16 +25,16 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c } function.Functions = allFunctions - function.Types = allTypes + function.Structs = allStructs allFunctions[function.UniqueName] = function - case typ, ok := <-structs: + case structure, ok := <-structs: if !ok { structs = nil continue } - allTypes[typ.Name()] = typ + allStructs[structure.UniqueName] = structure case file, ok := <-files: if !ok { @@ -66,14 +55,8 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c } // Calculate size of structs - for _, typ := range allTypes { - structure, isStruct := typ.(*types.Struct) - - if !isStruct { - continue - } - - structure.Update(allTypes) + for _, structure := range allStructs { + structure.Update(allStructs) } // Resolve the types diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 9ac1b93..46b687c 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -30,15 +30,15 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { case "syscall": return nil, f.CompileSyscall(root) case "new": + typ, err := f.CompileNew(root) + return &Function{ Output: []*Output{ { - Type: &types.Pointer{ - To: f.Types[root.Children[1].Token.Text(f.File.Bytes)], - }, + Type: typ, }, }, - }, f.CompileNew(root) + }, err case "delete": return nil, f.CompileDelete(root) case "store": diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index d25818b..c0925b8 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -2,16 +2,49 @@ package core import ( "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" ) // CompileNew compiles a `new` function call which allocates a struct. -func (f *Function) CompileNew(root *expression.Expression) error { - parameters := root.Children[1:] - structName := parameters[0].Token.Text(f.File.Bytes) - typ := f.Types[structName] +func (f *Function) CompileNew(root *expression.Expression) (types.Type, error) { + var ( + parameters = root.Children[1:] + nameNode = parameters[0] + pkg = f.Package + name string + ) + + if nameNode.IsLeaf() { + name = nameNode.Token.Text(f.File.Bytes) + } else { + pkg = nameNode.Children[0].Token.Text(f.File.Bytes) + name = nameNode.Children[1].Token.Text(f.File.Bytes) + } + + if pkg != f.File.Package { + if f.File.Imports == nil { + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position) + } + + imp, exists := f.File.Imports[pkg] + + if !exists { + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position) + } + + imp.Used = true + } + + typ, exists := f.Structs[pkg+"."+name] + + if !exists { + return nil, errors.New(&errors.UnknownType{Name: name}, f.File, nameNode.Token.Position) + } + f.SaveRegister(f.CPU.Input[0]) f.RegisterNumber(asm.MOVE, f.CPU.Input[0], typ.Size()) f.CallSafe(f.Functions["mem.alloc"], f.CPU.Input[:1]) - return nil + return &types.Pointer{To: typ}, nil } diff --git a/src/core/Function.go b/src/core/Function.go index 7aeb189..a502ccb 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -19,7 +19,7 @@ type Function struct { Input []*Input Output []*Output Functions map[string]*Function - Types map[string]types.Type + Structs map[string]*types.Struct DLLs dll.List Err error deferred []func() diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index c779c13..5636fcd 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -4,6 +4,7 @@ import ( "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" "git.akyoto.dev/cli/q/src/x86" ) @@ -12,7 +13,7 @@ func (f *Function) ResolveTypes() error { for i, param := range f.Input { param.Name = param.tokens[0].Text(f.File.Bytes) typeName := param.tokens[1:].Text(f.File.Bytes) - param.Type = f.TypeByName(typeName) + param.Type = types.ByName(typeName, f.Package, f.Structs) if param.Type == nil { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) @@ -34,7 +35,7 @@ func (f *Function) ResolveTypes() error { for _, param := range f.Output { typeName := param.tokens.Text(f.File.Bytes) - param.Type = f.TypeByName(typeName) + param.Type = types.ByName(typeName, f.Package, f.Structs) if param.Type == nil { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) diff --git a/src/core/TypeByName.go b/src/core/TypeByName.go index fcad711..9a8bc95 100644 --- a/src/core/TypeByName.go +++ b/src/core/TypeByName.go @@ -1,17 +1 @@ package core - -import ( - "strings" - - "git.akyoto.dev/cli/q/src/types" -) - -// TypeByName returns the type with the given name or `nil` if it doesn't exist. -func (f *Function) TypeByName(name string) types.Type { - if strings.HasPrefix(name, "*") { - to := strings.TrimPrefix(name, "*") - return &types.Pointer{To: f.TypeByName(to)} - } - - return f.Types[name] -} diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go index a0ca92d..fdb3354 100644 --- a/src/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -10,11 +10,11 @@ import ( ) // Scan scans the list of files. -func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan types.Type, <-chan error) { +func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan *types.Struct, <-chan error) { scanner := Scanner{ files: make(chan *fs.File), functions: make(chan *core.Function), - types: make(chan types.Type), + structs: make(chan *types.Struct), errors: make(chan error), } @@ -24,9 +24,9 @@ func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan types. scanner.group.Wait() close(scanner.files) close(scanner.functions) - close(scanner.types) + close(scanner.structs) close(scanner.errors) }() - return scanner.files, scanner.functions, scanner.types, scanner.errors + return scanner.files, scanner.functions, scanner.structs, scanner.errors } diff --git a/src/scanner/Scanner.go b/src/scanner/Scanner.go index 13485f5..97847bd 100644 --- a/src/scanner/Scanner.go +++ b/src/scanner/Scanner.go @@ -12,7 +12,7 @@ import ( type Scanner struct { files chan *fs.File functions chan *core.Function - types chan types.Type + structs chan *types.Struct errors chan error queued sync.Map group sync.WaitGroup diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index d5fe98f..d471ce1 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -16,7 +16,7 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro } structName := tokens[i].Text(file.Bytes) - structure := types.NewStruct(structName) + structure := types.NewStruct(file.Package, structName) i++ @@ -54,6 +54,6 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) } - s.types <- structure + s.structs <- structure return i, nil } diff --git a/src/types/ByName.go b/src/types/ByName.go new file mode 100644 index 0000000..1f402c1 --- /dev/null +++ b/src/types/ByName.go @@ -0,0 +1,36 @@ +package types + +import ( + "strings" +) + +// ByName returns the type with the given name or `nil` if it doesn't exist. +func ByName(name string, pkg string, structs map[string]*Struct) Type { + if strings.HasPrefix(name, "*") { + to := strings.TrimPrefix(name, "*") + return &Pointer{To: ByName(to, pkg, structs)} + } + + switch name { + case "Int": + return Int + case "Int64": + return Int64 + case "Int32": + return Int32 + case "Int16": + return Int16 + case "Int8": + return Int8 + case "Float": + return Float + case "Float64": + return Float64 + case "Float32": + return Float32 + case "Pointer": + return PointerAny + } + + return structs[pkg+"."+name] +} diff --git a/src/types/Struct.go b/src/types/Struct.go index ac3d20e..c1250a4 100644 --- a/src/types/Struct.go +++ b/src/types/Struct.go @@ -2,14 +2,20 @@ package types // Struct is a structure in memory whose regions are addressable with named fields. type Struct struct { - name string - fields []*Field - size int + Package string + UniqueName string + name string + fields []*Field + size int } // NewStruct creates a new struct. -func NewStruct(name string) *Struct { - return &Struct{name: name} +func NewStruct(pkg string, name string) *Struct { + return &Struct{ + Package: pkg, + UniqueName: pkg + "." + name, + name: name, + } } // AddField adds a new field to the end of the struct. @@ -39,11 +45,11 @@ func (s *Struct) Size() int { } // Update updates the offsets and structure size. -func (s *Struct) Update(types map[string]Type) { +func (s *Struct) Update(allTypes map[string]*Struct) { s.size = 0 for _, field := range s.fields { - field.Type = types[field.TypeName] + field.Type = ByName(field.TypeName, s.Package, allTypes) field.Offset = s.size s.size += field.Type.Size() } diff --git a/src/types/types_test.go b/src/types/types_test.go index d5b6ba8..e134ad1 100644 --- a/src/types/types_test.go +++ b/src/types/types_test.go @@ -24,12 +24,12 @@ func TestSize(t *testing.T) { } func TestStruct(t *testing.T) { - s := types.NewStruct("Test") + s := types.NewStruct("main", "Test") assert.Equal(t, s.Name(), "Test") assert.Equal(t, s.Size(), 0) field := &types.Field{Name: "TestField", TypeName: "Int8"} s.AddField(field) - s.Update(map[string]types.Type{"Int8": types.Int8}) + s.Update(nil) assert.Equal(t, s.Size(), 1) assert.Equal(t, s.FieldByName("TestField"), field) assert.Nil(t, s.FieldByName("does-not-exist")) From 97cdcbd1cba9371e70bcb93d9e8c60a4664aedc4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 16:06:39 +0100 Subject: [PATCH 0684/1012] Implemented package specific structs --- examples/server/server.q | 9 +------- lib/sys/net_linux.q | 7 +++++++ src/build/Build.go | 4 ++-- src/compiler/Compile.go | 31 +++++++--------------------- src/core/CompileCall.go | 8 ++++---- src/core/CompileNew.go | 43 ++++++++++++++++++++++++++++++++++----- src/core/Function.go | 2 +- src/core/ResolveTypes.go | 5 +++-- src/core/TypeByName.go | 16 --------------- src/scanner/Scan.go | 8 ++++---- src/scanner/Scanner.go | 2 +- src/scanner/scanStruct.go | 4 ++-- src/types/ByName.go | 36 ++++++++++++++++++++++++++++++++ src/types/Struct.go | 20 +++++++++++------- src/types/types_test.go | 4 ++-- 15 files changed, 121 insertions(+), 78 deletions(-) create mode 100644 src/types/ByName.go diff --git a/examples/server/server.q b/examples/server/server.q index 852ae50..e9146c7 100644 --- a/examples/server/server.q +++ b/examples/server/server.q @@ -3,13 +3,6 @@ // [2] curl http://127.0.0.1:8080 import sys -struct sockaddr_in { - sin_family Int16 - sin_port Int16 - sin_addr Int64 - sin_zero Int64 -} - main() { socket := sys.socket(2, 1, 0) @@ -18,7 +11,7 @@ main() { sys.exit(1) } - addr := new(sockaddr_in) + addr := new(sys.sockaddr_in) addr.sin_family = 2 addr.sin_port = 0x901F addr.sin_addr = 0 diff --git a/lib/sys/net_linux.q b/lib/sys/net_linux.q index 00ed81c..91b0da6 100644 --- a/lib/sys/net_linux.q +++ b/lib/sys/net_linux.q @@ -1,3 +1,10 @@ +struct sockaddr_in { + sin_family Int16 + sin_port Int16 + sin_addr Int64 + sin_zero Int64 +} + socket(family Int, type Int, protocol Int) -> Int { return syscall(41, family, type, protocol) } diff --git a/src/build/Build.go b/src/build/Build.go index e3b620e..9784a97 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -23,8 +23,8 @@ func New(files ...string) *Build { // Run compiles the input files. func (build *Build) Run() (compiler.Result, error) { - files, functions, types, errors := scanner.Scan(build.Files) - return compiler.Compile(files, functions, types, errors) + files, functions, structs, errors := scanner.Scan(build.Files) + return compiler.Compile(files, functions, structs, errors) } // Executable returns the path to the executable. diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 328c9f2..57ccb04 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -10,22 +10,11 @@ import ( ) // Compile waits for the scan to finish and compiles all functions. -func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-chan types.Type, errs <-chan error) (Result, error) { +func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Struct, errs <-chan error) (Result, error) { result := Result{} allFiles := make([]*fs.File, 0, 8) allFunctions := map[string]*core.Function{} - - allTypes := map[string]types.Type{ - "Int": types.Int, - "Int64": types.Int64, - "Int32": types.Int32, - "Int16": types.Int16, - "Int8": types.Int8, - "Float": types.Float, - "Float64": types.Float64, - "Float32": types.Float32, - "Pointer": types.PointerAny, - } + allStructs := map[string]*types.Struct{} for functions != nil || files != nil || errs != nil { select { @@ -36,16 +25,16 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c } function.Functions = allFunctions - function.Types = allTypes + function.Structs = allStructs allFunctions[function.UniqueName] = function - case typ, ok := <-structs: + case structure, ok := <-structs: if !ok { structs = nil continue } - allTypes[typ.Name()] = typ + allStructs[structure.UniqueName] = structure case file, ok := <-files: if !ok { @@ -66,14 +55,8 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c } // Calculate size of structs - for _, typ := range allTypes { - structure, isStruct := typ.(*types.Struct) - - if !isStruct { - continue - } - - structure.Update(allTypes) + for _, structure := range allStructs { + structure.Update(allStructs) } // Resolve the types diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 9ac1b93..46b687c 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -30,15 +30,15 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { case "syscall": return nil, f.CompileSyscall(root) case "new": + typ, err := f.CompileNew(root) + return &Function{ Output: []*Output{ { - Type: &types.Pointer{ - To: f.Types[root.Children[1].Token.Text(f.File.Bytes)], - }, + Type: typ, }, }, - }, f.CompileNew(root) + }, err case "delete": return nil, f.CompileDelete(root) case "store": diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index d25818b..c0925b8 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -2,16 +2,49 @@ package core import ( "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" ) // CompileNew compiles a `new` function call which allocates a struct. -func (f *Function) CompileNew(root *expression.Expression) error { - parameters := root.Children[1:] - structName := parameters[0].Token.Text(f.File.Bytes) - typ := f.Types[structName] +func (f *Function) CompileNew(root *expression.Expression) (types.Type, error) { + var ( + parameters = root.Children[1:] + nameNode = parameters[0] + pkg = f.Package + name string + ) + + if nameNode.IsLeaf() { + name = nameNode.Token.Text(f.File.Bytes) + } else { + pkg = nameNode.Children[0].Token.Text(f.File.Bytes) + name = nameNode.Children[1].Token.Text(f.File.Bytes) + } + + if pkg != f.File.Package { + if f.File.Imports == nil { + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position) + } + + imp, exists := f.File.Imports[pkg] + + if !exists { + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position) + } + + imp.Used = true + } + + typ, exists := f.Structs[pkg+"."+name] + + if !exists { + return nil, errors.New(&errors.UnknownType{Name: name}, f.File, nameNode.Token.Position) + } + f.SaveRegister(f.CPU.Input[0]) f.RegisterNumber(asm.MOVE, f.CPU.Input[0], typ.Size()) f.CallSafe(f.Functions["mem.alloc"], f.CPU.Input[:1]) - return nil + return &types.Pointer{To: typ}, nil } diff --git a/src/core/Function.go b/src/core/Function.go index 7aeb189..a502ccb 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -19,7 +19,7 @@ type Function struct { Input []*Input Output []*Output Functions map[string]*Function - Types map[string]types.Type + Structs map[string]*types.Struct DLLs dll.List Err error deferred []func() diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index c779c13..5636fcd 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -4,6 +4,7 @@ import ( "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/scope" "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" "git.akyoto.dev/cli/q/src/x86" ) @@ -12,7 +13,7 @@ func (f *Function) ResolveTypes() error { for i, param := range f.Input { param.Name = param.tokens[0].Text(f.File.Bytes) typeName := param.tokens[1:].Text(f.File.Bytes) - param.Type = f.TypeByName(typeName) + param.Type = types.ByName(typeName, f.Package, f.Structs) if param.Type == nil { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) @@ -34,7 +35,7 @@ func (f *Function) ResolveTypes() error { for _, param := range f.Output { typeName := param.tokens.Text(f.File.Bytes) - param.Type = f.TypeByName(typeName) + param.Type = types.ByName(typeName, f.Package, f.Structs) if param.Type == nil { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) diff --git a/src/core/TypeByName.go b/src/core/TypeByName.go index fcad711..9a8bc95 100644 --- a/src/core/TypeByName.go +++ b/src/core/TypeByName.go @@ -1,17 +1 @@ package core - -import ( - "strings" - - "git.akyoto.dev/cli/q/src/types" -) - -// TypeByName returns the type with the given name or `nil` if it doesn't exist. -func (f *Function) TypeByName(name string) types.Type { - if strings.HasPrefix(name, "*") { - to := strings.TrimPrefix(name, "*") - return &types.Pointer{To: f.TypeByName(to)} - } - - return f.Types[name] -} diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go index a0ca92d..fdb3354 100644 --- a/src/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -10,11 +10,11 @@ import ( ) // Scan scans the list of files. -func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan types.Type, <-chan error) { +func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan *types.Struct, <-chan error) { scanner := Scanner{ files: make(chan *fs.File), functions: make(chan *core.Function), - types: make(chan types.Type), + structs: make(chan *types.Struct), errors: make(chan error), } @@ -24,9 +24,9 @@ func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan types. scanner.group.Wait() close(scanner.files) close(scanner.functions) - close(scanner.types) + close(scanner.structs) close(scanner.errors) }() - return scanner.files, scanner.functions, scanner.types, scanner.errors + return scanner.files, scanner.functions, scanner.structs, scanner.errors } diff --git a/src/scanner/Scanner.go b/src/scanner/Scanner.go index 13485f5..97847bd 100644 --- a/src/scanner/Scanner.go +++ b/src/scanner/Scanner.go @@ -12,7 +12,7 @@ import ( type Scanner struct { files chan *fs.File functions chan *core.Function - types chan types.Type + structs chan *types.Struct errors chan error queued sync.Map group sync.WaitGroup diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index d5fe98f..d471ce1 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -16,7 +16,7 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro } structName := tokens[i].Text(file.Bytes) - structure := types.NewStruct(structName) + structure := types.NewStruct(file.Package, structName) i++ @@ -54,6 +54,6 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) } - s.types <- structure + s.structs <- structure return i, nil } diff --git a/src/types/ByName.go b/src/types/ByName.go new file mode 100644 index 0000000..1f402c1 --- /dev/null +++ b/src/types/ByName.go @@ -0,0 +1,36 @@ +package types + +import ( + "strings" +) + +// ByName returns the type with the given name or `nil` if it doesn't exist. +func ByName(name string, pkg string, structs map[string]*Struct) Type { + if strings.HasPrefix(name, "*") { + to := strings.TrimPrefix(name, "*") + return &Pointer{To: ByName(to, pkg, structs)} + } + + switch name { + case "Int": + return Int + case "Int64": + return Int64 + case "Int32": + return Int32 + case "Int16": + return Int16 + case "Int8": + return Int8 + case "Float": + return Float + case "Float64": + return Float64 + case "Float32": + return Float32 + case "Pointer": + return PointerAny + } + + return structs[pkg+"."+name] +} diff --git a/src/types/Struct.go b/src/types/Struct.go index ac3d20e..c1250a4 100644 --- a/src/types/Struct.go +++ b/src/types/Struct.go @@ -2,14 +2,20 @@ package types // Struct is a structure in memory whose regions are addressable with named fields. type Struct struct { - name string - fields []*Field - size int + Package string + UniqueName string + name string + fields []*Field + size int } // NewStruct creates a new struct. -func NewStruct(name string) *Struct { - return &Struct{name: name} +func NewStruct(pkg string, name string) *Struct { + return &Struct{ + Package: pkg, + UniqueName: pkg + "." + name, + name: name, + } } // AddField adds a new field to the end of the struct. @@ -39,11 +45,11 @@ func (s *Struct) Size() int { } // Update updates the offsets and structure size. -func (s *Struct) Update(types map[string]Type) { +func (s *Struct) Update(allTypes map[string]*Struct) { s.size = 0 for _, field := range s.fields { - field.Type = types[field.TypeName] + field.Type = ByName(field.TypeName, s.Package, allTypes) field.Offset = s.size s.size += field.Type.Size() } diff --git a/src/types/types_test.go b/src/types/types_test.go index d5b6ba8..e134ad1 100644 --- a/src/types/types_test.go +++ b/src/types/types_test.go @@ -24,12 +24,12 @@ func TestSize(t *testing.T) { } func TestStruct(t *testing.T) { - s := types.NewStruct("Test") + s := types.NewStruct("main", "Test") assert.Equal(t, s.Name(), "Test") assert.Equal(t, s.Size(), 0) field := &types.Field{Name: "TestField", TypeName: "Int8"} s.AddField(field) - s.Update(map[string]types.Type{"Int8": types.Int8}) + s.Update(nil) assert.Equal(t, s.Size(), 1) assert.Equal(t, s.FieldByName("TestField"), field) assert.Nil(t, s.FieldByName("does-not-exist")) From b2030c506cfa3bbe32e11e59b3d798563bf9e31a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 16:29:56 +0100 Subject: [PATCH 0685/1012] Improved error handling for struct types --- lib/sys/net_linux.q | 2 +- lib/sys/time_linux.q | 7 ++++++- lib/time/time.q | 2 +- lib/time/timespec.q | 4 ---- src/types/ByName.go | 16 ++++++++++++++-- 5 files changed, 22 insertions(+), 9 deletions(-) delete mode 100644 lib/time/timespec.q diff --git a/lib/sys/net_linux.q b/lib/sys/net_linux.q index 91b0da6..dc6b3eb 100644 --- a/lib/sys/net_linux.q +++ b/lib/sys/net_linux.q @@ -13,7 +13,7 @@ accept(fd Int, address Pointer, length Int) -> Int { return syscall(43, fd, address, length) } -bind(fd Int, address Pointer, length Int) -> Int { +bind(fd Int, address *sockaddr_in, length Int) -> Int { return syscall(49, fd, address, length) } diff --git a/lib/sys/time_linux.q b/lib/sys/time_linux.q index 6727476..f2957d3 100644 --- a/lib/sys/time_linux.q +++ b/lib/sys/time_linux.q @@ -1,3 +1,8 @@ -nanosleep(duration Pointer) -> Int { +struct timespec { + seconds Int + nanoseconds Int +} + +nanosleep(duration *timespec) -> Int { return syscall(35, duration, 0) } \ No newline at end of file diff --git a/lib/time/time.q b/lib/time/time.q index bd22148..b39dbe8 100644 --- a/lib/time/time.q +++ b/lib/time/time.q @@ -7,7 +7,7 @@ sleep(nanoseconds Int) { seconds, nanoseconds = nanoseconds / 1000000000 } - duration := new(timespec) + duration := new(sys.timespec) duration.seconds = seconds duration.nanoseconds = nanoseconds sys.nanosleep(duration) diff --git a/lib/time/timespec.q b/lib/time/timespec.q deleted file mode 100644 index 84d53d6..0000000 --- a/lib/time/timespec.q +++ /dev/null @@ -1,4 +0,0 @@ -struct timespec { - seconds Int - nanoseconds Int -} \ No newline at end of file diff --git a/src/types/ByName.go b/src/types/ByName.go index 1f402c1..765fb8f 100644 --- a/src/types/ByName.go +++ b/src/types/ByName.go @@ -8,7 +8,13 @@ import ( func ByName(name string, pkg string, structs map[string]*Struct) Type { if strings.HasPrefix(name, "*") { to := strings.TrimPrefix(name, "*") - return &Pointer{To: ByName(to, pkg, structs)} + typ := ByName(to, pkg, structs) + + if typ != nil { + return &Pointer{To: typ} + } + + return nil } switch name { @@ -32,5 +38,11 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { return PointerAny } - return structs[pkg+"."+name] + typ, exists := structs[pkg+"."+name] + + if !exists { + return nil + } + + return typ } From 526385280a21b23e432d734a41c6054888ccd5ea Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 16:29:56 +0100 Subject: [PATCH 0686/1012] Improved error handling for struct types --- lib/sys/net_linux.q | 2 +- lib/sys/time_linux.q | 7 ++++++- lib/time/time.q | 2 +- lib/time/timespec.q | 4 ---- src/types/ByName.go | 16 ++++++++++++++-- 5 files changed, 22 insertions(+), 9 deletions(-) delete mode 100644 lib/time/timespec.q diff --git a/lib/sys/net_linux.q b/lib/sys/net_linux.q index 91b0da6..dc6b3eb 100644 --- a/lib/sys/net_linux.q +++ b/lib/sys/net_linux.q @@ -13,7 +13,7 @@ accept(fd Int, address Pointer, length Int) -> Int { return syscall(43, fd, address, length) } -bind(fd Int, address Pointer, length Int) -> Int { +bind(fd Int, address *sockaddr_in, length Int) -> Int { return syscall(49, fd, address, length) } diff --git a/lib/sys/time_linux.q b/lib/sys/time_linux.q index 6727476..f2957d3 100644 --- a/lib/sys/time_linux.q +++ b/lib/sys/time_linux.q @@ -1,3 +1,8 @@ -nanosleep(duration Pointer) -> Int { +struct timespec { + seconds Int + nanoseconds Int +} + +nanosleep(duration *timespec) -> Int { return syscall(35, duration, 0) } \ No newline at end of file diff --git a/lib/time/time.q b/lib/time/time.q index bd22148..b39dbe8 100644 --- a/lib/time/time.q +++ b/lib/time/time.q @@ -7,7 +7,7 @@ sleep(nanoseconds Int) { seconds, nanoseconds = nanoseconds / 1000000000 } - duration := new(timespec) + duration := new(sys.timespec) duration.seconds = seconds duration.nanoseconds = nanoseconds sys.nanosleep(duration) diff --git a/lib/time/timespec.q b/lib/time/timespec.q deleted file mode 100644 index 84d53d6..0000000 --- a/lib/time/timespec.q +++ /dev/null @@ -1,4 +0,0 @@ -struct timespec { - seconds Int - nanoseconds Int -} \ No newline at end of file diff --git a/src/types/ByName.go b/src/types/ByName.go index 1f402c1..765fb8f 100644 --- a/src/types/ByName.go +++ b/src/types/ByName.go @@ -8,7 +8,13 @@ import ( func ByName(name string, pkg string, structs map[string]*Struct) Type { if strings.HasPrefix(name, "*") { to := strings.TrimPrefix(name, "*") - return &Pointer{To: ByName(to, pkg, structs)} + typ := ByName(to, pkg, structs) + + if typ != nil { + return &Pointer{To: typ} + } + + return nil } switch name { @@ -32,5 +38,11 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { return PointerAny } - return structs[pkg+"."+name] + typ, exists := structs[pkg+"."+name] + + if !exists { + return nil + } + + return typ } From 971b7f4b76717eaed32d02e03d242f549cf99d69 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 17:34:11 +0100 Subject: [PATCH 0687/1012] Improved server example --- examples/server/server.q | 11 ++--------- lib/net/net_linux.q | 10 ++++++++++ lib/net/net_mac.q | 10 ++++++++++ lib/sys/net_mac.q | 10 +++++++++- src/core/CompileDelete.go | 6 ++++++ 5 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 lib/net/net_linux.q create mode 100644 lib/net/net_mac.q diff --git a/examples/server/server.q b/examples/server/server.q index e9146c7..17252a3 100644 --- a/examples/server/server.q +++ b/examples/server/server.q @@ -1,6 +1,7 @@ // Open server and client in 2 terminals: // [1] q run examples/server // [2] curl http://127.0.0.1:8080 +import net import sys main() { @@ -11,19 +12,11 @@ main() { sys.exit(1) } - addr := new(sys.sockaddr_in) - addr.sin_family = 2 - addr.sin_port = 0x901F - addr.sin_addr = 0 - addr.sin_zero = 0 - - if sys.bind(socket, addr, 20) != 0 { + if net.bind(socket, 0x901F) != 0 { sys.write(1, "bind error\n", 11) sys.exit(1) } - delete(addr) - if sys.listen(socket, 128) != 0 { sys.write(1, "listen error\n", 13) sys.exit(1) diff --git a/lib/net/net_linux.q b/lib/net/net_linux.q new file mode 100644 index 0000000..1692469 --- /dev/null +++ b/lib/net/net_linux.q @@ -0,0 +1,10 @@ +import sys + +bind(socket Int, port Int) -> Int { + addr := new(sys.sockaddr_in) + addr.sin_family = 2 + addr.sin_port = port + err := sys.bind(socket, addr, 20) + delete(addr) + return err +} \ No newline at end of file diff --git a/lib/net/net_mac.q b/lib/net/net_mac.q new file mode 100644 index 0000000..78a32f5 --- /dev/null +++ b/lib/net/net_mac.q @@ -0,0 +1,10 @@ +import sys + +bind(socket Int, port Int) -> Int { + addr := new(sys.sockaddr_in_bsd) + addr.sin_family = 2 + addr.sin_port = port + err := sys.bind(socket, addr, 20) + delete(addr) + return err +} \ No newline at end of file diff --git a/lib/sys/net_mac.q b/lib/sys/net_mac.q index 667c9ac..9a98067 100644 --- a/lib/sys/net_mac.q +++ b/lib/sys/net_mac.q @@ -1,3 +1,11 @@ +struct sockaddr_in_bsd { + sin_len Int8 + sin_family Int8 + sin_port Int16 + sin_addr Int64 + sin_zero Int64 +} + socket(family Int, type Int, protocol Int) -> Int { return syscall(0x2000061, family, type, protocol) } @@ -6,7 +14,7 @@ accept(fd Int, address Pointer, length Int) -> Int { return syscall(0x200001E, fd, address, length) } -bind(fd Int, address Pointer, length Int) -> Int { +bind(fd Int, address *sockaddr_in_bsd, length Int) -> Int { return syscall(0x2000068, fd, address, length) } diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index 3d144f8..6eae244 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -2,6 +2,7 @@ package core import ( "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/types" ) @@ -11,6 +12,11 @@ func (f *Function) CompileDelete(root *expression.Expression) error { parameters := root.Children[1:] variableName := parameters[0].Token.Text(f.File.Bytes) variable := f.VariableByName(variableName) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: variableName}, f.File, parameters[0].Token.Position) + } + defer f.UseVariable(variable) f.SaveRegister(f.CPU.Input[0]) f.SaveRegister(f.CPU.Input[1]) From d0d096bcd2385793e337d742331b19707cccee85 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 17:34:11 +0100 Subject: [PATCH 0688/1012] Improved server example --- examples/server/server.q | 11 ++--------- lib/net/net_linux.q | 10 ++++++++++ lib/net/net_mac.q | 10 ++++++++++ lib/sys/net_mac.q | 10 +++++++++- src/core/CompileDelete.go | 6 ++++++ 5 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 lib/net/net_linux.q create mode 100644 lib/net/net_mac.q diff --git a/examples/server/server.q b/examples/server/server.q index e9146c7..17252a3 100644 --- a/examples/server/server.q +++ b/examples/server/server.q @@ -1,6 +1,7 @@ // Open server and client in 2 terminals: // [1] q run examples/server // [2] curl http://127.0.0.1:8080 +import net import sys main() { @@ -11,19 +12,11 @@ main() { sys.exit(1) } - addr := new(sys.sockaddr_in) - addr.sin_family = 2 - addr.sin_port = 0x901F - addr.sin_addr = 0 - addr.sin_zero = 0 - - if sys.bind(socket, addr, 20) != 0 { + if net.bind(socket, 0x901F) != 0 { sys.write(1, "bind error\n", 11) sys.exit(1) } - delete(addr) - if sys.listen(socket, 128) != 0 { sys.write(1, "listen error\n", 13) sys.exit(1) diff --git a/lib/net/net_linux.q b/lib/net/net_linux.q new file mode 100644 index 0000000..1692469 --- /dev/null +++ b/lib/net/net_linux.q @@ -0,0 +1,10 @@ +import sys + +bind(socket Int, port Int) -> Int { + addr := new(sys.sockaddr_in) + addr.sin_family = 2 + addr.sin_port = port + err := sys.bind(socket, addr, 20) + delete(addr) + return err +} \ No newline at end of file diff --git a/lib/net/net_mac.q b/lib/net/net_mac.q new file mode 100644 index 0000000..78a32f5 --- /dev/null +++ b/lib/net/net_mac.q @@ -0,0 +1,10 @@ +import sys + +bind(socket Int, port Int) -> Int { + addr := new(sys.sockaddr_in_bsd) + addr.sin_family = 2 + addr.sin_port = port + err := sys.bind(socket, addr, 20) + delete(addr) + return err +} \ No newline at end of file diff --git a/lib/sys/net_mac.q b/lib/sys/net_mac.q index 667c9ac..9a98067 100644 --- a/lib/sys/net_mac.q +++ b/lib/sys/net_mac.q @@ -1,3 +1,11 @@ +struct sockaddr_in_bsd { + sin_len Int8 + sin_family Int8 + sin_port Int16 + sin_addr Int64 + sin_zero Int64 +} + socket(family Int, type Int, protocol Int) -> Int { return syscall(0x2000061, family, type, protocol) } @@ -6,7 +14,7 @@ accept(fd Int, address Pointer, length Int) -> Int { return syscall(0x200001E, fd, address, length) } -bind(fd Int, address Pointer, length Int) -> Int { +bind(fd Int, address *sockaddr_in_bsd, length Int) -> Int { return syscall(0x2000068, fd, address, length) } diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index 3d144f8..6eae244 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -2,6 +2,7 @@ package core import ( "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/types" ) @@ -11,6 +12,11 @@ func (f *Function) CompileDelete(root *expression.Expression) error { parameters := root.Children[1:] variableName := parameters[0].Token.Text(f.File.Bytes) variable := f.VariableByName(variableName) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: variableName}, f.File, parameters[0].Token.Position) + } + defer f.UseVariable(variable) f.SaveRegister(f.CPU.Input[0]) f.SaveRegister(f.CPU.Input[1]) From 084edba480ef5940acc4bbccbf90fa9032234d0c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 19:09:36 +0100 Subject: [PATCH 0689/1012] Fixed server example on Mac --- lib/net/net_mac.q | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/net/net_mac.q b/lib/net/net_mac.q index 78a32f5..8f2e19d 100644 --- a/lib/net/net_mac.q +++ b/lib/net/net_mac.q @@ -4,7 +4,7 @@ bind(socket Int, port Int) -> Int { addr := new(sys.sockaddr_in_bsd) addr.sin_family = 2 addr.sin_port = port - err := sys.bind(socket, addr, 20) + err := sys.bind(socket, addr, 16) delete(addr) return err } \ No newline at end of file From 9002116ee47d79e1f97422e1fcb6e0cfcb81b0f5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 19:09:36 +0100 Subject: [PATCH 0690/1012] Fixed server example on Mac --- lib/net/net_mac.q | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/net/net_mac.q b/lib/net/net_mac.q index 78a32f5..8f2e19d 100644 --- a/lib/net/net_mac.q +++ b/lib/net/net_mac.q @@ -4,7 +4,7 @@ bind(socket Int, port Int) -> Int { addr := new(sys.sockaddr_in_bsd) addr.sin_family = 2 addr.sin_port = port - err := sys.bind(socket, addr, 20) + err := sys.bind(socket, addr, 16) delete(addr) return err } \ No newline at end of file From 79bb606e8dc405f4a65688cd1016485a1b2e5f59 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 23:59:19 +0100 Subject: [PATCH 0691/1012] Implemented network byte order conversion --- examples/server/server.q | 2 +- lib/net/htons.q | 3 +++ lib/net/net_linux.q | 2 +- lib/net/net_mac.q | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 lib/net/htons.q diff --git a/examples/server/server.q b/examples/server/server.q index 17252a3..a729032 100644 --- a/examples/server/server.q +++ b/examples/server/server.q @@ -12,7 +12,7 @@ main() { sys.exit(1) } - if net.bind(socket, 0x901F) != 0 { + if net.bind(socket, 8080) != 0 { sys.write(1, "bind error\n", 11) sys.exit(1) } diff --git a/lib/net/htons.q b/lib/net/htons.q new file mode 100644 index 0000000..c491d9b --- /dev/null +++ b/lib/net/htons.q @@ -0,0 +1,3 @@ +htons(num Int) -> Int { + return ((num & 0xFF) << 8) | (num >> 8) +} \ No newline at end of file diff --git a/lib/net/net_linux.q b/lib/net/net_linux.q index 1692469..4f226aa 100644 --- a/lib/net/net_linux.q +++ b/lib/net/net_linux.q @@ -3,7 +3,7 @@ import sys bind(socket Int, port Int) -> Int { addr := new(sys.sockaddr_in) addr.sin_family = 2 - addr.sin_port = port + addr.sin_port = htons(port) err := sys.bind(socket, addr, 20) delete(addr) return err diff --git a/lib/net/net_mac.q b/lib/net/net_mac.q index 8f2e19d..5a6cb00 100644 --- a/lib/net/net_mac.q +++ b/lib/net/net_mac.q @@ -3,7 +3,7 @@ import sys bind(socket Int, port Int) -> Int { addr := new(sys.sockaddr_in_bsd) addr.sin_family = 2 - addr.sin_port = port + addr.sin_port = htons(port) err := sys.bind(socket, addr, 16) delete(addr) return err From 1ff56e0856a7d8eb0b26cc0620c1a53a3f6021af Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Feb 2025 23:59:19 +0100 Subject: [PATCH 0692/1012] Implemented network byte order conversion --- examples/server/server.q | 2 +- lib/net/htons.q | 3 +++ lib/net/net_linux.q | 2 +- lib/net/net_mac.q | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 lib/net/htons.q diff --git a/examples/server/server.q b/examples/server/server.q index 17252a3..a729032 100644 --- a/examples/server/server.q +++ b/examples/server/server.q @@ -12,7 +12,7 @@ main() { sys.exit(1) } - if net.bind(socket, 0x901F) != 0 { + if net.bind(socket, 8080) != 0 { sys.write(1, "bind error\n", 11) sys.exit(1) } diff --git a/lib/net/htons.q b/lib/net/htons.q new file mode 100644 index 0000000..c491d9b --- /dev/null +++ b/lib/net/htons.q @@ -0,0 +1,3 @@ +htons(num Int) -> Int { + return ((num & 0xFF) << 8) | (num >> 8) +} \ No newline at end of file diff --git a/lib/net/net_linux.q b/lib/net/net_linux.q index 1692469..4f226aa 100644 --- a/lib/net/net_linux.q +++ b/lib/net/net_linux.q @@ -3,7 +3,7 @@ import sys bind(socket Int, port Int) -> Int { addr := new(sys.sockaddr_in) addr.sin_family = 2 - addr.sin_port = port + addr.sin_port = htons(port) err := sys.bind(socket, addr, 20) delete(addr) return err diff --git a/lib/net/net_mac.q b/lib/net/net_mac.q index 8f2e19d..5a6cb00 100644 --- a/lib/net/net_mac.q +++ b/lib/net/net_mac.q @@ -3,7 +3,7 @@ import sys bind(socket Int, port Int) -> Int { addr := new(sys.sockaddr_in_bsd) addr.sin_family = 2 - addr.sin_port = port + addr.sin_port = htons(port) err := sys.bind(socket, addr, 16) delete(addr) return err From 3043e4e2c41e5f1eeb6b7c7b846bb38803038763 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Feb 2025 14:14:41 +0100 Subject: [PATCH 0693/1012] Implemented length storage of allocated memory --- examples/collatz/collatz.q | 4 ++-- examples/fizzbuzz/fizzbuzz.q | 14 +++++--------- examples/hello/hello.q | 8 ++------ examples/prime/prime.q | 4 ++-- examples/server/server.q | 13 +++++++------ examples/shell/shell.q | 5 +++-- examples/thread/thread.q | 5 +++-- lib/io/io.q | 21 +++++++++++++++++++++ lib/mem/alloc_linux.q | 9 ++++++++- lib/mem/alloc_mac.q | 9 ++++++++- lib/mem/alloc_windows.q | 9 ++++++++- lib/sys/io_windows.q | 8 +++++++- src/asm/Assembler.go | 2 +- src/asm/Memory.go | 2 +- src/asmc/resolvePointers.go | 2 +- src/core/CompileAssignArray.go | 2 +- src/core/CompileAssignField.go | 2 +- src/core/CompileCall.go | 8 ++++++++ src/core/CompileLen.go | 26 ++++++++++++++++++++++++++ src/core/ExpressionToRegister.go | 4 ++-- src/core/TokenToRegister.go | 9 ++++++++- src/register/SaveRegister.go | 1 + src/x86/Load.go | 2 +- src/x86/Load_test.go | 2 +- src/x86/Store.go | 4 ++-- src/x86/Store_test.go | 4 ++-- src/x86/memoryAccess.go | 4 ++-- 27 files changed, 134 insertions(+), 49 deletions(-) create mode 100644 lib/io/io.q create mode 100644 src/core/CompileLen.go diff --git a/examples/collatz/collatz.q b/examples/collatz/collatz.q index f7d9037..5f71d2c 100644 --- a/examples/collatz/collatz.q +++ b/examples/collatz/collatz.q @@ -1,5 +1,5 @@ +import io import log -import sys main() { collatz(12) @@ -19,6 +19,6 @@ collatz(x Int) { return } - sys.write(1, " ", 1) + io.out(" ") } } \ No newline at end of file diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q index 5cca2d5..7823731 100644 --- a/examples/fizzbuzz/fizzbuzz.q +++ b/examples/fizzbuzz/fizzbuzz.q @@ -1,5 +1,5 @@ +import io import log -import sys main() { fizzbuzz(15) @@ -10,9 +10,9 @@ fizzbuzz(n Int) { loop { switch { - x % 15 == 0 { print("FizzBuzz", 8) } - x % 5 == 0 { print("Buzz", 4) } - x % 3 == 0 { print("Fizz", 4) } + x % 15 == 0 { io.out("FizzBuzz") } + x % 5 == 0 { io.out("Buzz") } + x % 3 == 0 { io.out("Fizz") } _ { log.number(x) } } @@ -22,10 +22,6 @@ fizzbuzz(n Int) { return } - print(" ", 1) + io.out(" ") } -} - -print(address Pointer, length Int) { - sys.write(1, address, length) } \ No newline at end of file diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 1d9220d..0475212 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,9 +1,5 @@ -import sys +import io main() { - print("Hello\n", 6) -} - -print(address Pointer, length Int) { - sys.write(1, address, length) + io.out("Hello\n") } \ No newline at end of file diff --git a/examples/prime/prime.q b/examples/prime/prime.q index 836c831..76e3d77 100644 --- a/examples/prime/prime.q +++ b/examples/prime/prime.q @@ -1,5 +1,5 @@ +import io import log -import sys main() { n := 100 @@ -12,7 +12,7 @@ main() { if isPrime(i) == 1 { if i != 2 { - sys.write(1, " ", 1) + io.out(" ") } log.number(i) diff --git a/examples/server/server.q b/examples/server/server.q index a729032..6ac4ee3 100644 --- a/examples/server/server.q +++ b/examples/server/server.q @@ -1,6 +1,7 @@ // Open server and client in 2 terminals: // [1] q run examples/server // [2] curl http://127.0.0.1:8080 +import io import net import sys @@ -8,30 +9,30 @@ main() { socket := sys.socket(2, 1, 0) if socket < 0 { - sys.write(1, "socket error\n", 13) + io.error("socket error\n") sys.exit(1) } if net.bind(socket, 8080) != 0 { - sys.write(1, "bind error\n", 11) + io.error("bind error\n") sys.exit(1) } if sys.listen(socket, 128) != 0 { - sys.write(1, "listen error\n", 13) + io.error("listen error\n") sys.exit(1) } - sys.write(1, "listening...\n", 13) + io.out("listening...\n") loop { conn := sys.accept(socket, 0, 0) if conn >= 0 { - sys.write(conn, "HTTP/1.0 200 OK\r\nContent-Length: 6\r\n\r\nHello\n", 44) + io.write(conn, "HTTP/1.0 200 OK\r\nContent-Length: 6\r\n\r\nHello\n") sys.close(conn) } else { - sys.write(1, "error\n", 6) + io.error("accept error\n") } } } \ No newline at end of file diff --git a/examples/shell/shell.q b/examples/shell/shell.q index 0b094f6..3353391 100644 --- a/examples/shell/shell.q +++ b/examples/shell/shell.q @@ -1,3 +1,4 @@ +import io import mem import sys @@ -9,8 +10,8 @@ main() { info := mem.alloc(24) loop { - sys.write(1, "$ ", 2) - n := sys.read(0, command, length) + io.out("$ ") + n := io.in(command) if n <= 0 { return diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 929ebbb..22c9d05 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -1,3 +1,4 @@ +import io import sys import thread import time @@ -10,8 +11,8 @@ main() { } work() { - sys.write(1, "[ ] start\n", 10) + io.out("[ ] start\n") time.sleep(10 * 1000 * 1000) - sys.write(1, "[x] end\n", 8) + io.out("[x] end\n") sys.exit(0) } \ No newline at end of file diff --git a/lib/io/io.q b/lib/io/io.q new file mode 100644 index 0000000..4349f94 --- /dev/null +++ b/lib/io/io.q @@ -0,0 +1,21 @@ +import sys + +in(buffer Pointer) -> Int { + return sys.read(0, buffer, len(buffer)) +} + +out(message Pointer) -> Int { + return sys.write(1, message, len(message)) +} + +error(message Pointer) -> Int { + return sys.write(2, message, len(message)) +} + +read(fd Int, buffer Pointer) -> Int { + return sys.read(fd, buffer, len(buffer)) +} + +write(fd Int, message Pointer) -> Int { + return sys.write(fd, message, len(message)) +} \ No newline at end of file diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_linux.q index 70929fe..df8fe5b 100644 --- a/lib/mem/alloc_linux.q +++ b/lib/mem/alloc_linux.q @@ -1,5 +1,12 @@ import sys alloc(length Int) -> Pointer { - return sys.mmap(0, length, 0x1|0x2, 0x02|0x20) + x := sys.mmap(0, length+8, 0x1|0x2, 0x02|0x20) + + if x <= 0 { + return x + } + + store(x, 8, length) + return x + 8 } \ No newline at end of file diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q index bdeb356..42da24b 100644 --- a/lib/mem/alloc_mac.q +++ b/lib/mem/alloc_mac.q @@ -1,5 +1,12 @@ import sys alloc(length Int) -> Pointer { - return sys.mmap(0, length, 0x1|0x2, 0x02|0x1000) + x := sys.mmap(0, length, 0x1|0x2, 0x02|0x1000) + + if x <= 0 { + return x + } + + store(x, 8, length) + return x + 8 } \ No newline at end of file diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index a6fafde..616fb84 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,5 +1,12 @@ import sys alloc(length Int) -> Pointer { - return sys.mmap(0, length, 0x0004, 0x3000) + x := sys.mmap(0, length, 0x0004, 0x3000) + + if x <= 0 { + return x + } + + store(x, 8, length) + return x + 8 } \ No newline at end of file diff --git a/lib/sys/io_windows.q b/lib/sys/io_windows.q index 0bf7b09..a07a586 100644 --- a/lib/sys/io_windows.q +++ b/lib/sys/io_windows.q @@ -1,3 +1,9 @@ +read(fd Int, address Pointer, length Int) -> Int { + kernel32.ReadFile(fd, address, length) + return length +} + write(fd Int, address Pointer, length Int) -> Int { - return kernel32.WriteFile(fd, address, length) + kernel32.WriteFile(fd, address, length) + return length } \ No newline at end of file diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index c5c9b8d..3f3cb88 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -24,5 +24,5 @@ func (a *Assembler) SetData(label string, bytes []byte) { a.Data = data.Data{} } - a.Data[label] = bytes + a.Data.Insert(label, bytes) } diff --git a/src/asm/Memory.go b/src/asm/Memory.go index 1c0cc4e..e97924e 100644 --- a/src/asm/Memory.go +++ b/src/asm/Memory.go @@ -4,7 +4,7 @@ import "git.akyoto.dev/cli/q/src/cpu" type Memory struct { Base cpu.Register - Offset byte + Offset int8 OffsetRegister cpu.Register Length byte } diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index 748af18..0dc879e 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -80,7 +80,7 @@ restart: dataStart, _ := fs.Align(c.codeStart+Address(len(c.code)), config.Align) for _, pointer := range c.dataPointers { - address := config.BaseAddress + dataStart + pointer.Resolve() + address := config.BaseAddress + dataStart + pointer.Resolve() + 8 slice := c.code[pointer.Position : pointer.Position+4] binary.LittleEndian.PutUint32(slice, uint32(address)) } diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 08f73e6..1cab08a 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -40,7 +40,7 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { return err } - memory.Offset = byte(offset) + memory.Offset = int8(offset) } else { _, indexRegister, isTemporary, err := f.Evaluate(index) diff --git a/src/core/CompileAssignField.go b/src/core/CompileAssignField.go index 852ae2d..412be15 100644 --- a/src/core/CompileAssignField.go +++ b/src/core/CompileAssignField.go @@ -32,7 +32,7 @@ func (f *Function) CompileAssignField(node *ast.Assign) error { memory := asm.Memory{ Base: variable.Register, - Offset: byte(field.Offset), + Offset: int8(field.Offset), OffsetRegister: math.MaxUint8, Length: byte(field.Type.Size()), } diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 46b687c..69db515 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -27,6 +27,14 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { name = nameNode.Token.Text(f.File.Bytes) switch name { + case "len": + return &Function{ + Output: []*Output{ + { + Type: types.Int, + }, + }, + }, f.CompileLen(root) case "syscall": return nil, f.CompileSyscall(root) case "new": diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go new file mode 100644 index 0000000..cb7cb44 --- /dev/null +++ b/src/core/CompileLen.go @@ -0,0 +1,26 @@ +package core + +import ( + "math" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/expression" +) + +// CompileLen returns the length of a slice. +func (f *Function) CompileLen(root *expression.Expression) error { + _, register, isTemporary, err := f.Evaluate(root.Children[1]) + + if err != nil { + return err + } + + f.SaveRegister(f.CPU.Output[0]) + f.MemoryRegister(asm.LOAD, asm.Memory{Base: register, Offset: -8, OffsetRegister: math.MaxUint8, Length: 8}, f.CPU.Output[0]) + + if isTemporary { + f.FreeRegister(register) + } + + return nil +} diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 17f3feb..a72b381 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -40,7 +40,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if node.Token.Kind == token.Array { array := f.VariableByName(node.Children[0].Token.Text(f.File.Bytes)) offset, err := f.Number(node.Children[1].Token) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: array.Register, Offset: byte(offset), Length: 1}, register) + f.MemoryRegister(asm.LOAD, asm.Memory{Base: array.Register, Offset: int8(offset), Length: 1}, register) return types.Int, err } @@ -51,7 +51,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp right := node.Children[1] name = right.Token.Text(f.File.Bytes) field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(name) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: byte(field.Offset), Length: byte(field.Type.Size())}, register) + f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: int8(field.Offset), Length: byte(field.Type.Size())}, register) return field.Type, nil } diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index af65813..d2daab6 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -1,6 +1,8 @@ package core import ( + "encoding/binary" + "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" @@ -45,7 +47,12 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. case token.String: data := t.Bytes(f.File.Bytes) data = String(data) - label := f.AddBytes(data) + + slice := make([]byte, len(data)+8) + binary.LittleEndian.PutUint64(slice, uint64(len(data))) + copy(slice[8:], data) + + label := f.AddBytes(slice) f.SaveRegister(register) f.RegisterLabel(asm.MOVE, register, label) return types.PointerAny, nil diff --git a/src/register/SaveRegister.go b/src/register/SaveRegister.go index c5a56c9..c0aa17b 100644 --- a/src/register/SaveRegister.go +++ b/src/register/SaveRegister.go @@ -26,4 +26,5 @@ func (f *Machine) SaveRegister(register cpu.Register) { newRegister := f.NewRegister() f.RegisterRegister(asm.MOVE, newRegister, register) variable.Register = newRegister + f.FreeRegister(register) } diff --git a/src/x86/Load.go b/src/x86/Load.go index ae32daa..1a56236 100644 --- a/src/x86/Load.go +++ b/src/x86/Load.go @@ -3,6 +3,6 @@ package x86 import "git.akyoto.dev/cli/q/src/cpu" // LoadRegister loads from memory into a register. -func LoadRegister(code []byte, destination cpu.Register, offset byte, length byte, source cpu.Register) []byte { +func LoadRegister(code []byte, destination cpu.Register, offset int8, length byte, source cpu.Register) []byte { return memoryAccess(code, 0x8A, 0x8B, source, offset, length, destination) } diff --git a/src/x86/Load_test.go b/src/x86/Load_test.go index 2633e09..32d4405 100644 --- a/src/x86/Load_test.go +++ b/src/x86/Load_test.go @@ -12,7 +12,7 @@ func TestLoadRegister(t *testing.T) { usagePatterns := []struct { Destination cpu.Register Source cpu.Register - Offset byte + Offset int8 Length byte Code []byte }{ diff --git a/src/x86/Store.go b/src/x86/Store.go index 0bf06e0..c0fc055 100644 --- a/src/x86/Store.go +++ b/src/x86/Store.go @@ -7,7 +7,7 @@ import ( ) // StoreNumber stores a number into the memory address included in the given register. -func StoreNumber(code []byte, register cpu.Register, offset byte, length byte, number int) []byte { +func StoreNumber(code []byte, register cpu.Register, offset int8, length byte, number int) []byte { code = memoryAccess(code, 0xC6, 0xC7, register, offset, length, 0b000) switch length { @@ -22,6 +22,6 @@ func StoreNumber(code []byte, register cpu.Register, offset byte, length byte, n } // StoreRegister stores the contents of the `source` register into the memory address included in the given register. -func StoreRegister(code []byte, register cpu.Register, offset byte, length byte, source cpu.Register) []byte { +func StoreRegister(code []byte, register cpu.Register, offset int8, length byte, source cpu.Register) []byte { return memoryAccess(code, 0x88, 0x89, register, offset, length, source) } diff --git a/src/x86/Store_test.go b/src/x86/Store_test.go index e9e1e26..7d34503 100644 --- a/src/x86/Store_test.go +++ b/src/x86/Store_test.go @@ -11,7 +11,7 @@ import ( func TestStoreNumber(t *testing.T) { usagePatterns := []struct { Register cpu.Register - Offset byte + Offset int8 Length byte Number int Code []byte @@ -159,7 +159,7 @@ func TestStoreNumber(t *testing.T) { func TestStoreRegister(t *testing.T) { usagePatterns := []struct { RegisterTo cpu.Register - Offset byte + Offset int8 Length byte RegisterFrom cpu.Register Code []byte diff --git a/src/x86/memoryAccess.go b/src/x86/memoryAccess.go index b1dd605..af526a0 100644 --- a/src/x86/memoryAccess.go +++ b/src/x86/memoryAccess.go @@ -3,7 +3,7 @@ package x86 import "git.akyoto.dev/cli/q/src/cpu" // memoryAccess encodes a memory access. -func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { +func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset int8, numBytes byte, source cpu.Register) []byte { opCode := opCode32 if numBytes == 1 { @@ -27,7 +27,7 @@ func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Registe } if mod == AddressMemoryOffset8 { - code = append(code, offset) + code = append(code, byte(offset)) } return code From 2b2e7075202bf23e4f0bbc498134ada406a91ab6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Feb 2025 14:14:41 +0100 Subject: [PATCH 0694/1012] Implemented length storage of allocated memory --- examples/collatz/collatz.q | 4 ++-- examples/fizzbuzz/fizzbuzz.q | 14 +++++--------- examples/hello/hello.q | 8 ++------ examples/prime/prime.q | 4 ++-- examples/server/server.q | 13 +++++++------ examples/shell/shell.q | 5 +++-- examples/thread/thread.q | 5 +++-- lib/io/io.q | 21 +++++++++++++++++++++ lib/mem/alloc_linux.q | 9 ++++++++- lib/mem/alloc_mac.q | 9 ++++++++- lib/mem/alloc_windows.q | 9 ++++++++- lib/sys/io_windows.q | 8 +++++++- src/asm/Assembler.go | 2 +- src/asm/Memory.go | 2 +- src/asmc/resolvePointers.go | 2 +- src/core/CompileAssignArray.go | 2 +- src/core/CompileAssignField.go | 2 +- src/core/CompileCall.go | 8 ++++++++ src/core/CompileLen.go | 26 ++++++++++++++++++++++++++ src/core/ExpressionToRegister.go | 4 ++-- src/core/TokenToRegister.go | 9 ++++++++- src/register/SaveRegister.go | 1 + src/x86/Load.go | 2 +- src/x86/Load_test.go | 2 +- src/x86/Store.go | 4 ++-- src/x86/Store_test.go | 4 ++-- src/x86/memoryAccess.go | 4 ++-- 27 files changed, 134 insertions(+), 49 deletions(-) create mode 100644 lib/io/io.q create mode 100644 src/core/CompileLen.go diff --git a/examples/collatz/collatz.q b/examples/collatz/collatz.q index f7d9037..5f71d2c 100644 --- a/examples/collatz/collatz.q +++ b/examples/collatz/collatz.q @@ -1,5 +1,5 @@ +import io import log -import sys main() { collatz(12) @@ -19,6 +19,6 @@ collatz(x Int) { return } - sys.write(1, " ", 1) + io.out(" ") } } \ No newline at end of file diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q index 5cca2d5..7823731 100644 --- a/examples/fizzbuzz/fizzbuzz.q +++ b/examples/fizzbuzz/fizzbuzz.q @@ -1,5 +1,5 @@ +import io import log -import sys main() { fizzbuzz(15) @@ -10,9 +10,9 @@ fizzbuzz(n Int) { loop { switch { - x % 15 == 0 { print("FizzBuzz", 8) } - x % 5 == 0 { print("Buzz", 4) } - x % 3 == 0 { print("Fizz", 4) } + x % 15 == 0 { io.out("FizzBuzz") } + x % 5 == 0 { io.out("Buzz") } + x % 3 == 0 { io.out("Fizz") } _ { log.number(x) } } @@ -22,10 +22,6 @@ fizzbuzz(n Int) { return } - print(" ", 1) + io.out(" ") } -} - -print(address Pointer, length Int) { - sys.write(1, address, length) } \ No newline at end of file diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 1d9220d..0475212 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,9 +1,5 @@ -import sys +import io main() { - print("Hello\n", 6) -} - -print(address Pointer, length Int) { - sys.write(1, address, length) + io.out("Hello\n") } \ No newline at end of file diff --git a/examples/prime/prime.q b/examples/prime/prime.q index 836c831..76e3d77 100644 --- a/examples/prime/prime.q +++ b/examples/prime/prime.q @@ -1,5 +1,5 @@ +import io import log -import sys main() { n := 100 @@ -12,7 +12,7 @@ main() { if isPrime(i) == 1 { if i != 2 { - sys.write(1, " ", 1) + io.out(" ") } log.number(i) diff --git a/examples/server/server.q b/examples/server/server.q index a729032..6ac4ee3 100644 --- a/examples/server/server.q +++ b/examples/server/server.q @@ -1,6 +1,7 @@ // Open server and client in 2 terminals: // [1] q run examples/server // [2] curl http://127.0.0.1:8080 +import io import net import sys @@ -8,30 +9,30 @@ main() { socket := sys.socket(2, 1, 0) if socket < 0 { - sys.write(1, "socket error\n", 13) + io.error("socket error\n") sys.exit(1) } if net.bind(socket, 8080) != 0 { - sys.write(1, "bind error\n", 11) + io.error("bind error\n") sys.exit(1) } if sys.listen(socket, 128) != 0 { - sys.write(1, "listen error\n", 13) + io.error("listen error\n") sys.exit(1) } - sys.write(1, "listening...\n", 13) + io.out("listening...\n") loop { conn := sys.accept(socket, 0, 0) if conn >= 0 { - sys.write(conn, "HTTP/1.0 200 OK\r\nContent-Length: 6\r\n\r\nHello\n", 44) + io.write(conn, "HTTP/1.0 200 OK\r\nContent-Length: 6\r\n\r\nHello\n") sys.close(conn) } else { - sys.write(1, "error\n", 6) + io.error("accept error\n") } } } \ No newline at end of file diff --git a/examples/shell/shell.q b/examples/shell/shell.q index 0b094f6..3353391 100644 --- a/examples/shell/shell.q +++ b/examples/shell/shell.q @@ -1,3 +1,4 @@ +import io import mem import sys @@ -9,8 +10,8 @@ main() { info := mem.alloc(24) loop { - sys.write(1, "$ ", 2) - n := sys.read(0, command, length) + io.out("$ ") + n := io.in(command) if n <= 0 { return diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 929ebbb..22c9d05 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -1,3 +1,4 @@ +import io import sys import thread import time @@ -10,8 +11,8 @@ main() { } work() { - sys.write(1, "[ ] start\n", 10) + io.out("[ ] start\n") time.sleep(10 * 1000 * 1000) - sys.write(1, "[x] end\n", 8) + io.out("[x] end\n") sys.exit(0) } \ No newline at end of file diff --git a/lib/io/io.q b/lib/io/io.q new file mode 100644 index 0000000..4349f94 --- /dev/null +++ b/lib/io/io.q @@ -0,0 +1,21 @@ +import sys + +in(buffer Pointer) -> Int { + return sys.read(0, buffer, len(buffer)) +} + +out(message Pointer) -> Int { + return sys.write(1, message, len(message)) +} + +error(message Pointer) -> Int { + return sys.write(2, message, len(message)) +} + +read(fd Int, buffer Pointer) -> Int { + return sys.read(fd, buffer, len(buffer)) +} + +write(fd Int, message Pointer) -> Int { + return sys.write(fd, message, len(message)) +} \ No newline at end of file diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_linux.q index 70929fe..df8fe5b 100644 --- a/lib/mem/alloc_linux.q +++ b/lib/mem/alloc_linux.q @@ -1,5 +1,12 @@ import sys alloc(length Int) -> Pointer { - return sys.mmap(0, length, 0x1|0x2, 0x02|0x20) + x := sys.mmap(0, length+8, 0x1|0x2, 0x02|0x20) + + if x <= 0 { + return x + } + + store(x, 8, length) + return x + 8 } \ No newline at end of file diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q index bdeb356..42da24b 100644 --- a/lib/mem/alloc_mac.q +++ b/lib/mem/alloc_mac.q @@ -1,5 +1,12 @@ import sys alloc(length Int) -> Pointer { - return sys.mmap(0, length, 0x1|0x2, 0x02|0x1000) + x := sys.mmap(0, length, 0x1|0x2, 0x02|0x1000) + + if x <= 0 { + return x + } + + store(x, 8, length) + return x + 8 } \ No newline at end of file diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index a6fafde..616fb84 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,5 +1,12 @@ import sys alloc(length Int) -> Pointer { - return sys.mmap(0, length, 0x0004, 0x3000) + x := sys.mmap(0, length, 0x0004, 0x3000) + + if x <= 0 { + return x + } + + store(x, 8, length) + return x + 8 } \ No newline at end of file diff --git a/lib/sys/io_windows.q b/lib/sys/io_windows.q index 0bf7b09..a07a586 100644 --- a/lib/sys/io_windows.q +++ b/lib/sys/io_windows.q @@ -1,3 +1,9 @@ +read(fd Int, address Pointer, length Int) -> Int { + kernel32.ReadFile(fd, address, length) + return length +} + write(fd Int, address Pointer, length Int) -> Int { - return kernel32.WriteFile(fd, address, length) + kernel32.WriteFile(fd, address, length) + return length } \ No newline at end of file diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index c5c9b8d..3f3cb88 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -24,5 +24,5 @@ func (a *Assembler) SetData(label string, bytes []byte) { a.Data = data.Data{} } - a.Data[label] = bytes + a.Data.Insert(label, bytes) } diff --git a/src/asm/Memory.go b/src/asm/Memory.go index 1c0cc4e..e97924e 100644 --- a/src/asm/Memory.go +++ b/src/asm/Memory.go @@ -4,7 +4,7 @@ import "git.akyoto.dev/cli/q/src/cpu" type Memory struct { Base cpu.Register - Offset byte + Offset int8 OffsetRegister cpu.Register Length byte } diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index 748af18..0dc879e 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -80,7 +80,7 @@ restart: dataStart, _ := fs.Align(c.codeStart+Address(len(c.code)), config.Align) for _, pointer := range c.dataPointers { - address := config.BaseAddress + dataStart + pointer.Resolve() + address := config.BaseAddress + dataStart + pointer.Resolve() + 8 slice := c.code[pointer.Position : pointer.Position+4] binary.LittleEndian.PutUint32(slice, uint32(address)) } diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 08f73e6..1cab08a 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -40,7 +40,7 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { return err } - memory.Offset = byte(offset) + memory.Offset = int8(offset) } else { _, indexRegister, isTemporary, err := f.Evaluate(index) diff --git a/src/core/CompileAssignField.go b/src/core/CompileAssignField.go index 852ae2d..412be15 100644 --- a/src/core/CompileAssignField.go +++ b/src/core/CompileAssignField.go @@ -32,7 +32,7 @@ func (f *Function) CompileAssignField(node *ast.Assign) error { memory := asm.Memory{ Base: variable.Register, - Offset: byte(field.Offset), + Offset: int8(field.Offset), OffsetRegister: math.MaxUint8, Length: byte(field.Type.Size()), } diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 46b687c..69db515 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -27,6 +27,14 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { name = nameNode.Token.Text(f.File.Bytes) switch name { + case "len": + return &Function{ + Output: []*Output{ + { + Type: types.Int, + }, + }, + }, f.CompileLen(root) case "syscall": return nil, f.CompileSyscall(root) case "new": diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go new file mode 100644 index 0000000..cb7cb44 --- /dev/null +++ b/src/core/CompileLen.go @@ -0,0 +1,26 @@ +package core + +import ( + "math" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/expression" +) + +// CompileLen returns the length of a slice. +func (f *Function) CompileLen(root *expression.Expression) error { + _, register, isTemporary, err := f.Evaluate(root.Children[1]) + + if err != nil { + return err + } + + f.SaveRegister(f.CPU.Output[0]) + f.MemoryRegister(asm.LOAD, asm.Memory{Base: register, Offset: -8, OffsetRegister: math.MaxUint8, Length: 8}, f.CPU.Output[0]) + + if isTemporary { + f.FreeRegister(register) + } + + return nil +} diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 17f3feb..a72b381 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -40,7 +40,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if node.Token.Kind == token.Array { array := f.VariableByName(node.Children[0].Token.Text(f.File.Bytes)) offset, err := f.Number(node.Children[1].Token) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: array.Register, Offset: byte(offset), Length: 1}, register) + f.MemoryRegister(asm.LOAD, asm.Memory{Base: array.Register, Offset: int8(offset), Length: 1}, register) return types.Int, err } @@ -51,7 +51,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp right := node.Children[1] name = right.Token.Text(f.File.Bytes) field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(name) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: byte(field.Offset), Length: byte(field.Type.Size())}, register) + f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: int8(field.Offset), Length: byte(field.Type.Size())}, register) return field.Type, nil } diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index af65813..d2daab6 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -1,6 +1,8 @@ package core import ( + "encoding/binary" + "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" @@ -45,7 +47,12 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. case token.String: data := t.Bytes(f.File.Bytes) data = String(data) - label := f.AddBytes(data) + + slice := make([]byte, len(data)+8) + binary.LittleEndian.PutUint64(slice, uint64(len(data))) + copy(slice[8:], data) + + label := f.AddBytes(slice) f.SaveRegister(register) f.RegisterLabel(asm.MOVE, register, label) return types.PointerAny, nil diff --git a/src/register/SaveRegister.go b/src/register/SaveRegister.go index c5a56c9..c0aa17b 100644 --- a/src/register/SaveRegister.go +++ b/src/register/SaveRegister.go @@ -26,4 +26,5 @@ func (f *Machine) SaveRegister(register cpu.Register) { newRegister := f.NewRegister() f.RegisterRegister(asm.MOVE, newRegister, register) variable.Register = newRegister + f.FreeRegister(register) } diff --git a/src/x86/Load.go b/src/x86/Load.go index ae32daa..1a56236 100644 --- a/src/x86/Load.go +++ b/src/x86/Load.go @@ -3,6 +3,6 @@ package x86 import "git.akyoto.dev/cli/q/src/cpu" // LoadRegister loads from memory into a register. -func LoadRegister(code []byte, destination cpu.Register, offset byte, length byte, source cpu.Register) []byte { +func LoadRegister(code []byte, destination cpu.Register, offset int8, length byte, source cpu.Register) []byte { return memoryAccess(code, 0x8A, 0x8B, source, offset, length, destination) } diff --git a/src/x86/Load_test.go b/src/x86/Load_test.go index 2633e09..32d4405 100644 --- a/src/x86/Load_test.go +++ b/src/x86/Load_test.go @@ -12,7 +12,7 @@ func TestLoadRegister(t *testing.T) { usagePatterns := []struct { Destination cpu.Register Source cpu.Register - Offset byte + Offset int8 Length byte Code []byte }{ diff --git a/src/x86/Store.go b/src/x86/Store.go index 0bf06e0..c0fc055 100644 --- a/src/x86/Store.go +++ b/src/x86/Store.go @@ -7,7 +7,7 @@ import ( ) // StoreNumber stores a number into the memory address included in the given register. -func StoreNumber(code []byte, register cpu.Register, offset byte, length byte, number int) []byte { +func StoreNumber(code []byte, register cpu.Register, offset int8, length byte, number int) []byte { code = memoryAccess(code, 0xC6, 0xC7, register, offset, length, 0b000) switch length { @@ -22,6 +22,6 @@ func StoreNumber(code []byte, register cpu.Register, offset byte, length byte, n } // StoreRegister stores the contents of the `source` register into the memory address included in the given register. -func StoreRegister(code []byte, register cpu.Register, offset byte, length byte, source cpu.Register) []byte { +func StoreRegister(code []byte, register cpu.Register, offset int8, length byte, source cpu.Register) []byte { return memoryAccess(code, 0x88, 0x89, register, offset, length, source) } diff --git a/src/x86/Store_test.go b/src/x86/Store_test.go index e9e1e26..7d34503 100644 --- a/src/x86/Store_test.go +++ b/src/x86/Store_test.go @@ -11,7 +11,7 @@ import ( func TestStoreNumber(t *testing.T) { usagePatterns := []struct { Register cpu.Register - Offset byte + Offset int8 Length byte Number int Code []byte @@ -159,7 +159,7 @@ func TestStoreNumber(t *testing.T) { func TestStoreRegister(t *testing.T) { usagePatterns := []struct { RegisterTo cpu.Register - Offset byte + Offset int8 Length byte RegisterFrom cpu.Register Code []byte diff --git a/src/x86/memoryAccess.go b/src/x86/memoryAccess.go index b1dd605..af526a0 100644 --- a/src/x86/memoryAccess.go +++ b/src/x86/memoryAccess.go @@ -3,7 +3,7 @@ package x86 import "git.akyoto.dev/cli/q/src/cpu" // memoryAccess encodes a memory access. -func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte { +func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset int8, numBytes byte, source cpu.Register) []byte { opCode := opCode32 if numBytes == 1 { @@ -27,7 +27,7 @@ func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Registe } if mod == AddressMemoryOffset8 { - code = append(code, offset) + code = append(code, byte(offset)) } return code From 129a674c2ec873e4db7871aafd278bedb5f4e25d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Feb 2025 14:38:01 +0100 Subject: [PATCH 0695/1012] Fixed incorrect memory allocations --- lib/mem/alloc_linux.q | 2 +- lib/mem/alloc_mac.q | 4 ++-- lib/mem/alloc_windows.q | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_linux.q index df8fe5b..2389b04 100644 --- a/lib/mem/alloc_linux.q +++ b/lib/mem/alloc_linux.q @@ -3,7 +3,7 @@ import sys alloc(length Int) -> Pointer { x := sys.mmap(0, length+8, 0x1|0x2, 0x02|0x20) - if x <= 0 { + if x < 0x1000 { return x } diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q index 42da24b..6ffda84 100644 --- a/lib/mem/alloc_mac.q +++ b/lib/mem/alloc_mac.q @@ -1,9 +1,9 @@ import sys alloc(length Int) -> Pointer { - x := sys.mmap(0, length, 0x1|0x2, 0x02|0x1000) + x := sys.mmap(0, length+8, 0x1|0x2, 0x02|0x1000) - if x <= 0 { + if x < 0x1000 { return x } diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index 616fb84..d448dd0 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,9 +1,9 @@ import sys alloc(length Int) -> Pointer { - x := sys.mmap(0, length, 0x0004, 0x3000) + x := sys.mmap(0, length+8, 0x0004, 0x3000) - if x <= 0 { + if x < 0x1000 { return x } From ade1fd8fff4a3d750c5c85c63da5c7371fc82f17 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Feb 2025 14:38:01 +0100 Subject: [PATCH 0696/1012] Fixed incorrect memory allocations --- lib/mem/alloc_linux.q | 2 +- lib/mem/alloc_mac.q | 4 ++-- lib/mem/alloc_windows.q | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_linux.q index df8fe5b..2389b04 100644 --- a/lib/mem/alloc_linux.q +++ b/lib/mem/alloc_linux.q @@ -3,7 +3,7 @@ import sys alloc(length Int) -> Pointer { x := sys.mmap(0, length+8, 0x1|0x2, 0x02|0x20) - if x <= 0 { + if x < 0x1000 { return x } diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q index 42da24b..6ffda84 100644 --- a/lib/mem/alloc_mac.q +++ b/lib/mem/alloc_mac.q @@ -1,9 +1,9 @@ import sys alloc(length Int) -> Pointer { - x := sys.mmap(0, length, 0x1|0x2, 0x02|0x1000) + x := sys.mmap(0, length+8, 0x1|0x2, 0x02|0x1000) - if x <= 0 { + if x < 0x1000 { return x } diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index 616fb84..d448dd0 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,9 +1,9 @@ import sys alloc(length Int) -> Pointer { - x := sys.mmap(0, length, 0x0004, 0x3000) + x := sys.mmap(0, length+8, 0x0004, 0x3000) - if x <= 0 { + if x < 0x1000 { return x } From a24b8a7586701d8743396f5e3329853205892d99 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Feb 2025 15:25:25 +0100 Subject: [PATCH 0697/1012] Fixed incorrect parameters for memory deallocation --- examples/winapi/winapi.q | 10 ++-------- lib/mem/free.q | 2 +- tests/programs/memory-free.q | 7 +++++++ tests/programs_test.go | 1 + 4 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 tests/programs/memory-free.q diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q index e232a1b..6e81b03 100644 --- a/examples/winapi/winapi.q +++ b/examples/winapi/winapi.q @@ -1,10 +1,4 @@ -import mem - main() { - text := mem.alloc(4) - text[0] = 'H' - text[1] = 'i' - text[2] = '!' - user32.MessageBoxA(0, text, text, 0x00240040) - mem.free(text, 4) + text := "Hi!\0" + user32.MessageBoxA(0, text, text, 0x240040) } \ No newline at end of file diff --git a/lib/mem/free.q b/lib/mem/free.q index 432d65d..17be5bd 100644 --- a/lib/mem/free.q +++ b/lib/mem/free.q @@ -1,5 +1,5 @@ import sys free(address Pointer, length Int) -> Int { - return sys.munmap(address, length) + return sys.munmap(address-8, length+8) } \ No newline at end of file diff --git a/tests/programs/memory-free.q b/tests/programs/memory-free.q new file mode 100644 index 0000000..a584ef3 --- /dev/null +++ b/tests/programs/memory-free.q @@ -0,0 +1,7 @@ +import mem + +main() { + address := mem.alloc(1024) + err := mem.free(address, 1024) + assert err == 0 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index f4b7d4d..675548b 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -59,6 +59,7 @@ var programs = []struct { {"switch", "", "", 0}, {"loop", "", "", 0}, {"loop-lifetime", "", "", 0}, + {"memory-free", "", "", 0}, {"out-of-memory", "", "", 0}, {"struct", "", "", 0}, } From b1228be79a4f9ce26fbcb3a85b0c14fa6a8f3326 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Feb 2025 15:25:25 +0100 Subject: [PATCH 0698/1012] Fixed incorrect parameters for memory deallocation --- examples/winapi/winapi.q | 10 ++-------- lib/mem/free.q | 2 +- tests/programs/memory-free.q | 7 +++++++ tests/programs_test.go | 1 + 4 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 tests/programs/memory-free.q diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q index e232a1b..6e81b03 100644 --- a/examples/winapi/winapi.q +++ b/examples/winapi/winapi.q @@ -1,10 +1,4 @@ -import mem - main() { - text := mem.alloc(4) - text[0] = 'H' - text[1] = 'i' - text[2] = '!' - user32.MessageBoxA(0, text, text, 0x00240040) - mem.free(text, 4) + text := "Hi!\0" + user32.MessageBoxA(0, text, text, 0x240040) } \ No newline at end of file diff --git a/lib/mem/free.q b/lib/mem/free.q index 432d65d..17be5bd 100644 --- a/lib/mem/free.q +++ b/lib/mem/free.q @@ -1,5 +1,5 @@ import sys free(address Pointer, length Int) -> Int { - return sys.munmap(address, length) + return sys.munmap(address-8, length+8) } \ No newline at end of file diff --git a/tests/programs/memory-free.q b/tests/programs/memory-free.q new file mode 100644 index 0000000..a584ef3 --- /dev/null +++ b/tests/programs/memory-free.q @@ -0,0 +1,7 @@ +import mem + +main() { + address := mem.alloc(1024) + err := mem.free(address, 1024) + assert err == 0 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index f4b7d4d..675548b 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -59,6 +59,7 @@ var programs = []struct { {"switch", "", "", 0}, {"loop", "", "", 0}, {"loop-lifetime", "", "", 0}, + {"memory-free", "", "", 0}, {"out-of-memory", "", "", 0}, {"struct", "", "", 0}, } From abd19903ecdb67f1d7ffdf270e2909dd063158a6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Feb 2025 15:37:00 +0100 Subject: [PATCH 0699/1012] Added a zero byte at the end of strings --- examples/winapi/winapi.q | 5 +++-- src/core/TokenToRegister.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q index 6e81b03..e4a670f 100644 --- a/examples/winapi/winapi.q +++ b/examples/winapi/winapi.q @@ -1,4 +1,5 @@ main() { - text := "Hi!\0" - user32.MessageBoxA(0, text, text, 0x240040) + title := "Title." + text := "Hi!" + user32.MessageBoxA(0, text, title, 0x240040) } \ No newline at end of file diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index d2daab6..c467895 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -48,7 +48,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. data := t.Bytes(f.File.Bytes) data = String(data) - slice := make([]byte, len(data)+8) + slice := make([]byte, len(data)+8+1) binary.LittleEndian.PutUint64(slice, uint64(len(data))) copy(slice[8:], data) From 064bb3acc76607a941fa69b8da8416b46e4690e9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Feb 2025 15:37:00 +0100 Subject: [PATCH 0700/1012] Added a zero byte at the end of strings --- examples/winapi/winapi.q | 5 +++-- src/core/TokenToRegister.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q index 6e81b03..e4a670f 100644 --- a/examples/winapi/winapi.q +++ b/examples/winapi/winapi.q @@ -1,4 +1,5 @@ main() { - text := "Hi!\0" - user32.MessageBoxA(0, text, text, 0x240040) + title := "Title." + text := "Hi!" + user32.MessageBoxA(0, text, title, 0x240040) } \ No newline at end of file diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index d2daab6..c467895 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -48,7 +48,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. data := t.Bytes(f.File.Bytes) data = String(data) - slice := make([]byte, len(data)+8) + slice := make([]byte, len(data)+8+1) binary.LittleEndian.PutUint64(slice, uint64(len(data))) copy(slice[8:], data) From ba4f07893c19369b38b2fa90e43089eacf48f284 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Feb 2025 16:24:36 +0100 Subject: [PATCH 0701/1012] Implemented console output for Windows --- lib/sys/io_windows.q | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/sys/io_windows.q b/lib/sys/io_windows.q index a07a586..c890b14 100644 --- a/lib/sys/io_windows.q +++ b/lib/sys/io_windows.q @@ -4,6 +4,7 @@ read(fd Int, address Pointer, length Int) -> Int { } write(fd Int, address Pointer, length Int) -> Int { - kernel32.WriteFile(fd, address, length) + fd = kernel32.GetStdHandle(-10 - fd) + kernel32.WriteConsoleA(fd, address, length, 0) return length } \ No newline at end of file From 01ea5439950cfc0794f2984d621fabace39235d5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Feb 2025 16:24:36 +0100 Subject: [PATCH 0702/1012] Implemented console output for Windows --- lib/sys/io_windows.q | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/sys/io_windows.q b/lib/sys/io_windows.q index a07a586..c890b14 100644 --- a/lib/sys/io_windows.q +++ b/lib/sys/io_windows.q @@ -4,6 +4,7 @@ read(fd Int, address Pointer, length Int) -> Int { } write(fd Int, address Pointer, length Int) -> Int { - kernel32.WriteFile(fd, address, length) + fd = kernel32.GetStdHandle(-10 - fd) + kernel32.WriteConsoleA(fd, address, length, 0) return length } \ No newline at end of file From bb010709509a528596273d6d8fddce71c2c1f023 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Feb 2025 16:31:16 +0100 Subject: [PATCH 0703/1012] Completed basic support for Windows --- docs/todo.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/todo.md b/docs/todo.md index 19132e0..55d1038 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -17,9 +17,9 @@ - [x] Hexadecimal, octal and binary literals - [x] Escape sequences - [x] Multiple return values -- [ ] Type system +- [x] Data structures +- [x] Type system - [ ] Type operator `?` -- [ ] Data structures - [ ] Slices - [ ] Floating-point arithmetic - [ ] Error handling @@ -77,4 +77,4 @@ - [x] Linux - [x] Mac -- [ ] Windows \ No newline at end of file +- [x] Windows \ No newline at end of file From c69f1aab5c48bacb406a9f53d8d7b1babf52afc1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Feb 2025 16:31:16 +0100 Subject: [PATCH 0704/1012] Completed basic support for Windows --- docs/todo.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/todo.md b/docs/todo.md index 19132e0..55d1038 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -17,9 +17,9 @@ - [x] Hexadecimal, octal and binary literals - [x] Escape sequences - [x] Multiple return values -- [ ] Type system +- [x] Data structures +- [x] Type system - [ ] Type operator `?` -- [ ] Data structures - [ ] Slices - [ ] Floating-point arithmetic - [ ] Error handling @@ -77,4 +77,4 @@ - [x] Linux - [x] Mac -- [ ] Windows \ No newline at end of file +- [x] Windows \ No newline at end of file From c0ffddaba81fff58615a36c7961c9f5f2ef1415f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Feb 2025 20:25:37 +0100 Subject: [PATCH 0705/1012] Added array type --- lib/io/io.q | 10 +++++----- lib/mem/alloc_linux.q | 2 +- lib/mem/alloc_mac.q | 2 +- lib/mem/alloc_windows.q | 2 +- src/core/CompileReturn.go | 4 ++++ src/core/ExpressionToMemory.go | 4 ++-- src/core/ExpressionToRegister.go | 2 +- src/core/TokenToRegister.go | 4 ++-- src/types/Array.go | 18 ++++++++++++++++++ src/types/ByName.go | 13 ++++++++++++- src/types/Is.go | 12 ++++++++++++ src/types/Pointer.go | 4 ++-- src/types/types_test.go | 16 ++++++++++------ 13 files changed, 71 insertions(+), 22 deletions(-) create mode 100644 src/types/Array.go diff --git a/lib/io/io.q b/lib/io/io.q index 4349f94..545c810 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -1,21 +1,21 @@ import sys -in(buffer Pointer) -> Int { +in(buffer []Int8) -> Int { return sys.read(0, buffer, len(buffer)) } -out(message Pointer) -> Int { +out(message []Int8) -> Int { return sys.write(1, message, len(message)) } -error(message Pointer) -> Int { +error(message []Int8) -> Int { return sys.write(2, message, len(message)) } -read(fd Int, buffer Pointer) -> Int { +read(fd Int, buffer []Int8) -> Int { return sys.read(fd, buffer, len(buffer)) } -write(fd Int, message Pointer) -> Int { +write(fd Int, message []Int8) -> Int { return sys.write(fd, message, len(message)) } \ No newline at end of file diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_linux.q index 2389b04..9b9878b 100644 --- a/lib/mem/alloc_linux.q +++ b/lib/mem/alloc_linux.q @@ -1,6 +1,6 @@ import sys -alloc(length Int) -> Pointer { +alloc(length Int) -> []Int8 { x := sys.mmap(0, length+8, 0x1|0x2, 0x02|0x20) if x < 0x1000 { diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q index 6ffda84..128d40c 100644 --- a/lib/mem/alloc_mac.q +++ b/lib/mem/alloc_mac.q @@ -1,6 +1,6 @@ import sys -alloc(length Int) -> Pointer { +alloc(length Int) -> []Int8 { x := sys.mmap(0, length+8, 0x1|0x2, 0x02|0x1000) if x < 0x1000 { diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index d448dd0..50bb75d 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,6 +1,6 @@ import sys -alloc(length Int) -> Pointer { +alloc(length Int) -> []Int8 { x := sys.mmap(0, length+8, 0x0004, 0x3000) if x < 0x1000 { diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 16180a8..f535936 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -26,6 +26,10 @@ func (f *Function) CompileReturn(node *ast.Return) error { } if !types.Is(typ, f.Output[i].Type) { + if f.Package == "mem" && f.Name == "alloc" { + return nil + } + return errors.New(&errors.TypeMismatch{ Encountered: typ.Name(), Expected: f.Output[i].Type.Name(), diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index 0176701..e73fe91 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -18,12 +18,12 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me if variable != nil { f.MemoryRegister(asm.STORE, memory, variable.Register) f.UseVariable(variable) - return types.PointerAny, nil + return types.AnyPointer, nil } if function != nil { f.MemoryLabel(asm.STORE, memory, function.UniqueName) - return types.PointerAny, nil + return types.AnyPointer, nil } return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Token.Position) diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index a72b381..4157a1d 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -83,7 +83,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return nil, err } - if typ == types.PointerAny && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.PointerAny { + if typ == types.AnyPointer && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.AnyPointer { typ = types.Int } diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index c467895..ed934cb 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -28,7 +28,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. if function != nil { f.SaveRegister(register) f.RegisterLabel(asm.MOVE, register, function.UniqueName) - return types.PointerAny, nil + return types.AnyPointer, nil } return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) @@ -55,7 +55,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. label := f.AddBytes(slice) f.SaveRegister(register) f.RegisterLabel(asm.MOVE, register, label) - return types.PointerAny, nil + return types.String, nil default: return nil, errors.New(errors.InvalidExpression, f.File, t.Position) diff --git a/src/types/Array.go b/src/types/Array.go new file mode 100644 index 0000000..fd6add2 --- /dev/null +++ b/src/types/Array.go @@ -0,0 +1,18 @@ +package types + +var String = &Array{Of: Int8} + +// Array is the address of an object. +type Array struct { + Of Type +} + +// Name returns the type name. +func (a *Array) Name() string { + return "[]" + a.Of.Name() +} + +// Size returns the total size in bytes. +func (a *Array) Size() int { + return 8 +} diff --git a/src/types/ByName.go b/src/types/ByName.go index 765fb8f..9c43a19 100644 --- a/src/types/ByName.go +++ b/src/types/ByName.go @@ -17,6 +17,17 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { return nil } + if strings.HasPrefix(name, "[]") { + to := strings.TrimPrefix(name, "[]") + typ := ByName(to, pkg, structs) + + if typ != nil { + return &Array{Of: typ} + } + + return nil + } + switch name { case "Int": return Int @@ -35,7 +46,7 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { case "Float32": return Float32 case "Pointer": - return PointerAny + return AnyPointer } typ, exists := structs[pkg+"."+name] diff --git a/src/types/Is.go b/src/types/Is.go index 9adbac5..059086f 100644 --- a/src/types/Is.go +++ b/src/types/Is.go @@ -17,5 +17,17 @@ func Is(a Type, b Type) bool { return true } + aArray, aIsArray := a.(*Array) + + if aIsArray && bIsPointer && (bPointer.To == nil || aArray.Of == bPointer.To) { + return true + } + + bArray, bIsArray := b.(*Array) + + if aIsArray && bIsArray && aArray.Of == bArray.Of { + return true + } + return false } diff --git a/src/types/Pointer.go b/src/types/Pointer.go index 06a712e..cc08bd3 100644 --- a/src/types/Pointer.go +++ b/src/types/Pointer.go @@ -1,6 +1,6 @@ package types -var PointerAny = &Pointer{To: nil} +var AnyPointer = &Pointer{To: nil} // Pointer is the address of an object. type Pointer struct { @@ -13,7 +13,7 @@ func (p *Pointer) Name() string { return "Pointer" } - return "Pointer:" + p.To.Name() + return "*" + p.To.Name() } // Size returns the total size in bytes. diff --git a/src/types/types_test.go b/src/types/types_test.go index e134ad1..f8f3818 100644 --- a/src/types/types_test.go +++ b/src/types/types_test.go @@ -9,8 +9,10 @@ import ( func TestName(t *testing.T) { assert.Equal(t, types.Int.Name(), "Int64") - assert.Equal(t, types.PointerAny.Name(), "Pointer") - assert.Equal(t, (&types.Pointer{To: types.Int}).Name(), "Pointer:Int64") + assert.Equal(t, types.AnyPointer.Name(), "Pointer") + assert.Equal(t, (&types.Pointer{To: types.Int}).Name(), "*Int64") + assert.Equal(t, (&types.Array{Of: types.Int}).Name(), "[]Int64") + assert.Equal(t, types.String.Name(), "[]Int8") } func TestSize(t *testing.T) { @@ -19,7 +21,8 @@ func TestSize(t *testing.T) { assert.Equal(t, types.Int16.Size(), 2) assert.Equal(t, types.Int32.Size(), 4) assert.Equal(t, types.Int64.Size(), 8) - assert.Equal(t, types.PointerAny.Size(), 8) + assert.Equal(t, types.AnyPointer.Size(), 8) + assert.Equal(t, types.String.Size(), 8) assert.Equal(t, (&types.Pointer{To: types.Int}).Size(), 8) } @@ -37,7 +40,7 @@ func TestStruct(t *testing.T) { func TestBasics(t *testing.T) { assert.True(t, types.Is(types.Int, types.Int)) - assert.True(t, types.Is(types.PointerAny, types.PointerAny)) + assert.True(t, types.Is(types.AnyPointer, types.AnyPointer)) assert.False(t, types.Is(types.Int, types.Float)) assert.False(t, types.Is(&types.Pointer{To: types.Int}, &types.Pointer{To: types.Float})) } @@ -50,6 +53,7 @@ func TestSpecialCases(t *testing.T) { // Case #2: // A pointer pointing to a known type fulfills the requirement of a pointer to anything. - assert.True(t, types.Is(&types.Pointer{To: types.Int}, &types.Pointer{To: nil})) - assert.True(t, types.Is(&types.Pointer{To: types.Float}, &types.Pointer{To: nil})) + assert.True(t, types.Is(&types.Pointer{To: types.Int}, types.AnyPointer)) + assert.True(t, types.Is(&types.Pointer{To: types.Float}, types.AnyPointer)) + assert.True(t, types.Is(&types.Array{Of: types.Int}, types.AnyPointer)) } From 7634244c567010d7d45921a8769bd941d14400cb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Feb 2025 20:25:37 +0100 Subject: [PATCH 0706/1012] Added array type --- lib/io/io.q | 10 +++++----- lib/mem/alloc_linux.q | 2 +- lib/mem/alloc_mac.q | 2 +- lib/mem/alloc_windows.q | 2 +- src/core/CompileReturn.go | 4 ++++ src/core/ExpressionToMemory.go | 4 ++-- src/core/ExpressionToRegister.go | 2 +- src/core/TokenToRegister.go | 4 ++-- src/types/Array.go | 18 ++++++++++++++++++ src/types/ByName.go | 13 ++++++++++++- src/types/Is.go | 12 ++++++++++++ src/types/Pointer.go | 4 ++-- src/types/types_test.go | 16 ++++++++++------ 13 files changed, 71 insertions(+), 22 deletions(-) create mode 100644 src/types/Array.go diff --git a/lib/io/io.q b/lib/io/io.q index 4349f94..545c810 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -1,21 +1,21 @@ import sys -in(buffer Pointer) -> Int { +in(buffer []Int8) -> Int { return sys.read(0, buffer, len(buffer)) } -out(message Pointer) -> Int { +out(message []Int8) -> Int { return sys.write(1, message, len(message)) } -error(message Pointer) -> Int { +error(message []Int8) -> Int { return sys.write(2, message, len(message)) } -read(fd Int, buffer Pointer) -> Int { +read(fd Int, buffer []Int8) -> Int { return sys.read(fd, buffer, len(buffer)) } -write(fd Int, message Pointer) -> Int { +write(fd Int, message []Int8) -> Int { return sys.write(fd, message, len(message)) } \ No newline at end of file diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_linux.q index 2389b04..9b9878b 100644 --- a/lib/mem/alloc_linux.q +++ b/lib/mem/alloc_linux.q @@ -1,6 +1,6 @@ import sys -alloc(length Int) -> Pointer { +alloc(length Int) -> []Int8 { x := sys.mmap(0, length+8, 0x1|0x2, 0x02|0x20) if x < 0x1000 { diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q index 6ffda84..128d40c 100644 --- a/lib/mem/alloc_mac.q +++ b/lib/mem/alloc_mac.q @@ -1,6 +1,6 @@ import sys -alloc(length Int) -> Pointer { +alloc(length Int) -> []Int8 { x := sys.mmap(0, length+8, 0x1|0x2, 0x02|0x1000) if x < 0x1000 { diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index d448dd0..50bb75d 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,6 +1,6 @@ import sys -alloc(length Int) -> Pointer { +alloc(length Int) -> []Int8 { x := sys.mmap(0, length+8, 0x0004, 0x3000) if x < 0x1000 { diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 16180a8..f535936 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -26,6 +26,10 @@ func (f *Function) CompileReturn(node *ast.Return) error { } if !types.Is(typ, f.Output[i].Type) { + if f.Package == "mem" && f.Name == "alloc" { + return nil + } + return errors.New(&errors.TypeMismatch{ Encountered: typ.Name(), Expected: f.Output[i].Type.Name(), diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index 0176701..e73fe91 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -18,12 +18,12 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me if variable != nil { f.MemoryRegister(asm.STORE, memory, variable.Register) f.UseVariable(variable) - return types.PointerAny, nil + return types.AnyPointer, nil } if function != nil { f.MemoryLabel(asm.STORE, memory, function.UniqueName) - return types.PointerAny, nil + return types.AnyPointer, nil } return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Token.Position) diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index a72b381..4157a1d 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -83,7 +83,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return nil, err } - if typ == types.PointerAny && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.PointerAny { + if typ == types.AnyPointer && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.AnyPointer { typ = types.Int } diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index c467895..ed934cb 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -28,7 +28,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. if function != nil { f.SaveRegister(register) f.RegisterLabel(asm.MOVE, register, function.UniqueName) - return types.PointerAny, nil + return types.AnyPointer, nil } return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) @@ -55,7 +55,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. label := f.AddBytes(slice) f.SaveRegister(register) f.RegisterLabel(asm.MOVE, register, label) - return types.PointerAny, nil + return types.String, nil default: return nil, errors.New(errors.InvalidExpression, f.File, t.Position) diff --git a/src/types/Array.go b/src/types/Array.go new file mode 100644 index 0000000..fd6add2 --- /dev/null +++ b/src/types/Array.go @@ -0,0 +1,18 @@ +package types + +var String = &Array{Of: Int8} + +// Array is the address of an object. +type Array struct { + Of Type +} + +// Name returns the type name. +func (a *Array) Name() string { + return "[]" + a.Of.Name() +} + +// Size returns the total size in bytes. +func (a *Array) Size() int { + return 8 +} diff --git a/src/types/ByName.go b/src/types/ByName.go index 765fb8f..9c43a19 100644 --- a/src/types/ByName.go +++ b/src/types/ByName.go @@ -17,6 +17,17 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { return nil } + if strings.HasPrefix(name, "[]") { + to := strings.TrimPrefix(name, "[]") + typ := ByName(to, pkg, structs) + + if typ != nil { + return &Array{Of: typ} + } + + return nil + } + switch name { case "Int": return Int @@ -35,7 +46,7 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { case "Float32": return Float32 case "Pointer": - return PointerAny + return AnyPointer } typ, exists := structs[pkg+"."+name] diff --git a/src/types/Is.go b/src/types/Is.go index 9adbac5..059086f 100644 --- a/src/types/Is.go +++ b/src/types/Is.go @@ -17,5 +17,17 @@ func Is(a Type, b Type) bool { return true } + aArray, aIsArray := a.(*Array) + + if aIsArray && bIsPointer && (bPointer.To == nil || aArray.Of == bPointer.To) { + return true + } + + bArray, bIsArray := b.(*Array) + + if aIsArray && bIsArray && aArray.Of == bArray.Of { + return true + } + return false } diff --git a/src/types/Pointer.go b/src/types/Pointer.go index 06a712e..cc08bd3 100644 --- a/src/types/Pointer.go +++ b/src/types/Pointer.go @@ -1,6 +1,6 @@ package types -var PointerAny = &Pointer{To: nil} +var AnyPointer = &Pointer{To: nil} // Pointer is the address of an object. type Pointer struct { @@ -13,7 +13,7 @@ func (p *Pointer) Name() string { return "Pointer" } - return "Pointer:" + p.To.Name() + return "*" + p.To.Name() } // Size returns the total size in bytes. diff --git a/src/types/types_test.go b/src/types/types_test.go index e134ad1..f8f3818 100644 --- a/src/types/types_test.go +++ b/src/types/types_test.go @@ -9,8 +9,10 @@ import ( func TestName(t *testing.T) { assert.Equal(t, types.Int.Name(), "Int64") - assert.Equal(t, types.PointerAny.Name(), "Pointer") - assert.Equal(t, (&types.Pointer{To: types.Int}).Name(), "Pointer:Int64") + assert.Equal(t, types.AnyPointer.Name(), "Pointer") + assert.Equal(t, (&types.Pointer{To: types.Int}).Name(), "*Int64") + assert.Equal(t, (&types.Array{Of: types.Int}).Name(), "[]Int64") + assert.Equal(t, types.String.Name(), "[]Int8") } func TestSize(t *testing.T) { @@ -19,7 +21,8 @@ func TestSize(t *testing.T) { assert.Equal(t, types.Int16.Size(), 2) assert.Equal(t, types.Int32.Size(), 4) assert.Equal(t, types.Int64.Size(), 8) - assert.Equal(t, types.PointerAny.Size(), 8) + assert.Equal(t, types.AnyPointer.Size(), 8) + assert.Equal(t, types.String.Size(), 8) assert.Equal(t, (&types.Pointer{To: types.Int}).Size(), 8) } @@ -37,7 +40,7 @@ func TestStruct(t *testing.T) { func TestBasics(t *testing.T) { assert.True(t, types.Is(types.Int, types.Int)) - assert.True(t, types.Is(types.PointerAny, types.PointerAny)) + assert.True(t, types.Is(types.AnyPointer, types.AnyPointer)) assert.False(t, types.Is(types.Int, types.Float)) assert.False(t, types.Is(&types.Pointer{To: types.Int}, &types.Pointer{To: types.Float})) } @@ -50,6 +53,7 @@ func TestSpecialCases(t *testing.T) { // Case #2: // A pointer pointing to a known type fulfills the requirement of a pointer to anything. - assert.True(t, types.Is(&types.Pointer{To: types.Int}, &types.Pointer{To: nil})) - assert.True(t, types.Is(&types.Pointer{To: types.Float}, &types.Pointer{To: nil})) + assert.True(t, types.Is(&types.Pointer{To: types.Int}, types.AnyPointer)) + assert.True(t, types.Is(&types.Pointer{To: types.Float}, types.AnyPointer)) + assert.True(t, types.Is(&types.Array{Of: types.Int}, types.AnyPointer)) } From 2e3857622a6e8de9b88858b36260175a46c8155d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Feb 2025 23:52:07 +0100 Subject: [PATCH 0707/1012] Improved type system --- examples/array/array.q | 2 +- examples/echo/echo.q | 2 +- examples/point/point.q | 2 +- lib/log/number.q | 4 ++-- lib/mem/free.q | 4 ++-- lib/sys/fs_linux.q | 12 +++++----- lib/sys/io_linux.q | 8 +++---- lib/sys/io_mac.q | 8 +++---- lib/sys/io_windows.q | 4 ++-- lib/sys/mem_linux.q | 4 ++-- lib/sys/mem_mac.q | 4 ++-- lib/sys/mem_windows.q | 4 ++-- lib/sys/net_linux.q | 4 ++-- lib/sys/net_mac.q | 2 +- lib/sys/proc_linux.q | 6 ++--- lib/sys/proc_mac.q | 4 ++-- lib/thread/thread_linux.q | 2 +- src/core/CompileCall.go | 34 ++++++++++++++++++++++------ src/errors/ParameterCountMismatch.go | 19 ++++++++++++++++ src/types/Array.go | 4 ++++ src/types/ByName.go | 16 ++++++------- src/types/Common.go | 18 +++++++++++++++ src/types/Float.go | 7 ------ src/types/Int.go | 9 -------- src/types/Is.go | 12 ++++------ src/types/Pointer.go | 4 +--- src/types/types_test.go | 11 ++++++++- tests/errors/TypeMismatch.q | 2 +- tests/errors_test.go | 2 +- tests/programs/memory-free.q | 3 +-- 30 files changed, 132 insertions(+), 85 deletions(-) create mode 100644 src/errors/ParameterCountMismatch.go create mode 100644 src/types/Common.go delete mode 100644 src/types/Float.go delete mode 100644 src/types/Int.go diff --git a/examples/array/array.q b/examples/array/array.q index a57691a..4f17ef0 100644 --- a/examples/array/array.q +++ b/examples/array/array.q @@ -10,5 +10,5 @@ main() { address[3] = 'l' address[4] = 'o' sys.write(1, address, length) - mem.free(address, length) + mem.free(address) } \ No newline at end of file diff --git a/examples/echo/echo.q b/examples/echo/echo.q index fb1159e..7e0483b 100644 --- a/examples/echo/echo.q +++ b/examples/echo/echo.q @@ -9,7 +9,7 @@ main() { n := sys.read(0, address, length) if n <= 0 { - mem.free(address, length) + mem.free(address) return } diff --git a/examples/point/point.q b/examples/point/point.q index ccb57c9..4026af6 100644 --- a/examples/point/point.q +++ b/examples/point/point.q @@ -30,5 +30,5 @@ print(p *Point) { out[6] = '0' + p.y out[7] = '\n' sys.write(1, out, 8) - mem.free(out, 8) + mem.free(out) } \ No newline at end of file diff --git a/lib/log/number.q b/lib/log/number.q index 93b7388..0dc6e55 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -6,10 +6,10 @@ number(x Int) { buffer := mem.alloc(length) address, count := itoa(x, buffer, length) sys.write(1, address, count) - mem.free(buffer, length) + mem.free(buffer) } -itoa(x Int, buffer Pointer, length Int) -> (Pointer, Int) { +itoa(x Int, buffer *Any, length Int) -> (*Any, Int) { end := buffer + length tmp := end digit := 0 diff --git a/lib/mem/free.q b/lib/mem/free.q index 17be5bd..2568b2b 100644 --- a/lib/mem/free.q +++ b/lib/mem/free.q @@ -1,5 +1,5 @@ import sys -free(address Pointer, length Int) -> Int { - return sys.munmap(address-8, length+8) +free(address []Any) -> Int { + return sys.munmap(address-8, len(address)+8) } \ No newline at end of file diff --git a/lib/sys/fs_linux.q b/lib/sys/fs_linux.q index dc1bd11..11b7503 100644 --- a/lib/sys/fs_linux.q +++ b/lib/sys/fs_linux.q @@ -1,23 +1,23 @@ -getcwd(buffer Pointer, length Int) -> Int { +getcwd(buffer *Any, length Int) -> Int { return syscall(79, buffer, length) } -chdir(path Pointer) -> Int { +chdir(path *Any) -> Int { return syscall(80, path) } -rename(old Pointer, new Pointer) -> Int { +rename(old *Any, new *Any) -> Int { return syscall(82, old, new) } -mkdir(path Pointer, mode Int) -> Int { +mkdir(path *Any, mode Int) -> Int { return syscall(83, path, mode) } -rmdir(path Pointer) -> Int { +rmdir(path *Any) -> Int { return syscall(84, path) } -unlink(file Pointer) -> Int { +unlink(file *Any) -> Int { return syscall(87, file) } \ No newline at end of file diff --git a/lib/sys/io_linux.q b/lib/sys/io_linux.q index 0bfae7e..9a3b2c5 100644 --- a/lib/sys/io_linux.q +++ b/lib/sys/io_linux.q @@ -1,13 +1,13 @@ -read(fd Int, address Pointer, length Int) -> Int { +read(fd Int, address *Any, length Int) -> Int { return syscall(0, fd, address, length) } -write(fd Int, address Pointer, length Int) -> Int { +write(fd Int, address *Any, length Int) -> Int { return syscall(1, fd, address, length) } -open(file Pointer, flags Int, mode Int) -> Int { - return syscall(2, file, flags, mode) +open(path *Any, flags Int, mode Int) -> Int { + return syscall(2, path, flags, mode) } close(fd Int) -> Int { diff --git a/lib/sys/io_mac.q b/lib/sys/io_mac.q index 17b07aa..4ef451a 100644 --- a/lib/sys/io_mac.q +++ b/lib/sys/io_mac.q @@ -1,13 +1,13 @@ -read(fd Int, address Pointer, length Int) -> Int { +read(fd Int, address *Any, length Int) -> Int { return syscall(0x2000003, fd, address, length) } -write(fd Int, address Pointer, length Int) -> Int { +write(fd Int, address *Any, length Int) -> Int { return syscall(0x2000004, fd, address, length) } -open(file Pointer, flags Int, mode Int) -> Int { - return syscall(0x2000005, file, flags, mode) +open(path *Any, flags Int, mode Int) -> Int { + return syscall(0x2000005, path, flags, mode) } close(fd Int) -> Int { diff --git a/lib/sys/io_windows.q b/lib/sys/io_windows.q index c890b14..964a201 100644 --- a/lib/sys/io_windows.q +++ b/lib/sys/io_windows.q @@ -1,9 +1,9 @@ -read(fd Int, address Pointer, length Int) -> Int { +read(fd Int, address *Any, length Int) -> Int { kernel32.ReadFile(fd, address, length) return length } -write(fd Int, address Pointer, length Int) -> Int { +write(fd Int, address *Any, length Int) -> Int { fd = kernel32.GetStdHandle(-10 - fd) kernel32.WriteConsoleA(fd, address, length, 0) return length diff --git a/lib/sys/mem_linux.q b/lib/sys/mem_linux.q index 5d8bb7c..5821426 100644 --- a/lib/sys/mem_linux.q +++ b/lib/sys/mem_linux.q @@ -1,7 +1,7 @@ -mmap(address Int, length Int, protection Int, flags Int) -> Pointer { +mmap(address Int, length Int, protection Int, flags Int) -> *Any { return syscall(9, address, length, protection, flags) } -munmap(address Pointer, length Int) -> Int { +munmap(address *Any, length Int) -> Int { return syscall(11, address, length) } \ No newline at end of file diff --git a/lib/sys/mem_mac.q b/lib/sys/mem_mac.q index cbae713..6bf509d 100644 --- a/lib/sys/mem_mac.q +++ b/lib/sys/mem_mac.q @@ -1,7 +1,7 @@ -mmap(address Int, length Int, protection Int, flags Int) -> Pointer { +mmap(address Int, length Int, protection Int, flags Int) -> *Any { return syscall(0x20000C5, address, length, protection, flags) } -munmap(address Pointer, length Int) -> Int { +munmap(address *Any, length Int) -> Int { return syscall(0x2000049, address, length) } \ No newline at end of file diff --git a/lib/sys/mem_windows.q b/lib/sys/mem_windows.q index c9ada81..319e966 100644 --- a/lib/sys/mem_windows.q +++ b/lib/sys/mem_windows.q @@ -1,7 +1,7 @@ -mmap(address Int, length Int, protection Int, flags Int) -> Pointer { +mmap(address Int, length Int, protection Int, flags Int) -> *Any { return kernel32.VirtualAlloc(address, length, flags, protection) } -munmap(address Pointer, length Int) -> Int { +munmap(address *Any, length Int) -> Int { return kernel32.VirtualFree(address, length, 0x4000) } \ No newline at end of file diff --git a/lib/sys/net_linux.q b/lib/sys/net_linux.q index dc6b3eb..6e2e53c 100644 --- a/lib/sys/net_linux.q +++ b/lib/sys/net_linux.q @@ -9,7 +9,7 @@ socket(family Int, type Int, protocol Int) -> Int { return syscall(41, family, type, protocol) } -accept(fd Int, address Pointer, length Int) -> Int { +accept(fd Int, address *Any, length Int) -> Int { return syscall(43, fd, address, length) } @@ -21,6 +21,6 @@ listen(fd Int, backlog Int) -> Int { return syscall(50, fd, backlog) } -setsockopt(fd Int, level Int, optname Int, optval Pointer, optlen Int) -> Int { +setsockopt(fd Int, level Int, optname Int, optval *Any, optlen Int) -> Int { return syscall(54, fd, level, optname, optval, optlen) } \ No newline at end of file diff --git a/lib/sys/net_mac.q b/lib/sys/net_mac.q index 9a98067..ca0a735 100644 --- a/lib/sys/net_mac.q +++ b/lib/sys/net_mac.q @@ -10,7 +10,7 @@ socket(family Int, type Int, protocol Int) -> Int { return syscall(0x2000061, family, type, protocol) } -accept(fd Int, address Pointer, length Int) -> Int { +accept(fd Int, address *Any, length Int) -> Int { return syscall(0x200001E, fd, address, length) } diff --git a/lib/sys/proc_linux.q b/lib/sys/proc_linux.q index 2ef2ec7..3540b0c 100644 --- a/lib/sys/proc_linux.q +++ b/lib/sys/proc_linux.q @@ -1,4 +1,4 @@ -clone(flags Int, stack Pointer) -> Int { +clone(flags Int, stack *Any) -> Int { return syscall(56, flags, stack) } @@ -6,7 +6,7 @@ fork() -> Int { return syscall(57) } -execve(path Pointer, argv Pointer, envp Pointer) -> Int { +execve(path *Any, argv *Any, envp *Any) -> Int { return syscall(59, path, argv, envp) } @@ -14,6 +14,6 @@ exit(status Int) { syscall(60, status) } -waitid(type Int, id Int, info Pointer, options Int) -> Int { +waitid(type Int, id Int, info *Any, options Int) -> Int { return syscall(247, type, id, info, options) } \ No newline at end of file diff --git a/lib/sys/proc_mac.q b/lib/sys/proc_mac.q index bf70e55..aa3454a 100644 --- a/lib/sys/proc_mac.q +++ b/lib/sys/proc_mac.q @@ -6,10 +6,10 @@ fork() -> Int { return syscall(0x2000002) } -execve(path Pointer, argv Pointer, envp Pointer) -> Int { +execve(path *Any, argv *Any, envp *Any) -> Int { return syscall(0x200003B, path, argv, envp) } -waitid(type Int, id Int, info Pointer, options Int) -> Int { +waitid(type Int, id Int, info *Any, options Int) -> Int { return syscall(0x20000AD, type, id, info, options) } \ No newline at end of file diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index eaa873b..be70f4f 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -1,6 +1,6 @@ import sys -create(func Pointer) -> Int { +create(func *Any) -> Int { size := 4096 stack := sys.mmap(0, size, 0x1|0x2, 0x02|0x20|0x100|0x20000) rip := stack + size - 8 diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 69db515..fa0ffce 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -17,9 +17,10 @@ import ( func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { var ( pkg = f.Package + pkgNode *expression.Expression + name string nameNode = root.Children[0] fn *Function - name string exists bool ) @@ -53,8 +54,11 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { return nil, f.CompileMemoryStore(root) } } else { - pkg = nameNode.Children[0].Token.Text(f.File.Bytes) - name = nameNode.Children[1].Token.Text(f.File.Bytes) + pkgNode = nameNode.Children[0] + nameNode = nameNode.Children[1] + + pkg = pkgNode.Token.Text(f.File.Bytes) + name = nameNode.Token.Text(f.File.Bytes) } if pkg == "kernel32" || pkg == "user32" || pkg == "gdi32" || pkg == "comctl32" { @@ -74,13 +78,13 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { return nil, nil } else if pkg != f.File.Package { if f.File.Imports == nil { - return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position) + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, pkgNode.Token.Position) } imp, exists := f.File.Imports[pkg] if !exists { - return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position) + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, pkgNode.Token.Position) } imp.Used = true @@ -93,6 +97,11 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { } parameters := root.Children[1:] + + if len(parameters) != len(fn.Input) { + return nil, errors.New(&errors.ParameterCountMismatch{Function: fn.Name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, nameNode.Token.End()) + } + registers := f.CPU.Input[:len(parameters)] for i := len(parameters) - 1; i >= 0; i-- { @@ -107,9 +116,20 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { continue } + encountered := "" + expected := "" + + if typ != nil { + encountered = typ.Name() + } + + if fn.Input[i].Type != nil { + expected = fn.Input[i].Type.Name() + } + return nil, errors.New(&errors.TypeMismatch{ - Encountered: typ.Name(), - Expected: fn.Input[i].Type.Name(), + Encountered: encountered, + Expected: expected, ParameterName: fn.Input[i].Name, }, f.File, parameters[i].Token.Position) } diff --git a/src/errors/ParameterCountMismatch.go b/src/errors/ParameterCountMismatch.go new file mode 100644 index 0000000..d626e10 --- /dev/null +++ b/src/errors/ParameterCountMismatch.go @@ -0,0 +1,19 @@ +package errors + +import "fmt" + +// ParameterCountMismatch error is created when the number of parameters doesn't match the function prototype. +type ParameterCountMismatch struct { + Function string + Count int + ExpectedCount int +} + +// Error generates the string representation. +func (err *ParameterCountMismatch) Error() string { + if err.Count > err.ExpectedCount { + return fmt.Sprintf("Too many parameters in '%s' function call", err.Function) + } + + return fmt.Sprintf("Not enough parameters in '%s' function call", err.Function) +} diff --git a/src/types/Array.go b/src/types/Array.go index fd6add2..d46b216 100644 --- a/src/types/Array.go +++ b/src/types/Array.go @@ -9,6 +9,10 @@ type Array struct { // Name returns the type name. func (a *Array) Name() string { + if a.Of == Any { + return "[]Any" + } + return "[]" + a.Of.Name() } diff --git a/src/types/ByName.go b/src/types/ByName.go index 9c43a19..365e55b 100644 --- a/src/types/ByName.go +++ b/src/types/ByName.go @@ -10,25 +10,27 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { to := strings.TrimPrefix(name, "*") typ := ByName(to, pkg, structs) - if typ != nil { - return &Pointer{To: typ} + if typ == Any { + return AnyPointer } - return nil + return &Pointer{To: typ} } if strings.HasPrefix(name, "[]") { to := strings.TrimPrefix(name, "[]") typ := ByName(to, pkg, structs) - if typ != nil { - return &Array{Of: typ} + if typ == Any { + return AnyArray } - return nil + return &Array{Of: typ} } switch name { + case "Any": + return Any case "Int": return Int case "Int64": @@ -45,8 +47,6 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { return Float64 case "Float32": return Float32 - case "Pointer": - return AnyPointer } typ, exists := structs[pkg+"."+name] diff --git a/src/types/Common.go b/src/types/Common.go new file mode 100644 index 0000000..6f1918f --- /dev/null +++ b/src/types/Common.go @@ -0,0 +1,18 @@ +package types + +var ( + Any = &Base{name: "Any", size: 0} + AnyArray = &Array{Of: Any} + AnyPointer = &Pointer{To: Any} + Int64 = &Base{name: "Int64", size: 8} + Int32 = &Base{name: "Int32", size: 4} + Int16 = &Base{name: "Int16", size: 2} + Int8 = &Base{name: "Int8", size: 1} + Float64 = &Base{name: "Float64", size: 8} + Float32 = &Base{name: "Float32", size: 4} +) + +var ( + Int = Int64 + Float = Float64 +) diff --git a/src/types/Float.go b/src/types/Float.go deleted file mode 100644 index cbfc638..0000000 --- a/src/types/Float.go +++ /dev/null @@ -1,7 +0,0 @@ -package types - -var ( - Float64 = &Base{name: "Float64", size: 8} - Float32 = &Base{name: "Float32", size: 4} - Float = Float64 -) diff --git a/src/types/Int.go b/src/types/Int.go deleted file mode 100644 index f07d7c6..0000000 --- a/src/types/Int.go +++ /dev/null @@ -1,9 +0,0 @@ -package types - -var ( - Int64 = &Base{name: "Int64", size: 8} - Int32 = &Base{name: "Int32", size: 4} - Int16 = &Base{name: "Int16", size: 2} - Int8 = &Base{name: "Int8", size: 1} - Int = Int64 -) diff --git a/src/types/Is.go b/src/types/Is.go index 059086f..438b917 100644 --- a/src/types/Is.go +++ b/src/types/Is.go @@ -2,30 +2,26 @@ package types // Is returns true if the encountered type `a` can be converted into the expected type `b`. func Is(a Type, b Type) bool { - if a == nil { - return true - } - - if a == b { + if a == b || b == Any || a == nil { return true } aPointer, aIsPointer := a.(*Pointer) bPointer, bIsPointer := b.(*Pointer) - if aIsPointer && bIsPointer && (bPointer.To == nil || aPointer.To == bPointer.To) { + if aIsPointer && bIsPointer && (bPointer.To == Any || aPointer.To == bPointer.To) { return true } aArray, aIsArray := a.(*Array) - if aIsArray && bIsPointer && (bPointer.To == nil || aArray.Of == bPointer.To) { + if aIsArray && bIsPointer && (bPointer.To == Any || aArray.Of == bPointer.To) { return true } bArray, bIsArray := b.(*Array) - if aIsArray && bIsArray && aArray.Of == bArray.Of { + if aIsArray && bIsArray && (bArray.Of == Any || aArray.Of == bArray.Of) { return true } diff --git a/src/types/Pointer.go b/src/types/Pointer.go index cc08bd3..1cf180b 100644 --- a/src/types/Pointer.go +++ b/src/types/Pointer.go @@ -1,7 +1,5 @@ package types -var AnyPointer = &Pointer{To: nil} - // Pointer is the address of an object. type Pointer struct { To Type @@ -10,7 +8,7 @@ type Pointer struct { // Name returns the type name. func (p *Pointer) Name() string { if p.To == nil { - return "Pointer" + return "*Any" } return "*" + p.To.Name() diff --git a/src/types/types_test.go b/src/types/types_test.go index f8f3818..e19602c 100644 --- a/src/types/types_test.go +++ b/src/types/types_test.go @@ -9,7 +9,8 @@ import ( func TestName(t *testing.T) { assert.Equal(t, types.Int.Name(), "Int64") - assert.Equal(t, types.AnyPointer.Name(), "Pointer") + assert.Equal(t, types.AnyArray.Name(), "[]Any") + assert.Equal(t, types.AnyPointer.Name(), "*Any") assert.Equal(t, (&types.Pointer{To: types.Int}).Name(), "*Int64") assert.Equal(t, (&types.Array{Of: types.Int}).Name(), "[]Int64") assert.Equal(t, types.String.Name(), "[]Int8") @@ -21,6 +22,7 @@ func TestSize(t *testing.T) { assert.Equal(t, types.Int16.Size(), 2) assert.Equal(t, types.Int32.Size(), 4) assert.Equal(t, types.Int64.Size(), 8) + assert.Equal(t, types.AnyArray.Size(), 8) assert.Equal(t, types.AnyPointer.Size(), 8) assert.Equal(t, types.String.Size(), 8) assert.Equal(t, (&types.Pointer{To: types.Int}).Size(), 8) @@ -39,9 +41,12 @@ func TestStruct(t *testing.T) { } func TestBasics(t *testing.T) { + assert.True(t, types.Is(types.Int, types.Any)) assert.True(t, types.Is(types.Int, types.Int)) assert.True(t, types.Is(types.AnyPointer, types.AnyPointer)) + assert.True(t, types.Is(&types.Array{Of: types.Int}, types.AnyArray)) assert.False(t, types.Is(types.Int, types.Float)) + assert.False(t, types.Is(types.Any, types.Int)) assert.False(t, types.Is(&types.Pointer{To: types.Int}, &types.Pointer{To: types.Float})) } @@ -55,5 +60,9 @@ func TestSpecialCases(t *testing.T) { // A pointer pointing to a known type fulfills the requirement of a pointer to anything. assert.True(t, types.Is(&types.Pointer{To: types.Int}, types.AnyPointer)) assert.True(t, types.Is(&types.Pointer{To: types.Float}, types.AnyPointer)) + + // Case #3: + // Arrays are also just pointers. assert.True(t, types.Is(&types.Array{Of: types.Int}, types.AnyPointer)) + assert.True(t, types.Is(&types.Array{Of: types.Float}, types.AnyPointer)) } diff --git a/tests/errors/TypeMismatch.q b/tests/errors/TypeMismatch.q index 4da2421..57bdd6b 100644 --- a/tests/errors/TypeMismatch.q +++ b/tests/errors/TypeMismatch.q @@ -2,6 +2,6 @@ main() { writeToMemory(42) } -writeToMemory(p Pointer) { +writeToMemory(p *Any) { p[0] = 'A' } \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 24c6b52..92f12a1 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -46,7 +46,7 @@ var errs = []struct { {"MissingParameter3.q", errors.MissingParameter}, {"MissingType.q", errors.MissingType}, {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, - {"TypeMismatch.q", &errors.TypeMismatch{Expected: "Pointer", Encountered: "Int64", ParameterName: "p"}}, + {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*Any", Encountered: "Int64", ParameterName: "p"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, diff --git a/tests/programs/memory-free.q b/tests/programs/memory-free.q index a584ef3..7fb937b 100644 --- a/tests/programs/memory-free.q +++ b/tests/programs/memory-free.q @@ -2,6 +2,5 @@ import mem main() { address := mem.alloc(1024) - err := mem.free(address, 1024) - assert err == 0 + assert mem.free(address) == 0 } \ No newline at end of file From f19d9063a5863b975eb00f679b65d602ead2398a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Feb 2025 23:52:07 +0100 Subject: [PATCH 0708/1012] Improved type system --- examples/array/array.q | 2 +- examples/echo/echo.q | 2 +- examples/point/point.q | 2 +- lib/log/number.q | 4 ++-- lib/mem/free.q | 4 ++-- lib/sys/fs_linux.q | 12 +++++----- lib/sys/io_linux.q | 8 +++---- lib/sys/io_mac.q | 8 +++---- lib/sys/io_windows.q | 4 ++-- lib/sys/mem_linux.q | 4 ++-- lib/sys/mem_mac.q | 4 ++-- lib/sys/mem_windows.q | 4 ++-- lib/sys/net_linux.q | 4 ++-- lib/sys/net_mac.q | 2 +- lib/sys/proc_linux.q | 6 ++--- lib/sys/proc_mac.q | 4 ++-- lib/thread/thread_linux.q | 2 +- src/core/CompileCall.go | 34 ++++++++++++++++++++++------ src/errors/ParameterCountMismatch.go | 19 ++++++++++++++++ src/types/Array.go | 4 ++++ src/types/ByName.go | 16 ++++++------- src/types/Common.go | 18 +++++++++++++++ src/types/Float.go | 7 ------ src/types/Int.go | 9 -------- src/types/Is.go | 12 ++++------ src/types/Pointer.go | 4 +--- src/types/types_test.go | 11 ++++++++- tests/errors/TypeMismatch.q | 2 +- tests/errors_test.go | 2 +- tests/programs/memory-free.q | 3 +-- 30 files changed, 132 insertions(+), 85 deletions(-) create mode 100644 src/errors/ParameterCountMismatch.go create mode 100644 src/types/Common.go delete mode 100644 src/types/Float.go delete mode 100644 src/types/Int.go diff --git a/examples/array/array.q b/examples/array/array.q index a57691a..4f17ef0 100644 --- a/examples/array/array.q +++ b/examples/array/array.q @@ -10,5 +10,5 @@ main() { address[3] = 'l' address[4] = 'o' sys.write(1, address, length) - mem.free(address, length) + mem.free(address) } \ No newline at end of file diff --git a/examples/echo/echo.q b/examples/echo/echo.q index fb1159e..7e0483b 100644 --- a/examples/echo/echo.q +++ b/examples/echo/echo.q @@ -9,7 +9,7 @@ main() { n := sys.read(0, address, length) if n <= 0 { - mem.free(address, length) + mem.free(address) return } diff --git a/examples/point/point.q b/examples/point/point.q index ccb57c9..4026af6 100644 --- a/examples/point/point.q +++ b/examples/point/point.q @@ -30,5 +30,5 @@ print(p *Point) { out[6] = '0' + p.y out[7] = '\n' sys.write(1, out, 8) - mem.free(out, 8) + mem.free(out) } \ No newline at end of file diff --git a/lib/log/number.q b/lib/log/number.q index 93b7388..0dc6e55 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -6,10 +6,10 @@ number(x Int) { buffer := mem.alloc(length) address, count := itoa(x, buffer, length) sys.write(1, address, count) - mem.free(buffer, length) + mem.free(buffer) } -itoa(x Int, buffer Pointer, length Int) -> (Pointer, Int) { +itoa(x Int, buffer *Any, length Int) -> (*Any, Int) { end := buffer + length tmp := end digit := 0 diff --git a/lib/mem/free.q b/lib/mem/free.q index 17be5bd..2568b2b 100644 --- a/lib/mem/free.q +++ b/lib/mem/free.q @@ -1,5 +1,5 @@ import sys -free(address Pointer, length Int) -> Int { - return sys.munmap(address-8, length+8) +free(address []Any) -> Int { + return sys.munmap(address-8, len(address)+8) } \ No newline at end of file diff --git a/lib/sys/fs_linux.q b/lib/sys/fs_linux.q index dc1bd11..11b7503 100644 --- a/lib/sys/fs_linux.q +++ b/lib/sys/fs_linux.q @@ -1,23 +1,23 @@ -getcwd(buffer Pointer, length Int) -> Int { +getcwd(buffer *Any, length Int) -> Int { return syscall(79, buffer, length) } -chdir(path Pointer) -> Int { +chdir(path *Any) -> Int { return syscall(80, path) } -rename(old Pointer, new Pointer) -> Int { +rename(old *Any, new *Any) -> Int { return syscall(82, old, new) } -mkdir(path Pointer, mode Int) -> Int { +mkdir(path *Any, mode Int) -> Int { return syscall(83, path, mode) } -rmdir(path Pointer) -> Int { +rmdir(path *Any) -> Int { return syscall(84, path) } -unlink(file Pointer) -> Int { +unlink(file *Any) -> Int { return syscall(87, file) } \ No newline at end of file diff --git a/lib/sys/io_linux.q b/lib/sys/io_linux.q index 0bfae7e..9a3b2c5 100644 --- a/lib/sys/io_linux.q +++ b/lib/sys/io_linux.q @@ -1,13 +1,13 @@ -read(fd Int, address Pointer, length Int) -> Int { +read(fd Int, address *Any, length Int) -> Int { return syscall(0, fd, address, length) } -write(fd Int, address Pointer, length Int) -> Int { +write(fd Int, address *Any, length Int) -> Int { return syscall(1, fd, address, length) } -open(file Pointer, flags Int, mode Int) -> Int { - return syscall(2, file, flags, mode) +open(path *Any, flags Int, mode Int) -> Int { + return syscall(2, path, flags, mode) } close(fd Int) -> Int { diff --git a/lib/sys/io_mac.q b/lib/sys/io_mac.q index 17b07aa..4ef451a 100644 --- a/lib/sys/io_mac.q +++ b/lib/sys/io_mac.q @@ -1,13 +1,13 @@ -read(fd Int, address Pointer, length Int) -> Int { +read(fd Int, address *Any, length Int) -> Int { return syscall(0x2000003, fd, address, length) } -write(fd Int, address Pointer, length Int) -> Int { +write(fd Int, address *Any, length Int) -> Int { return syscall(0x2000004, fd, address, length) } -open(file Pointer, flags Int, mode Int) -> Int { - return syscall(0x2000005, file, flags, mode) +open(path *Any, flags Int, mode Int) -> Int { + return syscall(0x2000005, path, flags, mode) } close(fd Int) -> Int { diff --git a/lib/sys/io_windows.q b/lib/sys/io_windows.q index c890b14..964a201 100644 --- a/lib/sys/io_windows.q +++ b/lib/sys/io_windows.q @@ -1,9 +1,9 @@ -read(fd Int, address Pointer, length Int) -> Int { +read(fd Int, address *Any, length Int) -> Int { kernel32.ReadFile(fd, address, length) return length } -write(fd Int, address Pointer, length Int) -> Int { +write(fd Int, address *Any, length Int) -> Int { fd = kernel32.GetStdHandle(-10 - fd) kernel32.WriteConsoleA(fd, address, length, 0) return length diff --git a/lib/sys/mem_linux.q b/lib/sys/mem_linux.q index 5d8bb7c..5821426 100644 --- a/lib/sys/mem_linux.q +++ b/lib/sys/mem_linux.q @@ -1,7 +1,7 @@ -mmap(address Int, length Int, protection Int, flags Int) -> Pointer { +mmap(address Int, length Int, protection Int, flags Int) -> *Any { return syscall(9, address, length, protection, flags) } -munmap(address Pointer, length Int) -> Int { +munmap(address *Any, length Int) -> Int { return syscall(11, address, length) } \ No newline at end of file diff --git a/lib/sys/mem_mac.q b/lib/sys/mem_mac.q index cbae713..6bf509d 100644 --- a/lib/sys/mem_mac.q +++ b/lib/sys/mem_mac.q @@ -1,7 +1,7 @@ -mmap(address Int, length Int, protection Int, flags Int) -> Pointer { +mmap(address Int, length Int, protection Int, flags Int) -> *Any { return syscall(0x20000C5, address, length, protection, flags) } -munmap(address Pointer, length Int) -> Int { +munmap(address *Any, length Int) -> Int { return syscall(0x2000049, address, length) } \ No newline at end of file diff --git a/lib/sys/mem_windows.q b/lib/sys/mem_windows.q index c9ada81..319e966 100644 --- a/lib/sys/mem_windows.q +++ b/lib/sys/mem_windows.q @@ -1,7 +1,7 @@ -mmap(address Int, length Int, protection Int, flags Int) -> Pointer { +mmap(address Int, length Int, protection Int, flags Int) -> *Any { return kernel32.VirtualAlloc(address, length, flags, protection) } -munmap(address Pointer, length Int) -> Int { +munmap(address *Any, length Int) -> Int { return kernel32.VirtualFree(address, length, 0x4000) } \ No newline at end of file diff --git a/lib/sys/net_linux.q b/lib/sys/net_linux.q index dc6b3eb..6e2e53c 100644 --- a/lib/sys/net_linux.q +++ b/lib/sys/net_linux.q @@ -9,7 +9,7 @@ socket(family Int, type Int, protocol Int) -> Int { return syscall(41, family, type, protocol) } -accept(fd Int, address Pointer, length Int) -> Int { +accept(fd Int, address *Any, length Int) -> Int { return syscall(43, fd, address, length) } @@ -21,6 +21,6 @@ listen(fd Int, backlog Int) -> Int { return syscall(50, fd, backlog) } -setsockopt(fd Int, level Int, optname Int, optval Pointer, optlen Int) -> Int { +setsockopt(fd Int, level Int, optname Int, optval *Any, optlen Int) -> Int { return syscall(54, fd, level, optname, optval, optlen) } \ No newline at end of file diff --git a/lib/sys/net_mac.q b/lib/sys/net_mac.q index 9a98067..ca0a735 100644 --- a/lib/sys/net_mac.q +++ b/lib/sys/net_mac.q @@ -10,7 +10,7 @@ socket(family Int, type Int, protocol Int) -> Int { return syscall(0x2000061, family, type, protocol) } -accept(fd Int, address Pointer, length Int) -> Int { +accept(fd Int, address *Any, length Int) -> Int { return syscall(0x200001E, fd, address, length) } diff --git a/lib/sys/proc_linux.q b/lib/sys/proc_linux.q index 2ef2ec7..3540b0c 100644 --- a/lib/sys/proc_linux.q +++ b/lib/sys/proc_linux.q @@ -1,4 +1,4 @@ -clone(flags Int, stack Pointer) -> Int { +clone(flags Int, stack *Any) -> Int { return syscall(56, flags, stack) } @@ -6,7 +6,7 @@ fork() -> Int { return syscall(57) } -execve(path Pointer, argv Pointer, envp Pointer) -> Int { +execve(path *Any, argv *Any, envp *Any) -> Int { return syscall(59, path, argv, envp) } @@ -14,6 +14,6 @@ exit(status Int) { syscall(60, status) } -waitid(type Int, id Int, info Pointer, options Int) -> Int { +waitid(type Int, id Int, info *Any, options Int) -> Int { return syscall(247, type, id, info, options) } \ No newline at end of file diff --git a/lib/sys/proc_mac.q b/lib/sys/proc_mac.q index bf70e55..aa3454a 100644 --- a/lib/sys/proc_mac.q +++ b/lib/sys/proc_mac.q @@ -6,10 +6,10 @@ fork() -> Int { return syscall(0x2000002) } -execve(path Pointer, argv Pointer, envp Pointer) -> Int { +execve(path *Any, argv *Any, envp *Any) -> Int { return syscall(0x200003B, path, argv, envp) } -waitid(type Int, id Int, info Pointer, options Int) -> Int { +waitid(type Int, id Int, info *Any, options Int) -> Int { return syscall(0x20000AD, type, id, info, options) } \ No newline at end of file diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index eaa873b..be70f4f 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -1,6 +1,6 @@ import sys -create(func Pointer) -> Int { +create(func *Any) -> Int { size := 4096 stack := sys.mmap(0, size, 0x1|0x2, 0x02|0x20|0x100|0x20000) rip := stack + size - 8 diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 69db515..fa0ffce 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -17,9 +17,10 @@ import ( func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { var ( pkg = f.Package + pkgNode *expression.Expression + name string nameNode = root.Children[0] fn *Function - name string exists bool ) @@ -53,8 +54,11 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { return nil, f.CompileMemoryStore(root) } } else { - pkg = nameNode.Children[0].Token.Text(f.File.Bytes) - name = nameNode.Children[1].Token.Text(f.File.Bytes) + pkgNode = nameNode.Children[0] + nameNode = nameNode.Children[1] + + pkg = pkgNode.Token.Text(f.File.Bytes) + name = nameNode.Token.Text(f.File.Bytes) } if pkg == "kernel32" || pkg == "user32" || pkg == "gdi32" || pkg == "comctl32" { @@ -74,13 +78,13 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { return nil, nil } else if pkg != f.File.Package { if f.File.Imports == nil { - return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position) + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, pkgNode.Token.Position) } imp, exists := f.File.Imports[pkg] if !exists { - return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position) + return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, pkgNode.Token.Position) } imp.Used = true @@ -93,6 +97,11 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { } parameters := root.Children[1:] + + if len(parameters) != len(fn.Input) { + return nil, errors.New(&errors.ParameterCountMismatch{Function: fn.Name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, nameNode.Token.End()) + } + registers := f.CPU.Input[:len(parameters)] for i := len(parameters) - 1; i >= 0; i-- { @@ -107,9 +116,20 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { continue } + encountered := "" + expected := "" + + if typ != nil { + encountered = typ.Name() + } + + if fn.Input[i].Type != nil { + expected = fn.Input[i].Type.Name() + } + return nil, errors.New(&errors.TypeMismatch{ - Encountered: typ.Name(), - Expected: fn.Input[i].Type.Name(), + Encountered: encountered, + Expected: expected, ParameterName: fn.Input[i].Name, }, f.File, parameters[i].Token.Position) } diff --git a/src/errors/ParameterCountMismatch.go b/src/errors/ParameterCountMismatch.go new file mode 100644 index 0000000..d626e10 --- /dev/null +++ b/src/errors/ParameterCountMismatch.go @@ -0,0 +1,19 @@ +package errors + +import "fmt" + +// ParameterCountMismatch error is created when the number of parameters doesn't match the function prototype. +type ParameterCountMismatch struct { + Function string + Count int + ExpectedCount int +} + +// Error generates the string representation. +func (err *ParameterCountMismatch) Error() string { + if err.Count > err.ExpectedCount { + return fmt.Sprintf("Too many parameters in '%s' function call", err.Function) + } + + return fmt.Sprintf("Not enough parameters in '%s' function call", err.Function) +} diff --git a/src/types/Array.go b/src/types/Array.go index fd6add2..d46b216 100644 --- a/src/types/Array.go +++ b/src/types/Array.go @@ -9,6 +9,10 @@ type Array struct { // Name returns the type name. func (a *Array) Name() string { + if a.Of == Any { + return "[]Any" + } + return "[]" + a.Of.Name() } diff --git a/src/types/ByName.go b/src/types/ByName.go index 9c43a19..365e55b 100644 --- a/src/types/ByName.go +++ b/src/types/ByName.go @@ -10,25 +10,27 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { to := strings.TrimPrefix(name, "*") typ := ByName(to, pkg, structs) - if typ != nil { - return &Pointer{To: typ} + if typ == Any { + return AnyPointer } - return nil + return &Pointer{To: typ} } if strings.HasPrefix(name, "[]") { to := strings.TrimPrefix(name, "[]") typ := ByName(to, pkg, structs) - if typ != nil { - return &Array{Of: typ} + if typ == Any { + return AnyArray } - return nil + return &Array{Of: typ} } switch name { + case "Any": + return Any case "Int": return Int case "Int64": @@ -45,8 +47,6 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { return Float64 case "Float32": return Float32 - case "Pointer": - return AnyPointer } typ, exists := structs[pkg+"."+name] diff --git a/src/types/Common.go b/src/types/Common.go new file mode 100644 index 0000000..6f1918f --- /dev/null +++ b/src/types/Common.go @@ -0,0 +1,18 @@ +package types + +var ( + Any = &Base{name: "Any", size: 0} + AnyArray = &Array{Of: Any} + AnyPointer = &Pointer{To: Any} + Int64 = &Base{name: "Int64", size: 8} + Int32 = &Base{name: "Int32", size: 4} + Int16 = &Base{name: "Int16", size: 2} + Int8 = &Base{name: "Int8", size: 1} + Float64 = &Base{name: "Float64", size: 8} + Float32 = &Base{name: "Float32", size: 4} +) + +var ( + Int = Int64 + Float = Float64 +) diff --git a/src/types/Float.go b/src/types/Float.go deleted file mode 100644 index cbfc638..0000000 --- a/src/types/Float.go +++ /dev/null @@ -1,7 +0,0 @@ -package types - -var ( - Float64 = &Base{name: "Float64", size: 8} - Float32 = &Base{name: "Float32", size: 4} - Float = Float64 -) diff --git a/src/types/Int.go b/src/types/Int.go deleted file mode 100644 index f07d7c6..0000000 --- a/src/types/Int.go +++ /dev/null @@ -1,9 +0,0 @@ -package types - -var ( - Int64 = &Base{name: "Int64", size: 8} - Int32 = &Base{name: "Int32", size: 4} - Int16 = &Base{name: "Int16", size: 2} - Int8 = &Base{name: "Int8", size: 1} - Int = Int64 -) diff --git a/src/types/Is.go b/src/types/Is.go index 059086f..438b917 100644 --- a/src/types/Is.go +++ b/src/types/Is.go @@ -2,30 +2,26 @@ package types // Is returns true if the encountered type `a` can be converted into the expected type `b`. func Is(a Type, b Type) bool { - if a == nil { - return true - } - - if a == b { + if a == b || b == Any || a == nil { return true } aPointer, aIsPointer := a.(*Pointer) bPointer, bIsPointer := b.(*Pointer) - if aIsPointer && bIsPointer && (bPointer.To == nil || aPointer.To == bPointer.To) { + if aIsPointer && bIsPointer && (bPointer.To == Any || aPointer.To == bPointer.To) { return true } aArray, aIsArray := a.(*Array) - if aIsArray && bIsPointer && (bPointer.To == nil || aArray.Of == bPointer.To) { + if aIsArray && bIsPointer && (bPointer.To == Any || aArray.Of == bPointer.To) { return true } bArray, bIsArray := b.(*Array) - if aIsArray && bIsArray && aArray.Of == bArray.Of { + if aIsArray && bIsArray && (bArray.Of == Any || aArray.Of == bArray.Of) { return true } diff --git a/src/types/Pointer.go b/src/types/Pointer.go index cc08bd3..1cf180b 100644 --- a/src/types/Pointer.go +++ b/src/types/Pointer.go @@ -1,7 +1,5 @@ package types -var AnyPointer = &Pointer{To: nil} - // Pointer is the address of an object. type Pointer struct { To Type @@ -10,7 +8,7 @@ type Pointer struct { // Name returns the type name. func (p *Pointer) Name() string { if p.To == nil { - return "Pointer" + return "*Any" } return "*" + p.To.Name() diff --git a/src/types/types_test.go b/src/types/types_test.go index f8f3818..e19602c 100644 --- a/src/types/types_test.go +++ b/src/types/types_test.go @@ -9,7 +9,8 @@ import ( func TestName(t *testing.T) { assert.Equal(t, types.Int.Name(), "Int64") - assert.Equal(t, types.AnyPointer.Name(), "Pointer") + assert.Equal(t, types.AnyArray.Name(), "[]Any") + assert.Equal(t, types.AnyPointer.Name(), "*Any") assert.Equal(t, (&types.Pointer{To: types.Int}).Name(), "*Int64") assert.Equal(t, (&types.Array{Of: types.Int}).Name(), "[]Int64") assert.Equal(t, types.String.Name(), "[]Int8") @@ -21,6 +22,7 @@ func TestSize(t *testing.T) { assert.Equal(t, types.Int16.Size(), 2) assert.Equal(t, types.Int32.Size(), 4) assert.Equal(t, types.Int64.Size(), 8) + assert.Equal(t, types.AnyArray.Size(), 8) assert.Equal(t, types.AnyPointer.Size(), 8) assert.Equal(t, types.String.Size(), 8) assert.Equal(t, (&types.Pointer{To: types.Int}).Size(), 8) @@ -39,9 +41,12 @@ func TestStruct(t *testing.T) { } func TestBasics(t *testing.T) { + assert.True(t, types.Is(types.Int, types.Any)) assert.True(t, types.Is(types.Int, types.Int)) assert.True(t, types.Is(types.AnyPointer, types.AnyPointer)) + assert.True(t, types.Is(&types.Array{Of: types.Int}, types.AnyArray)) assert.False(t, types.Is(types.Int, types.Float)) + assert.False(t, types.Is(types.Any, types.Int)) assert.False(t, types.Is(&types.Pointer{To: types.Int}, &types.Pointer{To: types.Float})) } @@ -55,5 +60,9 @@ func TestSpecialCases(t *testing.T) { // A pointer pointing to a known type fulfills the requirement of a pointer to anything. assert.True(t, types.Is(&types.Pointer{To: types.Int}, types.AnyPointer)) assert.True(t, types.Is(&types.Pointer{To: types.Float}, types.AnyPointer)) + + // Case #3: + // Arrays are also just pointers. assert.True(t, types.Is(&types.Array{Of: types.Int}, types.AnyPointer)) + assert.True(t, types.Is(&types.Array{Of: types.Float}, types.AnyPointer)) } diff --git a/tests/errors/TypeMismatch.q b/tests/errors/TypeMismatch.q index 4da2421..57bdd6b 100644 --- a/tests/errors/TypeMismatch.q +++ b/tests/errors/TypeMismatch.q @@ -2,6 +2,6 @@ main() { writeToMemory(42) } -writeToMemory(p Pointer) { +writeToMemory(p *Any) { p[0] = 'A' } \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 24c6b52..92f12a1 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -46,7 +46,7 @@ var errs = []struct { {"MissingParameter3.q", errors.MissingParameter}, {"MissingType.q", errors.MissingType}, {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, - {"TypeMismatch.q", &errors.TypeMismatch{Expected: "Pointer", Encountered: "Int64", ParameterName: "p"}}, + {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*Any", Encountered: "Int64", ParameterName: "p"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, diff --git a/tests/programs/memory-free.q b/tests/programs/memory-free.q index a584ef3..7fb937b 100644 --- a/tests/programs/memory-free.q +++ b/tests/programs/memory-free.q @@ -2,6 +2,5 @@ import mem main() { address := mem.alloc(1024) - err := mem.free(address, 1024) - assert err == 0 + assert mem.free(address) == 0 } \ No newline at end of file From e53ae57a7449ab369835370fb7956ea1adf1df6e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 10 Feb 2025 10:56:32 +0100 Subject: [PATCH 0709/1012] Improved documentation --- docs/readme.md | 59 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index ef9bec5..d9d9c54 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -16,20 +16,65 @@ cd q go build ``` +This will place the compiler inside the repository. +Either use `./q` for the following examples or get access to the shorter `q` in any directory via symlink: + +```shell +ln -s $PWD/q ~/.local/bin/q +``` + +This assumes that your shell loads `~/.local/bin`. + ## Usage ```shell -./q run examples/hello +q [command] [options] ``` -Builds an executable from `examples/hello` and runs it. +The `build` command creates an executable: -## Links +```shell +q build examples/hello +``` -- [Chat](https://matrix.to/#/#community:akyoto.dev) -- [Donate](https://buy.stripe.com/4gw7vf5Jxflf83m7st) -- [License](https://akyoto.dev/license) -- [Todo](todo.md) +The `run` command does everything `build` does but also executes it: + +```shell +q run examples/hello +``` + +You don't have to use physical directories, it's perfectly valid to use a list of files: + +```shell +q build hello.q world.q +``` + +To show verbose compiler output use `-v` or `--verbose`: + +```shell +q build examples/hello -v +``` + +For more information see `q help`. + +## Platforms + +You can cross-compile executables for Linux, Mac and Windows. + +```shell +q build examples/hello --os linux +q build examples/hello --os mac +q build examples/hello --os windows +``` + +## Status + +This project is under heavy development. +Feel free to [get in touch](https://akyoto.dev/contact) if you are interested in helping out. + +## License + +Please see the [license documentation](https://akyoto.dev/license). ## Copyright From 2b46e68f6f40759c6b0c3bf8b2aabd43f47b4cf2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 10 Feb 2025 10:56:32 +0100 Subject: [PATCH 0710/1012] Improved documentation --- docs/readme.md | 59 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index ef9bec5..d9d9c54 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -16,20 +16,65 @@ cd q go build ``` +This will place the compiler inside the repository. +Either use `./q` for the following examples or get access to the shorter `q` in any directory via symlink: + +```shell +ln -s $PWD/q ~/.local/bin/q +``` + +This assumes that your shell loads `~/.local/bin`. + ## Usage ```shell -./q run examples/hello +q [command] [options] ``` -Builds an executable from `examples/hello` and runs it. +The `build` command creates an executable: -## Links +```shell +q build examples/hello +``` -- [Chat](https://matrix.to/#/#community:akyoto.dev) -- [Donate](https://buy.stripe.com/4gw7vf5Jxflf83m7st) -- [License](https://akyoto.dev/license) -- [Todo](todo.md) +The `run` command does everything `build` does but also executes it: + +```shell +q run examples/hello +``` + +You don't have to use physical directories, it's perfectly valid to use a list of files: + +```shell +q build hello.q world.q +``` + +To show verbose compiler output use `-v` or `--verbose`: + +```shell +q build examples/hello -v +``` + +For more information see `q help`. + +## Platforms + +You can cross-compile executables for Linux, Mac and Windows. + +```shell +q build examples/hello --os linux +q build examples/hello --os mac +q build examples/hello --os windows +``` + +## Status + +This project is under heavy development. +Feel free to [get in touch](https://akyoto.dev/contact) if you are interested in helping out. + +## License + +Please see the [license documentation](https://akyoto.dev/license). ## Copyright From f1c8b2daf5903dca4c60d7d52e8fd5fa9c20a4c9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 10 Feb 2025 11:16:22 +0100 Subject: [PATCH 0711/1012] Added funding information --- docs/readme.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/readme.md b/docs/readme.md index d9d9c54..698829d 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -7,6 +7,7 @@ A programming language that compiles down to machine code. - Fast compilation - High performance - Small executables +- Zero dependencies ## Installation @@ -69,9 +70,11 @@ q build examples/hello --os windows ## Status -This project is under heavy development. +`q` is under heavy development and not ready for production yet. Feel free to [get in touch](https://akyoto.dev/contact) if you are interested in helping out. +The biggest obstacle right now is the lack of funding. If you want to help out financially you can [donate towards the project](https://en.liberapay.com/akyoto). + ## License Please see the [license documentation](https://akyoto.dev/license). From b2a9dc3aa788714340161e02656f6831362a7fae Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 10 Feb 2025 11:16:22 +0100 Subject: [PATCH 0712/1012] Added funding information --- docs/readme.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/readme.md b/docs/readme.md index d9d9c54..698829d 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -7,6 +7,7 @@ A programming language that compiles down to machine code. - Fast compilation - High performance - Small executables +- Zero dependencies ## Installation @@ -69,9 +70,11 @@ q build examples/hello --os windows ## Status -This project is under heavy development. +`q` is under heavy development and not ready for production yet. Feel free to [get in touch](https://akyoto.dev/contact) if you are interested in helping out. +The biggest obstacle right now is the lack of funding. If you want to help out financially you can [donate towards the project](https://en.liberapay.com/akyoto). + ## License Please see the [license documentation](https://akyoto.dev/license). From e2f607805c6848e69dd6040d88930072aedf5c0e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 10 Feb 2025 14:09:27 +0100 Subject: [PATCH 0713/1012] Improved type system checks --- src/core/CompileCall.go | 42 ++++++-------------------------- src/core/CompileDefinition.go | 6 ++--- src/core/CompileLen.go | 10 +++++++- src/core/CompileReturn.go | 7 +----- src/core/ExpressionToRegister.go | 12 ++++++--- src/core/Function.go | 27 ++++++++++---------- src/core/ResolveTypes.go | 2 ++ tests/errors/TypeMismatch2.q | 3 +++ tests/errors_test.go | 1 + 9 files changed, 49 insertions(+), 61 deletions(-) create mode 100644 tests/errors/TypeMismatch2.q diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index fa0ffce..a6e78c9 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -14,7 +14,7 @@ import ( // All call registers must hold the correct parameter values before the function invocation. // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. -func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { +func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error) { var ( pkg = f.Package pkgNode *expression.Expression @@ -29,25 +29,12 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { switch name { case "len": - return &Function{ - Output: []*Output{ - { - Type: types.Int, - }, - }, - }, f.CompileLen(root) + return _len.OutputTypes, f.CompileLen(root) case "syscall": return nil, f.CompileSyscall(root) case "new": typ, err := f.CompileNew(root) - - return &Function{ - Output: []*Output{ - { - Type: typ, - }, - }, - }, err + return []types.Type{typ}, err case "delete": return nil, f.CompileDelete(root) case "store": @@ -112,29 +99,16 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { } if !types.Is(typ, fn.Input[i].Type) { - if parameters[i].Token.Kind == token.Number && parameters[i].Token.Text(f.File.Bytes) == "0" { + _, expectsPointer := fn.Input[i].Type.(*types.Pointer) + + if expectsPointer && parameters[i].Token.Kind == token.Number && parameters[i].Token.Text(f.File.Bytes) == "0" { continue } - encountered := "" - expected := "" - - if typ != nil { - encountered = typ.Name() - } - - if fn.Input[i].Type != nil { - expected = fn.Input[i].Type.Name() - } - - return nil, errors.New(&errors.TypeMismatch{ - Encountered: encountered, - Expected: expected, - ParameterName: fn.Input[i].Name, - }, f.File, parameters[i].Token.Position) + return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: fn.Input[i].Type.Name(), ParameterName: fn.Input[i].Name}, f.File, parameters[i].Token.Position) } } f.CallSafe(fn, registers) - return fn, nil + return fn.OutputTypes, nil } diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index b50b3dd..0887e0f 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -40,7 +40,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { } count := 0 - called, err := f.CompileCall(right) + types, err := f.CompileCall(right) if err != nil { return err @@ -53,8 +53,8 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return err } - if called != nil { - variable.Type = called.Output[count].Type + if count < len(types) { + variable.Type = types[count] } f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index cb7cb44..e7264f3 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -4,17 +4,25 @@ import ( "math" "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" ) +var _len = Function{OutputTypes: []types.Type{types.Int}} + // CompileLen returns the length of a slice. func (f *Function) CompileLen(root *expression.Expression) error { - _, register, isTemporary, err := f.Evaluate(root.Children[1]) + typ, register, isTemporary, err := f.Evaluate(root.Children[1]) if err != nil { return err } + if !types.Is(typ, types.AnyArray) { + return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.AnyArray.Name(), ParameterName: "array"}, f.File, root.Children[1].Token.Position) + } + f.SaveRegister(f.CPU.Output[0]) f.MemoryRegister(asm.LOAD, asm.Memory{Base: register, Offset: -8, OffsetRegister: math.MaxUint8, Length: 8}, f.CPU.Output[0]) diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index f535936..19d7963 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -30,12 +30,7 @@ func (f *Function) CompileReturn(node *ast.Return) error { return nil } - return errors.New(&errors.TypeMismatch{ - Encountered: typ.Name(), - Expected: f.Output[i].Type.Name(), - ParameterName: "", - IsReturn: true, - }, f.File, node.Values[i].Token.Position) + return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: f.Output[i].Type.Name(), ParameterName: "", IsReturn: true}, f.File, node.Values[i].Token.Position) } } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 4157a1d..4dfb2d7 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -24,17 +24,21 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } if ast.IsFunctionCall(node) { - fn, err := f.CompileCall(node) + types, err := f.CompileCall(node) + + if err != nil { + return nil, err + } if register != f.CPU.Output[0] { f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) } - if fn == nil || len(fn.Output) == 0 { - return nil, err + if len(types) == 0 { + return nil, nil } - return fn.Output[0].Type, err + return types[0], err } if node.Token.Kind == token.Array { diff --git a/src/core/Function.go b/src/core/Function.go index a502ccb..d0a6098 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -11,19 +11,20 @@ import ( // Function represents the smallest unit of code. type Function struct { register.Machine - Package string - Name string - UniqueName string - File *fs.File - Body token.List - Input []*Input - Output []*Output - Functions map[string]*Function - Structs map[string]*types.Struct - DLLs dll.List - Err error - deferred []func() - count counter + Package string + Name string + UniqueName string + File *fs.File + Body token.List + Input []*Input + Output []*Output + OutputTypes []types.Type + Functions map[string]*Function + Structs map[string]*types.Struct + DLLs dll.List + Err error + deferred []func() + count counter } // counter stores how often a certain statement appeared so we can generate a unique label from it. diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index 5636fcd..1a395cc 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -40,6 +40,8 @@ func (f *Function) ResolveTypes() error { if param.Type == nil { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) } + + f.OutputTypes = append(f.OutputTypes, param.Type) } return nil diff --git a/tests/errors/TypeMismatch2.q b/tests/errors/TypeMismatch2.q new file mode 100644 index 0000000..bc1b199 --- /dev/null +++ b/tests/errors/TypeMismatch2.q @@ -0,0 +1,3 @@ +main() { + len(123) +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 92f12a1..db440a2 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -47,6 +47,7 @@ var errs = []struct { {"MissingType.q", errors.MissingType}, {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*Any", Encountered: "Int64", ParameterName: "p"}}, + {"TypeMismatch2.q", &errors.TypeMismatch{Expected: "[]Any", Encountered: "Int64", ParameterName: "array"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, From 371059d08a90bcb6a2de46fc53d1ee3f1c778af6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 10 Feb 2025 14:09:27 +0100 Subject: [PATCH 0714/1012] Improved type system checks --- src/core/CompileCall.go | 42 ++++++-------------------------- src/core/CompileDefinition.go | 6 ++--- src/core/CompileLen.go | 10 +++++++- src/core/CompileReturn.go | 7 +----- src/core/ExpressionToRegister.go | 12 ++++++--- src/core/Function.go | 27 ++++++++++---------- src/core/ResolveTypes.go | 2 ++ tests/errors/TypeMismatch2.q | 3 +++ tests/errors_test.go | 1 + 9 files changed, 49 insertions(+), 61 deletions(-) create mode 100644 tests/errors/TypeMismatch2.q diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index fa0ffce..a6e78c9 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -14,7 +14,7 @@ import ( // All call registers must hold the correct parameter values before the function invocation. // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. -func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { +func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error) { var ( pkg = f.Package pkgNode *expression.Expression @@ -29,25 +29,12 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { switch name { case "len": - return &Function{ - Output: []*Output{ - { - Type: types.Int, - }, - }, - }, f.CompileLen(root) + return _len.OutputTypes, f.CompileLen(root) case "syscall": return nil, f.CompileSyscall(root) case "new": typ, err := f.CompileNew(root) - - return &Function{ - Output: []*Output{ - { - Type: typ, - }, - }, - }, err + return []types.Type{typ}, err case "delete": return nil, f.CompileDelete(root) case "store": @@ -112,29 +99,16 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { } if !types.Is(typ, fn.Input[i].Type) { - if parameters[i].Token.Kind == token.Number && parameters[i].Token.Text(f.File.Bytes) == "0" { + _, expectsPointer := fn.Input[i].Type.(*types.Pointer) + + if expectsPointer && parameters[i].Token.Kind == token.Number && parameters[i].Token.Text(f.File.Bytes) == "0" { continue } - encountered := "" - expected := "" - - if typ != nil { - encountered = typ.Name() - } - - if fn.Input[i].Type != nil { - expected = fn.Input[i].Type.Name() - } - - return nil, errors.New(&errors.TypeMismatch{ - Encountered: encountered, - Expected: expected, - ParameterName: fn.Input[i].Name, - }, f.File, parameters[i].Token.Position) + return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: fn.Input[i].Type.Name(), ParameterName: fn.Input[i].Name}, f.File, parameters[i].Token.Position) } } f.CallSafe(fn, registers) - return fn, nil + return fn.OutputTypes, nil } diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index b50b3dd..0887e0f 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -40,7 +40,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { } count := 0 - called, err := f.CompileCall(right) + types, err := f.CompileCall(right) if err != nil { return err @@ -53,8 +53,8 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return err } - if called != nil { - variable.Type = called.Output[count].Type + if count < len(types) { + variable.Type = types[count] } f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index cb7cb44..e7264f3 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -4,17 +4,25 @@ import ( "math" "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" ) +var _len = Function{OutputTypes: []types.Type{types.Int}} + // CompileLen returns the length of a slice. func (f *Function) CompileLen(root *expression.Expression) error { - _, register, isTemporary, err := f.Evaluate(root.Children[1]) + typ, register, isTemporary, err := f.Evaluate(root.Children[1]) if err != nil { return err } + if !types.Is(typ, types.AnyArray) { + return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.AnyArray.Name(), ParameterName: "array"}, f.File, root.Children[1].Token.Position) + } + f.SaveRegister(f.CPU.Output[0]) f.MemoryRegister(asm.LOAD, asm.Memory{Base: register, Offset: -8, OffsetRegister: math.MaxUint8, Length: 8}, f.CPU.Output[0]) diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index f535936..19d7963 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -30,12 +30,7 @@ func (f *Function) CompileReturn(node *ast.Return) error { return nil } - return errors.New(&errors.TypeMismatch{ - Encountered: typ.Name(), - Expected: f.Output[i].Type.Name(), - ParameterName: "", - IsReturn: true, - }, f.File, node.Values[i].Token.Position) + return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: f.Output[i].Type.Name(), ParameterName: "", IsReturn: true}, f.File, node.Values[i].Token.Position) } } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 4157a1d..4dfb2d7 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -24,17 +24,21 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } if ast.IsFunctionCall(node) { - fn, err := f.CompileCall(node) + types, err := f.CompileCall(node) + + if err != nil { + return nil, err + } if register != f.CPU.Output[0] { f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) } - if fn == nil || len(fn.Output) == 0 { - return nil, err + if len(types) == 0 { + return nil, nil } - return fn.Output[0].Type, err + return types[0], err } if node.Token.Kind == token.Array { diff --git a/src/core/Function.go b/src/core/Function.go index a502ccb..d0a6098 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -11,19 +11,20 @@ import ( // Function represents the smallest unit of code. type Function struct { register.Machine - Package string - Name string - UniqueName string - File *fs.File - Body token.List - Input []*Input - Output []*Output - Functions map[string]*Function - Structs map[string]*types.Struct - DLLs dll.List - Err error - deferred []func() - count counter + Package string + Name string + UniqueName string + File *fs.File + Body token.List + Input []*Input + Output []*Output + OutputTypes []types.Type + Functions map[string]*Function + Structs map[string]*types.Struct + DLLs dll.List + Err error + deferred []func() + count counter } // counter stores how often a certain statement appeared so we can generate a unique label from it. diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index 5636fcd..1a395cc 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -40,6 +40,8 @@ func (f *Function) ResolveTypes() error { if param.Type == nil { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) } + + f.OutputTypes = append(f.OutputTypes, param.Type) } return nil diff --git a/tests/errors/TypeMismatch2.q b/tests/errors/TypeMismatch2.q new file mode 100644 index 0000000..bc1b199 --- /dev/null +++ b/tests/errors/TypeMismatch2.q @@ -0,0 +1,3 @@ +main() { + len(123) +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 92f12a1..db440a2 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -47,6 +47,7 @@ var errs = []struct { {"MissingType.q", errors.MissingType}, {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*Any", Encountered: "Int64", ParameterName: "p"}}, + {"TypeMismatch2.q", &errors.TypeMismatch{Expected: "[]Any", Encountered: "Int64", ParameterName: "array"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, From f1f09eaa2cab555e624746c8fc320c462c5986d8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 10 Feb 2025 17:17:59 +0100 Subject: [PATCH 0715/1012] Added number of used functions to the statistics --- src/compiler/Result.go | 23 ++++++++++++++++++++--- src/core/PrintInstructions.go | 4 ++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 0788a36..9ca52e4 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -22,6 +22,7 @@ import ( type Result struct { Main *core.Function Functions map[string]*core.Function + Traversed map[*core.Function]bool InstructionCount int DataCount int Code []byte @@ -60,9 +61,11 @@ func (r *Result) finalize() { {Name: "kernel32", Functions: []string{"ExitProcess"}}, } + r.Traversed = make(map[*core.Function]bool, len(r.Functions)) + // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. - r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { + r.eachFunction(r.Main, r.Traversed, func(f *core.Function) { final.Merge(f.Assembler) for _, library := range f.DLLs { @@ -127,8 +130,22 @@ func (r *Result) PrintInstructions() { // PrintStatistics shows the statistics. func (r *Result) PrintStatistics() { ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮") - ansi.Dim.Printf("│ %-44s%-32s │\n", "Code:", fmt.Sprintf("%d bytes", len(r.Code))) - ansi.Dim.Printf("│ %-44s%-32s │\n", "Data:", fmt.Sprintf("%d bytes", len(r.Data))) + + ansi.Dim.Print("│ ") + ansi.Dim.Printf("%-44s", "Code:") + fmt.Printf("%-32s", fmt.Sprintf("%d bytes", len(r.Code))) + ansi.Dim.Print(" │\n") + + ansi.Dim.Print("│ ") + ansi.Dim.Printf("%-44s", "Data:") + fmt.Printf("%-32s", fmt.Sprintf("%d bytes", len(r.Data))) + ansi.Dim.Print(" │\n") + + ansi.Dim.Print("│ ") + ansi.Dim.Printf("%-44s", "Functions:") + fmt.Printf("%-32s", fmt.Sprintf("%d / %d", len(r.Traversed), len(r.Functions))) + ansi.Dim.Print(" │\n") + ansi.Dim.Println("╰──────────────────────────────────────────────────────────────────────────────╯") } diff --git a/src/core/PrintInstructions.go b/src/core/PrintInstructions.go index d777ff0..3a1acf5 100644 --- a/src/core/PrintInstructions.go +++ b/src/core/PrintInstructions.go @@ -46,9 +46,9 @@ func (f *Function) PrintInstructions() { for _, reg := range f.CPU.All { if used&(1< Date: Mon, 10 Feb 2025 17:17:59 +0100 Subject: [PATCH 0716/1012] Added number of used functions to the statistics --- src/compiler/Result.go | 23 ++++++++++++++++++++--- src/core/PrintInstructions.go | 4 ++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 0788a36..9ca52e4 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -22,6 +22,7 @@ import ( type Result struct { Main *core.Function Functions map[string]*core.Function + Traversed map[*core.Function]bool InstructionCount int DataCount int Code []byte @@ -60,9 +61,11 @@ func (r *Result) finalize() { {Name: "kernel32", Functions: []string{"ExitProcess"}}, } + r.Traversed = make(map[*core.Function]bool, len(r.Functions)) + // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. - r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { + r.eachFunction(r.Main, r.Traversed, func(f *core.Function) { final.Merge(f.Assembler) for _, library := range f.DLLs { @@ -127,8 +130,22 @@ func (r *Result) PrintInstructions() { // PrintStatistics shows the statistics. func (r *Result) PrintStatistics() { ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮") - ansi.Dim.Printf("│ %-44s%-32s │\n", "Code:", fmt.Sprintf("%d bytes", len(r.Code))) - ansi.Dim.Printf("│ %-44s%-32s │\n", "Data:", fmt.Sprintf("%d bytes", len(r.Data))) + + ansi.Dim.Print("│ ") + ansi.Dim.Printf("%-44s", "Code:") + fmt.Printf("%-32s", fmt.Sprintf("%d bytes", len(r.Code))) + ansi.Dim.Print(" │\n") + + ansi.Dim.Print("│ ") + ansi.Dim.Printf("%-44s", "Data:") + fmt.Printf("%-32s", fmt.Sprintf("%d bytes", len(r.Data))) + ansi.Dim.Print(" │\n") + + ansi.Dim.Print("│ ") + ansi.Dim.Printf("%-44s", "Functions:") + fmt.Printf("%-32s", fmt.Sprintf("%d / %d", len(r.Traversed), len(r.Functions))) + ansi.Dim.Print(" │\n") + ansi.Dim.Println("╰──────────────────────────────────────────────────────────────────────────────╯") } diff --git a/src/core/PrintInstructions.go b/src/core/PrintInstructions.go index d777ff0..3a1acf5 100644 --- a/src/core/PrintInstructions.go +++ b/src/core/PrintInstructions.go @@ -46,9 +46,9 @@ func (f *Function) PrintInstructions() { for _, reg := range f.CPU.All { if used&(1< Date: Tue, 11 Feb 2025 12:41:20 +0100 Subject: [PATCH 0717/1012] Improved color support on Windows --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a911fe7..3a61a1f 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.23 require ( git.akyoto.dev/go/assert v0.1.3 - git.akyoto.dev/go/color v0.1.1 + git.akyoto.dev/go/color v0.1.2 ) require golang.org/x/sys v0.30.0 // indirect diff --git a/go.sum b/go.sum index 11abd11..439aeca 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= -git.akyoto.dev/go/color v0.1.1 h1:mMAoMIwLBPNy7ocRSxdsCFs7onPC3GfDEiJErCneqRE= -git.akyoto.dev/go/color v0.1.1/go.mod h1:ywOjoD0O0sk6bIn92uAJf7mErlEFCuQInL84y4Lqi3Q= +git.akyoto.dev/go/color v0.1.2 h1:aNJP3upGimmhtV576Hxa4FZhU8GfnPuI5ZYZpVWsFLI= +git.akyoto.dev/go/color v0.1.2/go.mod h1:e00cRnX0fzFyIYEpAA7dCR/Hlk0/2YpXpVQaMT5Zoss= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= From b2f67429f3d7ee754dc09234e4625e3cbe5cdf26 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 11 Feb 2025 12:41:20 +0100 Subject: [PATCH 0718/1012] Improved color support on Windows --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a911fe7..3a61a1f 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.23 require ( git.akyoto.dev/go/assert v0.1.3 - git.akyoto.dev/go/color v0.1.1 + git.akyoto.dev/go/color v0.1.2 ) require golang.org/x/sys v0.30.0 // indirect diff --git a/go.sum b/go.sum index 11abd11..439aeca 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= -git.akyoto.dev/go/color v0.1.1 h1:mMAoMIwLBPNy7ocRSxdsCFs7onPC3GfDEiJErCneqRE= -git.akyoto.dev/go/color v0.1.1/go.mod h1:ywOjoD0O0sk6bIn92uAJf7mErlEFCuQInL84y4Lqi3Q= +git.akyoto.dev/go/color v0.1.2 h1:aNJP3upGimmhtV576Hxa4FZhU8GfnPuI5ZYZpVWsFLI= +git.akyoto.dev/go/color v0.1.2/go.mod h1:e00cRnX0fzFyIYEpAA7dCR/Hlk0/2YpXpVQaMT5Zoss= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= From 38b573085ec3460f693e346ca49776eb168e4496 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 11 Feb 2025 18:48:40 +0100 Subject: [PATCH 0719/1012] Added more tests --- tests/programs/len.q | 14 ++++++++++++++ tests/programs_test.go | 1 + 2 files changed, 15 insertions(+) create mode 100644 tests/programs/len.q diff --git a/tests/programs/len.q b/tests/programs/len.q new file mode 100644 index 0000000..5efdfc7 --- /dev/null +++ b/tests/programs/len.q @@ -0,0 +1,14 @@ +import mem + +main() { + a := mem.alloc(16) + assert len(a) == 16 + mem.free(a) + + assert len("H") == 1 + assert len("He") == 2 + assert len("Hel") == 3 + assert len("Hell") == 4 + assert len("Hello") == 5 + assert len("日本") == 6 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 675548b..d5a1eca 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -62,6 +62,7 @@ var programs = []struct { {"memory-free", "", "", 0}, {"out-of-memory", "", "", 0}, {"struct", "", "", 0}, + {"len", "", "", 0}, } func TestPrograms(t *testing.T) { From 1083db6ab2795c38b97db2c06bfa35cbaa1fec96 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 11 Feb 2025 18:48:40 +0100 Subject: [PATCH 0720/1012] Added more tests --- tests/programs/len.q | 14 ++++++++++++++ tests/programs_test.go | 1 + 2 files changed, 15 insertions(+) create mode 100644 tests/programs/len.q diff --git a/tests/programs/len.q b/tests/programs/len.q new file mode 100644 index 0000000..5efdfc7 --- /dev/null +++ b/tests/programs/len.q @@ -0,0 +1,14 @@ +import mem + +main() { + a := mem.alloc(16) + assert len(a) == 16 + mem.free(a) + + assert len("H") == 1 + assert len("He") == 2 + assert len("Hel") == 3 + assert len("Hell") == 4 + assert len("Hello") == 5 + assert len("日本") == 6 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 675548b..d5a1eca 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -62,6 +62,7 @@ var programs = []struct { {"memory-free", "", "", 0}, {"out-of-memory", "", "", 0}, {"struct", "", "", 0}, + {"len", "", "", 0}, } func TestPrograms(t *testing.T) { From 4803b81845f03880570dd14b16ef93357fd960d2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Feb 2025 00:04:30 +0100 Subject: [PATCH 0721/1012] Implemented extern functions --- examples/winapi/winapi.q | 4 + lib/io/io.q | 12 +-- lib/sys/io_linux.q | 8 +- lib/sys/io_mac.q | 8 +- lib/sys/io_windows.q | 14 ++- lib/sys/mem_windows.q | 5 ++ lib/sys/proc_windows.q | 4 + src/compiler/Compile.go | 4 + src/core/BeforeCall.go | 32 +++++++ src/core/CompileCall.go | 48 ++++------ src/core/IsExtern.go | 6 ++ src/core/ResolveTypes.go | 6 +- src/errors/Common.go | 1 + src/scanner/scanExtern.go | 54 ++++++++++++ src/scanner/scanFile.go | 2 + src/scanner/scanFunction.go | 126 ++------------------------- src/scanner/scanFunctionSignature.go | 125 ++++++++++++++++++++++++++ src/token/Kind.go | 1 + src/token/Tokenize_test.go | 3 +- src/token/identifier.go | 2 + src/types/ByName.go | 4 + src/types/Common.go | 2 + tests/errors/ExpectedDLLName.q | 1 + tests/errors_test.go | 1 + 24 files changed, 304 insertions(+), 169 deletions(-) create mode 100644 src/core/BeforeCall.go create mode 100644 src/core/IsExtern.go create mode 100644 src/scanner/scanExtern.go create mode 100644 src/scanner/scanFunctionSignature.go create mode 100644 tests/errors/ExpectedDLLName.q diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q index e4a670f..a3f1dd7 100644 --- a/examples/winapi/winapi.q +++ b/examples/winapi/winapi.q @@ -1,3 +1,7 @@ +extern user32 { + MessageBoxA(window *Any, text *Int8, title *Int8, type UInt) +} + main() { title := "Title." text := "Hi!" diff --git a/lib/io/io.q b/lib/io/io.q index 545c810..5c195a4 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -4,18 +4,18 @@ in(buffer []Int8) -> Int { return sys.read(0, buffer, len(buffer)) } -out(message []Int8) -> Int { - return sys.write(1, message, len(message)) +out(buffer []Int8) -> Int { + return sys.write(1, buffer, len(buffer)) } -error(message []Int8) -> Int { - return sys.write(2, message, len(message)) +error(buffer []Int8) -> Int { + return sys.write(2, buffer, len(buffer)) } read(fd Int, buffer []Int8) -> Int { return sys.read(fd, buffer, len(buffer)) } -write(fd Int, message []Int8) -> Int { - return sys.write(fd, message, len(message)) +write(fd Int, buffer []Int8) -> Int { + return sys.write(fd, buffer, len(buffer)) } \ No newline at end of file diff --git a/lib/sys/io_linux.q b/lib/sys/io_linux.q index 9a3b2c5..d204bb4 100644 --- a/lib/sys/io_linux.q +++ b/lib/sys/io_linux.q @@ -1,9 +1,9 @@ -read(fd Int, address *Any, length Int) -> Int { - return syscall(0, fd, address, length) +read(fd Int, buffer *Any, length Int) -> Int { + return syscall(0, fd, buffer, length) } -write(fd Int, address *Any, length Int) -> Int { - return syscall(1, fd, address, length) +write(fd Int, buffer *Any, length Int) -> Int { + return syscall(1, fd, buffer, length) } open(path *Any, flags Int, mode Int) -> Int { diff --git a/lib/sys/io_mac.q b/lib/sys/io_mac.q index 4ef451a..8fc3dbd 100644 --- a/lib/sys/io_mac.q +++ b/lib/sys/io_mac.q @@ -1,9 +1,9 @@ -read(fd Int, address *Any, length Int) -> Int { - return syscall(0x2000003, fd, address, length) +read(fd Int, buffer *Any, length Int) -> Int { + return syscall(0x2000003, fd, buffer, length) } -write(fd Int, address *Any, length Int) -> Int { - return syscall(0x2000004, fd, address, length) +write(fd Int, buffer *Any, length Int) -> Int { + return syscall(0x2000004, fd, buffer, length) } open(path *Any, flags Int, mode Int) -> Int { diff --git a/lib/sys/io_windows.q b/lib/sys/io_windows.q index 964a201..81351c3 100644 --- a/lib/sys/io_windows.q +++ b/lib/sys/io_windows.q @@ -1,10 +1,16 @@ -read(fd Int, address *Any, length Int) -> Int { - kernel32.ReadFile(fd, address, length) +extern kernel32 { + GetStdHandle(handle Int) -> Int + WriteConsoleA(fd Int, buffer *Any, length Int, written *Int) -> Bool + ReadFile(fd Int, buffer *Any, length Int) -> Bool +} + +read(fd Int, buffer *Any, length Int) -> Int { + kernel32.ReadFile(fd, buffer, length) return length } -write(fd Int, address *Any, length Int) -> Int { +write(fd Int, buffer *Any, length Int) -> Int { fd = kernel32.GetStdHandle(-10 - fd) - kernel32.WriteConsoleA(fd, address, length, 0) + kernel32.WriteConsoleA(fd, buffer, length, 0) return length } \ No newline at end of file diff --git a/lib/sys/mem_windows.q b/lib/sys/mem_windows.q index 319e966..79b093a 100644 --- a/lib/sys/mem_windows.q +++ b/lib/sys/mem_windows.q @@ -1,3 +1,8 @@ +extern kernel32 { + VirtualAlloc(address Int, length Int, flags Int, protection Int) + VirtualFree(address *Any, length Int, type Int) -> Bool +} + mmap(address Int, length Int, protection Int, flags Int) -> *Any { return kernel32.VirtualAlloc(address, length, flags, protection) } diff --git a/lib/sys/proc_windows.q b/lib/sys/proc_windows.q index 7819bca..c5c4cdc 100644 --- a/lib/sys/proc_windows.q +++ b/lib/sys/proc_windows.q @@ -1,3 +1,7 @@ +extern kernel32 { + ExitProcess(code UInt) +} + exit(code Int) { kernel32.ExitProcess(code) } \ No newline at end of file diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 57ccb04..df5f2dc 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -108,6 +108,10 @@ func CompileFunctions(functions map[string]*core.Function) { wg := sync.WaitGroup{} for _, function := range functions { + if function.IsExtern() { + continue + } + wg.Add(1) go func() { diff --git a/src/core/BeforeCall.go b/src/core/BeforeCall.go new file mode 100644 index 0000000..57812f8 --- /dev/null +++ b/src/core/BeforeCall.go @@ -0,0 +1,32 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" +) + +// BeforeCall loads the registers with the parameter values and checks that the types match with the function signature. +func (f *Function) BeforeCall(fn *Function, parameters []*expression.Expression, registers []cpu.Register) error { + for i := len(parameters) - 1; i >= 0; i-- { + typ, err := f.ExpressionToRegister(parameters[i], registers[i]) + + if err != nil { + return err + } + + if !types.Is(typ, fn.Input[i].Type) { + _, expectsPointer := fn.Input[i].Type.(*types.Pointer) + + if expectsPointer && parameters[i].Token.Kind == token.Number && parameters[i].Token.Text(f.File.Bytes) == "0" { + continue + } + + return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: fn.Input[i].Type.Name(), ParameterName: fn.Input[i].Name}, f.File, parameters[i].Token.Position) + } + } + + return nil +} diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index a6e78c9..688673a 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -5,7 +5,6 @@ import ( "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/types" "git.akyoto.dev/cli/q/src/x86" ) @@ -48,22 +47,13 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error name = nameNode.Token.Text(f.File.Bytes) } - if pkg == "kernel32" || pkg == "user32" || pkg == "gdi32" || pkg == "comctl32" { - parameters := root.Children[1:] - registers := x86.WindowsInputRegisters[:len(parameters)] + fn, exists = f.Functions[pkg+"."+name] - for i := len(parameters) - 1; i >= 0; i-- { - _, err := f.ExpressionToRegister(parameters[i], registers[i]) + if !exists { + return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) + } - if err != nil { - return nil, err - } - } - - f.DLLs = f.DLLs.Append(pkg, name) - f.DLLCall(fmt.Sprintf("%s.%s", pkg, name)) - return nil, nil - } else if pkg != f.File.Package { + if pkg != f.File.Package && !fn.IsExtern() { if f.File.Imports == nil { return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, pkgNode.Token.Position) } @@ -77,36 +67,30 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error imp.Used = true } - fn, exists = f.Functions[pkg+"."+name] - - if !exists { - return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) - } - parameters := root.Children[1:] if len(parameters) != len(fn.Input) { return nil, errors.New(&errors.ParameterCountMismatch{Function: fn.Name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, nameNode.Token.End()) } - registers := f.CPU.Input[:len(parameters)] - - for i := len(parameters) - 1; i >= 0; i-- { - typ, err := f.ExpressionToRegister(parameters[i], registers[i]) + if fn.IsExtern() { + f.DLLs = f.DLLs.Append(pkg, name) + registers := x86.WindowsInputRegisters[:len(parameters)] + err := f.BeforeCall(fn, parameters, registers) if err != nil { return nil, err } - if !types.Is(typ, fn.Input[i].Type) { - _, expectsPointer := fn.Input[i].Type.(*types.Pointer) + f.DLLCall(fmt.Sprintf("%s.%s", pkg, name)) + return fn.OutputTypes, nil + } - if expectsPointer && parameters[i].Token.Kind == token.Number && parameters[i].Token.Text(f.File.Bytes) == "0" { - continue - } + registers := f.CPU.Input[:len(parameters)] + err := f.BeforeCall(fn, parameters, registers) - return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: fn.Input[i].Type.Name(), ParameterName: fn.Input[i].Name}, f.File, parameters[i].Token.Position) - } + if err != nil { + return nil, err } f.CallSafe(fn, registers) diff --git a/src/core/IsExtern.go b/src/core/IsExtern.go new file mode 100644 index 0000000..6722db9 --- /dev/null +++ b/src/core/IsExtern.go @@ -0,0 +1,6 @@ +package core + +// IsExtern returns true if the function has no body. +func (f *Function) IsExtern() bool { + return f.Body == nil +} diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index 1a395cc..79ce233 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -19,6 +19,10 @@ func (f *Function) ResolveTypes() error { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) } + if f.IsExtern() { + continue + } + uses := token.Count(f.Body, f.File.Bytes, token.Identifier, param.Name) if uses == 0 && param.Name != "_" { @@ -38,7 +42,7 @@ func (f *Function) ResolveTypes() error { param.Type = types.ByName(typeName, f.Package, f.Structs) if param.Type == nil { - return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) + return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[0].Position) } f.OutputTypes = append(f.OutputTypes, param.Type) diff --git a/src/errors/Common.go b/src/errors/Common.go index e78af1d..7b00ddd 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -9,6 +9,7 @@ var ( ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} ExpectedPackageName = &Base{"Expected package name"} ExpectedStructName = &Base{"Expected struct name"} + ExpectedDLLName = &Base{"Expected DLL name"} InvalidNumber = &Base{"Invalid number"} InvalidExpression = &Base{"Invalid expression"} InvalidRune = &Base{"Invalid rune"} diff --git a/src/scanner/scanExtern.go b/src/scanner/scanExtern.go new file mode 100644 index 0000000..e4dc19d --- /dev/null +++ b/src/scanner/scanExtern.go @@ -0,0 +1,54 @@ +package scanner + +import ( + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" +) + +// scanExtern scans a block of external function declarations. +func (s *Scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, error) { + i++ + + if tokens[i].Kind != token.Identifier { + return i, errors.New(errors.ExpectedDLLName, file, tokens[i].Position) + } + + dllName := tokens[i].Text(file.Bytes) + i++ + + if tokens[i].Kind != token.BlockStart { + return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) + } + + i++ + closed := false + + for i < len(tokens) { + if tokens[i].Kind == token.Identifier { + function, j, err := scanFunctionSignature(file, tokens, i, token.NewLine) + + if err != nil { + return j, err + } + + i = j + function.Package = dllName + function.UniqueName = dllName + "." + function.Name + s.functions <- function + } + + if tokens[i].Kind == token.BlockEnd { + closed = true + break + } + + i++ + } + + if !closed { + return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) + } + + return i, nil +} diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 3b1bff2..2aae3af 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -37,6 +37,8 @@ func (s *Scanner) scanFile(path string, pkg string) error { i, err = s.scanStruct(file, tokens, i) case token.Identifier: i, err = s.scanFunction(file, tokens, i) + case token.Extern: + i, err = s.scanExtern(file, tokens, i) case token.EOF: return nil case token.Invalid: diff --git a/src/scanner/scanFunction.go b/src/scanner/scanFunction.go index 63fb880..6d85d83 100644 --- a/src/scanner/scanFunction.go +++ b/src/scanner/scanFunction.go @@ -1,7 +1,6 @@ package scanner import ( - "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/token" @@ -9,84 +8,17 @@ import ( // scanFunction scans a function. func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, error) { + function, i, err := scanFunctionSignature(file, tokens, i, token.BlockStart) + + if err != nil { + return i, err + } + var ( - groupLevel = 0 - blockLevel = 0 - nameStart = i - paramsStart = -1 - paramsEnd = -1 - bodyStart = -1 - typeStart = -1 - typeEnd = -1 + blockLevel = 0 + bodyStart = -1 ) - i++ - - // Function parameters - for i < len(tokens) { - if tokens[i].Kind == token.GroupStart { - groupLevel++ - i++ - - if groupLevel == 1 { - paramsStart = i - } - - continue - } - - if tokens[i].Kind == token.GroupEnd { - groupLevel-- - - if groupLevel < 0 { - return i, errors.New(errors.MissingGroupStart, file, tokens[i].Position) - } - - if groupLevel == 0 { - paramsEnd = i - i++ - break - } - - i++ - continue - } - - if tokens[i].Kind == token.Invalid { - return i, errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position) - } - - if tokens[i].Kind == token.EOF { - if groupLevel > 0 { - return i, errors.New(errors.MissingGroupEnd, file, tokens[i].Position) - } - - if paramsStart == -1 { - return i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) - } - - return i, nil - } - - if groupLevel > 0 { - i++ - continue - } - - return i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) - } - - // Return type - if i < len(tokens) && tokens[i].Kind == token.ReturnType { - typeStart = i + 1 - - for i < len(tokens) && tokens[i].Kind != token.BlockStart { - i++ - } - - typeEnd = i - } - // Function definition for i < len(tokens) { if tokens[i].Kind == token.ReturnType { @@ -144,47 +76,7 @@ func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, er return i, errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) } - name := tokens[nameStart].Text(file.Bytes) - body := tokens[bodyStart:i] - function := core.NewFunction(file.Package, name, file, body) - - if typeStart != -1 { - if tokens[typeStart].Kind == token.GroupStart && tokens[typeEnd-1].Kind == token.GroupEnd { - typeStart++ - typeEnd-- - } - - outputTokens := tokens[typeStart:typeEnd] - - err := outputTokens.Split(func(tokens token.List) error { - function.Output = append(function.Output, core.NewOutput(tokens)) - return nil - }) - - if err != nil { - return i, err - } - } - - parameters := tokens[paramsStart:paramsEnd] - - err := parameters.Split(func(tokens token.List) error { - if len(tokens) == 0 { - return errors.New(errors.MissingParameter, file, parameters[0].Position) - } - - if len(tokens) == 1 { - return errors.New(errors.MissingType, file, tokens[0].End()) - } - - function.Input = append(function.Input, core.NewInput(tokens)) - return nil - }) - - if err != nil { - return i, err - } - + function.Body = tokens[bodyStart:i] s.functions <- function i++ return i, nil diff --git a/src/scanner/scanFunctionSignature.go b/src/scanner/scanFunctionSignature.go new file mode 100644 index 0000000..f060214 --- /dev/null +++ b/src/scanner/scanFunctionSignature.go @@ -0,0 +1,125 @@ +package scanner + +import ( + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" +) + +// scanFunctionSignature scans a function declaration without the body. +func scanFunctionSignature(file *fs.File, tokens token.List, i int, delimiter token.Kind) (*core.Function, int, error) { + var ( + groupLevel = 0 + nameStart = i + paramsStart = -1 + paramsEnd = -1 + typeStart = -1 + typeEnd = -1 + ) + + i++ + + // Function parameters + for i < len(tokens) { + if tokens[i].Kind == token.GroupStart { + groupLevel++ + i++ + + if groupLevel == 1 { + paramsStart = i + } + + continue + } + + if tokens[i].Kind == token.GroupEnd { + groupLevel-- + + if groupLevel < 0 { + return nil, i, errors.New(errors.MissingGroupStart, file, tokens[i].Position) + } + + if groupLevel == 0 { + paramsEnd = i + i++ + break + } + + i++ + continue + } + + if tokens[i].Kind == token.Invalid { + return nil, i, errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position) + } + + if tokens[i].Kind == token.EOF { + if groupLevel > 0 { + return nil, i, errors.New(errors.MissingGroupEnd, file, tokens[i].Position) + } + + if paramsStart == -1 { + return nil, i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) + } + + return nil, i, nil + } + + if groupLevel > 0 { + i++ + continue + } + + return nil, i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) + } + + // Return type + if i < len(tokens) && tokens[i].Kind == token.ReturnType { + typeStart = i + 1 + + for i < len(tokens) && tokens[i].Kind != delimiter { + i++ + } + + typeEnd = i + } + + name := tokens[nameStart].Text(file.Bytes) + function := core.NewFunction(file.Package, name, file, nil) + + if typeStart != -1 { + if tokens[typeStart].Kind == token.GroupStart && tokens[typeEnd-1].Kind == token.GroupEnd { + typeStart++ + typeEnd-- + } + + outputTokens := tokens[typeStart:typeEnd] + + err := outputTokens.Split(func(tokens token.List) error { + function.Output = append(function.Output, core.NewOutput(tokens)) + return nil + }) + + if err != nil { + return nil, i, err + } + } + + parameters := tokens[paramsStart:paramsEnd] + + err := parameters.Split(func(tokens token.List) error { + if len(tokens) == 0 { + return errors.New(errors.MissingParameter, file, parameters[0].Position) + } + + if len(tokens) == 1 { + return errors.New(errors.MissingType, file, tokens[0].End()) + } + + function.Input = append(function.Input, core.NewInput(tokens)) + return nil + }) + + return function, i, err +} diff --git a/src/token/Kind.go b/src/token/Kind.go index 1a652b2..6e729e4 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -66,6 +66,7 @@ const ( ___KEYWORDS___ // Assert // assert Else // else + Extern // extern If // if Import // import Loop // loop diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index e755f97..022115d 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -25,13 +25,14 @@ func TestFunction(t *testing.T) { } func TestKeyword(t *testing.T) { - tokens := token.Tokenize([]byte("assert if import else loop return struct switch")) + tokens := token.Tokenize([]byte("assert if import else extern loop return struct switch")) expected := []token.Kind{ token.Assert, token.If, token.Import, token.Else, + token.Extern, token.Loop, token.Return, token.Struct, diff --git a/src/token/identifier.go b/src/token/identifier.go index fa9cc63..6b6823b 100644 --- a/src/token/identifier.go +++ b/src/token/identifier.go @@ -19,6 +19,8 @@ func identifier(tokens List, buffer []byte, i Position) (List, Position) { kind = If case "else": kind = Else + case "extern": + kind = Extern case "import": kind = Import case "loop": diff --git a/src/types/ByName.go b/src/types/ByName.go index 365e55b..27ac5fc 100644 --- a/src/types/ByName.go +++ b/src/types/ByName.go @@ -31,6 +31,8 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { switch name { case "Any": return Any + case "Bool": + return Bool case "Int": return Int case "Int64": @@ -47,6 +49,8 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { return Float64 case "Float32": return Float32 + case "UInt": + return UInt } typ, exists := structs[pkg+"."+name] diff --git a/src/types/Common.go b/src/types/Common.go index 6f1918f..aab4a96 100644 --- a/src/types/Common.go +++ b/src/types/Common.go @@ -13,6 +13,8 @@ var ( ) var ( + Bool = Int Int = Int64 Float = Float64 + UInt = Int ) diff --git a/tests/errors/ExpectedDLLName.q b/tests/errors/ExpectedDLLName.q new file mode 100644 index 0000000..de11df4 --- /dev/null +++ b/tests/errors/ExpectedDLLName.q @@ -0,0 +1 @@ +extern {} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index db440a2..c2b6588 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -15,6 +15,7 @@ var errs = []struct { ExpectedError error }{ {"EmptySwitch.q", errors.EmptySwitch}, + {"ExpectedDLLName.q", errors.ExpectedDLLName}, {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse}, From 3b66dae1d4ec70c24d44d4dea5968e4d41915975 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Feb 2025 00:04:30 +0100 Subject: [PATCH 0722/1012] Implemented extern functions --- examples/winapi/winapi.q | 4 + lib/io/io.q | 12 +-- lib/sys/io_linux.q | 8 +- lib/sys/io_mac.q | 8 +- lib/sys/io_windows.q | 14 ++- lib/sys/mem_windows.q | 5 ++ lib/sys/proc_windows.q | 4 + src/compiler/Compile.go | 4 + src/core/BeforeCall.go | 32 +++++++ src/core/CompileCall.go | 48 ++++------ src/core/IsExtern.go | 6 ++ src/core/ResolveTypes.go | 6 +- src/errors/Common.go | 1 + src/scanner/scanExtern.go | 54 ++++++++++++ src/scanner/scanFile.go | 2 + src/scanner/scanFunction.go | 126 ++------------------------- src/scanner/scanFunctionSignature.go | 125 ++++++++++++++++++++++++++ src/token/Kind.go | 1 + src/token/Tokenize_test.go | 3 +- src/token/identifier.go | 2 + src/types/ByName.go | 4 + src/types/Common.go | 2 + tests/errors/ExpectedDLLName.q | 1 + tests/errors_test.go | 1 + 24 files changed, 304 insertions(+), 169 deletions(-) create mode 100644 src/core/BeforeCall.go create mode 100644 src/core/IsExtern.go create mode 100644 src/scanner/scanExtern.go create mode 100644 src/scanner/scanFunctionSignature.go create mode 100644 tests/errors/ExpectedDLLName.q diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q index e4a670f..a3f1dd7 100644 --- a/examples/winapi/winapi.q +++ b/examples/winapi/winapi.q @@ -1,3 +1,7 @@ +extern user32 { + MessageBoxA(window *Any, text *Int8, title *Int8, type UInt) +} + main() { title := "Title." text := "Hi!" diff --git a/lib/io/io.q b/lib/io/io.q index 545c810..5c195a4 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -4,18 +4,18 @@ in(buffer []Int8) -> Int { return sys.read(0, buffer, len(buffer)) } -out(message []Int8) -> Int { - return sys.write(1, message, len(message)) +out(buffer []Int8) -> Int { + return sys.write(1, buffer, len(buffer)) } -error(message []Int8) -> Int { - return sys.write(2, message, len(message)) +error(buffer []Int8) -> Int { + return sys.write(2, buffer, len(buffer)) } read(fd Int, buffer []Int8) -> Int { return sys.read(fd, buffer, len(buffer)) } -write(fd Int, message []Int8) -> Int { - return sys.write(fd, message, len(message)) +write(fd Int, buffer []Int8) -> Int { + return sys.write(fd, buffer, len(buffer)) } \ No newline at end of file diff --git a/lib/sys/io_linux.q b/lib/sys/io_linux.q index 9a3b2c5..d204bb4 100644 --- a/lib/sys/io_linux.q +++ b/lib/sys/io_linux.q @@ -1,9 +1,9 @@ -read(fd Int, address *Any, length Int) -> Int { - return syscall(0, fd, address, length) +read(fd Int, buffer *Any, length Int) -> Int { + return syscall(0, fd, buffer, length) } -write(fd Int, address *Any, length Int) -> Int { - return syscall(1, fd, address, length) +write(fd Int, buffer *Any, length Int) -> Int { + return syscall(1, fd, buffer, length) } open(path *Any, flags Int, mode Int) -> Int { diff --git a/lib/sys/io_mac.q b/lib/sys/io_mac.q index 4ef451a..8fc3dbd 100644 --- a/lib/sys/io_mac.q +++ b/lib/sys/io_mac.q @@ -1,9 +1,9 @@ -read(fd Int, address *Any, length Int) -> Int { - return syscall(0x2000003, fd, address, length) +read(fd Int, buffer *Any, length Int) -> Int { + return syscall(0x2000003, fd, buffer, length) } -write(fd Int, address *Any, length Int) -> Int { - return syscall(0x2000004, fd, address, length) +write(fd Int, buffer *Any, length Int) -> Int { + return syscall(0x2000004, fd, buffer, length) } open(path *Any, flags Int, mode Int) -> Int { diff --git a/lib/sys/io_windows.q b/lib/sys/io_windows.q index 964a201..81351c3 100644 --- a/lib/sys/io_windows.q +++ b/lib/sys/io_windows.q @@ -1,10 +1,16 @@ -read(fd Int, address *Any, length Int) -> Int { - kernel32.ReadFile(fd, address, length) +extern kernel32 { + GetStdHandle(handle Int) -> Int + WriteConsoleA(fd Int, buffer *Any, length Int, written *Int) -> Bool + ReadFile(fd Int, buffer *Any, length Int) -> Bool +} + +read(fd Int, buffer *Any, length Int) -> Int { + kernel32.ReadFile(fd, buffer, length) return length } -write(fd Int, address *Any, length Int) -> Int { +write(fd Int, buffer *Any, length Int) -> Int { fd = kernel32.GetStdHandle(-10 - fd) - kernel32.WriteConsoleA(fd, address, length, 0) + kernel32.WriteConsoleA(fd, buffer, length, 0) return length } \ No newline at end of file diff --git a/lib/sys/mem_windows.q b/lib/sys/mem_windows.q index 319e966..79b093a 100644 --- a/lib/sys/mem_windows.q +++ b/lib/sys/mem_windows.q @@ -1,3 +1,8 @@ +extern kernel32 { + VirtualAlloc(address Int, length Int, flags Int, protection Int) + VirtualFree(address *Any, length Int, type Int) -> Bool +} + mmap(address Int, length Int, protection Int, flags Int) -> *Any { return kernel32.VirtualAlloc(address, length, flags, protection) } diff --git a/lib/sys/proc_windows.q b/lib/sys/proc_windows.q index 7819bca..c5c4cdc 100644 --- a/lib/sys/proc_windows.q +++ b/lib/sys/proc_windows.q @@ -1,3 +1,7 @@ +extern kernel32 { + ExitProcess(code UInt) +} + exit(code Int) { kernel32.ExitProcess(code) } \ No newline at end of file diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 57ccb04..df5f2dc 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -108,6 +108,10 @@ func CompileFunctions(functions map[string]*core.Function) { wg := sync.WaitGroup{} for _, function := range functions { + if function.IsExtern() { + continue + } + wg.Add(1) go func() { diff --git a/src/core/BeforeCall.go b/src/core/BeforeCall.go new file mode 100644 index 0000000..57812f8 --- /dev/null +++ b/src/core/BeforeCall.go @@ -0,0 +1,32 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" +) + +// BeforeCall loads the registers with the parameter values and checks that the types match with the function signature. +func (f *Function) BeforeCall(fn *Function, parameters []*expression.Expression, registers []cpu.Register) error { + for i := len(parameters) - 1; i >= 0; i-- { + typ, err := f.ExpressionToRegister(parameters[i], registers[i]) + + if err != nil { + return err + } + + if !types.Is(typ, fn.Input[i].Type) { + _, expectsPointer := fn.Input[i].Type.(*types.Pointer) + + if expectsPointer && parameters[i].Token.Kind == token.Number && parameters[i].Token.Text(f.File.Bytes) == "0" { + continue + } + + return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: fn.Input[i].Type.Name(), ParameterName: fn.Input[i].Name}, f.File, parameters[i].Token.Position) + } + } + + return nil +} diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index a6e78c9..688673a 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -5,7 +5,6 @@ import ( "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/types" "git.akyoto.dev/cli/q/src/x86" ) @@ -48,22 +47,13 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error name = nameNode.Token.Text(f.File.Bytes) } - if pkg == "kernel32" || pkg == "user32" || pkg == "gdi32" || pkg == "comctl32" { - parameters := root.Children[1:] - registers := x86.WindowsInputRegisters[:len(parameters)] + fn, exists = f.Functions[pkg+"."+name] - for i := len(parameters) - 1; i >= 0; i-- { - _, err := f.ExpressionToRegister(parameters[i], registers[i]) + if !exists { + return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) + } - if err != nil { - return nil, err - } - } - - f.DLLs = f.DLLs.Append(pkg, name) - f.DLLCall(fmt.Sprintf("%s.%s", pkg, name)) - return nil, nil - } else if pkg != f.File.Package { + if pkg != f.File.Package && !fn.IsExtern() { if f.File.Imports == nil { return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, pkgNode.Token.Position) } @@ -77,36 +67,30 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error imp.Used = true } - fn, exists = f.Functions[pkg+"."+name] - - if !exists { - return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) - } - parameters := root.Children[1:] if len(parameters) != len(fn.Input) { return nil, errors.New(&errors.ParameterCountMismatch{Function: fn.Name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, nameNode.Token.End()) } - registers := f.CPU.Input[:len(parameters)] - - for i := len(parameters) - 1; i >= 0; i-- { - typ, err := f.ExpressionToRegister(parameters[i], registers[i]) + if fn.IsExtern() { + f.DLLs = f.DLLs.Append(pkg, name) + registers := x86.WindowsInputRegisters[:len(parameters)] + err := f.BeforeCall(fn, parameters, registers) if err != nil { return nil, err } - if !types.Is(typ, fn.Input[i].Type) { - _, expectsPointer := fn.Input[i].Type.(*types.Pointer) + f.DLLCall(fmt.Sprintf("%s.%s", pkg, name)) + return fn.OutputTypes, nil + } - if expectsPointer && parameters[i].Token.Kind == token.Number && parameters[i].Token.Text(f.File.Bytes) == "0" { - continue - } + registers := f.CPU.Input[:len(parameters)] + err := f.BeforeCall(fn, parameters, registers) - return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: fn.Input[i].Type.Name(), ParameterName: fn.Input[i].Name}, f.File, parameters[i].Token.Position) - } + if err != nil { + return nil, err } f.CallSafe(fn, registers) diff --git a/src/core/IsExtern.go b/src/core/IsExtern.go new file mode 100644 index 0000000..6722db9 --- /dev/null +++ b/src/core/IsExtern.go @@ -0,0 +1,6 @@ +package core + +// IsExtern returns true if the function has no body. +func (f *Function) IsExtern() bool { + return f.Body == nil +} diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index 1a395cc..79ce233 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -19,6 +19,10 @@ func (f *Function) ResolveTypes() error { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) } + if f.IsExtern() { + continue + } + uses := token.Count(f.Body, f.File.Bytes, token.Identifier, param.Name) if uses == 0 && param.Name != "_" { @@ -38,7 +42,7 @@ func (f *Function) ResolveTypes() error { param.Type = types.ByName(typeName, f.Package, f.Structs) if param.Type == nil { - return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) + return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[0].Position) } f.OutputTypes = append(f.OutputTypes, param.Type) diff --git a/src/errors/Common.go b/src/errors/Common.go index e78af1d..7b00ddd 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -9,6 +9,7 @@ var ( ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} ExpectedPackageName = &Base{"Expected package name"} ExpectedStructName = &Base{"Expected struct name"} + ExpectedDLLName = &Base{"Expected DLL name"} InvalidNumber = &Base{"Invalid number"} InvalidExpression = &Base{"Invalid expression"} InvalidRune = &Base{"Invalid rune"} diff --git a/src/scanner/scanExtern.go b/src/scanner/scanExtern.go new file mode 100644 index 0000000..e4dc19d --- /dev/null +++ b/src/scanner/scanExtern.go @@ -0,0 +1,54 @@ +package scanner + +import ( + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" +) + +// scanExtern scans a block of external function declarations. +func (s *Scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, error) { + i++ + + if tokens[i].Kind != token.Identifier { + return i, errors.New(errors.ExpectedDLLName, file, tokens[i].Position) + } + + dllName := tokens[i].Text(file.Bytes) + i++ + + if tokens[i].Kind != token.BlockStart { + return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) + } + + i++ + closed := false + + for i < len(tokens) { + if tokens[i].Kind == token.Identifier { + function, j, err := scanFunctionSignature(file, tokens, i, token.NewLine) + + if err != nil { + return j, err + } + + i = j + function.Package = dllName + function.UniqueName = dllName + "." + function.Name + s.functions <- function + } + + if tokens[i].Kind == token.BlockEnd { + closed = true + break + } + + i++ + } + + if !closed { + return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) + } + + return i, nil +} diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 3b1bff2..2aae3af 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -37,6 +37,8 @@ func (s *Scanner) scanFile(path string, pkg string) error { i, err = s.scanStruct(file, tokens, i) case token.Identifier: i, err = s.scanFunction(file, tokens, i) + case token.Extern: + i, err = s.scanExtern(file, tokens, i) case token.EOF: return nil case token.Invalid: diff --git a/src/scanner/scanFunction.go b/src/scanner/scanFunction.go index 63fb880..6d85d83 100644 --- a/src/scanner/scanFunction.go +++ b/src/scanner/scanFunction.go @@ -1,7 +1,6 @@ package scanner import ( - "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/token" @@ -9,84 +8,17 @@ import ( // scanFunction scans a function. func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, error) { + function, i, err := scanFunctionSignature(file, tokens, i, token.BlockStart) + + if err != nil { + return i, err + } + var ( - groupLevel = 0 - blockLevel = 0 - nameStart = i - paramsStart = -1 - paramsEnd = -1 - bodyStart = -1 - typeStart = -1 - typeEnd = -1 + blockLevel = 0 + bodyStart = -1 ) - i++ - - // Function parameters - for i < len(tokens) { - if tokens[i].Kind == token.GroupStart { - groupLevel++ - i++ - - if groupLevel == 1 { - paramsStart = i - } - - continue - } - - if tokens[i].Kind == token.GroupEnd { - groupLevel-- - - if groupLevel < 0 { - return i, errors.New(errors.MissingGroupStart, file, tokens[i].Position) - } - - if groupLevel == 0 { - paramsEnd = i - i++ - break - } - - i++ - continue - } - - if tokens[i].Kind == token.Invalid { - return i, errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position) - } - - if tokens[i].Kind == token.EOF { - if groupLevel > 0 { - return i, errors.New(errors.MissingGroupEnd, file, tokens[i].Position) - } - - if paramsStart == -1 { - return i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) - } - - return i, nil - } - - if groupLevel > 0 { - i++ - continue - } - - return i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) - } - - // Return type - if i < len(tokens) && tokens[i].Kind == token.ReturnType { - typeStart = i + 1 - - for i < len(tokens) && tokens[i].Kind != token.BlockStart { - i++ - } - - typeEnd = i - } - // Function definition for i < len(tokens) { if tokens[i].Kind == token.ReturnType { @@ -144,47 +76,7 @@ func (s *Scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, er return i, errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) } - name := tokens[nameStart].Text(file.Bytes) - body := tokens[bodyStart:i] - function := core.NewFunction(file.Package, name, file, body) - - if typeStart != -1 { - if tokens[typeStart].Kind == token.GroupStart && tokens[typeEnd-1].Kind == token.GroupEnd { - typeStart++ - typeEnd-- - } - - outputTokens := tokens[typeStart:typeEnd] - - err := outputTokens.Split(func(tokens token.List) error { - function.Output = append(function.Output, core.NewOutput(tokens)) - return nil - }) - - if err != nil { - return i, err - } - } - - parameters := tokens[paramsStart:paramsEnd] - - err := parameters.Split(func(tokens token.List) error { - if len(tokens) == 0 { - return errors.New(errors.MissingParameter, file, parameters[0].Position) - } - - if len(tokens) == 1 { - return errors.New(errors.MissingType, file, tokens[0].End()) - } - - function.Input = append(function.Input, core.NewInput(tokens)) - return nil - }) - - if err != nil { - return i, err - } - + function.Body = tokens[bodyStart:i] s.functions <- function i++ return i, nil diff --git a/src/scanner/scanFunctionSignature.go b/src/scanner/scanFunctionSignature.go new file mode 100644 index 0000000..f060214 --- /dev/null +++ b/src/scanner/scanFunctionSignature.go @@ -0,0 +1,125 @@ +package scanner + +import ( + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" +) + +// scanFunctionSignature scans a function declaration without the body. +func scanFunctionSignature(file *fs.File, tokens token.List, i int, delimiter token.Kind) (*core.Function, int, error) { + var ( + groupLevel = 0 + nameStart = i + paramsStart = -1 + paramsEnd = -1 + typeStart = -1 + typeEnd = -1 + ) + + i++ + + // Function parameters + for i < len(tokens) { + if tokens[i].Kind == token.GroupStart { + groupLevel++ + i++ + + if groupLevel == 1 { + paramsStart = i + } + + continue + } + + if tokens[i].Kind == token.GroupEnd { + groupLevel-- + + if groupLevel < 0 { + return nil, i, errors.New(errors.MissingGroupStart, file, tokens[i].Position) + } + + if groupLevel == 0 { + paramsEnd = i + i++ + break + } + + i++ + continue + } + + if tokens[i].Kind == token.Invalid { + return nil, i, errors.New(&errors.InvalidCharacter{Character: tokens[i].Text(file.Bytes)}, file, tokens[i].Position) + } + + if tokens[i].Kind == token.EOF { + if groupLevel > 0 { + return nil, i, errors.New(errors.MissingGroupEnd, file, tokens[i].Position) + } + + if paramsStart == -1 { + return nil, i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) + } + + return nil, i, nil + } + + if groupLevel > 0 { + i++ + continue + } + + return nil, i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) + } + + // Return type + if i < len(tokens) && tokens[i].Kind == token.ReturnType { + typeStart = i + 1 + + for i < len(tokens) && tokens[i].Kind != delimiter { + i++ + } + + typeEnd = i + } + + name := tokens[nameStart].Text(file.Bytes) + function := core.NewFunction(file.Package, name, file, nil) + + if typeStart != -1 { + if tokens[typeStart].Kind == token.GroupStart && tokens[typeEnd-1].Kind == token.GroupEnd { + typeStart++ + typeEnd-- + } + + outputTokens := tokens[typeStart:typeEnd] + + err := outputTokens.Split(func(tokens token.List) error { + function.Output = append(function.Output, core.NewOutput(tokens)) + return nil + }) + + if err != nil { + return nil, i, err + } + } + + parameters := tokens[paramsStart:paramsEnd] + + err := parameters.Split(func(tokens token.List) error { + if len(tokens) == 0 { + return errors.New(errors.MissingParameter, file, parameters[0].Position) + } + + if len(tokens) == 1 { + return errors.New(errors.MissingType, file, tokens[0].End()) + } + + function.Input = append(function.Input, core.NewInput(tokens)) + return nil + }) + + return function, i, err +} diff --git a/src/token/Kind.go b/src/token/Kind.go index 1a652b2..6e729e4 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -66,6 +66,7 @@ const ( ___KEYWORDS___ // Assert // assert Else // else + Extern // extern If // if Import // import Loop // loop diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index e755f97..022115d 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -25,13 +25,14 @@ func TestFunction(t *testing.T) { } func TestKeyword(t *testing.T) { - tokens := token.Tokenize([]byte("assert if import else loop return struct switch")) + tokens := token.Tokenize([]byte("assert if import else extern loop return struct switch")) expected := []token.Kind{ token.Assert, token.If, token.Import, token.Else, + token.Extern, token.Loop, token.Return, token.Struct, diff --git a/src/token/identifier.go b/src/token/identifier.go index fa9cc63..6b6823b 100644 --- a/src/token/identifier.go +++ b/src/token/identifier.go @@ -19,6 +19,8 @@ func identifier(tokens List, buffer []byte, i Position) (List, Position) { kind = If case "else": kind = Else + case "extern": + kind = Extern case "import": kind = Import case "loop": diff --git a/src/types/ByName.go b/src/types/ByName.go index 365e55b..27ac5fc 100644 --- a/src/types/ByName.go +++ b/src/types/ByName.go @@ -31,6 +31,8 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { switch name { case "Any": return Any + case "Bool": + return Bool case "Int": return Int case "Int64": @@ -47,6 +49,8 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { return Float64 case "Float32": return Float32 + case "UInt": + return UInt } typ, exists := structs[pkg+"."+name] diff --git a/src/types/Common.go b/src/types/Common.go index 6f1918f..aab4a96 100644 --- a/src/types/Common.go +++ b/src/types/Common.go @@ -13,6 +13,8 @@ var ( ) var ( + Bool = Int Int = Int64 Float = Float64 + UInt = Int ) diff --git a/tests/errors/ExpectedDLLName.q b/tests/errors/ExpectedDLLName.q new file mode 100644 index 0000000..de11df4 --- /dev/null +++ b/tests/errors/ExpectedDLLName.q @@ -0,0 +1 @@ +extern {} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index db440a2..c2b6588 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -15,6 +15,7 @@ var errs = []struct { ExpectedError error }{ {"EmptySwitch.q", errors.EmptySwitch}, + {"ExpectedDLLName.q", errors.ExpectedDLLName}, {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse}, From 2203b124b71916025e5279bedd1443b7a1472a21 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Feb 2025 11:25:20 +0100 Subject: [PATCH 0723/1012] Moved external calls to a separate function --- src/core/CallExtern.go | 23 +++++++++++++++++++++++ src/core/CompileCall.go | 14 +------------- 2 files changed, 24 insertions(+), 13 deletions(-) create mode 100644 src/core/CallExtern.go diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go new file mode 100644 index 0000000..8d3f358 --- /dev/null +++ b/src/core/CallExtern.go @@ -0,0 +1,23 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" + "git.akyoto.dev/cli/q/src/x86" +) + +// CallExtern calls an external function. +func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) ([]types.Type, error) { + f.DLLs = f.DLLs.Append(fn.Package, fn.Name) + registers := x86.WindowsInputRegisters[:len(parameters)] + err := f.BeforeCall(fn, parameters, registers) + + if err != nil { + return nil, err + } + + f.DLLCall(fmt.Sprintf("%s.%s", fn.Package, fn.Name)) + return fn.OutputTypes, nil +} diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 688673a..2518ab5 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -1,12 +1,9 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/cli/q/src/x86" ) // CompileCall executes a function call. @@ -74,16 +71,7 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error } if fn.IsExtern() { - f.DLLs = f.DLLs.Append(pkg, name) - registers := x86.WindowsInputRegisters[:len(parameters)] - err := f.BeforeCall(fn, parameters, registers) - - if err != nil { - return nil, err - } - - f.DLLCall(fmt.Sprintf("%s.%s", pkg, name)) - return fn.OutputTypes, nil + return f.CallExtern(fn, parameters) } registers := f.CPU.Input[:len(parameters)] From 49afa5d800a9a3e7f3b7f0a5f1bfb253650bc7ad Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Feb 2025 11:25:20 +0100 Subject: [PATCH 0724/1012] Moved external calls to a separate function --- src/core/CallExtern.go | 23 +++++++++++++++++++++++ src/core/CompileCall.go | 14 +------------- 2 files changed, 24 insertions(+), 13 deletions(-) create mode 100644 src/core/CallExtern.go diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go new file mode 100644 index 0000000..8d3f358 --- /dev/null +++ b/src/core/CallExtern.go @@ -0,0 +1,23 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" + "git.akyoto.dev/cli/q/src/x86" +) + +// CallExtern calls an external function. +func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) ([]types.Type, error) { + f.DLLs = f.DLLs.Append(fn.Package, fn.Name) + registers := x86.WindowsInputRegisters[:len(parameters)] + err := f.BeforeCall(fn, parameters, registers) + + if err != nil { + return nil, err + } + + f.DLLCall(fmt.Sprintf("%s.%s", fn.Package, fn.Name)) + return fn.OutputTypes, nil +} diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 688673a..2518ab5 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -1,12 +1,9 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/cli/q/src/x86" ) // CompileCall executes a function call. @@ -74,16 +71,7 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error } if fn.IsExtern() { - f.DLLs = f.DLLs.Append(pkg, name) - registers := x86.WindowsInputRegisters[:len(parameters)] - err := f.BeforeCall(fn, parameters, registers) - - if err != nil { - return nil, err - } - - f.DLLCall(fmt.Sprintf("%s.%s", pkg, name)) - return fn.OutputTypes, nil + return f.CallExtern(fn, parameters) } registers := f.CPU.Input[:len(parameters)] From 77d354936c80b96e6cfc0eb05bf5fbbd94d0b6f7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Feb 2025 12:47:08 +0100 Subject: [PATCH 0725/1012] Fixed volatile registers on external calls --- src/core/CallExtern.go | 23 +++++++++++++++++++++++ src/x86/Registers.go | 17 +++++++++-------- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go index 8d3f358..abb1a7a 100644 --- a/src/core/CallExtern.go +++ b/src/core/CallExtern.go @@ -2,7 +2,10 @@ package core import ( "fmt" + "slices" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/types" "git.akyoto.dev/cli/q/src/x86" @@ -11,6 +14,17 @@ import ( // CallExtern calls an external function. func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) ([]types.Type, error) { f.DLLs = f.DLLs.Append(fn.Package, fn.Name) + + var pushedRegisters []cpu.Register + + for _, register := range x86.WindowsVolatileRegisters { + if f.RegisterIsUsed(register) { + f.Register(asm.PUSH, register) + f.FreeRegister(register) + pushedRegisters = append(pushedRegisters, register) + } + } + registers := x86.WindowsInputRegisters[:len(parameters)] err := f.BeforeCall(fn, parameters, registers) @@ -19,5 +33,14 @@ func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) } f.DLLCall(fmt.Sprintf("%s.%s", fn.Package, fn.Name)) + + for _, register := range registers { + f.FreeRegister(register) + } + + for _, register := range slices.Backward(pushedRegisters) { + f.Register(asm.POP, register) + } + return fn.OutputTypes, nil } diff --git a/src/x86/Registers.go b/src/x86/Registers.go index 0ed7c5f..090a678 100644 --- a/src/x86/Registers.go +++ b/src/x86/Registers.go @@ -22,12 +22,13 @@ const ( ) var ( - AllRegisters = []cpu.Register{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15} - SyscallInputRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} - SyscallOutputRegisters = []cpu.Register{RAX, RCX, R11} - GeneralRegisters = []cpu.Register{RCX, RBX, R11, R12, R13, R14, R15, RBP} - InputRegisters = SyscallInputRegisters - OutputRegisters = SyscallInputRegisters - WindowsInputRegisters = []cpu.Register{RCX, RDX, R8, R9} - WindowsOutputRegisters = []cpu.Register{RAX} + AllRegisters = []cpu.Register{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15} + SyscallInputRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} + SyscallOutputRegisters = []cpu.Register{RAX, RCX, R11} + GeneralRegisters = []cpu.Register{RBX, R12, R13, R14, R15, RCX, R11, RBP} + InputRegisters = SyscallInputRegisters + OutputRegisters = SyscallInputRegisters + WindowsInputRegisters = []cpu.Register{RCX, RDX, R8, R9} + WindowsOutputRegisters = []cpu.Register{RAX} + WindowsVolatileRegisters = []cpu.Register{RCX, RDX, R8, R9, R10, R11} ) From c10395eddcaa669e60bca4525a8fd9719f85f08d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Feb 2025 12:47:08 +0100 Subject: [PATCH 0726/1012] Fixed volatile registers on external calls --- src/core/CallExtern.go | 23 +++++++++++++++++++++++ src/x86/Registers.go | 17 +++++++++-------- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go index 8d3f358..abb1a7a 100644 --- a/src/core/CallExtern.go +++ b/src/core/CallExtern.go @@ -2,7 +2,10 @@ package core import ( "fmt" + "slices" + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/types" "git.akyoto.dev/cli/q/src/x86" @@ -11,6 +14,17 @@ import ( // CallExtern calls an external function. func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) ([]types.Type, error) { f.DLLs = f.DLLs.Append(fn.Package, fn.Name) + + var pushedRegisters []cpu.Register + + for _, register := range x86.WindowsVolatileRegisters { + if f.RegisterIsUsed(register) { + f.Register(asm.PUSH, register) + f.FreeRegister(register) + pushedRegisters = append(pushedRegisters, register) + } + } + registers := x86.WindowsInputRegisters[:len(parameters)] err := f.BeforeCall(fn, parameters, registers) @@ -19,5 +33,14 @@ func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) } f.DLLCall(fmt.Sprintf("%s.%s", fn.Package, fn.Name)) + + for _, register := range registers { + f.FreeRegister(register) + } + + for _, register := range slices.Backward(pushedRegisters) { + f.Register(asm.POP, register) + } + return fn.OutputTypes, nil } diff --git a/src/x86/Registers.go b/src/x86/Registers.go index 0ed7c5f..090a678 100644 --- a/src/x86/Registers.go +++ b/src/x86/Registers.go @@ -22,12 +22,13 @@ const ( ) var ( - AllRegisters = []cpu.Register{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15} - SyscallInputRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} - SyscallOutputRegisters = []cpu.Register{RAX, RCX, R11} - GeneralRegisters = []cpu.Register{RCX, RBX, R11, R12, R13, R14, R15, RBP} - InputRegisters = SyscallInputRegisters - OutputRegisters = SyscallInputRegisters - WindowsInputRegisters = []cpu.Register{RCX, RDX, R8, R9} - WindowsOutputRegisters = []cpu.Register{RAX} + AllRegisters = []cpu.Register{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15} + SyscallInputRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} + SyscallOutputRegisters = []cpu.Register{RAX, RCX, R11} + GeneralRegisters = []cpu.Register{RBX, R12, R13, R14, R15, RCX, R11, RBP} + InputRegisters = SyscallInputRegisters + OutputRegisters = SyscallInputRegisters + WindowsInputRegisters = []cpu.Register{RCX, RDX, R8, R9} + WindowsOutputRegisters = []cpu.Register{RAX} + WindowsVolatileRegisters = []cpu.Register{RCX, RDX, R8, R9, R10, R11} ) From 5f8b859f4a240791d115a5f5b8074d94faa2654d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Feb 2025 15:00:19 +0100 Subject: [PATCH 0727/1012] Fixed incorrect number of history entries --- src/asm/{unnecessary.go => CanSkip.go} | 8 ++++++-- src/asm/CanSkipReturn.go | 16 ++++++++++++++++ src/asm/Instructions.go | 8 -------- src/asm/RegisterRegister.go | 4 ---- src/core/CompileReturn.go | 6 +++--- src/register/MemoryRegister.go | 5 +++++ src/register/RegisterNumber.go | 3 +-- src/register/RegisterRegister.go | 2 +- src/register/Return.go | 7 +++++++ 9 files changed, 39 insertions(+), 20 deletions(-) rename src/asm/{unnecessary.go => CanSkip.go} (67%) create mode 100644 src/asm/CanSkipReturn.go diff --git a/src/asm/unnecessary.go b/src/asm/CanSkip.go similarity index 67% rename from src/asm/unnecessary.go rename to src/asm/CanSkip.go index c3a915a..c707990 100644 --- a/src/asm/unnecessary.go +++ b/src/asm/CanSkip.go @@ -2,8 +2,12 @@ package asm import "git.akyoto.dev/cli/q/src/cpu" -// unnecessary returns true if the register/register operation can be skipped. -func (a *Assembler) unnecessary(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool { +// CanSkip returns true if the register/register operation can be skipped. +func (a *Assembler) CanSkip(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool { + if mnemonic == MOVE && left == right { + return true + } + if len(a.Instructions) == 0 { return false } diff --git a/src/asm/CanSkipReturn.go b/src/asm/CanSkipReturn.go new file mode 100644 index 0000000..0ce31cb --- /dev/null +++ b/src/asm/CanSkipReturn.go @@ -0,0 +1,16 @@ +package asm + +// CanSkipReturn returns true if the return operation can be skipped. +func (a *Assembler) CanSkipReturn() bool { + if len(a.Instructions) == 0 { + return false + } + + lastMnemonic := a.Instructions[len(a.Instructions)-1].Mnemonic + + if lastMnemonic == RETURN || lastMnemonic == JUMP { + return true + } + + return false +} diff --git a/src/asm/Instructions.go b/src/asm/Instructions.go index acfa639..ab6a510 100644 --- a/src/asm/Instructions.go +++ b/src/asm/Instructions.go @@ -32,14 +32,6 @@ func (a *Assembler) DLLCall(name string) { // Return returns back to the caller. func (a *Assembler) Return() { - if len(a.Instructions) > 0 { - lastMnemonic := a.Instructions[len(a.Instructions)-1].Mnemonic - - if lastMnemonic == RETURN || lastMnemonic == JUMP { - return - } - } - a.Instructions = append(a.Instructions, Instruction{Mnemonic: RETURN}) } diff --git a/src/asm/RegisterRegister.go b/src/asm/RegisterRegister.go index 8631fd6..c133e2c 100644 --- a/src/asm/RegisterRegister.go +++ b/src/asm/RegisterRegister.go @@ -19,10 +19,6 @@ func (data *RegisterRegister) String() string { // RegisterRegister adds an instruction using two registers. func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right cpu.Register) { - if a.unnecessary(mnemonic, left, right) { - return - } - a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, Data: &RegisterRegister{ diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 19d7963..87050e8 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -8,9 +8,8 @@ import ( // CompileReturn compiles a return instruction. func (f *Function) CompileReturn(node *ast.Return) error { - defer f.Return() - if len(node.Values) == 0 { + f.Return() return nil } @@ -27,12 +26,13 @@ func (f *Function) CompileReturn(node *ast.Return) error { if !types.Is(typ, f.Output[i].Type) { if f.Package == "mem" && f.Name == "alloc" { - return nil + continue } return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: f.Output[i].Type.Name(), ParameterName: "", IsReturn: true}, f.File, node.Values[i].Token.Position) } } + f.Return() return nil } diff --git a/src/register/MemoryRegister.go b/src/register/MemoryRegister.go index 1a3c770..fa13e9f 100644 --- a/src/register/MemoryRegister.go +++ b/src/register/MemoryRegister.go @@ -7,5 +7,10 @@ import ( func (f *Machine) MemoryRegister(mnemonic asm.Mnemonic, a asm.Memory, b cpu.Register) { f.Assembler.MemoryRegister(mnemonic, a, b) + + if mnemonic == asm.LOAD { + f.UseRegister(b) + } + f.postInstruction() } diff --git a/src/register/RegisterNumber.go b/src/register/RegisterNumber.go index 23aa23f..f3986f5 100644 --- a/src/register/RegisterNumber.go +++ b/src/register/RegisterNumber.go @@ -27,7 +27,6 @@ func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { f.Assembler.RegisterNumber(asm.MOVE, tmp, b) f.UseRegister(tmp) f.postInstruction() - f.Assembler.RegisterRegister(mnemonic, a, tmp) - f.postInstruction() + f.RegisterRegister(mnemonic, a, tmp) f.FreeRegister(tmp) } diff --git a/src/register/RegisterRegister.go b/src/register/RegisterRegister.go index 98ac5ea..59208ae 100644 --- a/src/register/RegisterRegister.go +++ b/src/register/RegisterRegister.go @@ -6,7 +6,7 @@ import ( ) func (f *Machine) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) { - if mnemonic == asm.MOVE && a == b { + if f.Assembler.CanSkip(mnemonic, a, b) { return } diff --git a/src/register/Return.go b/src/register/Return.go index b9e09b7..032b74c 100644 --- a/src/register/Return.go +++ b/src/register/Return.go @@ -1,6 +1,13 @@ package register func (f *Machine) Return() { + if f.Assembler.CanSkipReturn() { + return + } + f.Assembler.Return() + scope := f.CurrentScope() + scope.Reserved = 0 + scope.Used = 0 f.postInstruction() } From be384c5136167a4ef7b1185fa048f6a471ef7422 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Feb 2025 15:00:19 +0100 Subject: [PATCH 0728/1012] Fixed incorrect number of history entries --- src/asm/{unnecessary.go => CanSkip.go} | 8 ++++++-- src/asm/CanSkipReturn.go | 16 ++++++++++++++++ src/asm/Instructions.go | 8 -------- src/asm/RegisterRegister.go | 4 ---- src/core/CompileReturn.go | 6 +++--- src/register/MemoryRegister.go | 5 +++++ src/register/RegisterNumber.go | 3 +-- src/register/RegisterRegister.go | 2 +- src/register/Return.go | 7 +++++++ 9 files changed, 39 insertions(+), 20 deletions(-) rename src/asm/{unnecessary.go => CanSkip.go} (67%) create mode 100644 src/asm/CanSkipReturn.go diff --git a/src/asm/unnecessary.go b/src/asm/CanSkip.go similarity index 67% rename from src/asm/unnecessary.go rename to src/asm/CanSkip.go index c3a915a..c707990 100644 --- a/src/asm/unnecessary.go +++ b/src/asm/CanSkip.go @@ -2,8 +2,12 @@ package asm import "git.akyoto.dev/cli/q/src/cpu" -// unnecessary returns true if the register/register operation can be skipped. -func (a *Assembler) unnecessary(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool { +// CanSkip returns true if the register/register operation can be skipped. +func (a *Assembler) CanSkip(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool { + if mnemonic == MOVE && left == right { + return true + } + if len(a.Instructions) == 0 { return false } diff --git a/src/asm/CanSkipReturn.go b/src/asm/CanSkipReturn.go new file mode 100644 index 0000000..0ce31cb --- /dev/null +++ b/src/asm/CanSkipReturn.go @@ -0,0 +1,16 @@ +package asm + +// CanSkipReturn returns true if the return operation can be skipped. +func (a *Assembler) CanSkipReturn() bool { + if len(a.Instructions) == 0 { + return false + } + + lastMnemonic := a.Instructions[len(a.Instructions)-1].Mnemonic + + if lastMnemonic == RETURN || lastMnemonic == JUMP { + return true + } + + return false +} diff --git a/src/asm/Instructions.go b/src/asm/Instructions.go index acfa639..ab6a510 100644 --- a/src/asm/Instructions.go +++ b/src/asm/Instructions.go @@ -32,14 +32,6 @@ func (a *Assembler) DLLCall(name string) { // Return returns back to the caller. func (a *Assembler) Return() { - if len(a.Instructions) > 0 { - lastMnemonic := a.Instructions[len(a.Instructions)-1].Mnemonic - - if lastMnemonic == RETURN || lastMnemonic == JUMP { - return - } - } - a.Instructions = append(a.Instructions, Instruction{Mnemonic: RETURN}) } diff --git a/src/asm/RegisterRegister.go b/src/asm/RegisterRegister.go index 8631fd6..c133e2c 100644 --- a/src/asm/RegisterRegister.go +++ b/src/asm/RegisterRegister.go @@ -19,10 +19,6 @@ func (data *RegisterRegister) String() string { // RegisterRegister adds an instruction using two registers. func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right cpu.Register) { - if a.unnecessary(mnemonic, left, right) { - return - } - a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, Data: &RegisterRegister{ diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 19d7963..87050e8 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -8,9 +8,8 @@ import ( // CompileReturn compiles a return instruction. func (f *Function) CompileReturn(node *ast.Return) error { - defer f.Return() - if len(node.Values) == 0 { + f.Return() return nil } @@ -27,12 +26,13 @@ func (f *Function) CompileReturn(node *ast.Return) error { if !types.Is(typ, f.Output[i].Type) { if f.Package == "mem" && f.Name == "alloc" { - return nil + continue } return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: f.Output[i].Type.Name(), ParameterName: "", IsReturn: true}, f.File, node.Values[i].Token.Position) } } + f.Return() return nil } diff --git a/src/register/MemoryRegister.go b/src/register/MemoryRegister.go index 1a3c770..fa13e9f 100644 --- a/src/register/MemoryRegister.go +++ b/src/register/MemoryRegister.go @@ -7,5 +7,10 @@ import ( func (f *Machine) MemoryRegister(mnemonic asm.Mnemonic, a asm.Memory, b cpu.Register) { f.Assembler.MemoryRegister(mnemonic, a, b) + + if mnemonic == asm.LOAD { + f.UseRegister(b) + } + f.postInstruction() } diff --git a/src/register/RegisterNumber.go b/src/register/RegisterNumber.go index 23aa23f..f3986f5 100644 --- a/src/register/RegisterNumber.go +++ b/src/register/RegisterNumber.go @@ -27,7 +27,6 @@ func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { f.Assembler.RegisterNumber(asm.MOVE, tmp, b) f.UseRegister(tmp) f.postInstruction() - f.Assembler.RegisterRegister(mnemonic, a, tmp) - f.postInstruction() + f.RegisterRegister(mnemonic, a, tmp) f.FreeRegister(tmp) } diff --git a/src/register/RegisterRegister.go b/src/register/RegisterRegister.go index 98ac5ea..59208ae 100644 --- a/src/register/RegisterRegister.go +++ b/src/register/RegisterRegister.go @@ -6,7 +6,7 @@ import ( ) func (f *Machine) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) { - if mnemonic == asm.MOVE && a == b { + if f.Assembler.CanSkip(mnemonic, a, b) { return } diff --git a/src/register/Return.go b/src/register/Return.go index b9e09b7..032b74c 100644 --- a/src/register/Return.go +++ b/src/register/Return.go @@ -1,6 +1,13 @@ package register func (f *Machine) Return() { + if f.Assembler.CanSkipReturn() { + return + } + f.Assembler.Return() + scope := f.CurrentScope() + scope.Reserved = 0 + scope.Used = 0 f.postInstruction() } From 2948a24478c1e045c2582e290a44be2a676955cd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Feb 2025 16:29:45 +0100 Subject: [PATCH 0729/1012] Removed unnecessary function --- src/core/BeforeCall.go | 32 ------------------------------ src/core/CallExtern.go | 2 +- src/core/CompileCall.go | 2 +- src/core/CompileReturn.go | 1 + src/core/CompileSyscall.go | 2 +- src/core/ExpressionToRegister.go | 2 -- src/core/ExpressionsToRegisters.go | 24 +++++++++++++++++++--- 7 files changed, 25 insertions(+), 40 deletions(-) delete mode 100644 src/core/BeforeCall.go diff --git a/src/core/BeforeCall.go b/src/core/BeforeCall.go deleted file mode 100644 index 57812f8..0000000 --- a/src/core/BeforeCall.go +++ /dev/null @@ -1,32 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" -) - -// BeforeCall loads the registers with the parameter values and checks that the types match with the function signature. -func (f *Function) BeforeCall(fn *Function, parameters []*expression.Expression, registers []cpu.Register) error { - for i := len(parameters) - 1; i >= 0; i-- { - typ, err := f.ExpressionToRegister(parameters[i], registers[i]) - - if err != nil { - return err - } - - if !types.Is(typ, fn.Input[i].Type) { - _, expectsPointer := fn.Input[i].Type.(*types.Pointer) - - if expectsPointer && parameters[i].Token.Kind == token.Number && parameters[i].Token.Text(f.File.Bytes) == "0" { - continue - } - - return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: fn.Input[i].Type.Name(), ParameterName: fn.Input[i].Name}, f.File, parameters[i].Token.Position) - } - } - - return nil -} diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go index abb1a7a..1b8fe8f 100644 --- a/src/core/CallExtern.go +++ b/src/core/CallExtern.go @@ -26,7 +26,7 @@ func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) } registers := x86.WindowsInputRegisters[:len(parameters)] - err := f.BeforeCall(fn, parameters, registers) + err := f.ExpressionsToRegisters(parameters, registers, fn.Input) if err != nil { return nil, err diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 2518ab5..86f284b 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -75,7 +75,7 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error } registers := f.CPU.Input[:len(parameters)] - err := f.BeforeCall(fn, parameters, registers) + err := f.ExpressionsToRegisters(parameters, registers, fn.Input) if err != nil { return nil, err diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 87050e8..752669f 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -18,6 +18,7 @@ func (f *Function) CompileReturn(node *ast.Return) error { } for i := len(node.Values) - 1; i >= 0; i-- { + f.SaveRegister(f.CPU.Output[i]) typ, err := f.ExpressionToRegister(node.Values[i], f.CPU.Output[i]) if err != nil { diff --git a/src/core/CompileSyscall.go b/src/core/CompileSyscall.go index cadce36..0b3b1d6 100644 --- a/src/core/CompileSyscall.go +++ b/src/core/CompileSyscall.go @@ -8,7 +8,7 @@ import ( func (f *Function) CompileSyscall(root *expression.Expression) error { parameters := root.Children[1:] registers := f.CPU.SyscallInput[:len(parameters)] - err := f.ExpressionsToRegisters(parameters, registers) + err := f.ExpressionsToRegisters(parameters, registers, nil) if err != nil { return err diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 4dfb2d7..d690f7f 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -12,8 +12,6 @@ import ( // ExpressionToRegister puts the result of an expression into the specified register. func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { - f.SaveRegister(register) - if node.IsFolded { f.RegisterNumber(asm.MOVE, register, node.Value) return types.Int, nil diff --git a/src/core/ExpressionsToRegisters.go b/src/core/ExpressionsToRegisters.go index 4f2dbed..8d1436a 100644 --- a/src/core/ExpressionsToRegisters.go +++ b/src/core/ExpressionsToRegisters.go @@ -2,17 +2,35 @@ package core import ( "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" ) -// ExpressionsToRegisters moves multiple expressions into the specified registers. -func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { +// ExpressionsToRegisters moves multiple expressions into the specified registers and checks that the types match with the function signature. +func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register, input []*Input) error { for i := len(expressions) - 1; i >= 0; i-- { - _, err := f.ExpressionToRegister(expressions[i], registers[i]) + f.SaveRegister(registers[i]) + typ, err := f.ExpressionToRegister(expressions[i], registers[i]) if err != nil { return err } + + if input == nil { + continue + } + + if !types.Is(typ, input[i].Type) { + _, expectsPointer := input[i].Type.(*types.Pointer) + + if expectsPointer && expressions[i].Token.Kind == token.Number && expressions[i].Token.Text(f.File.Bytes) == "0" { + continue + } + + return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: input[i].Type.Name(), ParameterName: input[i].Name}, f.File, expressions[i].Token.Position) + } } return nil From b32df452894f5105537fc4fa69c8ec307681b64e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Feb 2025 16:29:45 +0100 Subject: [PATCH 0730/1012] Removed unnecessary function --- src/core/BeforeCall.go | 32 ------------------------------ src/core/CallExtern.go | 2 +- src/core/CompileCall.go | 2 +- src/core/CompileReturn.go | 1 + src/core/CompileSyscall.go | 2 +- src/core/ExpressionToRegister.go | 2 -- src/core/ExpressionsToRegisters.go | 24 +++++++++++++++++++--- 7 files changed, 25 insertions(+), 40 deletions(-) delete mode 100644 src/core/BeforeCall.go diff --git a/src/core/BeforeCall.go b/src/core/BeforeCall.go deleted file mode 100644 index 57812f8..0000000 --- a/src/core/BeforeCall.go +++ /dev/null @@ -1,32 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" -) - -// BeforeCall loads the registers with the parameter values and checks that the types match with the function signature. -func (f *Function) BeforeCall(fn *Function, parameters []*expression.Expression, registers []cpu.Register) error { - for i := len(parameters) - 1; i >= 0; i-- { - typ, err := f.ExpressionToRegister(parameters[i], registers[i]) - - if err != nil { - return err - } - - if !types.Is(typ, fn.Input[i].Type) { - _, expectsPointer := fn.Input[i].Type.(*types.Pointer) - - if expectsPointer && parameters[i].Token.Kind == token.Number && parameters[i].Token.Text(f.File.Bytes) == "0" { - continue - } - - return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: fn.Input[i].Type.Name(), ParameterName: fn.Input[i].Name}, f.File, parameters[i].Token.Position) - } - } - - return nil -} diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go index abb1a7a..1b8fe8f 100644 --- a/src/core/CallExtern.go +++ b/src/core/CallExtern.go @@ -26,7 +26,7 @@ func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) } registers := x86.WindowsInputRegisters[:len(parameters)] - err := f.BeforeCall(fn, parameters, registers) + err := f.ExpressionsToRegisters(parameters, registers, fn.Input) if err != nil { return nil, err diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 2518ab5..86f284b 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -75,7 +75,7 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error } registers := f.CPU.Input[:len(parameters)] - err := f.BeforeCall(fn, parameters, registers) + err := f.ExpressionsToRegisters(parameters, registers, fn.Input) if err != nil { return nil, err diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 87050e8..752669f 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -18,6 +18,7 @@ func (f *Function) CompileReturn(node *ast.Return) error { } for i := len(node.Values) - 1; i >= 0; i-- { + f.SaveRegister(f.CPU.Output[i]) typ, err := f.ExpressionToRegister(node.Values[i], f.CPU.Output[i]) if err != nil { diff --git a/src/core/CompileSyscall.go b/src/core/CompileSyscall.go index cadce36..0b3b1d6 100644 --- a/src/core/CompileSyscall.go +++ b/src/core/CompileSyscall.go @@ -8,7 +8,7 @@ import ( func (f *Function) CompileSyscall(root *expression.Expression) error { parameters := root.Children[1:] registers := f.CPU.SyscallInput[:len(parameters)] - err := f.ExpressionsToRegisters(parameters, registers) + err := f.ExpressionsToRegisters(parameters, registers, nil) if err != nil { return err diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 4dfb2d7..d690f7f 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -12,8 +12,6 @@ import ( // ExpressionToRegister puts the result of an expression into the specified register. func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { - f.SaveRegister(register) - if node.IsFolded { f.RegisterNumber(asm.MOVE, register, node.Value) return types.Int, nil diff --git a/src/core/ExpressionsToRegisters.go b/src/core/ExpressionsToRegisters.go index 4f2dbed..8d1436a 100644 --- a/src/core/ExpressionsToRegisters.go +++ b/src/core/ExpressionsToRegisters.go @@ -2,17 +2,35 @@ package core import ( "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" ) -// ExpressionsToRegisters moves multiple expressions into the specified registers. -func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { +// ExpressionsToRegisters moves multiple expressions into the specified registers and checks that the types match with the function signature. +func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register, input []*Input) error { for i := len(expressions) - 1; i >= 0; i-- { - _, err := f.ExpressionToRegister(expressions[i], registers[i]) + f.SaveRegister(registers[i]) + typ, err := f.ExpressionToRegister(expressions[i], registers[i]) if err != nil { return err } + + if input == nil { + continue + } + + if !types.Is(typ, input[i].Type) { + _, expectsPointer := input[i].Type.(*types.Pointer) + + if expectsPointer && expressions[i].Token.Kind == token.Number && expressions[i].Token.Text(f.File.Bytes) == "0" { + continue + } + + return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: input[i].Type.Name(), ParameterName: input[i].Name}, f.File, expressions[i].Token.Position) + } } return nil From ef512441cf4685abf8a4cb8240d7f7ed24ce3dbc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Feb 2025 17:02:51 +0100 Subject: [PATCH 0731/1012] Reduced number of register moves --- src/core/CompileReturn.go | 8 +++++++- src/core/UsesRegister.go | 13 ++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 752669f..fb93354 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -18,7 +18,13 @@ func (f *Function) CompileReturn(node *ast.Return) error { } for i := len(node.Values) - 1; i >= 0; i-- { - f.SaveRegister(f.CPU.Output[i]) + for j := range i { + if f.UsesRegister(node.Values[j], f.CPU.Output[i]) { + f.SaveRegister(f.CPU.Output[i]) + break + } + } + typ, err := f.ExpressionToRegister(node.Values[i], f.CPU.Output[i]) if err != nil { diff --git a/src/core/UsesRegister.go b/src/core/UsesRegister.go index 0ad2d90..74519bd 100644 --- a/src/core/UsesRegister.go +++ b/src/core/UsesRegister.go @@ -4,12 +4,23 @@ import ( "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // UsesRegister returns true if evaluating the expression would write or read the given register. func (f *Function) UsesRegister(expr *expression.Expression, register cpu.Register) bool { if expr.IsLeaf() { - return false + if expr.Token.Kind != token.Identifier { + return false + } + + variable := f.VariableByName(expr.Token.Text(f.File.Bytes)) + + if variable == nil || variable.Register != register { + return false + } + + return true } if ast.IsFunctionCall(expr) { From 3fb05c382a386ee9ba98bc165732c62bb02167c9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Feb 2025 17:02:51 +0100 Subject: [PATCH 0732/1012] Reduced number of register moves --- src/core/CompileReturn.go | 8 +++++++- src/core/UsesRegister.go | 13 ++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 752669f..fb93354 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -18,7 +18,13 @@ func (f *Function) CompileReturn(node *ast.Return) error { } for i := len(node.Values) - 1; i >= 0; i-- { - f.SaveRegister(f.CPU.Output[i]) + for j := range i { + if f.UsesRegister(node.Values[j], f.CPU.Output[i]) { + f.SaveRegister(f.CPU.Output[i]) + break + } + } + typ, err := f.ExpressionToRegister(node.Values[i], f.CPU.Output[i]) if err != nil { diff --git a/src/core/UsesRegister.go b/src/core/UsesRegister.go index 0ad2d90..74519bd 100644 --- a/src/core/UsesRegister.go +++ b/src/core/UsesRegister.go @@ -4,12 +4,23 @@ import ( "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // UsesRegister returns true if evaluating the expression would write or read the given register. func (f *Function) UsesRegister(expr *expression.Expression, register cpu.Register) bool { if expr.IsLeaf() { - return false + if expr.Token.Kind != token.Identifier { + return false + } + + variable := f.VariableByName(expr.Token.Text(f.File.Bytes)) + + if variable == nil || variable.Register != register { + return false + } + + return true } if ast.IsFunctionCall(expr) { From bd71a29c04837d0fef18b66f0cf2a88d91c266aa Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Feb 2025 17:34:48 +0100 Subject: [PATCH 0733/1012] Simplified return code --- src/core/CallExtern.go | 2 +- src/core/CompileCall.go | 2 +- src/core/CompileReturn.go | 24 +++--------------------- src/core/CompileSyscall.go | 2 +- src/core/ExpressionsToRegisters.go | 26 ++++++++++++++++++++------ src/core/Function.go | 4 ++-- src/core/Input.go | 16 ---------------- src/core/Output.go | 15 --------------- src/core/Parameter.go | 24 ++++++++++++++++++++++++ src/core/PrintInstructions.go | 2 +- src/core/ResolveTypes.go | 22 +++++++++++----------- src/scanner/scanFunctionSignature.go | 4 ++-- 12 files changed, 66 insertions(+), 77 deletions(-) delete mode 100644 src/core/Input.go delete mode 100644 src/core/Output.go create mode 100644 src/core/Parameter.go diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go index 1b8fe8f..57d3118 100644 --- a/src/core/CallExtern.go +++ b/src/core/CallExtern.go @@ -26,7 +26,7 @@ func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) } registers := x86.WindowsInputRegisters[:len(parameters)] - err := f.ExpressionsToRegisters(parameters, registers, fn.Input) + err := f.ExpressionsToRegisters(parameters, registers, fn.Input, true) if err != nil { return nil, err diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 86f284b..4973ce2 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -75,7 +75,7 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error } registers := f.CPU.Input[:len(parameters)] - err := f.ExpressionsToRegisters(parameters, registers, fn.Input) + err := f.ExpressionsToRegisters(parameters, registers, fn.Input, true) if err != nil { return nil, err diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index fb93354..cb2ffe6 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -3,7 +3,6 @@ package core import ( "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/types" ) // CompileReturn compiles a return instruction. @@ -17,27 +16,10 @@ func (f *Function) CompileReturn(node *ast.Return) error { return errors.New(&errors.ReturnCountMismatch{Count: len(node.Values), ExpectedCount: len(f.Output)}, f.File, node.Values[0].Token.Position) } - for i := len(node.Values) - 1; i >= 0; i-- { - for j := range i { - if f.UsesRegister(node.Values[j], f.CPU.Output[i]) { - f.SaveRegister(f.CPU.Output[i]) - break - } - } + err := f.ExpressionsToRegisters(node.Values, f.CPU.Output, f.Output, false) - typ, err := f.ExpressionToRegister(node.Values[i], f.CPU.Output[i]) - - if err != nil { - return err - } - - if !types.Is(typ, f.Output[i].Type) { - if f.Package == "mem" && f.Name == "alloc" { - continue - } - - return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: f.Output[i].Type.Name(), ParameterName: "", IsReturn: true}, f.File, node.Values[i].Token.Position) - } + if err != nil { + return err } f.Return() diff --git a/src/core/CompileSyscall.go b/src/core/CompileSyscall.go index 0b3b1d6..a4edf16 100644 --- a/src/core/CompileSyscall.go +++ b/src/core/CompileSyscall.go @@ -8,7 +8,7 @@ import ( func (f *Function) CompileSyscall(root *expression.Expression) error { parameters := root.Children[1:] registers := f.CPU.SyscallInput[:len(parameters)] - err := f.ExpressionsToRegisters(parameters, registers, nil) + err := f.ExpressionsToRegisters(parameters, registers, nil, true) if err != nil { return err diff --git a/src/core/ExpressionsToRegisters.go b/src/core/ExpressionsToRegisters.go index 8d1436a..f6a21f3 100644 --- a/src/core/ExpressionsToRegisters.go +++ b/src/core/ExpressionsToRegisters.go @@ -9,27 +9,41 @@ import ( ) // ExpressionsToRegisters moves multiple expressions into the specified registers and checks that the types match with the function signature. -func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register, input []*Input) error { +func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register, params []*Parameter, alwaysSave bool) error { for i := len(expressions) - 1; i >= 0; i-- { - f.SaveRegister(registers[i]) + if alwaysSave { + f.SaveRegister(registers[i]) + } else { + for j := i - 1; j >= 0; j-- { + if f.UsesRegister(expressions[j], registers[i]) { + f.SaveRegister(registers[i]) + break + } + } + } + typ, err := f.ExpressionToRegister(expressions[i], registers[i]) if err != nil { return err } - if input == nil { + if params == nil { continue } - if !types.Is(typ, input[i].Type) { - _, expectsPointer := input[i].Type.(*types.Pointer) + if !types.Is(typ, params[i].Type()) { + _, expectsPointer := params[i].Type().(*types.Pointer) if expectsPointer && expressions[i].Token.Kind == token.Number && expressions[i].Token.Text(f.File.Bytes) == "0" { continue } - return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: input[i].Type.Name(), ParameterName: input[i].Name}, f.File, expressions[i].Token.Position) + if f.Package == "mem" && f.Name == "alloc" { + continue + } + + return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: params[i].Type().Name(), ParameterName: params[i].Name()}, f.File, expressions[i].Token.Position) } } diff --git a/src/core/Function.go b/src/core/Function.go index d0a6098..5565dea 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -16,8 +16,8 @@ type Function struct { UniqueName string File *fs.File Body token.List - Input []*Input - Output []*Output + Input []*Parameter + Output []*Parameter OutputTypes []types.Type Functions map[string]*Function Structs map[string]*types.Struct diff --git a/src/core/Input.go b/src/core/Input.go deleted file mode 100644 index 2b9aee2..0000000 --- a/src/core/Input.go +++ /dev/null @@ -1,16 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" -) - -type Input struct { - Name string - Type types.Type - tokens token.List -} - -func NewInput(tokens token.List) *Input { - return &Input{tokens: tokens} -} diff --git a/src/core/Output.go b/src/core/Output.go deleted file mode 100644 index 3b1b434..0000000 --- a/src/core/Output.go +++ /dev/null @@ -1,15 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" -) - -type Output struct { - Type types.Type - tokens token.List -} - -func NewOutput(tokens token.List) *Output { - return &Output{tokens: tokens} -} diff --git a/src/core/Parameter.go b/src/core/Parameter.go new file mode 100644 index 0000000..1375a09 --- /dev/null +++ b/src/core/Parameter.go @@ -0,0 +1,24 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" +) + +type Parameter struct { + name string + typ types.Type + tokens token.List +} + +func NewParameter(tokens token.List) *Parameter { + return &Parameter{tokens: tokens} +} + +func (p *Parameter) Name() string { + return p.name +} + +func (p *Parameter) Type() types.Type { + return p.typ +} diff --git a/src/core/PrintInstructions.go b/src/core/PrintInstructions.go index 3a1acf5..00fcf7c 100644 --- a/src/core/PrintInstructions.go +++ b/src/core/PrintInstructions.go @@ -14,7 +14,7 @@ func (f *Function) PrintInstructions() { if len(f.Input) > 0 { for i, input := range f.Input { - ansi.Dim.Printf("│ %-44s%-32s", input.Name, f.CPU.Input[i]) + ansi.Dim.Printf("│ %-44s%-32s", input.name, f.CPU.Input[i]) ansi.Dim.Print(" │\n") } diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index 79ce233..b885d15 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -11,11 +11,11 @@ import ( // ResolveTypes parses the input and output types. func (f *Function) ResolveTypes() error { for i, param := range f.Input { - param.Name = param.tokens[0].Text(f.File.Bytes) + param.name = param.tokens[0].Text(f.File.Bytes) typeName := param.tokens[1:].Text(f.File.Bytes) - param.Type = types.ByName(typeName, f.Package, f.Structs) + param.typ = types.ByName(typeName, f.Package, f.Structs) - if param.Type == nil { + if param.typ == nil { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) } @@ -23,15 +23,15 @@ func (f *Function) ResolveTypes() error { continue } - uses := token.Count(f.Body, f.File.Bytes, token.Identifier, param.Name) + uses := token.Count(f.Body, f.File.Bytes, token.Identifier, param.name) - if uses == 0 && param.Name != "_" { - return errors.New(&errors.UnusedVariable{Name: param.Name}, f.File, param.tokens[0].Position) + if uses == 0 && param.name != "_" { + return errors.New(&errors.UnusedVariable{Name: param.name}, f.File, param.tokens[0].Position) } f.AddVariable(&scope.Variable{ - Name: param.Name, - Type: param.Type, + Name: param.name, + Type: param.typ, Register: x86.InputRegisters[i], Alive: uses, }) @@ -39,13 +39,13 @@ func (f *Function) ResolveTypes() error { for _, param := range f.Output { typeName := param.tokens.Text(f.File.Bytes) - param.Type = types.ByName(typeName, f.Package, f.Structs) + param.typ = types.ByName(typeName, f.Package, f.Structs) - if param.Type == nil { + if param.typ == nil { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[0].Position) } - f.OutputTypes = append(f.OutputTypes, param.Type) + f.OutputTypes = append(f.OutputTypes, param.typ) } return nil diff --git a/src/scanner/scanFunctionSignature.go b/src/scanner/scanFunctionSignature.go index f060214..501e252 100644 --- a/src/scanner/scanFunctionSignature.go +++ b/src/scanner/scanFunctionSignature.go @@ -97,7 +97,7 @@ func scanFunctionSignature(file *fs.File, tokens token.List, i int, delimiter to outputTokens := tokens[typeStart:typeEnd] err := outputTokens.Split(func(tokens token.List) error { - function.Output = append(function.Output, core.NewOutput(tokens)) + function.Output = append(function.Output, core.NewParameter(tokens)) return nil }) @@ -117,7 +117,7 @@ func scanFunctionSignature(file *fs.File, tokens token.List, i int, delimiter to return errors.New(errors.MissingType, file, tokens[0].End()) } - function.Input = append(function.Input, core.NewInput(tokens)) + function.Input = append(function.Input, core.NewParameter(tokens)) return nil }) From b7b4dad1a51bcf3481c78497f33880f4d3bdd178 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Feb 2025 17:34:48 +0100 Subject: [PATCH 0734/1012] Simplified return code --- src/core/CallExtern.go | 2 +- src/core/CompileCall.go | 2 +- src/core/CompileReturn.go | 24 +++--------------------- src/core/CompileSyscall.go | 2 +- src/core/ExpressionsToRegisters.go | 26 ++++++++++++++++++++------ src/core/Function.go | 4 ++-- src/core/Input.go | 16 ---------------- src/core/Output.go | 15 --------------- src/core/Parameter.go | 24 ++++++++++++++++++++++++ src/core/PrintInstructions.go | 2 +- src/core/ResolveTypes.go | 22 +++++++++++----------- src/scanner/scanFunctionSignature.go | 4 ++-- 12 files changed, 66 insertions(+), 77 deletions(-) delete mode 100644 src/core/Input.go delete mode 100644 src/core/Output.go create mode 100644 src/core/Parameter.go diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go index 1b8fe8f..57d3118 100644 --- a/src/core/CallExtern.go +++ b/src/core/CallExtern.go @@ -26,7 +26,7 @@ func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) } registers := x86.WindowsInputRegisters[:len(parameters)] - err := f.ExpressionsToRegisters(parameters, registers, fn.Input) + err := f.ExpressionsToRegisters(parameters, registers, fn.Input, true) if err != nil { return nil, err diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 86f284b..4973ce2 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -75,7 +75,7 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error } registers := f.CPU.Input[:len(parameters)] - err := f.ExpressionsToRegisters(parameters, registers, fn.Input) + err := f.ExpressionsToRegisters(parameters, registers, fn.Input, true) if err != nil { return nil, err diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index fb93354..cb2ffe6 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -3,7 +3,6 @@ package core import ( "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/types" ) // CompileReturn compiles a return instruction. @@ -17,27 +16,10 @@ func (f *Function) CompileReturn(node *ast.Return) error { return errors.New(&errors.ReturnCountMismatch{Count: len(node.Values), ExpectedCount: len(f.Output)}, f.File, node.Values[0].Token.Position) } - for i := len(node.Values) - 1; i >= 0; i-- { - for j := range i { - if f.UsesRegister(node.Values[j], f.CPU.Output[i]) { - f.SaveRegister(f.CPU.Output[i]) - break - } - } + err := f.ExpressionsToRegisters(node.Values, f.CPU.Output, f.Output, false) - typ, err := f.ExpressionToRegister(node.Values[i], f.CPU.Output[i]) - - if err != nil { - return err - } - - if !types.Is(typ, f.Output[i].Type) { - if f.Package == "mem" && f.Name == "alloc" { - continue - } - - return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: f.Output[i].Type.Name(), ParameterName: "", IsReturn: true}, f.File, node.Values[i].Token.Position) - } + if err != nil { + return err } f.Return() diff --git a/src/core/CompileSyscall.go b/src/core/CompileSyscall.go index 0b3b1d6..a4edf16 100644 --- a/src/core/CompileSyscall.go +++ b/src/core/CompileSyscall.go @@ -8,7 +8,7 @@ import ( func (f *Function) CompileSyscall(root *expression.Expression) error { parameters := root.Children[1:] registers := f.CPU.SyscallInput[:len(parameters)] - err := f.ExpressionsToRegisters(parameters, registers, nil) + err := f.ExpressionsToRegisters(parameters, registers, nil, true) if err != nil { return err diff --git a/src/core/ExpressionsToRegisters.go b/src/core/ExpressionsToRegisters.go index 8d1436a..f6a21f3 100644 --- a/src/core/ExpressionsToRegisters.go +++ b/src/core/ExpressionsToRegisters.go @@ -9,27 +9,41 @@ import ( ) // ExpressionsToRegisters moves multiple expressions into the specified registers and checks that the types match with the function signature. -func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register, input []*Input) error { +func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register, params []*Parameter, alwaysSave bool) error { for i := len(expressions) - 1; i >= 0; i-- { - f.SaveRegister(registers[i]) + if alwaysSave { + f.SaveRegister(registers[i]) + } else { + for j := i - 1; j >= 0; j-- { + if f.UsesRegister(expressions[j], registers[i]) { + f.SaveRegister(registers[i]) + break + } + } + } + typ, err := f.ExpressionToRegister(expressions[i], registers[i]) if err != nil { return err } - if input == nil { + if params == nil { continue } - if !types.Is(typ, input[i].Type) { - _, expectsPointer := input[i].Type.(*types.Pointer) + if !types.Is(typ, params[i].Type()) { + _, expectsPointer := params[i].Type().(*types.Pointer) if expectsPointer && expressions[i].Token.Kind == token.Number && expressions[i].Token.Text(f.File.Bytes) == "0" { continue } - return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: input[i].Type.Name(), ParameterName: input[i].Name}, f.File, expressions[i].Token.Position) + if f.Package == "mem" && f.Name == "alloc" { + continue + } + + return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: params[i].Type().Name(), ParameterName: params[i].Name()}, f.File, expressions[i].Token.Position) } } diff --git a/src/core/Function.go b/src/core/Function.go index d0a6098..5565dea 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -16,8 +16,8 @@ type Function struct { UniqueName string File *fs.File Body token.List - Input []*Input - Output []*Output + Input []*Parameter + Output []*Parameter OutputTypes []types.Type Functions map[string]*Function Structs map[string]*types.Struct diff --git a/src/core/Input.go b/src/core/Input.go deleted file mode 100644 index 2b9aee2..0000000 --- a/src/core/Input.go +++ /dev/null @@ -1,16 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" -) - -type Input struct { - Name string - Type types.Type - tokens token.List -} - -func NewInput(tokens token.List) *Input { - return &Input{tokens: tokens} -} diff --git a/src/core/Output.go b/src/core/Output.go deleted file mode 100644 index 3b1b434..0000000 --- a/src/core/Output.go +++ /dev/null @@ -1,15 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" -) - -type Output struct { - Type types.Type - tokens token.List -} - -func NewOutput(tokens token.List) *Output { - return &Output{tokens: tokens} -} diff --git a/src/core/Parameter.go b/src/core/Parameter.go new file mode 100644 index 0000000..1375a09 --- /dev/null +++ b/src/core/Parameter.go @@ -0,0 +1,24 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" +) + +type Parameter struct { + name string + typ types.Type + tokens token.List +} + +func NewParameter(tokens token.List) *Parameter { + return &Parameter{tokens: tokens} +} + +func (p *Parameter) Name() string { + return p.name +} + +func (p *Parameter) Type() types.Type { + return p.typ +} diff --git a/src/core/PrintInstructions.go b/src/core/PrintInstructions.go index 3a1acf5..00fcf7c 100644 --- a/src/core/PrintInstructions.go +++ b/src/core/PrintInstructions.go @@ -14,7 +14,7 @@ func (f *Function) PrintInstructions() { if len(f.Input) > 0 { for i, input := range f.Input { - ansi.Dim.Printf("│ %-44s%-32s", input.Name, f.CPU.Input[i]) + ansi.Dim.Printf("│ %-44s%-32s", input.name, f.CPU.Input[i]) ansi.Dim.Print(" │\n") } diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index 79ce233..b885d15 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -11,11 +11,11 @@ import ( // ResolveTypes parses the input and output types. func (f *Function) ResolveTypes() error { for i, param := range f.Input { - param.Name = param.tokens[0].Text(f.File.Bytes) + param.name = param.tokens[0].Text(f.File.Bytes) typeName := param.tokens[1:].Text(f.File.Bytes) - param.Type = types.ByName(typeName, f.Package, f.Structs) + param.typ = types.ByName(typeName, f.Package, f.Structs) - if param.Type == nil { + if param.typ == nil { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) } @@ -23,15 +23,15 @@ func (f *Function) ResolveTypes() error { continue } - uses := token.Count(f.Body, f.File.Bytes, token.Identifier, param.Name) + uses := token.Count(f.Body, f.File.Bytes, token.Identifier, param.name) - if uses == 0 && param.Name != "_" { - return errors.New(&errors.UnusedVariable{Name: param.Name}, f.File, param.tokens[0].Position) + if uses == 0 && param.name != "_" { + return errors.New(&errors.UnusedVariable{Name: param.name}, f.File, param.tokens[0].Position) } f.AddVariable(&scope.Variable{ - Name: param.Name, - Type: param.Type, + Name: param.name, + Type: param.typ, Register: x86.InputRegisters[i], Alive: uses, }) @@ -39,13 +39,13 @@ func (f *Function) ResolveTypes() error { for _, param := range f.Output { typeName := param.tokens.Text(f.File.Bytes) - param.Type = types.ByName(typeName, f.Package, f.Structs) + param.typ = types.ByName(typeName, f.Package, f.Structs) - if param.Type == nil { + if param.typ == nil { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[0].Position) } - f.OutputTypes = append(f.OutputTypes, param.Type) + f.OutputTypes = append(f.OutputTypes, param.typ) } return nil diff --git a/src/scanner/scanFunctionSignature.go b/src/scanner/scanFunctionSignature.go index f060214..501e252 100644 --- a/src/scanner/scanFunctionSignature.go +++ b/src/scanner/scanFunctionSignature.go @@ -97,7 +97,7 @@ func scanFunctionSignature(file *fs.File, tokens token.List, i int, delimiter to outputTokens := tokens[typeStart:typeEnd] err := outputTokens.Split(func(tokens token.List) error { - function.Output = append(function.Output, core.NewOutput(tokens)) + function.Output = append(function.Output, core.NewParameter(tokens)) return nil }) @@ -117,7 +117,7 @@ func scanFunctionSignature(file *fs.File, tokens token.List, i int, delimiter to return errors.New(errors.MissingType, file, tokens[0].End()) } - function.Input = append(function.Input, core.NewInput(tokens)) + function.Input = append(function.Input, core.NewParameter(tokens)) return nil }) From 693ef7e9c338cb1915b62d48b994fb5f95dcffb2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Feb 2025 19:05:40 +0100 Subject: [PATCH 0735/1012] Simplified compiler package --- src/compiler/Compile.go | 24 +-- src/compiler/CompileFunctions.go | 27 +++ src/compiler/PrintInstructions.go | 10 ++ src/compiler/PrintStatistics.go | 29 ++++ src/compiler/Result.go | 179 +------------------- src/compiler/{Linux.go => SyscallsLinux.go} | 0 src/compiler/{Mac.go => SyscallsMac.go} | 0 src/compiler/Write.go | 33 ++++ src/compiler/WriteFile.go | 27 +++ src/compiler/eachFunction.go | 32 ++++ src/compiler/finalize.go | 74 ++++++++ 11 files changed, 234 insertions(+), 201 deletions(-) create mode 100644 src/compiler/CompileFunctions.go create mode 100644 src/compiler/PrintInstructions.go create mode 100644 src/compiler/PrintStatistics.go rename src/compiler/{Linux.go => SyscallsLinux.go} (100%) rename src/compiler/{Mac.go => SyscallsMac.go} (100%) create mode 100644 src/compiler/Write.go create mode 100644 src/compiler/WriteFile.go create mode 100644 src/compiler/eachFunction.go create mode 100644 src/compiler/finalize.go diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index df5f2dc..9a7f4e6 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -1,8 +1,6 @@ package compiler import ( - "sync" - "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/fs" @@ -16,7 +14,7 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c allFunctions := map[string]*core.Function{} allStructs := map[string]*types.Struct{} - for functions != nil || files != nil || errs != nil { + for functions != nil || structs != nil || files != nil || errs != nil { select { case function, ok := <-functions: if !ok { @@ -102,23 +100,3 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c result.finalize() return result, nil } - -// CompileFunctions starts a goroutine for each function compilation and waits for completion. -func CompileFunctions(functions map[string]*core.Function) { - wg := sync.WaitGroup{} - - for _, function := range functions { - if function.IsExtern() { - continue - } - - wg.Add(1) - - go func() { - defer wg.Done() - function.Compile() - }() - } - - wg.Wait() -} diff --git a/src/compiler/CompileFunctions.go b/src/compiler/CompileFunctions.go new file mode 100644 index 0000000..d4ee25b --- /dev/null +++ b/src/compiler/CompileFunctions.go @@ -0,0 +1,27 @@ +package compiler + +import ( + "sync" + + "git.akyoto.dev/cli/q/src/core" +) + +// CompileFunctions starts a goroutine for each function compilation and waits for completion. +func CompileFunctions(functions map[string]*core.Function) { + wg := sync.WaitGroup{} + + for _, function := range functions { + if function.IsExtern() { + continue + } + + wg.Add(1) + + go func() { + defer wg.Done() + function.Compile() + }() + } + + wg.Wait() +} diff --git a/src/compiler/PrintInstructions.go b/src/compiler/PrintInstructions.go new file mode 100644 index 0000000..686aa63 --- /dev/null +++ b/src/compiler/PrintInstructions.go @@ -0,0 +1,10 @@ +package compiler + +import "git.akyoto.dev/cli/q/src/core" + +// PrintInstructions prints out the generated instructions. +func (r *Result) PrintInstructions() { + r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { + f.PrintInstructions() + }) +} diff --git a/src/compiler/PrintStatistics.go b/src/compiler/PrintStatistics.go new file mode 100644 index 0000000..65b9066 --- /dev/null +++ b/src/compiler/PrintStatistics.go @@ -0,0 +1,29 @@ +package compiler + +import ( + "fmt" + + "git.akyoto.dev/go/color/ansi" +) + +// PrintStatistics shows the statistics. +func (r *Result) PrintStatistics() { + ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮") + + ansi.Dim.Print("│ ") + ansi.Dim.Printf("%-44s", "Code:") + fmt.Printf("%-32s", fmt.Sprintf("%d bytes", len(r.Code))) + ansi.Dim.Print(" │\n") + + ansi.Dim.Print("│ ") + ansi.Dim.Printf("%-44s", "Data:") + fmt.Printf("%-32s", fmt.Sprintf("%d bytes", len(r.Data))) + ansi.Dim.Print(" │\n") + + ansi.Dim.Print("│ ") + ansi.Dim.Printf("%-44s", "Functions:") + fmt.Printf("%-32s", fmt.Sprintf("%d / %d", len(r.Traversed), len(r.Functions))) + ansi.Dim.Print(" │\n") + + ansi.Dim.Println("╰──────────────────────────────────────────────────────────────────────────────╯") +} diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 9ca52e4..a0d6091 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -1,24 +1,11 @@ package compiler import ( - "bufio" - "fmt" - "io" - "os" - - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/asmc" - "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/elf" - "git.akyoto.dev/cli/q/src/macho" - "git.akyoto.dev/cli/q/src/pe" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/color/ansi" ) -// Result contains all the compiled functions in a build. +// Result contains everything we need to write an executable file to disk. type Result struct { Main *core.Function Functions map[string]*core.Function @@ -29,167 +16,3 @@ type Result struct { Data []byte DLLs dll.List } - -// finalize generates the final machine code. -func (r *Result) finalize() { - // This will be the entry point of the executable. - // The only job of the entry function is to call `main` and exit cleanly. - // The reason we call `main` instead of using `main` itself is to place - // a return address on the stack, which allows return statements in `main`. - final := asm.Assembler{ - Instructions: make([]asm.Instruction, 0, r.InstructionCount+8), - Data: make(map[string][]byte, r.DataCount), - } - - final.Call("main.main") - - switch config.TargetOS { - case config.Linux: - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], LinuxExit) - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 0) - final.Syscall() - case config.Mac: - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], MacExit) - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 0) - final.Syscall() - case config.Windows: - final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 0) - final.DLLCall("kernel32.ExitProcess") - } - - r.DLLs = dll.List{ - {Name: "kernel32", Functions: []string{"ExitProcess"}}, - } - - r.Traversed = make(map[*core.Function]bool, len(r.Functions)) - - // This will place the main function immediately after the entry point - // and also add everything the main function calls recursively. - r.eachFunction(r.Main, r.Traversed, func(f *core.Function) { - final.Merge(f.Assembler) - - for _, library := range f.DLLs { - for _, fn := range library.Functions { - r.DLLs = r.DLLs.Append(library.Name, fn) - } - } - }) - - final.Label(asm.LABEL, "_crash") - - switch config.TargetOS { - case config.Linux: - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], LinuxExit) - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 1) - final.Syscall() - case config.Mac: - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], MacExit) - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 1) - final.Syscall() - case config.Windows: - final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 1) - final.DLLCall("kernel32.ExitProcess") - } - - r.Code, r.Data = asmc.Finalize(final, r.DLLs) -} - -// eachFunction recursively finds all the calls to external functions. -// It avoids calling the same function twice with the help of a hashmap. -func (r *Result) eachFunction(caller *core.Function, traversed map[*core.Function]bool, call func(*core.Function)) { - call(caller) - traversed[caller] = true - - for _, x := range caller.Assembler.Instructions { - if x.Mnemonic != asm.CALL { - continue - } - - name := x.Data.(*asm.Label).Name - callee, exists := r.Functions[name] - - if !exists { - continue - } - - if traversed[callee] { - continue - } - - r.eachFunction(callee, traversed, call) - } -} - -// PrintInstructions prints out the generated instructions. -func (r *Result) PrintInstructions() { - r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { - f.PrintInstructions() - }) -} - -// PrintStatistics shows the statistics. -func (r *Result) PrintStatistics() { - ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮") - - ansi.Dim.Print("│ ") - ansi.Dim.Printf("%-44s", "Code:") - fmt.Printf("%-32s", fmt.Sprintf("%d bytes", len(r.Code))) - ansi.Dim.Print(" │\n") - - ansi.Dim.Print("│ ") - ansi.Dim.Printf("%-44s", "Data:") - fmt.Printf("%-32s", fmt.Sprintf("%d bytes", len(r.Data))) - ansi.Dim.Print(" │\n") - - ansi.Dim.Print("│ ") - ansi.Dim.Printf("%-44s", "Functions:") - fmt.Printf("%-32s", fmt.Sprintf("%d / %d", len(r.Traversed), len(r.Functions))) - ansi.Dim.Print(" │\n") - - ansi.Dim.Println("╰──────────────────────────────────────────────────────────────────────────────╯") -} - -// Write writes the executable to the given writer. -func (r *Result) Write(writer io.Writer) error { - return write(writer, r.Code, r.Data, r.DLLs) -} - -// Write writes an executable file to disk. -func (r *Result) WriteFile(path string) error { - file, err := os.Create(path) - - if err != nil { - return err - } - - err = r.Write(file) - - if err != nil { - file.Close() - return err - } - - err = file.Close() - - if err != nil { - return err - } - - return os.Chmod(path, 0755) -} - -// write writes an executable file to the given writer. -func write(writer io.Writer, code []byte, data []byte, dlls dll.List) error { - buffer := bufio.NewWriter(writer) - - switch config.TargetOS { - case config.Linux: - elf.Write(buffer, code, data) - case config.Mac: - macho.Write(buffer, code, data) - case config.Windows: - pe.Write(buffer, code, data, dlls) - } - - return buffer.Flush() -} diff --git a/src/compiler/Linux.go b/src/compiler/SyscallsLinux.go similarity index 100% rename from src/compiler/Linux.go rename to src/compiler/SyscallsLinux.go diff --git a/src/compiler/Mac.go b/src/compiler/SyscallsMac.go similarity index 100% rename from src/compiler/Mac.go rename to src/compiler/SyscallsMac.go diff --git a/src/compiler/Write.go b/src/compiler/Write.go new file mode 100644 index 0000000..70c1e96 --- /dev/null +++ b/src/compiler/Write.go @@ -0,0 +1,33 @@ +package compiler + +import ( + "bufio" + "io" + + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/dll" + "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/macho" + "git.akyoto.dev/cli/q/src/pe" +) + +// Write writes the executable to the given writer. +func (r *Result) Write(writer io.Writer) error { + return write(writer, r.Code, r.Data, r.DLLs) +} + +// write writes an executable file to the given writer. +func write(writer io.Writer, code []byte, data []byte, dlls dll.List) error { + buffer := bufio.NewWriter(writer) + + switch config.TargetOS { + case config.Linux: + elf.Write(buffer, code, data) + case config.Mac: + macho.Write(buffer, code, data) + case config.Windows: + pe.Write(buffer, code, data, dlls) + } + + return buffer.Flush() +} diff --git a/src/compiler/WriteFile.go b/src/compiler/WriteFile.go new file mode 100644 index 0000000..009562e --- /dev/null +++ b/src/compiler/WriteFile.go @@ -0,0 +1,27 @@ +package compiler + +import "os" + +// Write writes an executable file to disk. +func (r *Result) WriteFile(path string) error { + file, err := os.Create(path) + + if err != nil { + return err + } + + err = r.Write(file) + + if err != nil { + file.Close() + return err + } + + err = file.Close() + + if err != nil { + return err + } + + return os.Chmod(path, 0755) +} diff --git a/src/compiler/eachFunction.go b/src/compiler/eachFunction.go new file mode 100644 index 0000000..03973f4 --- /dev/null +++ b/src/compiler/eachFunction.go @@ -0,0 +1,32 @@ +package compiler + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/core" +) + +// eachFunction recursively finds all the calls to external functions. +// It avoids calling the same function twice with the help of a hashmap. +func (r *Result) eachFunction(caller *core.Function, traversed map[*core.Function]bool, call func(*core.Function)) { + call(caller) + traversed[caller] = true + + for _, x := range caller.Assembler.Instructions { + if x.Mnemonic != asm.CALL { + continue + } + + name := x.Data.(*asm.Label).Name + callee, exists := r.Functions[name] + + if !exists { + continue + } + + if traversed[callee] { + continue + } + + r.eachFunction(callee, traversed, call) + } +} diff --git a/src/compiler/finalize.go b/src/compiler/finalize.go new file mode 100644 index 0000000..343347e --- /dev/null +++ b/src/compiler/finalize.go @@ -0,0 +1,74 @@ +package compiler + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/asmc" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/dll" + "git.akyoto.dev/cli/q/src/x86" +) + +// finalize generates the final machine code. +func (r *Result) finalize() { + // This will be the entry point of the executable. + // The only job of the entry function is to call `main` and exit cleanly. + // The reason we call `main` instead of using `main` itself is to place + // a return address on the stack, which allows return statements in `main`. + final := asm.Assembler{ + Instructions: make([]asm.Instruction, 0, r.InstructionCount+8), + Data: make(map[string][]byte, r.DataCount), + } + + final.Call("main.main") + + switch config.TargetOS { + case config.Linux: + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], LinuxExit) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 0) + final.Syscall() + case config.Mac: + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], MacExit) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 0) + final.Syscall() + case config.Windows: + final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 0) + final.DLLCall("kernel32.ExitProcess") + } + + r.DLLs = dll.List{ + {Name: "kernel32", Functions: []string{"ExitProcess"}}, + } + + r.Traversed = make(map[*core.Function]bool, len(r.Functions)) + + // This will place the main function immediately after the entry point + // and also add everything the main function calls recursively. + r.eachFunction(r.Main, r.Traversed, func(f *core.Function) { + final.Merge(f.Assembler) + + for _, library := range f.DLLs { + for _, fn := range library.Functions { + r.DLLs = r.DLLs.Append(library.Name, fn) + } + } + }) + + final.Label(asm.LABEL, "_crash") + + switch config.TargetOS { + case config.Linux: + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], LinuxExit) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 1) + final.Syscall() + case config.Mac: + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], MacExit) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 1) + final.Syscall() + case config.Windows: + final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 1) + final.DLLCall("kernel32.ExitProcess") + } + + r.Code, r.Data = asmc.Finalize(final, r.DLLs) +} From 0dffb7936411e05198c558f7e3463cd8a74775a2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Feb 2025 19:05:40 +0100 Subject: [PATCH 0736/1012] Simplified compiler package --- src/compiler/Compile.go | 24 +-- src/compiler/CompileFunctions.go | 27 +++ src/compiler/PrintInstructions.go | 10 ++ src/compiler/PrintStatistics.go | 29 ++++ src/compiler/Result.go | 179 +------------------- src/compiler/{Linux.go => SyscallsLinux.go} | 0 src/compiler/{Mac.go => SyscallsMac.go} | 0 src/compiler/Write.go | 33 ++++ src/compiler/WriteFile.go | 27 +++ src/compiler/eachFunction.go | 32 ++++ src/compiler/finalize.go | 74 ++++++++ 11 files changed, 234 insertions(+), 201 deletions(-) create mode 100644 src/compiler/CompileFunctions.go create mode 100644 src/compiler/PrintInstructions.go create mode 100644 src/compiler/PrintStatistics.go rename src/compiler/{Linux.go => SyscallsLinux.go} (100%) rename src/compiler/{Mac.go => SyscallsMac.go} (100%) create mode 100644 src/compiler/Write.go create mode 100644 src/compiler/WriteFile.go create mode 100644 src/compiler/eachFunction.go create mode 100644 src/compiler/finalize.go diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index df5f2dc..9a7f4e6 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -1,8 +1,6 @@ package compiler import ( - "sync" - "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/fs" @@ -16,7 +14,7 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c allFunctions := map[string]*core.Function{} allStructs := map[string]*types.Struct{} - for functions != nil || files != nil || errs != nil { + for functions != nil || structs != nil || files != nil || errs != nil { select { case function, ok := <-functions: if !ok { @@ -102,23 +100,3 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c result.finalize() return result, nil } - -// CompileFunctions starts a goroutine for each function compilation and waits for completion. -func CompileFunctions(functions map[string]*core.Function) { - wg := sync.WaitGroup{} - - for _, function := range functions { - if function.IsExtern() { - continue - } - - wg.Add(1) - - go func() { - defer wg.Done() - function.Compile() - }() - } - - wg.Wait() -} diff --git a/src/compiler/CompileFunctions.go b/src/compiler/CompileFunctions.go new file mode 100644 index 0000000..d4ee25b --- /dev/null +++ b/src/compiler/CompileFunctions.go @@ -0,0 +1,27 @@ +package compiler + +import ( + "sync" + + "git.akyoto.dev/cli/q/src/core" +) + +// CompileFunctions starts a goroutine for each function compilation and waits for completion. +func CompileFunctions(functions map[string]*core.Function) { + wg := sync.WaitGroup{} + + for _, function := range functions { + if function.IsExtern() { + continue + } + + wg.Add(1) + + go func() { + defer wg.Done() + function.Compile() + }() + } + + wg.Wait() +} diff --git a/src/compiler/PrintInstructions.go b/src/compiler/PrintInstructions.go new file mode 100644 index 0000000..686aa63 --- /dev/null +++ b/src/compiler/PrintInstructions.go @@ -0,0 +1,10 @@ +package compiler + +import "git.akyoto.dev/cli/q/src/core" + +// PrintInstructions prints out the generated instructions. +func (r *Result) PrintInstructions() { + r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { + f.PrintInstructions() + }) +} diff --git a/src/compiler/PrintStatistics.go b/src/compiler/PrintStatistics.go new file mode 100644 index 0000000..65b9066 --- /dev/null +++ b/src/compiler/PrintStatistics.go @@ -0,0 +1,29 @@ +package compiler + +import ( + "fmt" + + "git.akyoto.dev/go/color/ansi" +) + +// PrintStatistics shows the statistics. +func (r *Result) PrintStatistics() { + ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮") + + ansi.Dim.Print("│ ") + ansi.Dim.Printf("%-44s", "Code:") + fmt.Printf("%-32s", fmt.Sprintf("%d bytes", len(r.Code))) + ansi.Dim.Print(" │\n") + + ansi.Dim.Print("│ ") + ansi.Dim.Printf("%-44s", "Data:") + fmt.Printf("%-32s", fmt.Sprintf("%d bytes", len(r.Data))) + ansi.Dim.Print(" │\n") + + ansi.Dim.Print("│ ") + ansi.Dim.Printf("%-44s", "Functions:") + fmt.Printf("%-32s", fmt.Sprintf("%d / %d", len(r.Traversed), len(r.Functions))) + ansi.Dim.Print(" │\n") + + ansi.Dim.Println("╰──────────────────────────────────────────────────────────────────────────────╯") +} diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 9ca52e4..a0d6091 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -1,24 +1,11 @@ package compiler import ( - "bufio" - "fmt" - "io" - "os" - - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/asmc" - "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/elf" - "git.akyoto.dev/cli/q/src/macho" - "git.akyoto.dev/cli/q/src/pe" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/color/ansi" ) -// Result contains all the compiled functions in a build. +// Result contains everything we need to write an executable file to disk. type Result struct { Main *core.Function Functions map[string]*core.Function @@ -29,167 +16,3 @@ type Result struct { Data []byte DLLs dll.List } - -// finalize generates the final machine code. -func (r *Result) finalize() { - // This will be the entry point of the executable. - // The only job of the entry function is to call `main` and exit cleanly. - // The reason we call `main` instead of using `main` itself is to place - // a return address on the stack, which allows return statements in `main`. - final := asm.Assembler{ - Instructions: make([]asm.Instruction, 0, r.InstructionCount+8), - Data: make(map[string][]byte, r.DataCount), - } - - final.Call("main.main") - - switch config.TargetOS { - case config.Linux: - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], LinuxExit) - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 0) - final.Syscall() - case config.Mac: - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], MacExit) - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 0) - final.Syscall() - case config.Windows: - final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 0) - final.DLLCall("kernel32.ExitProcess") - } - - r.DLLs = dll.List{ - {Name: "kernel32", Functions: []string{"ExitProcess"}}, - } - - r.Traversed = make(map[*core.Function]bool, len(r.Functions)) - - // This will place the main function immediately after the entry point - // and also add everything the main function calls recursively. - r.eachFunction(r.Main, r.Traversed, func(f *core.Function) { - final.Merge(f.Assembler) - - for _, library := range f.DLLs { - for _, fn := range library.Functions { - r.DLLs = r.DLLs.Append(library.Name, fn) - } - } - }) - - final.Label(asm.LABEL, "_crash") - - switch config.TargetOS { - case config.Linux: - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], LinuxExit) - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 1) - final.Syscall() - case config.Mac: - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], MacExit) - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 1) - final.Syscall() - case config.Windows: - final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 1) - final.DLLCall("kernel32.ExitProcess") - } - - r.Code, r.Data = asmc.Finalize(final, r.DLLs) -} - -// eachFunction recursively finds all the calls to external functions. -// It avoids calling the same function twice with the help of a hashmap. -func (r *Result) eachFunction(caller *core.Function, traversed map[*core.Function]bool, call func(*core.Function)) { - call(caller) - traversed[caller] = true - - for _, x := range caller.Assembler.Instructions { - if x.Mnemonic != asm.CALL { - continue - } - - name := x.Data.(*asm.Label).Name - callee, exists := r.Functions[name] - - if !exists { - continue - } - - if traversed[callee] { - continue - } - - r.eachFunction(callee, traversed, call) - } -} - -// PrintInstructions prints out the generated instructions. -func (r *Result) PrintInstructions() { - r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { - f.PrintInstructions() - }) -} - -// PrintStatistics shows the statistics. -func (r *Result) PrintStatistics() { - ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮") - - ansi.Dim.Print("│ ") - ansi.Dim.Printf("%-44s", "Code:") - fmt.Printf("%-32s", fmt.Sprintf("%d bytes", len(r.Code))) - ansi.Dim.Print(" │\n") - - ansi.Dim.Print("│ ") - ansi.Dim.Printf("%-44s", "Data:") - fmt.Printf("%-32s", fmt.Sprintf("%d bytes", len(r.Data))) - ansi.Dim.Print(" │\n") - - ansi.Dim.Print("│ ") - ansi.Dim.Printf("%-44s", "Functions:") - fmt.Printf("%-32s", fmt.Sprintf("%d / %d", len(r.Traversed), len(r.Functions))) - ansi.Dim.Print(" │\n") - - ansi.Dim.Println("╰──────────────────────────────────────────────────────────────────────────────╯") -} - -// Write writes the executable to the given writer. -func (r *Result) Write(writer io.Writer) error { - return write(writer, r.Code, r.Data, r.DLLs) -} - -// Write writes an executable file to disk. -func (r *Result) WriteFile(path string) error { - file, err := os.Create(path) - - if err != nil { - return err - } - - err = r.Write(file) - - if err != nil { - file.Close() - return err - } - - err = file.Close() - - if err != nil { - return err - } - - return os.Chmod(path, 0755) -} - -// write writes an executable file to the given writer. -func write(writer io.Writer, code []byte, data []byte, dlls dll.List) error { - buffer := bufio.NewWriter(writer) - - switch config.TargetOS { - case config.Linux: - elf.Write(buffer, code, data) - case config.Mac: - macho.Write(buffer, code, data) - case config.Windows: - pe.Write(buffer, code, data, dlls) - } - - return buffer.Flush() -} diff --git a/src/compiler/Linux.go b/src/compiler/SyscallsLinux.go similarity index 100% rename from src/compiler/Linux.go rename to src/compiler/SyscallsLinux.go diff --git a/src/compiler/Mac.go b/src/compiler/SyscallsMac.go similarity index 100% rename from src/compiler/Mac.go rename to src/compiler/SyscallsMac.go diff --git a/src/compiler/Write.go b/src/compiler/Write.go new file mode 100644 index 0000000..70c1e96 --- /dev/null +++ b/src/compiler/Write.go @@ -0,0 +1,33 @@ +package compiler + +import ( + "bufio" + "io" + + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/dll" + "git.akyoto.dev/cli/q/src/elf" + "git.akyoto.dev/cli/q/src/macho" + "git.akyoto.dev/cli/q/src/pe" +) + +// Write writes the executable to the given writer. +func (r *Result) Write(writer io.Writer) error { + return write(writer, r.Code, r.Data, r.DLLs) +} + +// write writes an executable file to the given writer. +func write(writer io.Writer, code []byte, data []byte, dlls dll.List) error { + buffer := bufio.NewWriter(writer) + + switch config.TargetOS { + case config.Linux: + elf.Write(buffer, code, data) + case config.Mac: + macho.Write(buffer, code, data) + case config.Windows: + pe.Write(buffer, code, data, dlls) + } + + return buffer.Flush() +} diff --git a/src/compiler/WriteFile.go b/src/compiler/WriteFile.go new file mode 100644 index 0000000..009562e --- /dev/null +++ b/src/compiler/WriteFile.go @@ -0,0 +1,27 @@ +package compiler + +import "os" + +// Write writes an executable file to disk. +func (r *Result) WriteFile(path string) error { + file, err := os.Create(path) + + if err != nil { + return err + } + + err = r.Write(file) + + if err != nil { + file.Close() + return err + } + + err = file.Close() + + if err != nil { + return err + } + + return os.Chmod(path, 0755) +} diff --git a/src/compiler/eachFunction.go b/src/compiler/eachFunction.go new file mode 100644 index 0000000..03973f4 --- /dev/null +++ b/src/compiler/eachFunction.go @@ -0,0 +1,32 @@ +package compiler + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/core" +) + +// eachFunction recursively finds all the calls to external functions. +// It avoids calling the same function twice with the help of a hashmap. +func (r *Result) eachFunction(caller *core.Function, traversed map[*core.Function]bool, call func(*core.Function)) { + call(caller) + traversed[caller] = true + + for _, x := range caller.Assembler.Instructions { + if x.Mnemonic != asm.CALL { + continue + } + + name := x.Data.(*asm.Label).Name + callee, exists := r.Functions[name] + + if !exists { + continue + } + + if traversed[callee] { + continue + } + + r.eachFunction(callee, traversed, call) + } +} diff --git a/src/compiler/finalize.go b/src/compiler/finalize.go new file mode 100644 index 0000000..343347e --- /dev/null +++ b/src/compiler/finalize.go @@ -0,0 +1,74 @@ +package compiler + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/asmc" + "git.akyoto.dev/cli/q/src/config" + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/dll" + "git.akyoto.dev/cli/q/src/x86" +) + +// finalize generates the final machine code. +func (r *Result) finalize() { + // This will be the entry point of the executable. + // The only job of the entry function is to call `main` and exit cleanly. + // The reason we call `main` instead of using `main` itself is to place + // a return address on the stack, which allows return statements in `main`. + final := asm.Assembler{ + Instructions: make([]asm.Instruction, 0, r.InstructionCount+8), + Data: make(map[string][]byte, r.DataCount), + } + + final.Call("main.main") + + switch config.TargetOS { + case config.Linux: + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], LinuxExit) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 0) + final.Syscall() + case config.Mac: + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], MacExit) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 0) + final.Syscall() + case config.Windows: + final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 0) + final.DLLCall("kernel32.ExitProcess") + } + + r.DLLs = dll.List{ + {Name: "kernel32", Functions: []string{"ExitProcess"}}, + } + + r.Traversed = make(map[*core.Function]bool, len(r.Functions)) + + // This will place the main function immediately after the entry point + // and also add everything the main function calls recursively. + r.eachFunction(r.Main, r.Traversed, func(f *core.Function) { + final.Merge(f.Assembler) + + for _, library := range f.DLLs { + for _, fn := range library.Functions { + r.DLLs = r.DLLs.Append(library.Name, fn) + } + } + }) + + final.Label(asm.LABEL, "_crash") + + switch config.TargetOS { + case config.Linux: + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], LinuxExit) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 1) + final.Syscall() + case config.Mac: + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], MacExit) + final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 1) + final.Syscall() + case config.Windows: + final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 1) + final.DLLCall("kernel32.ExitProcess") + } + + r.Code, r.Data = asmc.Finalize(final, r.DLLs) +} From 28292a64e36cbc664be6f0ab1448f83100155e0a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 13 Feb 2025 16:58:59 +0100 Subject: [PATCH 0737/1012] Removed unnecessary parameter --- src/core/NewFunction.go | 4 +--- src/scanner/scanFunctionSignature.go | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/core/NewFunction.go b/src/core/NewFunction.go index e2d93d2..3da5ed5 100644 --- a/src/core/NewFunction.go +++ b/src/core/NewFunction.go @@ -6,18 +6,16 @@ import ( "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/register" "git.akyoto.dev/cli/q/src/scope" - "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/x86" ) // NewFunction creates a new function. -func NewFunction(pkg string, name string, file *fs.File, body []token.Token) *Function { +func NewFunction(pkg string, name string, file *fs.File) *Function { return &Function{ Package: pkg, Name: name, UniqueName: pkg + "." + name, File: file, - Body: body, Machine: register.Machine{ Assembler: asm.Assembler{ Instructions: make([]asm.Instruction, 0, 8), diff --git a/src/scanner/scanFunctionSignature.go b/src/scanner/scanFunctionSignature.go index 501e252..874464e 100644 --- a/src/scanner/scanFunctionSignature.go +++ b/src/scanner/scanFunctionSignature.go @@ -86,7 +86,7 @@ func scanFunctionSignature(file *fs.File, tokens token.List, i int, delimiter to } name := tokens[nameStart].Text(file.Bytes) - function := core.NewFunction(file.Package, name, file, nil) + function := core.NewFunction(file.Package, name, file) if typeStart != -1 { if tokens[typeStart].Kind == token.GroupStart && tokens[typeEnd-1].Kind == token.GroupEnd { From aec22b0a7e053c0591756f4f0261c2a1fc4a6bca Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 13 Feb 2025 16:58:59 +0100 Subject: [PATCH 0738/1012] Removed unnecessary parameter --- src/core/NewFunction.go | 4 +--- src/scanner/scanFunctionSignature.go | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/core/NewFunction.go b/src/core/NewFunction.go index e2d93d2..3da5ed5 100644 --- a/src/core/NewFunction.go +++ b/src/core/NewFunction.go @@ -6,18 +6,16 @@ import ( "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/register" "git.akyoto.dev/cli/q/src/scope" - "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/x86" ) // NewFunction creates a new function. -func NewFunction(pkg string, name string, file *fs.File, body []token.Token) *Function { +func NewFunction(pkg string, name string, file *fs.File) *Function { return &Function{ Package: pkg, Name: name, UniqueName: pkg + "." + name, File: file, - Body: body, Machine: register.Machine{ Assembler: asm.Assembler{ Instructions: make([]asm.Instruction, 0, 8), diff --git a/src/scanner/scanFunctionSignature.go b/src/scanner/scanFunctionSignature.go index 501e252..874464e 100644 --- a/src/scanner/scanFunctionSignature.go +++ b/src/scanner/scanFunctionSignature.go @@ -86,7 +86,7 @@ func scanFunctionSignature(file *fs.File, tokens token.List, i int, delimiter to } name := tokens[nameStart].Text(file.Bytes) - function := core.NewFunction(file.Package, name, file, nil) + function := core.NewFunction(file.Package, name, file) if typeStart != -1 { if tokens[typeStart].Kind == token.GroupStart && tokens[typeEnd-1].Kind == token.GroupEnd { From 2b9cecd62b5d2cc83415ce880cddacde1a99bc58 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 13 Feb 2025 22:12:57 +0100 Subject: [PATCH 0739/1012] Improved error message for invalid instructions --- src/ast/parseNode.go | 2 +- tests/errors/InvalidInstructionCall.q | 5 +++++ tests/errors_test.go | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 tests/errors/InvalidInstructionCall.q diff --git a/src/ast/parseNode.go b/src/ast/parseNode.go index 93fada4..919566e 100644 --- a/src/ast/parseNode.go +++ b/src/ast/parseNode.go @@ -37,6 +37,6 @@ func parseNode(tokens token.List, source []byte, nodes AST) (Node, error) { return &Call{Expression: expr}, nil default: - return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text(source)}, nil, expr.Token.Position) + return nil, errors.New(&errors.InvalidInstruction{Instruction: tokens.Text(source)}, nil, tokens[0].Position) } } diff --git a/tests/errors/InvalidInstructionCall.q b/tests/errors/InvalidInstructionCall.q new file mode 100644 index 0000000..c11371a --- /dev/null +++ b/tests/errors/InvalidInstructionCall.q @@ -0,0 +1,5 @@ +import sys + +main() { + sys.write +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index c2b6588..f861d26 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -22,7 +22,8 @@ var errs = []struct { {"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse}, {"ExpectedStructName.q", errors.ExpectedStructName}, {"ExpectedPackageName.q", errors.ExpectedPackageName}, - {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}}, + {"InvalidInstructionCall.q", &errors.InvalidInstruction{Instruction: "sys.write"}}, + {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "2+3"}}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, {"InvalidInstructionString.q", &errors.InvalidInstruction{Instruction: "\"Hello\""}}, From e4cbb91f61dd4f3edf1b4314444c17fed0d893cf Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 13 Feb 2025 22:12:57 +0100 Subject: [PATCH 0740/1012] Improved error message for invalid instructions --- src/ast/parseNode.go | 2 +- tests/errors/InvalidInstructionCall.q | 5 +++++ tests/errors_test.go | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 tests/errors/InvalidInstructionCall.q diff --git a/src/ast/parseNode.go b/src/ast/parseNode.go index 93fada4..919566e 100644 --- a/src/ast/parseNode.go +++ b/src/ast/parseNode.go @@ -37,6 +37,6 @@ func parseNode(tokens token.List, source []byte, nodes AST) (Node, error) { return &Call{Expression: expr}, nil default: - return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text(source)}, nil, expr.Token.Position) + return nil, errors.New(&errors.InvalidInstruction{Instruction: tokens.Text(source)}, nil, tokens[0].Position) } } diff --git a/tests/errors/InvalidInstructionCall.q b/tests/errors/InvalidInstructionCall.q new file mode 100644 index 0000000..c11371a --- /dev/null +++ b/tests/errors/InvalidInstructionCall.q @@ -0,0 +1,5 @@ +import sys + +main() { + sys.write +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index c2b6588..f861d26 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -22,7 +22,8 @@ var errs = []struct { {"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse}, {"ExpectedStructName.q", errors.ExpectedStructName}, {"ExpectedPackageName.q", errors.ExpectedPackageName}, - {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "+"}}, + {"InvalidInstructionCall.q", &errors.InvalidInstruction{Instruction: "sys.write"}}, + {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "2+3"}}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, {"InvalidInstructionNumber.q", &errors.InvalidInstruction{Instruction: "123"}}, {"InvalidInstructionString.q", &errors.InvalidInstruction{Instruction: "\"Hello\""}}, From 5f3cb227799b27ecc4dd662435af914cdf8de3e3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Feb 2025 00:26:48 +0100 Subject: [PATCH 0741/1012] Added more tests --- src/core/CompileAssignArray.go | 10 ++++--- src/core/CompileDefinition.go | 7 +++-- src/core/ExpressionToRegister.go | 46 +++++++++++++++++++++++++++++--- src/errors/Common.go | 3 +-- tests/errors/TypeMismatch3.q | 7 +++++ tests/errors/TypeMismatch4.q | 6 +++++ tests/errors/UntypedExpression.q | 6 +++++ tests/errors_test.go | 1 + 8 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 tests/errors/TypeMismatch3.q create mode 100644 tests/errors/TypeMismatch4.q create mode 100644 tests/errors/UntypedExpression.q diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 1cab08a..2fee834 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -7,7 +7,7 @@ import ( "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" ) // CompileAssignArray compiles an assign statement for array elements. @@ -33,7 +33,7 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { index := left.Children[1] - if index.Token.Kind == token.Number { + if index.Token.IsNumeric() { offset, err := f.Number(index.Token) if err != nil { @@ -42,12 +42,16 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { memory.Offset = int8(offset) } else { - _, indexRegister, isTemporary, err := f.Evaluate(index) + typ, indexRegister, isTemporary, err := f.Evaluate(index) if err != nil { return err } + if !types.Is(typ, types.Int) { + return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) + } + memory.OffsetRegister = indexRegister if isTemporary { diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index 0887e0f..1143315 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -25,12 +25,11 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return err } - variable.Type = typ - - if variable.Type == nil { - return errors.New(errors.CouldNotInferType, f.File, node.Expression.Token.End()) + if typ == nil { + return errors.New(errors.UntypedExpression, f.File, node.Expression.Token.End()) } + variable.Type = typ f.AddVariable(variable) return nil } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index d690f7f..4e52cc9 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -1,6 +1,8 @@ package core import ( + "math" + "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/cpu" @@ -40,10 +42,46 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } if node.Token.Kind == token.Array { - array := f.VariableByName(node.Children[0].Token.Text(f.File.Bytes)) - offset, err := f.Number(node.Children[1].Token) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: array.Register, Offset: int8(offset), Length: 1}, register) - return types.Int, err + name := node.Children[0].Token.Text(f.File.Bytes) + array := f.VariableByName(name) + + if array == nil { + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Children[0].Token.Position) + } + + index := node.Children[1] + + memory := asm.Memory{ + Base: array.Register, + Offset: 0, + OffsetRegister: cpu.Register(math.MaxUint8), + Length: byte(1), + } + + if index.Token.IsNumeric() { + offset, err := f.Number(index.Token) + + if err != nil { + return nil, err + } + + memory.Offset = int8(offset) + } else { + typ, err := f.ExpressionToRegister(index, register) + + if err != nil { + return nil, err + } + + if !types.Is(typ, types.Int) { + return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) + } + + memory.OffsetRegister = register + } + + f.MemoryRegister(asm.LOAD, memory, register) + return types.Int, nil } if node.Token.Kind == token.Period { diff --git a/src/errors/Common.go b/src/errors/Common.go index 7b00ddd..d4199e4 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -1,9 +1,7 @@ package errors var ( - CouldNotInferType = &Base{"Couldn't infer type"} EmptySwitch = &Base{"Empty switch"} - ExpectedFunctionName = &Base{"Expected function name"} ExpectedFunctionParameters = &Base{"Expected function parameters"} ExpectedFunctionDefinition = &Base{"Expected function definition"} ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} @@ -24,4 +22,5 @@ var ( MissingParameter = &Base{"Missing parameter"} MissingType = &Base{"Missing type"} NotImplemented = &Base{"Not implemented"} + UntypedExpression = &Base{"Untyped expression"} ) diff --git a/tests/errors/TypeMismatch3.q b/tests/errors/TypeMismatch3.q new file mode 100644 index 0000000..06d0075 --- /dev/null +++ b/tests/errors/TypeMismatch3.q @@ -0,0 +1,7 @@ +import mem + +main() { + a := mem.alloc(16) + b := a["not a number"] + b += 1 +} \ No newline at end of file diff --git a/tests/errors/TypeMismatch4.q b/tests/errors/TypeMismatch4.q new file mode 100644 index 0000000..708c19a --- /dev/null +++ b/tests/errors/TypeMismatch4.q @@ -0,0 +1,6 @@ +import mem + +main() { + a := mem.alloc(16) + a["not a number"] = 42 +} \ No newline at end of file diff --git a/tests/errors/UntypedExpression.q b/tests/errors/UntypedExpression.q new file mode 100644 index 0000000..f63d553 --- /dev/null +++ b/tests/errors/UntypedExpression.q @@ -0,0 +1,6 @@ +main() { + a := unknown() + a += 1 +} + +unknown() {} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index f861d26..0386339 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -57,6 +57,7 @@ var errs = []struct { {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownPackage.q", &errors.UnknownPackage{Name: "sys"}}, {"UnknownStructField.q", &errors.UnknownStructField{StructName: "A", FieldName: "x"}}, + {"UntypedExpression.q", errors.UntypedExpression}, {"UnusedImport.q", &errors.UnusedImport{Package: "sys"}}, {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, From c8b5c4dbfe01eb620934150b7d52b1848c202bf2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Feb 2025 00:26:48 +0100 Subject: [PATCH 0742/1012] Added more tests --- src/core/CompileAssignArray.go | 10 ++++--- src/core/CompileDefinition.go | 7 +++-- src/core/ExpressionToRegister.go | 46 +++++++++++++++++++++++++++++--- src/errors/Common.go | 3 +-- tests/errors/TypeMismatch3.q | 7 +++++ tests/errors/TypeMismatch4.q | 6 +++++ tests/errors/UntypedExpression.q | 6 +++++ tests/errors_test.go | 1 + 8 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 tests/errors/TypeMismatch3.q create mode 100644 tests/errors/TypeMismatch4.q create mode 100644 tests/errors/UntypedExpression.q diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 1cab08a..2fee834 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -7,7 +7,7 @@ import ( "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" ) // CompileAssignArray compiles an assign statement for array elements. @@ -33,7 +33,7 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { index := left.Children[1] - if index.Token.Kind == token.Number { + if index.Token.IsNumeric() { offset, err := f.Number(index.Token) if err != nil { @@ -42,12 +42,16 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { memory.Offset = int8(offset) } else { - _, indexRegister, isTemporary, err := f.Evaluate(index) + typ, indexRegister, isTemporary, err := f.Evaluate(index) if err != nil { return err } + if !types.Is(typ, types.Int) { + return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) + } + memory.OffsetRegister = indexRegister if isTemporary { diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index 0887e0f..1143315 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -25,12 +25,11 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return err } - variable.Type = typ - - if variable.Type == nil { - return errors.New(errors.CouldNotInferType, f.File, node.Expression.Token.End()) + if typ == nil { + return errors.New(errors.UntypedExpression, f.File, node.Expression.Token.End()) } + variable.Type = typ f.AddVariable(variable) return nil } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index d690f7f..4e52cc9 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -1,6 +1,8 @@ package core import ( + "math" + "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/cpu" @@ -40,10 +42,46 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } if node.Token.Kind == token.Array { - array := f.VariableByName(node.Children[0].Token.Text(f.File.Bytes)) - offset, err := f.Number(node.Children[1].Token) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: array.Register, Offset: int8(offset), Length: 1}, register) - return types.Int, err + name := node.Children[0].Token.Text(f.File.Bytes) + array := f.VariableByName(name) + + if array == nil { + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Children[0].Token.Position) + } + + index := node.Children[1] + + memory := asm.Memory{ + Base: array.Register, + Offset: 0, + OffsetRegister: cpu.Register(math.MaxUint8), + Length: byte(1), + } + + if index.Token.IsNumeric() { + offset, err := f.Number(index.Token) + + if err != nil { + return nil, err + } + + memory.Offset = int8(offset) + } else { + typ, err := f.ExpressionToRegister(index, register) + + if err != nil { + return nil, err + } + + if !types.Is(typ, types.Int) { + return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) + } + + memory.OffsetRegister = register + } + + f.MemoryRegister(asm.LOAD, memory, register) + return types.Int, nil } if node.Token.Kind == token.Period { diff --git a/src/errors/Common.go b/src/errors/Common.go index 7b00ddd..d4199e4 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -1,9 +1,7 @@ package errors var ( - CouldNotInferType = &Base{"Couldn't infer type"} EmptySwitch = &Base{"Empty switch"} - ExpectedFunctionName = &Base{"Expected function name"} ExpectedFunctionParameters = &Base{"Expected function parameters"} ExpectedFunctionDefinition = &Base{"Expected function definition"} ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} @@ -24,4 +22,5 @@ var ( MissingParameter = &Base{"Missing parameter"} MissingType = &Base{"Missing type"} NotImplemented = &Base{"Not implemented"} + UntypedExpression = &Base{"Untyped expression"} ) diff --git a/tests/errors/TypeMismatch3.q b/tests/errors/TypeMismatch3.q new file mode 100644 index 0000000..06d0075 --- /dev/null +++ b/tests/errors/TypeMismatch3.q @@ -0,0 +1,7 @@ +import mem + +main() { + a := mem.alloc(16) + b := a["not a number"] + b += 1 +} \ No newline at end of file diff --git a/tests/errors/TypeMismatch4.q b/tests/errors/TypeMismatch4.q new file mode 100644 index 0000000..708c19a --- /dev/null +++ b/tests/errors/TypeMismatch4.q @@ -0,0 +1,6 @@ +import mem + +main() { + a := mem.alloc(16) + a["not a number"] = 42 +} \ No newline at end of file diff --git a/tests/errors/UntypedExpression.q b/tests/errors/UntypedExpression.q new file mode 100644 index 0000000..f63d553 --- /dev/null +++ b/tests/errors/UntypedExpression.q @@ -0,0 +1,6 @@ +main() { + a := unknown() + a += 1 +} + +unknown() {} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index f861d26..0386339 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -57,6 +57,7 @@ var errs = []struct { {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownPackage.q", &errors.UnknownPackage{Name: "sys"}}, {"UnknownStructField.q", &errors.UnknownStructField{StructName: "A", FieldName: "x"}}, + {"UntypedExpression.q", errors.UntypedExpression}, {"UnusedImport.q", &errors.UnusedImport{Package: "sys"}}, {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, From b065f56b8e09005a453740c94cd38bb9e29df6c9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Feb 2025 15:26:07 +0100 Subject: [PATCH 0743/1012] Added more tests --- src/errors/Common.go | 1 - tests/errors/MissingExpression.q | 3 +++ tests/errors_test.go | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 tests/errors/MissingExpression.q diff --git a/src/errors/Common.go b/src/errors/Common.go index d4199e4..c313c86 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -11,7 +11,6 @@ var ( InvalidNumber = &Base{"Invalid number"} InvalidExpression = &Base{"Invalid expression"} InvalidRune = &Base{"Invalid rune"} - InvalidStatement = &Base{"Invalid statement"} MissingBlockStart = &Base{"Missing '{'"} MissingBlockEnd = &Base{"Missing '}'"} MissingExpression = &Base{"Missing expression"} diff --git a/tests/errors/MissingExpression.q b/tests/errors/MissingExpression.q new file mode 100644 index 0000000..eacbe00 --- /dev/null +++ b/tests/errors/MissingExpression.q @@ -0,0 +1,3 @@ +main() { + assert +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 0386339..7b3812e 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -38,6 +38,7 @@ var errs = []struct { {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockEnd2.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, + {"MissingExpression.q", errors.MissingExpression}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, {"MissingGroupStart.q", errors.MissingGroupStart}, {"MissingMainFunction.q", errors.MissingMainFunction}, From d589b0257083d2391f0a6975889579a04411758a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Feb 2025 15:26:07 +0100 Subject: [PATCH 0744/1012] Added more tests --- src/errors/Common.go | 1 - tests/errors/MissingExpression.q | 3 +++ tests/errors_test.go | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 tests/errors/MissingExpression.q diff --git a/src/errors/Common.go b/src/errors/Common.go index d4199e4..c313c86 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -11,7 +11,6 @@ var ( InvalidNumber = &Base{"Invalid number"} InvalidExpression = &Base{"Invalid expression"} InvalidRune = &Base{"Invalid rune"} - InvalidStatement = &Base{"Invalid statement"} MissingBlockStart = &Base{"Missing '{'"} MissingBlockEnd = &Base{"Missing '}'"} MissingExpression = &Base{"Missing expression"} diff --git a/tests/errors/MissingExpression.q b/tests/errors/MissingExpression.q new file mode 100644 index 0000000..eacbe00 --- /dev/null +++ b/tests/errors/MissingExpression.q @@ -0,0 +1,3 @@ +main() { + assert +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 0386339..7b3812e 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -38,6 +38,7 @@ var errs = []struct { {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockEnd2.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, + {"MissingExpression.q", errors.MissingExpression}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, {"MissingGroupStart.q", errors.MissingGroupStart}, {"MissingMainFunction.q", errors.MissingMainFunction}, From 3b6b87f223cda8f4a7a3da5dc362cd5ebb5db5cd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Feb 2025 16:46:36 +0100 Subject: [PATCH 0745/1012] Updated Go version --- go.mod | 2 +- src/asmc/Finalize.go | 2 +- src/compiler/Compile.go | 4 ++-- src/compiler/PrintInstructions.go | 2 +- src/expression/bench_test.go | 2 +- src/scanner/scanImport.go | 2 +- src/token/bench_test.go | 2 +- tests/examples_test.go | 2 +- tests/programs_test.go | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 3a61a1f..c74451e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module git.akyoto.dev/cli/q -go 1.23 +go 1.24 require ( git.akyoto.dev/go/assert v0.1.3 diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index ff47fb3..71bb996 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -16,7 +16,7 @@ func Finalize(a asm.Assembler, dlls dll.List) ([]byte, []byte) { c := compiler{ code: make([]byte, 0, len(a.Instructions)*8), - codeLabels: map[string]Address{}, + codeLabels: make(map[string]Address, 32), codeStart: codeOffset(), data: data, dataLabels: dataLabels, diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 9a7f4e6..d66ce52 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -11,8 +11,8 @@ import ( func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Struct, errs <-chan error) (Result, error) { result := Result{} allFiles := make([]*fs.File, 0, 8) - allFunctions := map[string]*core.Function{} - allStructs := map[string]*types.Struct{} + allFunctions := make(map[string]*core.Function, 32) + allStructs := make(map[string]*types.Struct, 8) for functions != nil || structs != nil || files != nil || errs != nil { select { diff --git a/src/compiler/PrintInstructions.go b/src/compiler/PrintInstructions.go index 686aa63..3d92da2 100644 --- a/src/compiler/PrintInstructions.go +++ b/src/compiler/PrintInstructions.go @@ -4,7 +4,7 @@ import "git.akyoto.dev/cli/q/src/core" // PrintInstructions prints out the generated instructions. func (r *Result) PrintInstructions() { - r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { + r.eachFunction(r.Main, make(map[*core.Function]bool, len(r.Functions)), func(f *core.Function) { f.PrintInstructions() }) } diff --git a/src/expression/bench_test.go b/src/expression/bench_test.go index ec63729..7353cce 100644 --- a/src/expression/bench_test.go +++ b/src/expression/bench_test.go @@ -11,7 +11,7 @@ func BenchmarkExpression(b *testing.B) { src := []byte("(1+2-3*4)+(5*6-7+8)") tokens := token.Tokenize(src) - for range b.N { + for b.Loop() { expression.Parse(tokens) } } diff --git a/src/scanner/scanImport.go b/src/scanner/scanImport.go index 710a76a..47ed367 100644 --- a/src/scanner/scanImport.go +++ b/src/scanner/scanImport.go @@ -20,7 +20,7 @@ func (s *Scanner) scanImport(file *fs.File, tokens token.List, i int) (int, erro packageName := tokens[i].Text(file.Bytes) if file.Imports == nil { - file.Imports = map[string]*fs.Import{} + file.Imports = make(map[string]*fs.Import, 4) } fullPath := filepath.Join(config.Library, packageName) diff --git a/src/token/bench_test.go b/src/token/bench_test.go index 3c54881..6004fa3 100644 --- a/src/token/bench_test.go +++ b/src/token/bench_test.go @@ -17,7 +17,7 @@ func bench(n int) func(b *testing.B) { return func(b *testing.B) { input := bytes.Repeat(line, n) - for range b.N { + for b.Loop() { token.Tokenize(input) } } diff --git a/tests/examples_test.go b/tests/examples_test.go index 7373867..5096c62 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -49,7 +49,7 @@ func BenchmarkExamples(b *testing.B) { b.Run(test.Name, func(b *testing.B) { compiler := build.New(filepath.Join("..", "examples", test.Name)) - for range b.N { + for b.Loop() { _, err := compiler.Run() assert.Nil(b, err) } diff --git a/tests/programs_test.go b/tests/programs_test.go index d5a1eca..f3a13cb 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -86,7 +86,7 @@ func BenchmarkPrograms(b *testing.B) { b.Run(test.Name, func(b *testing.B) { compiler := build.New(filepath.Join("programs", test.Name+".q")) - for range b.N { + for b.Loop() { _, err := compiler.Run() assert.Nil(b, err) } From 88b3f468d149c4faba5410be5dfaa2ce50111616 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Feb 2025 16:46:36 +0100 Subject: [PATCH 0746/1012] Updated Go version --- go.mod | 2 +- src/asmc/Finalize.go | 2 +- src/compiler/Compile.go | 4 ++-- src/compiler/PrintInstructions.go | 2 +- src/expression/bench_test.go | 2 +- src/scanner/scanImport.go | 2 +- src/token/bench_test.go | 2 +- tests/examples_test.go | 2 +- tests/programs_test.go | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 3a61a1f..c74451e 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module git.akyoto.dev/cli/q -go 1.23 +go 1.24 require ( git.akyoto.dev/go/assert v0.1.3 diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index ff47fb3..71bb996 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -16,7 +16,7 @@ func Finalize(a asm.Assembler, dlls dll.List) ([]byte, []byte) { c := compiler{ code: make([]byte, 0, len(a.Instructions)*8), - codeLabels: map[string]Address{}, + codeLabels: make(map[string]Address, 32), codeStart: codeOffset(), data: data, dataLabels: dataLabels, diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 9a7f4e6..d66ce52 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -11,8 +11,8 @@ import ( func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Struct, errs <-chan error) (Result, error) { result := Result{} allFiles := make([]*fs.File, 0, 8) - allFunctions := map[string]*core.Function{} - allStructs := map[string]*types.Struct{} + allFunctions := make(map[string]*core.Function, 32) + allStructs := make(map[string]*types.Struct, 8) for functions != nil || structs != nil || files != nil || errs != nil { select { diff --git a/src/compiler/PrintInstructions.go b/src/compiler/PrintInstructions.go index 686aa63..3d92da2 100644 --- a/src/compiler/PrintInstructions.go +++ b/src/compiler/PrintInstructions.go @@ -4,7 +4,7 @@ import "git.akyoto.dev/cli/q/src/core" // PrintInstructions prints out the generated instructions. func (r *Result) PrintInstructions() { - r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { + r.eachFunction(r.Main, make(map[*core.Function]bool, len(r.Functions)), func(f *core.Function) { f.PrintInstructions() }) } diff --git a/src/expression/bench_test.go b/src/expression/bench_test.go index ec63729..7353cce 100644 --- a/src/expression/bench_test.go +++ b/src/expression/bench_test.go @@ -11,7 +11,7 @@ func BenchmarkExpression(b *testing.B) { src := []byte("(1+2-3*4)+(5*6-7+8)") tokens := token.Tokenize(src) - for range b.N { + for b.Loop() { expression.Parse(tokens) } } diff --git a/src/scanner/scanImport.go b/src/scanner/scanImport.go index 710a76a..47ed367 100644 --- a/src/scanner/scanImport.go +++ b/src/scanner/scanImport.go @@ -20,7 +20,7 @@ func (s *Scanner) scanImport(file *fs.File, tokens token.List, i int) (int, erro packageName := tokens[i].Text(file.Bytes) if file.Imports == nil { - file.Imports = map[string]*fs.Import{} + file.Imports = make(map[string]*fs.Import, 4) } fullPath := filepath.Join(config.Library, packageName) diff --git a/src/token/bench_test.go b/src/token/bench_test.go index 3c54881..6004fa3 100644 --- a/src/token/bench_test.go +++ b/src/token/bench_test.go @@ -17,7 +17,7 @@ func bench(n int) func(b *testing.B) { return func(b *testing.B) { input := bytes.Repeat(line, n) - for range b.N { + for b.Loop() { token.Tokenize(input) } } diff --git a/tests/examples_test.go b/tests/examples_test.go index 7373867..5096c62 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -49,7 +49,7 @@ func BenchmarkExamples(b *testing.B) { b.Run(test.Name, func(b *testing.B) { compiler := build.New(filepath.Join("..", "examples", test.Name)) - for range b.N { + for b.Loop() { _, err := compiler.Run() assert.Nil(b, err) } diff --git a/tests/programs_test.go b/tests/programs_test.go index d5a1eca..f3a13cb 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -86,7 +86,7 @@ func BenchmarkPrograms(b *testing.B) { b.Run(test.Name, func(b *testing.B) { compiler := build.New(filepath.Join("programs", test.Name+".q")) - for range b.N { + for b.Loop() { _, err := compiler.Run() assert.Nil(b, err) } From 46102bd44a5418438447e927e0cad07d67d84cd3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Feb 2025 20:35:44 +0100 Subject: [PATCH 0747/1012] Fixed fs package tests on Windows --- src/fs/Walk_windows.go | 30 ++++++++++++++++------------ src/fs/bench_test.go | 45 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 src/fs/bench_test.go diff --git a/src/fs/Walk_windows.go b/src/fs/Walk_windows.go index ab0b771..28cae1a 100644 --- a/src/fs/Walk_windows.go +++ b/src/fs/Walk_windows.go @@ -2,23 +2,27 @@ package fs -import ( - "io/fs" - "path/filepath" - "strings" -) +import "os" // Walk calls your callback function for every file name inside the directory. // It doesn't distinguish between files and directories. func Walk(directory string, callBack func(string)) error { - return filepath.WalkDir(directory, func(path string, d fs.DirEntry, err error) error { - fileName := d.Name() + f, err := os.Open(directory) - if strings.HasPrefix(fileName, ".") { - return filepath.SkipDir - } + if err != nil { + return err + } - callBack(fileName) - return nil - }) + files, err := f.Readdirnames(0) + f.Close() + + if err != nil { + return err + } + + for _, file := range files { + callBack(file) + } + + return nil } diff --git a/src/fs/bench_test.go b/src/fs/bench_test.go new file mode 100644 index 0000000..3462acd --- /dev/null +++ b/src/fs/bench_test.go @@ -0,0 +1,45 @@ +package fs_test + +import ( + "os" + "testing" + + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/go/assert" +) + +func BenchmarkReadDir(b *testing.B) { + for b.Loop() { + files, err := os.ReadDir(".") + assert.Nil(b, err) + + for _, file := range files { + func(string) {}(file.Name()) + } + } +} + +func BenchmarkReaddirnames(b *testing.B) { + for b.Loop() { + f, err := os.Open(".") + assert.Nil(b, err) + files, err := f.Readdirnames(0) + assert.Nil(b, err) + + for _, file := range files { + func(string) {}(file) + } + + f.Close() + } +} + +func BenchmarkWalk(b *testing.B) { + for b.Loop() { + err := fs.Walk(".", func(file string) { + func(string) {}(file) + }) + + assert.Nil(b, err) + } +} From 4df847bf60bfceee856336560b3808758082f35a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Feb 2025 20:35:44 +0100 Subject: [PATCH 0748/1012] Fixed fs package tests on Windows --- src/fs/Walk_windows.go | 30 ++++++++++++++++------------ src/fs/bench_test.go | 45 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 src/fs/bench_test.go diff --git a/src/fs/Walk_windows.go b/src/fs/Walk_windows.go index ab0b771..28cae1a 100644 --- a/src/fs/Walk_windows.go +++ b/src/fs/Walk_windows.go @@ -2,23 +2,27 @@ package fs -import ( - "io/fs" - "path/filepath" - "strings" -) +import "os" // Walk calls your callback function for every file name inside the directory. // It doesn't distinguish between files and directories. func Walk(directory string, callBack func(string)) error { - return filepath.WalkDir(directory, func(path string, d fs.DirEntry, err error) error { - fileName := d.Name() + f, err := os.Open(directory) - if strings.HasPrefix(fileName, ".") { - return filepath.SkipDir - } + if err != nil { + return err + } - callBack(fileName) - return nil - }) + files, err := f.Readdirnames(0) + f.Close() + + if err != nil { + return err + } + + for _, file := range files { + callBack(file) + } + + return nil } diff --git a/src/fs/bench_test.go b/src/fs/bench_test.go new file mode 100644 index 0000000..3462acd --- /dev/null +++ b/src/fs/bench_test.go @@ -0,0 +1,45 @@ +package fs_test + +import ( + "os" + "testing" + + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/go/assert" +) + +func BenchmarkReadDir(b *testing.B) { + for b.Loop() { + files, err := os.ReadDir(".") + assert.Nil(b, err) + + for _, file := range files { + func(string) {}(file.Name()) + } + } +} + +func BenchmarkReaddirnames(b *testing.B) { + for b.Loop() { + f, err := os.Open(".") + assert.Nil(b, err) + files, err := f.Readdirnames(0) + assert.Nil(b, err) + + for _, file := range files { + func(string) {}(file) + } + + f.Close() + } +} + +func BenchmarkWalk(b *testing.B) { + for b.Loop() { + err := fs.Walk(".", func(file string) { + func(string) {}(file) + }) + + assert.Nil(b, err) + } +} From 5f37a9e84af1b9bbaa50e4be7e3039e4d79053ce Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Feb 2025 20:57:13 +0100 Subject: [PATCH 0749/1012] Fixed build package tests on Windows --- src/build/build_test.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/build/build_test.go b/src/build/build_test.go index 36242d8..e442e12 100644 --- a/src/build/build_test.go +++ b/src/build/build_test.go @@ -5,6 +5,7 @@ import ( "testing" "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/go/assert" ) @@ -22,12 +23,24 @@ func TestBuildFile(t *testing.T) { func TestExecutableFromDirectory(t *testing.T) { b := build.New("../../examples/hello") - assert.Equal(t, filepath.Base(b.Executable()), "hello") + exe := filepath.Base(b.Executable()) + + if config.TargetOS != config.Windows { + assert.Equal(t, exe, "hello") + } else { + assert.Equal(t, exe, "hello.exe") + } } func TestExecutableFromFile(t *testing.T) { b := build.New("../../examples/hello/hello.q") - assert.Equal(t, filepath.Base(b.Executable()), "hello") + exe := filepath.Base(b.Executable()) + + if config.TargetOS != config.Windows { + assert.Equal(t, exe, "hello") + } else { + assert.Equal(t, exe, "hello.exe") + } } func TestNonExisting(t *testing.T) { From be028a52d1f2d6a5b4122d643601fcd0ba5f6c66 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Feb 2025 20:57:13 +0100 Subject: [PATCH 0750/1012] Fixed build package tests on Windows --- src/build/build_test.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/build/build_test.go b/src/build/build_test.go index 36242d8..e442e12 100644 --- a/src/build/build_test.go +++ b/src/build/build_test.go @@ -5,6 +5,7 @@ import ( "testing" "git.akyoto.dev/cli/q/src/build" + "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/go/assert" ) @@ -22,12 +23,24 @@ func TestBuildFile(t *testing.T) { func TestExecutableFromDirectory(t *testing.T) { b := build.New("../../examples/hello") - assert.Equal(t, filepath.Base(b.Executable()), "hello") + exe := filepath.Base(b.Executable()) + + if config.TargetOS != config.Windows { + assert.Equal(t, exe, "hello") + } else { + assert.Equal(t, exe, "hello.exe") + } } func TestExecutableFromFile(t *testing.T) { b := build.New("../../examples/hello/hello.q") - assert.Equal(t, filepath.Base(b.Executable()), "hello") + exe := filepath.Base(b.Executable()) + + if config.TargetOS != config.Windows { + assert.Equal(t, exe, "hello") + } else { + assert.Equal(t, exe, "hello.exe") + } } func TestNonExisting(t *testing.T) { From 91a3ec9d526dc6e7b6dc4e95e7c7c32b43a04a8d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Feb 2025 14:38:01 +0100 Subject: [PATCH 0751/1012] Implemented const keyword --- lib/io/io.q | 12 ++++++-- lib/mem/alloc_linux.q | 12 +++++++- lib/mem/alloc_mac.q | 12 +++++++- lib/mem/alloc_windows.q | 11 +++++++- lib/sys/mem_windows.q | 6 +++- src/build/Build.go | 4 +-- src/compiler/Compile.go | 14 ++++++++-- src/core/Constant.go | 13 +++++++++ src/core/ExpressionToRegister.go | 19 ++++++++++--- src/core/Function.go | 1 + src/errors/Common.go | 1 + src/scanner/Scan.go | 6 ++-- src/scanner/Scanner.go | 1 + src/scanner/scanConst.go | 47 ++++++++++++++++++++++++++++++++ src/scanner/scanExtern.go | 10 ++----- src/scanner/scanFile.go | 2 ++ src/token/Kind.go | 1 + src/token/Tokenize_test.go | 3 +- src/token/identifier.go | 2 ++ tests/programs/const.q | 12 ++++++++ tests/programs_test.go | 1 + 21 files changed, 164 insertions(+), 26 deletions(-) create mode 100644 src/core/Constant.go create mode 100644 src/scanner/scanConst.go create mode 100644 tests/programs/const.q diff --git a/lib/io/io.q b/lib/io/io.q index 5c195a4..4b34566 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -1,15 +1,21 @@ import sys +const std { + in 0 + out 1 + err 2 +} + in(buffer []Int8) -> Int { - return sys.read(0, buffer, len(buffer)) + return sys.read(std.in, buffer, len(buffer)) } out(buffer []Int8) -> Int { - return sys.write(1, buffer, len(buffer)) + return sys.write(std.out, buffer, len(buffer)) } error(buffer []Int8) -> Int { - return sys.write(2, buffer, len(buffer)) + return sys.write(std.err, buffer, len(buffer)) } read(fd Int, buffer []Int8) -> Int { diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_linux.q index 9b9878b..8ade3e6 100644 --- a/lib/mem/alloc_linux.q +++ b/lib/mem/alloc_linux.q @@ -1,7 +1,17 @@ import sys +const prot { + read 0x1 + write 0x2 +} + +const map { + private 0x02 + anonymous 0x20 +} + alloc(length Int) -> []Int8 { - x := sys.mmap(0, length+8, 0x1|0x2, 0x02|0x20) + x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) if x < 0x1000 { return x diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q index 128d40c..cdfbe86 100644 --- a/lib/mem/alloc_mac.q +++ b/lib/mem/alloc_mac.q @@ -1,7 +1,17 @@ import sys +const prot { + read 0x1 + write 0x2 +} + +const map { + private 0x02 + anonymous 0x1000 +} + alloc(length Int) -> []Int8 { - x := sys.mmap(0, length+8, 0x1|0x2, 0x02|0x1000) + x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) if x < 0x1000 { return x diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index 50bb75d..3167a43 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,7 +1,16 @@ import sys +const page { + readwrite 0x0004 +} + +const mem { + commit 0x1000 + reserve 0x2000 +} + alloc(length Int) -> []Int8 { - x := sys.mmap(0, length+8, 0x0004, 0x3000) + x := sys.mmap(0, length+8, page.readwrite, mem.commit|mem.reserve) if x < 0x1000 { return x diff --git a/lib/sys/mem_windows.q b/lib/sys/mem_windows.q index 79b093a..97772e9 100644 --- a/lib/sys/mem_windows.q +++ b/lib/sys/mem_windows.q @@ -3,10 +3,14 @@ extern kernel32 { VirtualFree(address *Any, length Int, type Int) -> Bool } +const mem { + decommit 0x4000 +} + mmap(address Int, length Int, protection Int, flags Int) -> *Any { return kernel32.VirtualAlloc(address, length, flags, protection) } munmap(address *Any, length Int) -> Int { - return kernel32.VirtualFree(address, length, 0x4000) + return kernel32.VirtualFree(address, length, mem.decommit) } \ No newline at end of file diff --git a/src/build/Build.go b/src/build/Build.go index 9784a97..64e1190 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -23,8 +23,8 @@ func New(files ...string) *Build { // Run compiles the input files. func (build *Build) Run() (compiler.Result, error) { - files, functions, structs, errors := scanner.Scan(build.Files) - return compiler.Compile(files, functions, structs, errors) + constants, files, functions, structs, errors := scanner.Scan(build.Files) + return compiler.Compile(constants, files, functions, structs, errors) } // Executable returns the path to the executable. diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index d66ce52..f964d78 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -8,13 +8,14 @@ import ( ) // Compile waits for the scan to finish and compiles all functions. -func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Struct, errs <-chan error) (Result, error) { +func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Struct, errs <-chan error) (Result, error) { result := Result{} allFiles := make([]*fs.File, 0, 8) allFunctions := make(map[string]*core.Function, 32) allStructs := make(map[string]*types.Struct, 8) + allConstants := make(map[string]*core.Constant, 8) - for functions != nil || structs != nil || files != nil || errs != nil { + for constants != nil || files != nil || functions != nil || structs != nil || errs != nil { select { case function, ok := <-functions: if !ok { @@ -24,6 +25,7 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c function.Functions = allFunctions function.Structs = allStructs + function.Constants = allConstants allFunctions[function.UniqueName] = function case structure, ok := <-structs: @@ -42,6 +44,14 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c allFiles = append(allFiles, file) + case constant, ok := <-constants: + if !ok { + constants = nil + continue + } + + allConstants[constant.Name] = constant + case err, ok := <-errs: if !ok { errs = nil diff --git a/src/core/Constant.go b/src/core/Constant.go new file mode 100644 index 0000000..1156e05 --- /dev/null +++ b/src/core/Constant.go @@ -0,0 +1,13 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" +) + +// Constant registers a single value to be accessible under a descriptive name. +type Constant struct { + Name string + Value token.Token + File *fs.File +} diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 4e52cc9..d1a85ec 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -86,11 +86,22 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if node.Token.Kind == token.Period { left := node.Children[0] - name := left.Token.Text(f.File.Bytes) - variable := f.VariableByName(name) + leftText := left.Token.Text(f.File.Bytes) right := node.Children[1] - name = right.Token.Text(f.File.Bytes) - field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(name) + rightText := right.Token.Text(f.File.Bytes) + variable := f.VariableByName(leftText) + + if variable == nil { + constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] + + if isConst { + return f.TokenToRegister(constant.Value, register) + } + + return nil, errors.New(&errors.UnknownIdentifier{Name: leftText}, f.File, left.Token.Position) + } + + field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: int8(field.Offset), Length: byte(field.Type.Size())}, register) return field.Type, nil } diff --git a/src/core/Function.go b/src/core/Function.go index 5565dea..956956c 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -21,6 +21,7 @@ type Function struct { OutputTypes []types.Type Functions map[string]*Function Structs map[string]*types.Struct + Constants map[string]*Constant DLLs dll.List Err error deferred []func() diff --git a/src/errors/Common.go b/src/errors/Common.go index c313c86..04638d8 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -2,6 +2,7 @@ package errors var ( EmptySwitch = &Base{"Empty switch"} + ExpectedConstName = &Base{"Expected a name for the const group"} ExpectedFunctionParameters = &Base{"Expected function parameters"} ExpectedFunctionDefinition = &Base{"Expected function definition"} ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go index fdb3354..f745619 100644 --- a/src/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -10,8 +10,9 @@ import ( ) // Scan scans the list of files. -func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan *types.Struct, <-chan error) { +func Scan(files []string) (chan *core.Constant, <-chan *fs.File, <-chan *core.Function, <-chan *types.Struct, <-chan error) { scanner := Scanner{ + constants: make(chan *core.Constant), files: make(chan *fs.File), functions: make(chan *core.Function), structs: make(chan *types.Struct), @@ -22,11 +23,12 @@ func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan *types scanner.queueDirectory(filepath.Join(config.Library, "mem"), "mem") scanner.queue(files...) scanner.group.Wait() + close(scanner.constants) close(scanner.files) close(scanner.functions) close(scanner.structs) close(scanner.errors) }() - return scanner.files, scanner.functions, scanner.structs, scanner.errors + return scanner.constants, scanner.files, scanner.functions, scanner.structs, scanner.errors } diff --git a/src/scanner/Scanner.go b/src/scanner/Scanner.go index 97847bd..9bb05fa 100644 --- a/src/scanner/Scanner.go +++ b/src/scanner/Scanner.go @@ -10,6 +10,7 @@ import ( // Scanner is used to scan files before the actual compilation step. type Scanner struct { + constants chan *core.Constant files chan *fs.File functions chan *core.Function structs chan *types.Struct diff --git a/src/scanner/scanConst.go b/src/scanner/scanConst.go new file mode 100644 index 0000000..f382813 --- /dev/null +++ b/src/scanner/scanConst.go @@ -0,0 +1,47 @@ +package scanner + +import ( + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" +) + +// scanConst scans a block of constants. +func (s *Scanner) scanConst(file *fs.File, tokens token.List, i int) (int, error) { + i++ + + if tokens[i].Kind != token.Identifier { + return i, errors.New(errors.ExpectedConstName, file, tokens[i].Position) + } + + groupName := tokens[i].Text(file.Bytes) + i++ + + if tokens[i].Kind != token.BlockStart { + return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) + } + + i++ + + for i < len(tokens) { + if tokens[i].Kind == token.Identifier { + name := tokens[i].Text(file.Bytes) + i++ + + s.constants <- &core.Constant{ + Name: file.Package + "." + groupName + "." + name, + Value: tokens[i], + File: file, + } + } + + if tokens[i].Kind == token.BlockEnd { + return i, nil + } + + i++ + } + + return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) +} diff --git a/src/scanner/scanExtern.go b/src/scanner/scanExtern.go index e4dc19d..4e4e7e8 100644 --- a/src/scanner/scanExtern.go +++ b/src/scanner/scanExtern.go @@ -22,7 +22,6 @@ func (s *Scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, erro } i++ - closed := false for i < len(tokens) { if tokens[i].Kind == token.Identifier { @@ -39,16 +38,11 @@ func (s *Scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, erro } if tokens[i].Kind == token.BlockEnd { - closed = true - break + return i, nil } i++ } - if !closed { - return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) - } - - return i, nil + return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) } diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 2aae3af..65601f0 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -39,6 +39,8 @@ func (s *Scanner) scanFile(path string, pkg string) error { i, err = s.scanFunction(file, tokens, i) case token.Extern: i, err = s.scanExtern(file, tokens, i) + case token.Const: + i, err = s.scanConst(file, tokens, i) case token.EOF: return nil case token.Invalid: diff --git a/src/token/Kind.go b/src/token/Kind.go index 6e729e4..200d131 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -65,6 +65,7 @@ const ( ___END_OPERATORS___ // ___KEYWORDS___ // Assert // assert + Const // const Else // else Extern // extern If // if diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index 022115d..b38a5d7 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -25,10 +25,11 @@ func TestFunction(t *testing.T) { } func TestKeyword(t *testing.T) { - tokens := token.Tokenize([]byte("assert if import else extern loop return struct switch")) + tokens := token.Tokenize([]byte("assert const if import else extern loop return struct switch")) expected := []token.Kind{ token.Assert, + token.Const, token.If, token.Import, token.Else, diff --git a/src/token/identifier.go b/src/token/identifier.go index 6b6823b..6973fc6 100644 --- a/src/token/identifier.go +++ b/src/token/identifier.go @@ -15,6 +15,8 @@ func identifier(tokens List, buffer []byte, i Position) (List, Position) { switch string(identifier) { case "assert": kind = Assert + case "const": + kind = Const case "if": kind = If case "else": diff --git a/tests/programs/const.q b/tests/programs/const.q new file mode 100644 index 0000000..c947f1a --- /dev/null +++ b/tests/programs/const.q @@ -0,0 +1,12 @@ +const num { + one 1 + two 2 + three 3 +} + +main() { + assert num.one == 1 + assert num.two == 2 + assert num.three == 3 + assert num.one + num.two == num.three +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index f3a13cb..23ec235 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -31,6 +31,7 @@ var programs = []struct { {"binary", "", "", 0}, {"octal", "", "", 0}, {"hexadecimal", "", "", 0}, + {"const", "", "", 0}, {"array", "", "", 0}, {"escape-rune", "", "", 0}, {"escape-string", "", "", 0}, From 0a1a8f741dbecd7838de9e31927bfbd4e85f2403 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Feb 2025 14:38:01 +0100 Subject: [PATCH 0752/1012] Implemented const keyword --- lib/io/io.q | 12 ++++++-- lib/mem/alloc_linux.q | 12 +++++++- lib/mem/alloc_mac.q | 12 +++++++- lib/mem/alloc_windows.q | 11 +++++++- lib/sys/mem_windows.q | 6 +++- src/build/Build.go | 4 +-- src/compiler/Compile.go | 14 ++++++++-- src/core/Constant.go | 13 +++++++++ src/core/ExpressionToRegister.go | 19 ++++++++++--- src/core/Function.go | 1 + src/errors/Common.go | 1 + src/scanner/Scan.go | 6 ++-- src/scanner/Scanner.go | 1 + src/scanner/scanConst.go | 47 ++++++++++++++++++++++++++++++++ src/scanner/scanExtern.go | 10 ++----- src/scanner/scanFile.go | 2 ++ src/token/Kind.go | 1 + src/token/Tokenize_test.go | 3 +- src/token/identifier.go | 2 ++ tests/programs/const.q | 12 ++++++++ tests/programs_test.go | 1 + 21 files changed, 164 insertions(+), 26 deletions(-) create mode 100644 src/core/Constant.go create mode 100644 src/scanner/scanConst.go create mode 100644 tests/programs/const.q diff --git a/lib/io/io.q b/lib/io/io.q index 5c195a4..4b34566 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -1,15 +1,21 @@ import sys +const std { + in 0 + out 1 + err 2 +} + in(buffer []Int8) -> Int { - return sys.read(0, buffer, len(buffer)) + return sys.read(std.in, buffer, len(buffer)) } out(buffer []Int8) -> Int { - return sys.write(1, buffer, len(buffer)) + return sys.write(std.out, buffer, len(buffer)) } error(buffer []Int8) -> Int { - return sys.write(2, buffer, len(buffer)) + return sys.write(std.err, buffer, len(buffer)) } read(fd Int, buffer []Int8) -> Int { diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_linux.q index 9b9878b..8ade3e6 100644 --- a/lib/mem/alloc_linux.q +++ b/lib/mem/alloc_linux.q @@ -1,7 +1,17 @@ import sys +const prot { + read 0x1 + write 0x2 +} + +const map { + private 0x02 + anonymous 0x20 +} + alloc(length Int) -> []Int8 { - x := sys.mmap(0, length+8, 0x1|0x2, 0x02|0x20) + x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) if x < 0x1000 { return x diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q index 128d40c..cdfbe86 100644 --- a/lib/mem/alloc_mac.q +++ b/lib/mem/alloc_mac.q @@ -1,7 +1,17 @@ import sys +const prot { + read 0x1 + write 0x2 +} + +const map { + private 0x02 + anonymous 0x1000 +} + alloc(length Int) -> []Int8 { - x := sys.mmap(0, length+8, 0x1|0x2, 0x02|0x1000) + x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) if x < 0x1000 { return x diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index 50bb75d..3167a43 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,7 +1,16 @@ import sys +const page { + readwrite 0x0004 +} + +const mem { + commit 0x1000 + reserve 0x2000 +} + alloc(length Int) -> []Int8 { - x := sys.mmap(0, length+8, 0x0004, 0x3000) + x := sys.mmap(0, length+8, page.readwrite, mem.commit|mem.reserve) if x < 0x1000 { return x diff --git a/lib/sys/mem_windows.q b/lib/sys/mem_windows.q index 79b093a..97772e9 100644 --- a/lib/sys/mem_windows.q +++ b/lib/sys/mem_windows.q @@ -3,10 +3,14 @@ extern kernel32 { VirtualFree(address *Any, length Int, type Int) -> Bool } +const mem { + decommit 0x4000 +} + mmap(address Int, length Int, protection Int, flags Int) -> *Any { return kernel32.VirtualAlloc(address, length, flags, protection) } munmap(address *Any, length Int) -> Int { - return kernel32.VirtualFree(address, length, 0x4000) + return kernel32.VirtualFree(address, length, mem.decommit) } \ No newline at end of file diff --git a/src/build/Build.go b/src/build/Build.go index 9784a97..64e1190 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -23,8 +23,8 @@ func New(files ...string) *Build { // Run compiles the input files. func (build *Build) Run() (compiler.Result, error) { - files, functions, structs, errors := scanner.Scan(build.Files) - return compiler.Compile(files, functions, structs, errors) + constants, files, functions, structs, errors := scanner.Scan(build.Files) + return compiler.Compile(constants, files, functions, structs, errors) } // Executable returns the path to the executable. diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index d66ce52..f964d78 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -8,13 +8,14 @@ import ( ) // Compile waits for the scan to finish and compiles all functions. -func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Struct, errs <-chan error) (Result, error) { +func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Struct, errs <-chan error) (Result, error) { result := Result{} allFiles := make([]*fs.File, 0, 8) allFunctions := make(map[string]*core.Function, 32) allStructs := make(map[string]*types.Struct, 8) + allConstants := make(map[string]*core.Constant, 8) - for functions != nil || structs != nil || files != nil || errs != nil { + for constants != nil || files != nil || functions != nil || structs != nil || errs != nil { select { case function, ok := <-functions: if !ok { @@ -24,6 +25,7 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c function.Functions = allFunctions function.Structs = allStructs + function.Constants = allConstants allFunctions[function.UniqueName] = function case structure, ok := <-structs: @@ -42,6 +44,14 @@ func Compile(files <-chan *fs.File, functions <-chan *core.Function, structs <-c allFiles = append(allFiles, file) + case constant, ok := <-constants: + if !ok { + constants = nil + continue + } + + allConstants[constant.Name] = constant + case err, ok := <-errs: if !ok { errs = nil diff --git a/src/core/Constant.go b/src/core/Constant.go new file mode 100644 index 0000000..1156e05 --- /dev/null +++ b/src/core/Constant.go @@ -0,0 +1,13 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" +) + +// Constant registers a single value to be accessible under a descriptive name. +type Constant struct { + Name string + Value token.Token + File *fs.File +} diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 4e52cc9..d1a85ec 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -86,11 +86,22 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if node.Token.Kind == token.Period { left := node.Children[0] - name := left.Token.Text(f.File.Bytes) - variable := f.VariableByName(name) + leftText := left.Token.Text(f.File.Bytes) right := node.Children[1] - name = right.Token.Text(f.File.Bytes) - field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(name) + rightText := right.Token.Text(f.File.Bytes) + variable := f.VariableByName(leftText) + + if variable == nil { + constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] + + if isConst { + return f.TokenToRegister(constant.Value, register) + } + + return nil, errors.New(&errors.UnknownIdentifier{Name: leftText}, f.File, left.Token.Position) + } + + field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: int8(field.Offset), Length: byte(field.Type.Size())}, register) return field.Type, nil } diff --git a/src/core/Function.go b/src/core/Function.go index 5565dea..956956c 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -21,6 +21,7 @@ type Function struct { OutputTypes []types.Type Functions map[string]*Function Structs map[string]*types.Struct + Constants map[string]*Constant DLLs dll.List Err error deferred []func() diff --git a/src/errors/Common.go b/src/errors/Common.go index c313c86..04638d8 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -2,6 +2,7 @@ package errors var ( EmptySwitch = &Base{"Empty switch"} + ExpectedConstName = &Base{"Expected a name for the const group"} ExpectedFunctionParameters = &Base{"Expected function parameters"} ExpectedFunctionDefinition = &Base{"Expected function definition"} ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go index fdb3354..f745619 100644 --- a/src/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -10,8 +10,9 @@ import ( ) // Scan scans the list of files. -func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan *types.Struct, <-chan error) { +func Scan(files []string) (chan *core.Constant, <-chan *fs.File, <-chan *core.Function, <-chan *types.Struct, <-chan error) { scanner := Scanner{ + constants: make(chan *core.Constant), files: make(chan *fs.File), functions: make(chan *core.Function), structs: make(chan *types.Struct), @@ -22,11 +23,12 @@ func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan *types scanner.queueDirectory(filepath.Join(config.Library, "mem"), "mem") scanner.queue(files...) scanner.group.Wait() + close(scanner.constants) close(scanner.files) close(scanner.functions) close(scanner.structs) close(scanner.errors) }() - return scanner.files, scanner.functions, scanner.structs, scanner.errors + return scanner.constants, scanner.files, scanner.functions, scanner.structs, scanner.errors } diff --git a/src/scanner/Scanner.go b/src/scanner/Scanner.go index 97847bd..9bb05fa 100644 --- a/src/scanner/Scanner.go +++ b/src/scanner/Scanner.go @@ -10,6 +10,7 @@ import ( // Scanner is used to scan files before the actual compilation step. type Scanner struct { + constants chan *core.Constant files chan *fs.File functions chan *core.Function structs chan *types.Struct diff --git a/src/scanner/scanConst.go b/src/scanner/scanConst.go new file mode 100644 index 0000000..f382813 --- /dev/null +++ b/src/scanner/scanConst.go @@ -0,0 +1,47 @@ +package scanner + +import ( + "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" +) + +// scanConst scans a block of constants. +func (s *Scanner) scanConst(file *fs.File, tokens token.List, i int) (int, error) { + i++ + + if tokens[i].Kind != token.Identifier { + return i, errors.New(errors.ExpectedConstName, file, tokens[i].Position) + } + + groupName := tokens[i].Text(file.Bytes) + i++ + + if tokens[i].Kind != token.BlockStart { + return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) + } + + i++ + + for i < len(tokens) { + if tokens[i].Kind == token.Identifier { + name := tokens[i].Text(file.Bytes) + i++ + + s.constants <- &core.Constant{ + Name: file.Package + "." + groupName + "." + name, + Value: tokens[i], + File: file, + } + } + + if tokens[i].Kind == token.BlockEnd { + return i, nil + } + + i++ + } + + return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) +} diff --git a/src/scanner/scanExtern.go b/src/scanner/scanExtern.go index e4dc19d..4e4e7e8 100644 --- a/src/scanner/scanExtern.go +++ b/src/scanner/scanExtern.go @@ -22,7 +22,6 @@ func (s *Scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, erro } i++ - closed := false for i < len(tokens) { if tokens[i].Kind == token.Identifier { @@ -39,16 +38,11 @@ func (s *Scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, erro } if tokens[i].Kind == token.BlockEnd { - closed = true - break + return i, nil } i++ } - if !closed { - return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) - } - - return i, nil + return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) } diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 2aae3af..65601f0 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -39,6 +39,8 @@ func (s *Scanner) scanFile(path string, pkg string) error { i, err = s.scanFunction(file, tokens, i) case token.Extern: i, err = s.scanExtern(file, tokens, i) + case token.Const: + i, err = s.scanConst(file, tokens, i) case token.EOF: return nil case token.Invalid: diff --git a/src/token/Kind.go b/src/token/Kind.go index 6e729e4..200d131 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -65,6 +65,7 @@ const ( ___END_OPERATORS___ // ___KEYWORDS___ // Assert // assert + Const // const Else // else Extern // extern If // if diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index 022115d..b38a5d7 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -25,10 +25,11 @@ func TestFunction(t *testing.T) { } func TestKeyword(t *testing.T) { - tokens := token.Tokenize([]byte("assert if import else extern loop return struct switch")) + tokens := token.Tokenize([]byte("assert const if import else extern loop return struct switch")) expected := []token.Kind{ token.Assert, + token.Const, token.If, token.Import, token.Else, diff --git a/src/token/identifier.go b/src/token/identifier.go index 6b6823b..6973fc6 100644 --- a/src/token/identifier.go +++ b/src/token/identifier.go @@ -15,6 +15,8 @@ func identifier(tokens List, buffer []byte, i Position) (List, Position) { switch string(identifier) { case "assert": kind = Assert + case "const": + kind = Const case "if": kind = If case "else": diff --git a/tests/programs/const.q b/tests/programs/const.q new file mode 100644 index 0000000..c947f1a --- /dev/null +++ b/tests/programs/const.q @@ -0,0 +1,12 @@ +const num { + one 1 + two 2 + three 3 +} + +main() { + assert num.one == 1 + assert num.two == 2 + assert num.three == 3 + assert num.one + num.two == num.three +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index f3a13cb..23ec235 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -31,6 +31,7 @@ var programs = []struct { {"binary", "", "", 0}, {"octal", "", "", 0}, {"hexadecimal", "", "", 0}, + {"const", "", "", 0}, {"array", "", "", 0}, {"escape-rune", "", "", 0}, {"escape-string", "", "", 0}, From b3fec98bafee555f0d4b4ea8765c64713f4f4e18 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Feb 2025 18:23:33 +0100 Subject: [PATCH 0753/1012] Improved Windows ABI support --- examples/thread/thread.q | 2 -- lib/thread/thread_windows.q | 7 +++++++ src/asm/Number.go | 25 +++++++++++++++++++++++++ src/asmc/compile.go | 2 ++ src/asmc/dllCall.go | 7 +------ src/compiler/finalize.go | 2 ++ src/core/CallExtern.go | 8 ++++++++ src/core/CompileAssignArray.go | 2 +- src/core/CompileMemoryStore.go | 9 +++++++-- src/core/ExecuteLeaf.go | 2 +- src/core/ExpressionToMemory.go | 2 +- src/core/ExpressionToRegister.go | 2 +- src/core/Fold.go | 2 +- src/core/{Number.go => ToNumber.go} | 4 ++-- src/core/TokenToRegister.go | 2 +- src/register/Number.go | 8 ++++++++ src/x86/AlignStack.go | 6 ------ src/x86/Push.go | 27 ++++++++++++++++++++++++++- src/x86/Push_test.go | 22 ++++++++++++++++++++++ src/x86/Registers.go | 2 +- src/x86/x86_test.go | 1 - tests/programs/variables.q | 12 +++++++----- 22 files changed, 124 insertions(+), 32 deletions(-) create mode 100644 lib/thread/thread_windows.q create mode 100644 src/asm/Number.go rename src/core/{Number.go => ToNumber.go} (89%) create mode 100644 src/register/Number.go delete mode 100644 src/x86/AlignStack.go diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 22c9d05..8e33e07 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -1,7 +1,6 @@ import io import sys import thread -import time main() { thread.create(work) @@ -12,7 +11,6 @@ main() { work() { io.out("[ ] start\n") - time.sleep(10 * 1000 * 1000) io.out("[x] end\n") sys.exit(0) } \ No newline at end of file diff --git a/lib/thread/thread_windows.q b/lib/thread/thread_windows.q new file mode 100644 index 0000000..0faef0d --- /dev/null +++ b/lib/thread/thread_windows.q @@ -0,0 +1,7 @@ +extern kernel32 { + CreateThread(attributes Int, stackSize Int, address *Any, parameter Int) -> Int +} + +create(func *Any) -> Int { + return kernel32.CreateThread(0, 4096, func, 0) +} \ No newline at end of file diff --git a/src/asm/Number.go b/src/asm/Number.go new file mode 100644 index 0000000..768b232 --- /dev/null +++ b/src/asm/Number.go @@ -0,0 +1,25 @@ +package asm + +import ( + "fmt" +) + +// Number operates with just a number. +type Number struct { + Number int +} + +// String returns a human readable version. +func (data *Number) String() string { + return fmt.Sprintf("%d", data.Number) +} + +// Number adds an instruction with a number. +func (a *Assembler) Number(mnemonic Mnemonic, number int) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &Number{ + Number: number, + }, + }) +} diff --git a/src/asmc/compile.go b/src/asmc/compile.go index e671884..1e76cf4 100644 --- a/src/asmc/compile.go +++ b/src/asmc/compile.go @@ -123,6 +123,8 @@ func (c *compiler) compile(x asm.Instruction) { case asm.PUSH: switch operands := x.Data.(type) { + case *asm.Number: + c.code = x86.PushNumber(c.code, operands.Number) case *asm.Register: c.code = x86.PushRegister(c.code, operands.Register) } diff --git a/src/asmc/dllCall.go b/src/asmc/dllCall.go index 8c9856e..bd37582 100644 --- a/src/asmc/dllCall.go +++ b/src/asmc/dllCall.go @@ -9,15 +9,10 @@ import ( func (c *compiler) dllCall(x asm.Instruction) { size := 4 - // TODO: R15 could be in use. - c.code = x86.MoveRegisterRegister(c.code, x86.R15, x86.RSP) - c.code = x86.AlignStack(c.code) - c.code = x86.SubRegisterNumber(c.code, x86.RSP, 32) c.code = x86.CallAtAddress(c.code, 0x00_00_00_00) position := len(c.code) - size - c.code = x86.MoveRegisterRegister(c.code, x86.RSP, x86.R15) - label := x.Data.(*asm.Label) + pointer := &pointer{ Position: Address(position), OpSize: 2, diff --git a/src/compiler/finalize.go b/src/compiler/finalize.go index 343347e..dc21b8a 100644 --- a/src/compiler/finalize.go +++ b/src/compiler/finalize.go @@ -33,6 +33,7 @@ func (r *Result) finalize() { final.Syscall() case config.Windows: final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 0) + final.RegisterNumber(asm.AND, x86.RSP, -16) final.DLLCall("kernel32.ExitProcess") } @@ -67,6 +68,7 @@ func (r *Result) finalize() { final.Syscall() case config.Windows: final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 1) + final.RegisterNumber(asm.AND, x86.RSP, -16) final.DLLCall("kernel32.ExitProcess") } diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go index 57d3118..1413a45 100644 --- a/src/core/CallExtern.go +++ b/src/core/CallExtern.go @@ -32,7 +32,15 @@ func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) return nil, err } + f.Register(asm.PUSH, x86.RBP) + f.RegisterRegister(asm.MOVE, x86.RBP, x86.RSP) + f.RegisterNumber(asm.AND, x86.RSP, -16) + f.Number(asm.PUSH, 0) + f.Number(asm.PUSH, 0) + f.RegisterNumber(asm.SUB, x86.RSP, 32) f.DLLCall(fmt.Sprintf("%s.%s", fn.Package, fn.Name)) + f.RegisterRegister(asm.MOVE, x86.RSP, x86.RBP) + f.Register(asm.POP, x86.RBP) for _, register := range registers { f.FreeRegister(register) diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 2fee834..ec13b4c 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -34,7 +34,7 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { index := left.Children[1] if index.Token.IsNumeric() { - offset, err := f.Number(index.Token) + offset, err := f.ToNumber(index.Token) if err != nil { return err diff --git a/src/core/CompileMemoryStore.go b/src/core/CompileMemoryStore.go index 1c84e72..1277268 100644 --- a/src/core/CompileMemoryStore.go +++ b/src/core/CompileMemoryStore.go @@ -12,7 +12,12 @@ import ( func (f *Function) CompileMemoryStore(root *expression.Expression) error { parameters := root.Children[1:] name := parameters[0].Token.Text(f.File.Bytes) - numBytes, _ := f.Number(parameters[1].Token) + numBytes, err := f.ToNumber(parameters[1].Token) + + if err != nil { + return err + } + value := parameters[2] variable := f.VariableByName(name) @@ -28,6 +33,6 @@ func (f *Function) CompileMemoryStore(root *expression.Expression) error { Length: byte(numBytes), } - _, err := f.ExpressionToMemory(value, memory) + _, err = f.ExpressionToMemory(value, memory) return err } diff --git a/src/core/ExecuteLeaf.go b/src/core/ExecuteLeaf.go index 45cbe23..32feb13 100644 --- a/src/core/ExecuteLeaf.go +++ b/src/core/ExecuteLeaf.go @@ -21,7 +21,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope return f.ExecuteRegisterRegister(operation, register, variable.Register) case token.Number, token.Rune: - number, err := f.Number(operand) + number, err := f.ToNumber(operand) if err != nil { return err diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index e73fe91..21f01f6 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -30,7 +30,7 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me } if node.Token.IsNumeric() { - number, err := f.Number(node.Token) + number, err := f.ToNumber(node.Token) if err != nil { return nil, err diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index d1a85ec..b143fac 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -59,7 +59,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } if index.Token.IsNumeric() { - offset, err := f.Number(index.Token) + offset, err := f.ToNumber(index.Token) if err != nil { return nil, err diff --git a/src/core/Fold.go b/src/core/Fold.go index 983d42f..46615de 100644 --- a/src/core/Fold.go +++ b/src/core/Fold.go @@ -18,7 +18,7 @@ func (f *Function) Fold(expr *expression.Expression) error { if expr.IsLeaf() { if expr.Token.IsNumeric() { - value, err := f.Number(expr.Token) + value, err := f.ToNumber(expr.Token) expr.Value = value expr.IsFolded = true return err diff --git a/src/core/Number.go b/src/core/ToNumber.go similarity index 89% rename from src/core/Number.go rename to src/core/ToNumber.go index da39481..95d65a2 100644 --- a/src/core/Number.go +++ b/src/core/ToNumber.go @@ -9,8 +9,8 @@ import ( "git.akyoto.dev/cli/q/src/token" ) -// Number tries to convert the token into a numeric value. -func (f *Function) Number(t token.Token) (int, error) { +// ToNumber tries to convert the token into a numeric value. +func (f *Function) ToNumber(t token.Token) (int, error) { switch t.Kind { case token.Number: digits := t.Text(f.File.Bytes) diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index ed934cb..e8dd66e 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -34,7 +34,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) case token.Number, token.Rune: - number, err := f.Number(t) + number, err := f.ToNumber(t) if err != nil { return nil, err diff --git a/src/register/Number.go b/src/register/Number.go new file mode 100644 index 0000000..b2c1188 --- /dev/null +++ b/src/register/Number.go @@ -0,0 +1,8 @@ +package register + +import "git.akyoto.dev/cli/q/src/asm" + +func (f *Machine) Number(mnemonic asm.Mnemonic, number int) { + f.Assembler.Number(mnemonic, number) + f.postInstruction() +} diff --git a/src/x86/AlignStack.go b/src/x86/AlignStack.go deleted file mode 100644 index b2ce237..0000000 --- a/src/x86/AlignStack.go +++ /dev/null @@ -1,6 +0,0 @@ -package x86 - -// AlignStack aligns RSP on a 16-byte boundary. -func AlignStack(code []byte) []byte { - return append(code, 0x48, 0x83, 0xE4, 0xF0) -} diff --git a/src/x86/Push.go b/src/x86/Push.go index b9c82b0..4bd6e5a 100644 --- a/src/x86/Push.go +++ b/src/x86/Push.go @@ -1,6 +1,31 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import ( + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/sizeof" +) + +// PushNumber pushes a number onto the stack. +func PushNumber(code []byte, number int) []byte { + length := sizeof.Signed(number) + + if length >= 8 { + panic("x86 does not support pushing 64-bit numbers") + } + + if length >= 2 { + return append( + code, + 0x68, + byte(number), + byte(number>>8), + byte(number>>16), + byte(number>>24), + ) + } + + return append(code, 0x6A, byte(number)) +} // PushRegister pushes the value inside the register onto the stack. func PushRegister(code []byte, register cpu.Register) []byte { diff --git a/src/x86/Push_test.go b/src/x86/Push_test.go index 29cedac..3552fc3 100644 --- a/src/x86/Push_test.go +++ b/src/x86/Push_test.go @@ -8,6 +8,28 @@ import ( "git.akyoto.dev/go/assert" ) +func TestPushNumber(t *testing.T) { + usagePatterns := []struct { + Number int + Code []byte + }{ + {0, []byte{0x6A, 0x00}}, + {1, []byte{0x6A, 0x01}}, + {-1, []byte{0x6A, 0xFF}}, + {127, []byte{0x6A, 0x7F}}, + {128, []byte{0x68, 0x80, 0x00, 0x00, 0x00}}, + {0xFF, []byte{0x68, 0xFF, 0x00, 0x00, 0x00}}, + {0xFFFF, []byte{0x68, 0xFF, 0xFF, 0x00, 0x00}}, + {0x7FFFFFFF, []byte{0x68, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("push %d", pattern.Number) + code := x86.PushNumber(nil, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + func TestPushRegister(t *testing.T) { usagePatterns := []struct { Register cpu.Register diff --git a/src/x86/Registers.go b/src/x86/Registers.go index 090a678..9ed0df0 100644 --- a/src/x86/Registers.go +++ b/src/x86/Registers.go @@ -25,7 +25,7 @@ var ( AllRegisters = []cpu.Register{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15} SyscallInputRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} SyscallOutputRegisters = []cpu.Register{RAX, RCX, R11} - GeneralRegisters = []cpu.Register{RBX, R12, R13, R14, R15, RCX, R11, RBP} + GeneralRegisters = []cpu.Register{RBX, R12, R13, R14, R15, RCX, R11} InputRegisters = SyscallInputRegisters OutputRegisters = SyscallInputRegisters WindowsInputRegisters = []cpu.Register{RCX, RDX, R8, R9} diff --git a/src/x86/x86_test.go b/src/x86/x86_test.go index 12efd4d..393b2fb 100644 --- a/src/x86/x86_test.go +++ b/src/x86/x86_test.go @@ -8,7 +8,6 @@ import ( ) func TestX86(t *testing.T) { - assert.DeepEqual(t, x86.AlignStack(nil), []byte{0x48, 0x83, 0xE4, 0xF0}) assert.DeepEqual(t, x86.Call(nil, 1), []byte{0xE8, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x86.CallAtAddress(nil, 1), []byte{0xFF, 0x15, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x86.ExtendRAXToRDX(nil), []byte{0x48, 0x99}) diff --git a/tests/programs/variables.q b/tests/programs/variables.q index aa461ab..8334626 100644 --- a/tests/programs/variables.q +++ b/tests/programs/variables.q @@ -6,10 +6,12 @@ main() { e := 5 f := 6 g := 7 - h := 8 - assert a != b - assert c != d - assert e != f - assert g != h + assert a != 0 + assert b != 0 + assert c != 0 + assert d != 0 + assert e != 0 + assert f != 0 + assert g != 0 } \ No newline at end of file From d0bcd8cf9f54a2b60eda5d008f2a721ab667ccf2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Feb 2025 18:23:33 +0100 Subject: [PATCH 0754/1012] Improved Windows ABI support --- examples/thread/thread.q | 2 -- lib/thread/thread_windows.q | 7 +++++++ src/asm/Number.go | 25 +++++++++++++++++++++++++ src/asmc/compile.go | 2 ++ src/asmc/dllCall.go | 7 +------ src/compiler/finalize.go | 2 ++ src/core/CallExtern.go | 8 ++++++++ src/core/CompileAssignArray.go | 2 +- src/core/CompileMemoryStore.go | 9 +++++++-- src/core/ExecuteLeaf.go | 2 +- src/core/ExpressionToMemory.go | 2 +- src/core/ExpressionToRegister.go | 2 +- src/core/Fold.go | 2 +- src/core/{Number.go => ToNumber.go} | 4 ++-- src/core/TokenToRegister.go | 2 +- src/register/Number.go | 8 ++++++++ src/x86/AlignStack.go | 6 ------ src/x86/Push.go | 27 ++++++++++++++++++++++++++- src/x86/Push_test.go | 22 ++++++++++++++++++++++ src/x86/Registers.go | 2 +- src/x86/x86_test.go | 1 - tests/programs/variables.q | 12 +++++++----- 22 files changed, 124 insertions(+), 32 deletions(-) create mode 100644 lib/thread/thread_windows.q create mode 100644 src/asm/Number.go rename src/core/{Number.go => ToNumber.go} (89%) create mode 100644 src/register/Number.go delete mode 100644 src/x86/AlignStack.go diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 22c9d05..8e33e07 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -1,7 +1,6 @@ import io import sys import thread -import time main() { thread.create(work) @@ -12,7 +11,6 @@ main() { work() { io.out("[ ] start\n") - time.sleep(10 * 1000 * 1000) io.out("[x] end\n") sys.exit(0) } \ No newline at end of file diff --git a/lib/thread/thread_windows.q b/lib/thread/thread_windows.q new file mode 100644 index 0000000..0faef0d --- /dev/null +++ b/lib/thread/thread_windows.q @@ -0,0 +1,7 @@ +extern kernel32 { + CreateThread(attributes Int, stackSize Int, address *Any, parameter Int) -> Int +} + +create(func *Any) -> Int { + return kernel32.CreateThread(0, 4096, func, 0) +} \ No newline at end of file diff --git a/src/asm/Number.go b/src/asm/Number.go new file mode 100644 index 0000000..768b232 --- /dev/null +++ b/src/asm/Number.go @@ -0,0 +1,25 @@ +package asm + +import ( + "fmt" +) + +// Number operates with just a number. +type Number struct { + Number int +} + +// String returns a human readable version. +func (data *Number) String() string { + return fmt.Sprintf("%d", data.Number) +} + +// Number adds an instruction with a number. +func (a *Assembler) Number(mnemonic Mnemonic, number int) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &Number{ + Number: number, + }, + }) +} diff --git a/src/asmc/compile.go b/src/asmc/compile.go index e671884..1e76cf4 100644 --- a/src/asmc/compile.go +++ b/src/asmc/compile.go @@ -123,6 +123,8 @@ func (c *compiler) compile(x asm.Instruction) { case asm.PUSH: switch operands := x.Data.(type) { + case *asm.Number: + c.code = x86.PushNumber(c.code, operands.Number) case *asm.Register: c.code = x86.PushRegister(c.code, operands.Register) } diff --git a/src/asmc/dllCall.go b/src/asmc/dllCall.go index 8c9856e..bd37582 100644 --- a/src/asmc/dllCall.go +++ b/src/asmc/dllCall.go @@ -9,15 +9,10 @@ import ( func (c *compiler) dllCall(x asm.Instruction) { size := 4 - // TODO: R15 could be in use. - c.code = x86.MoveRegisterRegister(c.code, x86.R15, x86.RSP) - c.code = x86.AlignStack(c.code) - c.code = x86.SubRegisterNumber(c.code, x86.RSP, 32) c.code = x86.CallAtAddress(c.code, 0x00_00_00_00) position := len(c.code) - size - c.code = x86.MoveRegisterRegister(c.code, x86.RSP, x86.R15) - label := x.Data.(*asm.Label) + pointer := &pointer{ Position: Address(position), OpSize: 2, diff --git a/src/compiler/finalize.go b/src/compiler/finalize.go index 343347e..dc21b8a 100644 --- a/src/compiler/finalize.go +++ b/src/compiler/finalize.go @@ -33,6 +33,7 @@ func (r *Result) finalize() { final.Syscall() case config.Windows: final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 0) + final.RegisterNumber(asm.AND, x86.RSP, -16) final.DLLCall("kernel32.ExitProcess") } @@ -67,6 +68,7 @@ func (r *Result) finalize() { final.Syscall() case config.Windows: final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 1) + final.RegisterNumber(asm.AND, x86.RSP, -16) final.DLLCall("kernel32.ExitProcess") } diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go index 57d3118..1413a45 100644 --- a/src/core/CallExtern.go +++ b/src/core/CallExtern.go @@ -32,7 +32,15 @@ func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) return nil, err } + f.Register(asm.PUSH, x86.RBP) + f.RegisterRegister(asm.MOVE, x86.RBP, x86.RSP) + f.RegisterNumber(asm.AND, x86.RSP, -16) + f.Number(asm.PUSH, 0) + f.Number(asm.PUSH, 0) + f.RegisterNumber(asm.SUB, x86.RSP, 32) f.DLLCall(fmt.Sprintf("%s.%s", fn.Package, fn.Name)) + f.RegisterRegister(asm.MOVE, x86.RSP, x86.RBP) + f.Register(asm.POP, x86.RBP) for _, register := range registers { f.FreeRegister(register) diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 2fee834..ec13b4c 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -34,7 +34,7 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { index := left.Children[1] if index.Token.IsNumeric() { - offset, err := f.Number(index.Token) + offset, err := f.ToNumber(index.Token) if err != nil { return err diff --git a/src/core/CompileMemoryStore.go b/src/core/CompileMemoryStore.go index 1c84e72..1277268 100644 --- a/src/core/CompileMemoryStore.go +++ b/src/core/CompileMemoryStore.go @@ -12,7 +12,12 @@ import ( func (f *Function) CompileMemoryStore(root *expression.Expression) error { parameters := root.Children[1:] name := parameters[0].Token.Text(f.File.Bytes) - numBytes, _ := f.Number(parameters[1].Token) + numBytes, err := f.ToNumber(parameters[1].Token) + + if err != nil { + return err + } + value := parameters[2] variable := f.VariableByName(name) @@ -28,6 +33,6 @@ func (f *Function) CompileMemoryStore(root *expression.Expression) error { Length: byte(numBytes), } - _, err := f.ExpressionToMemory(value, memory) + _, err = f.ExpressionToMemory(value, memory) return err } diff --git a/src/core/ExecuteLeaf.go b/src/core/ExecuteLeaf.go index 45cbe23..32feb13 100644 --- a/src/core/ExecuteLeaf.go +++ b/src/core/ExecuteLeaf.go @@ -21,7 +21,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope return f.ExecuteRegisterRegister(operation, register, variable.Register) case token.Number, token.Rune: - number, err := f.Number(operand) + number, err := f.ToNumber(operand) if err != nil { return err diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index e73fe91..21f01f6 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -30,7 +30,7 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me } if node.Token.IsNumeric() { - number, err := f.Number(node.Token) + number, err := f.ToNumber(node.Token) if err != nil { return nil, err diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index d1a85ec..b143fac 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -59,7 +59,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } if index.Token.IsNumeric() { - offset, err := f.Number(index.Token) + offset, err := f.ToNumber(index.Token) if err != nil { return nil, err diff --git a/src/core/Fold.go b/src/core/Fold.go index 983d42f..46615de 100644 --- a/src/core/Fold.go +++ b/src/core/Fold.go @@ -18,7 +18,7 @@ func (f *Function) Fold(expr *expression.Expression) error { if expr.IsLeaf() { if expr.Token.IsNumeric() { - value, err := f.Number(expr.Token) + value, err := f.ToNumber(expr.Token) expr.Value = value expr.IsFolded = true return err diff --git a/src/core/Number.go b/src/core/ToNumber.go similarity index 89% rename from src/core/Number.go rename to src/core/ToNumber.go index da39481..95d65a2 100644 --- a/src/core/Number.go +++ b/src/core/ToNumber.go @@ -9,8 +9,8 @@ import ( "git.akyoto.dev/cli/q/src/token" ) -// Number tries to convert the token into a numeric value. -func (f *Function) Number(t token.Token) (int, error) { +// ToNumber tries to convert the token into a numeric value. +func (f *Function) ToNumber(t token.Token) (int, error) { switch t.Kind { case token.Number: digits := t.Text(f.File.Bytes) diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index ed934cb..e8dd66e 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -34,7 +34,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) case token.Number, token.Rune: - number, err := f.Number(t) + number, err := f.ToNumber(t) if err != nil { return nil, err diff --git a/src/register/Number.go b/src/register/Number.go new file mode 100644 index 0000000..b2c1188 --- /dev/null +++ b/src/register/Number.go @@ -0,0 +1,8 @@ +package register + +import "git.akyoto.dev/cli/q/src/asm" + +func (f *Machine) Number(mnemonic asm.Mnemonic, number int) { + f.Assembler.Number(mnemonic, number) + f.postInstruction() +} diff --git a/src/x86/AlignStack.go b/src/x86/AlignStack.go deleted file mode 100644 index b2ce237..0000000 --- a/src/x86/AlignStack.go +++ /dev/null @@ -1,6 +0,0 @@ -package x86 - -// AlignStack aligns RSP on a 16-byte boundary. -func AlignStack(code []byte) []byte { - return append(code, 0x48, 0x83, 0xE4, 0xF0) -} diff --git a/src/x86/Push.go b/src/x86/Push.go index b9c82b0..4bd6e5a 100644 --- a/src/x86/Push.go +++ b/src/x86/Push.go @@ -1,6 +1,31 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import ( + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/sizeof" +) + +// PushNumber pushes a number onto the stack. +func PushNumber(code []byte, number int) []byte { + length := sizeof.Signed(number) + + if length >= 8 { + panic("x86 does not support pushing 64-bit numbers") + } + + if length >= 2 { + return append( + code, + 0x68, + byte(number), + byte(number>>8), + byte(number>>16), + byte(number>>24), + ) + } + + return append(code, 0x6A, byte(number)) +} // PushRegister pushes the value inside the register onto the stack. func PushRegister(code []byte, register cpu.Register) []byte { diff --git a/src/x86/Push_test.go b/src/x86/Push_test.go index 29cedac..3552fc3 100644 --- a/src/x86/Push_test.go +++ b/src/x86/Push_test.go @@ -8,6 +8,28 @@ import ( "git.akyoto.dev/go/assert" ) +func TestPushNumber(t *testing.T) { + usagePatterns := []struct { + Number int + Code []byte + }{ + {0, []byte{0x6A, 0x00}}, + {1, []byte{0x6A, 0x01}}, + {-1, []byte{0x6A, 0xFF}}, + {127, []byte{0x6A, 0x7F}}, + {128, []byte{0x68, 0x80, 0x00, 0x00, 0x00}}, + {0xFF, []byte{0x68, 0xFF, 0x00, 0x00, 0x00}}, + {0xFFFF, []byte{0x68, 0xFF, 0xFF, 0x00, 0x00}}, + {0x7FFFFFFF, []byte{0x68, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("push %d", pattern.Number) + code := x86.PushNumber(nil, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + func TestPushRegister(t *testing.T) { usagePatterns := []struct { Register cpu.Register diff --git a/src/x86/Registers.go b/src/x86/Registers.go index 090a678..9ed0df0 100644 --- a/src/x86/Registers.go +++ b/src/x86/Registers.go @@ -25,7 +25,7 @@ var ( AllRegisters = []cpu.Register{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15} SyscallInputRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} SyscallOutputRegisters = []cpu.Register{RAX, RCX, R11} - GeneralRegisters = []cpu.Register{RBX, R12, R13, R14, R15, RCX, R11, RBP} + GeneralRegisters = []cpu.Register{RBX, R12, R13, R14, R15, RCX, R11} InputRegisters = SyscallInputRegisters OutputRegisters = SyscallInputRegisters WindowsInputRegisters = []cpu.Register{RCX, RDX, R8, R9} diff --git a/src/x86/x86_test.go b/src/x86/x86_test.go index 12efd4d..393b2fb 100644 --- a/src/x86/x86_test.go +++ b/src/x86/x86_test.go @@ -8,7 +8,6 @@ import ( ) func TestX86(t *testing.T) { - assert.DeepEqual(t, x86.AlignStack(nil), []byte{0x48, 0x83, 0xE4, 0xF0}) assert.DeepEqual(t, x86.Call(nil, 1), []byte{0xE8, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x86.CallAtAddress(nil, 1), []byte{0xFF, 0x15, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x86.ExtendRAXToRDX(nil), []byte{0x48, 0x99}) diff --git a/tests/programs/variables.q b/tests/programs/variables.q index aa461ab..8334626 100644 --- a/tests/programs/variables.q +++ b/tests/programs/variables.q @@ -6,10 +6,12 @@ main() { e := 5 f := 6 g := 7 - h := 8 - assert a != b - assert c != d - assert e != f - assert g != h + assert a != 0 + assert b != 0 + assert c != 0 + assert d != 0 + assert e != 0 + assert f != 0 + assert g != 0 } \ No newline at end of file From 0ddaeca84402c2ff87e9b08b7260e4f2c4b436e8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 11:31:31 +0100 Subject: [PATCH 0755/1012] Improved thread creation on Linux --- examples/thread/thread.q | 2 -- lib/thread/thread_linux.q | 19 ++++++++++++++++--- src/compiler/finalize.go | 1 + src/core/ExpressionToMemory.go | 5 +++++ src/core/Fold.go | 17 +++++++++++++++++ 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 8e33e07..6d0493b 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -1,5 +1,4 @@ import io -import sys import thread main() { @@ -12,5 +11,4 @@ main() { work() { io.out("[ ] start\n") io.out("[x] end\n") - sys.exit(0) } \ No newline at end of file diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index be70f4f..5179bed 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -1,9 +1,22 @@ import sys +const clone { + vm 0x100 + fs 0x200 + files 0x400 + sighand 0x800 + parent 0x8000 + thread 0x10000 + io 0x80000000 +} + create(func *Any) -> Int { size := 4096 stack := sys.mmap(0, size, 0x1|0x2, 0x02|0x20|0x100|0x20000) - rip := stack + size - 8 - store(rip, 8, func) - return sys.clone(0x100|0x200|0x400|0x800|0x8000|0x10000|0x80000000, rip) + stack += size + stack -= 8 + store(stack, 8, _exit) + stack -= 8 + store(stack, 8, func) + return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack) } \ No newline at end of file diff --git a/src/compiler/finalize.go b/src/compiler/finalize.go index dc21b8a..c317754 100644 --- a/src/compiler/finalize.go +++ b/src/compiler/finalize.go @@ -21,6 +21,7 @@ func (r *Result) finalize() { } final.Call("main.main") + final.Label(asm.LABEL, "_exit") switch config.TargetOS { case config.Linux: diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index 21f01f6..461cc1c 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -26,6 +26,11 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me return types.AnyPointer, nil } + if name == "_exit" { + f.MemoryLabel(asm.STORE, memory, "_exit") + return types.AnyPointer, nil + } + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Token.Position) } diff --git a/src/core/Fold.go b/src/core/Fold.go index 46615de..eaaf0cd 100644 --- a/src/core/Fold.go +++ b/src/core/Fold.go @@ -27,6 +27,23 @@ func (f *Function) Fold(expr *expression.Expression) error { return nil } + if expr.Token.Kind == token.Period { + left := expr.Children[0] + leftText := left.Token.Text(f.File.Bytes) + right := expr.Children[1] + rightText := right.Token.Text(f.File.Bytes) + constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] + + if !isConst { + return nil + } + + value, err := f.ToNumber(constant.Value) + expr.Value = value + expr.IsFolded = true + return err + } + canFold := true for _, child := range expr.Children { From 00be603b8f3850a3a846730d9235a3267fc84afa Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 11:31:31 +0100 Subject: [PATCH 0756/1012] Improved thread creation on Linux --- examples/thread/thread.q | 2 -- lib/thread/thread_linux.q | 19 ++++++++++++++++--- src/compiler/finalize.go | 1 + src/core/ExpressionToMemory.go | 5 +++++ src/core/Fold.go | 17 +++++++++++++++++ 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 8e33e07..6d0493b 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -1,5 +1,4 @@ import io -import sys import thread main() { @@ -12,5 +11,4 @@ main() { work() { io.out("[ ] start\n") io.out("[x] end\n") - sys.exit(0) } \ No newline at end of file diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index be70f4f..5179bed 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -1,9 +1,22 @@ import sys +const clone { + vm 0x100 + fs 0x200 + files 0x400 + sighand 0x800 + parent 0x8000 + thread 0x10000 + io 0x80000000 +} + create(func *Any) -> Int { size := 4096 stack := sys.mmap(0, size, 0x1|0x2, 0x02|0x20|0x100|0x20000) - rip := stack + size - 8 - store(rip, 8, func) - return sys.clone(0x100|0x200|0x400|0x800|0x8000|0x10000|0x80000000, rip) + stack += size + stack -= 8 + store(stack, 8, _exit) + stack -= 8 + store(stack, 8, func) + return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack) } \ No newline at end of file diff --git a/src/compiler/finalize.go b/src/compiler/finalize.go index dc21b8a..c317754 100644 --- a/src/compiler/finalize.go +++ b/src/compiler/finalize.go @@ -21,6 +21,7 @@ func (r *Result) finalize() { } final.Call("main.main") + final.Label(asm.LABEL, "_exit") switch config.TargetOS { case config.Linux: diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index 21f01f6..461cc1c 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -26,6 +26,11 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me return types.AnyPointer, nil } + if name == "_exit" { + f.MemoryLabel(asm.STORE, memory, "_exit") + return types.AnyPointer, nil + } + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Token.Position) } diff --git a/src/core/Fold.go b/src/core/Fold.go index 46615de..eaaf0cd 100644 --- a/src/core/Fold.go +++ b/src/core/Fold.go @@ -27,6 +27,23 @@ func (f *Function) Fold(expr *expression.Expression) error { return nil } + if expr.Token.Kind == token.Period { + left := expr.Children[0] + leftText := left.Token.Text(f.File.Bytes) + right := expr.Children[1] + rightText := right.Token.Text(f.File.Bytes) + constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] + + if !isConst { + return nil + } + + value, err := f.ToNumber(constant.Value) + expr.Value = value + expr.IsFolded = true + return err + } + canFold := true for _, child := range expr.Children { From 9da25da525f9095999b8cad8b8f6088a017a85a7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 15:46:54 +0100 Subject: [PATCH 0757/1012] Added a program init function --- lib/core/core_linux.q | 8 ++++++++ lib/core/core_mac.q | 8 ++++++++ lib/core/core_windows.q | 20 ++++++++++++++++++++ lib/thread/thread_linux.q | 3 ++- src/compiler/Compile.go | 8 ++++++++ src/compiler/Result.go | 1 + src/compiler/finalize.go | 29 +++-------------------------- src/core/CompileCall.go | 5 +++++ src/core/ExpressionToRegister.go | 32 +++++++++++++++++++++----------- src/errors/Common.go | 1 + src/scanner/Scan.go | 1 + 11 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 lib/core/core_linux.q create mode 100644 lib/core/core_mac.q create mode 100644 lib/core/core_windows.q diff --git a/lib/core/core_linux.q b/lib/core/core_linux.q new file mode 100644 index 0000000..41e7100 --- /dev/null +++ b/lib/core/core_linux.q @@ -0,0 +1,8 @@ +init() { + main.main() + exit() +} + +exit() { + syscall(60, 0) +} \ No newline at end of file diff --git a/lib/core/core_mac.q b/lib/core/core_mac.q new file mode 100644 index 0000000..875a04a --- /dev/null +++ b/lib/core/core_mac.q @@ -0,0 +1,8 @@ +init() { + main.main() + exit() +} + +exit() { + syscall(0x2000001, 0) +} \ No newline at end of file diff --git a/lib/core/core_windows.q b/lib/core/core_windows.q new file mode 100644 index 0000000..2b50ec1 --- /dev/null +++ b/lib/core/core_windows.q @@ -0,0 +1,20 @@ +extern kernel32 { + SetConsoleCP(cp UInt) + SetConsoleOutputCP(cp UInt) + ExitProcess(code UInt) +} + +const cp { + utf8 65001 +} + +init() { + kernel32.SetConsoleCP(cp.utf8) + kernel32.SetConsoleOutputCP(cp.utf8) + main.main() + exit() +} + +exit() { + kernel32.ExitProcess(0) +} \ No newline at end of file diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index 5179bed..98b4d6f 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -1,3 +1,4 @@ +import core import sys const clone { @@ -15,7 +16,7 @@ create(func *Any) -> Int { stack := sys.mmap(0, size, 0x1|0x2, 0x02|0x20|0x100|0x20000) stack += size stack -= 8 - store(stack, 8, _exit) + store(stack, 8, core.exit) stack -= 8 store(stack, 8, func) return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack) diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index f964d78..749b71b 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -98,6 +98,13 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < } } + // Check for existence of `init` + init, exists := allFunctions["core.init"] + + if !exists { + return result, errors.MissingInitFunction + } + // Check for existence of `main` main, exists := allFunctions["main.main"] @@ -105,6 +112,7 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < return result, errors.MissingMainFunction } + result.Init = init result.Main = main result.Functions = allFunctions result.finalize() diff --git a/src/compiler/Result.go b/src/compiler/Result.go index a0d6091..5c96bf3 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -7,6 +7,7 @@ import ( // Result contains everything we need to write an executable file to disk. type Result struct { + Init *core.Function Main *core.Function Functions map[string]*core.Function Traversed map[*core.Function]bool diff --git a/src/compiler/finalize.go b/src/compiler/finalize.go index c317754..e82a049 100644 --- a/src/compiler/finalize.go +++ b/src/compiler/finalize.go @@ -5,7 +5,6 @@ import ( "git.akyoto.dev/cli/q/src/asmc" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/x86" ) @@ -20,33 +19,11 @@ func (r *Result) finalize() { Data: make(map[string][]byte, r.DataCount), } - final.Call("main.main") - final.Label(asm.LABEL, "_exit") - - switch config.TargetOS { - case config.Linux: - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], LinuxExit) - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 0) - final.Syscall() - case config.Mac: - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], MacExit) - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 0) - final.Syscall() - case config.Windows: - final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 0) - final.RegisterNumber(asm.AND, x86.RSP, -16) - final.DLLCall("kernel32.ExitProcess") - } - - r.DLLs = dll.List{ - {Name: "kernel32", Functions: []string{"ExitProcess"}}, - } - r.Traversed = make(map[*core.Function]bool, len(r.Functions)) - // This will place the main function immediately after the entry point - // and also add everything the main function calls recursively. - r.eachFunction(r.Main, r.Traversed, func(f *core.Function) { + // This will place the init function immediately after the entry point + // and also add everything the init function calls recursively. + r.eachFunction(r.Init, r.Traversed, func(f *core.Function) { final.Merge(f.Assembler) for _, library := range f.DLLs { diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 4973ce2..ee320f2 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -44,6 +44,11 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error name = nameNode.Token.Text(f.File.Bytes) } + if f.UniqueName == "core.init" && pkg == "main" && name == "main" { + f.Call("main.main") + return nil, nil + } + fn, exists = f.Functions[pkg+"."+name] if !exists { diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index b143fac..d2b0cae 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "math" "git.akyoto.dev/cli/q/src/asm" @@ -91,19 +92,28 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp rightText := right.Token.Text(f.File.Bytes) variable := f.VariableByName(leftText) - if variable == nil { - constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] - - if isConst { - return f.TokenToRegister(constant.Value, register) - } - - return nil, errors.New(&errors.UnknownIdentifier{Name: leftText}, f.File, left.Token.Position) + if variable != nil { + field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: int8(field.Offset), Length: byte(field.Type.Size())}, register) + return field.Type, nil } - field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: int8(field.Offset), Length: byte(field.Type.Size())}, register) - return field.Type, nil + constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] + + if isConst { + return f.TokenToRegister(constant.Value, register) + } + + uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) + function, exists := f.Functions[uniqueName] + + if exists { + f.File.Imports[leftText].Used = true + f.RegisterLabel(asm.MOVE, register, function.UniqueName) + return types.AnyPointer, nil + } + + return nil, errors.New(&errors.UnknownIdentifier{Name: leftText}, f.File, left.Token.Position) } if len(node.Children) == 1 { diff --git a/src/errors/Common.go b/src/errors/Common.go index 04638d8..3b1d665 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -17,6 +17,7 @@ var ( MissingExpression = &Base{"Missing expression"} MissingGroupStart = &Base{"Missing '('"} MissingGroupEnd = &Base{"Missing ')'"} + MissingInitFunction = &Base{"Missing init function"} MissingMainFunction = &Base{"Missing main function"} MissingOperand = &Base{"Missing operand"} MissingParameter = &Base{"Missing parameter"} diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go index f745619..4295972 100644 --- a/src/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -20,6 +20,7 @@ func Scan(files []string) (chan *core.Constant, <-chan *fs.File, <-chan *core.Fu } go func() { + scanner.queueDirectory(filepath.Join(config.Library, "core"), "core") scanner.queueDirectory(filepath.Join(config.Library, "mem"), "mem") scanner.queue(files...) scanner.group.Wait() From 9f125694bec8a33eb800f93ccfd8a267654ae3f6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 15:46:54 +0100 Subject: [PATCH 0758/1012] Added a program init function --- lib/core/core_linux.q | 8 ++++++++ lib/core/core_mac.q | 8 ++++++++ lib/core/core_windows.q | 20 ++++++++++++++++++++ lib/thread/thread_linux.q | 3 ++- src/compiler/Compile.go | 8 ++++++++ src/compiler/Result.go | 1 + src/compiler/finalize.go | 29 +++-------------------------- src/core/CompileCall.go | 5 +++++ src/core/ExpressionToRegister.go | 32 +++++++++++++++++++++----------- src/errors/Common.go | 1 + src/scanner/Scan.go | 1 + 11 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 lib/core/core_linux.q create mode 100644 lib/core/core_mac.q create mode 100644 lib/core/core_windows.q diff --git a/lib/core/core_linux.q b/lib/core/core_linux.q new file mode 100644 index 0000000..41e7100 --- /dev/null +++ b/lib/core/core_linux.q @@ -0,0 +1,8 @@ +init() { + main.main() + exit() +} + +exit() { + syscall(60, 0) +} \ No newline at end of file diff --git a/lib/core/core_mac.q b/lib/core/core_mac.q new file mode 100644 index 0000000..875a04a --- /dev/null +++ b/lib/core/core_mac.q @@ -0,0 +1,8 @@ +init() { + main.main() + exit() +} + +exit() { + syscall(0x2000001, 0) +} \ No newline at end of file diff --git a/lib/core/core_windows.q b/lib/core/core_windows.q new file mode 100644 index 0000000..2b50ec1 --- /dev/null +++ b/lib/core/core_windows.q @@ -0,0 +1,20 @@ +extern kernel32 { + SetConsoleCP(cp UInt) + SetConsoleOutputCP(cp UInt) + ExitProcess(code UInt) +} + +const cp { + utf8 65001 +} + +init() { + kernel32.SetConsoleCP(cp.utf8) + kernel32.SetConsoleOutputCP(cp.utf8) + main.main() + exit() +} + +exit() { + kernel32.ExitProcess(0) +} \ No newline at end of file diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index 5179bed..98b4d6f 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -1,3 +1,4 @@ +import core import sys const clone { @@ -15,7 +16,7 @@ create(func *Any) -> Int { stack := sys.mmap(0, size, 0x1|0x2, 0x02|0x20|0x100|0x20000) stack += size stack -= 8 - store(stack, 8, _exit) + store(stack, 8, core.exit) stack -= 8 store(stack, 8, func) return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack) diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index f964d78..749b71b 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -98,6 +98,13 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < } } + // Check for existence of `init` + init, exists := allFunctions["core.init"] + + if !exists { + return result, errors.MissingInitFunction + } + // Check for existence of `main` main, exists := allFunctions["main.main"] @@ -105,6 +112,7 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < return result, errors.MissingMainFunction } + result.Init = init result.Main = main result.Functions = allFunctions result.finalize() diff --git a/src/compiler/Result.go b/src/compiler/Result.go index a0d6091..5c96bf3 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -7,6 +7,7 @@ import ( // Result contains everything we need to write an executable file to disk. type Result struct { + Init *core.Function Main *core.Function Functions map[string]*core.Function Traversed map[*core.Function]bool diff --git a/src/compiler/finalize.go b/src/compiler/finalize.go index c317754..e82a049 100644 --- a/src/compiler/finalize.go +++ b/src/compiler/finalize.go @@ -5,7 +5,6 @@ import ( "git.akyoto.dev/cli/q/src/asmc" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/x86" ) @@ -20,33 +19,11 @@ func (r *Result) finalize() { Data: make(map[string][]byte, r.DataCount), } - final.Call("main.main") - final.Label(asm.LABEL, "_exit") - - switch config.TargetOS { - case config.Linux: - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], LinuxExit) - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 0) - final.Syscall() - case config.Mac: - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], MacExit) - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 0) - final.Syscall() - case config.Windows: - final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 0) - final.RegisterNumber(asm.AND, x86.RSP, -16) - final.DLLCall("kernel32.ExitProcess") - } - - r.DLLs = dll.List{ - {Name: "kernel32", Functions: []string{"ExitProcess"}}, - } - r.Traversed = make(map[*core.Function]bool, len(r.Functions)) - // This will place the main function immediately after the entry point - // and also add everything the main function calls recursively. - r.eachFunction(r.Main, r.Traversed, func(f *core.Function) { + // This will place the init function immediately after the entry point + // and also add everything the init function calls recursively. + r.eachFunction(r.Init, r.Traversed, func(f *core.Function) { final.Merge(f.Assembler) for _, library := range f.DLLs { diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 4973ce2..ee320f2 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -44,6 +44,11 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error name = nameNode.Token.Text(f.File.Bytes) } + if f.UniqueName == "core.init" && pkg == "main" && name == "main" { + f.Call("main.main") + return nil, nil + } + fn, exists = f.Functions[pkg+"."+name] if !exists { diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index b143fac..d2b0cae 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "math" "git.akyoto.dev/cli/q/src/asm" @@ -91,19 +92,28 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp rightText := right.Token.Text(f.File.Bytes) variable := f.VariableByName(leftText) - if variable == nil { - constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] - - if isConst { - return f.TokenToRegister(constant.Value, register) - } - - return nil, errors.New(&errors.UnknownIdentifier{Name: leftText}, f.File, left.Token.Position) + if variable != nil { + field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: int8(field.Offset), Length: byte(field.Type.Size())}, register) + return field.Type, nil } - field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: int8(field.Offset), Length: byte(field.Type.Size())}, register) - return field.Type, nil + constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] + + if isConst { + return f.TokenToRegister(constant.Value, register) + } + + uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) + function, exists := f.Functions[uniqueName] + + if exists { + f.File.Imports[leftText].Used = true + f.RegisterLabel(asm.MOVE, register, function.UniqueName) + return types.AnyPointer, nil + } + + return nil, errors.New(&errors.UnknownIdentifier{Name: leftText}, f.File, left.Token.Position) } if len(node.Children) == 1 { diff --git a/src/errors/Common.go b/src/errors/Common.go index 04638d8..3b1d665 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -17,6 +17,7 @@ var ( MissingExpression = &Base{"Missing expression"} MissingGroupStart = &Base{"Missing '('"} MissingGroupEnd = &Base{"Missing ')'"} + MissingInitFunction = &Base{"Missing init function"} MissingMainFunction = &Base{"Missing main function"} MissingOperand = &Base{"Missing operand"} MissingParameter = &Base{"Missing parameter"} diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go index f745619..4295972 100644 --- a/src/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -20,6 +20,7 @@ func Scan(files []string) (chan *core.Constant, <-chan *fs.File, <-chan *core.Fu } go func() { + scanner.queueDirectory(filepath.Join(config.Library, "core"), "core") scanner.queueDirectory(filepath.Join(config.Library, "mem"), "mem") scanner.queue(files...) scanner.group.Wait() From e7370384417512dee9b999723fb6e200529695b1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 17:30:03 +0100 Subject: [PATCH 0759/1012] Updated dependencies --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c74451e..baf1b38 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.24 require ( git.akyoto.dev/go/assert v0.1.3 - git.akyoto.dev/go/color v0.1.2 + git.akyoto.dev/go/color v0.1.3 ) require golang.org/x/sys v0.30.0 // indirect diff --git a/go.sum b/go.sum index 439aeca..236fc35 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= -git.akyoto.dev/go/color v0.1.2 h1:aNJP3upGimmhtV576Hxa4FZhU8GfnPuI5ZYZpVWsFLI= -git.akyoto.dev/go/color v0.1.2/go.mod h1:e00cRnX0fzFyIYEpAA7dCR/Hlk0/2YpXpVQaMT5Zoss= +git.akyoto.dev/go/color v0.1.3 h1:kqOVYaPJJDHi8qEwTZkZecQaUENBToaLmiiV1Gg++GM= +git.akyoto.dev/go/color v0.1.3/go.mod h1:e00cRnX0fzFyIYEpAA7dCR/Hlk0/2YpXpVQaMT5Zoss= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= From eeb8f86e46087aba6ef7af157577a07328aa8310 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 17:30:03 +0100 Subject: [PATCH 0760/1012] Updated dependencies --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c74451e..baf1b38 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.24 require ( git.akyoto.dev/go/assert v0.1.3 - git.akyoto.dev/go/color v0.1.2 + git.akyoto.dev/go/color v0.1.3 ) require golang.org/x/sys v0.30.0 // indirect diff --git a/go.sum b/go.sum index 439aeca..236fc35 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= -git.akyoto.dev/go/color v0.1.2 h1:aNJP3upGimmhtV576Hxa4FZhU8GfnPuI5ZYZpVWsFLI= -git.akyoto.dev/go/color v0.1.2/go.mod h1:e00cRnX0fzFyIYEpAA7dCR/Hlk0/2YpXpVQaMT5Zoss= +git.akyoto.dev/go/color v0.1.3 h1:kqOVYaPJJDHi8qEwTZkZecQaUENBToaLmiiV1Gg++GM= +git.akyoto.dev/go/color v0.1.3/go.mod h1:e00cRnX0fzFyIYEpAA7dCR/Hlk0/2YpXpVQaMT5Zoss= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= From ac55700dddcd91aac4953414f7e4b033113b3295 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 17:32:48 +0100 Subject: [PATCH 0761/1012] Fixed build constraints for FreeBSD --- src/fs/Walk.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fs/Walk.go b/src/fs/Walk.go index 0a169ab..8253db5 100644 --- a/src/fs/Walk.go +++ b/src/fs/Walk.go @@ -1,4 +1,4 @@ -//go:build linux || darwin +//go:build !windows package fs From 546da5d8b27527776330c8a1474f47aa4dfe3174 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 17:32:48 +0100 Subject: [PATCH 0762/1012] Fixed build constraints for FreeBSD --- src/fs/Walk.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fs/Walk.go b/src/fs/Walk.go index 0a169ab..8253db5 100644 --- a/src/fs/Walk.go +++ b/src/fs/Walk.go @@ -1,4 +1,4 @@ -//go:build linux || darwin +//go:build !windows package fs From 6cdcfbba1dc83d9a11803cc5119ff62cf4537723 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 17:39:03 +0100 Subject: [PATCH 0763/1012] Fixed build constraints for other systems --- src/fs/{Walk.go => Walk_fast.go} | 2 +- src/fs/{Walk_windows.go => Walk_slow.go} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/fs/{Walk.go => Walk_fast.go} (97%) rename src/fs/{Walk_windows.go => Walk_slow.go} (93%) diff --git a/src/fs/Walk.go b/src/fs/Walk_fast.go similarity index 97% rename from src/fs/Walk.go rename to src/fs/Walk_fast.go index 8253db5..0a169ab 100644 --- a/src/fs/Walk.go +++ b/src/fs/Walk_fast.go @@ -1,4 +1,4 @@ -//go:build !windows +//go:build linux || darwin package fs diff --git a/src/fs/Walk_windows.go b/src/fs/Walk_slow.go similarity index 93% rename from src/fs/Walk_windows.go rename to src/fs/Walk_slow.go index 28cae1a..b77cd17 100644 --- a/src/fs/Walk_windows.go +++ b/src/fs/Walk_slow.go @@ -1,4 +1,4 @@ -//go:build windows +//go:build !linux && !darwin package fs From 7262f497176492dfcca13ec1cfe975416ec80e7b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 17:39:03 +0100 Subject: [PATCH 0764/1012] Fixed build constraints for other systems --- src/fs/{Walk.go => Walk_fast.go} | 2 +- src/fs/{Walk_windows.go => Walk_slow.go} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/fs/{Walk.go => Walk_fast.go} (97%) rename src/fs/{Walk_windows.go => Walk_slow.go} (93%) diff --git a/src/fs/Walk.go b/src/fs/Walk_fast.go similarity index 97% rename from src/fs/Walk.go rename to src/fs/Walk_fast.go index 8253db5..0a169ab 100644 --- a/src/fs/Walk.go +++ b/src/fs/Walk_fast.go @@ -1,4 +1,4 @@ -//go:build !windows +//go:build linux || darwin package fs diff --git a/src/fs/Walk_windows.go b/src/fs/Walk_slow.go similarity index 93% rename from src/fs/Walk_windows.go rename to src/fs/Walk_slow.go index 28cae1a..b77cd17 100644 --- a/src/fs/Walk_windows.go +++ b/src/fs/Walk_slow.go @@ -1,4 +1,4 @@ -//go:build windows +//go:build !linux && !darwin package fs From 11d6e43e6704b102cd9bb94d058ef729430d40e6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 17:40:06 +0100 Subject: [PATCH 0765/1012] Fixed fs package tests --- src/fs/Walk_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/fs/Walk_test.go b/src/fs/Walk_test.go index 09a0830..c63f62b 100644 --- a/src/fs/Walk_test.go +++ b/src/fs/Walk_test.go @@ -15,7 +15,6 @@ func TestWalk(t *testing.T) { }) assert.Nil(t, err) - assert.Contains(t, files, "Walk.go") assert.Contains(t, files, "Walk_test.go") } From c633f94d29f9ebcce2f3c95d08fbc230e3893c6d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 17:40:06 +0100 Subject: [PATCH 0766/1012] Fixed fs package tests --- src/fs/Walk_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/fs/Walk_test.go b/src/fs/Walk_test.go index 09a0830..c63f62b 100644 --- a/src/fs/Walk_test.go +++ b/src/fs/Walk_test.go @@ -15,7 +15,6 @@ func TestWalk(t *testing.T) { }) assert.Nil(t, err) - assert.Contains(t, files, "Walk.go") assert.Contains(t, files, "Walk_test.go") } From 90c1bc54b120cf54a92ba42a2ebaa77d9cee334c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 19:29:22 +0100 Subject: [PATCH 0767/1012] Improved formatting of load and store commands --- src/asm/Memory.go | 34 +++++++++++++++++++++++++++++++++- src/asm/MemoryLabel.go | 11 +---------- src/asm/MemoryNumber.go | 7 +------ src/asm/MemoryRegister.go | 7 +------ 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/asm/Memory.go b/src/asm/Memory.go index e97924e..a183de6 100644 --- a/src/asm/Memory.go +++ b/src/asm/Memory.go @@ -1,6 +1,12 @@ package asm -import "git.akyoto.dev/cli/q/src/cpu" +import ( + "fmt" + "math" + "strings" + + "git.akyoto.dev/cli/q/src/cpu" +) type Memory struct { Base cpu.Register @@ -8,3 +14,29 @@ type Memory struct { OffsetRegister cpu.Register Length byte } + +// Format returns a human readable version. +func (mem *Memory) Format(custom string) string { + tmp := strings.Builder{} + tmp.WriteString("[") + tmp.WriteString(fmt.Sprint(mem.Base)) + + if mem.OffsetRegister != math.MaxUint8 { + tmp.WriteString("+") + tmp.WriteString(fmt.Sprint(mem.OffsetRegister)) + } + + if mem.Offset != 0 { + if mem.Offset > 0 { + tmp.WriteString("+") + } + + tmp.WriteString(fmt.Sprint(mem.Offset)) + } + + tmp.WriteString("], ") + tmp.WriteString(custom) + tmp.WriteString(", ") + tmp.WriteString(fmt.Sprint(mem.Length)) + return tmp.String() +} diff --git a/src/asm/MemoryLabel.go b/src/asm/MemoryLabel.go index 63108c8..3ea1b9b 100644 --- a/src/asm/MemoryLabel.go +++ b/src/asm/MemoryLabel.go @@ -1,10 +1,5 @@ package asm -import ( - "fmt" - "math" -) - // MemoryLabel operates with a memory address and a number. type MemoryLabel struct { Label string @@ -13,11 +8,7 @@ type MemoryLabel struct { // String returns a human readable version. func (data *MemoryLabel) String() string { - if data.Address.OffsetRegister == math.MaxUint8 { - return fmt.Sprintf("%dB [%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.Offset, data.Label) - } - - return fmt.Sprintf("%dB [%s+%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Label) + return data.Address.Format(data.Label) } // MemoryLabel adds an instruction with a memory address and a label. diff --git a/src/asm/MemoryNumber.go b/src/asm/MemoryNumber.go index 1a255b3..651a20b 100644 --- a/src/asm/MemoryNumber.go +++ b/src/asm/MemoryNumber.go @@ -2,7 +2,6 @@ package asm import ( "fmt" - "math" ) // MemoryNumber operates with a memory address and a number. @@ -13,11 +12,7 @@ type MemoryNumber struct { // String returns a human readable version. func (data *MemoryNumber) String() string { - if data.Address.OffsetRegister == math.MaxUint8 { - return fmt.Sprintf("%dB [%s+%d], %d", data.Address.Length, data.Address.Base, data.Address.Offset, data.Number) - } - - return fmt.Sprintf("%dB [%s+%s+%d], %d", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Number) + return data.Address.Format(fmt.Sprint(data.Number)) } // MemoryNumber adds an instruction with a memory address and a number. diff --git a/src/asm/MemoryRegister.go b/src/asm/MemoryRegister.go index c35a3c8..1c9f51b 100644 --- a/src/asm/MemoryRegister.go +++ b/src/asm/MemoryRegister.go @@ -2,7 +2,6 @@ package asm import ( "fmt" - "math" "git.akyoto.dev/cli/q/src/cpu" ) @@ -15,11 +14,7 @@ type MemoryRegister struct { // String returns a human readable version. func (data *MemoryRegister) String() string { - if data.Address.OffsetRegister == math.MaxUint8 { - return fmt.Sprintf("%dB [%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.Offset, data.Register) - } - - return fmt.Sprintf("%dB [%s+%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Register) + return data.Address.Format(fmt.Sprint(data.Register)) } // MemoryRegister adds an instruction with a memory address and a number. From dc3ba6504f770cdc5c93911dd86101082109fc21 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 19:29:22 +0100 Subject: [PATCH 0768/1012] Improved formatting of load and store commands --- src/asm/Memory.go | 34 +++++++++++++++++++++++++++++++++- src/asm/MemoryLabel.go | 11 +---------- src/asm/MemoryNumber.go | 7 +------ src/asm/MemoryRegister.go | 7 +------ 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/asm/Memory.go b/src/asm/Memory.go index e97924e..a183de6 100644 --- a/src/asm/Memory.go +++ b/src/asm/Memory.go @@ -1,6 +1,12 @@ package asm -import "git.akyoto.dev/cli/q/src/cpu" +import ( + "fmt" + "math" + "strings" + + "git.akyoto.dev/cli/q/src/cpu" +) type Memory struct { Base cpu.Register @@ -8,3 +14,29 @@ type Memory struct { OffsetRegister cpu.Register Length byte } + +// Format returns a human readable version. +func (mem *Memory) Format(custom string) string { + tmp := strings.Builder{} + tmp.WriteString("[") + tmp.WriteString(fmt.Sprint(mem.Base)) + + if mem.OffsetRegister != math.MaxUint8 { + tmp.WriteString("+") + tmp.WriteString(fmt.Sprint(mem.OffsetRegister)) + } + + if mem.Offset != 0 { + if mem.Offset > 0 { + tmp.WriteString("+") + } + + tmp.WriteString(fmt.Sprint(mem.Offset)) + } + + tmp.WriteString("], ") + tmp.WriteString(custom) + tmp.WriteString(", ") + tmp.WriteString(fmt.Sprint(mem.Length)) + return tmp.String() +} diff --git a/src/asm/MemoryLabel.go b/src/asm/MemoryLabel.go index 63108c8..3ea1b9b 100644 --- a/src/asm/MemoryLabel.go +++ b/src/asm/MemoryLabel.go @@ -1,10 +1,5 @@ package asm -import ( - "fmt" - "math" -) - // MemoryLabel operates with a memory address and a number. type MemoryLabel struct { Label string @@ -13,11 +8,7 @@ type MemoryLabel struct { // String returns a human readable version. func (data *MemoryLabel) String() string { - if data.Address.OffsetRegister == math.MaxUint8 { - return fmt.Sprintf("%dB [%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.Offset, data.Label) - } - - return fmt.Sprintf("%dB [%s+%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Label) + return data.Address.Format(data.Label) } // MemoryLabel adds an instruction with a memory address and a label. diff --git a/src/asm/MemoryNumber.go b/src/asm/MemoryNumber.go index 1a255b3..651a20b 100644 --- a/src/asm/MemoryNumber.go +++ b/src/asm/MemoryNumber.go @@ -2,7 +2,6 @@ package asm import ( "fmt" - "math" ) // MemoryNumber operates with a memory address and a number. @@ -13,11 +12,7 @@ type MemoryNumber struct { // String returns a human readable version. func (data *MemoryNumber) String() string { - if data.Address.OffsetRegister == math.MaxUint8 { - return fmt.Sprintf("%dB [%s+%d], %d", data.Address.Length, data.Address.Base, data.Address.Offset, data.Number) - } - - return fmt.Sprintf("%dB [%s+%s+%d], %d", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Number) + return data.Address.Format(fmt.Sprint(data.Number)) } // MemoryNumber adds an instruction with a memory address and a number. diff --git a/src/asm/MemoryRegister.go b/src/asm/MemoryRegister.go index c35a3c8..1c9f51b 100644 --- a/src/asm/MemoryRegister.go +++ b/src/asm/MemoryRegister.go @@ -2,7 +2,6 @@ package asm import ( "fmt" - "math" "git.akyoto.dev/cli/q/src/cpu" ) @@ -15,11 +14,7 @@ type MemoryRegister struct { // String returns a human readable version. func (data *MemoryRegister) String() string { - if data.Address.OffsetRegister == math.MaxUint8 { - return fmt.Sprintf("%dB [%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.Offset, data.Register) - } - - return fmt.Sprintf("%dB [%s+%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.OffsetRegister, data.Address.Offset, data.Register) + return data.Address.Format(fmt.Sprint(data.Register)) } // MemoryRegister adds an instruction with a memory address and a number. From aa5f4513e88affae33988dd249d4fbe3485a9b83 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 19:58:47 +0100 Subject: [PATCH 0769/1012] Updated todo list --- docs/todo.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/todo.md b/docs/todo.md index 55d1038..6dcd482 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -19,16 +19,24 @@ - [x] Multiple return values - [x] Data structures - [x] Type system +- [ ] Dynamic libraries: Linux Mac ~~Windows~~ +- [ ] Unsigned types - [ ] Type operator `?` - [ ] Slices - [ ] Floating-point arithmetic -- [ ] Error handling -- [ ] Threading library - [ ] Self-hosted compiler +### Library + +- [ ] Efficient memory allocator +- [ ] Error handling +- [ ] Multithreading: ~~Linux~~ Mac ~~Windows~~ + ### Keywords - [x] `assert` +- [x] `const` +- [x] `extern` - [x] `else` - [ ] `for` - [x] `if` @@ -69,7 +77,7 @@ ### Architecture -- [ ] arm64 +- [ ] arm - [ ] riscv - [x] x86 From 596dbc62262aecc8b0fc2ba29642242e19e0abe7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 19:58:47 +0100 Subject: [PATCH 0770/1012] Updated todo list --- docs/todo.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/todo.md b/docs/todo.md index 55d1038..6dcd482 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -19,16 +19,24 @@ - [x] Multiple return values - [x] Data structures - [x] Type system +- [ ] Dynamic libraries: Linux Mac ~~Windows~~ +- [ ] Unsigned types - [ ] Type operator `?` - [ ] Slices - [ ] Floating-point arithmetic -- [ ] Error handling -- [ ] Threading library - [ ] Self-hosted compiler +### Library + +- [ ] Efficient memory allocator +- [ ] Error handling +- [ ] Multithreading: ~~Linux~~ Mac ~~Windows~~ + ### Keywords - [x] `assert` +- [x] `const` +- [x] `extern` - [x] `else` - [ ] `for` - [x] `if` @@ -69,7 +77,7 @@ ### Architecture -- [ ] arm64 +- [ ] arm - [ ] riscv - [x] x86 From 03ce7a608a1280e82bda20e77abbf12b6d4e6cfc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 21:30:36 +0100 Subject: [PATCH 0771/1012] Improved shell example --- examples/shell/shell.q | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/examples/shell/shell.q b/examples/shell/shell.q index 3353391..509b53e 100644 --- a/examples/shell/shell.q +++ b/examples/shell/shell.q @@ -2,29 +2,35 @@ import io import mem import sys +const idtype { + pid 1 +} + +const state { + exited 0x4 +} + main() { length := 256 command := mem.alloc(length) - argv := mem.alloc(1) - envp := mem.alloc(1) - info := mem.alloc(24) + siginfo := mem.alloc(128) loop { - io.out("$ ") + io.out("λ ") n := io.in(command) if n <= 0 { return } - command[n-1] = '\0' + command[n-1] = 0 pid := sys.fork() if pid == 0 { - sys.execve(command, argv, envp) + sys.execve(command, 0, 0) return } - sys.waitid(0, pid, info, 4) + sys.waitid(idtype.pid, pid, siginfo, state.exited) } } \ No newline at end of file From 216e66473e6b94c1c0849c3b82f270f81681d02f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 21:30:36 +0100 Subject: [PATCH 0772/1012] Improved shell example --- examples/shell/shell.q | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/examples/shell/shell.q b/examples/shell/shell.q index 3353391..509b53e 100644 --- a/examples/shell/shell.q +++ b/examples/shell/shell.q @@ -2,29 +2,35 @@ import io import mem import sys +const idtype { + pid 1 +} + +const state { + exited 0x4 +} + main() { length := 256 command := mem.alloc(length) - argv := mem.alloc(1) - envp := mem.alloc(1) - info := mem.alloc(24) + siginfo := mem.alloc(128) loop { - io.out("$ ") + io.out("λ ") n := io.in(command) if n <= 0 { return } - command[n-1] = '\0' + command[n-1] = 0 pid := sys.fork() if pid == 0 { - sys.execve(command, argv, envp) + sys.execve(command, 0, 0) return } - sys.waitid(0, pid, info, 4) + sys.waitid(idtype.pid, pid, siginfo, state.exited) } } \ No newline at end of file From 75b0bd7e31d3ffb4ba203487674089dc5341b989 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 23:42:05 +0100 Subject: [PATCH 0773/1012] Added more tests --- src/expression/Expression_test.go | 10 +++++--- src/expression/Parse.go | 1 - src/token/List_test.go | 42 ++++++++++++++++++++----------- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/expression/Expression_test.go b/src/expression/Expression_test.go index 64a09ba..143b2d4 100644 --- a/src/expression/Expression_test.go +++ b/src/expression/Expression_test.go @@ -98,15 +98,19 @@ func TestParse(t *testing.T) { {"Function calls 24", "a((b(1,2,bb())+c(3,4,cc(0)))*d(5,6,dd(0)),e(7+8,9-10*11,12,ee(0)))", "(λ a (* (+ (λ b 1 2 (λ bb)) (λ c 3 4 (λ cc 0))) (λ d 5 6 (λ dd 0))) (λ e (+ 7 8) (- 9 (* 10 11)) 12 (λ ee 0)))"}, {"Function calls 25", "a(1-2*3)", "(λ a (- 1 (* 2 3)))"}, {"Function calls 26", "1+2*a()+4", "(+ (+ 1 (* 2 (λ a))) 4)"}, - {"Function calls 27", "sum(a,b)*2+15*4", "(+ (* (λ sum a b) 2) (* 15 4))"}, + {"Function calls 27", "a(b,c)*2+15*4", "(+ (* (λ a b c) 2) (* 15 4))"}, - {"Package function calls", "math.sum(a,b)", "(λ (. math sum) a b)"}, - {"Package function calls 2", "generic.math.sum(a,b)", "(λ (. (. generic math) sum) a b)"}, + {"Package function calls", "a.b(c)", "(λ (. a b) c)"}, + {"Package function calls 2", "a.b(c,d)", "(λ (. a b) c d)"}, + {"Package function calls 3", "a.b.c(d,e)", "(λ (. (. a b) c) d e)"}, {"Array access", "a[0]", "(@ a 0)"}, {"Array access 2", "a[b+c]", "(@ a (+ b c))"}, {"Array access 3", "a.b[c]", "(@ (. a b) c)"}, {"Array access 4", "a.b[c+d]", "(@ (. a b) (+ c d))"}, + {"Array access 5", "a()[b]", "(@ (λ a) b)"}, + {"Array access 6", "a.b()[c]", "(@ (λ (. a b)) c)"}, + // {"Array access 7", "a.b(c)[d]", "(@ (λ (. a b) c) d)"}, } for _, test := range tests { diff --git a/src/expression/Parse.go b/src/expression/Parse.go index 3ed513e..f839ac8 100644 --- a/src/expression/Parse.go +++ b/src/expression/Parse.go @@ -36,7 +36,6 @@ func Parse(tokens []token.Token) *Expression { // Function call or array access if isComplete(cursor) { parameters := NewList(tokens[groupPosition:i]) - node := New() node.Token.Position = tokens[groupPosition].Position diff --git a/src/token/List_test.go b/src/token/List_test.go index 9daf6a9..9b3638f 100644 --- a/src/token/List_test.go +++ b/src/token/List_test.go @@ -32,20 +32,6 @@ func TestSplit(t *testing.T) { assert.DeepEqual(t, parameters, []string{"1+2", "3*4", "5*6", "7+8"}) } -func TestSplitGroups(t *testing.T) { - src := []byte("f(1,2),g(3,4)") - tokens := token.Tokenize(src) - parameters := []string{} - - err := tokens.Split(func(parameter token.List) error { - parameters = append(parameters, parameter.Text(src)) - return nil - }) - - assert.Nil(t, err) - assert.DeepEqual(t, parameters, []string{"f(1,2)", "g(3,4)"}) -} - func TestSplitEmpty(t *testing.T) { tokens := token.List{} @@ -67,3 +53,31 @@ func TestSplitError(t *testing.T) { assert.NotNil(t, err) assert.Equal(t, err.Error(), "error") } + +func TestSplitGroups(t *testing.T) { + src := []byte("f(1,2),g(3,4)") + tokens := token.Tokenize(src) + parameters := []string{} + + err := tokens.Split(func(parameter token.List) error { + parameters = append(parameters, parameter.Text(src)) + return nil + }) + + assert.Nil(t, err) + assert.DeepEqual(t, parameters, []string{"f(1,2)", "g(3,4)"}) +} + +func TestSplitSingle(t *testing.T) { + src := []byte("123") + tokens := token.Tokenize(src) + parameters := []string{} + + err := tokens.Split(func(parameter token.List) error { + parameters = append(parameters, parameter.Text(src)) + return nil + }) + + assert.Nil(t, err) + assert.DeepEqual(t, parameters, []string{"123"}) +} From 9574a26da7fca6f50a6e4ae867ce1bcf04b4c2f6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 16 Feb 2025 23:42:05 +0100 Subject: [PATCH 0774/1012] Added more tests --- src/expression/Expression_test.go | 10 +++++--- src/expression/Parse.go | 1 - src/token/List_test.go | 42 ++++++++++++++++++++----------- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/expression/Expression_test.go b/src/expression/Expression_test.go index 64a09ba..143b2d4 100644 --- a/src/expression/Expression_test.go +++ b/src/expression/Expression_test.go @@ -98,15 +98,19 @@ func TestParse(t *testing.T) { {"Function calls 24", "a((b(1,2,bb())+c(3,4,cc(0)))*d(5,6,dd(0)),e(7+8,9-10*11,12,ee(0)))", "(λ a (* (+ (λ b 1 2 (λ bb)) (λ c 3 4 (λ cc 0))) (λ d 5 6 (λ dd 0))) (λ e (+ 7 8) (- 9 (* 10 11)) 12 (λ ee 0)))"}, {"Function calls 25", "a(1-2*3)", "(λ a (- 1 (* 2 3)))"}, {"Function calls 26", "1+2*a()+4", "(+ (+ 1 (* 2 (λ a))) 4)"}, - {"Function calls 27", "sum(a,b)*2+15*4", "(+ (* (λ sum a b) 2) (* 15 4))"}, + {"Function calls 27", "a(b,c)*2+15*4", "(+ (* (λ a b c) 2) (* 15 4))"}, - {"Package function calls", "math.sum(a,b)", "(λ (. math sum) a b)"}, - {"Package function calls 2", "generic.math.sum(a,b)", "(λ (. (. generic math) sum) a b)"}, + {"Package function calls", "a.b(c)", "(λ (. a b) c)"}, + {"Package function calls 2", "a.b(c,d)", "(λ (. a b) c d)"}, + {"Package function calls 3", "a.b.c(d,e)", "(λ (. (. a b) c) d e)"}, {"Array access", "a[0]", "(@ a 0)"}, {"Array access 2", "a[b+c]", "(@ a (+ b c))"}, {"Array access 3", "a.b[c]", "(@ (. a b) c)"}, {"Array access 4", "a.b[c+d]", "(@ (. a b) (+ c d))"}, + {"Array access 5", "a()[b]", "(@ (λ a) b)"}, + {"Array access 6", "a.b()[c]", "(@ (λ (. a b)) c)"}, + // {"Array access 7", "a.b(c)[d]", "(@ (λ (. a b) c) d)"}, } for _, test := range tests { diff --git a/src/expression/Parse.go b/src/expression/Parse.go index 3ed513e..f839ac8 100644 --- a/src/expression/Parse.go +++ b/src/expression/Parse.go @@ -36,7 +36,6 @@ func Parse(tokens []token.Token) *Expression { // Function call or array access if isComplete(cursor) { parameters := NewList(tokens[groupPosition:i]) - node := New() node.Token.Position = tokens[groupPosition].Position diff --git a/src/token/List_test.go b/src/token/List_test.go index 9daf6a9..9b3638f 100644 --- a/src/token/List_test.go +++ b/src/token/List_test.go @@ -32,20 +32,6 @@ func TestSplit(t *testing.T) { assert.DeepEqual(t, parameters, []string{"1+2", "3*4", "5*6", "7+8"}) } -func TestSplitGroups(t *testing.T) { - src := []byte("f(1,2),g(3,4)") - tokens := token.Tokenize(src) - parameters := []string{} - - err := tokens.Split(func(parameter token.List) error { - parameters = append(parameters, parameter.Text(src)) - return nil - }) - - assert.Nil(t, err) - assert.DeepEqual(t, parameters, []string{"f(1,2)", "g(3,4)"}) -} - func TestSplitEmpty(t *testing.T) { tokens := token.List{} @@ -67,3 +53,31 @@ func TestSplitError(t *testing.T) { assert.NotNil(t, err) assert.Equal(t, err.Error(), "error") } + +func TestSplitGroups(t *testing.T) { + src := []byte("f(1,2),g(3,4)") + tokens := token.Tokenize(src) + parameters := []string{} + + err := tokens.Split(func(parameter token.List) error { + parameters = append(parameters, parameter.Text(src)) + return nil + }) + + assert.Nil(t, err) + assert.DeepEqual(t, parameters, []string{"f(1,2)", "g(3,4)"}) +} + +func TestSplitSingle(t *testing.T) { + src := []byte("123") + tokens := token.Tokenize(src) + parameters := []string{} + + err := tokens.Split(func(parameter token.List) error { + parameters = append(parameters, parameter.Text(src)) + return nil + }) + + assert.Nil(t, err) + assert.DeepEqual(t, parameters, []string{"123"}) +} From 0bd8a94d56683ba2b8db2654eece49c7a784fe57 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Feb 2025 10:42:44 +0100 Subject: [PATCH 0775/1012] Fixed a special case in the expression parser --- src/expression/Expression_test.go | 3 ++- src/expression/Operator.go | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/expression/Expression_test.go b/src/expression/Expression_test.go index 143b2d4..df62868 100644 --- a/src/expression/Expression_test.go +++ b/src/expression/Expression_test.go @@ -110,7 +110,8 @@ func TestParse(t *testing.T) { {"Array access 4", "a.b[c+d]", "(@ (. a b) (+ c d))"}, {"Array access 5", "a()[b]", "(@ (λ a) b)"}, {"Array access 6", "a.b()[c]", "(@ (λ (. a b)) c)"}, - // {"Array access 7", "a.b(c)[d]", "(@ (λ (. a b) c) d)"}, + {"Array access 7", "a.b(c)[d]", "(@ (λ (. a b) c) d)"}, + {"Array access 8", "a.b(c)[d][e]", "(@ (@ (λ (. a b) c) d) e)"}, } for _, test := range tests { diff --git a/src/expression/Operator.go b/src/expression/Operator.go index 57468da..f69ba00 100644 --- a/src/expression/Operator.go +++ b/src/expression/Operator.go @@ -63,6 +63,10 @@ func isComplete(expr *Expression) bool { return true } + if expr.Token.Kind == token.Call { + return true + } + if expr.Token.IsOperator() && len(expr.Children) == numOperands(expr.Token.Kind) { return true } From 3fb6061c30de9c4eaffe7cd8640f7c140ddaf88d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Feb 2025 10:42:44 +0100 Subject: [PATCH 0776/1012] Fixed a special case in the expression parser --- src/expression/Expression_test.go | 3 ++- src/expression/Operator.go | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/expression/Expression_test.go b/src/expression/Expression_test.go index 143b2d4..df62868 100644 --- a/src/expression/Expression_test.go +++ b/src/expression/Expression_test.go @@ -110,7 +110,8 @@ func TestParse(t *testing.T) { {"Array access 4", "a.b[c+d]", "(@ (. a b) (+ c d))"}, {"Array access 5", "a()[b]", "(@ (λ a) b)"}, {"Array access 6", "a.b()[c]", "(@ (λ (. a b)) c)"}, - // {"Array access 7", "a.b(c)[d]", "(@ (λ (. a b) c) d)"}, + {"Array access 7", "a.b(c)[d]", "(@ (λ (. a b) c) d)"}, + {"Array access 8", "a.b(c)[d][e]", "(@ (@ (λ (. a b) c) d) e)"}, } for _, test := range tests { diff --git a/src/expression/Operator.go b/src/expression/Operator.go index 57468da..f69ba00 100644 --- a/src/expression/Operator.go +++ b/src/expression/Operator.go @@ -63,6 +63,10 @@ func isComplete(expr *Expression) bool { return true } + if expr.Token.Kind == token.Call { + return true + } + if expr.Token.IsOperator() && len(expr.Children) == numOperands(expr.Token.Kind) { return true } From 7951136234e08fb90e0f3095e7bbd770fb884abe Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Feb 2025 11:05:17 +0100 Subject: [PATCH 0777/1012] Updated documentation --- docs/readme.md | 43 ++++++++++++++++++------------------------- src/cli/Help.go | 4 ++-- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 698829d..b48c67e 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -18,6 +18,9 @@ go build ``` This will place the compiler inside the repository. + +### Symlink + Either use `./q` for the following examples or get access to the shorter `q` in any directory via symlink: ```shell @@ -28,36 +31,26 @@ This assumes that your shell loads `~/.local/bin`. ## Usage -```shell -q [command] [options] ``` +Usage: -The `build` command creates an executable: + q [command] [options] -```shell -q build examples/hello +Commands: + + build [directory | file] build an executable from a file or directory + --arch [arch] cross-compile for another CPU architecture [x86|arm|riscv] + --assembler, -a show assembler instructions + --dry, -d skip writing the executable to disk + --os [os] cross-compile for another OS [linux|mac|windows] + --statistics, -s show statistics + --verbose, -v show everything + + run [directory | file] build and run the executable + system show system information + help show this help ``` -The `run` command does everything `build` does but also executes it: - -```shell -q run examples/hello -``` - -You don't have to use physical directories, it's perfectly valid to use a list of files: - -```shell -q build hello.q world.q -``` - -To show verbose compiler output use `-v` or `--verbose`: - -```shell -q build examples/hello -v -``` - -For more information see `q help`. - ## Platforms You can cross-compile executables for Linux, Mac and Windows. diff --git a/src/cli/Help.go b/src/cli/Help.go index 0543ad8..7faf7dc 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -14,10 +14,10 @@ func Help(w io.Writer, code int) int { Commands: build [directory | file] build an executable from a file or directory - --arch [arch] cross-compile for a different CPU architecture (x86, arm or riscv) + --arch [arch] cross-compile for another CPU architecture [x86|arm|riscv] --assembler, -a show assembler instructions --dry, -d skip writing the executable to disk - --os [os] cross-compile for a different OS (linux, mac or windows) + --os [os] cross-compile for another OS [linux|mac|windows] --statistics, -s show statistics --verbose, -v show everything From 5905a5bfa5cbce70a5e8adbe62dc428255ea7364 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Feb 2025 11:05:17 +0100 Subject: [PATCH 0778/1012] Updated documentation --- docs/readme.md | 43 ++++++++++++++++++------------------------- src/cli/Help.go | 4 ++-- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 698829d..b48c67e 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -18,6 +18,9 @@ go build ``` This will place the compiler inside the repository. + +### Symlink + Either use `./q` for the following examples or get access to the shorter `q` in any directory via symlink: ```shell @@ -28,36 +31,26 @@ This assumes that your shell loads `~/.local/bin`. ## Usage -```shell -q [command] [options] ``` +Usage: -The `build` command creates an executable: + q [command] [options] -```shell -q build examples/hello +Commands: + + build [directory | file] build an executable from a file or directory + --arch [arch] cross-compile for another CPU architecture [x86|arm|riscv] + --assembler, -a show assembler instructions + --dry, -d skip writing the executable to disk + --os [os] cross-compile for another OS [linux|mac|windows] + --statistics, -s show statistics + --verbose, -v show everything + + run [directory | file] build and run the executable + system show system information + help show this help ``` -The `run` command does everything `build` does but also executes it: - -```shell -q run examples/hello -``` - -You don't have to use physical directories, it's perfectly valid to use a list of files: - -```shell -q build hello.q world.q -``` - -To show verbose compiler output use `-v` or `--verbose`: - -```shell -q build examples/hello -v -``` - -For more information see `q help`. - ## Platforms You can cross-compile executables for Linux, Mac and Windows. diff --git a/src/cli/Help.go b/src/cli/Help.go index 0543ad8..7faf7dc 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -14,10 +14,10 @@ func Help(w io.Writer, code int) int { Commands: build [directory | file] build an executable from a file or directory - --arch [arch] cross-compile for a different CPU architecture (x86, arm or riscv) + --arch [arch] cross-compile for another CPU architecture [x86|arm|riscv] --assembler, -a show assembler instructions --dry, -d skip writing the executable to disk - --os [os] cross-compile for a different OS (linux, mac or windows) + --os [os] cross-compile for another OS [linux|mac|windows] --statistics, -s show statistics --verbose, -v show everything From 4bc4e7ef055efc9ebd7986a2d2a0792d3e6edc51 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Feb 2025 11:15:59 +0100 Subject: [PATCH 0779/1012] Updated documentation --- docs/readme.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index b48c67e..f130c9d 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -18,17 +18,16 @@ go build ``` This will place the compiler inside the repository. +Either use `./q` or get access to the shorter `q` in any directory with `ln -s $PWD/q ~/.local/bin/q`. -### Symlink +## Examples -Either use `./q` for the following examples or get access to the shorter `q` in any directory via symlink: +You can take a look at the [examples](examples). ```shell -ln -s $PWD/q ~/.local/bin/q +q run examples/hello ``` -This assumes that your shell loads `~/.local/bin`. - ## Usage ``` From 29b3bed452fe0d222ac6009e8aaf0fb98cd4df94 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Feb 2025 11:15:59 +0100 Subject: [PATCH 0780/1012] Updated documentation --- docs/readme.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index b48c67e..f130c9d 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -18,17 +18,16 @@ go build ``` This will place the compiler inside the repository. +Either use `./q` or get access to the shorter `q` in any directory with `ln -s $PWD/q ~/.local/bin/q`. -### Symlink +## Examples -Either use `./q` for the following examples or get access to the shorter `q` in any directory via symlink: +You can take a look at the [examples](examples). ```shell -ln -s $PWD/q ~/.local/bin/q +q run examples/hello ``` -This assumes that your shell loads `~/.local/bin`. - ## Usage ``` From 78f2670553e419c4403857c86406f76b8a1e0c15 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Feb 2025 11:17:36 +0100 Subject: [PATCH 0781/1012] Fixed incorrect link --- docs/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/readme.md b/docs/readme.md index f130c9d..d144e65 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -22,7 +22,7 @@ Either use `./q` or get access to the shorter `q` in any directory with `ln -s $ ## Examples -You can take a look at the [examples](examples). +You can take a look at the [examples](../examples). ```shell q run examples/hello From 3550f9e24e8a1078df591cebb73d475a9d45ffd0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Feb 2025 11:17:36 +0100 Subject: [PATCH 0782/1012] Fixed incorrect link --- docs/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/readme.md b/docs/readme.md index f130c9d..d144e65 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -22,7 +22,7 @@ Either use `./q` or get access to the shorter `q` in any directory with `ln -s $ ## Examples -You can take a look at the [examples](examples). +You can take a look at the [examples](../examples). ```shell q run examples/hello From 6eab48c586fa13ea423f3c93f2bb6b7216d12414 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Feb 2025 14:31:47 +0100 Subject: [PATCH 0783/1012] Improved type system --- examples/collatz/collatz.q | 2 +- examples/factorial/factorial.q | 2 +- examples/fibonacci/fibonacci.q | 2 +- examples/fizzbuzz/fizzbuzz.q | 2 +- examples/gcd/gcd.q | 2 +- examples/point/point.q | 6 ++--- examples/prime/prime.q | 2 +- examples/winapi/winapi.q | 2 +- lib/core/core_windows.q | 6 ++--- lib/io/io.q | 10 ++++---- lib/log/number.q | 11 ++++----- lib/mem/alloc_linux.q | 2 +- lib/mem/alloc_mac.q | 2 +- lib/mem/alloc_windows.q | 2 +- lib/mem/free.q | 2 +- lib/net/htons.q | 2 +- lib/net/net_linux.q | 2 +- lib/net/net_mac.q | 2 +- lib/sys/fs_linux.q | 12 +++++----- lib/sys/io_linux.q | 8 +++---- lib/sys/io_mac.q | 8 +++---- lib/sys/io_windows.q | 13 +++++----- lib/sys/mem_linux.q | 4 ++-- lib/sys/mem_mac.q | 4 ++-- lib/sys/mem_windows.q | 8 +++---- lib/sys/net_linux.q | 18 +++++++------- lib/sys/net_mac.q | 18 +++++++------- lib/sys/proc_linux.q | 10 ++++---- lib/sys/proc_mac.q | 8 +++---- lib/sys/proc_windows.q | 4 ++-- lib/sys/time_linux.q | 6 ++--- lib/thread/thread_linux.q | 2 +- lib/thread/thread_windows.q | 4 ++-- lib/time/time.q | 2 +- src/core/ExpressionToRegister.go | 16 +++++++++---- src/types/Array.go | 4 ---- src/types/ByName.go | 40 ++++++++++++++++++------------- src/types/Common.go | 26 +++++++++++--------- src/types/Is.go | 5 ++++ src/types/Pointer.go | 4 ---- src/types/types_test.go | 14 +++++------ tests/errors/MissingParameter2.q | 2 +- tests/errors/MissingParameter3.q | 2 +- tests/errors/TypeMismatch.q | 2 +- tests/errors/UnknownIdentifier3.q | 2 +- tests/errors_test.go | 4 ++-- tests/programs/branch-save.q | 2 +- tests/programs/branch.q | 4 ++-- tests/programs/chained-calls.q | 2 +- tests/programs/loop-lifetime.q | 2 +- tests/programs/math.q | 4 ++-- tests/programs/negation.q | 2 +- tests/programs/nested-calls.q | 2 +- tests/programs/op-assign.q | 2 +- tests/programs/param-multi.q | 4 ++-- tests/programs/param-order.q | 4 ++-- tests/programs/param.q | 4 ++-- tests/programs/return-multi.q | 6 ++--- tests/programs/return.q | 4 ++-- tests/programs/reuse.q | 2 +- tests/programs/square-sum.q | 2 +- tests/programs/struct.q | 4 ++-- 62 files changed, 189 insertions(+), 172 deletions(-) diff --git a/examples/collatz/collatz.q b/examples/collatz/collatz.q index 5f71d2c..dab2869 100644 --- a/examples/collatz/collatz.q +++ b/examples/collatz/collatz.q @@ -5,7 +5,7 @@ main() { collatz(12) } -collatz(x Int) { +collatz(x int) { loop { if x & 1 == 0 { x /= 2 diff --git a/examples/factorial/factorial.q b/examples/factorial/factorial.q index 885fc2f..e34436f 100644 --- a/examples/factorial/factorial.q +++ b/examples/factorial/factorial.q @@ -4,7 +4,7 @@ main() { log.number(factorial(5)) } -factorial(x Int) -> Int { +factorial(x int) -> int { if x <= 1 { return 1 } diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q index a071002..4d0f940 100644 --- a/examples/fibonacci/fibonacci.q +++ b/examples/fibonacci/fibonacci.q @@ -4,7 +4,7 @@ main() { log.number(fibonacci(10)) } -fibonacci(x Int) -> Int { +fibonacci(x int) -> int { if x <= 1 { return x } diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q index 7823731..e626e7f 100644 --- a/examples/fizzbuzz/fizzbuzz.q +++ b/examples/fizzbuzz/fizzbuzz.q @@ -5,7 +5,7 @@ main() { fizzbuzz(15) } -fizzbuzz(n Int) { +fizzbuzz(n int) { x := 1 loop { diff --git a/examples/gcd/gcd.q b/examples/gcd/gcd.q index d952825..c9382b2 100644 --- a/examples/gcd/gcd.q +++ b/examples/gcd/gcd.q @@ -4,7 +4,7 @@ main() { log.number(gcd(1071, 462)) } -gcd(a Int, b Int) -> Int { +gcd(a int, b int) -> int { loop { switch { a == b { return a } diff --git a/examples/point/point.q b/examples/point/point.q index 4026af6..c2a7d5f 100644 --- a/examples/point/point.q +++ b/examples/point/point.q @@ -2,8 +2,8 @@ import mem import sys struct Point { - x Int - y Int + x int + y int } main() { @@ -12,7 +12,7 @@ main() { delete(p) } -construct(x Int, y Int) -> *Point { +construct(x int, y int) -> *Point { p := new(Point) p.x = x p.y = y diff --git a/examples/prime/prime.q b/examples/prime/prime.q index 76e3d77..e417cd8 100644 --- a/examples/prime/prime.q +++ b/examples/prime/prime.q @@ -22,7 +22,7 @@ main() { } } -isPrime(x Int) -> Int { +isPrime(x int) -> int { if x == 2 { return 1 } diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q index a3f1dd7..c7a9c8e 100644 --- a/examples/winapi/winapi.q +++ b/examples/winapi/winapi.q @@ -1,5 +1,5 @@ extern user32 { - MessageBoxA(window *Any, text *Int8, title *Int8, type UInt) + MessageBoxA(window *any, text *int8, title *int8, flags uint) } main() { diff --git a/lib/core/core_windows.q b/lib/core/core_windows.q index 2b50ec1..b0acd58 100644 --- a/lib/core/core_windows.q +++ b/lib/core/core_windows.q @@ -1,7 +1,7 @@ extern kernel32 { - SetConsoleCP(cp UInt) - SetConsoleOutputCP(cp UInt) - ExitProcess(code UInt) + SetConsoleCP(cp uint) + SetConsoleOutputCP(cp uint) + ExitProcess(code uint) } const cp { diff --git a/lib/io/io.q b/lib/io/io.q index 4b34566..c741271 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -6,22 +6,22 @@ const std { err 2 } -in(buffer []Int8) -> Int { +in(buffer []int8) -> int { return sys.read(std.in, buffer, len(buffer)) } -out(buffer []Int8) -> Int { +out(buffer []int8) -> int { return sys.write(std.out, buffer, len(buffer)) } -error(buffer []Int8) -> Int { +error(buffer []int8) -> int { return sys.write(std.err, buffer, len(buffer)) } -read(fd Int, buffer []Int8) -> Int { +read(fd int, buffer []int8) -> int { return sys.read(fd, buffer, len(buffer)) } -write(fd Int, buffer []Int8) -> Int { +write(fd int, buffer []int8) -> int { return sys.write(fd, buffer, len(buffer)) } \ No newline at end of file diff --git a/lib/log/number.q b/lib/log/number.q index 0dc6e55..c292167 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -1,16 +1,15 @@ import mem import sys -number(x Int) { - length := 20 - buffer := mem.alloc(length) - address, count := itoa(x, buffer, length) +number(x int) { + buffer := mem.alloc(20) + address, count := itoa(x, buffer) sys.write(1, address, count) mem.free(buffer) } -itoa(x Int, buffer *Any, length Int) -> (*Any, Int) { - end := buffer + length +itoa(x int, buffer []int8) -> (*any, int) { + end := buffer + len(buffer) tmp := end digit := 0 diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_linux.q index 8ade3e6..78e403c 100644 --- a/lib/mem/alloc_linux.q +++ b/lib/mem/alloc_linux.q @@ -10,7 +10,7 @@ const map { anonymous 0x20 } -alloc(length Int) -> []Int8 { +alloc(length int) -> []int8 { x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) if x < 0x1000 { diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q index cdfbe86..e45eaf4 100644 --- a/lib/mem/alloc_mac.q +++ b/lib/mem/alloc_mac.q @@ -10,7 +10,7 @@ const map { anonymous 0x1000 } -alloc(length Int) -> []Int8 { +alloc(length int) -> []int8 { x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) if x < 0x1000 { diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index 3167a43..3a4b0a4 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -9,7 +9,7 @@ const mem { reserve 0x2000 } -alloc(length Int) -> []Int8 { +alloc(length int) -> []int8 { x := sys.mmap(0, length+8, page.readwrite, mem.commit|mem.reserve) if x < 0x1000 { diff --git a/lib/mem/free.q b/lib/mem/free.q index 2568b2b..2c61b78 100644 --- a/lib/mem/free.q +++ b/lib/mem/free.q @@ -1,5 +1,5 @@ import sys -free(address []Any) -> Int { +free(address []any) -> int { return sys.munmap(address-8, len(address)+8) } \ No newline at end of file diff --git a/lib/net/htons.q b/lib/net/htons.q index c491d9b..8e414c6 100644 --- a/lib/net/htons.q +++ b/lib/net/htons.q @@ -1,3 +1,3 @@ -htons(num Int) -> Int { +htons(num int) -> int { return ((num & 0xFF) << 8) | (num >> 8) } \ No newline at end of file diff --git a/lib/net/net_linux.q b/lib/net/net_linux.q index 4f226aa..4a025e3 100644 --- a/lib/net/net_linux.q +++ b/lib/net/net_linux.q @@ -1,6 +1,6 @@ import sys -bind(socket Int, port Int) -> Int { +bind(socket int, port int) -> int { addr := new(sys.sockaddr_in) addr.sin_family = 2 addr.sin_port = htons(port) diff --git a/lib/net/net_mac.q b/lib/net/net_mac.q index 5a6cb00..ce2de78 100644 --- a/lib/net/net_mac.q +++ b/lib/net/net_mac.q @@ -1,6 +1,6 @@ import sys -bind(socket Int, port Int) -> Int { +bind(socket int, port int) -> int { addr := new(sys.sockaddr_in_bsd) addr.sin_family = 2 addr.sin_port = htons(port) diff --git a/lib/sys/fs_linux.q b/lib/sys/fs_linux.q index 11b7503..2eda9ff 100644 --- a/lib/sys/fs_linux.q +++ b/lib/sys/fs_linux.q @@ -1,23 +1,23 @@ -getcwd(buffer *Any, length Int) -> Int { +getcwd(buffer *any, length int) -> int { return syscall(79, buffer, length) } -chdir(path *Any) -> Int { +chdir(path *any) -> int { return syscall(80, path) } -rename(old *Any, new *Any) -> Int { +rename(old *any, new *any) -> int { return syscall(82, old, new) } -mkdir(path *Any, mode Int) -> Int { +mkdir(path *any, mode int) -> int { return syscall(83, path, mode) } -rmdir(path *Any) -> Int { +rmdir(path *any) -> int { return syscall(84, path) } -unlink(file *Any) -> Int { +unlink(file *any) -> int { return syscall(87, file) } \ No newline at end of file diff --git a/lib/sys/io_linux.q b/lib/sys/io_linux.q index d204bb4..d70e220 100644 --- a/lib/sys/io_linux.q +++ b/lib/sys/io_linux.q @@ -1,15 +1,15 @@ -read(fd Int, buffer *Any, length Int) -> Int { +read(fd int, buffer *any, length int) -> int { return syscall(0, fd, buffer, length) } -write(fd Int, buffer *Any, length Int) -> Int { +write(fd int, buffer *any, length int) -> int { return syscall(1, fd, buffer, length) } -open(path *Any, flags Int, mode Int) -> Int { +open(path *any, flags int, mode int) -> int { return syscall(2, path, flags, mode) } -close(fd Int) -> Int { +close(fd int) -> int { return syscall(3, fd) } \ No newline at end of file diff --git a/lib/sys/io_mac.q b/lib/sys/io_mac.q index 8fc3dbd..87f7adc 100644 --- a/lib/sys/io_mac.q +++ b/lib/sys/io_mac.q @@ -1,15 +1,15 @@ -read(fd Int, buffer *Any, length Int) -> Int { +read(fd int, buffer *any, length int) -> int { return syscall(0x2000003, fd, buffer, length) } -write(fd Int, buffer *Any, length Int) -> Int { +write(fd int, buffer *any, length int) -> int { return syscall(0x2000004, fd, buffer, length) } -open(path *Any, flags Int, mode Int) -> Int { +open(path *any, flags int, mode int) -> int { return syscall(0x2000005, path, flags, mode) } -close(fd Int) -> Int { +close(fd int) -> int { return syscall(0x2000006, fd) } \ No newline at end of file diff --git a/lib/sys/io_windows.q b/lib/sys/io_windows.q index 81351c3..29fc51a 100644 --- a/lib/sys/io_windows.q +++ b/lib/sys/io_windows.q @@ -1,15 +1,16 @@ extern kernel32 { - GetStdHandle(handle Int) -> Int - WriteConsoleA(fd Int, buffer *Any, length Int, written *Int) -> Bool - ReadFile(fd Int, buffer *Any, length Int) -> Bool + GetStdHandle(handle int64) -> int64 + WriteConsoleA(fd int64, buffer *any, length uint32, written *uint32) -> bool + ReadConsole(fd int64, buffer *any, length uint32, written *uint32) -> bool } -read(fd Int, buffer *Any, length Int) -> Int { - kernel32.ReadFile(fd, buffer, length) +read(fd int64, buffer *any, length int64) -> int64 { + fd = kernel32.GetStdHandle(-10 - fd) + kernel32.ReadConsole(fd, buffer, length, 0) return length } -write(fd Int, buffer *Any, length Int) -> Int { +write(fd int64, buffer *any, length int64) -> int64 { fd = kernel32.GetStdHandle(-10 - fd) kernel32.WriteConsoleA(fd, buffer, length, 0) return length diff --git a/lib/sys/mem_linux.q b/lib/sys/mem_linux.q index 5821426..14dca51 100644 --- a/lib/sys/mem_linux.q +++ b/lib/sys/mem_linux.q @@ -1,7 +1,7 @@ -mmap(address Int, length Int, protection Int, flags Int) -> *Any { +mmap(address int, length int, protection int, flags int) -> *any { return syscall(9, address, length, protection, flags) } -munmap(address *Any, length Int) -> Int { +munmap(address *any, length int) -> int { return syscall(11, address, length) } \ No newline at end of file diff --git a/lib/sys/mem_mac.q b/lib/sys/mem_mac.q index 6bf509d..2b0d33b 100644 --- a/lib/sys/mem_mac.q +++ b/lib/sys/mem_mac.q @@ -1,7 +1,7 @@ -mmap(address Int, length Int, protection Int, flags Int) -> *Any { +mmap(address int, length int, protection int, flags int) -> *any { return syscall(0x20000C5, address, length, protection, flags) } -munmap(address *Any, length Int) -> Int { +munmap(address *any, length int) -> int { return syscall(0x2000049, address, length) } \ No newline at end of file diff --git a/lib/sys/mem_windows.q b/lib/sys/mem_windows.q index 97772e9..cdcc65e 100644 --- a/lib/sys/mem_windows.q +++ b/lib/sys/mem_windows.q @@ -1,16 +1,16 @@ extern kernel32 { - VirtualAlloc(address Int, length Int, flags Int, protection Int) - VirtualFree(address *Any, length Int, type Int) -> Bool + VirtualAlloc(address int, length int, flags int, protection int) + VirtualFree(address *any, length int, type int) -> bool } const mem { decommit 0x4000 } -mmap(address Int, length Int, protection Int, flags Int) -> *Any { +mmap(address int, length int, protection int, flags int) -> *any { return kernel32.VirtualAlloc(address, length, flags, protection) } -munmap(address *Any, length Int) -> Int { +munmap(address *any, length int) -> int { return kernel32.VirtualFree(address, length, mem.decommit) } \ No newline at end of file diff --git a/lib/sys/net_linux.q b/lib/sys/net_linux.q index 6e2e53c..d4977bc 100644 --- a/lib/sys/net_linux.q +++ b/lib/sys/net_linux.q @@ -1,26 +1,26 @@ struct sockaddr_in { - sin_family Int16 - sin_port Int16 - sin_addr Int64 - sin_zero Int64 + sin_family int16 + sin_port int16 + sin_addr int64 + sin_zero int64 } -socket(family Int, type Int, protocol Int) -> Int { +socket(family int, type int, protocol int) -> int { return syscall(41, family, type, protocol) } -accept(fd Int, address *Any, length Int) -> Int { +accept(fd int, address *any, length int) -> int { return syscall(43, fd, address, length) } -bind(fd Int, address *sockaddr_in, length Int) -> Int { +bind(fd int, address *sockaddr_in, length int) -> int { return syscall(49, fd, address, length) } -listen(fd Int, backlog Int) -> Int { +listen(fd int, backlog int) -> int { return syscall(50, fd, backlog) } -setsockopt(fd Int, level Int, optname Int, optval *Any, optlen Int) -> Int { +setsockopt(fd int, level int, optname int, optval *any, optlen int) -> int { return syscall(54, fd, level, optname, optval, optlen) } \ No newline at end of file diff --git a/lib/sys/net_mac.q b/lib/sys/net_mac.q index ca0a735..f143b1e 100644 --- a/lib/sys/net_mac.q +++ b/lib/sys/net_mac.q @@ -1,23 +1,23 @@ struct sockaddr_in_bsd { - sin_len Int8 - sin_family Int8 - sin_port Int16 - sin_addr Int64 - sin_zero Int64 + sin_len int8 + sin_family int8 + sin_port int16 + sin_addr int64 + sin_zero int64 } -socket(family Int, type Int, protocol Int) -> Int { +socket(family int, type int, protocol int) -> int { return syscall(0x2000061, family, type, protocol) } -accept(fd Int, address *Any, length Int) -> Int { +accept(fd int, address *any, length int) -> int { return syscall(0x200001E, fd, address, length) } -bind(fd Int, address *sockaddr_in_bsd, length Int) -> Int { +bind(fd int, address *sockaddr_in_bsd, length int) -> int { return syscall(0x2000068, fd, address, length) } -listen(fd Int, backlog Int) -> Int { +listen(fd int, backlog int) -> int { return syscall(0x200006A, fd, backlog) } \ No newline at end of file diff --git a/lib/sys/proc_linux.q b/lib/sys/proc_linux.q index 3540b0c..6a6491b 100644 --- a/lib/sys/proc_linux.q +++ b/lib/sys/proc_linux.q @@ -1,19 +1,19 @@ -clone(flags Int, stack *Any) -> Int { +clone(flags int, stack *any) -> int { return syscall(56, flags, stack) } -fork() -> Int { +fork() -> int { return syscall(57) } -execve(path *Any, argv *Any, envp *Any) -> Int { +execve(path *any, argv *any, envp *any) -> int { return syscall(59, path, argv, envp) } -exit(status Int) { +exit(status int) { syscall(60, status) } -waitid(type Int, id Int, info *Any, options Int) -> Int { +waitid(type int, id int, info *any, options int) -> int { return syscall(247, type, id, info, options) } \ No newline at end of file diff --git a/lib/sys/proc_mac.q b/lib/sys/proc_mac.q index aa3454a..a3dc367 100644 --- a/lib/sys/proc_mac.q +++ b/lib/sys/proc_mac.q @@ -1,15 +1,15 @@ -exit(status Int) { +exit(status int) { syscall(0x2000001, status) } -fork() -> Int { +fork() -> int { return syscall(0x2000002) } -execve(path *Any, argv *Any, envp *Any) -> Int { +execve(path *any, argv *any, envp *any) -> int { return syscall(0x200003B, path, argv, envp) } -waitid(type Int, id Int, info *Any, options Int) -> Int { +waitid(type int, id int, info *any, options int) -> int { return syscall(0x20000AD, type, id, info, options) } \ No newline at end of file diff --git a/lib/sys/proc_windows.q b/lib/sys/proc_windows.q index c5c4cdc..b846e5c 100644 --- a/lib/sys/proc_windows.q +++ b/lib/sys/proc_windows.q @@ -1,7 +1,7 @@ extern kernel32 { - ExitProcess(code UInt) + ExitProcess(code uint) } -exit(code Int) { +exit(code int) { kernel32.ExitProcess(code) } \ No newline at end of file diff --git a/lib/sys/time_linux.q b/lib/sys/time_linux.q index f2957d3..3099f60 100644 --- a/lib/sys/time_linux.q +++ b/lib/sys/time_linux.q @@ -1,8 +1,8 @@ struct timespec { - seconds Int - nanoseconds Int + seconds int64 + nanoseconds int64 } -nanosleep(duration *timespec) -> Int { +nanosleep(duration *timespec) -> int { return syscall(35, duration, 0) } \ No newline at end of file diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index 98b4d6f..e57fa17 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -11,7 +11,7 @@ const clone { io 0x80000000 } -create(func *Any) -> Int { +create(func *any) -> int { size := 4096 stack := sys.mmap(0, size, 0x1|0x2, 0x02|0x20|0x100|0x20000) stack += size diff --git a/lib/thread/thread_windows.q b/lib/thread/thread_windows.q index 0faef0d..0885bd1 100644 --- a/lib/thread/thread_windows.q +++ b/lib/thread/thread_windows.q @@ -1,7 +1,7 @@ extern kernel32 { - CreateThread(attributes Int, stackSize Int, address *Any, parameter Int) -> Int + CreateThread(attributes int, stackSize int, address *any, parameter int) -> int } -create(func *Any) -> Int { +create(func *any) -> int { return kernel32.CreateThread(0, 4096, func, 0) } \ No newline at end of file diff --git a/lib/time/time.q b/lib/time/time.q index b39dbe8..9d459d8 100644 --- a/lib/time/time.q +++ b/lib/time/time.q @@ -1,6 +1,6 @@ import sys -sleep(nanoseconds Int) { +sleep(nanoseconds int) { seconds := 0 if nanoseconds >= 1000000000 { diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index d2b0cae..56ce93e 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -144,10 +144,6 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return nil, err } - if typ == types.AnyPointer && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.AnyPointer { - typ = types.Int - } - err = f.Execute(node.Token, register, right) if register != final { @@ -155,5 +151,17 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp f.FreeRegister(register) } + _, isArray := typ.(*types.Array) + + if isArray { + typ = types.AnyPointer + } else if right.Token.Kind == token.Identifier { + rightVariable := f.VariableByName(right.Token.Text(f.File.Bytes)) + + if typ == types.AnyPointer && rightVariable.Type == types.AnyPointer { + typ = types.Int + } + } + return typ, err } diff --git a/src/types/Array.go b/src/types/Array.go index d46b216..fd6add2 100644 --- a/src/types/Array.go +++ b/src/types/Array.go @@ -9,10 +9,6 @@ type Array struct { // Name returns the type name. func (a *Array) Name() string { - if a.Of == Any { - return "[]Any" - } - return "[]" + a.Of.Name() } diff --git a/src/types/ByName.go b/src/types/ByName.go index 27ac5fc..bba1c17 100644 --- a/src/types/ByName.go +++ b/src/types/ByName.go @@ -29,28 +29,36 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { } switch name { - case "Any": - return Any - case "Bool": - return Bool - case "Int": + case "int": return Int - case "Int64": + case "int64": return Int64 - case "Int32": + case "int32": return Int32 - case "Int16": + case "int16": return Int16 - case "Int8": + case "int8": return Int8 - case "Float": - return Float - case "Float64": - return Float64 - case "Float32": - return Float32 - case "UInt": + case "uint": return UInt + case "uint64": + return UInt64 + case "uint32": + return UInt32 + case "uint16": + return UInt16 + case "uint8": + return UInt8 + case "float": + return Float + case "float64": + return Float64 + case "float32": + return Float32 + case "bool": + return Bool + case "any": + return Any } typ, exists := structs[pkg+"."+name] diff --git a/src/types/Common.go b/src/types/Common.go index aab4a96..a846b61 100644 --- a/src/types/Common.go +++ b/src/types/Common.go @@ -1,20 +1,24 @@ package types var ( - Any = &Base{name: "Any", size: 0} + Any = &Base{name: "any", size: 0} AnyArray = &Array{Of: Any} AnyPointer = &Pointer{To: Any} - Int64 = &Base{name: "Int64", size: 8} - Int32 = &Base{name: "Int32", size: 4} - Int16 = &Base{name: "Int16", size: 2} - Int8 = &Base{name: "Int8", size: 1} - Float64 = &Base{name: "Float64", size: 8} - Float32 = &Base{name: "Float32", size: 4} + Int64 = &Base{name: "int64", size: 8} + Int32 = &Base{name: "int32", size: 4} + Int16 = &Base{name: "int16", size: 2} + Int8 = &Base{name: "int8", size: 1} + Float64 = &Base{name: "float64", size: 8} + Float32 = &Base{name: "float32", size: 4} ) var ( - Bool = Int - Int = Int64 - Float = Float64 - UInt = Int + Bool = Int + Int = Int64 + Float = Float64 + UInt = Int + UInt64 = Int64 + UInt32 = Int32 + UInt16 = Int16 + UInt8 = Int8 ) diff --git a/src/types/Is.go b/src/types/Is.go index 438b917..4134701 100644 --- a/src/types/Is.go +++ b/src/types/Is.go @@ -25,5 +25,10 @@ func Is(a Type, b Type) bool { return true } + // Temporary hack for implicit casts + if a.Size() > b.Size() { + return true + } + return false } diff --git a/src/types/Pointer.go b/src/types/Pointer.go index 1cf180b..1037776 100644 --- a/src/types/Pointer.go +++ b/src/types/Pointer.go @@ -7,10 +7,6 @@ type Pointer struct { // Name returns the type name. func (p *Pointer) Name() string { - if p.To == nil { - return "*Any" - } - return "*" + p.To.Name() } diff --git a/src/types/types_test.go b/src/types/types_test.go index e19602c..e3cb03e 100644 --- a/src/types/types_test.go +++ b/src/types/types_test.go @@ -8,12 +8,12 @@ import ( ) func TestName(t *testing.T) { - assert.Equal(t, types.Int.Name(), "Int64") - assert.Equal(t, types.AnyArray.Name(), "[]Any") - assert.Equal(t, types.AnyPointer.Name(), "*Any") - assert.Equal(t, (&types.Pointer{To: types.Int}).Name(), "*Int64") - assert.Equal(t, (&types.Array{Of: types.Int}).Name(), "[]Int64") - assert.Equal(t, types.String.Name(), "[]Int8") + assert.Equal(t, types.Int.Name(), "int64") + assert.Equal(t, types.AnyArray.Name(), "[]any") + assert.Equal(t, types.AnyPointer.Name(), "*any") + assert.Equal(t, (&types.Pointer{To: types.Int}).Name(), "*int64") + assert.Equal(t, (&types.Array{Of: types.Int}).Name(), "[]int64") + assert.Equal(t, types.String.Name(), "[]int8") } func TestSize(t *testing.T) { @@ -32,7 +32,7 @@ func TestStruct(t *testing.T) { s := types.NewStruct("main", "Test") assert.Equal(t, s.Name(), "Test") assert.Equal(t, s.Size(), 0) - field := &types.Field{Name: "TestField", TypeName: "Int8"} + field := &types.Field{Name: "TestField", TypeName: "int8"} s.AddField(field) s.Update(nil) assert.Equal(t, s.Size(), 1) diff --git a/tests/errors/MissingParameter2.q b/tests/errors/MissingParameter2.q index fd8af14..827b8d8 100644 --- a/tests/errors/MissingParameter2.q +++ b/tests/errors/MissingParameter2.q @@ -1 +1 @@ -f(a Int,) -> Int { return a } \ No newline at end of file +f(a int,) -> int { return a } \ No newline at end of file diff --git a/tests/errors/MissingParameter3.q b/tests/errors/MissingParameter3.q index 6b4650b..839bf40 100644 --- a/tests/errors/MissingParameter3.q +++ b/tests/errors/MissingParameter3.q @@ -1 +1 @@ -f(,a Int) {} \ No newline at end of file +f(,a int) {} \ No newline at end of file diff --git a/tests/errors/TypeMismatch.q b/tests/errors/TypeMismatch.q index 57bdd6b..09d6221 100644 --- a/tests/errors/TypeMismatch.q +++ b/tests/errors/TypeMismatch.q @@ -2,6 +2,6 @@ main() { writeToMemory(42) } -writeToMemory(p *Any) { +writeToMemory(p *any) { p[0] = 'A' } \ No newline at end of file diff --git a/tests/errors/UnknownIdentifier3.q b/tests/errors/UnknownIdentifier3.q index 4cc4927..4295081 100644 --- a/tests/errors/UnknownIdentifier3.q +++ b/tests/errors/UnknownIdentifier3.q @@ -2,6 +2,6 @@ main() { x := 1 + f(x) } -f(x Int) -> Int { +f(x int) -> int { return x } \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 7b3812e..781b662 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -49,8 +49,8 @@ var errs = []struct { {"MissingParameter3.q", errors.MissingParameter}, {"MissingType.q", errors.MissingType}, {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, - {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*Any", Encountered: "Int64", ParameterName: "p"}}, - {"TypeMismatch2.q", &errors.TypeMismatch{Expected: "[]Any", Encountered: "Int64", ParameterName: "array"}}, + {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*any", Encountered: "int64", ParameterName: "p"}}, + {"TypeMismatch2.q", &errors.TypeMismatch{Expected: "[]any", Encountered: "int64", ParameterName: "array"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, diff --git a/tests/programs/branch-save.q b/tests/programs/branch-save.q index 75ad7bf..325880a 100644 --- a/tests/programs/branch-save.q +++ b/tests/programs/branch-save.q @@ -12,7 +12,7 @@ main() { assert b == 5 } -f(b Int) -> (Int, Int) { +f(b int) -> (int, int) { a := 0 if b >= 10 { diff --git a/tests/programs/branch.q b/tests/programs/branch.q index 171002b..92b6669 100644 --- a/tests/programs/branch.q +++ b/tests/programs/branch.q @@ -74,10 +74,10 @@ main() { sys.exit(1) } -inc(x Int) -> Int { +inc(x int) -> int { return x + 1 } -dec(x Int) -> Int { +dec(x int) -> int { return x - 1 } \ No newline at end of file diff --git a/tests/programs/chained-calls.q b/tests/programs/chained-calls.q index cd3c519..b9068c4 100644 --- a/tests/programs/chained-calls.q +++ b/tests/programs/chained-calls.q @@ -2,6 +2,6 @@ main() { assert f(1) + f(2) + f(3) == 9 } -f(x Int) -> Int { +f(x int) -> int { return x + 1 } \ No newline at end of file diff --git a/tests/programs/loop-lifetime.q b/tests/programs/loop-lifetime.q index d30489e..59f4164 100644 --- a/tests/programs/loop-lifetime.q +++ b/tests/programs/loop-lifetime.q @@ -12,6 +12,6 @@ main() { } } -f(x Int) -> Int { +f(x int) -> int { return x } \ No newline at end of file diff --git a/tests/programs/math.q b/tests/programs/math.q index 2de1b1b..cb79331 100644 --- a/tests/programs/math.q +++ b/tests/programs/math.q @@ -4,10 +4,10 @@ main() { assert result == 10 } -div(x Int, y Int) -> Int { +div(x int, y int) -> int { return x / y } -div10(x Int) -> Int { +div10(x int) -> int { return x / 10 } \ No newline at end of file diff --git a/tests/programs/negation.q b/tests/programs/negation.q index 089d4d7..07cf8ac 100644 --- a/tests/programs/negation.q +++ b/tests/programs/negation.q @@ -5,6 +5,6 @@ main() { assert neg(256) == -256 } -neg(x Int) -> Int { +neg(x int) -> int { return -x } \ No newline at end of file diff --git a/tests/programs/nested-calls.q b/tests/programs/nested-calls.q index e555b99..515a8d3 100644 --- a/tests/programs/nested-calls.q +++ b/tests/programs/nested-calls.q @@ -2,6 +2,6 @@ main() { assert f(f(f(1))) == 4 } -f(x Int) -> Int { +f(x int) -> int { return x + 1 } \ No newline at end of file diff --git a/tests/programs/op-assign.q b/tests/programs/op-assign.q index 257b6ac..85dd9c4 100644 --- a/tests/programs/op-assign.q +++ b/tests/programs/op-assign.q @@ -2,7 +2,7 @@ main() { f(10) } -f(new Int) { +f(new int) { old := new new -= 1 assert new != old diff --git a/tests/programs/param-multi.q b/tests/programs/param-multi.q index 0e19661..040ed48 100644 --- a/tests/programs/param-multi.q +++ b/tests/programs/param-multi.q @@ -2,11 +2,11 @@ main() { assert f(1, 2, 3) == 21 } -f(x Int, y Int, z Int) -> Int { +f(x int, y int, z int) -> int { w := g(4, 5, 6) return x + y + z + w } -g(x Int, y Int, z Int) -> Int { +g(x int, y int, z int) -> int { return x + y + z } \ No newline at end of file diff --git a/tests/programs/param-order.q b/tests/programs/param-order.q index 3e299b1..25ffd6d 100644 --- a/tests/programs/param-order.q +++ b/tests/programs/param-order.q @@ -2,11 +2,11 @@ main() { f1(1, 2, 3, 4, 5, 6) } -f1(a Int, b Int, c Int, d Int, e Int, f Int) { +f1(a int, b int, c int, d int, e int, f int) { f2(f, e, d, c, b, a) } -f2(a Int, b Int, c Int, d Int, e Int, f Int) { +f2(a int, b int, c int, d int, e int, f int) { assert a == 6 assert b == 5 assert c == 4 diff --git a/tests/programs/param.q b/tests/programs/param.q index 5e1628d..5efd3c1 100644 --- a/tests/programs/param.q +++ b/tests/programs/param.q @@ -2,11 +2,11 @@ main() { assert f(1) == 3 } -f(x Int) -> Int { +f(x int) -> int { y := g() return x + y } -g() -> Int { +g() -> int { return 2 } \ No newline at end of file diff --git a/tests/programs/return-multi.q b/tests/programs/return-multi.q index be81c42..1b5bd1a 100644 --- a/tests/programs/return-multi.q +++ b/tests/programs/return-multi.q @@ -15,14 +15,14 @@ main() { assert i == 1 + 4 } -reverse2(a Int, b Int) -> (Int, Int) { +reverse2(a int, b int) -> (int, int) { return b, a } -reverse3(a Int, b Int, c Int) -> (Int, Int, Int) { +reverse3(a int, b int, c int) -> (int, int, int) { return c, b, a } -mix4(a Int, b Int, c Int, d Int) -> (Int, Int, Int, Int) { +mix4(a int, b int, c int, d int) -> (int, int, int, int) { return d + a, c + b, b + c, a + d } \ No newline at end of file diff --git a/tests/programs/return.q b/tests/programs/return.q index 36095bc..abee37c 100644 --- a/tests/programs/return.q +++ b/tests/programs/return.q @@ -2,10 +2,10 @@ main() { assert f(2) == 6 } -f(x Int) -> Int { +f(x int) -> int { return x + 1 + g(x) } -g(x Int) -> Int { +g(x int) -> int { return x + 1 } \ No newline at end of file diff --git a/tests/programs/reuse.q b/tests/programs/reuse.q index 4658edb..109071e 100644 --- a/tests/programs/reuse.q +++ b/tests/programs/reuse.q @@ -2,6 +2,6 @@ main() { assert f(1) == 3 } -f(x Int) -> Int { +f(x int) -> int { return x + 1 + x } \ No newline at end of file diff --git a/tests/programs/square-sum.q b/tests/programs/square-sum.q index a9c0850..d97ba73 100644 --- a/tests/programs/square-sum.q +++ b/tests/programs/square-sum.q @@ -2,6 +2,6 @@ main() { assert f(2, 3) == 25 } -f(x Int, y Int) -> Int { +f(x int, y int) -> int { return (x + y) * (x + y) } \ No newline at end of file diff --git a/tests/programs/struct.q b/tests/programs/struct.q index 514a863..ad438c4 100644 --- a/tests/programs/struct.q +++ b/tests/programs/struct.q @@ -1,6 +1,6 @@ struct Point { - x Int - y Int + x int + y int } main() { From b8e37fafae822401cd50b1765510738216ec26b6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Feb 2025 14:31:47 +0100 Subject: [PATCH 0784/1012] Improved type system --- examples/collatz/collatz.q | 2 +- examples/factorial/factorial.q | 2 +- examples/fibonacci/fibonacci.q | 2 +- examples/fizzbuzz/fizzbuzz.q | 2 +- examples/gcd/gcd.q | 2 +- examples/point/point.q | 6 ++--- examples/prime/prime.q | 2 +- examples/winapi/winapi.q | 2 +- lib/core/core_windows.q | 6 ++--- lib/io/io.q | 10 ++++---- lib/log/number.q | 11 ++++----- lib/mem/alloc_linux.q | 2 +- lib/mem/alloc_mac.q | 2 +- lib/mem/alloc_windows.q | 2 +- lib/mem/free.q | 2 +- lib/net/htons.q | 2 +- lib/net/net_linux.q | 2 +- lib/net/net_mac.q | 2 +- lib/sys/fs_linux.q | 12 +++++----- lib/sys/io_linux.q | 8 +++---- lib/sys/io_mac.q | 8 +++---- lib/sys/io_windows.q | 13 +++++----- lib/sys/mem_linux.q | 4 ++-- lib/sys/mem_mac.q | 4 ++-- lib/sys/mem_windows.q | 8 +++---- lib/sys/net_linux.q | 18 +++++++------- lib/sys/net_mac.q | 18 +++++++------- lib/sys/proc_linux.q | 10 ++++---- lib/sys/proc_mac.q | 8 +++---- lib/sys/proc_windows.q | 4 ++-- lib/sys/time_linux.q | 6 ++--- lib/thread/thread_linux.q | 2 +- lib/thread/thread_windows.q | 4 ++-- lib/time/time.q | 2 +- src/core/ExpressionToRegister.go | 16 +++++++++---- src/types/Array.go | 4 ---- src/types/ByName.go | 40 ++++++++++++++++++------------- src/types/Common.go | 26 +++++++++++--------- src/types/Is.go | 5 ++++ src/types/Pointer.go | 4 ---- src/types/types_test.go | 14 +++++------ tests/errors/MissingParameter2.q | 2 +- tests/errors/MissingParameter3.q | 2 +- tests/errors/TypeMismatch.q | 2 +- tests/errors/UnknownIdentifier3.q | 2 +- tests/errors_test.go | 4 ++-- tests/programs/branch-save.q | 2 +- tests/programs/branch.q | 4 ++-- tests/programs/chained-calls.q | 2 +- tests/programs/loop-lifetime.q | 2 +- tests/programs/math.q | 4 ++-- tests/programs/negation.q | 2 +- tests/programs/nested-calls.q | 2 +- tests/programs/op-assign.q | 2 +- tests/programs/param-multi.q | 4 ++-- tests/programs/param-order.q | 4 ++-- tests/programs/param.q | 4 ++-- tests/programs/return-multi.q | 6 ++--- tests/programs/return.q | 4 ++-- tests/programs/reuse.q | 2 +- tests/programs/square-sum.q | 2 +- tests/programs/struct.q | 4 ++-- 62 files changed, 189 insertions(+), 172 deletions(-) diff --git a/examples/collatz/collatz.q b/examples/collatz/collatz.q index 5f71d2c..dab2869 100644 --- a/examples/collatz/collatz.q +++ b/examples/collatz/collatz.q @@ -5,7 +5,7 @@ main() { collatz(12) } -collatz(x Int) { +collatz(x int) { loop { if x & 1 == 0 { x /= 2 diff --git a/examples/factorial/factorial.q b/examples/factorial/factorial.q index 885fc2f..e34436f 100644 --- a/examples/factorial/factorial.q +++ b/examples/factorial/factorial.q @@ -4,7 +4,7 @@ main() { log.number(factorial(5)) } -factorial(x Int) -> Int { +factorial(x int) -> int { if x <= 1 { return 1 } diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q index a071002..4d0f940 100644 --- a/examples/fibonacci/fibonacci.q +++ b/examples/fibonacci/fibonacci.q @@ -4,7 +4,7 @@ main() { log.number(fibonacci(10)) } -fibonacci(x Int) -> Int { +fibonacci(x int) -> int { if x <= 1 { return x } diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q index 7823731..e626e7f 100644 --- a/examples/fizzbuzz/fizzbuzz.q +++ b/examples/fizzbuzz/fizzbuzz.q @@ -5,7 +5,7 @@ main() { fizzbuzz(15) } -fizzbuzz(n Int) { +fizzbuzz(n int) { x := 1 loop { diff --git a/examples/gcd/gcd.q b/examples/gcd/gcd.q index d952825..c9382b2 100644 --- a/examples/gcd/gcd.q +++ b/examples/gcd/gcd.q @@ -4,7 +4,7 @@ main() { log.number(gcd(1071, 462)) } -gcd(a Int, b Int) -> Int { +gcd(a int, b int) -> int { loop { switch { a == b { return a } diff --git a/examples/point/point.q b/examples/point/point.q index 4026af6..c2a7d5f 100644 --- a/examples/point/point.q +++ b/examples/point/point.q @@ -2,8 +2,8 @@ import mem import sys struct Point { - x Int - y Int + x int + y int } main() { @@ -12,7 +12,7 @@ main() { delete(p) } -construct(x Int, y Int) -> *Point { +construct(x int, y int) -> *Point { p := new(Point) p.x = x p.y = y diff --git a/examples/prime/prime.q b/examples/prime/prime.q index 76e3d77..e417cd8 100644 --- a/examples/prime/prime.q +++ b/examples/prime/prime.q @@ -22,7 +22,7 @@ main() { } } -isPrime(x Int) -> Int { +isPrime(x int) -> int { if x == 2 { return 1 } diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q index a3f1dd7..c7a9c8e 100644 --- a/examples/winapi/winapi.q +++ b/examples/winapi/winapi.q @@ -1,5 +1,5 @@ extern user32 { - MessageBoxA(window *Any, text *Int8, title *Int8, type UInt) + MessageBoxA(window *any, text *int8, title *int8, flags uint) } main() { diff --git a/lib/core/core_windows.q b/lib/core/core_windows.q index 2b50ec1..b0acd58 100644 --- a/lib/core/core_windows.q +++ b/lib/core/core_windows.q @@ -1,7 +1,7 @@ extern kernel32 { - SetConsoleCP(cp UInt) - SetConsoleOutputCP(cp UInt) - ExitProcess(code UInt) + SetConsoleCP(cp uint) + SetConsoleOutputCP(cp uint) + ExitProcess(code uint) } const cp { diff --git a/lib/io/io.q b/lib/io/io.q index 4b34566..c741271 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -6,22 +6,22 @@ const std { err 2 } -in(buffer []Int8) -> Int { +in(buffer []int8) -> int { return sys.read(std.in, buffer, len(buffer)) } -out(buffer []Int8) -> Int { +out(buffer []int8) -> int { return sys.write(std.out, buffer, len(buffer)) } -error(buffer []Int8) -> Int { +error(buffer []int8) -> int { return sys.write(std.err, buffer, len(buffer)) } -read(fd Int, buffer []Int8) -> Int { +read(fd int, buffer []int8) -> int { return sys.read(fd, buffer, len(buffer)) } -write(fd Int, buffer []Int8) -> Int { +write(fd int, buffer []int8) -> int { return sys.write(fd, buffer, len(buffer)) } \ No newline at end of file diff --git a/lib/log/number.q b/lib/log/number.q index 0dc6e55..c292167 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -1,16 +1,15 @@ import mem import sys -number(x Int) { - length := 20 - buffer := mem.alloc(length) - address, count := itoa(x, buffer, length) +number(x int) { + buffer := mem.alloc(20) + address, count := itoa(x, buffer) sys.write(1, address, count) mem.free(buffer) } -itoa(x Int, buffer *Any, length Int) -> (*Any, Int) { - end := buffer + length +itoa(x int, buffer []int8) -> (*any, int) { + end := buffer + len(buffer) tmp := end digit := 0 diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_linux.q index 8ade3e6..78e403c 100644 --- a/lib/mem/alloc_linux.q +++ b/lib/mem/alloc_linux.q @@ -10,7 +10,7 @@ const map { anonymous 0x20 } -alloc(length Int) -> []Int8 { +alloc(length int) -> []int8 { x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) if x < 0x1000 { diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q index cdfbe86..e45eaf4 100644 --- a/lib/mem/alloc_mac.q +++ b/lib/mem/alloc_mac.q @@ -10,7 +10,7 @@ const map { anonymous 0x1000 } -alloc(length Int) -> []Int8 { +alloc(length int) -> []int8 { x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) if x < 0x1000 { diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index 3167a43..3a4b0a4 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -9,7 +9,7 @@ const mem { reserve 0x2000 } -alloc(length Int) -> []Int8 { +alloc(length int) -> []int8 { x := sys.mmap(0, length+8, page.readwrite, mem.commit|mem.reserve) if x < 0x1000 { diff --git a/lib/mem/free.q b/lib/mem/free.q index 2568b2b..2c61b78 100644 --- a/lib/mem/free.q +++ b/lib/mem/free.q @@ -1,5 +1,5 @@ import sys -free(address []Any) -> Int { +free(address []any) -> int { return sys.munmap(address-8, len(address)+8) } \ No newline at end of file diff --git a/lib/net/htons.q b/lib/net/htons.q index c491d9b..8e414c6 100644 --- a/lib/net/htons.q +++ b/lib/net/htons.q @@ -1,3 +1,3 @@ -htons(num Int) -> Int { +htons(num int) -> int { return ((num & 0xFF) << 8) | (num >> 8) } \ No newline at end of file diff --git a/lib/net/net_linux.q b/lib/net/net_linux.q index 4f226aa..4a025e3 100644 --- a/lib/net/net_linux.q +++ b/lib/net/net_linux.q @@ -1,6 +1,6 @@ import sys -bind(socket Int, port Int) -> Int { +bind(socket int, port int) -> int { addr := new(sys.sockaddr_in) addr.sin_family = 2 addr.sin_port = htons(port) diff --git a/lib/net/net_mac.q b/lib/net/net_mac.q index 5a6cb00..ce2de78 100644 --- a/lib/net/net_mac.q +++ b/lib/net/net_mac.q @@ -1,6 +1,6 @@ import sys -bind(socket Int, port Int) -> Int { +bind(socket int, port int) -> int { addr := new(sys.sockaddr_in_bsd) addr.sin_family = 2 addr.sin_port = htons(port) diff --git a/lib/sys/fs_linux.q b/lib/sys/fs_linux.q index 11b7503..2eda9ff 100644 --- a/lib/sys/fs_linux.q +++ b/lib/sys/fs_linux.q @@ -1,23 +1,23 @@ -getcwd(buffer *Any, length Int) -> Int { +getcwd(buffer *any, length int) -> int { return syscall(79, buffer, length) } -chdir(path *Any) -> Int { +chdir(path *any) -> int { return syscall(80, path) } -rename(old *Any, new *Any) -> Int { +rename(old *any, new *any) -> int { return syscall(82, old, new) } -mkdir(path *Any, mode Int) -> Int { +mkdir(path *any, mode int) -> int { return syscall(83, path, mode) } -rmdir(path *Any) -> Int { +rmdir(path *any) -> int { return syscall(84, path) } -unlink(file *Any) -> Int { +unlink(file *any) -> int { return syscall(87, file) } \ No newline at end of file diff --git a/lib/sys/io_linux.q b/lib/sys/io_linux.q index d204bb4..d70e220 100644 --- a/lib/sys/io_linux.q +++ b/lib/sys/io_linux.q @@ -1,15 +1,15 @@ -read(fd Int, buffer *Any, length Int) -> Int { +read(fd int, buffer *any, length int) -> int { return syscall(0, fd, buffer, length) } -write(fd Int, buffer *Any, length Int) -> Int { +write(fd int, buffer *any, length int) -> int { return syscall(1, fd, buffer, length) } -open(path *Any, flags Int, mode Int) -> Int { +open(path *any, flags int, mode int) -> int { return syscall(2, path, flags, mode) } -close(fd Int) -> Int { +close(fd int) -> int { return syscall(3, fd) } \ No newline at end of file diff --git a/lib/sys/io_mac.q b/lib/sys/io_mac.q index 8fc3dbd..87f7adc 100644 --- a/lib/sys/io_mac.q +++ b/lib/sys/io_mac.q @@ -1,15 +1,15 @@ -read(fd Int, buffer *Any, length Int) -> Int { +read(fd int, buffer *any, length int) -> int { return syscall(0x2000003, fd, buffer, length) } -write(fd Int, buffer *Any, length Int) -> Int { +write(fd int, buffer *any, length int) -> int { return syscall(0x2000004, fd, buffer, length) } -open(path *Any, flags Int, mode Int) -> Int { +open(path *any, flags int, mode int) -> int { return syscall(0x2000005, path, flags, mode) } -close(fd Int) -> Int { +close(fd int) -> int { return syscall(0x2000006, fd) } \ No newline at end of file diff --git a/lib/sys/io_windows.q b/lib/sys/io_windows.q index 81351c3..29fc51a 100644 --- a/lib/sys/io_windows.q +++ b/lib/sys/io_windows.q @@ -1,15 +1,16 @@ extern kernel32 { - GetStdHandle(handle Int) -> Int - WriteConsoleA(fd Int, buffer *Any, length Int, written *Int) -> Bool - ReadFile(fd Int, buffer *Any, length Int) -> Bool + GetStdHandle(handle int64) -> int64 + WriteConsoleA(fd int64, buffer *any, length uint32, written *uint32) -> bool + ReadConsole(fd int64, buffer *any, length uint32, written *uint32) -> bool } -read(fd Int, buffer *Any, length Int) -> Int { - kernel32.ReadFile(fd, buffer, length) +read(fd int64, buffer *any, length int64) -> int64 { + fd = kernel32.GetStdHandle(-10 - fd) + kernel32.ReadConsole(fd, buffer, length, 0) return length } -write(fd Int, buffer *Any, length Int) -> Int { +write(fd int64, buffer *any, length int64) -> int64 { fd = kernel32.GetStdHandle(-10 - fd) kernel32.WriteConsoleA(fd, buffer, length, 0) return length diff --git a/lib/sys/mem_linux.q b/lib/sys/mem_linux.q index 5821426..14dca51 100644 --- a/lib/sys/mem_linux.q +++ b/lib/sys/mem_linux.q @@ -1,7 +1,7 @@ -mmap(address Int, length Int, protection Int, flags Int) -> *Any { +mmap(address int, length int, protection int, flags int) -> *any { return syscall(9, address, length, protection, flags) } -munmap(address *Any, length Int) -> Int { +munmap(address *any, length int) -> int { return syscall(11, address, length) } \ No newline at end of file diff --git a/lib/sys/mem_mac.q b/lib/sys/mem_mac.q index 6bf509d..2b0d33b 100644 --- a/lib/sys/mem_mac.q +++ b/lib/sys/mem_mac.q @@ -1,7 +1,7 @@ -mmap(address Int, length Int, protection Int, flags Int) -> *Any { +mmap(address int, length int, protection int, flags int) -> *any { return syscall(0x20000C5, address, length, protection, flags) } -munmap(address *Any, length Int) -> Int { +munmap(address *any, length int) -> int { return syscall(0x2000049, address, length) } \ No newline at end of file diff --git a/lib/sys/mem_windows.q b/lib/sys/mem_windows.q index 97772e9..cdcc65e 100644 --- a/lib/sys/mem_windows.q +++ b/lib/sys/mem_windows.q @@ -1,16 +1,16 @@ extern kernel32 { - VirtualAlloc(address Int, length Int, flags Int, protection Int) - VirtualFree(address *Any, length Int, type Int) -> Bool + VirtualAlloc(address int, length int, flags int, protection int) + VirtualFree(address *any, length int, type int) -> bool } const mem { decommit 0x4000 } -mmap(address Int, length Int, protection Int, flags Int) -> *Any { +mmap(address int, length int, protection int, flags int) -> *any { return kernel32.VirtualAlloc(address, length, flags, protection) } -munmap(address *Any, length Int) -> Int { +munmap(address *any, length int) -> int { return kernel32.VirtualFree(address, length, mem.decommit) } \ No newline at end of file diff --git a/lib/sys/net_linux.q b/lib/sys/net_linux.q index 6e2e53c..d4977bc 100644 --- a/lib/sys/net_linux.q +++ b/lib/sys/net_linux.q @@ -1,26 +1,26 @@ struct sockaddr_in { - sin_family Int16 - sin_port Int16 - sin_addr Int64 - sin_zero Int64 + sin_family int16 + sin_port int16 + sin_addr int64 + sin_zero int64 } -socket(family Int, type Int, protocol Int) -> Int { +socket(family int, type int, protocol int) -> int { return syscall(41, family, type, protocol) } -accept(fd Int, address *Any, length Int) -> Int { +accept(fd int, address *any, length int) -> int { return syscall(43, fd, address, length) } -bind(fd Int, address *sockaddr_in, length Int) -> Int { +bind(fd int, address *sockaddr_in, length int) -> int { return syscall(49, fd, address, length) } -listen(fd Int, backlog Int) -> Int { +listen(fd int, backlog int) -> int { return syscall(50, fd, backlog) } -setsockopt(fd Int, level Int, optname Int, optval *Any, optlen Int) -> Int { +setsockopt(fd int, level int, optname int, optval *any, optlen int) -> int { return syscall(54, fd, level, optname, optval, optlen) } \ No newline at end of file diff --git a/lib/sys/net_mac.q b/lib/sys/net_mac.q index ca0a735..f143b1e 100644 --- a/lib/sys/net_mac.q +++ b/lib/sys/net_mac.q @@ -1,23 +1,23 @@ struct sockaddr_in_bsd { - sin_len Int8 - sin_family Int8 - sin_port Int16 - sin_addr Int64 - sin_zero Int64 + sin_len int8 + sin_family int8 + sin_port int16 + sin_addr int64 + sin_zero int64 } -socket(family Int, type Int, protocol Int) -> Int { +socket(family int, type int, protocol int) -> int { return syscall(0x2000061, family, type, protocol) } -accept(fd Int, address *Any, length Int) -> Int { +accept(fd int, address *any, length int) -> int { return syscall(0x200001E, fd, address, length) } -bind(fd Int, address *sockaddr_in_bsd, length Int) -> Int { +bind(fd int, address *sockaddr_in_bsd, length int) -> int { return syscall(0x2000068, fd, address, length) } -listen(fd Int, backlog Int) -> Int { +listen(fd int, backlog int) -> int { return syscall(0x200006A, fd, backlog) } \ No newline at end of file diff --git a/lib/sys/proc_linux.q b/lib/sys/proc_linux.q index 3540b0c..6a6491b 100644 --- a/lib/sys/proc_linux.q +++ b/lib/sys/proc_linux.q @@ -1,19 +1,19 @@ -clone(flags Int, stack *Any) -> Int { +clone(flags int, stack *any) -> int { return syscall(56, flags, stack) } -fork() -> Int { +fork() -> int { return syscall(57) } -execve(path *Any, argv *Any, envp *Any) -> Int { +execve(path *any, argv *any, envp *any) -> int { return syscall(59, path, argv, envp) } -exit(status Int) { +exit(status int) { syscall(60, status) } -waitid(type Int, id Int, info *Any, options Int) -> Int { +waitid(type int, id int, info *any, options int) -> int { return syscall(247, type, id, info, options) } \ No newline at end of file diff --git a/lib/sys/proc_mac.q b/lib/sys/proc_mac.q index aa3454a..a3dc367 100644 --- a/lib/sys/proc_mac.q +++ b/lib/sys/proc_mac.q @@ -1,15 +1,15 @@ -exit(status Int) { +exit(status int) { syscall(0x2000001, status) } -fork() -> Int { +fork() -> int { return syscall(0x2000002) } -execve(path *Any, argv *Any, envp *Any) -> Int { +execve(path *any, argv *any, envp *any) -> int { return syscall(0x200003B, path, argv, envp) } -waitid(type Int, id Int, info *Any, options Int) -> Int { +waitid(type int, id int, info *any, options int) -> int { return syscall(0x20000AD, type, id, info, options) } \ No newline at end of file diff --git a/lib/sys/proc_windows.q b/lib/sys/proc_windows.q index c5c4cdc..b846e5c 100644 --- a/lib/sys/proc_windows.q +++ b/lib/sys/proc_windows.q @@ -1,7 +1,7 @@ extern kernel32 { - ExitProcess(code UInt) + ExitProcess(code uint) } -exit(code Int) { +exit(code int) { kernel32.ExitProcess(code) } \ No newline at end of file diff --git a/lib/sys/time_linux.q b/lib/sys/time_linux.q index f2957d3..3099f60 100644 --- a/lib/sys/time_linux.q +++ b/lib/sys/time_linux.q @@ -1,8 +1,8 @@ struct timespec { - seconds Int - nanoseconds Int + seconds int64 + nanoseconds int64 } -nanosleep(duration *timespec) -> Int { +nanosleep(duration *timespec) -> int { return syscall(35, duration, 0) } \ No newline at end of file diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index 98b4d6f..e57fa17 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -11,7 +11,7 @@ const clone { io 0x80000000 } -create(func *Any) -> Int { +create(func *any) -> int { size := 4096 stack := sys.mmap(0, size, 0x1|0x2, 0x02|0x20|0x100|0x20000) stack += size diff --git a/lib/thread/thread_windows.q b/lib/thread/thread_windows.q index 0faef0d..0885bd1 100644 --- a/lib/thread/thread_windows.q +++ b/lib/thread/thread_windows.q @@ -1,7 +1,7 @@ extern kernel32 { - CreateThread(attributes Int, stackSize Int, address *Any, parameter Int) -> Int + CreateThread(attributes int, stackSize int, address *any, parameter int) -> int } -create(func *Any) -> Int { +create(func *any) -> int { return kernel32.CreateThread(0, 4096, func, 0) } \ No newline at end of file diff --git a/lib/time/time.q b/lib/time/time.q index b39dbe8..9d459d8 100644 --- a/lib/time/time.q +++ b/lib/time/time.q @@ -1,6 +1,6 @@ import sys -sleep(nanoseconds Int) { +sleep(nanoseconds int) { seconds := 0 if nanoseconds >= 1000000000 { diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index d2b0cae..56ce93e 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -144,10 +144,6 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return nil, err } - if typ == types.AnyPointer && right.Token.Kind == token.Identifier && f.VariableByName(right.Token.Text(f.File.Bytes)).Type == types.AnyPointer { - typ = types.Int - } - err = f.Execute(node.Token, register, right) if register != final { @@ -155,5 +151,17 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp f.FreeRegister(register) } + _, isArray := typ.(*types.Array) + + if isArray { + typ = types.AnyPointer + } else if right.Token.Kind == token.Identifier { + rightVariable := f.VariableByName(right.Token.Text(f.File.Bytes)) + + if typ == types.AnyPointer && rightVariable.Type == types.AnyPointer { + typ = types.Int + } + } + return typ, err } diff --git a/src/types/Array.go b/src/types/Array.go index d46b216..fd6add2 100644 --- a/src/types/Array.go +++ b/src/types/Array.go @@ -9,10 +9,6 @@ type Array struct { // Name returns the type name. func (a *Array) Name() string { - if a.Of == Any { - return "[]Any" - } - return "[]" + a.Of.Name() } diff --git a/src/types/ByName.go b/src/types/ByName.go index 27ac5fc..bba1c17 100644 --- a/src/types/ByName.go +++ b/src/types/ByName.go @@ -29,28 +29,36 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { } switch name { - case "Any": - return Any - case "Bool": - return Bool - case "Int": + case "int": return Int - case "Int64": + case "int64": return Int64 - case "Int32": + case "int32": return Int32 - case "Int16": + case "int16": return Int16 - case "Int8": + case "int8": return Int8 - case "Float": - return Float - case "Float64": - return Float64 - case "Float32": - return Float32 - case "UInt": + case "uint": return UInt + case "uint64": + return UInt64 + case "uint32": + return UInt32 + case "uint16": + return UInt16 + case "uint8": + return UInt8 + case "float": + return Float + case "float64": + return Float64 + case "float32": + return Float32 + case "bool": + return Bool + case "any": + return Any } typ, exists := structs[pkg+"."+name] diff --git a/src/types/Common.go b/src/types/Common.go index aab4a96..a846b61 100644 --- a/src/types/Common.go +++ b/src/types/Common.go @@ -1,20 +1,24 @@ package types var ( - Any = &Base{name: "Any", size: 0} + Any = &Base{name: "any", size: 0} AnyArray = &Array{Of: Any} AnyPointer = &Pointer{To: Any} - Int64 = &Base{name: "Int64", size: 8} - Int32 = &Base{name: "Int32", size: 4} - Int16 = &Base{name: "Int16", size: 2} - Int8 = &Base{name: "Int8", size: 1} - Float64 = &Base{name: "Float64", size: 8} - Float32 = &Base{name: "Float32", size: 4} + Int64 = &Base{name: "int64", size: 8} + Int32 = &Base{name: "int32", size: 4} + Int16 = &Base{name: "int16", size: 2} + Int8 = &Base{name: "int8", size: 1} + Float64 = &Base{name: "float64", size: 8} + Float32 = &Base{name: "float32", size: 4} ) var ( - Bool = Int - Int = Int64 - Float = Float64 - UInt = Int + Bool = Int + Int = Int64 + Float = Float64 + UInt = Int + UInt64 = Int64 + UInt32 = Int32 + UInt16 = Int16 + UInt8 = Int8 ) diff --git a/src/types/Is.go b/src/types/Is.go index 438b917..4134701 100644 --- a/src/types/Is.go +++ b/src/types/Is.go @@ -25,5 +25,10 @@ func Is(a Type, b Type) bool { return true } + // Temporary hack for implicit casts + if a.Size() > b.Size() { + return true + } + return false } diff --git a/src/types/Pointer.go b/src/types/Pointer.go index 1cf180b..1037776 100644 --- a/src/types/Pointer.go +++ b/src/types/Pointer.go @@ -7,10 +7,6 @@ type Pointer struct { // Name returns the type name. func (p *Pointer) Name() string { - if p.To == nil { - return "*Any" - } - return "*" + p.To.Name() } diff --git a/src/types/types_test.go b/src/types/types_test.go index e19602c..e3cb03e 100644 --- a/src/types/types_test.go +++ b/src/types/types_test.go @@ -8,12 +8,12 @@ import ( ) func TestName(t *testing.T) { - assert.Equal(t, types.Int.Name(), "Int64") - assert.Equal(t, types.AnyArray.Name(), "[]Any") - assert.Equal(t, types.AnyPointer.Name(), "*Any") - assert.Equal(t, (&types.Pointer{To: types.Int}).Name(), "*Int64") - assert.Equal(t, (&types.Array{Of: types.Int}).Name(), "[]Int64") - assert.Equal(t, types.String.Name(), "[]Int8") + assert.Equal(t, types.Int.Name(), "int64") + assert.Equal(t, types.AnyArray.Name(), "[]any") + assert.Equal(t, types.AnyPointer.Name(), "*any") + assert.Equal(t, (&types.Pointer{To: types.Int}).Name(), "*int64") + assert.Equal(t, (&types.Array{Of: types.Int}).Name(), "[]int64") + assert.Equal(t, types.String.Name(), "[]int8") } func TestSize(t *testing.T) { @@ -32,7 +32,7 @@ func TestStruct(t *testing.T) { s := types.NewStruct("main", "Test") assert.Equal(t, s.Name(), "Test") assert.Equal(t, s.Size(), 0) - field := &types.Field{Name: "TestField", TypeName: "Int8"} + field := &types.Field{Name: "TestField", TypeName: "int8"} s.AddField(field) s.Update(nil) assert.Equal(t, s.Size(), 1) diff --git a/tests/errors/MissingParameter2.q b/tests/errors/MissingParameter2.q index fd8af14..827b8d8 100644 --- a/tests/errors/MissingParameter2.q +++ b/tests/errors/MissingParameter2.q @@ -1 +1 @@ -f(a Int,) -> Int { return a } \ No newline at end of file +f(a int,) -> int { return a } \ No newline at end of file diff --git a/tests/errors/MissingParameter3.q b/tests/errors/MissingParameter3.q index 6b4650b..839bf40 100644 --- a/tests/errors/MissingParameter3.q +++ b/tests/errors/MissingParameter3.q @@ -1 +1 @@ -f(,a Int) {} \ No newline at end of file +f(,a int) {} \ No newline at end of file diff --git a/tests/errors/TypeMismatch.q b/tests/errors/TypeMismatch.q index 57bdd6b..09d6221 100644 --- a/tests/errors/TypeMismatch.q +++ b/tests/errors/TypeMismatch.q @@ -2,6 +2,6 @@ main() { writeToMemory(42) } -writeToMemory(p *Any) { +writeToMemory(p *any) { p[0] = 'A' } \ No newline at end of file diff --git a/tests/errors/UnknownIdentifier3.q b/tests/errors/UnknownIdentifier3.q index 4cc4927..4295081 100644 --- a/tests/errors/UnknownIdentifier3.q +++ b/tests/errors/UnknownIdentifier3.q @@ -2,6 +2,6 @@ main() { x := 1 + f(x) } -f(x Int) -> Int { +f(x int) -> int { return x } \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 7b3812e..781b662 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -49,8 +49,8 @@ var errs = []struct { {"MissingParameter3.q", errors.MissingParameter}, {"MissingType.q", errors.MissingType}, {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, - {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*Any", Encountered: "Int64", ParameterName: "p"}}, - {"TypeMismatch2.q", &errors.TypeMismatch{Expected: "[]Any", Encountered: "Int64", ParameterName: "array"}}, + {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*any", Encountered: "int64", ParameterName: "p"}}, + {"TypeMismatch2.q", &errors.TypeMismatch{Expected: "[]any", Encountered: "int64", ParameterName: "array"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, diff --git a/tests/programs/branch-save.q b/tests/programs/branch-save.q index 75ad7bf..325880a 100644 --- a/tests/programs/branch-save.q +++ b/tests/programs/branch-save.q @@ -12,7 +12,7 @@ main() { assert b == 5 } -f(b Int) -> (Int, Int) { +f(b int) -> (int, int) { a := 0 if b >= 10 { diff --git a/tests/programs/branch.q b/tests/programs/branch.q index 171002b..92b6669 100644 --- a/tests/programs/branch.q +++ b/tests/programs/branch.q @@ -74,10 +74,10 @@ main() { sys.exit(1) } -inc(x Int) -> Int { +inc(x int) -> int { return x + 1 } -dec(x Int) -> Int { +dec(x int) -> int { return x - 1 } \ No newline at end of file diff --git a/tests/programs/chained-calls.q b/tests/programs/chained-calls.q index cd3c519..b9068c4 100644 --- a/tests/programs/chained-calls.q +++ b/tests/programs/chained-calls.q @@ -2,6 +2,6 @@ main() { assert f(1) + f(2) + f(3) == 9 } -f(x Int) -> Int { +f(x int) -> int { return x + 1 } \ No newline at end of file diff --git a/tests/programs/loop-lifetime.q b/tests/programs/loop-lifetime.q index d30489e..59f4164 100644 --- a/tests/programs/loop-lifetime.q +++ b/tests/programs/loop-lifetime.q @@ -12,6 +12,6 @@ main() { } } -f(x Int) -> Int { +f(x int) -> int { return x } \ No newline at end of file diff --git a/tests/programs/math.q b/tests/programs/math.q index 2de1b1b..cb79331 100644 --- a/tests/programs/math.q +++ b/tests/programs/math.q @@ -4,10 +4,10 @@ main() { assert result == 10 } -div(x Int, y Int) -> Int { +div(x int, y int) -> int { return x / y } -div10(x Int) -> Int { +div10(x int) -> int { return x / 10 } \ No newline at end of file diff --git a/tests/programs/negation.q b/tests/programs/negation.q index 089d4d7..07cf8ac 100644 --- a/tests/programs/negation.q +++ b/tests/programs/negation.q @@ -5,6 +5,6 @@ main() { assert neg(256) == -256 } -neg(x Int) -> Int { +neg(x int) -> int { return -x } \ No newline at end of file diff --git a/tests/programs/nested-calls.q b/tests/programs/nested-calls.q index e555b99..515a8d3 100644 --- a/tests/programs/nested-calls.q +++ b/tests/programs/nested-calls.q @@ -2,6 +2,6 @@ main() { assert f(f(f(1))) == 4 } -f(x Int) -> Int { +f(x int) -> int { return x + 1 } \ No newline at end of file diff --git a/tests/programs/op-assign.q b/tests/programs/op-assign.q index 257b6ac..85dd9c4 100644 --- a/tests/programs/op-assign.q +++ b/tests/programs/op-assign.q @@ -2,7 +2,7 @@ main() { f(10) } -f(new Int) { +f(new int) { old := new new -= 1 assert new != old diff --git a/tests/programs/param-multi.q b/tests/programs/param-multi.q index 0e19661..040ed48 100644 --- a/tests/programs/param-multi.q +++ b/tests/programs/param-multi.q @@ -2,11 +2,11 @@ main() { assert f(1, 2, 3) == 21 } -f(x Int, y Int, z Int) -> Int { +f(x int, y int, z int) -> int { w := g(4, 5, 6) return x + y + z + w } -g(x Int, y Int, z Int) -> Int { +g(x int, y int, z int) -> int { return x + y + z } \ No newline at end of file diff --git a/tests/programs/param-order.q b/tests/programs/param-order.q index 3e299b1..25ffd6d 100644 --- a/tests/programs/param-order.q +++ b/tests/programs/param-order.q @@ -2,11 +2,11 @@ main() { f1(1, 2, 3, 4, 5, 6) } -f1(a Int, b Int, c Int, d Int, e Int, f Int) { +f1(a int, b int, c int, d int, e int, f int) { f2(f, e, d, c, b, a) } -f2(a Int, b Int, c Int, d Int, e Int, f Int) { +f2(a int, b int, c int, d int, e int, f int) { assert a == 6 assert b == 5 assert c == 4 diff --git a/tests/programs/param.q b/tests/programs/param.q index 5e1628d..5efd3c1 100644 --- a/tests/programs/param.q +++ b/tests/programs/param.q @@ -2,11 +2,11 @@ main() { assert f(1) == 3 } -f(x Int) -> Int { +f(x int) -> int { y := g() return x + y } -g() -> Int { +g() -> int { return 2 } \ No newline at end of file diff --git a/tests/programs/return-multi.q b/tests/programs/return-multi.q index be81c42..1b5bd1a 100644 --- a/tests/programs/return-multi.q +++ b/tests/programs/return-multi.q @@ -15,14 +15,14 @@ main() { assert i == 1 + 4 } -reverse2(a Int, b Int) -> (Int, Int) { +reverse2(a int, b int) -> (int, int) { return b, a } -reverse3(a Int, b Int, c Int) -> (Int, Int, Int) { +reverse3(a int, b int, c int) -> (int, int, int) { return c, b, a } -mix4(a Int, b Int, c Int, d Int) -> (Int, Int, Int, Int) { +mix4(a int, b int, c int, d int) -> (int, int, int, int) { return d + a, c + b, b + c, a + d } \ No newline at end of file diff --git a/tests/programs/return.q b/tests/programs/return.q index 36095bc..abee37c 100644 --- a/tests/programs/return.q +++ b/tests/programs/return.q @@ -2,10 +2,10 @@ main() { assert f(2) == 6 } -f(x Int) -> Int { +f(x int) -> int { return x + 1 + g(x) } -g(x Int) -> Int { +g(x int) -> int { return x + 1 } \ No newline at end of file diff --git a/tests/programs/reuse.q b/tests/programs/reuse.q index 4658edb..109071e 100644 --- a/tests/programs/reuse.q +++ b/tests/programs/reuse.q @@ -2,6 +2,6 @@ main() { assert f(1) == 3 } -f(x Int) -> Int { +f(x int) -> int { return x + 1 + x } \ No newline at end of file diff --git a/tests/programs/square-sum.q b/tests/programs/square-sum.q index a9c0850..d97ba73 100644 --- a/tests/programs/square-sum.q +++ b/tests/programs/square-sum.q @@ -2,6 +2,6 @@ main() { assert f(2, 3) == 25 } -f(x Int, y Int) -> Int { +f(x int, y int) -> int { return (x + y) * (x + y) } \ No newline at end of file diff --git a/tests/programs/struct.q b/tests/programs/struct.q index 514a863..ad438c4 100644 --- a/tests/programs/struct.q +++ b/tests/programs/struct.q @@ -1,6 +1,6 @@ struct Point { - x Int - y Int + x int + y int } main() { From 930b5e16f4fac90305783cdf9772a79a6f3c1459 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Feb 2025 17:49:42 +0100 Subject: [PATCH 0785/1012] Simplified standard library --- examples/shell/shell.q | 3 +- examples/winapi/winapi.q | 2 +- lib/core/core_windows.q | 20 +++--- lib/mem/alloc_linux.q | 20 +++--- lib/mem/alloc_mac.q | 20 +++--- lib/mem/alloc_windows.q | 18 ++--- lib/net/htons.q | 2 +- lib/sys/fs_linux.q | 23 ------- lib/sys/io_linux.q | 15 ---- lib/sys/io_mac.q | 15 ---- lib/sys/mem_linux.q | 7 -- lib/sys/mem_mac.q | 7 -- lib/sys/mem_windows.q | 16 ----- lib/sys/net_linux.q | 26 ------- lib/sys/net_mac.q | 23 ------- lib/sys/proc_linux.q | 19 ------ lib/sys/proc_mac.q | 15 ---- lib/sys/proc_windows.q | 7 -- lib/sys/struct_linux.q | 11 +++ lib/sys/struct_mac.q | 7 ++ lib/sys/sys_linux.q | 91 +++++++++++++++++++++++++ lib/sys/sys_mac.q | 55 +++++++++++++++ lib/sys/{io_windows.q => sys_windows.q} | 26 +++++-- lib/sys/time_linux.q | 8 --- src/core/ToNumber.go | 7 +- 25 files changed, 232 insertions(+), 231 deletions(-) delete mode 100644 lib/sys/fs_linux.q delete mode 100644 lib/sys/io_linux.q delete mode 100644 lib/sys/io_mac.q delete mode 100644 lib/sys/mem_linux.q delete mode 100644 lib/sys/mem_mac.q delete mode 100644 lib/sys/mem_windows.q delete mode 100644 lib/sys/net_linux.q delete mode 100644 lib/sys/net_mac.q delete mode 100644 lib/sys/proc_linux.q delete mode 100644 lib/sys/proc_mac.q delete mode 100644 lib/sys/proc_windows.q create mode 100644 lib/sys/struct_linux.q create mode 100644 lib/sys/struct_mac.q create mode 100644 lib/sys/sys_linux.q create mode 100644 lib/sys/sys_mac.q rename lib/sys/{io_windows.q => sys_windows.q} (56%) delete mode 100644 lib/sys/time_linux.q diff --git a/examples/shell/shell.q b/examples/shell/shell.q index 509b53e..f484486 100644 --- a/examples/shell/shell.q +++ b/examples/shell/shell.q @@ -13,7 +13,6 @@ const state { main() { length := 256 command := mem.alloc(length) - siginfo := mem.alloc(128) loop { io.out("λ ") @@ -31,6 +30,6 @@ main() { return } - sys.waitid(idtype.pid, pid, siginfo, state.exited) + sys.waitid(idtype.pid, pid, 0, state.exited) } } \ No newline at end of file diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q index c7a9c8e..be63231 100644 --- a/examples/winapi/winapi.q +++ b/examples/winapi/winapi.q @@ -1,5 +1,5 @@ extern user32 { - MessageBoxA(window *any, text *int8, title *int8, flags uint) + MessageBoxA(window *any, text *int8, title *int8, flags uint) -> int } main() { diff --git a/lib/core/core_windows.q b/lib/core/core_windows.q index b0acd58..575b41e 100644 --- a/lib/core/core_windows.q +++ b/lib/core/core_windows.q @@ -1,13 +1,3 @@ -extern kernel32 { - SetConsoleCP(cp uint) - SetConsoleOutputCP(cp uint) - ExitProcess(code uint) -} - -const cp { - utf8 65001 -} - init() { kernel32.SetConsoleCP(cp.utf8) kernel32.SetConsoleOutputCP(cp.utf8) @@ -17,4 +7,14 @@ init() { exit() { kernel32.ExitProcess(0) +} + +const cp { + utf8 65001 +} + +extern kernel32 { + SetConsoleCP(cp uint) + SetConsoleOutputCP(cp uint) + ExitProcess(code uint) } \ No newline at end of file diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_linux.q index 78e403c..f7178a2 100644 --- a/lib/mem/alloc_linux.q +++ b/lib/mem/alloc_linux.q @@ -1,15 +1,5 @@ import sys -const prot { - read 0x1 - write 0x2 -} - -const map { - private 0x02 - anonymous 0x20 -} - alloc(length int) -> []int8 { x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) @@ -19,4 +9,14 @@ alloc(length int) -> []int8 { store(x, 8, length) return x + 8 +} + +const prot { + read 0x1 + write 0x2 +} + +const map { + private 0x02 + anonymous 0x20 } \ No newline at end of file diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q index e45eaf4..fe0f500 100644 --- a/lib/mem/alloc_mac.q +++ b/lib/mem/alloc_mac.q @@ -1,15 +1,5 @@ import sys -const prot { - read 0x1 - write 0x2 -} - -const map { - private 0x02 - anonymous 0x1000 -} - alloc(length int) -> []int8 { x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) @@ -19,4 +9,14 @@ alloc(length int) -> []int8 { store(x, 8, length) return x + 8 +} + +const prot { + read 0x1 + write 0x2 +} + +const map { + private 0x02 + anonymous 0x1000 } \ No newline at end of file diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index 3a4b0a4..c5b74de 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,14 +1,5 @@ import sys -const page { - readwrite 0x0004 -} - -const mem { - commit 0x1000 - reserve 0x2000 -} - alloc(length int) -> []int8 { x := sys.mmap(0, length+8, page.readwrite, mem.commit|mem.reserve) @@ -18,4 +9,13 @@ alloc(length int) -> []int8 { store(x, 8, length) return x + 8 +} + +const page { + readwrite 0x0004 +} + +const mem { + commit 0x1000 + reserve 0x2000 } \ No newline at end of file diff --git a/lib/net/htons.q b/lib/net/htons.q index 8e414c6..fe2c047 100644 --- a/lib/net/htons.q +++ b/lib/net/htons.q @@ -1,3 +1,3 @@ -htons(num int) -> int { +htons(num uint16) -> uint16 { return ((num & 0xFF) << 8) | (num >> 8) } \ No newline at end of file diff --git a/lib/sys/fs_linux.q b/lib/sys/fs_linux.q deleted file mode 100644 index 2eda9ff..0000000 --- a/lib/sys/fs_linux.q +++ /dev/null @@ -1,23 +0,0 @@ -getcwd(buffer *any, length int) -> int { - return syscall(79, buffer, length) -} - -chdir(path *any) -> int { - return syscall(80, path) -} - -rename(old *any, new *any) -> int { - return syscall(82, old, new) -} - -mkdir(path *any, mode int) -> int { - return syscall(83, path, mode) -} - -rmdir(path *any) -> int { - return syscall(84, path) -} - -unlink(file *any) -> int { - return syscall(87, file) -} \ No newline at end of file diff --git a/lib/sys/io_linux.q b/lib/sys/io_linux.q deleted file mode 100644 index d70e220..0000000 --- a/lib/sys/io_linux.q +++ /dev/null @@ -1,15 +0,0 @@ -read(fd int, buffer *any, length int) -> int { - return syscall(0, fd, buffer, length) -} - -write(fd int, buffer *any, length int) -> int { - return syscall(1, fd, buffer, length) -} - -open(path *any, flags int, mode int) -> int { - return syscall(2, path, flags, mode) -} - -close(fd int) -> int { - return syscall(3, fd) -} \ No newline at end of file diff --git a/lib/sys/io_mac.q b/lib/sys/io_mac.q deleted file mode 100644 index 87f7adc..0000000 --- a/lib/sys/io_mac.q +++ /dev/null @@ -1,15 +0,0 @@ -read(fd int, buffer *any, length int) -> int { - return syscall(0x2000003, fd, buffer, length) -} - -write(fd int, buffer *any, length int) -> int { - return syscall(0x2000004, fd, buffer, length) -} - -open(path *any, flags int, mode int) -> int { - return syscall(0x2000005, path, flags, mode) -} - -close(fd int) -> int { - return syscall(0x2000006, fd) -} \ No newline at end of file diff --git a/lib/sys/mem_linux.q b/lib/sys/mem_linux.q deleted file mode 100644 index 14dca51..0000000 --- a/lib/sys/mem_linux.q +++ /dev/null @@ -1,7 +0,0 @@ -mmap(address int, length int, protection int, flags int) -> *any { - return syscall(9, address, length, protection, flags) -} - -munmap(address *any, length int) -> int { - return syscall(11, address, length) -} \ No newline at end of file diff --git a/lib/sys/mem_mac.q b/lib/sys/mem_mac.q deleted file mode 100644 index 2b0d33b..0000000 --- a/lib/sys/mem_mac.q +++ /dev/null @@ -1,7 +0,0 @@ -mmap(address int, length int, protection int, flags int) -> *any { - return syscall(0x20000C5, address, length, protection, flags) -} - -munmap(address *any, length int) -> int { - return syscall(0x2000049, address, length) -} \ No newline at end of file diff --git a/lib/sys/mem_windows.q b/lib/sys/mem_windows.q deleted file mode 100644 index cdcc65e..0000000 --- a/lib/sys/mem_windows.q +++ /dev/null @@ -1,16 +0,0 @@ -extern kernel32 { - VirtualAlloc(address int, length int, flags int, protection int) - VirtualFree(address *any, length int, type int) -> bool -} - -const mem { - decommit 0x4000 -} - -mmap(address int, length int, protection int, flags int) -> *any { - return kernel32.VirtualAlloc(address, length, flags, protection) -} - -munmap(address *any, length int) -> int { - return kernel32.VirtualFree(address, length, mem.decommit) -} \ No newline at end of file diff --git a/lib/sys/net_linux.q b/lib/sys/net_linux.q deleted file mode 100644 index d4977bc..0000000 --- a/lib/sys/net_linux.q +++ /dev/null @@ -1,26 +0,0 @@ -struct sockaddr_in { - sin_family int16 - sin_port int16 - sin_addr int64 - sin_zero int64 -} - -socket(family int, type int, protocol int) -> int { - return syscall(41, family, type, protocol) -} - -accept(fd int, address *any, length int) -> int { - return syscall(43, fd, address, length) -} - -bind(fd int, address *sockaddr_in, length int) -> int { - return syscall(49, fd, address, length) -} - -listen(fd int, backlog int) -> int { - return syscall(50, fd, backlog) -} - -setsockopt(fd int, level int, optname int, optval *any, optlen int) -> int { - return syscall(54, fd, level, optname, optval, optlen) -} \ No newline at end of file diff --git a/lib/sys/net_mac.q b/lib/sys/net_mac.q deleted file mode 100644 index f143b1e..0000000 --- a/lib/sys/net_mac.q +++ /dev/null @@ -1,23 +0,0 @@ -struct sockaddr_in_bsd { - sin_len int8 - sin_family int8 - sin_port int16 - sin_addr int64 - sin_zero int64 -} - -socket(family int, type int, protocol int) -> int { - return syscall(0x2000061, family, type, protocol) -} - -accept(fd int, address *any, length int) -> int { - return syscall(0x200001E, fd, address, length) -} - -bind(fd int, address *sockaddr_in_bsd, length int) -> int { - return syscall(0x2000068, fd, address, length) -} - -listen(fd int, backlog int) -> int { - return syscall(0x200006A, fd, backlog) -} \ No newline at end of file diff --git a/lib/sys/proc_linux.q b/lib/sys/proc_linux.q deleted file mode 100644 index 6a6491b..0000000 --- a/lib/sys/proc_linux.q +++ /dev/null @@ -1,19 +0,0 @@ -clone(flags int, stack *any) -> int { - return syscall(56, flags, stack) -} - -fork() -> int { - return syscall(57) -} - -execve(path *any, argv *any, envp *any) -> int { - return syscall(59, path, argv, envp) -} - -exit(status int) { - syscall(60, status) -} - -waitid(type int, id int, info *any, options int) -> int { - return syscall(247, type, id, info, options) -} \ No newline at end of file diff --git a/lib/sys/proc_mac.q b/lib/sys/proc_mac.q deleted file mode 100644 index a3dc367..0000000 --- a/lib/sys/proc_mac.q +++ /dev/null @@ -1,15 +0,0 @@ -exit(status int) { - syscall(0x2000001, status) -} - -fork() -> int { - return syscall(0x2000002) -} - -execve(path *any, argv *any, envp *any) -> int { - return syscall(0x200003B, path, argv, envp) -} - -waitid(type int, id int, info *any, options int) -> int { - return syscall(0x20000AD, type, id, info, options) -} \ No newline at end of file diff --git a/lib/sys/proc_windows.q b/lib/sys/proc_windows.q deleted file mode 100644 index b846e5c..0000000 --- a/lib/sys/proc_windows.q +++ /dev/null @@ -1,7 +0,0 @@ -extern kernel32 { - ExitProcess(code uint) -} - -exit(code int) { - kernel32.ExitProcess(code) -} \ No newline at end of file diff --git a/lib/sys/struct_linux.q b/lib/sys/struct_linux.q new file mode 100644 index 0000000..8a22345 --- /dev/null +++ b/lib/sys/struct_linux.q @@ -0,0 +1,11 @@ +struct sockaddr_in { + sin_family int16 + sin_port int16 + sin_addr int64 + sin_zero int64 +} + +struct timespec { + seconds int64 + nanoseconds int64 +} \ No newline at end of file diff --git a/lib/sys/struct_mac.q b/lib/sys/struct_mac.q new file mode 100644 index 0000000..480199a --- /dev/null +++ b/lib/sys/struct_mac.q @@ -0,0 +1,7 @@ +struct sockaddr_in_bsd { + sin_len int8 + sin_family int8 + sin_port int16 + sin_addr int64 + sin_zero int64 +} \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q new file mode 100644 index 0000000..f35439b --- /dev/null +++ b/lib/sys/sys_linux.q @@ -0,0 +1,91 @@ +read(fd int, buffer *any, length int) -> int { + return syscall(0, fd, buffer, length) +} + +write(fd int, buffer *any, length int) -> int { + return syscall(1, fd, buffer, length) +} + +open(path *any, flags int, mode int) -> int { + return syscall(2, path, flags, mode) +} + +close(fd int) -> int { + return syscall(3, fd) +} + +mmap(address int, length int, protection int, flags int) -> *any { + return syscall(9, address, length, protection, flags) +} + +munmap(address *any, length int) -> int { + return syscall(11, address, length) +} + +clone(flags int, stack *any) -> int { + return syscall(56, flags, stack) +} + +fork() -> int { + return syscall(57) +} + +execve(path *any, argv *any, envp *any) -> int { + return syscall(59, path, argv, envp) +} + +exit(status int) { + syscall(60, status) +} + +waitid(type int, id int, info *any, options int) -> int { + return syscall(247, type, id, info, options) +} + +socket(family int, type int, protocol int) -> int { + return syscall(41, family, type, protocol) +} + +accept(fd int, address *any, length int) -> int { + return syscall(43, fd, address, length) +} + +bind(fd int, address *sockaddr_in, length int) -> int { + return syscall(49, fd, address, length) +} + +listen(fd int, backlog int) -> int { + return syscall(50, fd, backlog) +} + +setsockopt(fd int, level int, optname int, optval *any, optlen int) -> int { + return syscall(54, fd, level, optname, optval, optlen) +} + +getcwd(buffer *any, length int) -> int { + return syscall(79, buffer, length) +} + +chdir(path *any) -> int { + return syscall(80, path) +} + +rename(old *any, new *any) -> int { + return syscall(82, old, new) +} + +mkdir(path *any, mode int) -> int { + return syscall(83, path, mode) +} + +rmdir(path *any) -> int { + return syscall(84, path) +} + +unlink(file *any) -> int { + return syscall(87, file) +} + +nanosleep(duration *timespec) -> int { + return syscall(35, duration, 0) +} \ No newline at end of file diff --git a/lib/sys/sys_mac.q b/lib/sys/sys_mac.q new file mode 100644 index 0000000..6c31517 --- /dev/null +++ b/lib/sys/sys_mac.q @@ -0,0 +1,55 @@ +read(fd int, buffer *any, length int) -> int { + return syscall(0x2000003, fd, buffer, length) +} + +write(fd int, buffer *any, length int) -> int { + return syscall(0x2000004, fd, buffer, length) +} + +open(path *any, flags int, mode int) -> int { + return syscall(0x2000005, path, flags, mode) +} + +close(fd int) -> int { + return syscall(0x2000006, fd) +} + +mmap(address int, length int, protection int, flags int) -> *any { + return syscall(0x20000C5, address, length, protection, flags) +} + +munmap(address *any, length int) -> int { + return syscall(0x2000049, address, length) +} + +exit(status int) { + syscall(0x2000001, status) +} + +fork() -> int { + return syscall(0x2000002) +} + +execve(path *any, argv *any, envp *any) -> int { + return syscall(0x200003B, path, argv, envp) +} + +waitid(type int, id int, info *any, options int) -> int { + return syscall(0x20000AD, type, id, info, options) +} + +socket(family int, type int, protocol int) -> int { + return syscall(0x2000061, family, type, protocol) +} + +accept(fd int, address *any, length int) -> int { + return syscall(0x200001E, fd, address, length) +} + +bind(fd int, address *sockaddr_in_bsd, length int) -> int { + return syscall(0x2000068, fd, address, length) +} + +listen(fd int, backlog int) -> int { + return syscall(0x200006A, fd, backlog) +} \ No newline at end of file diff --git a/lib/sys/io_windows.q b/lib/sys/sys_windows.q similarity index 56% rename from lib/sys/io_windows.q rename to lib/sys/sys_windows.q index 29fc51a..1d67740 100644 --- a/lib/sys/io_windows.q +++ b/lib/sys/sys_windows.q @@ -1,9 +1,3 @@ -extern kernel32 { - GetStdHandle(handle int64) -> int64 - WriteConsoleA(fd int64, buffer *any, length uint32, written *uint32) -> bool - ReadConsole(fd int64, buffer *any, length uint32, written *uint32) -> bool -} - read(fd int64, buffer *any, length int64) -> int64 { fd = kernel32.GetStdHandle(-10 - fd) kernel32.ReadConsole(fd, buffer, length, 0) @@ -14,4 +8,24 @@ write(fd int64, buffer *any, length int64) -> int64 { fd = kernel32.GetStdHandle(-10 - fd) kernel32.WriteConsoleA(fd, buffer, length, 0) return length +} + +mmap(address int, length int, protection int, flags int) -> *any { + return kernel32.VirtualAlloc(address, length, flags, protection) +} + +munmap(address *any, length int) -> int { + return kernel32.VirtualFree(address, length, mem.decommit) +} + +const mem { + decommit 0x4000 +} + +extern kernel32 { + GetStdHandle(handle int64) -> int64 + ReadConsole(fd int64, buffer *any, length uint32, written *uint32) -> bool + VirtualAlloc(address int, length int, flags int, protection int) + VirtualFree(address *any, length int, type int) -> bool + WriteConsoleA(fd int64, buffer *any, length uint32, written *uint32) -> bool } \ No newline at end of file diff --git a/lib/sys/time_linux.q b/lib/sys/time_linux.q deleted file mode 100644 index 3099f60..0000000 --- a/lib/sys/time_linux.q +++ /dev/null @@ -1,8 +0,0 @@ -struct timespec { - seconds int64 - nanoseconds int64 -} - -nanosleep(duration *timespec) -> int { - return syscall(35, duration, 0) -} \ No newline at end of file diff --git a/src/core/ToNumber.go b/src/core/ToNumber.go index 95d65a2..da73904 100644 --- a/src/core/ToNumber.go +++ b/src/core/ToNumber.go @@ -31,7 +31,12 @@ func (f *Function) ToNumber(t token.Token) (int, error) { } number, err := strconv.Atoi(digits) - return number, err + + if err != nil { + return 0, errors.New(err, f.File, t.Position) + } + + return number, nil case token.Rune: r := t.Bytes(f.File.Bytes) From b7685fd7ec093ddebeb57d975f1cb1865d3202a6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Feb 2025 17:49:42 +0100 Subject: [PATCH 0786/1012] Simplified standard library --- examples/shell/shell.q | 3 +- examples/winapi/winapi.q | 2 +- lib/core/core_windows.q | 20 +++--- lib/mem/alloc_linux.q | 20 +++--- lib/mem/alloc_mac.q | 20 +++--- lib/mem/alloc_windows.q | 18 ++--- lib/net/htons.q | 2 +- lib/sys/fs_linux.q | 23 ------- lib/sys/io_linux.q | 15 ---- lib/sys/io_mac.q | 15 ---- lib/sys/mem_linux.q | 7 -- lib/sys/mem_mac.q | 7 -- lib/sys/mem_windows.q | 16 ----- lib/sys/net_linux.q | 26 ------- lib/sys/net_mac.q | 23 ------- lib/sys/proc_linux.q | 19 ------ lib/sys/proc_mac.q | 15 ---- lib/sys/proc_windows.q | 7 -- lib/sys/struct_linux.q | 11 +++ lib/sys/struct_mac.q | 7 ++ lib/sys/sys_linux.q | 91 +++++++++++++++++++++++++ lib/sys/sys_mac.q | 55 +++++++++++++++ lib/sys/{io_windows.q => sys_windows.q} | 26 +++++-- lib/sys/time_linux.q | 8 --- src/core/ToNumber.go | 7 +- 25 files changed, 232 insertions(+), 231 deletions(-) delete mode 100644 lib/sys/fs_linux.q delete mode 100644 lib/sys/io_linux.q delete mode 100644 lib/sys/io_mac.q delete mode 100644 lib/sys/mem_linux.q delete mode 100644 lib/sys/mem_mac.q delete mode 100644 lib/sys/mem_windows.q delete mode 100644 lib/sys/net_linux.q delete mode 100644 lib/sys/net_mac.q delete mode 100644 lib/sys/proc_linux.q delete mode 100644 lib/sys/proc_mac.q delete mode 100644 lib/sys/proc_windows.q create mode 100644 lib/sys/struct_linux.q create mode 100644 lib/sys/struct_mac.q create mode 100644 lib/sys/sys_linux.q create mode 100644 lib/sys/sys_mac.q rename lib/sys/{io_windows.q => sys_windows.q} (56%) delete mode 100644 lib/sys/time_linux.q diff --git a/examples/shell/shell.q b/examples/shell/shell.q index 509b53e..f484486 100644 --- a/examples/shell/shell.q +++ b/examples/shell/shell.q @@ -13,7 +13,6 @@ const state { main() { length := 256 command := mem.alloc(length) - siginfo := mem.alloc(128) loop { io.out("λ ") @@ -31,6 +30,6 @@ main() { return } - sys.waitid(idtype.pid, pid, siginfo, state.exited) + sys.waitid(idtype.pid, pid, 0, state.exited) } } \ No newline at end of file diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q index c7a9c8e..be63231 100644 --- a/examples/winapi/winapi.q +++ b/examples/winapi/winapi.q @@ -1,5 +1,5 @@ extern user32 { - MessageBoxA(window *any, text *int8, title *int8, flags uint) + MessageBoxA(window *any, text *int8, title *int8, flags uint) -> int } main() { diff --git a/lib/core/core_windows.q b/lib/core/core_windows.q index b0acd58..575b41e 100644 --- a/lib/core/core_windows.q +++ b/lib/core/core_windows.q @@ -1,13 +1,3 @@ -extern kernel32 { - SetConsoleCP(cp uint) - SetConsoleOutputCP(cp uint) - ExitProcess(code uint) -} - -const cp { - utf8 65001 -} - init() { kernel32.SetConsoleCP(cp.utf8) kernel32.SetConsoleOutputCP(cp.utf8) @@ -17,4 +7,14 @@ init() { exit() { kernel32.ExitProcess(0) +} + +const cp { + utf8 65001 +} + +extern kernel32 { + SetConsoleCP(cp uint) + SetConsoleOutputCP(cp uint) + ExitProcess(code uint) } \ No newline at end of file diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_linux.q index 78e403c..f7178a2 100644 --- a/lib/mem/alloc_linux.q +++ b/lib/mem/alloc_linux.q @@ -1,15 +1,5 @@ import sys -const prot { - read 0x1 - write 0x2 -} - -const map { - private 0x02 - anonymous 0x20 -} - alloc(length int) -> []int8 { x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) @@ -19,4 +9,14 @@ alloc(length int) -> []int8 { store(x, 8, length) return x + 8 +} + +const prot { + read 0x1 + write 0x2 +} + +const map { + private 0x02 + anonymous 0x20 } \ No newline at end of file diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q index e45eaf4..fe0f500 100644 --- a/lib/mem/alloc_mac.q +++ b/lib/mem/alloc_mac.q @@ -1,15 +1,5 @@ import sys -const prot { - read 0x1 - write 0x2 -} - -const map { - private 0x02 - anonymous 0x1000 -} - alloc(length int) -> []int8 { x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) @@ -19,4 +9,14 @@ alloc(length int) -> []int8 { store(x, 8, length) return x + 8 +} + +const prot { + read 0x1 + write 0x2 +} + +const map { + private 0x02 + anonymous 0x1000 } \ No newline at end of file diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index 3a4b0a4..c5b74de 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,14 +1,5 @@ import sys -const page { - readwrite 0x0004 -} - -const mem { - commit 0x1000 - reserve 0x2000 -} - alloc(length int) -> []int8 { x := sys.mmap(0, length+8, page.readwrite, mem.commit|mem.reserve) @@ -18,4 +9,13 @@ alloc(length int) -> []int8 { store(x, 8, length) return x + 8 +} + +const page { + readwrite 0x0004 +} + +const mem { + commit 0x1000 + reserve 0x2000 } \ No newline at end of file diff --git a/lib/net/htons.q b/lib/net/htons.q index 8e414c6..fe2c047 100644 --- a/lib/net/htons.q +++ b/lib/net/htons.q @@ -1,3 +1,3 @@ -htons(num int) -> int { +htons(num uint16) -> uint16 { return ((num & 0xFF) << 8) | (num >> 8) } \ No newline at end of file diff --git a/lib/sys/fs_linux.q b/lib/sys/fs_linux.q deleted file mode 100644 index 2eda9ff..0000000 --- a/lib/sys/fs_linux.q +++ /dev/null @@ -1,23 +0,0 @@ -getcwd(buffer *any, length int) -> int { - return syscall(79, buffer, length) -} - -chdir(path *any) -> int { - return syscall(80, path) -} - -rename(old *any, new *any) -> int { - return syscall(82, old, new) -} - -mkdir(path *any, mode int) -> int { - return syscall(83, path, mode) -} - -rmdir(path *any) -> int { - return syscall(84, path) -} - -unlink(file *any) -> int { - return syscall(87, file) -} \ No newline at end of file diff --git a/lib/sys/io_linux.q b/lib/sys/io_linux.q deleted file mode 100644 index d70e220..0000000 --- a/lib/sys/io_linux.q +++ /dev/null @@ -1,15 +0,0 @@ -read(fd int, buffer *any, length int) -> int { - return syscall(0, fd, buffer, length) -} - -write(fd int, buffer *any, length int) -> int { - return syscall(1, fd, buffer, length) -} - -open(path *any, flags int, mode int) -> int { - return syscall(2, path, flags, mode) -} - -close(fd int) -> int { - return syscall(3, fd) -} \ No newline at end of file diff --git a/lib/sys/io_mac.q b/lib/sys/io_mac.q deleted file mode 100644 index 87f7adc..0000000 --- a/lib/sys/io_mac.q +++ /dev/null @@ -1,15 +0,0 @@ -read(fd int, buffer *any, length int) -> int { - return syscall(0x2000003, fd, buffer, length) -} - -write(fd int, buffer *any, length int) -> int { - return syscall(0x2000004, fd, buffer, length) -} - -open(path *any, flags int, mode int) -> int { - return syscall(0x2000005, path, flags, mode) -} - -close(fd int) -> int { - return syscall(0x2000006, fd) -} \ No newline at end of file diff --git a/lib/sys/mem_linux.q b/lib/sys/mem_linux.q deleted file mode 100644 index 14dca51..0000000 --- a/lib/sys/mem_linux.q +++ /dev/null @@ -1,7 +0,0 @@ -mmap(address int, length int, protection int, flags int) -> *any { - return syscall(9, address, length, protection, flags) -} - -munmap(address *any, length int) -> int { - return syscall(11, address, length) -} \ No newline at end of file diff --git a/lib/sys/mem_mac.q b/lib/sys/mem_mac.q deleted file mode 100644 index 2b0d33b..0000000 --- a/lib/sys/mem_mac.q +++ /dev/null @@ -1,7 +0,0 @@ -mmap(address int, length int, protection int, flags int) -> *any { - return syscall(0x20000C5, address, length, protection, flags) -} - -munmap(address *any, length int) -> int { - return syscall(0x2000049, address, length) -} \ No newline at end of file diff --git a/lib/sys/mem_windows.q b/lib/sys/mem_windows.q deleted file mode 100644 index cdcc65e..0000000 --- a/lib/sys/mem_windows.q +++ /dev/null @@ -1,16 +0,0 @@ -extern kernel32 { - VirtualAlloc(address int, length int, flags int, protection int) - VirtualFree(address *any, length int, type int) -> bool -} - -const mem { - decommit 0x4000 -} - -mmap(address int, length int, protection int, flags int) -> *any { - return kernel32.VirtualAlloc(address, length, flags, protection) -} - -munmap(address *any, length int) -> int { - return kernel32.VirtualFree(address, length, mem.decommit) -} \ No newline at end of file diff --git a/lib/sys/net_linux.q b/lib/sys/net_linux.q deleted file mode 100644 index d4977bc..0000000 --- a/lib/sys/net_linux.q +++ /dev/null @@ -1,26 +0,0 @@ -struct sockaddr_in { - sin_family int16 - sin_port int16 - sin_addr int64 - sin_zero int64 -} - -socket(family int, type int, protocol int) -> int { - return syscall(41, family, type, protocol) -} - -accept(fd int, address *any, length int) -> int { - return syscall(43, fd, address, length) -} - -bind(fd int, address *sockaddr_in, length int) -> int { - return syscall(49, fd, address, length) -} - -listen(fd int, backlog int) -> int { - return syscall(50, fd, backlog) -} - -setsockopt(fd int, level int, optname int, optval *any, optlen int) -> int { - return syscall(54, fd, level, optname, optval, optlen) -} \ No newline at end of file diff --git a/lib/sys/net_mac.q b/lib/sys/net_mac.q deleted file mode 100644 index f143b1e..0000000 --- a/lib/sys/net_mac.q +++ /dev/null @@ -1,23 +0,0 @@ -struct sockaddr_in_bsd { - sin_len int8 - sin_family int8 - sin_port int16 - sin_addr int64 - sin_zero int64 -} - -socket(family int, type int, protocol int) -> int { - return syscall(0x2000061, family, type, protocol) -} - -accept(fd int, address *any, length int) -> int { - return syscall(0x200001E, fd, address, length) -} - -bind(fd int, address *sockaddr_in_bsd, length int) -> int { - return syscall(0x2000068, fd, address, length) -} - -listen(fd int, backlog int) -> int { - return syscall(0x200006A, fd, backlog) -} \ No newline at end of file diff --git a/lib/sys/proc_linux.q b/lib/sys/proc_linux.q deleted file mode 100644 index 6a6491b..0000000 --- a/lib/sys/proc_linux.q +++ /dev/null @@ -1,19 +0,0 @@ -clone(flags int, stack *any) -> int { - return syscall(56, flags, stack) -} - -fork() -> int { - return syscall(57) -} - -execve(path *any, argv *any, envp *any) -> int { - return syscall(59, path, argv, envp) -} - -exit(status int) { - syscall(60, status) -} - -waitid(type int, id int, info *any, options int) -> int { - return syscall(247, type, id, info, options) -} \ No newline at end of file diff --git a/lib/sys/proc_mac.q b/lib/sys/proc_mac.q deleted file mode 100644 index a3dc367..0000000 --- a/lib/sys/proc_mac.q +++ /dev/null @@ -1,15 +0,0 @@ -exit(status int) { - syscall(0x2000001, status) -} - -fork() -> int { - return syscall(0x2000002) -} - -execve(path *any, argv *any, envp *any) -> int { - return syscall(0x200003B, path, argv, envp) -} - -waitid(type int, id int, info *any, options int) -> int { - return syscall(0x20000AD, type, id, info, options) -} \ No newline at end of file diff --git a/lib/sys/proc_windows.q b/lib/sys/proc_windows.q deleted file mode 100644 index b846e5c..0000000 --- a/lib/sys/proc_windows.q +++ /dev/null @@ -1,7 +0,0 @@ -extern kernel32 { - ExitProcess(code uint) -} - -exit(code int) { - kernel32.ExitProcess(code) -} \ No newline at end of file diff --git a/lib/sys/struct_linux.q b/lib/sys/struct_linux.q new file mode 100644 index 0000000..8a22345 --- /dev/null +++ b/lib/sys/struct_linux.q @@ -0,0 +1,11 @@ +struct sockaddr_in { + sin_family int16 + sin_port int16 + sin_addr int64 + sin_zero int64 +} + +struct timespec { + seconds int64 + nanoseconds int64 +} \ No newline at end of file diff --git a/lib/sys/struct_mac.q b/lib/sys/struct_mac.q new file mode 100644 index 0000000..480199a --- /dev/null +++ b/lib/sys/struct_mac.q @@ -0,0 +1,7 @@ +struct sockaddr_in_bsd { + sin_len int8 + sin_family int8 + sin_port int16 + sin_addr int64 + sin_zero int64 +} \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q new file mode 100644 index 0000000..f35439b --- /dev/null +++ b/lib/sys/sys_linux.q @@ -0,0 +1,91 @@ +read(fd int, buffer *any, length int) -> int { + return syscall(0, fd, buffer, length) +} + +write(fd int, buffer *any, length int) -> int { + return syscall(1, fd, buffer, length) +} + +open(path *any, flags int, mode int) -> int { + return syscall(2, path, flags, mode) +} + +close(fd int) -> int { + return syscall(3, fd) +} + +mmap(address int, length int, protection int, flags int) -> *any { + return syscall(9, address, length, protection, flags) +} + +munmap(address *any, length int) -> int { + return syscall(11, address, length) +} + +clone(flags int, stack *any) -> int { + return syscall(56, flags, stack) +} + +fork() -> int { + return syscall(57) +} + +execve(path *any, argv *any, envp *any) -> int { + return syscall(59, path, argv, envp) +} + +exit(status int) { + syscall(60, status) +} + +waitid(type int, id int, info *any, options int) -> int { + return syscall(247, type, id, info, options) +} + +socket(family int, type int, protocol int) -> int { + return syscall(41, family, type, protocol) +} + +accept(fd int, address *any, length int) -> int { + return syscall(43, fd, address, length) +} + +bind(fd int, address *sockaddr_in, length int) -> int { + return syscall(49, fd, address, length) +} + +listen(fd int, backlog int) -> int { + return syscall(50, fd, backlog) +} + +setsockopt(fd int, level int, optname int, optval *any, optlen int) -> int { + return syscall(54, fd, level, optname, optval, optlen) +} + +getcwd(buffer *any, length int) -> int { + return syscall(79, buffer, length) +} + +chdir(path *any) -> int { + return syscall(80, path) +} + +rename(old *any, new *any) -> int { + return syscall(82, old, new) +} + +mkdir(path *any, mode int) -> int { + return syscall(83, path, mode) +} + +rmdir(path *any) -> int { + return syscall(84, path) +} + +unlink(file *any) -> int { + return syscall(87, file) +} + +nanosleep(duration *timespec) -> int { + return syscall(35, duration, 0) +} \ No newline at end of file diff --git a/lib/sys/sys_mac.q b/lib/sys/sys_mac.q new file mode 100644 index 0000000..6c31517 --- /dev/null +++ b/lib/sys/sys_mac.q @@ -0,0 +1,55 @@ +read(fd int, buffer *any, length int) -> int { + return syscall(0x2000003, fd, buffer, length) +} + +write(fd int, buffer *any, length int) -> int { + return syscall(0x2000004, fd, buffer, length) +} + +open(path *any, flags int, mode int) -> int { + return syscall(0x2000005, path, flags, mode) +} + +close(fd int) -> int { + return syscall(0x2000006, fd) +} + +mmap(address int, length int, protection int, flags int) -> *any { + return syscall(0x20000C5, address, length, protection, flags) +} + +munmap(address *any, length int) -> int { + return syscall(0x2000049, address, length) +} + +exit(status int) { + syscall(0x2000001, status) +} + +fork() -> int { + return syscall(0x2000002) +} + +execve(path *any, argv *any, envp *any) -> int { + return syscall(0x200003B, path, argv, envp) +} + +waitid(type int, id int, info *any, options int) -> int { + return syscall(0x20000AD, type, id, info, options) +} + +socket(family int, type int, protocol int) -> int { + return syscall(0x2000061, family, type, protocol) +} + +accept(fd int, address *any, length int) -> int { + return syscall(0x200001E, fd, address, length) +} + +bind(fd int, address *sockaddr_in_bsd, length int) -> int { + return syscall(0x2000068, fd, address, length) +} + +listen(fd int, backlog int) -> int { + return syscall(0x200006A, fd, backlog) +} \ No newline at end of file diff --git a/lib/sys/io_windows.q b/lib/sys/sys_windows.q similarity index 56% rename from lib/sys/io_windows.q rename to lib/sys/sys_windows.q index 29fc51a..1d67740 100644 --- a/lib/sys/io_windows.q +++ b/lib/sys/sys_windows.q @@ -1,9 +1,3 @@ -extern kernel32 { - GetStdHandle(handle int64) -> int64 - WriteConsoleA(fd int64, buffer *any, length uint32, written *uint32) -> bool - ReadConsole(fd int64, buffer *any, length uint32, written *uint32) -> bool -} - read(fd int64, buffer *any, length int64) -> int64 { fd = kernel32.GetStdHandle(-10 - fd) kernel32.ReadConsole(fd, buffer, length, 0) @@ -14,4 +8,24 @@ write(fd int64, buffer *any, length int64) -> int64 { fd = kernel32.GetStdHandle(-10 - fd) kernel32.WriteConsoleA(fd, buffer, length, 0) return length +} + +mmap(address int, length int, protection int, flags int) -> *any { + return kernel32.VirtualAlloc(address, length, flags, protection) +} + +munmap(address *any, length int) -> int { + return kernel32.VirtualFree(address, length, mem.decommit) +} + +const mem { + decommit 0x4000 +} + +extern kernel32 { + GetStdHandle(handle int64) -> int64 + ReadConsole(fd int64, buffer *any, length uint32, written *uint32) -> bool + VirtualAlloc(address int, length int, flags int, protection int) + VirtualFree(address *any, length int, type int) -> bool + WriteConsoleA(fd int64, buffer *any, length uint32, written *uint32) -> bool } \ No newline at end of file diff --git a/lib/sys/time_linux.q b/lib/sys/time_linux.q deleted file mode 100644 index 3099f60..0000000 --- a/lib/sys/time_linux.q +++ /dev/null @@ -1,8 +0,0 @@ -struct timespec { - seconds int64 - nanoseconds int64 -} - -nanosleep(duration *timespec) -> int { - return syscall(35, duration, 0) -} \ No newline at end of file diff --git a/src/core/ToNumber.go b/src/core/ToNumber.go index 95d65a2..da73904 100644 --- a/src/core/ToNumber.go +++ b/src/core/ToNumber.go @@ -31,7 +31,12 @@ func (f *Function) ToNumber(t token.Token) (int, error) { } number, err := strconv.Atoi(digits) - return number, err + + if err != nil { + return 0, errors.New(err, f.File, t.Position) + } + + return number, nil case token.Rune: r := t.Bytes(f.File.Bytes) From 919537a33bfbe80f623cbc67f553536fb4ebcdd1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Feb 2025 23:36:11 +0100 Subject: [PATCH 0787/1012] Simplified config package --- docs/readme.md | 2 +- src/cli/Build.go | 14 +++++----- src/cli/Help.go | 2 +- src/config/config.go | 49 ++++++++++++--------------------- src/config/const.go | 12 ++++++++ src/config/findLibrary.go | 25 +++++++++++++++++ src/config/init.go | 20 -------------- src/register/postInstruction.go | 2 +- tests/examples_test.go | 4 +-- tests/programs_test.go | 4 +-- 10 files changed, 68 insertions(+), 66 deletions(-) create mode 100644 src/config/const.go create mode 100644 src/config/findLibrary.go diff --git a/docs/readme.md b/docs/readme.md index d144e65..4a48a13 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -39,7 +39,7 @@ Commands: build [directory | file] build an executable from a file or directory --arch [arch] cross-compile for another CPU architecture [x86|arm|riscv] - --assembler, -a show assembler instructions + --assembly, -a show assembly instructions --dry, -d skip writing the executable to disk --os [os] cross-compile for another OS [linux|mac|windows] --statistics, -s show statistics diff --git a/src/cli/Build.go b/src/cli/Build.go index bfb7e37..82daf25 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -28,18 +28,18 @@ func buildExecutable(args []string) (*build.Build, error) { for i := 0; i < len(args); i++ { switch args[i] { - case "-a", "--assembler": - config.Assembler = true + case "-a", "--assembly": + config.ShowAssembly = true case "-d", "--dry": config.Dry = true case "-s", "--statistics": - config.Statistics = true + config.ShowStatistics = true case "-v", "--verbose": - config.Assembler = true - config.Statistics = true + config.ShowAssembly = true + config.ShowStatistics = true case "--arch": i++ @@ -97,11 +97,11 @@ func start(b *build.Build) error { return err } - if config.Assembler { + if config.ShowAssembly { result.PrintInstructions() } - if config.Statistics { + if config.ShowStatistics { result.PrintStatistics() } diff --git a/src/cli/Help.go b/src/cli/Help.go index 7faf7dc..03dfd9c 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -15,7 +15,7 @@ Commands: build [directory | file] build an executable from a file or directory --arch [arch] cross-compile for another CPU architecture [x86|arm|riscv] - --assembler, -a show assembler instructions + --assembly, -a show assembly instructions --dry, -d skip writing the executable to disk --os [os] cross-compile for another OS [linux|mac|windows] --statistics, -s show statistics diff --git a/src/config/config.go b/src/config/config.go index 89644c8..b603eed 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -2,45 +2,21 @@ package config import "runtime" -const ( - // This is the absolute virtual minimum address we can load a program at, see `sysctl vm.mmap_min_addr`. - MinAddress = 0x10000 - - // The base address is the virtual address for our ELF file. - BaseAddress = 0x40 * MinAddress - - // Align is the alignment of the sections and it must be a multiple of the page size. - Align = 0x1000 -) - var ( - // Shows the assembly instructions at the end. - Assembler bool - - // Shows statistics at the end. - Statistics bool - - // Calculates the result of operations on constants at compile time. - ConstantFold bool - - // Skips writing the executable to disk. - Dry bool - - // Target architecture. - TargetArch string - - // Target platform. - TargetOS OS + ConstantFold bool // Calculates the result of operations on constants at compile time. + Dry bool // Skips writing the executable to disk. + ShowAssembly bool // Shows assembly instructions at the end. + ShowStatistics bool // Shows statistics at the end. + TargetArch string // Target architecture. + TargetOS OS // Target platform. ) // Reset resets the configuration to its default values. func Reset() { - Assembler = false - Statistics = false - ConstantFold = true + ShowAssembly = false + ShowStatistics = false Dry = false TargetArch = runtime.GOARCH - TargetOS = Unknown switch runtime.GOOS { case "linux": @@ -49,5 +25,14 @@ func Reset() { TargetOS = Mac case "windows": TargetOS = Windows + default: + TargetOS = Unknown } + + Optimize(true) +} + +// Optimize enables or disables all the optimizations at once. +func Optimize(enable bool) { + ConstantFold = enable } diff --git a/src/config/const.go b/src/config/const.go new file mode 100644 index 0000000..09d9946 --- /dev/null +++ b/src/config/const.go @@ -0,0 +1,12 @@ +package config + +const ( + // This is the absolute virtual minimum address we can load a program at, see `sysctl vm.mmap_min_addr`. + MinAddress = 0x10000 + + // The base address is the virtual address for our ELF file. + BaseAddress = 0x40 * MinAddress + + // Align is the alignment of the sections and it must be a multiple of the page size. + Align = 0x1000 +) diff --git a/src/config/findLibrary.go b/src/config/findLibrary.go new file mode 100644 index 0000000..f83de1b --- /dev/null +++ b/src/config/findLibrary.go @@ -0,0 +1,25 @@ +package config + +import ( + "os" + "path/filepath" +) + +func findLibrary() { + dir := WorkingDirectory + + for { + Library = filepath.Join(dir, "lib") + stat, err := os.Stat(Library) + + if !os.IsNotExist(err) && stat != nil && stat.IsDir() { + return + } + + if dir == "/" { + panic("standard library not found") + } + + dir = filepath.Dir(dir) + } +} diff --git a/src/config/init.go b/src/config/init.go index d1f9d75..52e87dc 100644 --- a/src/config/init.go +++ b/src/config/init.go @@ -2,7 +2,6 @@ package config import ( "os" - "path" "path/filepath" "runtime/debug" ) @@ -45,22 +44,3 @@ func init() { findLibrary() } } - -func findLibrary() { - dir := WorkingDirectory - - for { - Library = path.Join(dir, "lib") - stat, err := os.Stat(Library) - - if !os.IsNotExist(err) && stat != nil && stat.IsDir() { - return - } - - if dir == "/" { - panic("standard library not found") - } - - dir = filepath.Dir(dir) - } -} diff --git a/src/register/postInstruction.go b/src/register/postInstruction.go index 18f9120..fd55acb 100644 --- a/src/register/postInstruction.go +++ b/src/register/postInstruction.go @@ -3,7 +3,7 @@ package register import "git.akyoto.dev/cli/q/src/config" func (f *Machine) postInstruction() { - if !config.Assembler { + if !config.ShowAssembly { return } diff --git a/tests/examples_test.go b/tests/examples_test.go index 5096c62..9d788f6 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -33,12 +33,12 @@ func TestExamples(t *testing.T) { directory := filepath.Join("..", "examples", test.Name) t.Run(test.Name+"/debug", func(t *testing.T) { - config.ConstantFold = false + config.Optimize(false) run(t, directory, "debug", test.Input, test.Output, test.ExitCode) }) t.Run(test.Name+"/release", func(t *testing.T) { - config.ConstantFold = true + config.Optimize(true) run(t, directory, "release", test.Input, test.Output, test.ExitCode) }) } diff --git a/tests/programs_test.go b/tests/programs_test.go index 23ec235..2b3b55e 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -71,12 +71,12 @@ func TestPrograms(t *testing.T) { file := filepath.Join("programs", test.Name+".q") t.Run(test.Name+"/debug", func(t *testing.T) { - config.ConstantFold = false + config.Optimize(false) run(t, file, "debug", test.Input, test.Output, test.ExitCode) }) t.Run(test.Name+"/release", func(t *testing.T) { - config.ConstantFold = true + config.Optimize(true) run(t, file, "release", test.Input, test.Output, test.ExitCode) }) } From 3b76919fec352be58a732c5bc25770a0ced2317e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Feb 2025 23:36:11 +0100 Subject: [PATCH 0788/1012] Simplified config package --- docs/readme.md | 2 +- src/cli/Build.go | 14 +++++----- src/cli/Help.go | 2 +- src/config/config.go | 49 ++++++++++++--------------------- src/config/const.go | 12 ++++++++ src/config/findLibrary.go | 25 +++++++++++++++++ src/config/init.go | 20 -------------- src/register/postInstruction.go | 2 +- tests/examples_test.go | 4 +-- tests/programs_test.go | 4 +-- 10 files changed, 68 insertions(+), 66 deletions(-) create mode 100644 src/config/const.go create mode 100644 src/config/findLibrary.go diff --git a/docs/readme.md b/docs/readme.md index d144e65..4a48a13 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -39,7 +39,7 @@ Commands: build [directory | file] build an executable from a file or directory --arch [arch] cross-compile for another CPU architecture [x86|arm|riscv] - --assembler, -a show assembler instructions + --assembly, -a show assembly instructions --dry, -d skip writing the executable to disk --os [os] cross-compile for another OS [linux|mac|windows] --statistics, -s show statistics diff --git a/src/cli/Build.go b/src/cli/Build.go index bfb7e37..82daf25 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -28,18 +28,18 @@ func buildExecutable(args []string) (*build.Build, error) { for i := 0; i < len(args); i++ { switch args[i] { - case "-a", "--assembler": - config.Assembler = true + case "-a", "--assembly": + config.ShowAssembly = true case "-d", "--dry": config.Dry = true case "-s", "--statistics": - config.Statistics = true + config.ShowStatistics = true case "-v", "--verbose": - config.Assembler = true - config.Statistics = true + config.ShowAssembly = true + config.ShowStatistics = true case "--arch": i++ @@ -97,11 +97,11 @@ func start(b *build.Build) error { return err } - if config.Assembler { + if config.ShowAssembly { result.PrintInstructions() } - if config.Statistics { + if config.ShowStatistics { result.PrintStatistics() } diff --git a/src/cli/Help.go b/src/cli/Help.go index 7faf7dc..03dfd9c 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -15,7 +15,7 @@ Commands: build [directory | file] build an executable from a file or directory --arch [arch] cross-compile for another CPU architecture [x86|arm|riscv] - --assembler, -a show assembler instructions + --assembly, -a show assembly instructions --dry, -d skip writing the executable to disk --os [os] cross-compile for another OS [linux|mac|windows] --statistics, -s show statistics diff --git a/src/config/config.go b/src/config/config.go index 89644c8..b603eed 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -2,45 +2,21 @@ package config import "runtime" -const ( - // This is the absolute virtual minimum address we can load a program at, see `sysctl vm.mmap_min_addr`. - MinAddress = 0x10000 - - // The base address is the virtual address for our ELF file. - BaseAddress = 0x40 * MinAddress - - // Align is the alignment of the sections and it must be a multiple of the page size. - Align = 0x1000 -) - var ( - // Shows the assembly instructions at the end. - Assembler bool - - // Shows statistics at the end. - Statistics bool - - // Calculates the result of operations on constants at compile time. - ConstantFold bool - - // Skips writing the executable to disk. - Dry bool - - // Target architecture. - TargetArch string - - // Target platform. - TargetOS OS + ConstantFold bool // Calculates the result of operations on constants at compile time. + Dry bool // Skips writing the executable to disk. + ShowAssembly bool // Shows assembly instructions at the end. + ShowStatistics bool // Shows statistics at the end. + TargetArch string // Target architecture. + TargetOS OS // Target platform. ) // Reset resets the configuration to its default values. func Reset() { - Assembler = false - Statistics = false - ConstantFold = true + ShowAssembly = false + ShowStatistics = false Dry = false TargetArch = runtime.GOARCH - TargetOS = Unknown switch runtime.GOOS { case "linux": @@ -49,5 +25,14 @@ func Reset() { TargetOS = Mac case "windows": TargetOS = Windows + default: + TargetOS = Unknown } + + Optimize(true) +} + +// Optimize enables or disables all the optimizations at once. +func Optimize(enable bool) { + ConstantFold = enable } diff --git a/src/config/const.go b/src/config/const.go new file mode 100644 index 0000000..09d9946 --- /dev/null +++ b/src/config/const.go @@ -0,0 +1,12 @@ +package config + +const ( + // This is the absolute virtual minimum address we can load a program at, see `sysctl vm.mmap_min_addr`. + MinAddress = 0x10000 + + // The base address is the virtual address for our ELF file. + BaseAddress = 0x40 * MinAddress + + // Align is the alignment of the sections and it must be a multiple of the page size. + Align = 0x1000 +) diff --git a/src/config/findLibrary.go b/src/config/findLibrary.go new file mode 100644 index 0000000..f83de1b --- /dev/null +++ b/src/config/findLibrary.go @@ -0,0 +1,25 @@ +package config + +import ( + "os" + "path/filepath" +) + +func findLibrary() { + dir := WorkingDirectory + + for { + Library = filepath.Join(dir, "lib") + stat, err := os.Stat(Library) + + if !os.IsNotExist(err) && stat != nil && stat.IsDir() { + return + } + + if dir == "/" { + panic("standard library not found") + } + + dir = filepath.Dir(dir) + } +} diff --git a/src/config/init.go b/src/config/init.go index d1f9d75..52e87dc 100644 --- a/src/config/init.go +++ b/src/config/init.go @@ -2,7 +2,6 @@ package config import ( "os" - "path" "path/filepath" "runtime/debug" ) @@ -45,22 +44,3 @@ func init() { findLibrary() } } - -func findLibrary() { - dir := WorkingDirectory - - for { - Library = path.Join(dir, "lib") - stat, err := os.Stat(Library) - - if !os.IsNotExist(err) && stat != nil && stat.IsDir() { - return - } - - if dir == "/" { - panic("standard library not found") - } - - dir = filepath.Dir(dir) - } -} diff --git a/src/register/postInstruction.go b/src/register/postInstruction.go index 18f9120..fd55acb 100644 --- a/src/register/postInstruction.go +++ b/src/register/postInstruction.go @@ -3,7 +3,7 @@ package register import "git.akyoto.dev/cli/q/src/config" func (f *Machine) postInstruction() { - if !config.Assembler { + if !config.ShowAssembly { return } diff --git a/tests/examples_test.go b/tests/examples_test.go index 5096c62..9d788f6 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -33,12 +33,12 @@ func TestExamples(t *testing.T) { directory := filepath.Join("..", "examples", test.Name) t.Run(test.Name+"/debug", func(t *testing.T) { - config.ConstantFold = false + config.Optimize(false) run(t, directory, "debug", test.Input, test.Output, test.ExitCode) }) t.Run(test.Name+"/release", func(t *testing.T) { - config.ConstantFold = true + config.Optimize(true) run(t, directory, "release", test.Input, test.Output, test.ExitCode) }) } diff --git a/tests/programs_test.go b/tests/programs_test.go index 23ec235..2b3b55e 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -71,12 +71,12 @@ func TestPrograms(t *testing.T) { file := filepath.Join("programs", test.Name+".q") t.Run(test.Name+"/debug", func(t *testing.T) { - config.ConstantFold = false + config.Optimize(false) run(t, file, "debug", test.Input, test.Output, test.ExitCode) }) t.Run(test.Name+"/release", func(t *testing.T) { - config.ConstantFold = true + config.Optimize(true) run(t, file, "release", test.Input, test.Output, test.ExitCode) }) } From c86766bd336f1245855b6e1435e17ac0c24c88e1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Feb 2025 23:51:31 +0100 Subject: [PATCH 0789/1012] Updated documentation --- docs/readme.md | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 4a48a13..8716f7e 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -17,38 +17,23 @@ cd q go build ``` -This will place the compiler inside the repository. -Either use `./q` or get access to the shorter `q` in any directory with `ln -s $PWD/q ~/.local/bin/q`. +Either use `./q` from the build directory or get access to the shorter `q` in any directory with `ln -s $PWD/q ~/.local/bin/q`. -## Examples - -You can take a look at the [examples](../examples). +## Usage ```shell q run examples/hello ``` -## Usage +You can take a look at the [examples](../examples). +## Tests + +```shell +go run gotest.tools/gotestsum@latest ``` -Usage: - q [command] [options] - -Commands: - - build [directory | file] build an executable from a file or directory - --arch [arch] cross-compile for another CPU architecture [x86|arm|riscv] - --assembly, -a show assembly instructions - --dry, -d skip writing the executable to disk - --os [os] cross-compile for another OS [linux|mac|windows] - --statistics, -s show statistics - --verbose, -v show everything - - run [directory | file] build and run the executable - system show system information - help show this help -``` +This will run over 350 tests in various categories. ## Platforms From 4cff697bf57cc1f801666d6bebecc3d106da35fb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Feb 2025 23:51:31 +0100 Subject: [PATCH 0790/1012] Updated documentation --- docs/readme.md | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 4a48a13..8716f7e 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -17,38 +17,23 @@ cd q go build ``` -This will place the compiler inside the repository. -Either use `./q` or get access to the shorter `q` in any directory with `ln -s $PWD/q ~/.local/bin/q`. +Either use `./q` from the build directory or get access to the shorter `q` in any directory with `ln -s $PWD/q ~/.local/bin/q`. -## Examples - -You can take a look at the [examples](../examples). +## Usage ```shell q run examples/hello ``` -## Usage +You can take a look at the [examples](../examples). +## Tests + +```shell +go run gotest.tools/gotestsum@latest ``` -Usage: - q [command] [options] - -Commands: - - build [directory | file] build an executable from a file or directory - --arch [arch] cross-compile for another CPU architecture [x86|arm|riscv] - --assembly, -a show assembly instructions - --dry, -d skip writing the executable to disk - --os [os] cross-compile for another OS [linux|mac|windows] - --statistics, -s show statistics - --verbose, -v show everything - - run [directory | file] build and run the executable - system show system information - help show this help -``` +This will run over 350 tests in various categories. ## Platforms From 144810c6c841888cae85ba5fdbdbd7b3f133d223 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 19 Feb 2025 15:36:42 +0100 Subject: [PATCH 0791/1012] Implemented loads with register offsets --- src/asmc/compile.go | 5 +- src/asmc/load.go | 19 +++++ src/core/ArrayElementToRegister.go | 70 +++++++++++++++++++ src/core/CallToRegister.go | 27 +++++++ src/core/CompileAssignArray.go | 3 +- src/core/ExpressionToRegister.go | 101 ++------------------------- src/core/PeriodToRegister.go | 52 ++++++++++++++ src/x86/LoadDynamic.go | 8 +++ src/x86/LoadDynamic_test.go | 90 ++++++++++++++++++++++++ tests/programs/array-index-dynamic.q | 23 ++++++ tests/programs/array-index-static.q | 20 ++++++ tests/programs/array.q | 44 ------------ tests/programs_test.go | 3 +- 13 files changed, 320 insertions(+), 145 deletions(-) create mode 100644 src/asmc/load.go create mode 100644 src/core/ArrayElementToRegister.go create mode 100644 src/core/CallToRegister.go create mode 100644 src/core/PeriodToRegister.go create mode 100644 src/x86/LoadDynamic.go create mode 100644 src/x86/LoadDynamic_test.go create mode 100644 tests/programs/array-index-dynamic.q create mode 100644 tests/programs/array-index-static.q delete mode 100644 tests/programs/array.q diff --git a/src/asmc/compile.go b/src/asmc/compile.go index 1e76cf4..7cec3b2 100644 --- a/src/asmc/compile.go +++ b/src/asmc/compile.go @@ -93,10 +93,7 @@ func (c *compiler) compile(x asm.Instruction) { c.codeLabels[x.Data.(*asm.Label).Name] = Address(len(c.code)) case asm.LOAD: - switch operands := x.Data.(type) { - case *asm.MemoryRegister: - c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) - } + c.load(x) case asm.MOVE: c.move(x) diff --git a/src/asmc/load.go b/src/asmc/load.go new file mode 100644 index 0000000..e460a82 --- /dev/null +++ b/src/asmc/load.go @@ -0,0 +1,19 @@ +package asmc + +import ( + "math" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/x86" +) + +func (c *compiler) load(x asm.Instruction) { + switch operands := x.Data.(type) { + case *asm.MemoryRegister: + if operands.Address.OffsetRegister == math.MaxUint8 { + c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) + } else { + c.code = x86.LoadDynamicRegister(c.code, operands.Register, operands.Address.OffsetRegister, operands.Address.Length, operands.Address.Base) + } + } +} diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go new file mode 100644 index 0000000..46e24f7 --- /dev/null +++ b/src/core/ArrayElementToRegister.go @@ -0,0 +1,70 @@ +package core + +import ( + "math" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" +) + +// ArrayElementToRegister moves the value of an array element into the given register. +func (f *Function) ArrayElementToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { + name := node.Children[0].Token.Text(f.File.Bytes) + array := f.VariableByName(name) + + if array == nil { + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Children[0].Token.Position) + } + + index := node.Children[1] + + memory := asm.Memory{ + Base: array.Register, + Offset: 0, + OffsetRegister: math.MaxUint8, + Length: byte(1), + } + + if index.Token.IsNumeric() { + offset, err := f.ToNumber(index.Token) + + if err != nil { + return nil, err + } + + memory.Offset = int8(offset) + + } else if index.Token.Kind == token.Identifier { + indexName := index.Token.Text(f.File.Bytes) + indexVariable := f.VariableByName(indexName) + + if indexVariable == nil { + return nil, errors.New(&errors.UnknownIdentifier{Name: indexName}, f.File, index.Token.Position) + } + + if !types.Is(indexVariable.Type, types.Int) { + return nil, errors.New(&errors.TypeMismatch{Encountered: indexVariable.Type.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) + } + + memory.OffsetRegister = indexVariable.Register + } else { + typ, err := f.ExpressionToRegister(index, register) + + if err != nil { + return nil, err + } + + if !types.Is(typ, types.Int) { + return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) + } + + memory.OffsetRegister = register + } + + f.MemoryRegister(asm.LOAD, memory, register) + return types.Int, nil +} diff --git a/src/core/CallToRegister.go b/src/core/CallToRegister.go new file mode 100644 index 0000000..5b9901e --- /dev/null +++ b/src/core/CallToRegister.go @@ -0,0 +1,27 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" +) + +// CallToRegister moves the result of a function call into the given register. +func (f *Function) CallToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { + types, err := f.CompileCall(node) + + if err != nil { + return nil, err + } + + if register != f.CPU.Output[0] { + f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) + } + + if len(types) == 0 { + return nil, nil + } + + return types[0], err +} diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index ec13b4c..2e5d8be 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -5,7 +5,6 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/types" ) @@ -27,7 +26,7 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { memory := asm.Memory{ Base: variable.Register, Offset: 0, - OffsetRegister: cpu.Register(math.MaxUint8), + OffsetRegister: math.MaxUint8, Length: byte(1), } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 56ce93e..056ebe1 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -1,11 +1,7 @@ package core import ( - "fmt" - "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" @@ -24,96 +20,13 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return f.TokenToRegister(node.Token, register) } - if ast.IsFunctionCall(node) { - types, err := f.CompileCall(node) - - if err != nil { - return nil, err - } - - if register != f.CPU.Output[0] { - f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) - } - - if len(types) == 0 { - return nil, nil - } - - return types[0], err - } - - if node.Token.Kind == token.Array { - name := node.Children[0].Token.Text(f.File.Bytes) - array := f.VariableByName(name) - - if array == nil { - return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Children[0].Token.Position) - } - - index := node.Children[1] - - memory := asm.Memory{ - Base: array.Register, - Offset: 0, - OffsetRegister: cpu.Register(math.MaxUint8), - Length: byte(1), - } - - if index.Token.IsNumeric() { - offset, err := f.ToNumber(index.Token) - - if err != nil { - return nil, err - } - - memory.Offset = int8(offset) - } else { - typ, err := f.ExpressionToRegister(index, register) - - if err != nil { - return nil, err - } - - if !types.Is(typ, types.Int) { - return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) - } - - memory.OffsetRegister = register - } - - f.MemoryRegister(asm.LOAD, memory, register) - return types.Int, nil - } - - if node.Token.Kind == token.Period { - left := node.Children[0] - leftText := left.Token.Text(f.File.Bytes) - right := node.Children[1] - rightText := right.Token.Text(f.File.Bytes) - variable := f.VariableByName(leftText) - - if variable != nil { - field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: int8(field.Offset), Length: byte(field.Type.Size())}, register) - return field.Type, nil - } - - constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] - - if isConst { - return f.TokenToRegister(constant.Value, register) - } - - uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) - function, exists := f.Functions[uniqueName] - - if exists { - f.File.Imports[leftText].Used = true - f.RegisterLabel(asm.MOVE, register, function.UniqueName) - return types.AnyPointer, nil - } - - return nil, errors.New(&errors.UnknownIdentifier{Name: leftText}, f.File, left.Token.Position) + switch node.Token.Kind { + case token.Call: + return f.CallToRegister(node, register) + case token.Array: + return f.ArrayElementToRegister(node, register) + case token.Period: + return f.PeriodToRegister(node, register) } if len(node.Children) == 1 { diff --git a/src/core/PeriodToRegister.go b/src/core/PeriodToRegister.go new file mode 100644 index 0000000..d915f85 --- /dev/null +++ b/src/core/PeriodToRegister.go @@ -0,0 +1,52 @@ +package core + +import ( + "fmt" + "math" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" +) + +// PeriodToRegister moves a constant or a function address into the given register. +func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { + left := node.Children[0] + leftText := left.Token.Text(f.File.Bytes) + right := node.Children[1] + rightText := right.Token.Text(f.File.Bytes) + variable := f.VariableByName(leftText) + + if variable != nil { + field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + + memory := asm.Memory{ + Base: variable.Register, + Offset: int8(field.Offset), + OffsetRegister: math.MaxUint8, + Length: byte(field.Type.Size()), + } + + f.MemoryRegister(asm.LOAD, memory, register) + return field.Type, nil + } + + constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] + + if isConst { + return f.TokenToRegister(constant.Value, register) + } + + uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) + function, exists := f.Functions[uniqueName] + + if exists { + f.File.Imports[leftText].Used = true + f.RegisterLabel(asm.MOVE, register, function.UniqueName) + return types.AnyPointer, nil + } + + return nil, errors.New(&errors.UnknownIdentifier{Name: leftText}, f.File, left.Token.Position) +} diff --git a/src/x86/LoadDynamic.go b/src/x86/LoadDynamic.go new file mode 100644 index 0000000..d1ad253 --- /dev/null +++ b/src/x86/LoadDynamic.go @@ -0,0 +1,8 @@ +package x86 + +import "git.akyoto.dev/cli/q/src/cpu" + +// LoadDynamicRegister loads from memory with a register offset into a register. +func LoadDynamicRegister(code []byte, destination cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { + return memoryAccessDynamic(code, 0x8A, 0x8B, source, offset, length, destination) +} diff --git a/src/x86/LoadDynamic_test.go b/src/x86/LoadDynamic_test.go new file mode 100644 index 0000000..cdd46f2 --- /dev/null +++ b/src/x86/LoadDynamic_test.go @@ -0,0 +1,90 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestLoadDynamicRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Length byte + Source cpu.Register + OffsetRegister cpu.Register + Code []byte + }{ + {x86.R15, 8, x86.RAX, x86.R15, []byte{0x4E, 0x8B, 0x3C, 0x38}}, + {x86.R15, 4, x86.RAX, x86.R15, []byte{0x46, 0x8B, 0x3C, 0x38}}, + {x86.R15, 2, x86.RAX, x86.R15, []byte{0x66, 0x46, 0x8B, 0x3C, 0x38}}, + {x86.R15, 1, x86.RAX, x86.R15, []byte{0x46, 0x8A, 0x3C, 0x38}}, + {x86.R14, 8, x86.RCX, x86.R14, []byte{0x4E, 0x8B, 0x34, 0x31}}, + {x86.R14, 4, x86.RCX, x86.R14, []byte{0x46, 0x8B, 0x34, 0x31}}, + {x86.R14, 2, x86.RCX, x86.R14, []byte{0x66, 0x46, 0x8B, 0x34, 0x31}}, + {x86.R14, 1, x86.RCX, x86.R14, []byte{0x46, 0x8A, 0x34, 0x31}}, + {x86.R13, 8, x86.RDX, x86.R13, []byte{0x4E, 0x8B, 0x2C, 0x2A}}, + {x86.R13, 4, x86.RDX, x86.R13, []byte{0x46, 0x8B, 0x2C, 0x2A}}, + {x86.R13, 2, x86.RDX, x86.R13, []byte{0x66, 0x46, 0x8B, 0x2C, 0x2A}}, + {x86.R13, 1, x86.RDX, x86.R13, []byte{0x46, 0x8A, 0x2C, 0x2A}}, + {x86.R12, 8, x86.RBX, x86.R12, []byte{0x4E, 0x8B, 0x24, 0x23}}, + {x86.R12, 4, x86.RBX, x86.R12, []byte{0x46, 0x8B, 0x24, 0x23}}, + {x86.R12, 2, x86.RBX, x86.R12, []byte{0x66, 0x46, 0x8B, 0x24, 0x23}}, + {x86.R12, 1, x86.RBX, x86.R12, []byte{0x46, 0x8A, 0x24, 0x23}}, + {x86.R11, 8, x86.RSP, x86.R11, []byte{0x4E, 0x8B, 0x1C, 0x1C}}, + {x86.R11, 4, x86.RSP, x86.R11, []byte{0x46, 0x8B, 0x1C, 0x1C}}, + {x86.R11, 2, x86.RSP, x86.R11, []byte{0x66, 0x46, 0x8B, 0x1C, 0x1C}}, + {x86.R11, 1, x86.RSP, x86.R11, []byte{0x46, 0x8A, 0x1C, 0x1C}}, + {x86.R10, 8, x86.RBP, x86.R10, []byte{0x4E, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R10, 4, x86.RBP, x86.R10, []byte{0x46, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R10, 2, x86.RBP, x86.R10, []byte{0x66, 0x46, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R10, 1, x86.RBP, x86.R10, []byte{0x46, 0x8A, 0x54, 0x15, 0x00}}, + {x86.R9, 8, x86.RSI, x86.R9, []byte{0x4E, 0x8B, 0x0C, 0x0E}}, + {x86.R9, 4, x86.RSI, x86.R9, []byte{0x46, 0x8B, 0x0C, 0x0E}}, + {x86.R9, 2, x86.RSI, x86.R9, []byte{0x66, 0x46, 0x8B, 0x0C, 0x0E}}, + {x86.R9, 1, x86.RSI, x86.R9, []byte{0x46, 0x8A, 0x0C, 0x0E}}, + {x86.R8, 8, x86.RDI, x86.R8, []byte{0x4E, 0x8B, 0x04, 0x07}}, + {x86.R8, 4, x86.RDI, x86.R8, []byte{0x46, 0x8B, 0x04, 0x07}}, + {x86.R8, 2, x86.RDI, x86.R8, []byte{0x66, 0x46, 0x8B, 0x04, 0x07}}, + {x86.R8, 1, x86.RDI, x86.R8, []byte{0x46, 0x8A, 0x04, 0x07}}, + {x86.RDI, 8, x86.R8, x86.RDI, []byte{0x49, 0x8B, 0x3C, 0x38}}, + {x86.RDI, 4, x86.R8, x86.RDI, []byte{0x41, 0x8B, 0x3C, 0x38}}, + {x86.RDI, 2, x86.R8, x86.RDI, []byte{0x66, 0x41, 0x8B, 0x3C, 0x38}}, + {x86.RDI, 1, x86.R8, x86.RDI, []byte{0x41, 0x8A, 0x3C, 0x38}}, + {x86.RSI, 8, x86.R9, x86.RSI, []byte{0x49, 0x8B, 0x34, 0x31}}, + {x86.RSI, 4, x86.R9, x86.RSI, []byte{0x41, 0x8B, 0x34, 0x31}}, + {x86.RSI, 2, x86.R9, x86.RSI, []byte{0x66, 0x41, 0x8B, 0x34, 0x31}}, + {x86.RSI, 1, x86.R9, x86.RSI, []byte{0x41, 0x8A, 0x34, 0x31}}, + {x86.RBP, 8, x86.R10, x86.RBP, []byte{0x49, 0x8B, 0x2C, 0x2A}}, + {x86.RBP, 4, x86.R10, x86.RBP, []byte{0x41, 0x8B, 0x2C, 0x2A}}, + {x86.RBP, 2, x86.R10, x86.RBP, []byte{0x66, 0x41, 0x8B, 0x2C, 0x2A}}, + {x86.RBP, 1, x86.R10, x86.RBP, []byte{0x41, 0x8A, 0x2C, 0x2A}}, + {x86.RSP, 8, x86.R11, x86.RSP, []byte{0x4A, 0x8B, 0x24, 0x1C}}, + {x86.RSP, 4, x86.R11, x86.RSP, []byte{0x42, 0x8B, 0x24, 0x1C}}, + {x86.RSP, 2, x86.R11, x86.RSP, []byte{0x66, 0x42, 0x8B, 0x24, 0x1C}}, + {x86.RSP, 1, x86.R11, x86.RSP, []byte{0x42, 0x8A, 0x24, 0x1C}}, + {x86.RBX, 8, x86.R12, x86.RBX, []byte{0x49, 0x8B, 0x1C, 0x1C}}, + {x86.RBX, 4, x86.R12, x86.RBX, []byte{0x41, 0x8B, 0x1C, 0x1C}}, + {x86.RBX, 2, x86.R12, x86.RBX, []byte{0x66, 0x41, 0x8B, 0x1C, 0x1C}}, + {x86.RBX, 1, x86.R12, x86.RBX, []byte{0x41, 0x8A, 0x1C, 0x1C}}, + {x86.RDX, 8, x86.R13, x86.RDX, []byte{0x49, 0x8B, 0x54, 0x15, 0x00}}, + {x86.RDX, 4, x86.R13, x86.RDX, []byte{0x41, 0x8B, 0x54, 0x15, 0x00}}, + {x86.RDX, 2, x86.R13, x86.RDX, []byte{0x66, 0x41, 0x8B, 0x54, 0x15, 0x00}}, + {x86.RDX, 1, x86.R13, x86.RDX, []byte{0x41, 0x8A, 0x54, 0x15, 0x00}}, + {x86.RCX, 8, x86.R14, x86.RCX, []byte{0x49, 0x8B, 0x0C, 0x0E}}, + {x86.RCX, 4, x86.R14, x86.RCX, []byte{0x41, 0x8B, 0x0C, 0x0E}}, + {x86.RCX, 2, x86.R14, x86.RCX, []byte{0x66, 0x41, 0x8B, 0x0C, 0x0E}}, + {x86.RCX, 1, x86.R14, x86.RCX, []byte{0x41, 0x8A, 0x0C, 0x0E}}, + {x86.RAX, 8, x86.R15, x86.RAX, []byte{0x49, 0x8B, 0x04, 0x07}}, + {x86.RAX, 4, x86.R15, x86.RAX, []byte{0x41, 0x8B, 0x04, 0x07}}, + {x86.RAX, 2, x86.R15, x86.RAX, []byte{0x66, 0x41, 0x8B, 0x04, 0x07}}, + {x86.RAX, 1, x86.R15, x86.RAX, []byte{0x41, 0x8A, 0x04, 0x07}}, + } + + for _, pattern := range usagePatterns { + t.Logf("load %dB %s, [%s+%s]", pattern.Length, pattern.Destination, pattern.Source, pattern.OffsetRegister) + code := x86.LoadDynamicRegister(nil, pattern.Destination, pattern.OffsetRegister, pattern.Length, pattern.Source) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/tests/programs/array-index-dynamic.q b/tests/programs/array-index-dynamic.q new file mode 100644 index 0000000..4e4c03f --- /dev/null +++ b/tests/programs/array-index-dynamic.q @@ -0,0 +1,23 @@ +import mem + +main() { + a := mem.alloc(4) + i := 0 + + a[i] = i * 2 + i += 1 + a[i] = i * 2 + i += 1 + a[i] = i * 2 + i += 1 + a[i] = i * 2 + + i = 0 + assert a[i] == i * 2 + i += 1 + assert a[i] == i * 2 + i += 1 + assert a[i] == i * 2 + i += 1 + assert a[i] == i * 2 +} \ No newline at end of file diff --git a/tests/programs/array-index-static.q b/tests/programs/array-index-static.q new file mode 100644 index 0000000..3120816 --- /dev/null +++ b/tests/programs/array-index-static.q @@ -0,0 +1,20 @@ +import mem + +main() { + a := mem.alloc(4) + + assert a[0] == 0 + assert a[1] == 0 + assert a[2] == 0 + assert a[3] == 0 + + a[0] = 0 + a[1] = 1 + a[2] = 2 + a[3] = 3 + + assert a[0] == 0 + assert a[1] == 1 + assert a[2] == 2 + assert a[3] == 3 +} \ No newline at end of file diff --git a/tests/programs/array.q b/tests/programs/array.q deleted file mode 100644 index 6b4d16a..0000000 --- a/tests/programs/array.q +++ /dev/null @@ -1,44 +0,0 @@ -import mem - -main() { - a := mem.alloc(5) - - assert a[0] == 0 - assert a[1] == 0 - assert a[2] == 0 - assert a[3] == 0 - assert a[4] == 0 - - a[0] = 0 - a[1] = 1 - a[2] = 2 - a[3] = 3 - a[4] = 4 - - assert a[0] == 0 - assert a[1] == 1 - assert a[2] == 2 - assert a[3] == 3 - assert a[4] == 4 - - i := 0 - a[i] = i * 2 - - i += 1 - a[i] = i * 2 - - i += 1 - a[i] = i * 2 - - i += 1 - a[i] = i * 2 - - i += 1 - a[i] = i * 2 - - assert a[0] == 0 - assert a[1] == 2 - assert a[2] == 4 - assert a[3] == 6 - assert a[4] == 8 -} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 2b3b55e..32bf77e 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -32,7 +32,6 @@ var programs = []struct { {"octal", "", "", 0}, {"hexadecimal", "", "", 0}, {"const", "", "", 0}, - {"array", "", "", 0}, {"escape-rune", "", "", 0}, {"escape-string", "", "", 0}, {"bitwise-and", "", "", 0}, @@ -62,6 +61,8 @@ var programs = []struct { {"loop-lifetime", "", "", 0}, {"memory-free", "", "", 0}, {"out-of-memory", "", "", 0}, + {"array-index-static", "", "", 0}, + {"array-index-dynamic", "", "", 0}, {"struct", "", "", 0}, {"len", "", "", 0}, } From 1bc845e6f0d438e6f684527835a842fec63b30f3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 19 Feb 2025 15:36:42 +0100 Subject: [PATCH 0792/1012] Implemented loads with register offsets --- src/asmc/compile.go | 5 +- src/asmc/load.go | 19 +++++ src/core/ArrayElementToRegister.go | 70 +++++++++++++++++++ src/core/CallToRegister.go | 27 +++++++ src/core/CompileAssignArray.go | 3 +- src/core/ExpressionToRegister.go | 101 ++------------------------- src/core/PeriodToRegister.go | 52 ++++++++++++++ src/x86/LoadDynamic.go | 8 +++ src/x86/LoadDynamic_test.go | 90 ++++++++++++++++++++++++ tests/programs/array-index-dynamic.q | 23 ++++++ tests/programs/array-index-static.q | 20 ++++++ tests/programs/array.q | 44 ------------ tests/programs_test.go | 3 +- 13 files changed, 320 insertions(+), 145 deletions(-) create mode 100644 src/asmc/load.go create mode 100644 src/core/ArrayElementToRegister.go create mode 100644 src/core/CallToRegister.go create mode 100644 src/core/PeriodToRegister.go create mode 100644 src/x86/LoadDynamic.go create mode 100644 src/x86/LoadDynamic_test.go create mode 100644 tests/programs/array-index-dynamic.q create mode 100644 tests/programs/array-index-static.q delete mode 100644 tests/programs/array.q diff --git a/src/asmc/compile.go b/src/asmc/compile.go index 1e76cf4..7cec3b2 100644 --- a/src/asmc/compile.go +++ b/src/asmc/compile.go @@ -93,10 +93,7 @@ func (c *compiler) compile(x asm.Instruction) { c.codeLabels[x.Data.(*asm.Label).Name] = Address(len(c.code)) case asm.LOAD: - switch operands := x.Data.(type) { - case *asm.MemoryRegister: - c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) - } + c.load(x) case asm.MOVE: c.move(x) diff --git a/src/asmc/load.go b/src/asmc/load.go new file mode 100644 index 0000000..e460a82 --- /dev/null +++ b/src/asmc/load.go @@ -0,0 +1,19 @@ +package asmc + +import ( + "math" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/x86" +) + +func (c *compiler) load(x asm.Instruction) { + switch operands := x.Data.(type) { + case *asm.MemoryRegister: + if operands.Address.OffsetRegister == math.MaxUint8 { + c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) + } else { + c.code = x86.LoadDynamicRegister(c.code, operands.Register, operands.Address.OffsetRegister, operands.Address.Length, operands.Address.Base) + } + } +} diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go new file mode 100644 index 0000000..46e24f7 --- /dev/null +++ b/src/core/ArrayElementToRegister.go @@ -0,0 +1,70 @@ +package core + +import ( + "math" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" +) + +// ArrayElementToRegister moves the value of an array element into the given register. +func (f *Function) ArrayElementToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { + name := node.Children[0].Token.Text(f.File.Bytes) + array := f.VariableByName(name) + + if array == nil { + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Children[0].Token.Position) + } + + index := node.Children[1] + + memory := asm.Memory{ + Base: array.Register, + Offset: 0, + OffsetRegister: math.MaxUint8, + Length: byte(1), + } + + if index.Token.IsNumeric() { + offset, err := f.ToNumber(index.Token) + + if err != nil { + return nil, err + } + + memory.Offset = int8(offset) + + } else if index.Token.Kind == token.Identifier { + indexName := index.Token.Text(f.File.Bytes) + indexVariable := f.VariableByName(indexName) + + if indexVariable == nil { + return nil, errors.New(&errors.UnknownIdentifier{Name: indexName}, f.File, index.Token.Position) + } + + if !types.Is(indexVariable.Type, types.Int) { + return nil, errors.New(&errors.TypeMismatch{Encountered: indexVariable.Type.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) + } + + memory.OffsetRegister = indexVariable.Register + } else { + typ, err := f.ExpressionToRegister(index, register) + + if err != nil { + return nil, err + } + + if !types.Is(typ, types.Int) { + return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) + } + + memory.OffsetRegister = register + } + + f.MemoryRegister(asm.LOAD, memory, register) + return types.Int, nil +} diff --git a/src/core/CallToRegister.go b/src/core/CallToRegister.go new file mode 100644 index 0000000..5b9901e --- /dev/null +++ b/src/core/CallToRegister.go @@ -0,0 +1,27 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" +) + +// CallToRegister moves the result of a function call into the given register. +func (f *Function) CallToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { + types, err := f.CompileCall(node) + + if err != nil { + return nil, err + } + + if register != f.CPU.Output[0] { + f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) + } + + if len(types) == 0 { + return nil, nil + } + + return types[0], err +} diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index ec13b4c..2e5d8be 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -5,7 +5,6 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/types" ) @@ -27,7 +26,7 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { memory := asm.Memory{ Base: variable.Register, Offset: 0, - OffsetRegister: cpu.Register(math.MaxUint8), + OffsetRegister: math.MaxUint8, Length: byte(1), } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 56ce93e..056ebe1 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -1,11 +1,7 @@ package core import ( - "fmt" - "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" @@ -24,96 +20,13 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return f.TokenToRegister(node.Token, register) } - if ast.IsFunctionCall(node) { - types, err := f.CompileCall(node) - - if err != nil { - return nil, err - } - - if register != f.CPU.Output[0] { - f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) - } - - if len(types) == 0 { - return nil, nil - } - - return types[0], err - } - - if node.Token.Kind == token.Array { - name := node.Children[0].Token.Text(f.File.Bytes) - array := f.VariableByName(name) - - if array == nil { - return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Children[0].Token.Position) - } - - index := node.Children[1] - - memory := asm.Memory{ - Base: array.Register, - Offset: 0, - OffsetRegister: cpu.Register(math.MaxUint8), - Length: byte(1), - } - - if index.Token.IsNumeric() { - offset, err := f.ToNumber(index.Token) - - if err != nil { - return nil, err - } - - memory.Offset = int8(offset) - } else { - typ, err := f.ExpressionToRegister(index, register) - - if err != nil { - return nil, err - } - - if !types.Is(typ, types.Int) { - return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) - } - - memory.OffsetRegister = register - } - - f.MemoryRegister(asm.LOAD, memory, register) - return types.Int, nil - } - - if node.Token.Kind == token.Period { - left := node.Children[0] - leftText := left.Token.Text(f.File.Bytes) - right := node.Children[1] - rightText := right.Token.Text(f.File.Bytes) - variable := f.VariableByName(leftText) - - if variable != nil { - field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: int8(field.Offset), Length: byte(field.Type.Size())}, register) - return field.Type, nil - } - - constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] - - if isConst { - return f.TokenToRegister(constant.Value, register) - } - - uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) - function, exists := f.Functions[uniqueName] - - if exists { - f.File.Imports[leftText].Used = true - f.RegisterLabel(asm.MOVE, register, function.UniqueName) - return types.AnyPointer, nil - } - - return nil, errors.New(&errors.UnknownIdentifier{Name: leftText}, f.File, left.Token.Position) + switch node.Token.Kind { + case token.Call: + return f.CallToRegister(node, register) + case token.Array: + return f.ArrayElementToRegister(node, register) + case token.Period: + return f.PeriodToRegister(node, register) } if len(node.Children) == 1 { diff --git a/src/core/PeriodToRegister.go b/src/core/PeriodToRegister.go new file mode 100644 index 0000000..d915f85 --- /dev/null +++ b/src/core/PeriodToRegister.go @@ -0,0 +1,52 @@ +package core + +import ( + "fmt" + "math" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" +) + +// PeriodToRegister moves a constant or a function address into the given register. +func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { + left := node.Children[0] + leftText := left.Token.Text(f.File.Bytes) + right := node.Children[1] + rightText := right.Token.Text(f.File.Bytes) + variable := f.VariableByName(leftText) + + if variable != nil { + field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + + memory := asm.Memory{ + Base: variable.Register, + Offset: int8(field.Offset), + OffsetRegister: math.MaxUint8, + Length: byte(field.Type.Size()), + } + + f.MemoryRegister(asm.LOAD, memory, register) + return field.Type, nil + } + + constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] + + if isConst { + return f.TokenToRegister(constant.Value, register) + } + + uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) + function, exists := f.Functions[uniqueName] + + if exists { + f.File.Imports[leftText].Used = true + f.RegisterLabel(asm.MOVE, register, function.UniqueName) + return types.AnyPointer, nil + } + + return nil, errors.New(&errors.UnknownIdentifier{Name: leftText}, f.File, left.Token.Position) +} diff --git a/src/x86/LoadDynamic.go b/src/x86/LoadDynamic.go new file mode 100644 index 0000000..d1ad253 --- /dev/null +++ b/src/x86/LoadDynamic.go @@ -0,0 +1,8 @@ +package x86 + +import "git.akyoto.dev/cli/q/src/cpu" + +// LoadDynamicRegister loads from memory with a register offset into a register. +func LoadDynamicRegister(code []byte, destination cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { + return memoryAccessDynamic(code, 0x8A, 0x8B, source, offset, length, destination) +} diff --git a/src/x86/LoadDynamic_test.go b/src/x86/LoadDynamic_test.go new file mode 100644 index 0000000..cdd46f2 --- /dev/null +++ b/src/x86/LoadDynamic_test.go @@ -0,0 +1,90 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestLoadDynamicRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Length byte + Source cpu.Register + OffsetRegister cpu.Register + Code []byte + }{ + {x86.R15, 8, x86.RAX, x86.R15, []byte{0x4E, 0x8B, 0x3C, 0x38}}, + {x86.R15, 4, x86.RAX, x86.R15, []byte{0x46, 0x8B, 0x3C, 0x38}}, + {x86.R15, 2, x86.RAX, x86.R15, []byte{0x66, 0x46, 0x8B, 0x3C, 0x38}}, + {x86.R15, 1, x86.RAX, x86.R15, []byte{0x46, 0x8A, 0x3C, 0x38}}, + {x86.R14, 8, x86.RCX, x86.R14, []byte{0x4E, 0x8B, 0x34, 0x31}}, + {x86.R14, 4, x86.RCX, x86.R14, []byte{0x46, 0x8B, 0x34, 0x31}}, + {x86.R14, 2, x86.RCX, x86.R14, []byte{0x66, 0x46, 0x8B, 0x34, 0x31}}, + {x86.R14, 1, x86.RCX, x86.R14, []byte{0x46, 0x8A, 0x34, 0x31}}, + {x86.R13, 8, x86.RDX, x86.R13, []byte{0x4E, 0x8B, 0x2C, 0x2A}}, + {x86.R13, 4, x86.RDX, x86.R13, []byte{0x46, 0x8B, 0x2C, 0x2A}}, + {x86.R13, 2, x86.RDX, x86.R13, []byte{0x66, 0x46, 0x8B, 0x2C, 0x2A}}, + {x86.R13, 1, x86.RDX, x86.R13, []byte{0x46, 0x8A, 0x2C, 0x2A}}, + {x86.R12, 8, x86.RBX, x86.R12, []byte{0x4E, 0x8B, 0x24, 0x23}}, + {x86.R12, 4, x86.RBX, x86.R12, []byte{0x46, 0x8B, 0x24, 0x23}}, + {x86.R12, 2, x86.RBX, x86.R12, []byte{0x66, 0x46, 0x8B, 0x24, 0x23}}, + {x86.R12, 1, x86.RBX, x86.R12, []byte{0x46, 0x8A, 0x24, 0x23}}, + {x86.R11, 8, x86.RSP, x86.R11, []byte{0x4E, 0x8B, 0x1C, 0x1C}}, + {x86.R11, 4, x86.RSP, x86.R11, []byte{0x46, 0x8B, 0x1C, 0x1C}}, + {x86.R11, 2, x86.RSP, x86.R11, []byte{0x66, 0x46, 0x8B, 0x1C, 0x1C}}, + {x86.R11, 1, x86.RSP, x86.R11, []byte{0x46, 0x8A, 0x1C, 0x1C}}, + {x86.R10, 8, x86.RBP, x86.R10, []byte{0x4E, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R10, 4, x86.RBP, x86.R10, []byte{0x46, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R10, 2, x86.RBP, x86.R10, []byte{0x66, 0x46, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R10, 1, x86.RBP, x86.R10, []byte{0x46, 0x8A, 0x54, 0x15, 0x00}}, + {x86.R9, 8, x86.RSI, x86.R9, []byte{0x4E, 0x8B, 0x0C, 0x0E}}, + {x86.R9, 4, x86.RSI, x86.R9, []byte{0x46, 0x8B, 0x0C, 0x0E}}, + {x86.R9, 2, x86.RSI, x86.R9, []byte{0x66, 0x46, 0x8B, 0x0C, 0x0E}}, + {x86.R9, 1, x86.RSI, x86.R9, []byte{0x46, 0x8A, 0x0C, 0x0E}}, + {x86.R8, 8, x86.RDI, x86.R8, []byte{0x4E, 0x8B, 0x04, 0x07}}, + {x86.R8, 4, x86.RDI, x86.R8, []byte{0x46, 0x8B, 0x04, 0x07}}, + {x86.R8, 2, x86.RDI, x86.R8, []byte{0x66, 0x46, 0x8B, 0x04, 0x07}}, + {x86.R8, 1, x86.RDI, x86.R8, []byte{0x46, 0x8A, 0x04, 0x07}}, + {x86.RDI, 8, x86.R8, x86.RDI, []byte{0x49, 0x8B, 0x3C, 0x38}}, + {x86.RDI, 4, x86.R8, x86.RDI, []byte{0x41, 0x8B, 0x3C, 0x38}}, + {x86.RDI, 2, x86.R8, x86.RDI, []byte{0x66, 0x41, 0x8B, 0x3C, 0x38}}, + {x86.RDI, 1, x86.R8, x86.RDI, []byte{0x41, 0x8A, 0x3C, 0x38}}, + {x86.RSI, 8, x86.R9, x86.RSI, []byte{0x49, 0x8B, 0x34, 0x31}}, + {x86.RSI, 4, x86.R9, x86.RSI, []byte{0x41, 0x8B, 0x34, 0x31}}, + {x86.RSI, 2, x86.R9, x86.RSI, []byte{0x66, 0x41, 0x8B, 0x34, 0x31}}, + {x86.RSI, 1, x86.R9, x86.RSI, []byte{0x41, 0x8A, 0x34, 0x31}}, + {x86.RBP, 8, x86.R10, x86.RBP, []byte{0x49, 0x8B, 0x2C, 0x2A}}, + {x86.RBP, 4, x86.R10, x86.RBP, []byte{0x41, 0x8B, 0x2C, 0x2A}}, + {x86.RBP, 2, x86.R10, x86.RBP, []byte{0x66, 0x41, 0x8B, 0x2C, 0x2A}}, + {x86.RBP, 1, x86.R10, x86.RBP, []byte{0x41, 0x8A, 0x2C, 0x2A}}, + {x86.RSP, 8, x86.R11, x86.RSP, []byte{0x4A, 0x8B, 0x24, 0x1C}}, + {x86.RSP, 4, x86.R11, x86.RSP, []byte{0x42, 0x8B, 0x24, 0x1C}}, + {x86.RSP, 2, x86.R11, x86.RSP, []byte{0x66, 0x42, 0x8B, 0x24, 0x1C}}, + {x86.RSP, 1, x86.R11, x86.RSP, []byte{0x42, 0x8A, 0x24, 0x1C}}, + {x86.RBX, 8, x86.R12, x86.RBX, []byte{0x49, 0x8B, 0x1C, 0x1C}}, + {x86.RBX, 4, x86.R12, x86.RBX, []byte{0x41, 0x8B, 0x1C, 0x1C}}, + {x86.RBX, 2, x86.R12, x86.RBX, []byte{0x66, 0x41, 0x8B, 0x1C, 0x1C}}, + {x86.RBX, 1, x86.R12, x86.RBX, []byte{0x41, 0x8A, 0x1C, 0x1C}}, + {x86.RDX, 8, x86.R13, x86.RDX, []byte{0x49, 0x8B, 0x54, 0x15, 0x00}}, + {x86.RDX, 4, x86.R13, x86.RDX, []byte{0x41, 0x8B, 0x54, 0x15, 0x00}}, + {x86.RDX, 2, x86.R13, x86.RDX, []byte{0x66, 0x41, 0x8B, 0x54, 0x15, 0x00}}, + {x86.RDX, 1, x86.R13, x86.RDX, []byte{0x41, 0x8A, 0x54, 0x15, 0x00}}, + {x86.RCX, 8, x86.R14, x86.RCX, []byte{0x49, 0x8B, 0x0C, 0x0E}}, + {x86.RCX, 4, x86.R14, x86.RCX, []byte{0x41, 0x8B, 0x0C, 0x0E}}, + {x86.RCX, 2, x86.R14, x86.RCX, []byte{0x66, 0x41, 0x8B, 0x0C, 0x0E}}, + {x86.RCX, 1, x86.R14, x86.RCX, []byte{0x41, 0x8A, 0x0C, 0x0E}}, + {x86.RAX, 8, x86.R15, x86.RAX, []byte{0x49, 0x8B, 0x04, 0x07}}, + {x86.RAX, 4, x86.R15, x86.RAX, []byte{0x41, 0x8B, 0x04, 0x07}}, + {x86.RAX, 2, x86.R15, x86.RAX, []byte{0x66, 0x41, 0x8B, 0x04, 0x07}}, + {x86.RAX, 1, x86.R15, x86.RAX, []byte{0x41, 0x8A, 0x04, 0x07}}, + } + + for _, pattern := range usagePatterns { + t.Logf("load %dB %s, [%s+%s]", pattern.Length, pattern.Destination, pattern.Source, pattern.OffsetRegister) + code := x86.LoadDynamicRegister(nil, pattern.Destination, pattern.OffsetRegister, pattern.Length, pattern.Source) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/tests/programs/array-index-dynamic.q b/tests/programs/array-index-dynamic.q new file mode 100644 index 0000000..4e4c03f --- /dev/null +++ b/tests/programs/array-index-dynamic.q @@ -0,0 +1,23 @@ +import mem + +main() { + a := mem.alloc(4) + i := 0 + + a[i] = i * 2 + i += 1 + a[i] = i * 2 + i += 1 + a[i] = i * 2 + i += 1 + a[i] = i * 2 + + i = 0 + assert a[i] == i * 2 + i += 1 + assert a[i] == i * 2 + i += 1 + assert a[i] == i * 2 + i += 1 + assert a[i] == i * 2 +} \ No newline at end of file diff --git a/tests/programs/array-index-static.q b/tests/programs/array-index-static.q new file mode 100644 index 0000000..3120816 --- /dev/null +++ b/tests/programs/array-index-static.q @@ -0,0 +1,20 @@ +import mem + +main() { + a := mem.alloc(4) + + assert a[0] == 0 + assert a[1] == 0 + assert a[2] == 0 + assert a[3] == 0 + + a[0] = 0 + a[1] = 1 + a[2] = 2 + a[3] = 3 + + assert a[0] == 0 + assert a[1] == 1 + assert a[2] == 2 + assert a[3] == 3 +} \ No newline at end of file diff --git a/tests/programs/array.q b/tests/programs/array.q deleted file mode 100644 index 6b4d16a..0000000 --- a/tests/programs/array.q +++ /dev/null @@ -1,44 +0,0 @@ -import mem - -main() { - a := mem.alloc(5) - - assert a[0] == 0 - assert a[1] == 0 - assert a[2] == 0 - assert a[3] == 0 - assert a[4] == 0 - - a[0] = 0 - a[1] = 1 - a[2] = 2 - a[3] = 3 - a[4] = 4 - - assert a[0] == 0 - assert a[1] == 1 - assert a[2] == 2 - assert a[3] == 3 - assert a[4] == 4 - - i := 0 - a[i] = i * 2 - - i += 1 - a[i] = i * 2 - - i += 1 - a[i] = i * 2 - - i += 1 - a[i] = i * 2 - - i += 1 - a[i] = i * 2 - - assert a[0] == 0 - assert a[1] == 2 - assert a[2] == 4 - assert a[3] == 6 - assert a[4] == 8 -} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 2b3b55e..32bf77e 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -32,7 +32,6 @@ var programs = []struct { {"octal", "", "", 0}, {"hexadecimal", "", "", 0}, {"const", "", "", 0}, - {"array", "", "", 0}, {"escape-rune", "", "", 0}, {"escape-string", "", "", 0}, {"bitwise-and", "", "", 0}, @@ -62,6 +61,8 @@ var programs = []struct { {"loop-lifetime", "", "", 0}, {"memory-free", "", "", 0}, {"out-of-memory", "", "", 0}, + {"array-index-static", "", "", 0}, + {"array-index-dynamic", "", "", 0}, {"struct", "", "", 0}, {"len", "", "", 0}, } From ac036573071d9c375634ba01d4368427fb2e5837 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 19 Feb 2025 17:52:15 +0100 Subject: [PATCH 0793/1012] Implemented loop iterators --- examples/thread/thread.q | 7 +- src/ast/Loop.go | 3 + src/ast/parseKeyword.go | 11 ++- src/core/ArrayElementToRegister.go | 2 + src/core/CompileLoop.go | 77 ++++++++++++++++++- src/core/CompileLoopInfinite.go | 25 ++++++ src/core/Define.go | 2 +- src/core/Evaluate.go | 5 ++ src/expression/Operator.go | 1 + src/token/Kind.go | 1 + src/token/operator.go | 2 + tests/programs/array-index-dynamic.q | 23 ------ tests/programs/index-dynamic.q | 10 +++ .../{array-index-static.q => index-static.q} | 0 tests/programs/{loop.q => loop-infinite.q} | 0 tests/programs/loop-limited.q | 6 ++ tests/programs_test.go | 7 +- 17 files changed, 147 insertions(+), 35 deletions(-) create mode 100644 src/core/CompileLoopInfinite.go delete mode 100644 tests/programs/array-index-dynamic.q create mode 100644 tests/programs/index-dynamic.q rename tests/programs/{array-index-static.q => index-static.q} (100%) rename tests/programs/{loop.q => loop-infinite.q} (100%) create mode 100644 tests/programs/loop-limited.q diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 6d0493b..91d5689 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -2,9 +2,10 @@ import io import thread main() { - thread.create(work) - thread.create(work) - thread.create(work) + loop 0..3 { + thread.create(work) + } + work() } diff --git a/src/ast/Loop.go b/src/ast/Loop.go index e1ca210..4d7257c 100644 --- a/src/ast/Loop.go +++ b/src/ast/Loop.go @@ -1,6 +1,9 @@ package ast +import "git.akyoto.dev/cli/q/src/expression" + // Loop represents a block of repeatable statements. type Loop struct { + Head *expression.Expression Body AST } diff --git a/src/ast/parseKeyword.go b/src/ast/parseKeyword.go index 32c2bbd..9c2cd9b 100644 --- a/src/ast/parseKeyword.go +++ b/src/ast/parseKeyword.go @@ -40,8 +40,15 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { return nil, err case token.Loop: - _, _, body, err := block(tokens, source) - return &Loop{Body: body}, err + blockStart, _, body, err := block(tokens, source) + head := tokens[1:blockStart] + + loop := &Loop{ + Head: expression.Parse(head), + Body: body, + } + + return loop, err case token.Return: if len(tokens) == 1 { diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go index 46e24f7..c623035 100644 --- a/src/core/ArrayElementToRegister.go +++ b/src/core/ArrayElementToRegister.go @@ -46,6 +46,8 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register return nil, errors.New(&errors.UnknownIdentifier{Name: indexName}, f.File, index.Token.Position) } + defer f.UseVariable(indexVariable) + if !types.Is(indexVariable.Type, types.Int) { return nil, errors.New(&errors.TypeMismatch{Encountered: indexVariable.Type.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) } diff --git a/src/core/CompileLoop.go b/src/core/CompileLoop.go index f2ef623..1209fe1 100644 --- a/src/core/CompileLoop.go +++ b/src/core/CompileLoop.go @@ -5,21 +5,92 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // CompileLoop compiles a loop instruction. func (f *Function) CompileLoop(loop *ast.Loop) error { + if loop.Head == nil { + return f.CompileLoopInfinite(loop) + } + for _, register := range f.CPU.Input { f.SaveRegister(register) } f.count.loop++ - label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) - f.AddLabel(label) + + var ( + label = fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) + labelEnd = fmt.Sprintf("%s_loop_%d_end", f.UniqueName, f.count.loop) + counter cpu.Register + from *expression.Expression + to *expression.Expression + ) + scope := f.PushScope(loop.Body, f.File.Bytes) scope.InLoop = true - err := f.CompileAST(loop.Body) + + switch loop.Head.Token.Kind { + case token.Define: + variable, err := f.Define(loop.Head.Children[0]) + + if err != nil { + return err + } + + counter = variable.Register + from = loop.Head.Children[1].Children[0] + to = loop.Head.Children[1].Children[1] + f.AddVariable(variable) + + case token.Range: + counter = f.NewRegister() + defer f.FreeRegister(counter) + from = loop.Head.Children[0] + to = loop.Head.Children[1] + + default: + panic("could not recognize loop header") + } + + _, err := f.ExpressionToRegister(from, counter) + + if err != nil { + return err + } + + if to.Token.IsNumeric() { + number, err := f.ToNumber(to.Token) + + if err != nil { + return err + } + + f.AddLabel(label) + f.RegisterNumber(asm.COMPARE, counter, number) + } else { + _, register, isTemporary, err := f.Evaluate(to) + + if err != nil { + return err + } + + if isTemporary { + defer f.FreeRegister(register) + } + + f.AddLabel(label) + f.RegisterRegister(asm.COMPARE, counter, register) + } + + f.Jump(asm.JGE, labelEnd) + err = f.CompileAST(loop.Body) + f.RegisterNumber(asm.ADD, counter, 1) f.Jump(asm.JUMP, label) + f.AddLabel(labelEnd) f.PopScope() return err } diff --git a/src/core/CompileLoopInfinite.go b/src/core/CompileLoopInfinite.go new file mode 100644 index 0000000..e8eada3 --- /dev/null +++ b/src/core/CompileLoopInfinite.go @@ -0,0 +1,25 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" +) + +// CompileLoopInfinite compiles an infinite loop. +func (f *Function) CompileLoopInfinite(loop *ast.Loop) error { + for _, register := range f.CPU.Input { + f.SaveRegister(register) + } + + f.count.loop++ + label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) + f.AddLabel(label) + scope := f.PushScope(loop.Body, f.File.Bytes) + scope.InLoop = true + err := f.CompileAST(loop.Body) + f.Jump(asm.JUMP, label) + f.PopScope() + return err +} diff --git a/src/core/Define.go b/src/core/Define.go index 94f7428..d017d85 100644 --- a/src/core/Define.go +++ b/src/core/Define.go @@ -10,7 +10,7 @@ import ( // Define defines a new variable. func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) { name := leaf.Token.Text(f.File.Bytes) - variable, _ := f.Identifier(name) + variable := f.VariableByName(name) if variable != nil { return nil, errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, leaf.Token.Position) diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index cd3f384..68da992 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -2,6 +2,7 @@ package core import ( "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/types" @@ -13,6 +14,10 @@ func (f *Function) Evaluate(expr *expression.Expression) (types.Type, cpu.Regist name := expr.Token.Text(f.File.Bytes) variable := f.VariableByName(name) + if variable == nil { + return nil, 0, false, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) + } + if variable.Alive == 1 { f.UseVariable(variable) return variable.Type, variable.Register, false, nil diff --git a/src/expression/Operator.go b/src/expression/Operator.go index f69ba00..800277a 100644 --- a/src/expression/Operator.go +++ b/src/expression/Operator.go @@ -41,6 +41,7 @@ var Operators = [64]Operator{ token.LogicalAnd: {"&&", 2, 2}, token.LogicalOr: {"||", 1, 2}, + token.Range: {"..", 0, 2}, token.Separator: {",", 0, 2}, token.Assign: {"=", math.MinInt8, 2}, diff --git a/src/token/Kind.go b/src/token/Kind.go index 200d131..54feac0 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -34,6 +34,7 @@ const ( LogicalOr // || Define // := Period // . + Range // .. Call // x() Array // [x] Separator // , diff --git a/src/token/operator.go b/src/token/operator.go index 46251ad..5d74059 100644 --- a/src/token/operator.go +++ b/src/token/operator.go @@ -36,6 +36,8 @@ func operator(tokens List, buffer []byte, i Position) (List, Position) { kind = AddAssign case ".": kind = Period + case "..": + kind = Range case ":=": kind = Define case "<": diff --git a/tests/programs/array-index-dynamic.q b/tests/programs/array-index-dynamic.q deleted file mode 100644 index 4e4c03f..0000000 --- a/tests/programs/array-index-dynamic.q +++ /dev/null @@ -1,23 +0,0 @@ -import mem - -main() { - a := mem.alloc(4) - i := 0 - - a[i] = i * 2 - i += 1 - a[i] = i * 2 - i += 1 - a[i] = i * 2 - i += 1 - a[i] = i * 2 - - i = 0 - assert a[i] == i * 2 - i += 1 - assert a[i] == i * 2 - i += 1 - assert a[i] == i * 2 - i += 1 - assert a[i] == i * 2 -} \ No newline at end of file diff --git a/tests/programs/index-dynamic.q b/tests/programs/index-dynamic.q new file mode 100644 index 0000000..7f1cfe3 --- /dev/null +++ b/tests/programs/index-dynamic.q @@ -0,0 +1,10 @@ +import mem + +main() { + a := mem.alloc(4) + + loop i := 0..4 { + a[i] = i * 2 + assert a[i] == i * 2 + } +} \ No newline at end of file diff --git a/tests/programs/array-index-static.q b/tests/programs/index-static.q similarity index 100% rename from tests/programs/array-index-static.q rename to tests/programs/index-static.q diff --git a/tests/programs/loop.q b/tests/programs/loop-infinite.q similarity index 100% rename from tests/programs/loop.q rename to tests/programs/loop-infinite.q diff --git a/tests/programs/loop-limited.q b/tests/programs/loop-limited.q new file mode 100644 index 0000000..c26975c --- /dev/null +++ b/tests/programs/loop-limited.q @@ -0,0 +1,6 @@ +main() { + loop i := 0..10 { + assert i >= 0 + assert i < 10 + } +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 32bf77e..15e5e30 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -57,12 +57,13 @@ var programs = []struct { {"branch-save", "", "", 0}, {"jump-near", "", "", 0}, {"switch", "", "", 0}, - {"loop", "", "", 0}, + {"loop-infinite", "", "", 0}, {"loop-lifetime", "", "", 0}, + {"loop-limited", "", "", 0}, {"memory-free", "", "", 0}, {"out-of-memory", "", "", 0}, - {"array-index-static", "", "", 0}, - {"array-index-dynamic", "", "", 0}, + {"index-static", "", "", 0}, + {"index-dynamic", "", "", 0}, {"struct", "", "", 0}, {"len", "", "", 0}, } From 7922cff7baf965d086a8c83c6a12e8bdea2ea1a4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 19 Feb 2025 17:52:15 +0100 Subject: [PATCH 0794/1012] Implemented loop iterators --- examples/thread/thread.q | 7 +- src/ast/Loop.go | 3 + src/ast/parseKeyword.go | 11 ++- src/core/ArrayElementToRegister.go | 2 + src/core/CompileLoop.go | 77 ++++++++++++++++++- src/core/CompileLoopInfinite.go | 25 ++++++ src/core/Define.go | 2 +- src/core/Evaluate.go | 5 ++ src/expression/Operator.go | 1 + src/token/Kind.go | 1 + src/token/operator.go | 2 + tests/programs/array-index-dynamic.q | 23 ------ tests/programs/index-dynamic.q | 10 +++ .../{array-index-static.q => index-static.q} | 0 tests/programs/{loop.q => loop-infinite.q} | 0 tests/programs/loop-limited.q | 6 ++ tests/programs_test.go | 7 +- 17 files changed, 147 insertions(+), 35 deletions(-) create mode 100644 src/core/CompileLoopInfinite.go delete mode 100644 tests/programs/array-index-dynamic.q create mode 100644 tests/programs/index-dynamic.q rename tests/programs/{array-index-static.q => index-static.q} (100%) rename tests/programs/{loop.q => loop-infinite.q} (100%) create mode 100644 tests/programs/loop-limited.q diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 6d0493b..91d5689 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -2,9 +2,10 @@ import io import thread main() { - thread.create(work) - thread.create(work) - thread.create(work) + loop 0..3 { + thread.create(work) + } + work() } diff --git a/src/ast/Loop.go b/src/ast/Loop.go index e1ca210..4d7257c 100644 --- a/src/ast/Loop.go +++ b/src/ast/Loop.go @@ -1,6 +1,9 @@ package ast +import "git.akyoto.dev/cli/q/src/expression" + // Loop represents a block of repeatable statements. type Loop struct { + Head *expression.Expression Body AST } diff --git a/src/ast/parseKeyword.go b/src/ast/parseKeyword.go index 32c2bbd..9c2cd9b 100644 --- a/src/ast/parseKeyword.go +++ b/src/ast/parseKeyword.go @@ -40,8 +40,15 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { return nil, err case token.Loop: - _, _, body, err := block(tokens, source) - return &Loop{Body: body}, err + blockStart, _, body, err := block(tokens, source) + head := tokens[1:blockStart] + + loop := &Loop{ + Head: expression.Parse(head), + Body: body, + } + + return loop, err case token.Return: if len(tokens) == 1 { diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go index 46e24f7..c623035 100644 --- a/src/core/ArrayElementToRegister.go +++ b/src/core/ArrayElementToRegister.go @@ -46,6 +46,8 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register return nil, errors.New(&errors.UnknownIdentifier{Name: indexName}, f.File, index.Token.Position) } + defer f.UseVariable(indexVariable) + if !types.Is(indexVariable.Type, types.Int) { return nil, errors.New(&errors.TypeMismatch{Encountered: indexVariable.Type.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) } diff --git a/src/core/CompileLoop.go b/src/core/CompileLoop.go index f2ef623..1209fe1 100644 --- a/src/core/CompileLoop.go +++ b/src/core/CompileLoop.go @@ -5,21 +5,92 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // CompileLoop compiles a loop instruction. func (f *Function) CompileLoop(loop *ast.Loop) error { + if loop.Head == nil { + return f.CompileLoopInfinite(loop) + } + for _, register := range f.CPU.Input { f.SaveRegister(register) } f.count.loop++ - label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) - f.AddLabel(label) + + var ( + label = fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) + labelEnd = fmt.Sprintf("%s_loop_%d_end", f.UniqueName, f.count.loop) + counter cpu.Register + from *expression.Expression + to *expression.Expression + ) + scope := f.PushScope(loop.Body, f.File.Bytes) scope.InLoop = true - err := f.CompileAST(loop.Body) + + switch loop.Head.Token.Kind { + case token.Define: + variable, err := f.Define(loop.Head.Children[0]) + + if err != nil { + return err + } + + counter = variable.Register + from = loop.Head.Children[1].Children[0] + to = loop.Head.Children[1].Children[1] + f.AddVariable(variable) + + case token.Range: + counter = f.NewRegister() + defer f.FreeRegister(counter) + from = loop.Head.Children[0] + to = loop.Head.Children[1] + + default: + panic("could not recognize loop header") + } + + _, err := f.ExpressionToRegister(from, counter) + + if err != nil { + return err + } + + if to.Token.IsNumeric() { + number, err := f.ToNumber(to.Token) + + if err != nil { + return err + } + + f.AddLabel(label) + f.RegisterNumber(asm.COMPARE, counter, number) + } else { + _, register, isTemporary, err := f.Evaluate(to) + + if err != nil { + return err + } + + if isTemporary { + defer f.FreeRegister(register) + } + + f.AddLabel(label) + f.RegisterRegister(asm.COMPARE, counter, register) + } + + f.Jump(asm.JGE, labelEnd) + err = f.CompileAST(loop.Body) + f.RegisterNumber(asm.ADD, counter, 1) f.Jump(asm.JUMP, label) + f.AddLabel(labelEnd) f.PopScope() return err } diff --git a/src/core/CompileLoopInfinite.go b/src/core/CompileLoopInfinite.go new file mode 100644 index 0000000..e8eada3 --- /dev/null +++ b/src/core/CompileLoopInfinite.go @@ -0,0 +1,25 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" +) + +// CompileLoopInfinite compiles an infinite loop. +func (f *Function) CompileLoopInfinite(loop *ast.Loop) error { + for _, register := range f.CPU.Input { + f.SaveRegister(register) + } + + f.count.loop++ + label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) + f.AddLabel(label) + scope := f.PushScope(loop.Body, f.File.Bytes) + scope.InLoop = true + err := f.CompileAST(loop.Body) + f.Jump(asm.JUMP, label) + f.PopScope() + return err +} diff --git a/src/core/Define.go b/src/core/Define.go index 94f7428..d017d85 100644 --- a/src/core/Define.go +++ b/src/core/Define.go @@ -10,7 +10,7 @@ import ( // Define defines a new variable. func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) { name := leaf.Token.Text(f.File.Bytes) - variable, _ := f.Identifier(name) + variable := f.VariableByName(name) if variable != nil { return nil, errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, leaf.Token.Position) diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index cd3f384..68da992 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -2,6 +2,7 @@ package core import ( "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/token" "git.akyoto.dev/cli/q/src/types" @@ -13,6 +14,10 @@ func (f *Function) Evaluate(expr *expression.Expression) (types.Type, cpu.Regist name := expr.Token.Text(f.File.Bytes) variable := f.VariableByName(name) + if variable == nil { + return nil, 0, false, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) + } + if variable.Alive == 1 { f.UseVariable(variable) return variable.Type, variable.Register, false, nil diff --git a/src/expression/Operator.go b/src/expression/Operator.go index f69ba00..800277a 100644 --- a/src/expression/Operator.go +++ b/src/expression/Operator.go @@ -41,6 +41,7 @@ var Operators = [64]Operator{ token.LogicalAnd: {"&&", 2, 2}, token.LogicalOr: {"||", 1, 2}, + token.Range: {"..", 0, 2}, token.Separator: {",", 0, 2}, token.Assign: {"=", math.MinInt8, 2}, diff --git a/src/token/Kind.go b/src/token/Kind.go index 200d131..54feac0 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -34,6 +34,7 @@ const ( LogicalOr // || Define // := Period // . + Range // .. Call // x() Array // [x] Separator // , diff --git a/src/token/operator.go b/src/token/operator.go index 46251ad..5d74059 100644 --- a/src/token/operator.go +++ b/src/token/operator.go @@ -36,6 +36,8 @@ func operator(tokens List, buffer []byte, i Position) (List, Position) { kind = AddAssign case ".": kind = Period + case "..": + kind = Range case ":=": kind = Define case "<": diff --git a/tests/programs/array-index-dynamic.q b/tests/programs/array-index-dynamic.q deleted file mode 100644 index 4e4c03f..0000000 --- a/tests/programs/array-index-dynamic.q +++ /dev/null @@ -1,23 +0,0 @@ -import mem - -main() { - a := mem.alloc(4) - i := 0 - - a[i] = i * 2 - i += 1 - a[i] = i * 2 - i += 1 - a[i] = i * 2 - i += 1 - a[i] = i * 2 - - i = 0 - assert a[i] == i * 2 - i += 1 - assert a[i] == i * 2 - i += 1 - assert a[i] == i * 2 - i += 1 - assert a[i] == i * 2 -} \ No newline at end of file diff --git a/tests/programs/index-dynamic.q b/tests/programs/index-dynamic.q new file mode 100644 index 0000000..7f1cfe3 --- /dev/null +++ b/tests/programs/index-dynamic.q @@ -0,0 +1,10 @@ +import mem + +main() { + a := mem.alloc(4) + + loop i := 0..4 { + a[i] = i * 2 + assert a[i] == i * 2 + } +} \ No newline at end of file diff --git a/tests/programs/array-index-static.q b/tests/programs/index-static.q similarity index 100% rename from tests/programs/array-index-static.q rename to tests/programs/index-static.q diff --git a/tests/programs/loop.q b/tests/programs/loop-infinite.q similarity index 100% rename from tests/programs/loop.q rename to tests/programs/loop-infinite.q diff --git a/tests/programs/loop-limited.q b/tests/programs/loop-limited.q new file mode 100644 index 0000000..c26975c --- /dev/null +++ b/tests/programs/loop-limited.q @@ -0,0 +1,6 @@ +main() { + loop i := 0..10 { + assert i >= 0 + assert i < 10 + } +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 32bf77e..15e5e30 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -57,12 +57,13 @@ var programs = []struct { {"branch-save", "", "", 0}, {"jump-near", "", "", 0}, {"switch", "", "", 0}, - {"loop", "", "", 0}, + {"loop-infinite", "", "", 0}, {"loop-lifetime", "", "", 0}, + {"loop-limited", "", "", 0}, {"memory-free", "", "", 0}, {"out-of-memory", "", "", 0}, - {"array-index-static", "", "", 0}, - {"array-index-dynamic", "", "", 0}, + {"index-static", "", "", 0}, + {"index-dynamic", "", "", 0}, {"struct", "", "", 0}, {"len", "", "", 0}, } From d26bf9d1a9a63318732bf9c56559fd40e4b6299a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 19 Feb 2025 23:46:17 +0100 Subject: [PATCH 0795/1012] Implemented for loops --- docs/todo.md | 4 +- examples/thread/thread.q | 2 +- src/ast/For.go | 9 ++++ src/ast/Loop.go | 5 +- src/ast/parseKeyword.go | 8 ++- src/core/CompileASTNode.go | 4 ++ src/core/CompileFor.go | 92 +++++++++++++++++++++++++++++++++ src/core/CompileLoop.go | 79 ++-------------------------- src/core/CompileLoopInfinite.go | 25 --------- src/token/Kind.go | 1 + src/token/Tokenize_test.go | 7 +-- src/token/identifier.go | 2 + tests/programs/index-dynamic.q | 2 +- tests/programs/loop-for.q | 14 +++++ tests/programs/loop-limited.q | 6 --- tests/programs_test.go | 2 +- 16 files changed, 142 insertions(+), 120 deletions(-) create mode 100644 src/ast/For.go create mode 100644 src/core/CompileFor.go delete mode 100644 src/core/CompileLoopInfinite.go create mode 100644 tests/programs/loop-for.q delete mode 100644 tests/programs/loop-limited.q diff --git a/docs/todo.md b/docs/todo.md index 6dcd482..e08b1d2 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -36,9 +36,9 @@ - [x] `assert` - [x] `const` -- [x] `extern` - [x] `else` -- [ ] `for` +- [x] `extern` +- [x] `for` - [x] `if` - [x] `import` - [x] `loop` diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 91d5689..b878ce8 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -2,7 +2,7 @@ import io import thread main() { - loop 0..3 { + for 0..3 { thread.create(work) } diff --git a/src/ast/For.go b/src/ast/For.go new file mode 100644 index 0000000..32a080b --- /dev/null +++ b/src/ast/For.go @@ -0,0 +1,9 @@ +package ast + +import "git.akyoto.dev/cli/q/src/expression" + +// For is a loop with a defined iteration limit. +type For struct { + Head *expression.Expression + Body AST +} diff --git a/src/ast/Loop.go b/src/ast/Loop.go index 4d7257c..8bf3fac 100644 --- a/src/ast/Loop.go +++ b/src/ast/Loop.go @@ -1,9 +1,6 @@ package ast -import "git.akyoto.dev/cli/q/src/expression" - -// Loop represents a block of repeatable statements. +// Loop is a block of infinitely repeating instructions. type Loop struct { - Head *expression.Expression Body AST } diff --git a/src/ast/parseKeyword.go b/src/ast/parseKeyword.go index 9c2cd9b..a0a4d45 100644 --- a/src/ast/parseKeyword.go +++ b/src/ast/parseKeyword.go @@ -39,17 +39,21 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { ifNode.Else = body return nil, err - case token.Loop: + case token.For: blockStart, _, body, err := block(tokens, source) head := tokens[1:blockStart] - loop := &Loop{ + loop := &For{ Head: expression.Parse(head), Body: body, } return loop, err + case token.Loop: + _, _, body, err := block(tokens, source) + return &Loop{Body: body}, err + case token.Return: if len(tokens) == 1 { return &Return{}, nil diff --git a/src/core/CompileASTNode.go b/src/core/CompileASTNode.go index e5daa67..a32a321 100644 --- a/src/core/CompileASTNode.go +++ b/src/core/CompileASTNode.go @@ -28,6 +28,10 @@ func (f *Function) CompileASTNode(node ast.Node) error { f.Fold(node.Condition) return f.CompileIf(node) + case *ast.For: + f.Fold(node.Head) + return f.CompileFor(node) + case *ast.Loop: return f.CompileLoop(node) diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go new file mode 100644 index 0000000..6cfde4c --- /dev/null +++ b/src/core/CompileFor.go @@ -0,0 +1,92 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" +) + +// CompileFor compiles a for loop. +func (f *Function) CompileFor(loop *ast.For) error { + for _, register := range f.CPU.Input { + f.SaveRegister(register) + } + + f.count.loop++ + + var ( + label = fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) + labelEnd = fmt.Sprintf("%s_loop_%d_end", f.UniqueName, f.count.loop) + counter cpu.Register + from *expression.Expression + to *expression.Expression + ) + + scope := f.PushScope(loop.Body, f.File.Bytes) + scope.InLoop = true + + switch loop.Head.Token.Kind { + case token.Define: + variable, err := f.Define(loop.Head.Children[0]) + + if err != nil { + return err + } + + counter = variable.Register + from = loop.Head.Children[1].Children[0] + to = loop.Head.Children[1].Children[1] + f.AddVariable(variable) + + case token.Range: + counter = f.NewRegister() + defer f.FreeRegister(counter) + from = loop.Head.Children[0] + to = loop.Head.Children[1] + + default: + panic("could not recognize loop header") + } + + _, err := f.ExpressionToRegister(from, counter) + + if err != nil { + return err + } + + if to.Token.IsNumeric() { + number, err := f.ToNumber(to.Token) + + if err != nil { + return err + } + + f.AddLabel(label) + f.RegisterNumber(asm.COMPARE, counter, number) + } else { + _, register, isTemporary, err := f.Evaluate(to) + + if err != nil { + return err + } + + if isTemporary { + defer f.FreeRegister(register) + } + + f.AddLabel(label) + f.RegisterRegister(asm.COMPARE, counter, register) + } + + f.Jump(asm.JGE, labelEnd) + err = f.CompileAST(loop.Body) + f.RegisterNumber(asm.ADD, counter, 1) + f.Jump(asm.JUMP, label) + f.AddLabel(labelEnd) + f.PopScope() + return err +} diff --git a/src/core/CompileLoop.go b/src/core/CompileLoop.go index 1209fe1..ea9aea7 100644 --- a/src/core/CompileLoop.go +++ b/src/core/CompileLoop.go @@ -5,92 +5,21 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" ) -// CompileLoop compiles a loop instruction. +// CompileLoop compiles an infinite loop. func (f *Function) CompileLoop(loop *ast.Loop) error { - if loop.Head == nil { - return f.CompileLoopInfinite(loop) - } - for _, register := range f.CPU.Input { f.SaveRegister(register) } f.count.loop++ - - var ( - label = fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) - labelEnd = fmt.Sprintf("%s_loop_%d_end", f.UniqueName, f.count.loop) - counter cpu.Register - from *expression.Expression - to *expression.Expression - ) - + label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) + f.AddLabel(label) scope := f.PushScope(loop.Body, f.File.Bytes) scope.InLoop = true - - switch loop.Head.Token.Kind { - case token.Define: - variable, err := f.Define(loop.Head.Children[0]) - - if err != nil { - return err - } - - counter = variable.Register - from = loop.Head.Children[1].Children[0] - to = loop.Head.Children[1].Children[1] - f.AddVariable(variable) - - case token.Range: - counter = f.NewRegister() - defer f.FreeRegister(counter) - from = loop.Head.Children[0] - to = loop.Head.Children[1] - - default: - panic("could not recognize loop header") - } - - _, err := f.ExpressionToRegister(from, counter) - - if err != nil { - return err - } - - if to.Token.IsNumeric() { - number, err := f.ToNumber(to.Token) - - if err != nil { - return err - } - - f.AddLabel(label) - f.RegisterNumber(asm.COMPARE, counter, number) - } else { - _, register, isTemporary, err := f.Evaluate(to) - - if err != nil { - return err - } - - if isTemporary { - defer f.FreeRegister(register) - } - - f.AddLabel(label) - f.RegisterRegister(asm.COMPARE, counter, register) - } - - f.Jump(asm.JGE, labelEnd) - err = f.CompileAST(loop.Body) - f.RegisterNumber(asm.ADD, counter, 1) + err := f.CompileAST(loop.Body) f.Jump(asm.JUMP, label) - f.AddLabel(labelEnd) f.PopScope() return err } diff --git a/src/core/CompileLoopInfinite.go b/src/core/CompileLoopInfinite.go deleted file mode 100644 index e8eada3..0000000 --- a/src/core/CompileLoopInfinite.go +++ /dev/null @@ -1,25 +0,0 @@ -package core - -import ( - "fmt" - - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" -) - -// CompileLoopInfinite compiles an infinite loop. -func (f *Function) CompileLoopInfinite(loop *ast.Loop) error { - for _, register := range f.CPU.Input { - f.SaveRegister(register) - } - - f.count.loop++ - label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) - f.AddLabel(label) - scope := f.PushScope(loop.Body, f.File.Bytes) - scope.InLoop = true - err := f.CompileAST(loop.Body) - f.Jump(asm.JUMP, label) - f.PopScope() - return err -} diff --git a/src/token/Kind.go b/src/token/Kind.go index 54feac0..2e0d764 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -69,6 +69,7 @@ const ( Const // const Else // else Extern // extern + For // for If // if Import // import Loop // loop diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index b38a5d7..ab256cb 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -25,15 +25,16 @@ func TestFunction(t *testing.T) { } func TestKeyword(t *testing.T) { - tokens := token.Tokenize([]byte("assert const if import else extern loop return struct switch")) + tokens := token.Tokenize([]byte("assert const else extern if import for loop return struct switch")) expected := []token.Kind{ token.Assert, token.Const, - token.If, - token.Import, token.Else, token.Extern, + token.If, + token.Import, + token.For, token.Loop, token.Return, token.Struct, diff --git a/src/token/identifier.go b/src/token/identifier.go index 6973fc6..27849ed 100644 --- a/src/token/identifier.go +++ b/src/token/identifier.go @@ -23,6 +23,8 @@ func identifier(tokens List, buffer []byte, i Position) (List, Position) { kind = Else case "extern": kind = Extern + case "for": + kind = For case "import": kind = Import case "loop": diff --git a/tests/programs/index-dynamic.q b/tests/programs/index-dynamic.q index 7f1cfe3..fa2e3e4 100644 --- a/tests/programs/index-dynamic.q +++ b/tests/programs/index-dynamic.q @@ -3,7 +3,7 @@ import mem main() { a := mem.alloc(4) - loop i := 0..4 { + for i := 0..4 { a[i] = i * 2 assert a[i] == i * 2 } diff --git a/tests/programs/loop-for.q b/tests/programs/loop-for.q new file mode 100644 index 0000000..cc85bac --- /dev/null +++ b/tests/programs/loop-for.q @@ -0,0 +1,14 @@ +main() { + x := 0 + + for 0..5 { + x += 1 + } + + assert x == 5 + + for i := 0..5 { + assert i >= 0 + assert i < 5 + } +} \ No newline at end of file diff --git a/tests/programs/loop-limited.q b/tests/programs/loop-limited.q deleted file mode 100644 index c26975c..0000000 --- a/tests/programs/loop-limited.q +++ /dev/null @@ -1,6 +0,0 @@ -main() { - loop i := 0..10 { - assert i >= 0 - assert i < 10 - } -} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 15e5e30..3097960 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -59,7 +59,7 @@ var programs = []struct { {"switch", "", "", 0}, {"loop-infinite", "", "", 0}, {"loop-lifetime", "", "", 0}, - {"loop-limited", "", "", 0}, + {"loop-for", "", "", 0}, {"memory-free", "", "", 0}, {"out-of-memory", "", "", 0}, {"index-static", "", "", 0}, From 45a36a645add0a5279eae9913c8012af09e5f7c9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 19 Feb 2025 23:46:17 +0100 Subject: [PATCH 0796/1012] Implemented for loops --- docs/todo.md | 4 +- examples/thread/thread.q | 2 +- src/ast/For.go | 9 ++++ src/ast/Loop.go | 5 +- src/ast/parseKeyword.go | 8 ++- src/core/CompileASTNode.go | 4 ++ src/core/CompileFor.go | 92 +++++++++++++++++++++++++++++++++ src/core/CompileLoop.go | 79 ++-------------------------- src/core/CompileLoopInfinite.go | 25 --------- src/token/Kind.go | 1 + src/token/Tokenize_test.go | 7 +-- src/token/identifier.go | 2 + tests/programs/index-dynamic.q | 2 +- tests/programs/loop-for.q | 14 +++++ tests/programs/loop-limited.q | 6 --- tests/programs_test.go | 2 +- 16 files changed, 142 insertions(+), 120 deletions(-) create mode 100644 src/ast/For.go create mode 100644 src/core/CompileFor.go delete mode 100644 src/core/CompileLoopInfinite.go create mode 100644 tests/programs/loop-for.q delete mode 100644 tests/programs/loop-limited.q diff --git a/docs/todo.md b/docs/todo.md index 6dcd482..e08b1d2 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -36,9 +36,9 @@ - [x] `assert` - [x] `const` -- [x] `extern` - [x] `else` -- [ ] `for` +- [x] `extern` +- [x] `for` - [x] `if` - [x] `import` - [x] `loop` diff --git a/examples/thread/thread.q b/examples/thread/thread.q index 91d5689..b878ce8 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -2,7 +2,7 @@ import io import thread main() { - loop 0..3 { + for 0..3 { thread.create(work) } diff --git a/src/ast/For.go b/src/ast/For.go new file mode 100644 index 0000000..32a080b --- /dev/null +++ b/src/ast/For.go @@ -0,0 +1,9 @@ +package ast + +import "git.akyoto.dev/cli/q/src/expression" + +// For is a loop with a defined iteration limit. +type For struct { + Head *expression.Expression + Body AST +} diff --git a/src/ast/Loop.go b/src/ast/Loop.go index 4d7257c..8bf3fac 100644 --- a/src/ast/Loop.go +++ b/src/ast/Loop.go @@ -1,9 +1,6 @@ package ast -import "git.akyoto.dev/cli/q/src/expression" - -// Loop represents a block of repeatable statements. +// Loop is a block of infinitely repeating instructions. type Loop struct { - Head *expression.Expression Body AST } diff --git a/src/ast/parseKeyword.go b/src/ast/parseKeyword.go index 9c2cd9b..a0a4d45 100644 --- a/src/ast/parseKeyword.go +++ b/src/ast/parseKeyword.go @@ -39,17 +39,21 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { ifNode.Else = body return nil, err - case token.Loop: + case token.For: blockStart, _, body, err := block(tokens, source) head := tokens[1:blockStart] - loop := &Loop{ + loop := &For{ Head: expression.Parse(head), Body: body, } return loop, err + case token.Loop: + _, _, body, err := block(tokens, source) + return &Loop{Body: body}, err + case token.Return: if len(tokens) == 1 { return &Return{}, nil diff --git a/src/core/CompileASTNode.go b/src/core/CompileASTNode.go index e5daa67..a32a321 100644 --- a/src/core/CompileASTNode.go +++ b/src/core/CompileASTNode.go @@ -28,6 +28,10 @@ func (f *Function) CompileASTNode(node ast.Node) error { f.Fold(node.Condition) return f.CompileIf(node) + case *ast.For: + f.Fold(node.Head) + return f.CompileFor(node) + case *ast.Loop: return f.CompileLoop(node) diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go new file mode 100644 index 0000000..6cfde4c --- /dev/null +++ b/src/core/CompileFor.go @@ -0,0 +1,92 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" +) + +// CompileFor compiles a for loop. +func (f *Function) CompileFor(loop *ast.For) error { + for _, register := range f.CPU.Input { + f.SaveRegister(register) + } + + f.count.loop++ + + var ( + label = fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) + labelEnd = fmt.Sprintf("%s_loop_%d_end", f.UniqueName, f.count.loop) + counter cpu.Register + from *expression.Expression + to *expression.Expression + ) + + scope := f.PushScope(loop.Body, f.File.Bytes) + scope.InLoop = true + + switch loop.Head.Token.Kind { + case token.Define: + variable, err := f.Define(loop.Head.Children[0]) + + if err != nil { + return err + } + + counter = variable.Register + from = loop.Head.Children[1].Children[0] + to = loop.Head.Children[1].Children[1] + f.AddVariable(variable) + + case token.Range: + counter = f.NewRegister() + defer f.FreeRegister(counter) + from = loop.Head.Children[0] + to = loop.Head.Children[1] + + default: + panic("could not recognize loop header") + } + + _, err := f.ExpressionToRegister(from, counter) + + if err != nil { + return err + } + + if to.Token.IsNumeric() { + number, err := f.ToNumber(to.Token) + + if err != nil { + return err + } + + f.AddLabel(label) + f.RegisterNumber(asm.COMPARE, counter, number) + } else { + _, register, isTemporary, err := f.Evaluate(to) + + if err != nil { + return err + } + + if isTemporary { + defer f.FreeRegister(register) + } + + f.AddLabel(label) + f.RegisterRegister(asm.COMPARE, counter, register) + } + + f.Jump(asm.JGE, labelEnd) + err = f.CompileAST(loop.Body) + f.RegisterNumber(asm.ADD, counter, 1) + f.Jump(asm.JUMP, label) + f.AddLabel(labelEnd) + f.PopScope() + return err +} diff --git a/src/core/CompileLoop.go b/src/core/CompileLoop.go index 1209fe1..ea9aea7 100644 --- a/src/core/CompileLoop.go +++ b/src/core/CompileLoop.go @@ -5,92 +5,21 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" ) -// CompileLoop compiles a loop instruction. +// CompileLoop compiles an infinite loop. func (f *Function) CompileLoop(loop *ast.Loop) error { - if loop.Head == nil { - return f.CompileLoopInfinite(loop) - } - for _, register := range f.CPU.Input { f.SaveRegister(register) } f.count.loop++ - - var ( - label = fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) - labelEnd = fmt.Sprintf("%s_loop_%d_end", f.UniqueName, f.count.loop) - counter cpu.Register - from *expression.Expression - to *expression.Expression - ) - + label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) + f.AddLabel(label) scope := f.PushScope(loop.Body, f.File.Bytes) scope.InLoop = true - - switch loop.Head.Token.Kind { - case token.Define: - variable, err := f.Define(loop.Head.Children[0]) - - if err != nil { - return err - } - - counter = variable.Register - from = loop.Head.Children[1].Children[0] - to = loop.Head.Children[1].Children[1] - f.AddVariable(variable) - - case token.Range: - counter = f.NewRegister() - defer f.FreeRegister(counter) - from = loop.Head.Children[0] - to = loop.Head.Children[1] - - default: - panic("could not recognize loop header") - } - - _, err := f.ExpressionToRegister(from, counter) - - if err != nil { - return err - } - - if to.Token.IsNumeric() { - number, err := f.ToNumber(to.Token) - - if err != nil { - return err - } - - f.AddLabel(label) - f.RegisterNumber(asm.COMPARE, counter, number) - } else { - _, register, isTemporary, err := f.Evaluate(to) - - if err != nil { - return err - } - - if isTemporary { - defer f.FreeRegister(register) - } - - f.AddLabel(label) - f.RegisterRegister(asm.COMPARE, counter, register) - } - - f.Jump(asm.JGE, labelEnd) - err = f.CompileAST(loop.Body) - f.RegisterNumber(asm.ADD, counter, 1) + err := f.CompileAST(loop.Body) f.Jump(asm.JUMP, label) - f.AddLabel(labelEnd) f.PopScope() return err } diff --git a/src/core/CompileLoopInfinite.go b/src/core/CompileLoopInfinite.go deleted file mode 100644 index e8eada3..0000000 --- a/src/core/CompileLoopInfinite.go +++ /dev/null @@ -1,25 +0,0 @@ -package core - -import ( - "fmt" - - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" -) - -// CompileLoopInfinite compiles an infinite loop. -func (f *Function) CompileLoopInfinite(loop *ast.Loop) error { - for _, register := range f.CPU.Input { - f.SaveRegister(register) - } - - f.count.loop++ - label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) - f.AddLabel(label) - scope := f.PushScope(loop.Body, f.File.Bytes) - scope.InLoop = true - err := f.CompileAST(loop.Body) - f.Jump(asm.JUMP, label) - f.PopScope() - return err -} diff --git a/src/token/Kind.go b/src/token/Kind.go index 54feac0..2e0d764 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -69,6 +69,7 @@ const ( Const // const Else // else Extern // extern + For // for If // if Import // import Loop // loop diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index b38a5d7..ab256cb 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -25,15 +25,16 @@ func TestFunction(t *testing.T) { } func TestKeyword(t *testing.T) { - tokens := token.Tokenize([]byte("assert const if import else extern loop return struct switch")) + tokens := token.Tokenize([]byte("assert const else extern if import for loop return struct switch")) expected := []token.Kind{ token.Assert, token.Const, - token.If, - token.Import, token.Else, token.Extern, + token.If, + token.Import, + token.For, token.Loop, token.Return, token.Struct, diff --git a/src/token/identifier.go b/src/token/identifier.go index 6973fc6..27849ed 100644 --- a/src/token/identifier.go +++ b/src/token/identifier.go @@ -23,6 +23,8 @@ func identifier(tokens List, buffer []byte, i Position) (List, Position) { kind = Else case "extern": kind = Extern + case "for": + kind = For case "import": kind = Import case "loop": diff --git a/tests/programs/index-dynamic.q b/tests/programs/index-dynamic.q index 7f1cfe3..fa2e3e4 100644 --- a/tests/programs/index-dynamic.q +++ b/tests/programs/index-dynamic.q @@ -3,7 +3,7 @@ import mem main() { a := mem.alloc(4) - loop i := 0..4 { + for i := 0..4 { a[i] = i * 2 assert a[i] == i * 2 } diff --git a/tests/programs/loop-for.q b/tests/programs/loop-for.q new file mode 100644 index 0000000..cc85bac --- /dev/null +++ b/tests/programs/loop-for.q @@ -0,0 +1,14 @@ +main() { + x := 0 + + for 0..5 { + x += 1 + } + + assert x == 5 + + for i := 0..5 { + assert i >= 0 + assert i < 5 + } +} \ No newline at end of file diff --git a/tests/programs/loop-limited.q b/tests/programs/loop-limited.q deleted file mode 100644 index c26975c..0000000 --- a/tests/programs/loop-limited.q +++ /dev/null @@ -1,6 +0,0 @@ -main() { - loop i := 0..10 { - assert i >= 0 - assert i < 10 - } -} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 15e5e30..3097960 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -59,7 +59,7 @@ var programs = []struct { {"switch", "", "", 0}, {"loop-infinite", "", "", 0}, {"loop-lifetime", "", "", 0}, - {"loop-limited", "", "", 0}, + {"loop-for", "", "", 0}, {"memory-free", "", "", 0}, {"out-of-memory", "", "", 0}, {"index-static", "", "", 0}, From 35ee40988d0535c4e5e82876f8d443cf710628b9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 20 Feb 2025 14:12:25 +0100 Subject: [PATCH 0797/1012] Simplified ast package --- src/ast/AST.go | 56 +++++++++++++++++++ src/ast/Assert.go | 10 ---- src/ast/Assign.go | 10 ---- src/ast/Call.go | 8 --- src/ast/Count.go | 4 ++ src/ast/Define.go | 10 ---- src/ast/For.go | 9 --- src/ast/If.go | 12 ---- src/ast/Loop.go | 6 -- src/ast/Parse.go | 25 ++------- src/ast/Return.go | 10 ---- src/ast/Switch.go | 16 ------ src/ast/block.go | 26 +++++++++ ...{EachInstruction.go => eachInstruction.go} | 14 ++--- src/ast/helpers.go | 21 +++++++ src/ast/{parseSwitch.go => parseCases.go} | 11 ++-- src/ast/{parseNode.go => parseInstruction.go} | 19 ++++--- src/ast/parseKeyword.go | 46 +++++---------- src/core/CompileTokens.go | 6 +- 19 files changed, 151 insertions(+), 168 deletions(-) delete mode 100644 src/ast/Assert.go delete mode 100644 src/ast/Assign.go delete mode 100644 src/ast/Call.go delete mode 100644 src/ast/Define.go delete mode 100644 src/ast/For.go delete mode 100644 src/ast/If.go delete mode 100644 src/ast/Loop.go delete mode 100644 src/ast/Return.go delete mode 100644 src/ast/Switch.go create mode 100644 src/ast/block.go rename src/ast/{EachInstruction.go => eachInstruction.go} (69%) create mode 100644 src/ast/helpers.go rename src/ast/{parseSwitch.go => parseCases.go} (60%) rename src/ast/{parseNode.go => parseInstruction.go} (59%) diff --git a/src/ast/AST.go b/src/ast/AST.go index 8dcaa24..99f1ed1 100644 --- a/src/ast/AST.go +++ b/src/ast/AST.go @@ -1,7 +1,63 @@ package ast +import "git.akyoto.dev/cli/q/src/expression" + // Node is an interface used for all types of AST nodes. type Node any // AST is an abstract syntax tree which is simply a list of nodes. type AST []Node + +// Assert is a condition that must be true, otherwise the program stops. +type Assert struct { + Condition *expression.Expression +} + +// Assign is an assignment to an existing variable or memory location. +type Assign struct { + Expression *expression.Expression +} + +// Call is a function call. +type Call struct { + Expression *expression.Expression +} + +// Case is a case inside a switch. +type Case struct { + Condition *expression.Expression + Body AST +} + +// Define is a variable definition. +type Define struct { + Expression *expression.Expression +} + +// For is a loop with a defined iteration limit. +type For struct { + Head *expression.Expression + Body AST +} + +// If is a conditional branch. +type If struct { + Condition *expression.Expression + Body AST + Else AST +} + +// Loop is an infinite loop. +type Loop struct { + Body AST +} + +// Return is a return statement. +type Return struct { + Values []*expression.Expression +} + +// Switch is a conditional branch with multiple cases. +type Switch struct { + Cases []Case +} diff --git a/src/ast/Assert.go b/src/ast/Assert.go deleted file mode 100644 index 6da47fb..0000000 --- a/src/ast/Assert.go +++ /dev/null @@ -1,10 +0,0 @@ -package ast - -import ( - "git.akyoto.dev/cli/q/src/expression" -) - -// Assert represents a condition that must be true, otherwise the program stops. -type Assert struct { - Condition *expression.Expression -} diff --git a/src/ast/Assign.go b/src/ast/Assign.go deleted file mode 100644 index 8bdbe99..0000000 --- a/src/ast/Assign.go +++ /dev/null @@ -1,10 +0,0 @@ -package ast - -import ( - "git.akyoto.dev/cli/q/src/expression" -) - -// Assign represents an assignment to an existing variable or memory location. -type Assign struct { - Expression *expression.Expression -} diff --git a/src/ast/Call.go b/src/ast/Call.go deleted file mode 100644 index 8aa683e..0000000 --- a/src/ast/Call.go +++ /dev/null @@ -1,8 +0,0 @@ -package ast - -import "git.akyoto.dev/cli/q/src/expression" - -// Call represents a function call. -type Call struct { - Expression *expression.Expression -} diff --git a/src/ast/Count.go b/src/ast/Count.go index 2c9f1f7..83e7f69 100644 --- a/src/ast/Count.go +++ b/src/ast/Count.go @@ -25,6 +25,10 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { count += Count(node.Body, buffer, kind, name) count += Count(node.Else, buffer, kind, name) + case *For: + count += node.Head.Count(buffer, kind, name) + count += Count(node.Body, buffer, kind, name) + case *Loop: count += Count(node.Body, buffer, kind, name) diff --git a/src/ast/Define.go b/src/ast/Define.go deleted file mode 100644 index 0b8030c..0000000 --- a/src/ast/Define.go +++ /dev/null @@ -1,10 +0,0 @@ -package ast - -import ( - "git.akyoto.dev/cli/q/src/expression" -) - -// Define represents a variable definition. -type Define struct { - Expression *expression.Expression -} diff --git a/src/ast/For.go b/src/ast/For.go deleted file mode 100644 index 32a080b..0000000 --- a/src/ast/For.go +++ /dev/null @@ -1,9 +0,0 @@ -package ast - -import "git.akyoto.dev/cli/q/src/expression" - -// For is a loop with a defined iteration limit. -type For struct { - Head *expression.Expression - Body AST -} diff --git a/src/ast/If.go b/src/ast/If.go deleted file mode 100644 index 079640d..0000000 --- a/src/ast/If.go +++ /dev/null @@ -1,12 +0,0 @@ -package ast - -import ( - "git.akyoto.dev/cli/q/src/expression" -) - -// If represents an if statement. -type If struct { - Condition *expression.Expression - Body AST - Else AST -} diff --git a/src/ast/Loop.go b/src/ast/Loop.go deleted file mode 100644 index 8bf3fac..0000000 --- a/src/ast/Loop.go +++ /dev/null @@ -1,6 +0,0 @@ -package ast - -// Loop is a block of infinitely repeating instructions. -type Loop struct { - Body AST -} diff --git a/src/ast/Parse.go b/src/ast/Parse.go index 8b9083e..84c9972 100644 --- a/src/ast/Parse.go +++ b/src/ast/Parse.go @@ -1,18 +1,18 @@ package ast import ( - "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/token" ) // Parse generates an AST from a list of tokens. -func Parse(tokens []token.Token, source []byte) (AST, error) { +func Parse(tokens []token.Token, file *fs.File) (AST, error) { nodes := make(AST, 0, len(tokens)/64) - err := EachInstruction(tokens, func(instruction token.List) error { - node, err := parseNode(instruction, source, nodes) + err := eachInstruction(tokens, func(instruction token.List) error { + node, err := parseInstruction(instruction, file, nodes) - if err == nil && node != nil { + if node != nil { nodes = append(nodes, node) } @@ -21,18 +21,3 @@ func Parse(tokens []token.Token, source []byte) (AST, error) { return nodes, err } - -// IsAssignment returns true if the expression is an assignment. -func IsAssignment(expr *expression.Expression) bool { - return expr.Token.IsAssignment() -} - -// IsFunctionCall returns true if the expression is a function call. -func IsFunctionCall(expr *expression.Expression) bool { - return expr.Token.Kind == token.Call -} - -// IsVariableDefinition returns true if the expression is a variable definition. -func IsVariableDefinition(expr *expression.Expression) bool { - return expr.Token.Kind == token.Define -} diff --git a/src/ast/Return.go b/src/ast/Return.go deleted file mode 100644 index 8815638..0000000 --- a/src/ast/Return.go +++ /dev/null @@ -1,10 +0,0 @@ -package ast - -import ( - "git.akyoto.dev/cli/q/src/expression" -) - -// Return represents a return statement. -type Return struct { - Values []*expression.Expression -} diff --git a/src/ast/Switch.go b/src/ast/Switch.go deleted file mode 100644 index a5fc6a0..0000000 --- a/src/ast/Switch.go +++ /dev/null @@ -1,16 +0,0 @@ -package ast - -import ( - "git.akyoto.dev/cli/q/src/expression" -) - -// Switch represents a switch statement. -type Switch struct { - Cases []Case -} - -// Case represents a case inside a switch. -type Case struct { - Condition *expression.Expression - Body AST -} diff --git a/src/ast/block.go b/src/ast/block.go new file mode 100644 index 0000000..da58e2a --- /dev/null +++ b/src/ast/block.go @@ -0,0 +1,26 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" +) + +// block retrieves the start and end position of a block. +func block(tokens token.List, file *fs.File) (blockStart int, blockEnd int, body AST, err error) { + blockStart = tokens.IndexKind(token.BlockStart) + blockEnd = tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + err = errors.New(errors.MissingBlockStart, file, tokens[0].End()) + return + } + + if blockEnd == -1 { + err = errors.New(errors.MissingBlockEnd, file, tokens[len(tokens)-1].End()) + return + } + + body, err = Parse(tokens[blockStart+1:blockEnd], file) + return +} diff --git a/src/ast/EachInstruction.go b/src/ast/eachInstruction.go similarity index 69% rename from src/ast/EachInstruction.go rename to src/ast/eachInstruction.go index b1a354f..ceb653f 100644 --- a/src/ast/EachInstruction.go +++ b/src/ast/eachInstruction.go @@ -2,13 +2,13 @@ package ast import "git.akyoto.dev/cli/q/src/token" -// EachInstruction calls the function on each instruction. -func EachInstruction(body token.List, call func(token.List) error) error { +// eachInstruction calls the function on each AST node. +func eachInstruction(tokens token.List, call func(token.List) error) error { start := 0 groupLevel := 0 blockLevel := 0 - for i, t := range body { + for i, t := range tokens { if start == i && t.Kind == token.NewLine { start = i + 1 continue @@ -20,7 +20,7 @@ func EachInstruction(body token.List, call func(token.List) error) error { continue } - err := call(body[start:i]) + err := call(tokens[start:i]) if err != nil { return err @@ -44,7 +44,7 @@ func EachInstruction(body token.List, call func(token.List) error) error { continue } - err := call(body[start : i+1]) + err := call(tokens[start : i+1]) if err != nil { return err @@ -54,8 +54,8 @@ func EachInstruction(body token.List, call func(token.List) error) error { } } - if start != len(body) { - return call(body[start:]) + if start != len(tokens) { + return call(tokens[start:]) } return nil diff --git a/src/ast/helpers.go b/src/ast/helpers.go new file mode 100644 index 0000000..ffb2bf5 --- /dev/null +++ b/src/ast/helpers.go @@ -0,0 +1,21 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" +) + +// IsAssignment returns true if the expression is an assignment. +func IsAssignment(expr *expression.Expression) bool { + return expr.Token.IsAssignment() +} + +// IsFunctionCall returns true if the expression is a function call. +func IsFunctionCall(expr *expression.Expression) bool { + return expr.Token.Kind == token.Call +} + +// IsVariableDefinition returns true if the expression is a variable definition. +func IsVariableDefinition(expr *expression.Expression) bool { + return expr.Token.Kind == token.Define +} diff --git a/src/ast/parseSwitch.go b/src/ast/parseCases.go similarity index 60% rename from src/ast/parseSwitch.go rename to src/ast/parseCases.go index 65eb712..da6e561 100644 --- a/src/ast/parseSwitch.go +++ b/src/ast/parseCases.go @@ -2,15 +2,16 @@ package ast import ( "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/token" ) -// parseSwitch generates the cases inside a switch statement. -func parseSwitch(tokens token.List, source []byte) ([]Case, error) { +// parseCases generates the cases inside a switch statement. +func parseCases(tokens token.List, file *fs.File) ([]Case, error) { var cases []Case - err := EachInstruction(tokens, func(caseTokens token.List) error { - blockStart, _, body, err := block(caseTokens, source) + err := eachInstruction(tokens, func(caseTokens token.List) error { + blockStart, _, body, err := block(caseTokens, file) if err != nil { return err @@ -19,7 +20,7 @@ func parseSwitch(tokens token.List, source []byte) ([]Case, error) { conditionTokens := caseTokens[:blockStart] var condition *expression.Expression - if len(conditionTokens) == 1 && conditionTokens[0].Kind == token.Identifier && conditionTokens[0].Text(source) == "_" { + if len(conditionTokens) == 1 && conditionTokens[0].Kind == token.Identifier && conditionTokens[0].Text(file.Bytes) == "_" { condition = nil } else { condition = expression.Parse(conditionTokens) diff --git a/src/ast/parseNode.go b/src/ast/parseInstruction.go similarity index 59% rename from src/ast/parseNode.go rename to src/ast/parseInstruction.go index 919566e..c14db17 100644 --- a/src/ast/parseNode.go +++ b/src/ast/parseInstruction.go @@ -3,13 +3,14 @@ package ast import ( "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/token" ) -// parseNode generates an AST node from an instruction. -func parseNode(tokens token.List, source []byte, nodes AST) (Node, error) { +// parseInstruction generates an AST node from an instruction. +func parseInstruction(tokens token.List, file *fs.File, nodes AST) (Node, error) { if tokens[0].IsKeyword() { - return parseKeyword(tokens, source, nodes) + return parseKeyword(tokens, file, nodes) } expr := expression.Parse(tokens) @@ -19,24 +20,24 @@ func parseNode(tokens token.List, source []byte, nodes AST) (Node, error) { } switch { + case IsFunctionCall(expr): + return &Call{Expression: expr}, nil + case IsVariableDefinition(expr): if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) + return nil, errors.New(errors.MissingOperand, file, expr.Token.End()) } return &Define{Expression: expr}, nil case IsAssignment(expr): if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) + return nil, errors.New(errors.MissingOperand, file, expr.Token.End()) } return &Assign{Expression: expr}, nil - case IsFunctionCall(expr): - return &Call{Expression: expr}, nil - default: - return nil, errors.New(&errors.InvalidInstruction{Instruction: tokens.Text(source)}, nil, tokens[0].Position) + return nil, errors.New(&errors.InvalidInstruction{Instruction: tokens.Text(file.Bytes)}, file, tokens[0].Position) } } diff --git a/src/ast/parseKeyword.go b/src/ast/parseKeyword.go index a0a4d45..c012e38 100644 --- a/src/ast/parseKeyword.go +++ b/src/ast/parseKeyword.go @@ -3,44 +3,45 @@ package ast import ( "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/token" ) // parseKeyword generates a keyword node from an instruction. -func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { +func parseKeyword(tokens token.List, file *fs.File, nodes AST) (Node, error) { switch tokens[0].Kind { case token.Assert: if len(tokens) == 1 { - return nil, errors.New(errors.MissingExpression, nil, tokens[0].End()) + return nil, errors.New(errors.MissingExpression, file, tokens[0].End()) } condition := expression.Parse(tokens[1:]) return &Assert{Condition: condition}, nil case token.If: - blockStart, _, body, err := block(tokens, source) + blockStart, _, body, err := block(tokens, file) condition := expression.Parse(tokens[1:blockStart]) return &If{Condition: condition, Body: body}, err case token.Else: - _, _, body, err := block(tokens, source) + _, _, body, err := block(tokens, file) if len(nodes) == 0 { - return nil, errors.New(errors.ExpectedIfBeforeElse, nil, tokens[0].Position) + return nil, errors.New(errors.ExpectedIfBeforeElse, file, tokens[0].Position) } last := nodes[len(nodes)-1] ifNode, exists := last.(*If) if !exists { - return nil, errors.New(errors.ExpectedIfBeforeElse, nil, tokens[0].Position) + return nil, errors.New(errors.ExpectedIfBeforeElse, file, tokens[0].Position) } ifNode.Else = body return nil, err case token.For: - blockStart, _, body, err := block(tokens, source) + blockStart, _, body, err := block(tokens, file) head := tokens[1:blockStart] loop := &For{ @@ -51,7 +52,7 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { return loop, err case token.Loop: - _, _, body, err := block(tokens, source) + _, _, body, err := block(tokens, file) return &Loop{Body: body}, err case token.Return: @@ -67,42 +68,23 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { blockEnd := tokens.LastIndexKind(token.BlockEnd) if blockStart == -1 { - return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) + return nil, errors.New(errors.MissingBlockStart, file, tokens[0].End()) } if blockEnd == -1 { - return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) + return nil, errors.New(errors.MissingBlockEnd, file, tokens[len(tokens)-1].End()) } body := tokens[blockStart+1 : blockEnd] if len(body) == 0 { - return nil, errors.New(errors.EmptySwitch, nil, tokens[0].Position) + return nil, errors.New(errors.EmptySwitch, file, tokens[0].Position) } - cases, err := parseSwitch(body, source) + cases, err := parseCases(body, file) return &Switch{Cases: cases}, err default: - return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text(source)}, nil, tokens[0].Position) + return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text(file.Bytes)}, file, tokens[0].Position) } } - -// block retrieves the start and end position of a block. -func block(tokens token.List, source []byte) (blockStart int, blockEnd int, body AST, err error) { - blockStart = tokens.IndexKind(token.BlockStart) - blockEnd = tokens.LastIndexKind(token.BlockEnd) - - if blockStart == -1 { - err = errors.New(errors.MissingBlockStart, nil, tokens[0].End()) - return - } - - if blockEnd == -1 { - err = errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) - return - } - - body, err = Parse(tokens[blockStart+1:blockEnd], source) - return -} diff --git a/src/core/CompileTokens.go b/src/core/CompileTokens.go index 06bff0f..2aa390c 100644 --- a/src/core/CompileTokens.go +++ b/src/core/CompileTokens.go @@ -2,18 +2,16 @@ package core import ( "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/token" ) // CompileTokens compiles a token list. func (f *Function) CompileTokens(tokens []token.Token) error { - body, err := ast.Parse(tokens, f.File.Bytes) + tree, err := ast.Parse(tokens, f.File) if err != nil { - err.(*errors.Error).File = f.File return err } - return f.CompileAST(body) + return f.CompileAST(tree) } From b6559505169948404d44a1fc58ceba84f88e4ee5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 20 Feb 2025 14:12:25 +0100 Subject: [PATCH 0798/1012] Simplified ast package --- src/ast/AST.go | 56 +++++++++++++++++++ src/ast/Assert.go | 10 ---- src/ast/Assign.go | 10 ---- src/ast/Call.go | 8 --- src/ast/Count.go | 4 ++ src/ast/Define.go | 10 ---- src/ast/For.go | 9 --- src/ast/If.go | 12 ---- src/ast/Loop.go | 6 -- src/ast/Parse.go | 25 ++------- src/ast/Return.go | 10 ---- src/ast/Switch.go | 16 ------ src/ast/block.go | 26 +++++++++ ...{EachInstruction.go => eachInstruction.go} | 14 ++--- src/ast/helpers.go | 21 +++++++ src/ast/{parseSwitch.go => parseCases.go} | 11 ++-- src/ast/{parseNode.go => parseInstruction.go} | 19 ++++--- src/ast/parseKeyword.go | 46 +++++---------- src/core/CompileTokens.go | 6 +- 19 files changed, 151 insertions(+), 168 deletions(-) delete mode 100644 src/ast/Assert.go delete mode 100644 src/ast/Assign.go delete mode 100644 src/ast/Call.go delete mode 100644 src/ast/Define.go delete mode 100644 src/ast/For.go delete mode 100644 src/ast/If.go delete mode 100644 src/ast/Loop.go delete mode 100644 src/ast/Return.go delete mode 100644 src/ast/Switch.go create mode 100644 src/ast/block.go rename src/ast/{EachInstruction.go => eachInstruction.go} (69%) create mode 100644 src/ast/helpers.go rename src/ast/{parseSwitch.go => parseCases.go} (60%) rename src/ast/{parseNode.go => parseInstruction.go} (59%) diff --git a/src/ast/AST.go b/src/ast/AST.go index 8dcaa24..99f1ed1 100644 --- a/src/ast/AST.go +++ b/src/ast/AST.go @@ -1,7 +1,63 @@ package ast +import "git.akyoto.dev/cli/q/src/expression" + // Node is an interface used for all types of AST nodes. type Node any // AST is an abstract syntax tree which is simply a list of nodes. type AST []Node + +// Assert is a condition that must be true, otherwise the program stops. +type Assert struct { + Condition *expression.Expression +} + +// Assign is an assignment to an existing variable or memory location. +type Assign struct { + Expression *expression.Expression +} + +// Call is a function call. +type Call struct { + Expression *expression.Expression +} + +// Case is a case inside a switch. +type Case struct { + Condition *expression.Expression + Body AST +} + +// Define is a variable definition. +type Define struct { + Expression *expression.Expression +} + +// For is a loop with a defined iteration limit. +type For struct { + Head *expression.Expression + Body AST +} + +// If is a conditional branch. +type If struct { + Condition *expression.Expression + Body AST + Else AST +} + +// Loop is an infinite loop. +type Loop struct { + Body AST +} + +// Return is a return statement. +type Return struct { + Values []*expression.Expression +} + +// Switch is a conditional branch with multiple cases. +type Switch struct { + Cases []Case +} diff --git a/src/ast/Assert.go b/src/ast/Assert.go deleted file mode 100644 index 6da47fb..0000000 --- a/src/ast/Assert.go +++ /dev/null @@ -1,10 +0,0 @@ -package ast - -import ( - "git.akyoto.dev/cli/q/src/expression" -) - -// Assert represents a condition that must be true, otherwise the program stops. -type Assert struct { - Condition *expression.Expression -} diff --git a/src/ast/Assign.go b/src/ast/Assign.go deleted file mode 100644 index 8bdbe99..0000000 --- a/src/ast/Assign.go +++ /dev/null @@ -1,10 +0,0 @@ -package ast - -import ( - "git.akyoto.dev/cli/q/src/expression" -) - -// Assign represents an assignment to an existing variable or memory location. -type Assign struct { - Expression *expression.Expression -} diff --git a/src/ast/Call.go b/src/ast/Call.go deleted file mode 100644 index 8aa683e..0000000 --- a/src/ast/Call.go +++ /dev/null @@ -1,8 +0,0 @@ -package ast - -import "git.akyoto.dev/cli/q/src/expression" - -// Call represents a function call. -type Call struct { - Expression *expression.Expression -} diff --git a/src/ast/Count.go b/src/ast/Count.go index 2c9f1f7..83e7f69 100644 --- a/src/ast/Count.go +++ b/src/ast/Count.go @@ -25,6 +25,10 @@ func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { count += Count(node.Body, buffer, kind, name) count += Count(node.Else, buffer, kind, name) + case *For: + count += node.Head.Count(buffer, kind, name) + count += Count(node.Body, buffer, kind, name) + case *Loop: count += Count(node.Body, buffer, kind, name) diff --git a/src/ast/Define.go b/src/ast/Define.go deleted file mode 100644 index 0b8030c..0000000 --- a/src/ast/Define.go +++ /dev/null @@ -1,10 +0,0 @@ -package ast - -import ( - "git.akyoto.dev/cli/q/src/expression" -) - -// Define represents a variable definition. -type Define struct { - Expression *expression.Expression -} diff --git a/src/ast/For.go b/src/ast/For.go deleted file mode 100644 index 32a080b..0000000 --- a/src/ast/For.go +++ /dev/null @@ -1,9 +0,0 @@ -package ast - -import "git.akyoto.dev/cli/q/src/expression" - -// For is a loop with a defined iteration limit. -type For struct { - Head *expression.Expression - Body AST -} diff --git a/src/ast/If.go b/src/ast/If.go deleted file mode 100644 index 079640d..0000000 --- a/src/ast/If.go +++ /dev/null @@ -1,12 +0,0 @@ -package ast - -import ( - "git.akyoto.dev/cli/q/src/expression" -) - -// If represents an if statement. -type If struct { - Condition *expression.Expression - Body AST - Else AST -} diff --git a/src/ast/Loop.go b/src/ast/Loop.go deleted file mode 100644 index 8bf3fac..0000000 --- a/src/ast/Loop.go +++ /dev/null @@ -1,6 +0,0 @@ -package ast - -// Loop is a block of infinitely repeating instructions. -type Loop struct { - Body AST -} diff --git a/src/ast/Parse.go b/src/ast/Parse.go index 8b9083e..84c9972 100644 --- a/src/ast/Parse.go +++ b/src/ast/Parse.go @@ -1,18 +1,18 @@ package ast import ( - "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/token" ) // Parse generates an AST from a list of tokens. -func Parse(tokens []token.Token, source []byte) (AST, error) { +func Parse(tokens []token.Token, file *fs.File) (AST, error) { nodes := make(AST, 0, len(tokens)/64) - err := EachInstruction(tokens, func(instruction token.List) error { - node, err := parseNode(instruction, source, nodes) + err := eachInstruction(tokens, func(instruction token.List) error { + node, err := parseInstruction(instruction, file, nodes) - if err == nil && node != nil { + if node != nil { nodes = append(nodes, node) } @@ -21,18 +21,3 @@ func Parse(tokens []token.Token, source []byte) (AST, error) { return nodes, err } - -// IsAssignment returns true if the expression is an assignment. -func IsAssignment(expr *expression.Expression) bool { - return expr.Token.IsAssignment() -} - -// IsFunctionCall returns true if the expression is a function call. -func IsFunctionCall(expr *expression.Expression) bool { - return expr.Token.Kind == token.Call -} - -// IsVariableDefinition returns true if the expression is a variable definition. -func IsVariableDefinition(expr *expression.Expression) bool { - return expr.Token.Kind == token.Define -} diff --git a/src/ast/Return.go b/src/ast/Return.go deleted file mode 100644 index 8815638..0000000 --- a/src/ast/Return.go +++ /dev/null @@ -1,10 +0,0 @@ -package ast - -import ( - "git.akyoto.dev/cli/q/src/expression" -) - -// Return represents a return statement. -type Return struct { - Values []*expression.Expression -} diff --git a/src/ast/Switch.go b/src/ast/Switch.go deleted file mode 100644 index a5fc6a0..0000000 --- a/src/ast/Switch.go +++ /dev/null @@ -1,16 +0,0 @@ -package ast - -import ( - "git.akyoto.dev/cli/q/src/expression" -) - -// Switch represents a switch statement. -type Switch struct { - Cases []Case -} - -// Case represents a case inside a switch. -type Case struct { - Condition *expression.Expression - Body AST -} diff --git a/src/ast/block.go b/src/ast/block.go new file mode 100644 index 0000000..da58e2a --- /dev/null +++ b/src/ast/block.go @@ -0,0 +1,26 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" +) + +// block retrieves the start and end position of a block. +func block(tokens token.List, file *fs.File) (blockStart int, blockEnd int, body AST, err error) { + blockStart = tokens.IndexKind(token.BlockStart) + blockEnd = tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + err = errors.New(errors.MissingBlockStart, file, tokens[0].End()) + return + } + + if blockEnd == -1 { + err = errors.New(errors.MissingBlockEnd, file, tokens[len(tokens)-1].End()) + return + } + + body, err = Parse(tokens[blockStart+1:blockEnd], file) + return +} diff --git a/src/ast/EachInstruction.go b/src/ast/eachInstruction.go similarity index 69% rename from src/ast/EachInstruction.go rename to src/ast/eachInstruction.go index b1a354f..ceb653f 100644 --- a/src/ast/EachInstruction.go +++ b/src/ast/eachInstruction.go @@ -2,13 +2,13 @@ package ast import "git.akyoto.dev/cli/q/src/token" -// EachInstruction calls the function on each instruction. -func EachInstruction(body token.List, call func(token.List) error) error { +// eachInstruction calls the function on each AST node. +func eachInstruction(tokens token.List, call func(token.List) error) error { start := 0 groupLevel := 0 blockLevel := 0 - for i, t := range body { + for i, t := range tokens { if start == i && t.Kind == token.NewLine { start = i + 1 continue @@ -20,7 +20,7 @@ func EachInstruction(body token.List, call func(token.List) error) error { continue } - err := call(body[start:i]) + err := call(tokens[start:i]) if err != nil { return err @@ -44,7 +44,7 @@ func EachInstruction(body token.List, call func(token.List) error) error { continue } - err := call(body[start : i+1]) + err := call(tokens[start : i+1]) if err != nil { return err @@ -54,8 +54,8 @@ func EachInstruction(body token.List, call func(token.List) error) error { } } - if start != len(body) { - return call(body[start:]) + if start != len(tokens) { + return call(tokens[start:]) } return nil diff --git a/src/ast/helpers.go b/src/ast/helpers.go new file mode 100644 index 0000000..ffb2bf5 --- /dev/null +++ b/src/ast/helpers.go @@ -0,0 +1,21 @@ +package ast + +import ( + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" +) + +// IsAssignment returns true if the expression is an assignment. +func IsAssignment(expr *expression.Expression) bool { + return expr.Token.IsAssignment() +} + +// IsFunctionCall returns true if the expression is a function call. +func IsFunctionCall(expr *expression.Expression) bool { + return expr.Token.Kind == token.Call +} + +// IsVariableDefinition returns true if the expression is a variable definition. +func IsVariableDefinition(expr *expression.Expression) bool { + return expr.Token.Kind == token.Define +} diff --git a/src/ast/parseSwitch.go b/src/ast/parseCases.go similarity index 60% rename from src/ast/parseSwitch.go rename to src/ast/parseCases.go index 65eb712..da6e561 100644 --- a/src/ast/parseSwitch.go +++ b/src/ast/parseCases.go @@ -2,15 +2,16 @@ package ast import ( "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/token" ) -// parseSwitch generates the cases inside a switch statement. -func parseSwitch(tokens token.List, source []byte) ([]Case, error) { +// parseCases generates the cases inside a switch statement. +func parseCases(tokens token.List, file *fs.File) ([]Case, error) { var cases []Case - err := EachInstruction(tokens, func(caseTokens token.List) error { - blockStart, _, body, err := block(caseTokens, source) + err := eachInstruction(tokens, func(caseTokens token.List) error { + blockStart, _, body, err := block(caseTokens, file) if err != nil { return err @@ -19,7 +20,7 @@ func parseSwitch(tokens token.List, source []byte) ([]Case, error) { conditionTokens := caseTokens[:blockStart] var condition *expression.Expression - if len(conditionTokens) == 1 && conditionTokens[0].Kind == token.Identifier && conditionTokens[0].Text(source) == "_" { + if len(conditionTokens) == 1 && conditionTokens[0].Kind == token.Identifier && conditionTokens[0].Text(file.Bytes) == "_" { condition = nil } else { condition = expression.Parse(conditionTokens) diff --git a/src/ast/parseNode.go b/src/ast/parseInstruction.go similarity index 59% rename from src/ast/parseNode.go rename to src/ast/parseInstruction.go index 919566e..c14db17 100644 --- a/src/ast/parseNode.go +++ b/src/ast/parseInstruction.go @@ -3,13 +3,14 @@ package ast import ( "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/token" ) -// parseNode generates an AST node from an instruction. -func parseNode(tokens token.List, source []byte, nodes AST) (Node, error) { +// parseInstruction generates an AST node from an instruction. +func parseInstruction(tokens token.List, file *fs.File, nodes AST) (Node, error) { if tokens[0].IsKeyword() { - return parseKeyword(tokens, source, nodes) + return parseKeyword(tokens, file, nodes) } expr := expression.Parse(tokens) @@ -19,24 +20,24 @@ func parseNode(tokens token.List, source []byte, nodes AST) (Node, error) { } switch { + case IsFunctionCall(expr): + return &Call{Expression: expr}, nil + case IsVariableDefinition(expr): if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) + return nil, errors.New(errors.MissingOperand, file, expr.Token.End()) } return &Define{Expression: expr}, nil case IsAssignment(expr): if len(expr.Children) < 2 { - return nil, errors.New(errors.MissingOperand, nil, expr.Token.End()) + return nil, errors.New(errors.MissingOperand, file, expr.Token.End()) } return &Assign{Expression: expr}, nil - case IsFunctionCall(expr): - return &Call{Expression: expr}, nil - default: - return nil, errors.New(&errors.InvalidInstruction{Instruction: tokens.Text(source)}, nil, tokens[0].Position) + return nil, errors.New(&errors.InvalidInstruction{Instruction: tokens.Text(file.Bytes)}, file, tokens[0].Position) } } diff --git a/src/ast/parseKeyword.go b/src/ast/parseKeyword.go index a0a4d45..c012e38 100644 --- a/src/ast/parseKeyword.go +++ b/src/ast/parseKeyword.go @@ -3,44 +3,45 @@ package ast import ( "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/token" ) // parseKeyword generates a keyword node from an instruction. -func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { +func parseKeyword(tokens token.List, file *fs.File, nodes AST) (Node, error) { switch tokens[0].Kind { case token.Assert: if len(tokens) == 1 { - return nil, errors.New(errors.MissingExpression, nil, tokens[0].End()) + return nil, errors.New(errors.MissingExpression, file, tokens[0].End()) } condition := expression.Parse(tokens[1:]) return &Assert{Condition: condition}, nil case token.If: - blockStart, _, body, err := block(tokens, source) + blockStart, _, body, err := block(tokens, file) condition := expression.Parse(tokens[1:blockStart]) return &If{Condition: condition, Body: body}, err case token.Else: - _, _, body, err := block(tokens, source) + _, _, body, err := block(tokens, file) if len(nodes) == 0 { - return nil, errors.New(errors.ExpectedIfBeforeElse, nil, tokens[0].Position) + return nil, errors.New(errors.ExpectedIfBeforeElse, file, tokens[0].Position) } last := nodes[len(nodes)-1] ifNode, exists := last.(*If) if !exists { - return nil, errors.New(errors.ExpectedIfBeforeElse, nil, tokens[0].Position) + return nil, errors.New(errors.ExpectedIfBeforeElse, file, tokens[0].Position) } ifNode.Else = body return nil, err case token.For: - blockStart, _, body, err := block(tokens, source) + blockStart, _, body, err := block(tokens, file) head := tokens[1:blockStart] loop := &For{ @@ -51,7 +52,7 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { return loop, err case token.Loop: - _, _, body, err := block(tokens, source) + _, _, body, err := block(tokens, file) return &Loop{Body: body}, err case token.Return: @@ -67,42 +68,23 @@ func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) { blockEnd := tokens.LastIndexKind(token.BlockEnd) if blockStart == -1 { - return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) + return nil, errors.New(errors.MissingBlockStart, file, tokens[0].End()) } if blockEnd == -1 { - return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) + return nil, errors.New(errors.MissingBlockEnd, file, tokens[len(tokens)-1].End()) } body := tokens[blockStart+1 : blockEnd] if len(body) == 0 { - return nil, errors.New(errors.EmptySwitch, nil, tokens[0].Position) + return nil, errors.New(errors.EmptySwitch, file, tokens[0].Position) } - cases, err := parseSwitch(body, source) + cases, err := parseCases(body, file) return &Switch{Cases: cases}, err default: - return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text(source)}, nil, tokens[0].Position) + return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text(file.Bytes)}, file, tokens[0].Position) } } - -// block retrieves the start and end position of a block. -func block(tokens token.List, source []byte) (blockStart int, blockEnd int, body AST, err error) { - blockStart = tokens.IndexKind(token.BlockStart) - blockEnd = tokens.LastIndexKind(token.BlockEnd) - - if blockStart == -1 { - err = errors.New(errors.MissingBlockStart, nil, tokens[0].End()) - return - } - - if blockEnd == -1 { - err = errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) - return - } - - body, err = Parse(tokens[blockStart+1:blockEnd], source) - return -} diff --git a/src/core/CompileTokens.go b/src/core/CompileTokens.go index 06bff0f..2aa390c 100644 --- a/src/core/CompileTokens.go +++ b/src/core/CompileTokens.go @@ -2,18 +2,16 @@ package core import ( "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/token" ) // CompileTokens compiles a token list. func (f *Function) CompileTokens(tokens []token.Token) error { - body, err := ast.Parse(tokens, f.File.Bytes) + tree, err := ast.Parse(tokens, f.File) if err != nil { - err.(*errors.Error).File = f.File return err } - return f.CompileAST(body) + return f.CompileAST(tree) } From 5f7cc9115acfdaa58dd2251c5ce62dd6c9abf2c9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 20 Feb 2025 15:06:22 +0100 Subject: [PATCH 0799/1012] Added more tests --- src/ast/ast_test.go | 64 ++++++++++++++++++++++++++++++++++++++ src/ast/eachInstruction.go | 9 +++++- 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/ast/ast_test.go diff --git a/src/ast/ast_test.go b/src/ast/ast_test.go new file mode 100644 index 0000000..c7307a4 --- /dev/null +++ b/src/ast/ast_test.go @@ -0,0 +1,64 @@ +package ast_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/go/assert" +) + +func TestAssign(t *testing.T) { + tree, err := parse("a := 0\na = 0") + assert.Nil(t, err) + assert.Equal(t, len(tree), 2) + + definition := tree[0].(*ast.Define) + assignment := tree[1].(*ast.Assign) + assert.NotNil(t, definition.Expression) + assert.NotNil(t, assignment.Expression) +} + +func TestReturn(t *testing.T) { + tree, err := parse("return") + assert.Nil(t, err) + assert.Equal(t, len(tree), 1) + + ret := tree[0].(*ast.Return) + assert.Nil(t, ret.Values) +} + +func TestReturnValues(t *testing.T) { + tree, err := parse("return 42") + assert.Nil(t, err) + assert.Equal(t, len(tree), 1) + + ret := tree[0].(*ast.Return) + assert.Equal(t, len(ret.Values), 1) + assert.Equal(t, ret.Values[0].Token.Kind, token.Number) +} + +func TestIfElse(t *testing.T) { + tree, err := parse("if x == 0 {} else {}") + assert.Nil(t, err) + assert.Equal(t, len(tree), 1) + + branch := tree[0].(*ast.If) + assert.NotNil(t, branch.Condition) + assert.NotNil(t, branch.Body) + assert.NotNil(t, branch.Else) +} + +func TestLoop(t *testing.T) { + tree, err := parse("loop{}") + assert.Nil(t, err) + assert.Equal(t, len(tree), 1) +} + +func parse(code string) (ast.AST, error) { + src := []byte(code) + tokens := token.Tokenize(src) + file := &fs.File{Bytes: src, Tokens: tokens} + return ast.Parse(tokens, file) +} diff --git a/src/ast/eachInstruction.go b/src/ast/eachInstruction.go index ceb653f..e4df0e2 100644 --- a/src/ast/eachInstruction.go +++ b/src/ast/eachInstruction.go @@ -51,10 +51,17 @@ func eachInstruction(tokens token.List, call func(token.List) error) error { } start = i + 1 + + case token.EOF: + if start < i { + return call(tokens[start:i]) + } + + return nil } } - if start != len(tokens) { + if start < len(tokens) { return call(tokens[start:]) } From 9779476fe7e5ef2d47f7cd7a1777de7430120491 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 20 Feb 2025 15:06:22 +0100 Subject: [PATCH 0800/1012] Added more tests --- src/ast/ast_test.go | 64 ++++++++++++++++++++++++++++++++++++++ src/ast/eachInstruction.go | 9 +++++- 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/ast/ast_test.go diff --git a/src/ast/ast_test.go b/src/ast/ast_test.go new file mode 100644 index 0000000..c7307a4 --- /dev/null +++ b/src/ast/ast_test.go @@ -0,0 +1,64 @@ +package ast_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/ast" + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/go/assert" +) + +func TestAssign(t *testing.T) { + tree, err := parse("a := 0\na = 0") + assert.Nil(t, err) + assert.Equal(t, len(tree), 2) + + definition := tree[0].(*ast.Define) + assignment := tree[1].(*ast.Assign) + assert.NotNil(t, definition.Expression) + assert.NotNil(t, assignment.Expression) +} + +func TestReturn(t *testing.T) { + tree, err := parse("return") + assert.Nil(t, err) + assert.Equal(t, len(tree), 1) + + ret := tree[0].(*ast.Return) + assert.Nil(t, ret.Values) +} + +func TestReturnValues(t *testing.T) { + tree, err := parse("return 42") + assert.Nil(t, err) + assert.Equal(t, len(tree), 1) + + ret := tree[0].(*ast.Return) + assert.Equal(t, len(ret.Values), 1) + assert.Equal(t, ret.Values[0].Token.Kind, token.Number) +} + +func TestIfElse(t *testing.T) { + tree, err := parse("if x == 0 {} else {}") + assert.Nil(t, err) + assert.Equal(t, len(tree), 1) + + branch := tree[0].(*ast.If) + assert.NotNil(t, branch.Condition) + assert.NotNil(t, branch.Body) + assert.NotNil(t, branch.Else) +} + +func TestLoop(t *testing.T) { + tree, err := parse("loop{}") + assert.Nil(t, err) + assert.Equal(t, len(tree), 1) +} + +func parse(code string) (ast.AST, error) { + src := []byte(code) + tokens := token.Tokenize(src) + file := &fs.File{Bytes: src, Tokens: tokens} + return ast.Parse(tokens, file) +} diff --git a/src/ast/eachInstruction.go b/src/ast/eachInstruction.go index ceb653f..e4df0e2 100644 --- a/src/ast/eachInstruction.go +++ b/src/ast/eachInstruction.go @@ -51,10 +51,17 @@ func eachInstruction(tokens token.List, call func(token.List) error) error { } start = i + 1 + + case token.EOF: + if start < i { + return call(tokens[start:i]) + } + + return nil } } - if start != len(tokens) { + if start < len(tokens) { return call(tokens[start:]) } From f6242bc7ece42f34cda0b9d63834b0da33dcd872 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 20 Feb 2025 16:55:17 +0100 Subject: [PATCH 0801/1012] Improved code quality --- src/asm/Memory.go | 5 +++-- src/asm/MemoryNumber.go | 6 ++---- src/asm/Number.go | 4 ++-- src/asmc/call.go | 2 +- src/asmc/jump.go | 2 +- src/asmc/resolvePointers.go | 7 +++--- src/core/ArrayElementToRegister.go | 8 ++++--- src/core/CompileDelete.go | 2 +- src/core/ToNumber.go | 34 ++++++++++++++---------------- src/token/Tokenize_test.go | 15 +++++++++++++ src/types/Array.go | 2 -- src/types/Common.go | 1 + 12 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/asm/Memory.go b/src/asm/Memory.go index a183de6..1076405 100644 --- a/src/asm/Memory.go +++ b/src/asm/Memory.go @@ -3,6 +3,7 @@ package asm import ( "fmt" "math" + "strconv" "strings" "git.akyoto.dev/cli/q/src/cpu" @@ -31,12 +32,12 @@ func (mem *Memory) Format(custom string) string { tmp.WriteString("+") } - tmp.WriteString(fmt.Sprint(mem.Offset)) + tmp.WriteString(strconv.Itoa(int(mem.Offset))) } tmp.WriteString("], ") tmp.WriteString(custom) tmp.WriteString(", ") - tmp.WriteString(fmt.Sprint(mem.Length)) + tmp.WriteString(strconv.Itoa(int(mem.Length))) return tmp.String() } diff --git a/src/asm/MemoryNumber.go b/src/asm/MemoryNumber.go index 651a20b..47c39d5 100644 --- a/src/asm/MemoryNumber.go +++ b/src/asm/MemoryNumber.go @@ -1,8 +1,6 @@ package asm -import ( - "fmt" -) +import "strconv" // MemoryNumber operates with a memory address and a number. type MemoryNumber struct { @@ -12,7 +10,7 @@ type MemoryNumber struct { // String returns a human readable version. func (data *MemoryNumber) String() string { - return data.Address.Format(fmt.Sprint(data.Number)) + return data.Address.Format(strconv.Itoa(data.Number)) } // MemoryNumber adds an instruction with a memory address and a number. diff --git a/src/asm/Number.go b/src/asm/Number.go index 768b232..3f0271c 100644 --- a/src/asm/Number.go +++ b/src/asm/Number.go @@ -1,7 +1,7 @@ package asm import ( - "fmt" + "strconv" ) // Number operates with just a number. @@ -11,7 +11,7 @@ type Number struct { // String returns a human readable version. func (data *Number) String() string { - return fmt.Sprintf("%d", data.Number) + return strconv.Itoa(data.Number) } // Number adds an instruction with a number. diff --git a/src/asmc/call.go b/src/asmc/call.go index 940c0da..cf84156 100644 --- a/src/asmc/call.go +++ b/src/asmc/call.go @@ -24,7 +24,7 @@ func (c *compiler) call(x asm.Instruction) { } distance := destination - (pointer.Position + Address(pointer.Size)) - return Address(distance) + return distance } c.codePointers = append(c.codePointers, pointer) diff --git a/src/asmc/jump.go b/src/asmc/jump.go index e6d8f65..f01f547 100644 --- a/src/asmc/jump.go +++ b/src/asmc/jump.go @@ -40,7 +40,7 @@ func (c *compiler) jump(x asm.Instruction) { } distance := destination - (pointer.Position + Address(pointer.Size)) - return Address(distance) + return distance } c.codePointers = append(c.codePointers, pointer) diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index 0dc879e..112a0d1 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -40,7 +40,7 @@ restart: case 0xEB: // JMP jump = []byte{0xE9} default: - panic(fmt.Errorf("failed to increase pointer size for instruction 0x%x", opCode)) + panic(fmt.Sprintf("failed to increase pointer size for instruction 0x%x", opCode)) } pointer.Position += Address(len(jump) - int(pointer.OpSize)) @@ -90,9 +90,10 @@ restart: for _, pointer := range c.dllPointers { destination := importsStart + pointer.Resolve() - delta := destination - Address(c.codeStart+pointer.Position+Address(pointer.Size)) + from := c.codeStart + pointer.Position + Address(pointer.Size) + offset := destination - from slice := c.code[pointer.Position : pointer.Position+4] - binary.LittleEndian.PutUint32(slice, uint32(delta)) + binary.LittleEndian.PutUint32(slice, uint32(offset)) } } } diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go index c623035..66777b9 100644 --- a/src/core/ArrayElementToRegister.go +++ b/src/core/ArrayElementToRegister.go @@ -29,7 +29,8 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register Length: byte(1), } - if index.Token.IsNumeric() { + switch { + case index.Token.IsNumeric(): offset, err := f.ToNumber(index.Token) if err != nil { @@ -38,7 +39,7 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register memory.Offset = int8(offset) - } else if index.Token.Kind == token.Identifier { + case index.Token.Kind == token.Identifier: indexName := index.Token.Text(f.File.Bytes) indexVariable := f.VariableByName(indexName) @@ -53,7 +54,8 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register } memory.OffsetRegister = indexVariable.Register - } else { + + default: typ, err := f.ExpressionToRegister(index, register) if err != nil { diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index 6eae244..4792fb0 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -21,7 +21,7 @@ func (f *Function) CompileDelete(root *expression.Expression) error { f.SaveRegister(f.CPU.Input[0]) f.SaveRegister(f.CPU.Input[1]) f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Register) - f.RegisterNumber(asm.MOVE, f.CPU.Input[1], int(variable.Type.(*types.Pointer).To.Size())) + f.RegisterNumber(asm.MOVE, f.CPU.Input[1], variable.Type.(*types.Pointer).To.Size()) f.CallSafe(f.Functions["mem.free"], f.CPU.Input[:2]) return nil } diff --git a/src/core/ToNumber.go b/src/core/ToNumber.go index da73904..1978939 100644 --- a/src/core/ToNumber.go +++ b/src/core/ToNumber.go @@ -13,30 +13,28 @@ import ( func (f *Function) ToNumber(t token.Token) (int, error) { switch t.Kind { case token.Number: - digits := t.Text(f.File.Bytes) + var ( + digits = t.Text(f.File.Bytes) + number int64 + err error + ) - if strings.HasPrefix(digits, "0x") { - number, err := strconv.ParseInt(digits[2:], 16, 64) - return int(number), err + switch { + case strings.HasPrefix(digits, "0x"): + number, err = strconv.ParseInt(digits[2:], 16, 64) + case strings.HasPrefix(digits, "0o"): + number, err = strconv.ParseInt(digits[2:], 8, 64) + case strings.HasPrefix(digits, "0b"): + number, err = strconv.ParseInt(digits[2:], 2, 64) + default: + number, err = strconv.ParseInt(digits, 10, 64) } - if strings.HasPrefix(digits, "0o") { - number, err := strconv.ParseInt(digits[2:], 8, 64) - return int(number), err - } - - if strings.HasPrefix(digits, "0b") { - number, err := strconv.ParseInt(digits[2:], 2, 64) - return int(number), err - } - - number, err := strconv.Atoi(digits) - if err != nil { - return 0, errors.New(err, f.File, t.Position) + return 0, errors.New(errors.InvalidNumber, f.File, t.Position) } - return number, nil + return int(number), nil case token.Rune: r := t.Bytes(f.File.Bytes) diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index ab256cb..3ed541a 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -388,6 +388,21 @@ func TestLeadingZero(t *testing.T) { } } +func TestRange(t *testing.T) { + tokens := token.Tokenize([]byte("a..b")) + + expected := []token.Kind{ + token.Identifier, + token.Range, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + func TestSeparator(t *testing.T) { tokens := token.Tokenize([]byte("a,b,c")) diff --git a/src/types/Array.go b/src/types/Array.go index fd6add2..b6629f0 100644 --- a/src/types/Array.go +++ b/src/types/Array.go @@ -1,7 +1,5 @@ package types -var String = &Array{Of: Int8} - // Array is the address of an object. type Array struct { Of Type diff --git a/src/types/Common.go b/src/types/Common.go index a846b61..87d5889 100644 --- a/src/types/Common.go +++ b/src/types/Common.go @@ -10,6 +10,7 @@ var ( Int8 = &Base{name: "int8", size: 1} Float64 = &Base{name: "float64", size: 8} Float32 = &Base{name: "float32", size: 4} + String = &Array{Of: Int8} ) var ( From 36d0142573920d8d2128ec23d09521a2d8625a5f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 20 Feb 2025 16:55:17 +0100 Subject: [PATCH 0802/1012] Improved code quality --- src/asm/Memory.go | 5 +++-- src/asm/MemoryNumber.go | 6 ++---- src/asm/Number.go | 4 ++-- src/asmc/call.go | 2 +- src/asmc/jump.go | 2 +- src/asmc/resolvePointers.go | 7 +++--- src/core/ArrayElementToRegister.go | 8 ++++--- src/core/CompileDelete.go | 2 +- src/core/ToNumber.go | 34 ++++++++++++++---------------- src/token/Tokenize_test.go | 15 +++++++++++++ src/types/Array.go | 2 -- src/types/Common.go | 1 + 12 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/asm/Memory.go b/src/asm/Memory.go index a183de6..1076405 100644 --- a/src/asm/Memory.go +++ b/src/asm/Memory.go @@ -3,6 +3,7 @@ package asm import ( "fmt" "math" + "strconv" "strings" "git.akyoto.dev/cli/q/src/cpu" @@ -31,12 +32,12 @@ func (mem *Memory) Format(custom string) string { tmp.WriteString("+") } - tmp.WriteString(fmt.Sprint(mem.Offset)) + tmp.WriteString(strconv.Itoa(int(mem.Offset))) } tmp.WriteString("], ") tmp.WriteString(custom) tmp.WriteString(", ") - tmp.WriteString(fmt.Sprint(mem.Length)) + tmp.WriteString(strconv.Itoa(int(mem.Length))) return tmp.String() } diff --git a/src/asm/MemoryNumber.go b/src/asm/MemoryNumber.go index 651a20b..47c39d5 100644 --- a/src/asm/MemoryNumber.go +++ b/src/asm/MemoryNumber.go @@ -1,8 +1,6 @@ package asm -import ( - "fmt" -) +import "strconv" // MemoryNumber operates with a memory address and a number. type MemoryNumber struct { @@ -12,7 +10,7 @@ type MemoryNumber struct { // String returns a human readable version. func (data *MemoryNumber) String() string { - return data.Address.Format(fmt.Sprint(data.Number)) + return data.Address.Format(strconv.Itoa(data.Number)) } // MemoryNumber adds an instruction with a memory address and a number. diff --git a/src/asm/Number.go b/src/asm/Number.go index 768b232..3f0271c 100644 --- a/src/asm/Number.go +++ b/src/asm/Number.go @@ -1,7 +1,7 @@ package asm import ( - "fmt" + "strconv" ) // Number operates with just a number. @@ -11,7 +11,7 @@ type Number struct { // String returns a human readable version. func (data *Number) String() string { - return fmt.Sprintf("%d", data.Number) + return strconv.Itoa(data.Number) } // Number adds an instruction with a number. diff --git a/src/asmc/call.go b/src/asmc/call.go index 940c0da..cf84156 100644 --- a/src/asmc/call.go +++ b/src/asmc/call.go @@ -24,7 +24,7 @@ func (c *compiler) call(x asm.Instruction) { } distance := destination - (pointer.Position + Address(pointer.Size)) - return Address(distance) + return distance } c.codePointers = append(c.codePointers, pointer) diff --git a/src/asmc/jump.go b/src/asmc/jump.go index e6d8f65..f01f547 100644 --- a/src/asmc/jump.go +++ b/src/asmc/jump.go @@ -40,7 +40,7 @@ func (c *compiler) jump(x asm.Instruction) { } distance := destination - (pointer.Position + Address(pointer.Size)) - return Address(distance) + return distance } c.codePointers = append(c.codePointers, pointer) diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index 0dc879e..112a0d1 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -40,7 +40,7 @@ restart: case 0xEB: // JMP jump = []byte{0xE9} default: - panic(fmt.Errorf("failed to increase pointer size for instruction 0x%x", opCode)) + panic(fmt.Sprintf("failed to increase pointer size for instruction 0x%x", opCode)) } pointer.Position += Address(len(jump) - int(pointer.OpSize)) @@ -90,9 +90,10 @@ restart: for _, pointer := range c.dllPointers { destination := importsStart + pointer.Resolve() - delta := destination - Address(c.codeStart+pointer.Position+Address(pointer.Size)) + from := c.codeStart + pointer.Position + Address(pointer.Size) + offset := destination - from slice := c.code[pointer.Position : pointer.Position+4] - binary.LittleEndian.PutUint32(slice, uint32(delta)) + binary.LittleEndian.PutUint32(slice, uint32(offset)) } } } diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go index c623035..66777b9 100644 --- a/src/core/ArrayElementToRegister.go +++ b/src/core/ArrayElementToRegister.go @@ -29,7 +29,8 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register Length: byte(1), } - if index.Token.IsNumeric() { + switch { + case index.Token.IsNumeric(): offset, err := f.ToNumber(index.Token) if err != nil { @@ -38,7 +39,7 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register memory.Offset = int8(offset) - } else if index.Token.Kind == token.Identifier { + case index.Token.Kind == token.Identifier: indexName := index.Token.Text(f.File.Bytes) indexVariable := f.VariableByName(indexName) @@ -53,7 +54,8 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register } memory.OffsetRegister = indexVariable.Register - } else { + + default: typ, err := f.ExpressionToRegister(index, register) if err != nil { diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index 6eae244..4792fb0 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -21,7 +21,7 @@ func (f *Function) CompileDelete(root *expression.Expression) error { f.SaveRegister(f.CPU.Input[0]) f.SaveRegister(f.CPU.Input[1]) f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Register) - f.RegisterNumber(asm.MOVE, f.CPU.Input[1], int(variable.Type.(*types.Pointer).To.Size())) + f.RegisterNumber(asm.MOVE, f.CPU.Input[1], variable.Type.(*types.Pointer).To.Size()) f.CallSafe(f.Functions["mem.free"], f.CPU.Input[:2]) return nil } diff --git a/src/core/ToNumber.go b/src/core/ToNumber.go index da73904..1978939 100644 --- a/src/core/ToNumber.go +++ b/src/core/ToNumber.go @@ -13,30 +13,28 @@ import ( func (f *Function) ToNumber(t token.Token) (int, error) { switch t.Kind { case token.Number: - digits := t.Text(f.File.Bytes) + var ( + digits = t.Text(f.File.Bytes) + number int64 + err error + ) - if strings.HasPrefix(digits, "0x") { - number, err := strconv.ParseInt(digits[2:], 16, 64) - return int(number), err + switch { + case strings.HasPrefix(digits, "0x"): + number, err = strconv.ParseInt(digits[2:], 16, 64) + case strings.HasPrefix(digits, "0o"): + number, err = strconv.ParseInt(digits[2:], 8, 64) + case strings.HasPrefix(digits, "0b"): + number, err = strconv.ParseInt(digits[2:], 2, 64) + default: + number, err = strconv.ParseInt(digits, 10, 64) } - if strings.HasPrefix(digits, "0o") { - number, err := strconv.ParseInt(digits[2:], 8, 64) - return int(number), err - } - - if strings.HasPrefix(digits, "0b") { - number, err := strconv.ParseInt(digits[2:], 2, 64) - return int(number), err - } - - number, err := strconv.Atoi(digits) - if err != nil { - return 0, errors.New(err, f.File, t.Position) + return 0, errors.New(errors.InvalidNumber, f.File, t.Position) } - return number, nil + return int(number), nil case token.Rune: r := t.Bytes(f.File.Bytes) diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index ab256cb..3ed541a 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -388,6 +388,21 @@ func TestLeadingZero(t *testing.T) { } } +func TestRange(t *testing.T) { + tokens := token.Tokenize([]byte("a..b")) + + expected := []token.Kind{ + token.Identifier, + token.Range, + token.Identifier, + token.EOF, + } + + for i, kind := range expected { + assert.Equal(t, tokens[i].Kind, kind) + } +} + func TestSeparator(t *testing.T) { tokens := token.Tokenize([]byte("a,b,c")) diff --git a/src/types/Array.go b/src/types/Array.go index fd6add2..b6629f0 100644 --- a/src/types/Array.go +++ b/src/types/Array.go @@ -1,7 +1,5 @@ package types -var String = &Array{Of: Int8} - // Array is the address of an object. type Array struct { Of Type diff --git a/src/types/Common.go b/src/types/Common.go index a846b61..87d5889 100644 --- a/src/types/Common.go +++ b/src/types/Common.go @@ -10,6 +10,7 @@ var ( Int8 = &Base{name: "int8", size: 1} Float64 = &Base{name: "float64", size: 8} Float32 = &Base{name: "float32", size: 4} + String = &Array{Of: Int8} ) var ( From 46ceb738edc1e6fc5ffde71c1eb8300ebc88d30f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 20 Feb 2025 23:54:13 +0100 Subject: [PATCH 0803/1012] Improved documentation --- docs/readme.md | 2 +- src/asmc/Finalize.go | 13 ++++++------ tests/readme.md | 48 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 8716f7e..842942e 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -33,7 +33,7 @@ You can take a look at the [examples](../examples). go run gotest.tools/gotestsum@latest ``` -This will run over 350 tests in various categories. +This will run over 350 [tests](../tests) in various categories. ## Platforms diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index 71bb996..377b777 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -15,12 +15,13 @@ func Finalize(a asm.Assembler, dlls dll.List) ([]byte, []byte) { } c := compiler{ - code: make([]byte, 0, len(a.Instructions)*8), - codeLabels: make(map[string]Address, 32), - codeStart: codeOffset(), - data: data, - dataLabels: dataLabels, - dlls: dlls, + code: make([]byte, 0, len(a.Instructions)*8), + codeLabels: make(map[string]Address, 32), + codePointers: make([]*pointer, 0, len(a.Instructions)*8), + codeStart: codeOffset(), + data: data, + dataLabels: dataLabels, + dlls: dlls, } for _, x := range a.Instructions { diff --git a/tests/readme.md b/tests/readme.md index 07bcf2e..21419f9 100644 --- a/tests/readme.md +++ b/tests/readme.md @@ -1,11 +1,57 @@ ## Tests +Basic test run: + ```shell -go test ./... -v -cover +go test ./... -v +``` + +Prettier output using `gotestsum`: + +```shell +go run gotest.tools/gotestsum@latest +``` + +## Coverage + +Generate a coverage profile: + +```shell +go test -coverpkg=./... -coverprofile=cover.out ./... +``` + +View the data: + +```shell +go tool cover -func cover.out +go tool cover -html cover.out ``` ## Benchmarks +Run all benchmarks: + ```shell go test ./tests -run='^$' -bench=. -benchmem +``` + +Examples only: + +```shell +go test ./tests -run='^$' -bench=Examples/ -benchmem +``` + +## Profiling + +Generate a profile: + +```shell +go test ./tests -run='^$' -bench=Examples/ -benchmem -cpuprofile cpu.out -memprofile mem.out +``` + +View the data: + +```shell +go tool pprof --nodefraction=0.1 -http=:8080 ./cpu.out +go tool pprof --nodefraction=0.1 -http=:8080 ./mem.out ``` \ No newline at end of file From 8b932fb332fa533ce3b1f0ad71ce1705bb7b06af Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 20 Feb 2025 23:54:13 +0100 Subject: [PATCH 0804/1012] Improved documentation --- docs/readme.md | 2 +- src/asmc/Finalize.go | 13 ++++++------ tests/readme.md | 48 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 8716f7e..842942e 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -33,7 +33,7 @@ You can take a look at the [examples](../examples). go run gotest.tools/gotestsum@latest ``` -This will run over 350 tests in various categories. +This will run over 350 [tests](../tests) in various categories. ## Platforms diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index 71bb996..377b777 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -15,12 +15,13 @@ func Finalize(a asm.Assembler, dlls dll.List) ([]byte, []byte) { } c := compiler{ - code: make([]byte, 0, len(a.Instructions)*8), - codeLabels: make(map[string]Address, 32), - codeStart: codeOffset(), - data: data, - dataLabels: dataLabels, - dlls: dlls, + code: make([]byte, 0, len(a.Instructions)*8), + codeLabels: make(map[string]Address, 32), + codePointers: make([]*pointer, 0, len(a.Instructions)*8), + codeStart: codeOffset(), + data: data, + dataLabels: dataLabels, + dlls: dlls, } for _, x := range a.Instructions { diff --git a/tests/readme.md b/tests/readme.md index 07bcf2e..21419f9 100644 --- a/tests/readme.md +++ b/tests/readme.md @@ -1,11 +1,57 @@ ## Tests +Basic test run: + ```shell -go test ./... -v -cover +go test ./... -v +``` + +Prettier output using `gotestsum`: + +```shell +go run gotest.tools/gotestsum@latest +``` + +## Coverage + +Generate a coverage profile: + +```shell +go test -coverpkg=./... -coverprofile=cover.out ./... +``` + +View the data: + +```shell +go tool cover -func cover.out +go tool cover -html cover.out ``` ## Benchmarks +Run all benchmarks: + ```shell go test ./tests -run='^$' -bench=. -benchmem +``` + +Examples only: + +```shell +go test ./tests -run='^$' -bench=Examples/ -benchmem +``` + +## Profiling + +Generate a profile: + +```shell +go test ./tests -run='^$' -bench=Examples/ -benchmem -cpuprofile cpu.out -memprofile mem.out +``` + +View the data: + +```shell +go tool pprof --nodefraction=0.1 -http=:8080 ./cpu.out +go tool pprof --nodefraction=0.1 -http=:8080 ./mem.out ``` \ No newline at end of file From 8f3fa494ba46d5828165d6557d952f67593f5cf5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Feb 2025 11:27:45 +0100 Subject: [PATCH 0805/1012] Added byte type --- examples/winapi/winapi.q | 2 +- lib/io/io.q | 10 +++++----- lib/log/number.q | 2 +- lib/mem/alloc_linux.q | 2 +- lib/mem/alloc_mac.q | 2 +- lib/mem/alloc_windows.q | 2 +- src/types/ByName.go | 6 ++++-- src/types/Common.go | 3 ++- 8 files changed, 16 insertions(+), 13 deletions(-) diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q index be63231..213f1ed 100644 --- a/examples/winapi/winapi.q +++ b/examples/winapi/winapi.q @@ -1,5 +1,5 @@ extern user32 { - MessageBoxA(window *any, text *int8, title *int8, flags uint) -> int + MessageBoxA(window *any, text *byte, title *byte, flags uint) -> int } main() { diff --git a/lib/io/io.q b/lib/io/io.q index c741271..5c66a29 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -6,22 +6,22 @@ const std { err 2 } -in(buffer []int8) -> int { +in(buffer []byte) -> int { return sys.read(std.in, buffer, len(buffer)) } -out(buffer []int8) -> int { +out(buffer []byte) -> int { return sys.write(std.out, buffer, len(buffer)) } -error(buffer []int8) -> int { +error(buffer []byte) -> int { return sys.write(std.err, buffer, len(buffer)) } -read(fd int, buffer []int8) -> int { +read(fd int, buffer []byte) -> int { return sys.read(fd, buffer, len(buffer)) } -write(fd int, buffer []int8) -> int { +write(fd int, buffer []byte) -> int { return sys.write(fd, buffer, len(buffer)) } \ No newline at end of file diff --git a/lib/log/number.q b/lib/log/number.q index c292167..4945f93 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -8,7 +8,7 @@ number(x int) { mem.free(buffer) } -itoa(x int, buffer []int8) -> (*any, int) { +itoa(x int, buffer []byte) -> (*any, int) { end := buffer + len(buffer) tmp := end digit := 0 diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_linux.q index f7178a2..061693a 100644 --- a/lib/mem/alloc_linux.q +++ b/lib/mem/alloc_linux.q @@ -1,6 +1,6 @@ import sys -alloc(length int) -> []int8 { +alloc(length int) -> []byte { x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) if x < 0x1000 { diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q index fe0f500..a559240 100644 --- a/lib/mem/alloc_mac.q +++ b/lib/mem/alloc_mac.q @@ -1,6 +1,6 @@ import sys -alloc(length int) -> []int8 { +alloc(length int) -> []byte { x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) if x < 0x1000 { diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index c5b74de..d48fe0a 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,6 +1,6 @@ import sys -alloc(length int) -> []int8 { +alloc(length int) -> []byte { x := sys.mmap(0, length+8, page.readwrite, mem.commit|mem.reserve) if x < 0x1000 { diff --git a/src/types/ByName.go b/src/types/ByName.go index bba1c17..c1e9d96 100644 --- a/src/types/ByName.go +++ b/src/types/ByName.go @@ -49,14 +49,16 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { return UInt16 case "uint8": return UInt8 + case "byte": + return Byte + case "bool": + return Bool case "float": return Float case "float64": return Float64 case "float32": return Float32 - case "bool": - return Bool case "any": return Any } diff --git a/src/types/Common.go b/src/types/Common.go index 87d5889..3d2a870 100644 --- a/src/types/Common.go +++ b/src/types/Common.go @@ -10,11 +10,12 @@ var ( Int8 = &Base{name: "int8", size: 1} Float64 = &Base{name: "float64", size: 8} Float32 = &Base{name: "float32", size: 4} - String = &Array{Of: Int8} + String = &Array{Of: Byte} ) var ( Bool = Int + Byte = UInt8 Int = Int64 Float = Float64 UInt = Int From a42115c0fb139c044997f066907142a561322619 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Feb 2025 11:27:45 +0100 Subject: [PATCH 0806/1012] Added byte type --- examples/winapi/winapi.q | 2 +- lib/io/io.q | 10 +++++----- lib/log/number.q | 2 +- lib/mem/alloc_linux.q | 2 +- lib/mem/alloc_mac.q | 2 +- lib/mem/alloc_windows.q | 2 +- src/types/ByName.go | 6 ++++-- src/types/Common.go | 3 ++- 8 files changed, 16 insertions(+), 13 deletions(-) diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q index be63231..213f1ed 100644 --- a/examples/winapi/winapi.q +++ b/examples/winapi/winapi.q @@ -1,5 +1,5 @@ extern user32 { - MessageBoxA(window *any, text *int8, title *int8, flags uint) -> int + MessageBoxA(window *any, text *byte, title *byte, flags uint) -> int } main() { diff --git a/lib/io/io.q b/lib/io/io.q index c741271..5c66a29 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -6,22 +6,22 @@ const std { err 2 } -in(buffer []int8) -> int { +in(buffer []byte) -> int { return sys.read(std.in, buffer, len(buffer)) } -out(buffer []int8) -> int { +out(buffer []byte) -> int { return sys.write(std.out, buffer, len(buffer)) } -error(buffer []int8) -> int { +error(buffer []byte) -> int { return sys.write(std.err, buffer, len(buffer)) } -read(fd int, buffer []int8) -> int { +read(fd int, buffer []byte) -> int { return sys.read(fd, buffer, len(buffer)) } -write(fd int, buffer []int8) -> int { +write(fd int, buffer []byte) -> int { return sys.write(fd, buffer, len(buffer)) } \ No newline at end of file diff --git a/lib/log/number.q b/lib/log/number.q index c292167..4945f93 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -8,7 +8,7 @@ number(x int) { mem.free(buffer) } -itoa(x int, buffer []int8) -> (*any, int) { +itoa(x int, buffer []byte) -> (*any, int) { end := buffer + len(buffer) tmp := end digit := 0 diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_linux.q index f7178a2..061693a 100644 --- a/lib/mem/alloc_linux.q +++ b/lib/mem/alloc_linux.q @@ -1,6 +1,6 @@ import sys -alloc(length int) -> []int8 { +alloc(length int) -> []byte { x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) if x < 0x1000 { diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q index fe0f500..a559240 100644 --- a/lib/mem/alloc_mac.q +++ b/lib/mem/alloc_mac.q @@ -1,6 +1,6 @@ import sys -alloc(length int) -> []int8 { +alloc(length int) -> []byte { x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) if x < 0x1000 { diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index c5b74de..d48fe0a 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,6 +1,6 @@ import sys -alloc(length int) -> []int8 { +alloc(length int) -> []byte { x := sys.mmap(0, length+8, page.readwrite, mem.commit|mem.reserve) if x < 0x1000 { diff --git a/src/types/ByName.go b/src/types/ByName.go index bba1c17..c1e9d96 100644 --- a/src/types/ByName.go +++ b/src/types/ByName.go @@ -49,14 +49,16 @@ func ByName(name string, pkg string, structs map[string]*Struct) Type { return UInt16 case "uint8": return UInt8 + case "byte": + return Byte + case "bool": + return Bool case "float": return Float case "float64": return Float64 case "float32": return Float32 - case "bool": - return Bool case "any": return Any } diff --git a/src/types/Common.go b/src/types/Common.go index 87d5889..3d2a870 100644 --- a/src/types/Common.go +++ b/src/types/Common.go @@ -10,11 +10,12 @@ var ( Int8 = &Base{name: "int8", size: 1} Float64 = &Base{name: "float64", size: 8} Float32 = &Base{name: "float32", size: 4} - String = &Array{Of: Int8} + String = &Array{Of: Byte} ) var ( Bool = Int + Byte = UInt8 Int = Int64 Float = Float64 UInt = Int From 4dbfa80939b41b656d687ff04dbf33ba5257848d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Feb 2025 11:45:32 +0100 Subject: [PATCH 0807/1012] Added more tests --- examples/shell/const.q | 7 ++++++ examples/shell/shell.q | 8 ------- src/ast/ast_test.go | 47 ++++++++++++++++++++++++++------------ src/ast/eachInstruction.go | 10 ++++---- 4 files changed, 44 insertions(+), 28 deletions(-) create mode 100644 examples/shell/const.q diff --git a/examples/shell/const.q b/examples/shell/const.q new file mode 100644 index 0000000..4f7eed7 --- /dev/null +++ b/examples/shell/const.q @@ -0,0 +1,7 @@ +const idtype { + pid 1 +} + +const state { + exited 0x4 +} \ No newline at end of file diff --git a/examples/shell/shell.q b/examples/shell/shell.q index f484486..f5680d6 100644 --- a/examples/shell/shell.q +++ b/examples/shell/shell.q @@ -2,14 +2,6 @@ import io import mem import sys -const idtype { - pid 1 -} - -const state { - exited 0x4 -} - main() { length := 256 command := mem.alloc(length) diff --git a/src/ast/ast_test.go b/src/ast/ast_test.go index c7307a4..621ab6e 100644 --- a/src/ast/ast_test.go +++ b/src/ast/ast_test.go @@ -20,23 +20,15 @@ func TestAssign(t *testing.T) { assert.NotNil(t, assignment.Expression) } -func TestReturn(t *testing.T) { - tree, err := parse("return") +func TestGroups(t *testing.T) { + tree, err := parse("f(\nx\n)\ng(\nx\n)") assert.Nil(t, err) - assert.Equal(t, len(tree), 1) + assert.Equal(t, len(tree), 2) - ret := tree[0].(*ast.Return) - assert.Nil(t, ret.Values) -} - -func TestReturnValues(t *testing.T) { - tree, err := parse("return 42") - assert.Nil(t, err) - assert.Equal(t, len(tree), 1) - - ret := tree[0].(*ast.Return) - assert.Equal(t, len(ret.Values), 1) - assert.Equal(t, ret.Values[0].Token.Kind, token.Number) + f := tree[0].(*ast.Call) + assert.NotNil(t, f.Expression) + g := tree[1].(*ast.Call) + assert.NotNil(t, g.Expression) } func TestIfElse(t *testing.T) { @@ -56,6 +48,31 @@ func TestLoop(t *testing.T) { assert.Equal(t, len(tree), 1) } +func TestNewLine(t *testing.T) { + tree, err := parse("\n\n\n") + assert.Nil(t, err) + assert.Equal(t, len(tree), 0) +} + +func TestReturn(t *testing.T) { + tree, err := parse("return") + assert.Nil(t, err) + assert.Equal(t, len(tree), 1) + + ret := tree[0].(*ast.Return) + assert.Nil(t, ret.Values) +} + +func TestReturnValues(t *testing.T) { + tree, err := parse("return 42") + assert.Nil(t, err) + assert.Equal(t, len(tree), 1) + + ret := tree[0].(*ast.Return) + assert.Equal(t, len(ret.Values), 1) + assert.Equal(t, ret.Values[0].Token.Kind, token.Number) +} + func parse(code string) (ast.AST, error) { src := []byte(code) tokens := token.Tokenize(src) diff --git a/src/ast/eachInstruction.go b/src/ast/eachInstruction.go index e4df0e2..b593ebc 100644 --- a/src/ast/eachInstruction.go +++ b/src/ast/eachInstruction.go @@ -9,13 +9,13 @@ func eachInstruction(tokens token.List, call func(token.List) error) error { blockLevel := 0 for i, t := range tokens { - if start == i && t.Kind == token.NewLine { - start = i + 1 - continue - } - switch t.Kind { case token.NewLine: + if start == i { + start = i + 1 + continue + } + if groupLevel > 0 || blockLevel > 0 { continue } From 04ece57b279274006f49caf87a44adf787144bb2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Feb 2025 11:45:32 +0100 Subject: [PATCH 0808/1012] Added more tests --- examples/shell/const.q | 7 ++++++ examples/shell/shell.q | 8 ------- src/ast/ast_test.go | 47 ++++++++++++++++++++++++++------------ src/ast/eachInstruction.go | 10 ++++---- 4 files changed, 44 insertions(+), 28 deletions(-) create mode 100644 examples/shell/const.q diff --git a/examples/shell/const.q b/examples/shell/const.q new file mode 100644 index 0000000..4f7eed7 --- /dev/null +++ b/examples/shell/const.q @@ -0,0 +1,7 @@ +const idtype { + pid 1 +} + +const state { + exited 0x4 +} \ No newline at end of file diff --git a/examples/shell/shell.q b/examples/shell/shell.q index f484486..f5680d6 100644 --- a/examples/shell/shell.q +++ b/examples/shell/shell.q @@ -2,14 +2,6 @@ import io import mem import sys -const idtype { - pid 1 -} - -const state { - exited 0x4 -} - main() { length := 256 command := mem.alloc(length) diff --git a/src/ast/ast_test.go b/src/ast/ast_test.go index c7307a4..621ab6e 100644 --- a/src/ast/ast_test.go +++ b/src/ast/ast_test.go @@ -20,23 +20,15 @@ func TestAssign(t *testing.T) { assert.NotNil(t, assignment.Expression) } -func TestReturn(t *testing.T) { - tree, err := parse("return") +func TestGroups(t *testing.T) { + tree, err := parse("f(\nx\n)\ng(\nx\n)") assert.Nil(t, err) - assert.Equal(t, len(tree), 1) + assert.Equal(t, len(tree), 2) - ret := tree[0].(*ast.Return) - assert.Nil(t, ret.Values) -} - -func TestReturnValues(t *testing.T) { - tree, err := parse("return 42") - assert.Nil(t, err) - assert.Equal(t, len(tree), 1) - - ret := tree[0].(*ast.Return) - assert.Equal(t, len(ret.Values), 1) - assert.Equal(t, ret.Values[0].Token.Kind, token.Number) + f := tree[0].(*ast.Call) + assert.NotNil(t, f.Expression) + g := tree[1].(*ast.Call) + assert.NotNil(t, g.Expression) } func TestIfElse(t *testing.T) { @@ -56,6 +48,31 @@ func TestLoop(t *testing.T) { assert.Equal(t, len(tree), 1) } +func TestNewLine(t *testing.T) { + tree, err := parse("\n\n\n") + assert.Nil(t, err) + assert.Equal(t, len(tree), 0) +} + +func TestReturn(t *testing.T) { + tree, err := parse("return") + assert.Nil(t, err) + assert.Equal(t, len(tree), 1) + + ret := tree[0].(*ast.Return) + assert.Nil(t, ret.Values) +} + +func TestReturnValues(t *testing.T) { + tree, err := parse("return 42") + assert.Nil(t, err) + assert.Equal(t, len(tree), 1) + + ret := tree[0].(*ast.Return) + assert.Equal(t, len(ret.Values), 1) + assert.Equal(t, ret.Values[0].Token.Kind, token.Number) +} + func parse(code string) (ast.AST, error) { src := []byte(code) tokens := token.Tokenize(src) diff --git a/src/ast/eachInstruction.go b/src/ast/eachInstruction.go index e4df0e2..b593ebc 100644 --- a/src/ast/eachInstruction.go +++ b/src/ast/eachInstruction.go @@ -9,13 +9,13 @@ func eachInstruction(tokens token.List, call func(token.List) error) error { blockLevel := 0 for i, t := range tokens { - if start == i && t.Kind == token.NewLine { - start = i + 1 - continue - } - switch t.Kind { case token.NewLine: + if start == i { + start = i + 1 + continue + } + if groupLevel > 0 || blockLevel > 0 { continue } From d5bcf340e9091b07b312cd17c46890117e1c28da Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Feb 2025 13:47:56 +0100 Subject: [PATCH 0809/1012] Fixed incorrect const values --- examples/array/array.q | 4 ++-- lib/log/number.q | 2 +- lib/mem/alloc_mac.q | 22 ---------------------- lib/mem/{alloc_linux.q => alloc_unix.q} | 10 ---------- lib/mem/alloc_windows.q | 15 ++++----------- lib/mem/const_linux.q | 9 +++++++++ lib/mem/const_mac.q | 9 +++++++++ lib/mem/const_windows.q | 9 +++++++++ lib/mem/{free.q => free_unix.q} | 0 lib/mem/free_windows.q | 7 +++++++ lib/sys/sys_linux.q | 12 ++++++------ lib/sys/sys_mac.q | 4 ++-- lib/sys/sys_windows.q | 22 ++++------------------ lib/thread/thread_linux.q | 2 +- src/core/Constant.go | 2 +- src/core/ExpressionToRegister.go | 8 +++++--- src/core/Fold.go | 2 +- src/core/PeriodToRegister.go | 10 +++++++++- src/core/ToNumber.go | 18 ++++++++++++------ src/scanner/queueDirectory.go | 4 ++++ src/scanner/scanConst.go | 8 ++++---- src/scanner/scanExtern.go | 6 +++--- 22 files changed, 93 insertions(+), 92 deletions(-) delete mode 100644 lib/mem/alloc_mac.q rename lib/mem/{alloc_linux.q => alloc_unix.q} (68%) create mode 100644 lib/mem/const_linux.q create mode 100644 lib/mem/const_mac.q create mode 100644 lib/mem/const_windows.q rename lib/mem/{free.q => free_unix.q} (100%) create mode 100644 lib/mem/free_windows.q diff --git a/examples/array/array.q b/examples/array/array.q index 4f17ef0..0000ad3 100644 --- a/examples/array/array.q +++ b/examples/array/array.q @@ -1,5 +1,5 @@ +import io import mem -import sys main() { length := 5 @@ -9,6 +9,6 @@ main() { address[2] = 'l' address[3] = 'l' address[4] = 'o' - sys.write(1, address, length) + io.write(1, address) mem.free(address) } \ No newline at end of file diff --git a/lib/log/number.q b/lib/log/number.q index 4945f93..230571c 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -8,7 +8,7 @@ number(x int) { mem.free(buffer) } -itoa(x int, buffer []byte) -> (*any, int) { +itoa(x int, buffer []byte) -> (*byte, int) { end := buffer + len(buffer) tmp := end digit := 0 diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q deleted file mode 100644 index a559240..0000000 --- a/lib/mem/alloc_mac.q +++ /dev/null @@ -1,22 +0,0 @@ -import sys - -alloc(length int) -> []byte { - x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) - - if x < 0x1000 { - return x - } - - store(x, 8, length) - return x + 8 -} - -const prot { - read 0x1 - write 0x2 -} - -const map { - private 0x02 - anonymous 0x1000 -} \ No newline at end of file diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_unix.q similarity index 68% rename from lib/mem/alloc_linux.q rename to lib/mem/alloc_unix.q index 061693a..e12a331 100644 --- a/lib/mem/alloc_linux.q +++ b/lib/mem/alloc_unix.q @@ -9,14 +9,4 @@ alloc(length int) -> []byte { store(x, 8, length) return x + 8 -} - -const prot { - read 0x1 - write 0x2 -} - -const map { - private 0x02 - anonymous 0x20 } \ No newline at end of file diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index d48fe0a..3eda616 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,7 +1,9 @@ -import sys +extern kernel32 { + VirtualAlloc(address int, size uint, flags uint32, protection uint32) -> *any +} alloc(length int) -> []byte { - x := sys.mmap(0, length+8, page.readwrite, mem.commit|mem.reserve) + x := kernel32.VirtualAlloc(0, length+8, mem.commit|mem.reserve, page.readwrite) if x < 0x1000 { return x @@ -9,13 +11,4 @@ alloc(length int) -> []byte { store(x, 8, length) return x + 8 -} - -const page { - readwrite 0x0004 -} - -const mem { - commit 0x1000 - reserve 0x2000 } \ No newline at end of file diff --git a/lib/mem/const_linux.q b/lib/mem/const_linux.q new file mode 100644 index 0000000..9193a01 --- /dev/null +++ b/lib/mem/const_linux.q @@ -0,0 +1,9 @@ +const prot { + read 0x1 + write 0x2 +} + +const map { + private 0x02 + anonymous 0x20 +} \ No newline at end of file diff --git a/lib/mem/const_mac.q b/lib/mem/const_mac.q new file mode 100644 index 0000000..7aac429 --- /dev/null +++ b/lib/mem/const_mac.q @@ -0,0 +1,9 @@ +const prot { + read 0x1 + write 0x2 +} + +const map { + private 0x02 + anonymous 0x1000 +} \ No newline at end of file diff --git a/lib/mem/const_windows.q b/lib/mem/const_windows.q new file mode 100644 index 0000000..7008e67 --- /dev/null +++ b/lib/mem/const_windows.q @@ -0,0 +1,9 @@ +const page { + readwrite 0x0004 +} + +const mem { + commit 0x1000 + reserve 0x2000 + decommit 0x4000 +} \ No newline at end of file diff --git a/lib/mem/free.q b/lib/mem/free_unix.q similarity index 100% rename from lib/mem/free.q rename to lib/mem/free_unix.q diff --git a/lib/mem/free_windows.q b/lib/mem/free_windows.q new file mode 100644 index 0000000..1bb258a --- /dev/null +++ b/lib/mem/free_windows.q @@ -0,0 +1,7 @@ +extern kernel32 { + VirtualFree(address *any, size uint, type uint32) -> bool +} + +free(address []any) -> int { + return kernel32.VirtualFree(address-8, len(address)+8, mem.decommit) +} \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q index f35439b..4d82732 100644 --- a/lib/sys/sys_linux.q +++ b/lib/sys/sys_linux.q @@ -1,8 +1,8 @@ -read(fd int, buffer *any, length int) -> int { +read(fd int, buffer *byte, length int) -> int { return syscall(0, fd, buffer, length) } -write(fd int, buffer *any, length int) -> int { +write(fd int, buffer *byte, length int) -> int { return syscall(1, fd, buffer, length) } @@ -14,16 +14,16 @@ close(fd int) -> int { return syscall(3, fd) } -mmap(address int, length int, protection int, flags int) -> *any { +mmap(address int, length uint, protection int, flags int) -> *any { return syscall(9, address, length, protection, flags) } -munmap(address *any, length int) -> int { +munmap(address *any, length uint) -> int { return syscall(11, address, length) } -clone(flags int, stack *any) -> int { - return syscall(56, flags, stack) +clone(flags uint, stack *any, parent *int, child *int, tls uint) -> int { + return syscall(56, flags, stack, parent, child, tls) } fork() -> int { diff --git a/lib/sys/sys_mac.q b/lib/sys/sys_mac.q index 6c31517..aefa23e 100644 --- a/lib/sys/sys_mac.q +++ b/lib/sys/sys_mac.q @@ -1,8 +1,8 @@ -read(fd int, buffer *any, length int) -> int { +read(fd int, buffer *byte, length int) -> int { return syscall(0x2000003, fd, buffer, length) } -write(fd int, buffer *any, length int) -> int { +write(fd int, buffer *byte, length int) -> int { return syscall(0x2000004, fd, buffer, length) } diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q index 1d67740..2523338 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/sys_windows.q @@ -1,31 +1,17 @@ -read(fd int64, buffer *any, length int64) -> int64 { +read(fd int64, buffer *byte, length int64) -> int64 { fd = kernel32.GetStdHandle(-10 - fd) kernel32.ReadConsole(fd, buffer, length, 0) return length } -write(fd int64, buffer *any, length int64) -> int64 { +write(fd int64, buffer *byte, length int64) -> int64 { fd = kernel32.GetStdHandle(-10 - fd) kernel32.WriteConsoleA(fd, buffer, length, 0) return length } -mmap(address int, length int, protection int, flags int) -> *any { - return kernel32.VirtualAlloc(address, length, flags, protection) -} - -munmap(address *any, length int) -> int { - return kernel32.VirtualFree(address, length, mem.decommit) -} - -const mem { - decommit 0x4000 -} - extern kernel32 { GetStdHandle(handle int64) -> int64 - ReadConsole(fd int64, buffer *any, length uint32, written *uint32) -> bool - VirtualAlloc(address int, length int, flags int, protection int) - VirtualFree(address *any, length int, type int) -> bool - WriteConsoleA(fd int64, buffer *any, length uint32, written *uint32) -> bool + ReadConsole(fd int64, buffer *byte, length uint32, written *uint32) -> bool + WriteConsoleA(fd int64, buffer *byte, length uint32, written *uint32) -> bool } \ No newline at end of file diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index e57fa17..cf4d75a 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -19,5 +19,5 @@ create(func *any) -> int { store(stack, 8, core.exit) stack -= 8 store(stack, 8, func) - return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack) + return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0) } \ No newline at end of file diff --git a/src/core/Constant.go b/src/core/Constant.go index 1156e05..5fe78cf 100644 --- a/src/core/Constant.go +++ b/src/core/Constant.go @@ -8,6 +8,6 @@ import ( // Constant registers a single value to be accessible under a descriptive name. type Constant struct { Name string - Value token.Token + Token token.Token File *fs.File } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 056ebe1..785cb91 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -64,14 +64,16 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp f.FreeRegister(register) } - _, isArray := typ.(*types.Array) + arrayType, isArray := typ.(*types.Array) if isArray { - typ = types.AnyPointer + typ = &types.Pointer{To: arrayType.Of} } else if right.Token.Kind == token.Identifier { rightVariable := f.VariableByName(right.Token.Text(f.File.Bytes)) + leftPointer, leftIsPointer := typ.(*types.Pointer) + rightPointer, rightIsPointer := rightVariable.Type.(*types.Pointer) - if typ == types.AnyPointer && rightVariable.Type == types.AnyPointer { + if leftIsPointer && rightIsPointer && leftPointer.To == rightPointer.To { typ = types.Int } } diff --git a/src/core/Fold.go b/src/core/Fold.go index eaaf0cd..0f610e8 100644 --- a/src/core/Fold.go +++ b/src/core/Fold.go @@ -38,7 +38,7 @@ func (f *Function) Fold(expr *expression.Expression) error { return nil } - value, err := f.ToNumber(constant.Value) + value, err := ToNumber(constant.Token, constant.File) expr.Value = value expr.IsFolded = true return err diff --git a/src/core/PeriodToRegister.go b/src/core/PeriodToRegister.go index d915f85..fd2123b 100644 --- a/src/core/PeriodToRegister.go +++ b/src/core/PeriodToRegister.go @@ -36,7 +36,15 @@ func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Re constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] if isConst { - return f.TokenToRegister(constant.Value, register) + number, err := ToNumber(constant.Token, constant.File) + + if err != nil { + return nil, err + } + + f.SaveRegister(register) + f.RegisterNumber(asm.MOVE, register, number) + return types.Int, nil } uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) diff --git a/src/core/ToNumber.go b/src/core/ToNumber.go index 1978939..122b04d 100644 --- a/src/core/ToNumber.go +++ b/src/core/ToNumber.go @@ -6,15 +6,21 @@ import ( "unicode/utf8" "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/token" ) // ToNumber tries to convert the token into a numeric value. func (f *Function) ToNumber(t token.Token) (int, error) { + return ToNumber(t, f.File) +} + +// ToNumber tries to convert the token into a numeric value. +func ToNumber(t token.Token, file *fs.File) (int, error) { switch t.Kind { case token.Number: var ( - digits = t.Text(f.File.Bytes) + digits = t.Text(file.Bytes) number int64 err error ) @@ -31,27 +37,27 @@ func (f *Function) ToNumber(t token.Token) (int, error) { } if err != nil { - return 0, errors.New(errors.InvalidNumber, f.File, t.Position) + return 0, errors.New(errors.InvalidNumber, file, t.Position) } return int(number), nil case token.Rune: - r := t.Bytes(f.File.Bytes) + r := t.Bytes(file.Bytes) r = String(r) if len(r) == 0 { - return 0, errors.New(errors.InvalidRune, f.File, t.Position+1) + return 0, errors.New(errors.InvalidRune, file, t.Position+1) } number, size := utf8.DecodeRune(r) if len(r) > size { - return 0, errors.New(errors.InvalidRune, f.File, t.Position+1) + return 0, errors.New(errors.InvalidRune, file, t.Position+1) } return int(number), nil } - return 0, errors.New(errors.InvalidNumber, f.File, t.Position) + return 0, errors.New(errors.InvalidNumber, file, t.Position) } diff --git a/src/scanner/queueDirectory.go b/src/scanner/queueDirectory.go index eebd2d3..27b1906 100644 --- a/src/scanner/queueDirectory.go +++ b/src/scanner/queueDirectory.go @@ -29,6 +29,10 @@ func (s *Scanner) queueDirectory(directory string, pkg string) { return } + if strings.HasSuffix(name, "_unix.q") && config.TargetOS != config.Linux && config.TargetOS != config.Mac { + return + } + if strings.HasSuffix(name, "_windows.q") && config.TargetOS != config.Windows { return } diff --git a/src/scanner/scanConst.go b/src/scanner/scanConst.go index f382813..801dba4 100644 --- a/src/scanner/scanConst.go +++ b/src/scanner/scanConst.go @@ -25,18 +25,18 @@ func (s *Scanner) scanConst(file *fs.File, tokens token.List, i int) (int, error i++ for i < len(tokens) { - if tokens[i].Kind == token.Identifier { + switch tokens[i].Kind { + case token.Identifier: name := tokens[i].Text(file.Bytes) i++ s.constants <- &core.Constant{ Name: file.Package + "." + groupName + "." + name, - Value: tokens[i], + Token: tokens[i], File: file, } - } - if tokens[i].Kind == token.BlockEnd { + case token.BlockEnd: return i, nil } diff --git a/src/scanner/scanExtern.go b/src/scanner/scanExtern.go index 4e4e7e8..5f4e214 100644 --- a/src/scanner/scanExtern.go +++ b/src/scanner/scanExtern.go @@ -24,7 +24,8 @@ func (s *Scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, erro i++ for i < len(tokens) { - if tokens[i].Kind == token.Identifier { + switch tokens[i].Kind { + case token.Identifier: function, j, err := scanFunctionSignature(file, tokens, i, token.NewLine) if err != nil { @@ -35,9 +36,8 @@ func (s *Scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, erro function.Package = dllName function.UniqueName = dllName + "." + function.Name s.functions <- function - } - if tokens[i].Kind == token.BlockEnd { + case token.BlockEnd: return i, nil } From 1ca61190eb5bf75bc8f5aee5b805367bc2d8c995 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Feb 2025 13:47:56 +0100 Subject: [PATCH 0810/1012] Fixed incorrect const values --- examples/array/array.q | 4 ++-- lib/log/number.q | 2 +- lib/mem/alloc_mac.q | 22 ---------------------- lib/mem/{alloc_linux.q => alloc_unix.q} | 10 ---------- lib/mem/alloc_windows.q | 15 ++++----------- lib/mem/const_linux.q | 9 +++++++++ lib/mem/const_mac.q | 9 +++++++++ lib/mem/const_windows.q | 9 +++++++++ lib/mem/{free.q => free_unix.q} | 0 lib/mem/free_windows.q | 7 +++++++ lib/sys/sys_linux.q | 12 ++++++------ lib/sys/sys_mac.q | 4 ++-- lib/sys/sys_windows.q | 22 ++++------------------ lib/thread/thread_linux.q | 2 +- src/core/Constant.go | 2 +- src/core/ExpressionToRegister.go | 8 +++++--- src/core/Fold.go | 2 +- src/core/PeriodToRegister.go | 10 +++++++++- src/core/ToNumber.go | 18 ++++++++++++------ src/scanner/queueDirectory.go | 4 ++++ src/scanner/scanConst.go | 8 ++++---- src/scanner/scanExtern.go | 6 +++--- 22 files changed, 93 insertions(+), 92 deletions(-) delete mode 100644 lib/mem/alloc_mac.q rename lib/mem/{alloc_linux.q => alloc_unix.q} (68%) create mode 100644 lib/mem/const_linux.q create mode 100644 lib/mem/const_mac.q create mode 100644 lib/mem/const_windows.q rename lib/mem/{free.q => free_unix.q} (100%) create mode 100644 lib/mem/free_windows.q diff --git a/examples/array/array.q b/examples/array/array.q index 4f17ef0..0000ad3 100644 --- a/examples/array/array.q +++ b/examples/array/array.q @@ -1,5 +1,5 @@ +import io import mem -import sys main() { length := 5 @@ -9,6 +9,6 @@ main() { address[2] = 'l' address[3] = 'l' address[4] = 'o' - sys.write(1, address, length) + io.write(1, address) mem.free(address) } \ No newline at end of file diff --git a/lib/log/number.q b/lib/log/number.q index 4945f93..230571c 100644 --- a/lib/log/number.q +++ b/lib/log/number.q @@ -8,7 +8,7 @@ number(x int) { mem.free(buffer) } -itoa(x int, buffer []byte) -> (*any, int) { +itoa(x int, buffer []byte) -> (*byte, int) { end := buffer + len(buffer) tmp := end digit := 0 diff --git a/lib/mem/alloc_mac.q b/lib/mem/alloc_mac.q deleted file mode 100644 index a559240..0000000 --- a/lib/mem/alloc_mac.q +++ /dev/null @@ -1,22 +0,0 @@ -import sys - -alloc(length int) -> []byte { - x := sys.mmap(0, length+8, prot.read|prot.write, map.private|map.anonymous) - - if x < 0x1000 { - return x - } - - store(x, 8, length) - return x + 8 -} - -const prot { - read 0x1 - write 0x2 -} - -const map { - private 0x02 - anonymous 0x1000 -} \ No newline at end of file diff --git a/lib/mem/alloc_linux.q b/lib/mem/alloc_unix.q similarity index 68% rename from lib/mem/alloc_linux.q rename to lib/mem/alloc_unix.q index 061693a..e12a331 100644 --- a/lib/mem/alloc_linux.q +++ b/lib/mem/alloc_unix.q @@ -9,14 +9,4 @@ alloc(length int) -> []byte { store(x, 8, length) return x + 8 -} - -const prot { - read 0x1 - write 0x2 -} - -const map { - private 0x02 - anonymous 0x20 } \ No newline at end of file diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index d48fe0a..3eda616 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,7 +1,9 @@ -import sys +extern kernel32 { + VirtualAlloc(address int, size uint, flags uint32, protection uint32) -> *any +} alloc(length int) -> []byte { - x := sys.mmap(0, length+8, page.readwrite, mem.commit|mem.reserve) + x := kernel32.VirtualAlloc(0, length+8, mem.commit|mem.reserve, page.readwrite) if x < 0x1000 { return x @@ -9,13 +11,4 @@ alloc(length int) -> []byte { store(x, 8, length) return x + 8 -} - -const page { - readwrite 0x0004 -} - -const mem { - commit 0x1000 - reserve 0x2000 } \ No newline at end of file diff --git a/lib/mem/const_linux.q b/lib/mem/const_linux.q new file mode 100644 index 0000000..9193a01 --- /dev/null +++ b/lib/mem/const_linux.q @@ -0,0 +1,9 @@ +const prot { + read 0x1 + write 0x2 +} + +const map { + private 0x02 + anonymous 0x20 +} \ No newline at end of file diff --git a/lib/mem/const_mac.q b/lib/mem/const_mac.q new file mode 100644 index 0000000..7aac429 --- /dev/null +++ b/lib/mem/const_mac.q @@ -0,0 +1,9 @@ +const prot { + read 0x1 + write 0x2 +} + +const map { + private 0x02 + anonymous 0x1000 +} \ No newline at end of file diff --git a/lib/mem/const_windows.q b/lib/mem/const_windows.q new file mode 100644 index 0000000..7008e67 --- /dev/null +++ b/lib/mem/const_windows.q @@ -0,0 +1,9 @@ +const page { + readwrite 0x0004 +} + +const mem { + commit 0x1000 + reserve 0x2000 + decommit 0x4000 +} \ No newline at end of file diff --git a/lib/mem/free.q b/lib/mem/free_unix.q similarity index 100% rename from lib/mem/free.q rename to lib/mem/free_unix.q diff --git a/lib/mem/free_windows.q b/lib/mem/free_windows.q new file mode 100644 index 0000000..1bb258a --- /dev/null +++ b/lib/mem/free_windows.q @@ -0,0 +1,7 @@ +extern kernel32 { + VirtualFree(address *any, size uint, type uint32) -> bool +} + +free(address []any) -> int { + return kernel32.VirtualFree(address-8, len(address)+8, mem.decommit) +} \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q index f35439b..4d82732 100644 --- a/lib/sys/sys_linux.q +++ b/lib/sys/sys_linux.q @@ -1,8 +1,8 @@ -read(fd int, buffer *any, length int) -> int { +read(fd int, buffer *byte, length int) -> int { return syscall(0, fd, buffer, length) } -write(fd int, buffer *any, length int) -> int { +write(fd int, buffer *byte, length int) -> int { return syscall(1, fd, buffer, length) } @@ -14,16 +14,16 @@ close(fd int) -> int { return syscall(3, fd) } -mmap(address int, length int, protection int, flags int) -> *any { +mmap(address int, length uint, protection int, flags int) -> *any { return syscall(9, address, length, protection, flags) } -munmap(address *any, length int) -> int { +munmap(address *any, length uint) -> int { return syscall(11, address, length) } -clone(flags int, stack *any) -> int { - return syscall(56, flags, stack) +clone(flags uint, stack *any, parent *int, child *int, tls uint) -> int { + return syscall(56, flags, stack, parent, child, tls) } fork() -> int { diff --git a/lib/sys/sys_mac.q b/lib/sys/sys_mac.q index 6c31517..aefa23e 100644 --- a/lib/sys/sys_mac.q +++ b/lib/sys/sys_mac.q @@ -1,8 +1,8 @@ -read(fd int, buffer *any, length int) -> int { +read(fd int, buffer *byte, length int) -> int { return syscall(0x2000003, fd, buffer, length) } -write(fd int, buffer *any, length int) -> int { +write(fd int, buffer *byte, length int) -> int { return syscall(0x2000004, fd, buffer, length) } diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q index 1d67740..2523338 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/sys_windows.q @@ -1,31 +1,17 @@ -read(fd int64, buffer *any, length int64) -> int64 { +read(fd int64, buffer *byte, length int64) -> int64 { fd = kernel32.GetStdHandle(-10 - fd) kernel32.ReadConsole(fd, buffer, length, 0) return length } -write(fd int64, buffer *any, length int64) -> int64 { +write(fd int64, buffer *byte, length int64) -> int64 { fd = kernel32.GetStdHandle(-10 - fd) kernel32.WriteConsoleA(fd, buffer, length, 0) return length } -mmap(address int, length int, protection int, flags int) -> *any { - return kernel32.VirtualAlloc(address, length, flags, protection) -} - -munmap(address *any, length int) -> int { - return kernel32.VirtualFree(address, length, mem.decommit) -} - -const mem { - decommit 0x4000 -} - extern kernel32 { GetStdHandle(handle int64) -> int64 - ReadConsole(fd int64, buffer *any, length uint32, written *uint32) -> bool - VirtualAlloc(address int, length int, flags int, protection int) - VirtualFree(address *any, length int, type int) -> bool - WriteConsoleA(fd int64, buffer *any, length uint32, written *uint32) -> bool + ReadConsole(fd int64, buffer *byte, length uint32, written *uint32) -> bool + WriteConsoleA(fd int64, buffer *byte, length uint32, written *uint32) -> bool } \ No newline at end of file diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index e57fa17..cf4d75a 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -19,5 +19,5 @@ create(func *any) -> int { store(stack, 8, core.exit) stack -= 8 store(stack, 8, func) - return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack) + return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0) } \ No newline at end of file diff --git a/src/core/Constant.go b/src/core/Constant.go index 1156e05..5fe78cf 100644 --- a/src/core/Constant.go +++ b/src/core/Constant.go @@ -8,6 +8,6 @@ import ( // Constant registers a single value to be accessible under a descriptive name. type Constant struct { Name string - Value token.Token + Token token.Token File *fs.File } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 056ebe1..785cb91 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -64,14 +64,16 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp f.FreeRegister(register) } - _, isArray := typ.(*types.Array) + arrayType, isArray := typ.(*types.Array) if isArray { - typ = types.AnyPointer + typ = &types.Pointer{To: arrayType.Of} } else if right.Token.Kind == token.Identifier { rightVariable := f.VariableByName(right.Token.Text(f.File.Bytes)) + leftPointer, leftIsPointer := typ.(*types.Pointer) + rightPointer, rightIsPointer := rightVariable.Type.(*types.Pointer) - if typ == types.AnyPointer && rightVariable.Type == types.AnyPointer { + if leftIsPointer && rightIsPointer && leftPointer.To == rightPointer.To { typ = types.Int } } diff --git a/src/core/Fold.go b/src/core/Fold.go index eaaf0cd..0f610e8 100644 --- a/src/core/Fold.go +++ b/src/core/Fold.go @@ -38,7 +38,7 @@ func (f *Function) Fold(expr *expression.Expression) error { return nil } - value, err := f.ToNumber(constant.Value) + value, err := ToNumber(constant.Token, constant.File) expr.Value = value expr.IsFolded = true return err diff --git a/src/core/PeriodToRegister.go b/src/core/PeriodToRegister.go index d915f85..fd2123b 100644 --- a/src/core/PeriodToRegister.go +++ b/src/core/PeriodToRegister.go @@ -36,7 +36,15 @@ func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Re constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] if isConst { - return f.TokenToRegister(constant.Value, register) + number, err := ToNumber(constant.Token, constant.File) + + if err != nil { + return nil, err + } + + f.SaveRegister(register) + f.RegisterNumber(asm.MOVE, register, number) + return types.Int, nil } uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) diff --git a/src/core/ToNumber.go b/src/core/ToNumber.go index 1978939..122b04d 100644 --- a/src/core/ToNumber.go +++ b/src/core/ToNumber.go @@ -6,15 +6,21 @@ import ( "unicode/utf8" "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/token" ) // ToNumber tries to convert the token into a numeric value. func (f *Function) ToNumber(t token.Token) (int, error) { + return ToNumber(t, f.File) +} + +// ToNumber tries to convert the token into a numeric value. +func ToNumber(t token.Token, file *fs.File) (int, error) { switch t.Kind { case token.Number: var ( - digits = t.Text(f.File.Bytes) + digits = t.Text(file.Bytes) number int64 err error ) @@ -31,27 +37,27 @@ func (f *Function) ToNumber(t token.Token) (int, error) { } if err != nil { - return 0, errors.New(errors.InvalidNumber, f.File, t.Position) + return 0, errors.New(errors.InvalidNumber, file, t.Position) } return int(number), nil case token.Rune: - r := t.Bytes(f.File.Bytes) + r := t.Bytes(file.Bytes) r = String(r) if len(r) == 0 { - return 0, errors.New(errors.InvalidRune, f.File, t.Position+1) + return 0, errors.New(errors.InvalidRune, file, t.Position+1) } number, size := utf8.DecodeRune(r) if len(r) > size { - return 0, errors.New(errors.InvalidRune, f.File, t.Position+1) + return 0, errors.New(errors.InvalidRune, file, t.Position+1) } return int(number), nil } - return 0, errors.New(errors.InvalidNumber, f.File, t.Position) + return 0, errors.New(errors.InvalidNumber, file, t.Position) } diff --git a/src/scanner/queueDirectory.go b/src/scanner/queueDirectory.go index eebd2d3..27b1906 100644 --- a/src/scanner/queueDirectory.go +++ b/src/scanner/queueDirectory.go @@ -29,6 +29,10 @@ func (s *Scanner) queueDirectory(directory string, pkg string) { return } + if strings.HasSuffix(name, "_unix.q") && config.TargetOS != config.Linux && config.TargetOS != config.Mac { + return + } + if strings.HasSuffix(name, "_windows.q") && config.TargetOS != config.Windows { return } diff --git a/src/scanner/scanConst.go b/src/scanner/scanConst.go index f382813..801dba4 100644 --- a/src/scanner/scanConst.go +++ b/src/scanner/scanConst.go @@ -25,18 +25,18 @@ func (s *Scanner) scanConst(file *fs.File, tokens token.List, i int) (int, error i++ for i < len(tokens) { - if tokens[i].Kind == token.Identifier { + switch tokens[i].Kind { + case token.Identifier: name := tokens[i].Text(file.Bytes) i++ s.constants <- &core.Constant{ Name: file.Package + "." + groupName + "." + name, - Value: tokens[i], + Token: tokens[i], File: file, } - } - if tokens[i].Kind == token.BlockEnd { + case token.BlockEnd: return i, nil } diff --git a/src/scanner/scanExtern.go b/src/scanner/scanExtern.go index 4e4e7e8..5f4e214 100644 --- a/src/scanner/scanExtern.go +++ b/src/scanner/scanExtern.go @@ -24,7 +24,8 @@ func (s *Scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, erro i++ for i < len(tokens) { - if tokens[i].Kind == token.Identifier { + switch tokens[i].Kind { + case token.Identifier: function, j, err := scanFunctionSignature(file, tokens, i, token.NewLine) if err != nil { @@ -35,9 +36,8 @@ func (s *Scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, erro function.Package = dllName function.UniqueName = dllName + "." + function.Name s.functions <- function - } - if tokens[i].Kind == token.BlockEnd { + case token.BlockEnd: return i, nil } From a8d15a43054451fe0fb2c0599d9ab2d9cfbab534 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Feb 2025 15:57:03 +0100 Subject: [PATCH 0811/1012] Improved code consistency --- src/x86/Div.go | 4 ++-- src/x86/Pop.go | 4 ++-- src/x86/Push.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/x86/Div.go b/src/x86/Div.go index b6c5ebf..55ff659 100644 --- a/src/x86/Div.go +++ b/src/x86/Div.go @@ -6,9 +6,9 @@ import "git.akyoto.dev/cli/q/src/cpu" func DivRegister(code []byte, divisor cpu.Register) []byte { rex := byte(0x48) - if divisor >= 8 { + if divisor > 0b111 { rex++ - divisor -= 8 + divisor &= 0b111 } return append( diff --git a/src/x86/Pop.go b/src/x86/Pop.go index 5b23719..c2037b4 100644 --- a/src/x86/Pop.go +++ b/src/x86/Pop.go @@ -4,9 +4,9 @@ import "git.akyoto.dev/cli/q/src/cpu" // PopRegister pops a value from the stack and saves it into the register. func PopRegister(code []byte, register cpu.Register) []byte { - if register >= 8 { + if register > 0b111 { code = append(code, REX(0, 0, 0, 1)) - register -= 8 + register &= 0b111 } return append( diff --git a/src/x86/Push.go b/src/x86/Push.go index 4bd6e5a..15396c2 100644 --- a/src/x86/Push.go +++ b/src/x86/Push.go @@ -29,9 +29,9 @@ func PushNumber(code []byte, number int) []byte { // PushRegister pushes the value inside the register onto the stack. func PushRegister(code []byte, register cpu.Register) []byte { - if register >= 8 { + if register > 0b111 { code = append(code, REX(0, 0, 0, 1)) - register -= 8 + register &= 0b111 } return append( From 08436c31c022133dfe13f79f4db8443237288b76 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Feb 2025 15:57:03 +0100 Subject: [PATCH 0812/1012] Improved code consistency --- src/x86/Div.go | 4 ++-- src/x86/Pop.go | 4 ++-- src/x86/Push.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/x86/Div.go b/src/x86/Div.go index b6c5ebf..55ff659 100644 --- a/src/x86/Div.go +++ b/src/x86/Div.go @@ -6,9 +6,9 @@ import "git.akyoto.dev/cli/q/src/cpu" func DivRegister(code []byte, divisor cpu.Register) []byte { rex := byte(0x48) - if divisor >= 8 { + if divisor > 0b111 { rex++ - divisor -= 8 + divisor &= 0b111 } return append( diff --git a/src/x86/Pop.go b/src/x86/Pop.go index 5b23719..c2037b4 100644 --- a/src/x86/Pop.go +++ b/src/x86/Pop.go @@ -4,9 +4,9 @@ import "git.akyoto.dev/cli/q/src/cpu" // PopRegister pops a value from the stack and saves it into the register. func PopRegister(code []byte, register cpu.Register) []byte { - if register >= 8 { + if register > 0b111 { code = append(code, REX(0, 0, 0, 1)) - register -= 8 + register &= 0b111 } return append( diff --git a/src/x86/Push.go b/src/x86/Push.go index 4bd6e5a..15396c2 100644 --- a/src/x86/Push.go +++ b/src/x86/Push.go @@ -29,9 +29,9 @@ func PushNumber(code []byte, number int) []byte { // PushRegister pushes the value inside the register onto the stack. func PushRegister(code []byte, register cpu.Register) []byte { - if register >= 8 { + if register > 0b111 { code = append(code, REX(0, 0, 0, 1)) - register -= 8 + register &= 0b111 } return append( From 126834423847e160a8162a8758cb1e534ac60858 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Feb 2025 17:04:13 +0100 Subject: [PATCH 0813/1012] Improved label consistency --- src/asmc/move.go | 2 +- src/core/AddBytes.go | 6 +----- src/core/ArrayElementToRegister.go | 1 + src/core/CompileAssert.go | 6 ++---- src/core/CompileAssignDivision.go | 2 ++ src/core/CompileCondition.go | 6 ++---- src/core/CompileFor.go | 6 ++---- src/core/CompileIf.go | 8 +++----- src/core/CompileLoop.go | 4 +--- src/core/CompileSwitch.go | 8 +++----- src/core/CreateLabel.go | 18 ++++++++++++++++++ 11 files changed, 36 insertions(+), 31 deletions(-) create mode 100644 src/core/CreateLabel.go diff --git a/src/asmc/move.go b/src/asmc/move.go index 11742b3..b4f76ae 100644 --- a/src/asmc/move.go +++ b/src/asmc/move.go @@ -23,7 +23,7 @@ func (c *compiler) move(x asm.Instruction) { opSize := len(c.code) - size - start regLabel := x.Data.(*asm.RegisterLabel) - if strings.HasPrefix(regLabel.Label, "data_") { + if strings.HasPrefix(regLabel.Label, "data ") { c.dataPointers = append(c.dataPointers, &pointer{ Position: Address(len(c.code) - size), OpSize: uint8(opSize), diff --git a/src/core/AddBytes.go b/src/core/AddBytes.go index bd620e0..a6c3955 100644 --- a/src/core/AddBytes.go +++ b/src/core/AddBytes.go @@ -1,13 +1,9 @@ package core -import ( - "fmt" -) - // AddBytes adds a sequence of bytes and returns its address as a label. func (f *Function) AddBytes(value []byte) string { f.count.data++ - label := fmt.Sprintf("data_%s_%d", f.UniqueName, f.count.data) + label := f.CreateLabel("data", f.count.data) f.Assembler.SetData(label, value) return label } diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go index 66777b9..924f16f 100644 --- a/src/core/ArrayElementToRegister.go +++ b/src/core/ArrayElementToRegister.go @@ -20,6 +20,7 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Children[0].Token.Position) } + defer f.UseVariable(array) index := node.Children[1] memory := asm.Memory{ diff --git a/src/core/CompileAssert.go b/src/core/CompileAssert.go index f8608e0..f3db795 100644 --- a/src/core/CompileAssert.go +++ b/src/core/CompileAssert.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" ) @@ -10,8 +8,8 @@ import ( // CompileAssert compiles an assertion. func (f *Function) CompileAssert(assert *ast.Assert) error { f.count.assert++ - success := fmt.Sprintf("%s_assert_%d_true", f.UniqueName, f.count.assert) - fail := fmt.Sprintf("%s_assert_%d_false", f.UniqueName, f.count.assert) + success := f.CreateLabel("assert true", f.count.assert) + fail := f.CreateLabel("assert false", f.count.assert) err := f.CompileCondition(assert.Condition, success, fail) if err != nil { diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index d9f5f5c..c427b04 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -38,7 +38,9 @@ func (f *Function) CompileAssignDivision(node *ast.Assign) error { divisor := right.Children[1] err = f.Execute(right.Token, dividendRegister, divisor) f.RegisterRegister(asm.MOVE, quotientVariable.Register, x86.RAX) + f.UseVariable(quotientVariable) f.RegisterRegister(asm.MOVE, remainderVariable.Register, x86.RDX) + f.UseVariable(remainderVariable) if isTemporary { f.FreeRegister(dividendRegister) diff --git a/src/core/CompileCondition.go b/src/core/CompileCondition.go index 28b16b9..0a31b5a 100644 --- a/src/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/token" ) @@ -12,7 +10,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab switch condition.Token.Kind { case token.LogicalOr: f.count.subBranch++ - leftFailLabel := fmt.Sprintf("%s_false_%d", f.UniqueName, f.count.subBranch) + leftFailLabel := f.CreateLabel("false", f.count.subBranch) // Left left := condition.Children[0] @@ -39,7 +37,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab case token.LogicalAnd: f.count.subBranch++ - leftSuccessLabel := fmt.Sprintf("%s_true_%d", f.UniqueName, f.count.subBranch) + leftSuccessLabel := f.CreateLabel("true", f.count.subBranch) // Left left := condition.Children[0] diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index 6cfde4c..b9a6d7f 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/cpu" @@ -19,8 +17,8 @@ func (f *Function) CompileFor(loop *ast.For) error { f.count.loop++ var ( - label = fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) - labelEnd = fmt.Sprintf("%s_loop_%d_end", f.UniqueName, f.count.loop) + label = f.CreateLabel("for", f.count.loop) + labelEnd = f.CreateLabel("for end", f.count.loop) counter cpu.Register from *expression.Expression to *expression.Expression diff --git a/src/core/CompileIf.go b/src/core/CompileIf.go index 029d7e1..61fb3c6 100644 --- a/src/core/CompileIf.go +++ b/src/core/CompileIf.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" ) @@ -17,8 +15,8 @@ func (f *Function) CompileIf(branch *ast.If) error { var ( end string - success = fmt.Sprintf("%s_if_%d_true", f.UniqueName, f.count.branch) - fail = fmt.Sprintf("%s_if_%d_false", f.UniqueName, f.count.branch) + success = f.CreateLabel("if true", f.count.branch) + fail = f.CreateLabel("if false", f.count.branch) err = f.CompileCondition(branch.Condition, success, fail) ) @@ -35,7 +33,7 @@ func (f *Function) CompileIf(branch *ast.If) error { } if branch.Else != nil { - end = fmt.Sprintf("%s_if_%d_end", f.UniqueName, f.count.branch) + end = f.CreateLabel("if end", f.count.branch) f.Jump(asm.JUMP, end) } diff --git a/src/core/CompileLoop.go b/src/core/CompileLoop.go index ea9aea7..13ccf90 100644 --- a/src/core/CompileLoop.go +++ b/src/core/CompileLoop.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" ) @@ -14,7 +12,7 @@ func (f *Function) CompileLoop(loop *ast.Loop) error { } f.count.loop++ - label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) + label := f.CreateLabel("loop", f.count.loop) f.AddLabel(label) scope := f.PushScope(loop.Body, f.File.Bytes) scope.InLoop = true diff --git a/src/core/CompileSwitch.go b/src/core/CompileSwitch.go index 7a08153..e62b8ca 100644 --- a/src/core/CompileSwitch.go +++ b/src/core/CompileSwitch.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" ) @@ -10,7 +8,7 @@ import ( // CompileSwitch compiles a multi-branch instruction. func (f *Function) CompileSwitch(s *ast.Switch) error { f.count.multiBranch++ - end := fmt.Sprintf("%s_switch_%d_end", f.UniqueName, f.count.multiBranch) + end := f.CreateLabel("switch end", f.count.multiBranch) for _, branch := range s.Cases { if branch.Condition == nil { @@ -28,8 +26,8 @@ func (f *Function) CompileSwitch(s *ast.Switch) error { f.count.branch++ var ( - success = fmt.Sprintf("%s_case_%d_true", f.UniqueName, f.count.branch) - fail = fmt.Sprintf("%s_case_%d_false", f.UniqueName, f.count.branch) + success = f.CreateLabel("case true", f.count.branch) + fail = f.CreateLabel("case false", f.count.branch) err = f.CompileCondition(branch.Condition, success, fail) ) diff --git a/src/core/CreateLabel.go b/src/core/CreateLabel.go new file mode 100644 index 0000000..8ee8eb4 --- /dev/null +++ b/src/core/CreateLabel.go @@ -0,0 +1,18 @@ +package core + +import ( + "strconv" + "strings" +) + +// CreateLabel creates a label that is tied to this function by using a suffix. +func (f *Function) CreateLabel(prefix string, count int) string { + tmp := strings.Builder{} + tmp.WriteString(prefix) + tmp.WriteString(" ") + tmp.WriteString(strconv.Itoa(count)) + tmp.WriteString(" [") + tmp.WriteString(f.UniqueName) + tmp.WriteString("]") + return tmp.String() +} From 4caac572106a61319a58601d7c06dc84b07d0038 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Feb 2025 17:04:13 +0100 Subject: [PATCH 0814/1012] Improved label consistency --- src/asmc/move.go | 2 +- src/core/AddBytes.go | 6 +----- src/core/ArrayElementToRegister.go | 1 + src/core/CompileAssert.go | 6 ++---- src/core/CompileAssignDivision.go | 2 ++ src/core/CompileCondition.go | 6 ++---- src/core/CompileFor.go | 6 ++---- src/core/CompileIf.go | 8 +++----- src/core/CompileLoop.go | 4 +--- src/core/CompileSwitch.go | 8 +++----- src/core/CreateLabel.go | 18 ++++++++++++++++++ 11 files changed, 36 insertions(+), 31 deletions(-) create mode 100644 src/core/CreateLabel.go diff --git a/src/asmc/move.go b/src/asmc/move.go index 11742b3..b4f76ae 100644 --- a/src/asmc/move.go +++ b/src/asmc/move.go @@ -23,7 +23,7 @@ func (c *compiler) move(x asm.Instruction) { opSize := len(c.code) - size - start regLabel := x.Data.(*asm.RegisterLabel) - if strings.HasPrefix(regLabel.Label, "data_") { + if strings.HasPrefix(regLabel.Label, "data ") { c.dataPointers = append(c.dataPointers, &pointer{ Position: Address(len(c.code) - size), OpSize: uint8(opSize), diff --git a/src/core/AddBytes.go b/src/core/AddBytes.go index bd620e0..a6c3955 100644 --- a/src/core/AddBytes.go +++ b/src/core/AddBytes.go @@ -1,13 +1,9 @@ package core -import ( - "fmt" -) - // AddBytes adds a sequence of bytes and returns its address as a label. func (f *Function) AddBytes(value []byte) string { f.count.data++ - label := fmt.Sprintf("data_%s_%d", f.UniqueName, f.count.data) + label := f.CreateLabel("data", f.count.data) f.Assembler.SetData(label, value) return label } diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go index 66777b9..924f16f 100644 --- a/src/core/ArrayElementToRegister.go +++ b/src/core/ArrayElementToRegister.go @@ -20,6 +20,7 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Children[0].Token.Position) } + defer f.UseVariable(array) index := node.Children[1] memory := asm.Memory{ diff --git a/src/core/CompileAssert.go b/src/core/CompileAssert.go index f8608e0..f3db795 100644 --- a/src/core/CompileAssert.go +++ b/src/core/CompileAssert.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" ) @@ -10,8 +8,8 @@ import ( // CompileAssert compiles an assertion. func (f *Function) CompileAssert(assert *ast.Assert) error { f.count.assert++ - success := fmt.Sprintf("%s_assert_%d_true", f.UniqueName, f.count.assert) - fail := fmt.Sprintf("%s_assert_%d_false", f.UniqueName, f.count.assert) + success := f.CreateLabel("assert true", f.count.assert) + fail := f.CreateLabel("assert false", f.count.assert) err := f.CompileCondition(assert.Condition, success, fail) if err != nil { diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index d9f5f5c..c427b04 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -38,7 +38,9 @@ func (f *Function) CompileAssignDivision(node *ast.Assign) error { divisor := right.Children[1] err = f.Execute(right.Token, dividendRegister, divisor) f.RegisterRegister(asm.MOVE, quotientVariable.Register, x86.RAX) + f.UseVariable(quotientVariable) f.RegisterRegister(asm.MOVE, remainderVariable.Register, x86.RDX) + f.UseVariable(remainderVariable) if isTemporary { f.FreeRegister(dividendRegister) diff --git a/src/core/CompileCondition.go b/src/core/CompileCondition.go index 28b16b9..0a31b5a 100644 --- a/src/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/expression" "git.akyoto.dev/cli/q/src/token" ) @@ -12,7 +10,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab switch condition.Token.Kind { case token.LogicalOr: f.count.subBranch++ - leftFailLabel := fmt.Sprintf("%s_false_%d", f.UniqueName, f.count.subBranch) + leftFailLabel := f.CreateLabel("false", f.count.subBranch) // Left left := condition.Children[0] @@ -39,7 +37,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab case token.LogicalAnd: f.count.subBranch++ - leftSuccessLabel := fmt.Sprintf("%s_true_%d", f.UniqueName, f.count.subBranch) + leftSuccessLabel := f.CreateLabel("true", f.count.subBranch) // Left left := condition.Children[0] diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index 6cfde4c..b9a6d7f 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/cpu" @@ -19,8 +17,8 @@ func (f *Function) CompileFor(loop *ast.For) error { f.count.loop++ var ( - label = fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) - labelEnd = fmt.Sprintf("%s_loop_%d_end", f.UniqueName, f.count.loop) + label = f.CreateLabel("for", f.count.loop) + labelEnd = f.CreateLabel("for end", f.count.loop) counter cpu.Register from *expression.Expression to *expression.Expression diff --git a/src/core/CompileIf.go b/src/core/CompileIf.go index 029d7e1..61fb3c6 100644 --- a/src/core/CompileIf.go +++ b/src/core/CompileIf.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" ) @@ -17,8 +15,8 @@ func (f *Function) CompileIf(branch *ast.If) error { var ( end string - success = fmt.Sprintf("%s_if_%d_true", f.UniqueName, f.count.branch) - fail = fmt.Sprintf("%s_if_%d_false", f.UniqueName, f.count.branch) + success = f.CreateLabel("if true", f.count.branch) + fail = f.CreateLabel("if false", f.count.branch) err = f.CompileCondition(branch.Condition, success, fail) ) @@ -35,7 +33,7 @@ func (f *Function) CompileIf(branch *ast.If) error { } if branch.Else != nil { - end = fmt.Sprintf("%s_if_%d_end", f.UniqueName, f.count.branch) + end = f.CreateLabel("if end", f.count.branch) f.Jump(asm.JUMP, end) } diff --git a/src/core/CompileLoop.go b/src/core/CompileLoop.go index ea9aea7..13ccf90 100644 --- a/src/core/CompileLoop.go +++ b/src/core/CompileLoop.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" ) @@ -14,7 +12,7 @@ func (f *Function) CompileLoop(loop *ast.Loop) error { } f.count.loop++ - label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop) + label := f.CreateLabel("loop", f.count.loop) f.AddLabel(label) scope := f.PushScope(loop.Body, f.File.Bytes) scope.InLoop = true diff --git a/src/core/CompileSwitch.go b/src/core/CompileSwitch.go index 7a08153..e62b8ca 100644 --- a/src/core/CompileSwitch.go +++ b/src/core/CompileSwitch.go @@ -1,8 +1,6 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" ) @@ -10,7 +8,7 @@ import ( // CompileSwitch compiles a multi-branch instruction. func (f *Function) CompileSwitch(s *ast.Switch) error { f.count.multiBranch++ - end := fmt.Sprintf("%s_switch_%d_end", f.UniqueName, f.count.multiBranch) + end := f.CreateLabel("switch end", f.count.multiBranch) for _, branch := range s.Cases { if branch.Condition == nil { @@ -28,8 +26,8 @@ func (f *Function) CompileSwitch(s *ast.Switch) error { f.count.branch++ var ( - success = fmt.Sprintf("%s_case_%d_true", f.UniqueName, f.count.branch) - fail = fmt.Sprintf("%s_case_%d_false", f.UniqueName, f.count.branch) + success = f.CreateLabel("case true", f.count.branch) + fail = f.CreateLabel("case false", f.count.branch) err = f.CompileCondition(branch.Condition, success, fail) ) diff --git a/src/core/CreateLabel.go b/src/core/CreateLabel.go new file mode 100644 index 0000000..8ee8eb4 --- /dev/null +++ b/src/core/CreateLabel.go @@ -0,0 +1,18 @@ +package core + +import ( + "strconv" + "strings" +) + +// CreateLabel creates a label that is tied to this function by using a suffix. +func (f *Function) CreateLabel(prefix string, count int) string { + tmp := strings.Builder{} + tmp.WriteString(prefix) + tmp.WriteString(" ") + tmp.WriteString(strconv.Itoa(count)) + tmp.WriteString(" [") + tmp.WriteString(f.UniqueName) + tmp.WriteString("]") + return tmp.String() +} From 96ac2989806e4b64b78526c24534d626219c090e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Feb 2025 17:35:42 +0100 Subject: [PATCH 0815/1012] Implemented environment type --- src/asmc/compiler.go | 4 ++-- src/compiler/Compile.go | 41 ++++++++++++++++++------------------ src/compiler/Result.go | 4 ++-- src/core/CompileCall.go | 2 +- src/core/CompileDelete.go | 2 +- src/core/CompileNew.go | 4 ++-- src/core/Constant.go | 2 +- src/core/CreateLabel.go | 4 ++-- src/core/Environment.go | 14 ++++++++++++ src/core/Fold.go | 2 +- src/core/Function.go | 16 ++------------ src/core/Identifier.go | 2 +- src/core/PeriodToRegister.go | 4 ++-- src/core/ResolveTypes.go | 4 ++-- src/core/count.go | 13 ++++++++++++ 15 files changed, 67 insertions(+), 51 deletions(-) create mode 100644 src/core/Environment.go create mode 100644 src/core/count.go diff --git a/src/asmc/compiler.go b/src/asmc/compiler.go index 0847707..fd17c89 100644 --- a/src/asmc/compiler.go +++ b/src/asmc/compiler.go @@ -9,7 +9,7 @@ type compiler struct { dataLabels map[string]Address codePointers []*pointer dataPointers []*pointer - codeStart Address - dlls dll.List dllPointers []*pointer + dlls dll.List + codeStart Address } diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 749b71b..cc56464 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -9,11 +9,14 @@ import ( // Compile waits for the scan to finish and compiles all functions. func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Struct, errs <-chan error) (Result, error) { + all := core.Environment{ + Files: make([]*fs.File, 0, 8), + Functions: make(map[string]*core.Function, 32), + Structs: make(map[string]*types.Struct, 8), + Constants: make(map[string]*core.Constant, 8), + } + result := Result{} - allFiles := make([]*fs.File, 0, 8) - allFunctions := make(map[string]*core.Function, 32) - allStructs := make(map[string]*types.Struct, 8) - allConstants := make(map[string]*core.Constant, 8) for constants != nil || files != nil || functions != nil || structs != nil || errs != nil { select { @@ -23,10 +26,8 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < continue } - function.Functions = allFunctions - function.Structs = allStructs - function.Constants = allConstants - allFunctions[function.UniqueName] = function + function.All = &all + all.Functions[function.UniqueName] = function case structure, ok := <-structs: if !ok { @@ -34,7 +35,7 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < continue } - allStructs[structure.UniqueName] = structure + all.Structs[structure.UniqueName] = structure case file, ok := <-files: if !ok { @@ -42,7 +43,7 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < continue } - allFiles = append(allFiles, file) + all.Files = append(all.Files, file) case constant, ok := <-constants: if !ok { @@ -50,7 +51,7 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < continue } - allConstants[constant.Name] = constant + all.Constants[constant.Name] = constant case err, ok := <-errs: if !ok { @@ -63,12 +64,12 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < } // Calculate size of structs - for _, structure := range allStructs { - structure.Update(allStructs) + for _, structure := range all.Structs { + structure.Update(all.Structs) } // Resolve the types - for _, function := range allFunctions { + for _, function := range all.Functions { err := function.ResolveTypes() if err != nil { @@ -77,10 +78,10 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < } // Start parallel compilation - CompileFunctions(allFunctions) + CompileFunctions(all.Functions) // Report errors if any occurred - for _, function := range allFunctions { + for _, function := range all.Functions { if function.Err != nil { return result, function.Err } @@ -90,7 +91,7 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < } // Check for unused imports in all files - for _, file := range allFiles { + for _, file := range all.Files { for _, pkg := range file.Imports { if !pkg.Used { return result, errors.New(&errors.UnusedImport{Package: pkg.Path}, file, pkg.Position) @@ -99,14 +100,14 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < } // Check for existence of `init` - init, exists := allFunctions["core.init"] + init, exists := all.Functions["core.init"] if !exists { return result, errors.MissingInitFunction } // Check for existence of `main` - main, exists := allFunctions["main.main"] + main, exists := all.Functions["main.main"] if !exists { return result, errors.MissingMainFunction @@ -114,7 +115,7 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < result.Init = init result.Main = main - result.Functions = allFunctions + result.Functions = all.Functions result.finalize() return result, nil } diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 5c96bf3..ce94da8 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -11,9 +11,9 @@ type Result struct { Main *core.Function Functions map[string]*core.Function Traversed map[*core.Function]bool - InstructionCount int - DataCount int Code []byte Data []byte DLLs dll.List + InstructionCount int + DataCount int } diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index ee320f2..9089335 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -49,7 +49,7 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error return nil, nil } - fn, exists = f.Functions[pkg+"."+name] + fn, exists = f.All.Functions[pkg+"."+name] if !exists { return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index 4792fb0..f768dab 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -22,6 +22,6 @@ func (f *Function) CompileDelete(root *expression.Expression) error { f.SaveRegister(f.CPU.Input[1]) f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Register) f.RegisterNumber(asm.MOVE, f.CPU.Input[1], variable.Type.(*types.Pointer).To.Size()) - f.CallSafe(f.Functions["mem.free"], f.CPU.Input[:2]) + f.CallSafe(f.All.Functions["mem.free"], f.CPU.Input[:2]) return nil } diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index c0925b8..2589f3f 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -37,7 +37,7 @@ func (f *Function) CompileNew(root *expression.Expression) (types.Type, error) { imp.Used = true } - typ, exists := f.Structs[pkg+"."+name] + typ, exists := f.All.Structs[pkg+"."+name] if !exists { return nil, errors.New(&errors.UnknownType{Name: name}, f.File, nameNode.Token.Position) @@ -45,6 +45,6 @@ func (f *Function) CompileNew(root *expression.Expression) (types.Type, error) { f.SaveRegister(f.CPU.Input[0]) f.RegisterNumber(asm.MOVE, f.CPU.Input[0], typ.Size()) - f.CallSafe(f.Functions["mem.alloc"], f.CPU.Input[:1]) + f.CallSafe(f.All.Functions["mem.alloc"], f.CPU.Input[:1]) return &types.Pointer{To: typ}, nil } diff --git a/src/core/Constant.go b/src/core/Constant.go index 5fe78cf..b5bb523 100644 --- a/src/core/Constant.go +++ b/src/core/Constant.go @@ -7,7 +7,7 @@ import ( // Constant registers a single value to be accessible under a descriptive name. type Constant struct { + File *fs.File Name string Token token.Token - File *fs.File } diff --git a/src/core/CreateLabel.go b/src/core/CreateLabel.go index 8ee8eb4..2e874ac 100644 --- a/src/core/CreateLabel.go +++ b/src/core/CreateLabel.go @@ -6,11 +6,11 @@ import ( ) // CreateLabel creates a label that is tied to this function by using a suffix. -func (f *Function) CreateLabel(prefix string, count int) string { +func (f *Function) CreateLabel(prefix string, count uint16) string { tmp := strings.Builder{} tmp.WriteString(prefix) tmp.WriteString(" ") - tmp.WriteString(strconv.Itoa(count)) + tmp.WriteString(strconv.FormatUint(uint64(count), 10)) tmp.WriteString(" [") tmp.WriteString(f.UniqueName) tmp.WriteString("]") diff --git a/src/core/Environment.go b/src/core/Environment.go new file mode 100644 index 0000000..87b1a29 --- /dev/null +++ b/src/core/Environment.go @@ -0,0 +1,14 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/types" +) + +// Environment holds information about the entire build. +type Environment struct { + Constants map[string]*Constant + Functions map[string]*Function + Structs map[string]*types.Struct + Files []*fs.File +} diff --git a/src/core/Fold.go b/src/core/Fold.go index 0f610e8..6245114 100644 --- a/src/core/Fold.go +++ b/src/core/Fold.go @@ -32,7 +32,7 @@ func (f *Function) Fold(expr *expression.Expression) error { leftText := left.Token.Text(f.File.Bytes) right := expr.Children[1] rightText := right.Token.Text(f.File.Bytes) - constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] + constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] if !isConst { return nil diff --git a/src/core/Function.go b/src/core/Function.go index 956956c..f159815 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -14,26 +14,14 @@ type Function struct { Package string Name string UniqueName string + All *Environment File *fs.File Body token.List Input []*Parameter Output []*Parameter OutputTypes []types.Type - Functions map[string]*Function - Structs map[string]*types.Struct - Constants map[string]*Constant DLLs dll.List Err error deferred []func() - count counter -} - -// counter stores how often a certain statement appeared so we can generate a unique label from it. -type counter struct { - assert int - branch int - multiBranch int - data int - loop int - subBranch int + count count } diff --git a/src/core/Identifier.go b/src/core/Identifier.go index 0150c56..6a8a148 100644 --- a/src/core/Identifier.go +++ b/src/core/Identifier.go @@ -15,7 +15,7 @@ func (f *Function) Identifier(name string) (*scope.Variable, *Function) { } uniqueName := fmt.Sprintf("%s.%s", f.Package, name) - function, exists := f.Functions[uniqueName] + function, exists := f.All.Functions[uniqueName] if exists { return nil, function diff --git a/src/core/PeriodToRegister.go b/src/core/PeriodToRegister.go index fd2123b..c897853 100644 --- a/src/core/PeriodToRegister.go +++ b/src/core/PeriodToRegister.go @@ -33,7 +33,7 @@ func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Re return field.Type, nil } - constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] + constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] if isConst { number, err := ToNumber(constant.Token, constant.File) @@ -48,7 +48,7 @@ func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Re } uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) - function, exists := f.Functions[uniqueName] + function, exists := f.All.Functions[uniqueName] if exists { f.File.Imports[leftText].Used = true diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index b885d15..2a5bf08 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -13,7 +13,7 @@ func (f *Function) ResolveTypes() error { for i, param := range f.Input { param.name = param.tokens[0].Text(f.File.Bytes) typeName := param.tokens[1:].Text(f.File.Bytes) - param.typ = types.ByName(typeName, f.Package, f.Structs) + param.typ = types.ByName(typeName, f.Package, f.All.Structs) if param.typ == nil { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) @@ -39,7 +39,7 @@ func (f *Function) ResolveTypes() error { for _, param := range f.Output { typeName := param.tokens.Text(f.File.Bytes) - param.typ = types.ByName(typeName, f.Package, f.Structs) + param.typ = types.ByName(typeName, f.Package, f.All.Structs) if param.typ == nil { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[0].Position) diff --git a/src/core/count.go b/src/core/count.go new file mode 100644 index 0000000..f200d7e --- /dev/null +++ b/src/core/count.go @@ -0,0 +1,13 @@ +package core + +type counter = uint16 + +// count stores how often a certain statement appeared so we can generate a unique label from it. +type count struct { + assert counter + branch counter + multiBranch counter + data counter + loop counter + subBranch counter +} From 91e01de3ad3e65bb93090204f4c6fb826306673c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Feb 2025 17:35:42 +0100 Subject: [PATCH 0816/1012] Implemented environment type --- src/asmc/compiler.go | 4 ++-- src/compiler/Compile.go | 41 ++++++++++++++++++------------------ src/compiler/Result.go | 4 ++-- src/core/CompileCall.go | 2 +- src/core/CompileDelete.go | 2 +- src/core/CompileNew.go | 4 ++-- src/core/Constant.go | 2 +- src/core/CreateLabel.go | 4 ++-- src/core/Environment.go | 14 ++++++++++++ src/core/Fold.go | 2 +- src/core/Function.go | 16 ++------------ src/core/Identifier.go | 2 +- src/core/PeriodToRegister.go | 4 ++-- src/core/ResolveTypes.go | 4 ++-- src/core/count.go | 13 ++++++++++++ 15 files changed, 67 insertions(+), 51 deletions(-) create mode 100644 src/core/Environment.go create mode 100644 src/core/count.go diff --git a/src/asmc/compiler.go b/src/asmc/compiler.go index 0847707..fd17c89 100644 --- a/src/asmc/compiler.go +++ b/src/asmc/compiler.go @@ -9,7 +9,7 @@ type compiler struct { dataLabels map[string]Address codePointers []*pointer dataPointers []*pointer - codeStart Address - dlls dll.List dllPointers []*pointer + dlls dll.List + codeStart Address } diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 749b71b..cc56464 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -9,11 +9,14 @@ import ( // Compile waits for the scan to finish and compiles all functions. func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Struct, errs <-chan error) (Result, error) { + all := core.Environment{ + Files: make([]*fs.File, 0, 8), + Functions: make(map[string]*core.Function, 32), + Structs: make(map[string]*types.Struct, 8), + Constants: make(map[string]*core.Constant, 8), + } + result := Result{} - allFiles := make([]*fs.File, 0, 8) - allFunctions := make(map[string]*core.Function, 32) - allStructs := make(map[string]*types.Struct, 8) - allConstants := make(map[string]*core.Constant, 8) for constants != nil || files != nil || functions != nil || structs != nil || errs != nil { select { @@ -23,10 +26,8 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < continue } - function.Functions = allFunctions - function.Structs = allStructs - function.Constants = allConstants - allFunctions[function.UniqueName] = function + function.All = &all + all.Functions[function.UniqueName] = function case structure, ok := <-structs: if !ok { @@ -34,7 +35,7 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < continue } - allStructs[structure.UniqueName] = structure + all.Structs[structure.UniqueName] = structure case file, ok := <-files: if !ok { @@ -42,7 +43,7 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < continue } - allFiles = append(allFiles, file) + all.Files = append(all.Files, file) case constant, ok := <-constants: if !ok { @@ -50,7 +51,7 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < continue } - allConstants[constant.Name] = constant + all.Constants[constant.Name] = constant case err, ok := <-errs: if !ok { @@ -63,12 +64,12 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < } // Calculate size of structs - for _, structure := range allStructs { - structure.Update(allStructs) + for _, structure := range all.Structs { + structure.Update(all.Structs) } // Resolve the types - for _, function := range allFunctions { + for _, function := range all.Functions { err := function.ResolveTypes() if err != nil { @@ -77,10 +78,10 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < } // Start parallel compilation - CompileFunctions(allFunctions) + CompileFunctions(all.Functions) // Report errors if any occurred - for _, function := range allFunctions { + for _, function := range all.Functions { if function.Err != nil { return result, function.Err } @@ -90,7 +91,7 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < } // Check for unused imports in all files - for _, file := range allFiles { + for _, file := range all.Files { for _, pkg := range file.Imports { if !pkg.Used { return result, errors.New(&errors.UnusedImport{Package: pkg.Path}, file, pkg.Position) @@ -99,14 +100,14 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < } // Check for existence of `init` - init, exists := allFunctions["core.init"] + init, exists := all.Functions["core.init"] if !exists { return result, errors.MissingInitFunction } // Check for existence of `main` - main, exists := allFunctions["main.main"] + main, exists := all.Functions["main.main"] if !exists { return result, errors.MissingMainFunction @@ -114,7 +115,7 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < result.Init = init result.Main = main - result.Functions = allFunctions + result.Functions = all.Functions result.finalize() return result, nil } diff --git a/src/compiler/Result.go b/src/compiler/Result.go index 5c96bf3..ce94da8 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -11,9 +11,9 @@ type Result struct { Main *core.Function Functions map[string]*core.Function Traversed map[*core.Function]bool - InstructionCount int - DataCount int Code []byte Data []byte DLLs dll.List + InstructionCount int + DataCount int } diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index ee320f2..9089335 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -49,7 +49,7 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error return nil, nil } - fn, exists = f.Functions[pkg+"."+name] + fn, exists = f.All.Functions[pkg+"."+name] if !exists { return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index 4792fb0..f768dab 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -22,6 +22,6 @@ func (f *Function) CompileDelete(root *expression.Expression) error { f.SaveRegister(f.CPU.Input[1]) f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Register) f.RegisterNumber(asm.MOVE, f.CPU.Input[1], variable.Type.(*types.Pointer).To.Size()) - f.CallSafe(f.Functions["mem.free"], f.CPU.Input[:2]) + f.CallSafe(f.All.Functions["mem.free"], f.CPU.Input[:2]) return nil } diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index c0925b8..2589f3f 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -37,7 +37,7 @@ func (f *Function) CompileNew(root *expression.Expression) (types.Type, error) { imp.Used = true } - typ, exists := f.Structs[pkg+"."+name] + typ, exists := f.All.Structs[pkg+"."+name] if !exists { return nil, errors.New(&errors.UnknownType{Name: name}, f.File, nameNode.Token.Position) @@ -45,6 +45,6 @@ func (f *Function) CompileNew(root *expression.Expression) (types.Type, error) { f.SaveRegister(f.CPU.Input[0]) f.RegisterNumber(asm.MOVE, f.CPU.Input[0], typ.Size()) - f.CallSafe(f.Functions["mem.alloc"], f.CPU.Input[:1]) + f.CallSafe(f.All.Functions["mem.alloc"], f.CPU.Input[:1]) return &types.Pointer{To: typ}, nil } diff --git a/src/core/Constant.go b/src/core/Constant.go index 5fe78cf..b5bb523 100644 --- a/src/core/Constant.go +++ b/src/core/Constant.go @@ -7,7 +7,7 @@ import ( // Constant registers a single value to be accessible under a descriptive name. type Constant struct { + File *fs.File Name string Token token.Token - File *fs.File } diff --git a/src/core/CreateLabel.go b/src/core/CreateLabel.go index 8ee8eb4..2e874ac 100644 --- a/src/core/CreateLabel.go +++ b/src/core/CreateLabel.go @@ -6,11 +6,11 @@ import ( ) // CreateLabel creates a label that is tied to this function by using a suffix. -func (f *Function) CreateLabel(prefix string, count int) string { +func (f *Function) CreateLabel(prefix string, count uint16) string { tmp := strings.Builder{} tmp.WriteString(prefix) tmp.WriteString(" ") - tmp.WriteString(strconv.Itoa(count)) + tmp.WriteString(strconv.FormatUint(uint64(count), 10)) tmp.WriteString(" [") tmp.WriteString(f.UniqueName) tmp.WriteString("]") diff --git a/src/core/Environment.go b/src/core/Environment.go new file mode 100644 index 0000000..87b1a29 --- /dev/null +++ b/src/core/Environment.go @@ -0,0 +1,14 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/fs" + "git.akyoto.dev/cli/q/src/types" +) + +// Environment holds information about the entire build. +type Environment struct { + Constants map[string]*Constant + Functions map[string]*Function + Structs map[string]*types.Struct + Files []*fs.File +} diff --git a/src/core/Fold.go b/src/core/Fold.go index 0f610e8..6245114 100644 --- a/src/core/Fold.go +++ b/src/core/Fold.go @@ -32,7 +32,7 @@ func (f *Function) Fold(expr *expression.Expression) error { leftText := left.Token.Text(f.File.Bytes) right := expr.Children[1] rightText := right.Token.Text(f.File.Bytes) - constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] + constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] if !isConst { return nil diff --git a/src/core/Function.go b/src/core/Function.go index 956956c..f159815 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -14,26 +14,14 @@ type Function struct { Package string Name string UniqueName string + All *Environment File *fs.File Body token.List Input []*Parameter Output []*Parameter OutputTypes []types.Type - Functions map[string]*Function - Structs map[string]*types.Struct - Constants map[string]*Constant DLLs dll.List Err error deferred []func() - count counter -} - -// counter stores how often a certain statement appeared so we can generate a unique label from it. -type counter struct { - assert int - branch int - multiBranch int - data int - loop int - subBranch int + count count } diff --git a/src/core/Identifier.go b/src/core/Identifier.go index 0150c56..6a8a148 100644 --- a/src/core/Identifier.go +++ b/src/core/Identifier.go @@ -15,7 +15,7 @@ func (f *Function) Identifier(name string) (*scope.Variable, *Function) { } uniqueName := fmt.Sprintf("%s.%s", f.Package, name) - function, exists := f.Functions[uniqueName] + function, exists := f.All.Functions[uniqueName] if exists { return nil, function diff --git a/src/core/PeriodToRegister.go b/src/core/PeriodToRegister.go index fd2123b..c897853 100644 --- a/src/core/PeriodToRegister.go +++ b/src/core/PeriodToRegister.go @@ -33,7 +33,7 @@ func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Re return field.Type, nil } - constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] + constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] if isConst { number, err := ToNumber(constant.Token, constant.File) @@ -48,7 +48,7 @@ func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Re } uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) - function, exists := f.Functions[uniqueName] + function, exists := f.All.Functions[uniqueName] if exists { f.File.Imports[leftText].Used = true diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index b885d15..2a5bf08 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -13,7 +13,7 @@ func (f *Function) ResolveTypes() error { for i, param := range f.Input { param.name = param.tokens[0].Text(f.File.Bytes) typeName := param.tokens[1:].Text(f.File.Bytes) - param.typ = types.ByName(typeName, f.Package, f.Structs) + param.typ = types.ByName(typeName, f.Package, f.All.Structs) if param.typ == nil { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[1].Position) @@ -39,7 +39,7 @@ func (f *Function) ResolveTypes() error { for _, param := range f.Output { typeName := param.tokens.Text(f.File.Bytes) - param.typ = types.ByName(typeName, f.Package, f.Structs) + param.typ = types.ByName(typeName, f.Package, f.All.Structs) if param.typ == nil { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[0].Position) diff --git a/src/core/count.go b/src/core/count.go new file mode 100644 index 0000000..f200d7e --- /dev/null +++ b/src/core/count.go @@ -0,0 +1,13 @@ +package core + +type counter = uint16 + +// count stores how often a certain statement appeared so we can generate a unique label from it. +type count struct { + assert counter + branch counter + multiBranch counter + data counter + loop counter + subBranch counter +} From cf1ed9948f62bc2ca78939208d8314aeba226095 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Feb 2025 19:46:32 +0100 Subject: [PATCH 0817/1012] Simplified the functions that generate executables --- src/macho/MachO.go | 1 - src/pe/EXE.go | 74 ++++++--------------------------------- src/pe/importLibraries.go | 60 +++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 64 deletions(-) create mode 100644 src/pe/importLibraries.go diff --git a/src/macho/MachO.go b/src/macho/MachO.go index c463694..17d9564 100644 --- a/src/macho/MachO.go +++ b/src/macho/MachO.go @@ -116,7 +116,6 @@ func Write(writer io.Writer, code []byte, data []byte) { binary.Write(writer, binary.LittleEndian, &m.CodeHeader) binary.Write(writer, binary.LittleEndian, &m.DataHeader) binary.Write(writer, binary.LittleEndian, &m.UnixThread) - writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) writer.Write(code) writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) diff --git a/src/pe/EXE.go b/src/pe/EXE.go index 238b3a7..bfbaef9 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -25,73 +25,22 @@ type EXE struct { // Write writes the EXE file to the given writer. func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { - codeStart, codePadding := fs.Align(HeaderEnd, config.Align) - dataStart, dataPadding := fs.Align(codeStart+len(code), config.Align) - importsStart, importsPadding := fs.Align(dataStart+len(data), config.Align) - - subSystem := IMAGE_SUBSYSTEM_WINDOWS_CUI + var ( + codeStart, codePadding = fs.Align(HeaderEnd, config.Align) + dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) + importsStart, importsPadding = fs.Align(dataStart+len(data), config.Align) + subSystem = IMAGE_SUBSYSTEM_WINDOWS_CUI + imports, dllData, dllImports, dllDataStart = importLibraries(dlls, importsStart) + importDirectoryStart = dllDataStart + len(dllData) + importDirectorySize = DLLImportSize * len(dllImports) + importSectionSize = len(imports)*8 + len(dllData) + importDirectorySize + imageSize, _ = fs.Align(importsStart+importSectionSize, config.Align) + ) if dlls.Contains("user32") { subSystem = IMAGE_SUBSYSTEM_WINDOWS_GUI } - imports := make([]uint64, 0) - dllData := make([]byte, 0) - dllImports := []DLLImport{} - - for _, library := range dlls { - functionsStart := len(imports) * 8 - dllNamePos := len(dllData) - dllData = append(dllData, library.Name...) - dllData = append(dllData, ".dll"...) - dllData = append(dllData, 0x00) - - dllImports = append(dllImports, DLLImport{ - RvaFunctionNameList: uint32(importsStart + functionsStart), - TimeDateStamp: 0, - ForwarderChain: 0, - RvaModuleName: uint32(dllNamePos), - RvaFunctionAddressList: uint32(importsStart + functionsStart), - }) - - for _, fn := range library.Functions { - if len(dllData)&1 != 0 { - dllData = append(dllData, 0x00) // align the next entry on an even boundary - } - - offset := len(dllData) - dllData = append(dllData, 0x00, 0x00) - dllData = append(dllData, fn...) - dllData = append(dllData, 0x00) - - imports = append(imports, uint64(offset)) - } - - imports = append(imports, 0) - } - - dllDataStart := importsStart + len(imports)*8 - - for i := range imports { - if imports[i] == 0 { - continue - } - - imports[i] += uint64(dllDataStart) - } - - for i := range dllImports { - dllImports[i].RvaModuleName += uint32(dllDataStart) - } - - dllImports = append(dllImports, DLLImport{}) // a zeroed structure marks the end of the list - importDirectoryStart := dllDataStart + len(dllData) - importDirectorySize := DLLImportSize * len(dllImports) - - importSectionSize := len(imports)*8 + len(dllData) + importDirectorySize - imageSize := importsStart + importSectionSize - imageSize, _ = fs.Align(imageSize, config.Align) - pe := &EXE{ DOSHeader: DOSHeader{ Magic: [4]byte{'M', 'Z', 0, 0}, @@ -188,7 +137,6 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { binary.Write(writer, binary.LittleEndian, &pe.NTHeader) binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) binary.Write(writer, binary.LittleEndian, &pe.Sections) - writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) writer.Write(code) writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) diff --git a/src/pe/importLibraries.go b/src/pe/importLibraries.go new file mode 100644 index 0000000..d7c8d32 --- /dev/null +++ b/src/pe/importLibraries.go @@ -0,0 +1,60 @@ +package pe + +import "git.akyoto.dev/cli/q/src/dll" + +// importLibraries generates the import address table which contains the addresses of functions imported from DLLs. +func importLibraries(dlls dll.List, importsStart int) ([]uint64, []byte, []DLLImport, int) { + imports := make([]uint64, 0) + dllData := make([]byte, 0) + dllImports := []DLLImport{} + + for _, library := range dlls { + functionsStart := len(imports) * 8 + dllNamePos := len(dllData) + dllData = append(dllData, library.Name...) + dllData = append(dllData, ".dll"...) + dllData = append(dllData, 0x00) + + dllImports = append(dllImports, DLLImport{ + RvaFunctionNameList: uint32(importsStart + functionsStart), + TimeDateStamp: 0, + ForwarderChain: 0, + RvaModuleName: uint32(dllNamePos), + RvaFunctionAddressList: uint32(importsStart + functionsStart), + }) + + for _, fn := range library.Functions { + if len(dllData)&1 != 0 { + dllData = append(dllData, 0x00) // align the next entry on an even boundary + } + + offset := len(dllData) + dllData = append(dllData, 0x00, 0x00) + dllData = append(dllData, fn...) + dllData = append(dllData, 0x00) + + imports = append(imports, uint64(offset)) + } + + imports = append(imports, 0) + } + + dllDataStart := importsStart + len(imports)*8 + + for i := range imports { + if imports[i] == 0 { + continue + } + + imports[i] += uint64(dllDataStart) + } + + for i := range dllImports { + dllImports[i].RvaModuleName += uint32(dllDataStart) + } + + // a zeroed structure marks the end of the list + dllImports = append(dllImports, DLLImport{}) + + return imports, dllData, dllImports, dllDataStart +} From 264872e6387587345082979caa983b0cbf291cab Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Feb 2025 19:46:32 +0100 Subject: [PATCH 0818/1012] Simplified the functions that generate executables --- src/macho/MachO.go | 1 - src/pe/EXE.go | 74 ++++++--------------------------------- src/pe/importLibraries.go | 60 +++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 64 deletions(-) create mode 100644 src/pe/importLibraries.go diff --git a/src/macho/MachO.go b/src/macho/MachO.go index c463694..17d9564 100644 --- a/src/macho/MachO.go +++ b/src/macho/MachO.go @@ -116,7 +116,6 @@ func Write(writer io.Writer, code []byte, data []byte) { binary.Write(writer, binary.LittleEndian, &m.CodeHeader) binary.Write(writer, binary.LittleEndian, &m.DataHeader) binary.Write(writer, binary.LittleEndian, &m.UnixThread) - writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) writer.Write(code) writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) diff --git a/src/pe/EXE.go b/src/pe/EXE.go index 238b3a7..bfbaef9 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -25,73 +25,22 @@ type EXE struct { // Write writes the EXE file to the given writer. func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { - codeStart, codePadding := fs.Align(HeaderEnd, config.Align) - dataStart, dataPadding := fs.Align(codeStart+len(code), config.Align) - importsStart, importsPadding := fs.Align(dataStart+len(data), config.Align) - - subSystem := IMAGE_SUBSYSTEM_WINDOWS_CUI + var ( + codeStart, codePadding = fs.Align(HeaderEnd, config.Align) + dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) + importsStart, importsPadding = fs.Align(dataStart+len(data), config.Align) + subSystem = IMAGE_SUBSYSTEM_WINDOWS_CUI + imports, dllData, dllImports, dllDataStart = importLibraries(dlls, importsStart) + importDirectoryStart = dllDataStart + len(dllData) + importDirectorySize = DLLImportSize * len(dllImports) + importSectionSize = len(imports)*8 + len(dllData) + importDirectorySize + imageSize, _ = fs.Align(importsStart+importSectionSize, config.Align) + ) if dlls.Contains("user32") { subSystem = IMAGE_SUBSYSTEM_WINDOWS_GUI } - imports := make([]uint64, 0) - dllData := make([]byte, 0) - dllImports := []DLLImport{} - - for _, library := range dlls { - functionsStart := len(imports) * 8 - dllNamePos := len(dllData) - dllData = append(dllData, library.Name...) - dllData = append(dllData, ".dll"...) - dllData = append(dllData, 0x00) - - dllImports = append(dllImports, DLLImport{ - RvaFunctionNameList: uint32(importsStart + functionsStart), - TimeDateStamp: 0, - ForwarderChain: 0, - RvaModuleName: uint32(dllNamePos), - RvaFunctionAddressList: uint32(importsStart + functionsStart), - }) - - for _, fn := range library.Functions { - if len(dllData)&1 != 0 { - dllData = append(dllData, 0x00) // align the next entry on an even boundary - } - - offset := len(dllData) - dllData = append(dllData, 0x00, 0x00) - dllData = append(dllData, fn...) - dllData = append(dllData, 0x00) - - imports = append(imports, uint64(offset)) - } - - imports = append(imports, 0) - } - - dllDataStart := importsStart + len(imports)*8 - - for i := range imports { - if imports[i] == 0 { - continue - } - - imports[i] += uint64(dllDataStart) - } - - for i := range dllImports { - dllImports[i].RvaModuleName += uint32(dllDataStart) - } - - dllImports = append(dllImports, DLLImport{}) // a zeroed structure marks the end of the list - importDirectoryStart := dllDataStart + len(dllData) - importDirectorySize := DLLImportSize * len(dllImports) - - importSectionSize := len(imports)*8 + len(dllData) + importDirectorySize - imageSize := importsStart + importSectionSize - imageSize, _ = fs.Align(imageSize, config.Align) - pe := &EXE{ DOSHeader: DOSHeader{ Magic: [4]byte{'M', 'Z', 0, 0}, @@ -188,7 +137,6 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { binary.Write(writer, binary.LittleEndian, &pe.NTHeader) binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) binary.Write(writer, binary.LittleEndian, &pe.Sections) - writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) writer.Write(code) writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) diff --git a/src/pe/importLibraries.go b/src/pe/importLibraries.go new file mode 100644 index 0000000..d7c8d32 --- /dev/null +++ b/src/pe/importLibraries.go @@ -0,0 +1,60 @@ +package pe + +import "git.akyoto.dev/cli/q/src/dll" + +// importLibraries generates the import address table which contains the addresses of functions imported from DLLs. +func importLibraries(dlls dll.List, importsStart int) ([]uint64, []byte, []DLLImport, int) { + imports := make([]uint64, 0) + dllData := make([]byte, 0) + dllImports := []DLLImport{} + + for _, library := range dlls { + functionsStart := len(imports) * 8 + dllNamePos := len(dllData) + dllData = append(dllData, library.Name...) + dllData = append(dllData, ".dll"...) + dllData = append(dllData, 0x00) + + dllImports = append(dllImports, DLLImport{ + RvaFunctionNameList: uint32(importsStart + functionsStart), + TimeDateStamp: 0, + ForwarderChain: 0, + RvaModuleName: uint32(dllNamePos), + RvaFunctionAddressList: uint32(importsStart + functionsStart), + }) + + for _, fn := range library.Functions { + if len(dllData)&1 != 0 { + dllData = append(dllData, 0x00) // align the next entry on an even boundary + } + + offset := len(dllData) + dllData = append(dllData, 0x00, 0x00) + dllData = append(dllData, fn...) + dllData = append(dllData, 0x00) + + imports = append(imports, uint64(offset)) + } + + imports = append(imports, 0) + } + + dllDataStart := importsStart + len(imports)*8 + + for i := range imports { + if imports[i] == 0 { + continue + } + + imports[i] += uint64(dllDataStart) + } + + for i := range dllImports { + dllImports[i].RvaModuleName += uint32(dllDataStart) + } + + // a zeroed structure marks the end of the list + dllImports = append(dllImports, DLLImport{}) + + return imports, dllData, dllImports, dllDataStart +} From 65b74046e165daa44f0ede44c79d377a9dd18dc3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Feb 2025 22:39:01 +0100 Subject: [PATCH 0819/1012] Updated documentation --- src/build/Build.go | 42 ----------------------------------------- src/build/Executable.go | 35 ++++++++++++++++++++++++++++++++++ src/build/Run.go | 12 ++++++++++++ src/core/CreateLabel.go | 2 +- src/core/count.go | 2 +- src/readme.md | 9 +++++++++ 6 files changed, 58 insertions(+), 44 deletions(-) create mode 100644 src/build/Executable.go create mode 100644 src/build/Run.go diff --git a/src/build/Build.go b/src/build/Build.go index 64e1190..9d969ba 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -1,14 +1,5 @@ package build -import ( - "path/filepath" - "strings" - - "git.akyoto.dev/cli/q/src/compiler" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/scanner" -) - // Build describes a compiler build. type Build struct { Files []string @@ -20,36 +11,3 @@ func New(files ...string) *Build { Files: files, } } - -// Run compiles the input files. -func (build *Build) Run() (compiler.Result, error) { - constants, files, functions, structs, errors := scanner.Scan(build.Files) - return compiler.Compile(constants, files, functions, structs, errors) -} - -// Executable returns the path to the executable. -func (build *Build) Executable() string { - path, _ := filepath.Abs(build.Files[0]) - - if strings.HasSuffix(path, ".q") { - path = fromFileName(path) - } else { - path = fromDirectoryName(path) - } - - if config.TargetOS == config.Windows { - path += ".exe" - } - - return path -} - -// fromDirectoryName returns the executable path based on the directory name. -func fromDirectoryName(path string) string { - return filepath.Join(path, filepath.Base(path)) -} - -// fromFileName returns the executable path based on the file name. -func fromFileName(path string) string { - return filepath.Join(filepath.Dir(path), strings.TrimSuffix(filepath.Base(path), ".q")) -} diff --git a/src/build/Executable.go b/src/build/Executable.go new file mode 100644 index 0000000..4eff03e --- /dev/null +++ b/src/build/Executable.go @@ -0,0 +1,35 @@ +package build + +import ( + "path/filepath" + "strings" + + "git.akyoto.dev/cli/q/src/config" +) + +// Executable returns the path to the executable. +func (build *Build) Executable() string { + path, _ := filepath.Abs(build.Files[0]) + + if strings.HasSuffix(path, ".q") { + path = fromFileName(path) + } else { + path = fromDirectoryName(path) + } + + if config.TargetOS == config.Windows { + path += ".exe" + } + + return path +} + +// fromDirectoryName returns the executable path based on the directory name. +func fromDirectoryName(path string) string { + return filepath.Join(path, filepath.Base(path)) +} + +// fromFileName returns the executable path based on the file name. +func fromFileName(path string) string { + return filepath.Join(filepath.Dir(path), strings.TrimSuffix(filepath.Base(path), ".q")) +} diff --git a/src/build/Run.go b/src/build/Run.go new file mode 100644 index 0000000..bbc06d8 --- /dev/null +++ b/src/build/Run.go @@ -0,0 +1,12 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/compiler" + "git.akyoto.dev/cli/q/src/scanner" +) + +// Run compiles the input files. +func (build *Build) Run() (compiler.Result, error) { + constants, files, functions, structs, errors := scanner.Scan(build.Files) + return compiler.Compile(constants, files, functions, structs, errors) +} diff --git a/src/core/CreateLabel.go b/src/core/CreateLabel.go index 2e874ac..5342638 100644 --- a/src/core/CreateLabel.go +++ b/src/core/CreateLabel.go @@ -6,7 +6,7 @@ import ( ) // CreateLabel creates a label that is tied to this function by using a suffix. -func (f *Function) CreateLabel(prefix string, count uint16) string { +func (f *Function) CreateLabel(prefix string, count counter) string { tmp := strings.Builder{} tmp.WriteString(prefix) tmp.WriteString(" ") diff --git a/src/core/count.go b/src/core/count.go index f200d7e..5d793fc 100644 --- a/src/core/count.go +++ b/src/core/count.go @@ -1,6 +1,6 @@ package core -type counter = uint16 +type counter = uint8 // count stores how often a certain statement appeared so we can generate a unique label from it. type count struct { diff --git a/src/readme.md b/src/readme.md index 1716ab7..3dc207b 100644 --- a/src/readme.md +++ b/src/readme.md @@ -26,3 +26,12 @@ - [token](token) - Converts a file to tokens with the `Tokenize` function - [types](types) - Type system - [x86](x86) - x86-64 implementation + +## Typical flow + +1. [main](../main.go) +1. [cli.Main](cli/Main.go) +1. [build.Run](build/Run.go) +1. [scanner.Scan](scanner/Scan.go) +1. [compiler.Compile](compiler/Compile.go) +1. [compiler.WriteFile](compiler/WriteFile.go) \ No newline at end of file From 7598411c8f20c1419f05e4e94a067493bec442f0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 21 Feb 2025 22:39:01 +0100 Subject: [PATCH 0820/1012] Updated documentation --- src/build/Build.go | 42 ----------------------------------------- src/build/Executable.go | 35 ++++++++++++++++++++++++++++++++++ src/build/Run.go | 12 ++++++++++++ src/core/CreateLabel.go | 2 +- src/core/count.go | 2 +- src/readme.md | 9 +++++++++ 6 files changed, 58 insertions(+), 44 deletions(-) create mode 100644 src/build/Executable.go create mode 100644 src/build/Run.go diff --git a/src/build/Build.go b/src/build/Build.go index 64e1190..9d969ba 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -1,14 +1,5 @@ package build -import ( - "path/filepath" - "strings" - - "git.akyoto.dev/cli/q/src/compiler" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/scanner" -) - // Build describes a compiler build. type Build struct { Files []string @@ -20,36 +11,3 @@ func New(files ...string) *Build { Files: files, } } - -// Run compiles the input files. -func (build *Build) Run() (compiler.Result, error) { - constants, files, functions, structs, errors := scanner.Scan(build.Files) - return compiler.Compile(constants, files, functions, structs, errors) -} - -// Executable returns the path to the executable. -func (build *Build) Executable() string { - path, _ := filepath.Abs(build.Files[0]) - - if strings.HasSuffix(path, ".q") { - path = fromFileName(path) - } else { - path = fromDirectoryName(path) - } - - if config.TargetOS == config.Windows { - path += ".exe" - } - - return path -} - -// fromDirectoryName returns the executable path based on the directory name. -func fromDirectoryName(path string) string { - return filepath.Join(path, filepath.Base(path)) -} - -// fromFileName returns the executable path based on the file name. -func fromFileName(path string) string { - return filepath.Join(filepath.Dir(path), strings.TrimSuffix(filepath.Base(path), ".q")) -} diff --git a/src/build/Executable.go b/src/build/Executable.go new file mode 100644 index 0000000..4eff03e --- /dev/null +++ b/src/build/Executable.go @@ -0,0 +1,35 @@ +package build + +import ( + "path/filepath" + "strings" + + "git.akyoto.dev/cli/q/src/config" +) + +// Executable returns the path to the executable. +func (build *Build) Executable() string { + path, _ := filepath.Abs(build.Files[0]) + + if strings.HasSuffix(path, ".q") { + path = fromFileName(path) + } else { + path = fromDirectoryName(path) + } + + if config.TargetOS == config.Windows { + path += ".exe" + } + + return path +} + +// fromDirectoryName returns the executable path based on the directory name. +func fromDirectoryName(path string) string { + return filepath.Join(path, filepath.Base(path)) +} + +// fromFileName returns the executable path based on the file name. +func fromFileName(path string) string { + return filepath.Join(filepath.Dir(path), strings.TrimSuffix(filepath.Base(path), ".q")) +} diff --git a/src/build/Run.go b/src/build/Run.go new file mode 100644 index 0000000..bbc06d8 --- /dev/null +++ b/src/build/Run.go @@ -0,0 +1,12 @@ +package build + +import ( + "git.akyoto.dev/cli/q/src/compiler" + "git.akyoto.dev/cli/q/src/scanner" +) + +// Run compiles the input files. +func (build *Build) Run() (compiler.Result, error) { + constants, files, functions, structs, errors := scanner.Scan(build.Files) + return compiler.Compile(constants, files, functions, structs, errors) +} diff --git a/src/core/CreateLabel.go b/src/core/CreateLabel.go index 2e874ac..5342638 100644 --- a/src/core/CreateLabel.go +++ b/src/core/CreateLabel.go @@ -6,7 +6,7 @@ import ( ) // CreateLabel creates a label that is tied to this function by using a suffix. -func (f *Function) CreateLabel(prefix string, count uint16) string { +func (f *Function) CreateLabel(prefix string, count counter) string { tmp := strings.Builder{} tmp.WriteString(prefix) tmp.WriteString(" ") diff --git a/src/core/count.go b/src/core/count.go index f200d7e..5d793fc 100644 --- a/src/core/count.go +++ b/src/core/count.go @@ -1,6 +1,6 @@ package core -type counter = uint16 +type counter = uint8 // count stores how often a certain statement appeared so we can generate a unique label from it. type count struct { diff --git a/src/readme.md b/src/readme.md index 1716ab7..3dc207b 100644 --- a/src/readme.md +++ b/src/readme.md @@ -26,3 +26,12 @@ - [token](token) - Converts a file to tokens with the `Tokenize` function - [types](types) - Type system - [x86](x86) - x86-64 implementation + +## Typical flow + +1. [main](../main.go) +1. [cli.Main](cli/Main.go) +1. [build.Run](build/Run.go) +1. [scanner.Scan](scanner/Scan.go) +1. [compiler.Compile](compiler/Compile.go) +1. [compiler.WriteFile](compiler/WriteFile.go) \ No newline at end of file From a75aad52f6fce88533f24fd37c890e339a6744bc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 22 Feb 2025 12:54:23 +0100 Subject: [PATCH 0821/1012] Fixed incorrect division results --- examples/prime/prime.q | 2 +- src/core/CompileAssign.go | 2 +- src/core/CompileAssignDivision.go | 62 ++++++++++++----- src/core/CompileDefinition.go | 5 ++ src/core/ExecuteRegisterNumber.go | 5 +- src/core/ExecuteRegisterRegister.go | 5 +- tests/programs/div-split.q | 8 +-- tests/programs/op-assign.q | 9 --- tests/programs/operator-assign.q | 30 ++++++++ tests/programs/variables.q | 14 ++-- tests/programs_test.go | 102 ++++++++++++++-------------- 11 files changed, 150 insertions(+), 94 deletions(-) delete mode 100644 tests/programs/op-assign.q create mode 100644 tests/programs/operator-assign.q diff --git a/examples/prime/prime.q b/examples/prime/prime.q index e417cd8..fdd1bcf 100644 --- a/examples/prime/prime.q +++ b/examples/prime/prime.q @@ -22,7 +22,7 @@ main() { } } -isPrime(x int) -> int { +isPrime(x int) -> bool { if x == 2 { return 1 } diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 6325e21..bbc237b 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -35,7 +35,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { } if left.Token.Kind == token.Separator && right.Token.Kind == token.Div { - return f.CompileAssignDivision(node) + return f.CompileAssignDivision(node.Expression) } if !ast.IsFunctionCall(right) { diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index c427b04..c93d90b 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -2,30 +2,60 @@ package core import ( "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/scope" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" "git.akyoto.dev/cli/q/src/x86" ) // CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right. -func (f *Function) CompileAssignDivision(node *ast.Assign) error { - left := node.Expression.Children[0] - right := node.Expression.Children[1] +func (f *Function) CompileAssignDivision(expr *expression.Expression) error { + var ( + left = expr.Children[0] + right = expr.Children[1] + quotientVariable *scope.Variable + remainderVariable *scope.Variable + err error + ) - quotient := left.Children[0] - name := quotient.Token.Text(f.File.Bytes) - quotientVariable := f.VariableByName(name) + if expr.Token.Kind == token.Define { + quotientVariable, err = f.Define(left.Children[0]) - if quotientVariable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, quotient.Token.Position) - } + if err != nil { + return err + } - remainder := left.Children[1] - name = remainder.Token.Text(f.File.Bytes) - remainderVariable := f.VariableByName(name) + remainderVariable, err = f.Define(left.Children[1]) - if remainderVariable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, remainder.Token.Position) + if err != nil { + return err + } + + quotientVariable.Type = types.Int + remainderVariable.Type = types.Int + f.AddVariable(quotientVariable) + f.AddVariable(remainderVariable) + } else { + quotient := left.Children[0] + name := quotient.Token.Text(f.File.Bytes) + quotientVariable = f.VariableByName(name) + + if quotientVariable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, quotient.Token.Position) + } + + remainder := left.Children[1] + name = remainder.Token.Text(f.File.Bytes) + remainderVariable = f.VariableByName(name) + + if remainderVariable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, remainder.Token.Position) + } + + defer f.UseVariable(quotientVariable) + defer f.UseVariable(remainderVariable) } dividend := right.Children[0] @@ -38,9 +68,7 @@ func (f *Function) CompileAssignDivision(node *ast.Assign) error { divisor := right.Children[1] err = f.Execute(right.Token, dividendRegister, divisor) f.RegisterRegister(asm.MOVE, quotientVariable.Register, x86.RAX) - f.UseVariable(quotientVariable) f.RegisterRegister(asm.MOVE, remainderVariable.Register, x86.RDX) - f.UseVariable(remainderVariable) if isTemporary { f.FreeRegister(dividendRegister) diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index 1143315..6b46991 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -5,6 +5,7 @@ import ( "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // CompileDefinition compiles a variable definition. @@ -34,6 +35,10 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return nil } + if left.Token.Kind == token.Separator && right.Token.Kind == token.Div { + return f.CompileAssignDivision(node.Expression) + } + if !ast.IsFunctionCall(right) { return errors.New(errors.NotImplemented, f.File, node.Expression.Token.Position) } diff --git a/src/core/ExecuteRegisterNumber.go b/src/core/ExecuteRegisterNumber.go index f26f1a3..77a6ffb 100644 --- a/src/core/ExecuteRegisterNumber.go +++ b/src/core/ExecuteRegisterNumber.go @@ -25,7 +25,10 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg f.RegisterNumber(asm.MUL, register, number) case token.Div, token.DivAssign: - f.SaveRegister(x86.RAX) + if register != x86.RAX { + f.SaveRegister(x86.RAX) + } + f.SaveRegister(x86.RDX) tmp := f.NewRegister() f.RegisterNumber(asm.MOVE, tmp, number) diff --git a/src/core/ExecuteRegisterRegister.go b/src/core/ExecuteRegisterRegister.go index ebb6811..fc785b7 100644 --- a/src/core/ExecuteRegisterRegister.go +++ b/src/core/ExecuteRegisterRegister.go @@ -25,7 +25,10 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.R f.RegisterRegister(asm.MUL, register, operand) case token.Div, token.DivAssign: - f.SaveRegister(x86.RAX) + if register != x86.RAX { + f.SaveRegister(x86.RAX) + } + f.SaveRegister(x86.RDX) f.RegisterRegister(asm.DIV, register, operand) diff --git a/tests/programs/div-split.q b/tests/programs/div-split.q index e44e409..847950a 100644 --- a/tests/programs/div-split.q +++ b/tests/programs/div-split.q @@ -1,13 +1,11 @@ main() { - quotient := 0 - remainder := 0 dividend := 256 divisor := 100 - quotient, remainder = 256 / 100 + quotient, remainder := 256 / 100 assert quotient == 2 assert remainder == 56 - + quotient, remainder = dividend / 100 assert quotient == 2 assert remainder == 56 @@ -15,7 +13,7 @@ main() { quotient, remainder = 256 / divisor assert quotient == 2 assert remainder == 56 - + quotient, remainder = dividend / divisor assert quotient == 2 assert remainder == 56 diff --git a/tests/programs/op-assign.q b/tests/programs/op-assign.q deleted file mode 100644 index 85dd9c4..0000000 --- a/tests/programs/op-assign.q +++ /dev/null @@ -1,9 +0,0 @@ -main() { - f(10) -} - -f(new int) { - old := new - new -= 1 - assert new != old -} \ No newline at end of file diff --git a/tests/programs/operator-assign.q b/tests/programs/operator-assign.q new file mode 100644 index 0000000..36809f2 --- /dev/null +++ b/tests/programs/operator-assign.q @@ -0,0 +1,30 @@ +main() { + number(10) + register(10) +} + +number(x int) { + y := x + x -= 1 + assert x < y + x += 1 + assert x == y + x *= 2 + assert x > y + x /= 2 + assert x == y +} + +register(x int) { + y := x + num := 1 + x -= num + assert x < y + x += num + assert x == y + num = 2 + x *= num + assert x > y + x /= num + assert x == y +} \ No newline at end of file diff --git a/tests/programs/variables.q b/tests/programs/variables.q index 8334626..554f438 100644 --- a/tests/programs/variables.q +++ b/tests/programs/variables.q @@ -7,11 +7,11 @@ main() { f := 6 g := 7 - assert a != 0 - assert b != 0 - assert c != 0 - assert d != 0 - assert e != 0 - assert f != 0 - assert g != 0 + assert a == 1 + assert b == 2 + assert c == 3 + assert d == 4 + assert e == 5 + assert f == 6 + assert g == 7 } \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 3097960..da5dfc8 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -14,58 +14,56 @@ import ( var programs = []struct { Name string - Input string - Output string ExitCode int }{ - {"empty", "", "", 0}, - {"assert", "", "", 1}, - {"variables", "", "", 0}, - {"reassign", "", "", 0}, - {"reuse", "", "", 0}, - {"return", "", "", 0}, - {"return-multi", "", "", 0}, - {"math", "", "", 0}, - {"precedence", "", "", 0}, - {"op-assign", "", "", 0}, - {"binary", "", "", 0}, - {"octal", "", "", 0}, - {"hexadecimal", "", "", 0}, - {"const", "", "", 0}, - {"escape-rune", "", "", 0}, - {"escape-string", "", "", 0}, - {"bitwise-and", "", "", 0}, - {"bitwise-or", "", "", 0}, - {"bitwise-xor", "", "", 0}, - {"shift", "", "", 0}, - {"modulo", "", "", 0}, - {"modulo-assign", "", "", 0}, - {"div-split", "", "", 0}, - {"int64", "", "", 0}, - {"negative", "", "", 0}, - {"negation", "", "", 0}, - {"square-sum", "", "", 0}, - {"chained-calls", "", "", 0}, - {"nested-calls", "", "", 0}, - {"param", "", "", 0}, - {"param-multi", "", "", 0}, - {"param-order", "", "", 0}, - {"branch", "", "", 0}, - {"branch-and", "", "", 0}, - {"branch-or", "", "", 0}, - {"branch-both", "", "", 0}, - {"branch-save", "", "", 0}, - {"jump-near", "", "", 0}, - {"switch", "", "", 0}, - {"loop-infinite", "", "", 0}, - {"loop-lifetime", "", "", 0}, - {"loop-for", "", "", 0}, - {"memory-free", "", "", 0}, - {"out-of-memory", "", "", 0}, - {"index-static", "", "", 0}, - {"index-dynamic", "", "", 0}, - {"struct", "", "", 0}, - {"len", "", "", 0}, + {"empty", 0}, + {"assert", 1}, + {"binary", 0}, + {"octal", 0}, + {"hexadecimal", 0}, + {"variables", 0}, + {"reassign", 0}, + {"reuse", 0}, + {"return", 0}, + {"return-multi", 0}, + {"math", 0}, + {"precedence", 0}, + {"operator-assign", 0}, + {"const", 0}, + {"escape-rune", 0}, + {"escape-string", 0}, + {"bitwise-and", 0}, + {"bitwise-or", 0}, + {"bitwise-xor", 0}, + {"shift", 0}, + {"modulo", 0}, + {"modulo-assign", 0}, + {"div-split", 0}, + {"int64", 0}, + {"negative", 0}, + {"negation", 0}, + {"square-sum", 0}, + {"chained-calls", 0}, + {"nested-calls", 0}, + {"param", 0}, + {"param-multi", 0}, + {"param-order", 0}, + {"branch", 0}, + {"branch-and", 0}, + {"branch-or", 0}, + {"branch-both", 0}, + {"branch-save", 0}, + {"jump-near", 0}, + {"switch", 0}, + {"loop-infinite", 0}, + {"loop-lifetime", 0}, + {"loop-for", 0}, + {"memory-free", 0}, + {"out-of-memory", 0}, + {"index-static", 0}, + {"index-dynamic", 0}, + {"struct", 0}, + {"len", 0}, } func TestPrograms(t *testing.T) { @@ -74,12 +72,12 @@ func TestPrograms(t *testing.T) { t.Run(test.Name+"/debug", func(t *testing.T) { config.Optimize(false) - run(t, file, "debug", test.Input, test.Output, test.ExitCode) + run(t, file, "debug", "", "", test.ExitCode) }) t.Run(test.Name+"/release", func(t *testing.T) { config.Optimize(true) - run(t, file, "release", test.Input, test.Output, test.ExitCode) + run(t, file, "release", "", "", test.ExitCode) }) } } From c088697446fe529817a7eb46444e9163416919e8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 22 Feb 2025 12:54:23 +0100 Subject: [PATCH 0822/1012] Fixed incorrect division results --- examples/prime/prime.q | 2 +- src/core/CompileAssign.go | 2 +- src/core/CompileAssignDivision.go | 62 ++++++++++++----- src/core/CompileDefinition.go | 5 ++ src/core/ExecuteRegisterNumber.go | 5 +- src/core/ExecuteRegisterRegister.go | 5 +- tests/programs/div-split.q | 8 +-- tests/programs/op-assign.q | 9 --- tests/programs/operator-assign.q | 30 ++++++++ tests/programs/variables.q | 14 ++-- tests/programs_test.go | 102 ++++++++++++++-------------- 11 files changed, 150 insertions(+), 94 deletions(-) delete mode 100644 tests/programs/op-assign.q create mode 100644 tests/programs/operator-assign.q diff --git a/examples/prime/prime.q b/examples/prime/prime.q index e417cd8..fdd1bcf 100644 --- a/examples/prime/prime.q +++ b/examples/prime/prime.q @@ -22,7 +22,7 @@ main() { } } -isPrime(x int) -> int { +isPrime(x int) -> bool { if x == 2 { return 1 } diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 6325e21..bbc237b 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -35,7 +35,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { } if left.Token.Kind == token.Separator && right.Token.Kind == token.Div { - return f.CompileAssignDivision(node) + return f.CompileAssignDivision(node.Expression) } if !ast.IsFunctionCall(right) { diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index c427b04..c93d90b 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -2,30 +2,60 @@ package core import ( "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/scope" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" "git.akyoto.dev/cli/q/src/x86" ) // CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right. -func (f *Function) CompileAssignDivision(node *ast.Assign) error { - left := node.Expression.Children[0] - right := node.Expression.Children[1] +func (f *Function) CompileAssignDivision(expr *expression.Expression) error { + var ( + left = expr.Children[0] + right = expr.Children[1] + quotientVariable *scope.Variable + remainderVariable *scope.Variable + err error + ) - quotient := left.Children[0] - name := quotient.Token.Text(f.File.Bytes) - quotientVariable := f.VariableByName(name) + if expr.Token.Kind == token.Define { + quotientVariable, err = f.Define(left.Children[0]) - if quotientVariable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, quotient.Token.Position) - } + if err != nil { + return err + } - remainder := left.Children[1] - name = remainder.Token.Text(f.File.Bytes) - remainderVariable := f.VariableByName(name) + remainderVariable, err = f.Define(left.Children[1]) - if remainderVariable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, remainder.Token.Position) + if err != nil { + return err + } + + quotientVariable.Type = types.Int + remainderVariable.Type = types.Int + f.AddVariable(quotientVariable) + f.AddVariable(remainderVariable) + } else { + quotient := left.Children[0] + name := quotient.Token.Text(f.File.Bytes) + quotientVariable = f.VariableByName(name) + + if quotientVariable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, quotient.Token.Position) + } + + remainder := left.Children[1] + name = remainder.Token.Text(f.File.Bytes) + remainderVariable = f.VariableByName(name) + + if remainderVariable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, remainder.Token.Position) + } + + defer f.UseVariable(quotientVariable) + defer f.UseVariable(remainderVariable) } dividend := right.Children[0] @@ -38,9 +68,7 @@ func (f *Function) CompileAssignDivision(node *ast.Assign) error { divisor := right.Children[1] err = f.Execute(right.Token, dividendRegister, divisor) f.RegisterRegister(asm.MOVE, quotientVariable.Register, x86.RAX) - f.UseVariable(quotientVariable) f.RegisterRegister(asm.MOVE, remainderVariable.Register, x86.RDX) - f.UseVariable(remainderVariable) if isTemporary { f.FreeRegister(dividendRegister) diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index 1143315..6b46991 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -5,6 +5,7 @@ import ( "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" ) // CompileDefinition compiles a variable definition. @@ -34,6 +35,10 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return nil } + if left.Token.Kind == token.Separator && right.Token.Kind == token.Div { + return f.CompileAssignDivision(node.Expression) + } + if !ast.IsFunctionCall(right) { return errors.New(errors.NotImplemented, f.File, node.Expression.Token.Position) } diff --git a/src/core/ExecuteRegisterNumber.go b/src/core/ExecuteRegisterNumber.go index f26f1a3..77a6ffb 100644 --- a/src/core/ExecuteRegisterNumber.go +++ b/src/core/ExecuteRegisterNumber.go @@ -25,7 +25,10 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg f.RegisterNumber(asm.MUL, register, number) case token.Div, token.DivAssign: - f.SaveRegister(x86.RAX) + if register != x86.RAX { + f.SaveRegister(x86.RAX) + } + f.SaveRegister(x86.RDX) tmp := f.NewRegister() f.RegisterNumber(asm.MOVE, tmp, number) diff --git a/src/core/ExecuteRegisterRegister.go b/src/core/ExecuteRegisterRegister.go index ebb6811..fc785b7 100644 --- a/src/core/ExecuteRegisterRegister.go +++ b/src/core/ExecuteRegisterRegister.go @@ -25,7 +25,10 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.R f.RegisterRegister(asm.MUL, register, operand) case token.Div, token.DivAssign: - f.SaveRegister(x86.RAX) + if register != x86.RAX { + f.SaveRegister(x86.RAX) + } + f.SaveRegister(x86.RDX) f.RegisterRegister(asm.DIV, register, operand) diff --git a/tests/programs/div-split.q b/tests/programs/div-split.q index e44e409..847950a 100644 --- a/tests/programs/div-split.q +++ b/tests/programs/div-split.q @@ -1,13 +1,11 @@ main() { - quotient := 0 - remainder := 0 dividend := 256 divisor := 100 - quotient, remainder = 256 / 100 + quotient, remainder := 256 / 100 assert quotient == 2 assert remainder == 56 - + quotient, remainder = dividend / 100 assert quotient == 2 assert remainder == 56 @@ -15,7 +13,7 @@ main() { quotient, remainder = 256 / divisor assert quotient == 2 assert remainder == 56 - + quotient, remainder = dividend / divisor assert quotient == 2 assert remainder == 56 diff --git a/tests/programs/op-assign.q b/tests/programs/op-assign.q deleted file mode 100644 index 85dd9c4..0000000 --- a/tests/programs/op-assign.q +++ /dev/null @@ -1,9 +0,0 @@ -main() { - f(10) -} - -f(new int) { - old := new - new -= 1 - assert new != old -} \ No newline at end of file diff --git a/tests/programs/operator-assign.q b/tests/programs/operator-assign.q new file mode 100644 index 0000000..36809f2 --- /dev/null +++ b/tests/programs/operator-assign.q @@ -0,0 +1,30 @@ +main() { + number(10) + register(10) +} + +number(x int) { + y := x + x -= 1 + assert x < y + x += 1 + assert x == y + x *= 2 + assert x > y + x /= 2 + assert x == y +} + +register(x int) { + y := x + num := 1 + x -= num + assert x < y + x += num + assert x == y + num = 2 + x *= num + assert x > y + x /= num + assert x == y +} \ No newline at end of file diff --git a/tests/programs/variables.q b/tests/programs/variables.q index 8334626..554f438 100644 --- a/tests/programs/variables.q +++ b/tests/programs/variables.q @@ -7,11 +7,11 @@ main() { f := 6 g := 7 - assert a != 0 - assert b != 0 - assert c != 0 - assert d != 0 - assert e != 0 - assert f != 0 - assert g != 0 + assert a == 1 + assert b == 2 + assert c == 3 + assert d == 4 + assert e == 5 + assert f == 6 + assert g == 7 } \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 3097960..da5dfc8 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -14,58 +14,56 @@ import ( var programs = []struct { Name string - Input string - Output string ExitCode int }{ - {"empty", "", "", 0}, - {"assert", "", "", 1}, - {"variables", "", "", 0}, - {"reassign", "", "", 0}, - {"reuse", "", "", 0}, - {"return", "", "", 0}, - {"return-multi", "", "", 0}, - {"math", "", "", 0}, - {"precedence", "", "", 0}, - {"op-assign", "", "", 0}, - {"binary", "", "", 0}, - {"octal", "", "", 0}, - {"hexadecimal", "", "", 0}, - {"const", "", "", 0}, - {"escape-rune", "", "", 0}, - {"escape-string", "", "", 0}, - {"bitwise-and", "", "", 0}, - {"bitwise-or", "", "", 0}, - {"bitwise-xor", "", "", 0}, - {"shift", "", "", 0}, - {"modulo", "", "", 0}, - {"modulo-assign", "", "", 0}, - {"div-split", "", "", 0}, - {"int64", "", "", 0}, - {"negative", "", "", 0}, - {"negation", "", "", 0}, - {"square-sum", "", "", 0}, - {"chained-calls", "", "", 0}, - {"nested-calls", "", "", 0}, - {"param", "", "", 0}, - {"param-multi", "", "", 0}, - {"param-order", "", "", 0}, - {"branch", "", "", 0}, - {"branch-and", "", "", 0}, - {"branch-or", "", "", 0}, - {"branch-both", "", "", 0}, - {"branch-save", "", "", 0}, - {"jump-near", "", "", 0}, - {"switch", "", "", 0}, - {"loop-infinite", "", "", 0}, - {"loop-lifetime", "", "", 0}, - {"loop-for", "", "", 0}, - {"memory-free", "", "", 0}, - {"out-of-memory", "", "", 0}, - {"index-static", "", "", 0}, - {"index-dynamic", "", "", 0}, - {"struct", "", "", 0}, - {"len", "", "", 0}, + {"empty", 0}, + {"assert", 1}, + {"binary", 0}, + {"octal", 0}, + {"hexadecimal", 0}, + {"variables", 0}, + {"reassign", 0}, + {"reuse", 0}, + {"return", 0}, + {"return-multi", 0}, + {"math", 0}, + {"precedence", 0}, + {"operator-assign", 0}, + {"const", 0}, + {"escape-rune", 0}, + {"escape-string", 0}, + {"bitwise-and", 0}, + {"bitwise-or", 0}, + {"bitwise-xor", 0}, + {"shift", 0}, + {"modulo", 0}, + {"modulo-assign", 0}, + {"div-split", 0}, + {"int64", 0}, + {"negative", 0}, + {"negation", 0}, + {"square-sum", 0}, + {"chained-calls", 0}, + {"nested-calls", 0}, + {"param", 0}, + {"param-multi", 0}, + {"param-order", 0}, + {"branch", 0}, + {"branch-and", 0}, + {"branch-or", 0}, + {"branch-both", 0}, + {"branch-save", 0}, + {"jump-near", 0}, + {"switch", 0}, + {"loop-infinite", 0}, + {"loop-lifetime", 0}, + {"loop-for", 0}, + {"memory-free", 0}, + {"out-of-memory", 0}, + {"index-static", 0}, + {"index-dynamic", 0}, + {"struct", 0}, + {"len", 0}, } func TestPrograms(t *testing.T) { @@ -74,12 +72,12 @@ func TestPrograms(t *testing.T) { t.Run(test.Name+"/debug", func(t *testing.T) { config.Optimize(false) - run(t, file, "debug", test.Input, test.Output, test.ExitCode) + run(t, file, "debug", "", "", test.ExitCode) }) t.Run(test.Name+"/release", func(t *testing.T) { config.Optimize(true) - run(t, file, "release", test.Input, test.Output, test.ExitCode) + run(t, file, "release", "", "", test.ExitCode) }) } } From 49214c9a6e17f01ca5ca8b46ce807d1fedbc201d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 22 Feb 2025 13:58:07 +0100 Subject: [PATCH 0823/1012] Added more tests --- src/core/ExecuteRegisterNumber.go | 5 ---- src/core/ExecuteRegisterRegister.go | 5 ---- tests/programs/operator-assign-number.q | 17 +++++++++++++ tests/programs/operator-assign-variable.q | 18 ++++++++++++++ tests/programs/operator-assign.q | 30 ----------------------- tests/programs/operator-number.q | 17 +++++++++++++ tests/programs/operator-variable.q | 18 ++++++++++++++ tests/programs_test.go | 5 +++- 8 files changed, 74 insertions(+), 41 deletions(-) create mode 100644 tests/programs/operator-assign-number.q create mode 100644 tests/programs/operator-assign-variable.q delete mode 100644 tests/programs/operator-assign.q create mode 100644 tests/programs/operator-number.q create mode 100644 tests/programs/operator-variable.q diff --git a/src/core/ExecuteRegisterNumber.go b/src/core/ExecuteRegisterNumber.go index 77a6ffb..0176f82 100644 --- a/src/core/ExecuteRegisterNumber.go +++ b/src/core/ExecuteRegisterNumber.go @@ -25,10 +25,6 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg f.RegisterNumber(asm.MUL, register, number) case token.Div, token.DivAssign: - if register != x86.RAX { - f.SaveRegister(x86.RAX) - } - f.SaveRegister(x86.RDX) tmp := f.NewRegister() f.RegisterNumber(asm.MOVE, tmp, number) @@ -36,7 +32,6 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg f.FreeRegister(tmp) case token.Mod, token.ModAssign: - f.SaveRegister(x86.RAX) f.SaveRegister(x86.RDX) tmp := f.NewRegister() f.RegisterNumber(asm.MOVE, tmp, number) diff --git a/src/core/ExecuteRegisterRegister.go b/src/core/ExecuteRegisterRegister.go index fc785b7..3e699fb 100644 --- a/src/core/ExecuteRegisterRegister.go +++ b/src/core/ExecuteRegisterRegister.go @@ -25,15 +25,10 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.R f.RegisterRegister(asm.MUL, register, operand) case token.Div, token.DivAssign: - if register != x86.RAX { - f.SaveRegister(x86.RAX) - } - f.SaveRegister(x86.RDX) f.RegisterRegister(asm.DIV, register, operand) case token.Mod, token.ModAssign: - f.SaveRegister(x86.RAX) f.SaveRegister(x86.RDX) f.RegisterRegister(asm.MODULO, register, operand) diff --git a/tests/programs/operator-assign-number.q b/tests/programs/operator-assign-number.q new file mode 100644 index 0000000..532e409 --- /dev/null +++ b/tests/programs/operator-assign-number.q @@ -0,0 +1,17 @@ +main() { + f(10) +} + +f(x int) { + y := x + x -= 2 + assert x < y + x += 2 + assert x == y + x *= 2 + assert x > y + x /= 2 + assert x == y + x %= 2 + assert x < 2 +} \ No newline at end of file diff --git a/tests/programs/operator-assign-variable.q b/tests/programs/operator-assign-variable.q new file mode 100644 index 0000000..fe73954 --- /dev/null +++ b/tests/programs/operator-assign-variable.q @@ -0,0 +1,18 @@ +main() { + f(10) +} + +f(x int) { + y := x + num := 2 + x -= num + assert x < y + x += num + assert x == y + x *= num + assert x > y + x /= num + assert x == y + x %= num + assert x < num +} \ No newline at end of file diff --git a/tests/programs/operator-assign.q b/tests/programs/operator-assign.q deleted file mode 100644 index 36809f2..0000000 --- a/tests/programs/operator-assign.q +++ /dev/null @@ -1,30 +0,0 @@ -main() { - number(10) - register(10) -} - -number(x int) { - y := x - x -= 1 - assert x < y - x += 1 - assert x == y - x *= 2 - assert x > y - x /= 2 - assert x == y -} - -register(x int) { - y := x - num := 1 - x -= num - assert x < y - x += num - assert x == y - num = 2 - x *= num - assert x > y - x /= num - assert x == y -} \ No newline at end of file diff --git a/tests/programs/operator-number.q b/tests/programs/operator-number.q new file mode 100644 index 0000000..4bafba8 --- /dev/null +++ b/tests/programs/operator-number.q @@ -0,0 +1,17 @@ +main() { + f(10) +} + +f(x int) { + y := x + x = x - 2 + assert x < y + x = x + 2 + assert x == y + x = x * 2 + assert x > y + x = x / 2 + assert x == y + x = x % 2 + assert x < 2 +} \ No newline at end of file diff --git a/tests/programs/operator-variable.q b/tests/programs/operator-variable.q new file mode 100644 index 0000000..abc4d7c --- /dev/null +++ b/tests/programs/operator-variable.q @@ -0,0 +1,18 @@ +main() { + f(10) +} + +f(x int) { + y := x + num := 2 + x = x - num + assert x < y + x = x + num + assert x == y + x = x * num + assert x > y + x = x / num + assert x == y + x = x % num + assert x < num +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index da5dfc8..6e97a11 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -28,7 +28,10 @@ var programs = []struct { {"return-multi", 0}, {"math", 0}, {"precedence", 0}, - {"operator-assign", 0}, + {"operator-number", 0}, + {"operator-variable", 0}, + {"operator-assign-number", 0}, + {"operator-assign-variable", 0}, {"const", 0}, {"escape-rune", 0}, {"escape-string", 0}, From df782ae1cb2eb69bc2804fc54607b4c462d7a325 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 22 Feb 2025 13:58:07 +0100 Subject: [PATCH 0824/1012] Added more tests --- src/core/ExecuteRegisterNumber.go | 5 ---- src/core/ExecuteRegisterRegister.go | 5 ---- tests/programs/operator-assign-number.q | 17 +++++++++++++ tests/programs/operator-assign-variable.q | 18 ++++++++++++++ tests/programs/operator-assign.q | 30 ----------------------- tests/programs/operator-number.q | 17 +++++++++++++ tests/programs/operator-variable.q | 18 ++++++++++++++ tests/programs_test.go | 5 +++- 8 files changed, 74 insertions(+), 41 deletions(-) create mode 100644 tests/programs/operator-assign-number.q create mode 100644 tests/programs/operator-assign-variable.q delete mode 100644 tests/programs/operator-assign.q create mode 100644 tests/programs/operator-number.q create mode 100644 tests/programs/operator-variable.q diff --git a/src/core/ExecuteRegisterNumber.go b/src/core/ExecuteRegisterNumber.go index 77a6ffb..0176f82 100644 --- a/src/core/ExecuteRegisterNumber.go +++ b/src/core/ExecuteRegisterNumber.go @@ -25,10 +25,6 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg f.RegisterNumber(asm.MUL, register, number) case token.Div, token.DivAssign: - if register != x86.RAX { - f.SaveRegister(x86.RAX) - } - f.SaveRegister(x86.RDX) tmp := f.NewRegister() f.RegisterNumber(asm.MOVE, tmp, number) @@ -36,7 +32,6 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg f.FreeRegister(tmp) case token.Mod, token.ModAssign: - f.SaveRegister(x86.RAX) f.SaveRegister(x86.RDX) tmp := f.NewRegister() f.RegisterNumber(asm.MOVE, tmp, number) diff --git a/src/core/ExecuteRegisterRegister.go b/src/core/ExecuteRegisterRegister.go index fc785b7..3e699fb 100644 --- a/src/core/ExecuteRegisterRegister.go +++ b/src/core/ExecuteRegisterRegister.go @@ -25,15 +25,10 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.R f.RegisterRegister(asm.MUL, register, operand) case token.Div, token.DivAssign: - if register != x86.RAX { - f.SaveRegister(x86.RAX) - } - f.SaveRegister(x86.RDX) f.RegisterRegister(asm.DIV, register, operand) case token.Mod, token.ModAssign: - f.SaveRegister(x86.RAX) f.SaveRegister(x86.RDX) f.RegisterRegister(asm.MODULO, register, operand) diff --git a/tests/programs/operator-assign-number.q b/tests/programs/operator-assign-number.q new file mode 100644 index 0000000..532e409 --- /dev/null +++ b/tests/programs/operator-assign-number.q @@ -0,0 +1,17 @@ +main() { + f(10) +} + +f(x int) { + y := x + x -= 2 + assert x < y + x += 2 + assert x == y + x *= 2 + assert x > y + x /= 2 + assert x == y + x %= 2 + assert x < 2 +} \ No newline at end of file diff --git a/tests/programs/operator-assign-variable.q b/tests/programs/operator-assign-variable.q new file mode 100644 index 0000000..fe73954 --- /dev/null +++ b/tests/programs/operator-assign-variable.q @@ -0,0 +1,18 @@ +main() { + f(10) +} + +f(x int) { + y := x + num := 2 + x -= num + assert x < y + x += num + assert x == y + x *= num + assert x > y + x /= num + assert x == y + x %= num + assert x < num +} \ No newline at end of file diff --git a/tests/programs/operator-assign.q b/tests/programs/operator-assign.q deleted file mode 100644 index 36809f2..0000000 --- a/tests/programs/operator-assign.q +++ /dev/null @@ -1,30 +0,0 @@ -main() { - number(10) - register(10) -} - -number(x int) { - y := x - x -= 1 - assert x < y - x += 1 - assert x == y - x *= 2 - assert x > y - x /= 2 - assert x == y -} - -register(x int) { - y := x - num := 1 - x -= num - assert x < y - x += num - assert x == y - num = 2 - x *= num - assert x > y - x /= num - assert x == y -} \ No newline at end of file diff --git a/tests/programs/operator-number.q b/tests/programs/operator-number.q new file mode 100644 index 0000000..4bafba8 --- /dev/null +++ b/tests/programs/operator-number.q @@ -0,0 +1,17 @@ +main() { + f(10) +} + +f(x int) { + y := x + x = x - 2 + assert x < y + x = x + 2 + assert x == y + x = x * 2 + assert x > y + x = x / 2 + assert x == y + x = x % 2 + assert x < 2 +} \ No newline at end of file diff --git a/tests/programs/operator-variable.q b/tests/programs/operator-variable.q new file mode 100644 index 0000000..abc4d7c --- /dev/null +++ b/tests/programs/operator-variable.q @@ -0,0 +1,18 @@ +main() { + f(10) +} + +f(x int) { + y := x + num := 2 + x = x - num + assert x < y + x = x + num + assert x == y + x = x * num + assert x > y + x = x / num + assert x == y + x = x % num + assert x < num +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index da5dfc8..6e97a11 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -28,7 +28,10 @@ var programs = []struct { {"return-multi", 0}, {"math", 0}, {"precedence", 0}, - {"operator-assign", 0}, + {"operator-number", 0}, + {"operator-variable", 0}, + {"operator-assign-number", 0}, + {"operator-assign-variable", 0}, {"const", 0}, {"escape-rune", 0}, {"escape-string", 0}, From 4587503a57f262503ec5f8a2496b819e1fe56b25 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 22 Feb 2025 18:02:50 +0100 Subject: [PATCH 0825/1012] Removed log from the standard library --- examples/array/array.q | 17 ++++++++--------- examples/collatz/collatz.q | 3 +-- examples/factorial/factorial.q | 4 ++-- examples/fibonacci/fibonacci.q | 4 ++-- examples/fizzbuzz/fizzbuzz.q | 3 +-- examples/gcd/gcd.q | 4 ++-- examples/itoa/itoa.q | 4 ++-- examples/prime/prime.q | 3 +-- lib/{log/number.q => io/fmt.q} | 0 lib/io/io.q | 18 ------------------ lib/io/std.q | 19 +++++++++++++++++++ 11 files changed, 38 insertions(+), 41 deletions(-) rename lib/{log/number.q => io/fmt.q} (100%) create mode 100644 lib/io/std.q diff --git a/examples/array/array.q b/examples/array/array.q index 0000ad3..c81a7b0 100644 --- a/examples/array/array.q +++ b/examples/array/array.q @@ -2,13 +2,12 @@ import io import mem main() { - length := 5 - address := mem.alloc(length) - address[0] = 'H' - address[1] = 'e' - address[2] = 'l' - address[3] = 'l' - address[4] = 'o' - io.write(1, address) - mem.free(address) + buffer := mem.alloc(5) + buffer[0] = 'H' + buffer[1] = 'e' + buffer[2] = 'l' + buffer[3] = 'l' + buffer[4] = 'o' + io.write(1, buffer) + mem.free(buffer) } \ No newline at end of file diff --git a/examples/collatz/collatz.q b/examples/collatz/collatz.q index dab2869..00048d6 100644 --- a/examples/collatz/collatz.q +++ b/examples/collatz/collatz.q @@ -1,5 +1,4 @@ import io -import log main() { collatz(12) @@ -13,7 +12,7 @@ collatz(x int) { x = 3 * x + 1 } - log.number(x) + io.number(x) if x == 1 { return diff --git a/examples/factorial/factorial.q b/examples/factorial/factorial.q index e34436f..07390b5 100644 --- a/examples/factorial/factorial.q +++ b/examples/factorial/factorial.q @@ -1,7 +1,7 @@ -import log +import io main() { - log.number(factorial(5)) + io.number(factorial(5)) } factorial(x int) -> int { diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q index 4d0f940..d189253 100644 --- a/examples/fibonacci/fibonacci.q +++ b/examples/fibonacci/fibonacci.q @@ -1,7 +1,7 @@ -import log +import io main() { - log.number(fibonacci(10)) + io.number(fibonacci(10)) } fibonacci(x int) -> int { diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q index e626e7f..e4986d1 100644 --- a/examples/fizzbuzz/fizzbuzz.q +++ b/examples/fizzbuzz/fizzbuzz.q @@ -1,5 +1,4 @@ import io -import log main() { fizzbuzz(15) @@ -13,7 +12,7 @@ fizzbuzz(n int) { x % 15 == 0 { io.out("FizzBuzz") } x % 5 == 0 { io.out("Buzz") } x % 3 == 0 { io.out("Fizz") } - _ { log.number(x) } + _ { io.number(x) } } x += 1 diff --git a/examples/gcd/gcd.q b/examples/gcd/gcd.q index c9382b2..9b6ece1 100644 --- a/examples/gcd/gcd.q +++ b/examples/gcd/gcd.q @@ -1,7 +1,7 @@ -import log +import io main() { - log.number(gcd(1071, 462)) + io.number(gcd(1071, 462)) } gcd(a int, b int) -> int { diff --git a/examples/itoa/itoa.q b/examples/itoa/itoa.q index ee4d224..72d3599 100644 --- a/examples/itoa/itoa.q +++ b/examples/itoa/itoa.q @@ -1,5 +1,5 @@ -import log +import io main() { - log.number(9223372036854775807) + io.number(9223372036854775807) } \ No newline at end of file diff --git a/examples/prime/prime.q b/examples/prime/prime.q index fdd1bcf..7d639a2 100644 --- a/examples/prime/prime.q +++ b/examples/prime/prime.q @@ -1,5 +1,4 @@ import io -import log main() { n := 100 @@ -15,7 +14,7 @@ main() { io.out(" ") } - log.number(i) + io.number(i) } i += 1 diff --git a/lib/log/number.q b/lib/io/fmt.q similarity index 100% rename from lib/log/number.q rename to lib/io/fmt.q diff --git a/lib/io/io.q b/lib/io/io.q index 5c66a29..760c9db 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -1,23 +1,5 @@ import sys -const std { - in 0 - out 1 - err 2 -} - -in(buffer []byte) -> int { - return sys.read(std.in, buffer, len(buffer)) -} - -out(buffer []byte) -> int { - return sys.write(std.out, buffer, len(buffer)) -} - -error(buffer []byte) -> int { - return sys.write(std.err, buffer, len(buffer)) -} - read(fd int, buffer []byte) -> int { return sys.read(fd, buffer, len(buffer)) } diff --git a/lib/io/std.q b/lib/io/std.q new file mode 100644 index 0000000..c74cb52 --- /dev/null +++ b/lib/io/std.q @@ -0,0 +1,19 @@ +import sys + +in(buffer []byte) -> int { + return sys.read(std.in, buffer, len(buffer)) +} + +out(buffer []byte) -> int { + return sys.write(std.out, buffer, len(buffer)) +} + +error(buffer []byte) -> int { + return sys.write(std.err, buffer, len(buffer)) +} + +const std { + in 0 + out 1 + err 2 +} \ No newline at end of file From 467fbd9725760f7f5107fd05f9be70ebd71c4a7e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 22 Feb 2025 18:02:50 +0100 Subject: [PATCH 0826/1012] Removed log from the standard library --- examples/array/array.q | 17 ++++++++--------- examples/collatz/collatz.q | 3 +-- examples/factorial/factorial.q | 4 ++-- examples/fibonacci/fibonacci.q | 4 ++-- examples/fizzbuzz/fizzbuzz.q | 3 +-- examples/gcd/gcd.q | 4 ++-- examples/itoa/itoa.q | 4 ++-- examples/prime/prime.q | 3 +-- lib/{log/number.q => io/fmt.q} | 0 lib/io/io.q | 18 ------------------ lib/io/std.q | 19 +++++++++++++++++++ 11 files changed, 38 insertions(+), 41 deletions(-) rename lib/{log/number.q => io/fmt.q} (100%) create mode 100644 lib/io/std.q diff --git a/examples/array/array.q b/examples/array/array.q index 0000ad3..c81a7b0 100644 --- a/examples/array/array.q +++ b/examples/array/array.q @@ -2,13 +2,12 @@ import io import mem main() { - length := 5 - address := mem.alloc(length) - address[0] = 'H' - address[1] = 'e' - address[2] = 'l' - address[3] = 'l' - address[4] = 'o' - io.write(1, address) - mem.free(address) + buffer := mem.alloc(5) + buffer[0] = 'H' + buffer[1] = 'e' + buffer[2] = 'l' + buffer[3] = 'l' + buffer[4] = 'o' + io.write(1, buffer) + mem.free(buffer) } \ No newline at end of file diff --git a/examples/collatz/collatz.q b/examples/collatz/collatz.q index dab2869..00048d6 100644 --- a/examples/collatz/collatz.q +++ b/examples/collatz/collatz.q @@ -1,5 +1,4 @@ import io -import log main() { collatz(12) @@ -13,7 +12,7 @@ collatz(x int) { x = 3 * x + 1 } - log.number(x) + io.number(x) if x == 1 { return diff --git a/examples/factorial/factorial.q b/examples/factorial/factorial.q index e34436f..07390b5 100644 --- a/examples/factorial/factorial.q +++ b/examples/factorial/factorial.q @@ -1,7 +1,7 @@ -import log +import io main() { - log.number(factorial(5)) + io.number(factorial(5)) } factorial(x int) -> int { diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q index 4d0f940..d189253 100644 --- a/examples/fibonacci/fibonacci.q +++ b/examples/fibonacci/fibonacci.q @@ -1,7 +1,7 @@ -import log +import io main() { - log.number(fibonacci(10)) + io.number(fibonacci(10)) } fibonacci(x int) -> int { diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q index e626e7f..e4986d1 100644 --- a/examples/fizzbuzz/fizzbuzz.q +++ b/examples/fizzbuzz/fizzbuzz.q @@ -1,5 +1,4 @@ import io -import log main() { fizzbuzz(15) @@ -13,7 +12,7 @@ fizzbuzz(n int) { x % 15 == 0 { io.out("FizzBuzz") } x % 5 == 0 { io.out("Buzz") } x % 3 == 0 { io.out("Fizz") } - _ { log.number(x) } + _ { io.number(x) } } x += 1 diff --git a/examples/gcd/gcd.q b/examples/gcd/gcd.q index c9382b2..9b6ece1 100644 --- a/examples/gcd/gcd.q +++ b/examples/gcd/gcd.q @@ -1,7 +1,7 @@ -import log +import io main() { - log.number(gcd(1071, 462)) + io.number(gcd(1071, 462)) } gcd(a int, b int) -> int { diff --git a/examples/itoa/itoa.q b/examples/itoa/itoa.q index ee4d224..72d3599 100644 --- a/examples/itoa/itoa.q +++ b/examples/itoa/itoa.q @@ -1,5 +1,5 @@ -import log +import io main() { - log.number(9223372036854775807) + io.number(9223372036854775807) } \ No newline at end of file diff --git a/examples/prime/prime.q b/examples/prime/prime.q index fdd1bcf..7d639a2 100644 --- a/examples/prime/prime.q +++ b/examples/prime/prime.q @@ -1,5 +1,4 @@ import io -import log main() { n := 100 @@ -15,7 +14,7 @@ main() { io.out(" ") } - log.number(i) + io.number(i) } i += 1 diff --git a/lib/log/number.q b/lib/io/fmt.q similarity index 100% rename from lib/log/number.q rename to lib/io/fmt.q diff --git a/lib/io/io.q b/lib/io/io.q index 5c66a29..760c9db 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -1,23 +1,5 @@ import sys -const std { - in 0 - out 1 - err 2 -} - -in(buffer []byte) -> int { - return sys.read(std.in, buffer, len(buffer)) -} - -out(buffer []byte) -> int { - return sys.write(std.out, buffer, len(buffer)) -} - -error(buffer []byte) -> int { - return sys.write(std.err, buffer, len(buffer)) -} - read(fd int, buffer []byte) -> int { return sys.read(fd, buffer, len(buffer)) } diff --git a/lib/io/std.q b/lib/io/std.q new file mode 100644 index 0000000..c74cb52 --- /dev/null +++ b/lib/io/std.q @@ -0,0 +1,19 @@ +import sys + +in(buffer []byte) -> int { + return sys.read(std.in, buffer, len(buffer)) +} + +out(buffer []byte) -> int { + return sys.write(std.out, buffer, len(buffer)) +} + +error(buffer []byte) -> int { + return sys.write(std.err, buffer, len(buffer)) +} + +const std { + in 0 + out 1 + err 2 +} \ No newline at end of file From 06c662589e5126d0fc784ed34fad3ed376b0390f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 25 Feb 2025 17:06:46 +0100 Subject: [PATCH 0827/1012] Changed test order --- tests/programs/bitwise-and.q | 28 ++++++++++++++-------------- tests/programs/bitwise-or.q | 28 ++++++++++++++-------------- tests/programs/bitwise-xor.q | 28 ++++++++++++++-------------- tests/programs_test.go | 6 +++--- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/tests/programs/bitwise-and.q b/tests/programs/bitwise-and.q index 34df541..76eb101 100644 --- a/tests/programs/bitwise-and.q +++ b/tests/programs/bitwise-and.q @@ -1,16 +1,16 @@ main() { - assert 0 & 0 == 0 - assert 0 & 1 == 0 - assert 1 & 0 == 0 - assert 1 & 1 == 1 - assert 1 & 2 == 0 - assert 1 & 3 == 1 - assert 2 & 0 == 0 - assert 2 & 1 == 0 - assert 2 & 2 == 2 - assert 2 & 3 == 2 - assert 3 & 0 == 0 - assert 3 & 1 == 1 - assert 3 & 2 == 2 - assert 3 & 3 == 3 + assert 0b00 & 0b00 == 0b00 + assert 0b00 & 0b01 == 0b00 + assert 0b01 & 0b00 == 0b00 + assert 0b01 & 0b01 == 0b01 + assert 0b01 & 0b10 == 0b00 + assert 0b01 & 0b11 == 0b01 + assert 0b10 & 0b00 == 0b00 + assert 0b10 & 0b01 == 0b00 + assert 0b10 & 0b10 == 0b10 + assert 0b10 & 0b11 == 0b10 + assert 0b11 & 0b00 == 0b00 + assert 0b11 & 0b01 == 0b01 + assert 0b11 & 0b10 == 0b10 + assert 0b11 & 0b11 == 0b11 } \ No newline at end of file diff --git a/tests/programs/bitwise-or.q b/tests/programs/bitwise-or.q index 12de2d2..a537b7d 100644 --- a/tests/programs/bitwise-or.q +++ b/tests/programs/bitwise-or.q @@ -1,16 +1,16 @@ main() { - assert 0 | 0 == 0 - assert 0 | 1 == 1 - assert 1 | 0 == 1 - assert 1 | 1 == 1 - assert 1 | 2 == 3 - assert 1 | 3 == 3 - assert 2 | 0 == 2 - assert 2 | 1 == 3 - assert 2 | 2 == 2 - assert 2 | 3 == 3 - assert 3 | 0 == 3 - assert 3 | 1 == 3 - assert 3 | 2 == 3 - assert 3 | 3 == 3 + assert 0b00 | 0b00 == 0b00 + assert 0b00 | 0b01 == 0b01 + assert 0b01 | 0b00 == 0b01 + assert 0b01 | 0b01 == 0b01 + assert 0b01 | 0b10 == 0b11 + assert 0b01 | 0b11 == 0b11 + assert 0b10 | 0b00 == 0b10 + assert 0b10 | 0b01 == 0b11 + assert 0b10 | 0b10 == 0b10 + assert 0b10 | 0b11 == 0b11 + assert 0b11 | 0b00 == 0b11 + assert 0b11 | 0b01 == 0b11 + assert 0b11 | 0b10 == 0b11 + assert 0b11 | 0b11 == 0b11 } \ No newline at end of file diff --git a/tests/programs/bitwise-xor.q b/tests/programs/bitwise-xor.q index e18f5e8..02d4ca6 100644 --- a/tests/programs/bitwise-xor.q +++ b/tests/programs/bitwise-xor.q @@ -1,16 +1,16 @@ main() { - assert 0 ^ 0 == 0 - assert 0 ^ 1 == 1 - assert 1 ^ 0 == 1 - assert 1 ^ 1 == 0 - assert 1 ^ 2 == 3 - assert 1 ^ 3 == 2 - assert 2 ^ 0 == 2 - assert 2 ^ 1 == 3 - assert 2 ^ 2 == 0 - assert 2 ^ 3 == 1 - assert 3 ^ 0 == 3 - assert 3 ^ 1 == 2 - assert 3 ^ 2 == 1 - assert 3 ^ 3 == 0 + assert 0b00 ^ 0b00 == 0b00 + assert 0b00 ^ 0b01 == 0b01 + assert 0b01 ^ 0b00 == 0b01 + assert 0b01 ^ 0b01 == 0b00 + assert 0b01 ^ 0b10 == 0b11 + assert 0b01 ^ 0b11 == 0b10 + assert 0b10 ^ 0b00 == 0b10 + assert 0b10 ^ 0b01 == 0b11 + assert 0b10 ^ 0b10 == 0b00 + assert 0b10 ^ 0b11 == 0b01 + assert 0b11 ^ 0b00 == 0b11 + assert 0b11 ^ 0b01 == 0b10 + assert 0b11 ^ 0b10 == 0b01 + assert 0b11 ^ 0b11 == 0b00 } \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 6e97a11..4dc257e 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -32,9 +32,6 @@ var programs = []struct { {"operator-variable", 0}, {"operator-assign-number", 0}, {"operator-assign-variable", 0}, - {"const", 0}, - {"escape-rune", 0}, - {"escape-string", 0}, {"bitwise-and", 0}, {"bitwise-or", 0}, {"bitwise-xor", 0}, @@ -42,6 +39,9 @@ var programs = []struct { {"modulo", 0}, {"modulo-assign", 0}, {"div-split", 0}, + {"const", 0}, + {"escape-rune", 0}, + {"escape-string", 0}, {"int64", 0}, {"negative", 0}, {"negation", 0}, From 98edfb19fd2b991cbdf08215b2e61e780f1fa1c6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 25 Feb 2025 17:06:46 +0100 Subject: [PATCH 0828/1012] Changed test order --- tests/programs/bitwise-and.q | 28 ++++++++++++++-------------- tests/programs/bitwise-or.q | 28 ++++++++++++++-------------- tests/programs/bitwise-xor.q | 28 ++++++++++++++-------------- tests/programs_test.go | 6 +++--- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/tests/programs/bitwise-and.q b/tests/programs/bitwise-and.q index 34df541..76eb101 100644 --- a/tests/programs/bitwise-and.q +++ b/tests/programs/bitwise-and.q @@ -1,16 +1,16 @@ main() { - assert 0 & 0 == 0 - assert 0 & 1 == 0 - assert 1 & 0 == 0 - assert 1 & 1 == 1 - assert 1 & 2 == 0 - assert 1 & 3 == 1 - assert 2 & 0 == 0 - assert 2 & 1 == 0 - assert 2 & 2 == 2 - assert 2 & 3 == 2 - assert 3 & 0 == 0 - assert 3 & 1 == 1 - assert 3 & 2 == 2 - assert 3 & 3 == 3 + assert 0b00 & 0b00 == 0b00 + assert 0b00 & 0b01 == 0b00 + assert 0b01 & 0b00 == 0b00 + assert 0b01 & 0b01 == 0b01 + assert 0b01 & 0b10 == 0b00 + assert 0b01 & 0b11 == 0b01 + assert 0b10 & 0b00 == 0b00 + assert 0b10 & 0b01 == 0b00 + assert 0b10 & 0b10 == 0b10 + assert 0b10 & 0b11 == 0b10 + assert 0b11 & 0b00 == 0b00 + assert 0b11 & 0b01 == 0b01 + assert 0b11 & 0b10 == 0b10 + assert 0b11 & 0b11 == 0b11 } \ No newline at end of file diff --git a/tests/programs/bitwise-or.q b/tests/programs/bitwise-or.q index 12de2d2..a537b7d 100644 --- a/tests/programs/bitwise-or.q +++ b/tests/programs/bitwise-or.q @@ -1,16 +1,16 @@ main() { - assert 0 | 0 == 0 - assert 0 | 1 == 1 - assert 1 | 0 == 1 - assert 1 | 1 == 1 - assert 1 | 2 == 3 - assert 1 | 3 == 3 - assert 2 | 0 == 2 - assert 2 | 1 == 3 - assert 2 | 2 == 2 - assert 2 | 3 == 3 - assert 3 | 0 == 3 - assert 3 | 1 == 3 - assert 3 | 2 == 3 - assert 3 | 3 == 3 + assert 0b00 | 0b00 == 0b00 + assert 0b00 | 0b01 == 0b01 + assert 0b01 | 0b00 == 0b01 + assert 0b01 | 0b01 == 0b01 + assert 0b01 | 0b10 == 0b11 + assert 0b01 | 0b11 == 0b11 + assert 0b10 | 0b00 == 0b10 + assert 0b10 | 0b01 == 0b11 + assert 0b10 | 0b10 == 0b10 + assert 0b10 | 0b11 == 0b11 + assert 0b11 | 0b00 == 0b11 + assert 0b11 | 0b01 == 0b11 + assert 0b11 | 0b10 == 0b11 + assert 0b11 | 0b11 == 0b11 } \ No newline at end of file diff --git a/tests/programs/bitwise-xor.q b/tests/programs/bitwise-xor.q index e18f5e8..02d4ca6 100644 --- a/tests/programs/bitwise-xor.q +++ b/tests/programs/bitwise-xor.q @@ -1,16 +1,16 @@ main() { - assert 0 ^ 0 == 0 - assert 0 ^ 1 == 1 - assert 1 ^ 0 == 1 - assert 1 ^ 1 == 0 - assert 1 ^ 2 == 3 - assert 1 ^ 3 == 2 - assert 2 ^ 0 == 2 - assert 2 ^ 1 == 3 - assert 2 ^ 2 == 0 - assert 2 ^ 3 == 1 - assert 3 ^ 0 == 3 - assert 3 ^ 1 == 2 - assert 3 ^ 2 == 1 - assert 3 ^ 3 == 0 + assert 0b00 ^ 0b00 == 0b00 + assert 0b00 ^ 0b01 == 0b01 + assert 0b01 ^ 0b00 == 0b01 + assert 0b01 ^ 0b01 == 0b00 + assert 0b01 ^ 0b10 == 0b11 + assert 0b01 ^ 0b11 == 0b10 + assert 0b10 ^ 0b00 == 0b10 + assert 0b10 ^ 0b01 == 0b11 + assert 0b10 ^ 0b10 == 0b00 + assert 0b10 ^ 0b11 == 0b01 + assert 0b11 ^ 0b00 == 0b11 + assert 0b11 ^ 0b01 == 0b10 + assert 0b11 ^ 0b10 == 0b01 + assert 0b11 ^ 0b11 == 0b00 } \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 6e97a11..4dc257e 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -32,9 +32,6 @@ var programs = []struct { {"operator-variable", 0}, {"operator-assign-number", 0}, {"operator-assign-variable", 0}, - {"const", 0}, - {"escape-rune", 0}, - {"escape-string", 0}, {"bitwise-and", 0}, {"bitwise-or", 0}, {"bitwise-xor", 0}, @@ -42,6 +39,9 @@ var programs = []struct { {"modulo", 0}, {"modulo-assign", 0}, {"div-split", 0}, + {"const", 0}, + {"escape-rune", 0}, + {"escape-string", 0}, {"int64", 0}, {"negative", 0}, {"negation", 0}, From 1f0db51a9531043c3503f2b01365671fb15b0a8d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 25 Feb 2025 17:16:09 +0100 Subject: [PATCH 0829/1012] Updated module path --- docs/readme.md | 6 +++--- go.mod | 6 +++--- go.sum | 8 ++++---- main.go | 2 +- src/arm/Registers.go | 2 +- src/arm/Registers_test.go | 4 ++-- src/asm/Assembler.go | 2 +- src/asm/CanSkip.go | 2 +- src/asm/Memory.go | 2 +- src/asm/MemoryRegister.go | 2 +- src/asm/Register.go | 2 +- src/asm/RegisterLabel.go | 2 +- src/asm/RegisterNumber.go | 2 +- src/asm/RegisterRegister.go | 2 +- src/asmc/Finalize.go | 6 +++--- src/asmc/call.go | 4 ++-- src/asmc/codeOffset.go | 10 +++++----- src/asmc/compile.go | 4 ++-- src/asmc/compiler.go | 2 +- src/asmc/dllCall.go | 4 ++-- src/asmc/jump.go | 4 ++-- src/asmc/load.go | 4 ++-- src/asmc/move.go | 6 +++--- src/asmc/resolvePointers.go | 6 +++--- src/asmc/store.go | 6 +++--- src/ast/AST.go | 2 +- src/ast/Count.go | 2 +- src/ast/Parse.go | 4 ++-- src/ast/ast_test.go | 8 ++++---- src/ast/block.go | 6 +++--- src/ast/eachInstruction.go | 2 +- src/ast/helpers.go | 4 ++-- src/ast/parseCases.go | 6 +++--- src/ast/parseInstruction.go | 8 ++++---- src/ast/parseKeyword.go | 8 ++++---- src/build/Executable.go | 2 +- src/build/Run.go | 4 ++-- src/build/build_test.go | 6 +++--- src/cli/Build.go | 4 ++-- src/cli/Main_test.go | 6 +++--- src/cli/System.go | 2 +- src/compiler/Compile.go | 8 ++++---- src/compiler/CompileFunctions.go | 2 +- src/compiler/PrintInstructions.go | 2 +- src/compiler/PrintStatistics.go | 2 +- src/compiler/Result.go | 4 ++-- src/compiler/Write.go | 10 +++++----- src/compiler/eachFunction.go | 4 ++-- src/compiler/finalize.go | 10 +++++----- src/core/ArrayElementToRegister.go | 12 ++++++------ src/core/CallExtern.go | 10 +++++----- src/core/CallSafe.go | 4 ++-- src/core/CallToRegister.go | 8 ++++---- src/core/Compare.go | 8 ++++---- src/core/CompileAST.go | 2 +- src/core/CompileASTNode.go | 2 +- src/core/CompileAssert.go | 4 ++-- src/core/CompileAssign.go | 10 +++++----- src/core/CompileAssignArray.go | 8 ++++---- src/core/CompileAssignDivision.go | 14 +++++++------- src/core/CompileAssignField.go | 8 ++++---- src/core/CompileCall.go | 6 +++--- src/core/CompileCondition.go | 4 ++-- src/core/CompileDefinition.go | 10 +++++----- src/core/CompileDelete.go | 8 ++++---- src/core/CompileFor.go | 10 +++++----- src/core/CompileIf.go | 4 ++-- src/core/CompileLen.go | 8 ++++---- src/core/CompileLoop.go | 4 ++-- src/core/CompileMemoryStore.go | 6 +++--- src/core/CompileNew.go | 8 ++++---- src/core/CompileReturn.go | 4 ++-- src/core/CompileSwitch.go | 4 ++-- src/core/CompileSyscall.go | 2 +- src/core/CompileTokens.go | 4 ++-- src/core/Constant.go | 4 ++-- src/core/Define.go | 8 ++++---- src/core/Environment.go | 4 ++-- src/core/Evaluate.go | 10 +++++----- src/core/Execute.go | 8 ++++---- src/core/ExecuteLeaf.go | 6 +++--- src/core/ExecuteRegister.go | 8 ++++---- src/core/ExecuteRegisterNumber.go | 10 +++++----- src/core/ExecuteRegisterRegister.go | 10 +++++----- src/core/ExpressionToMemory.go | 10 +++++----- src/core/ExpressionToRegister.go | 12 ++++++------ src/core/ExpressionsToRegisters.go | 10 +++++----- src/core/Fold.go | 6 +++--- src/core/Function.go | 10 +++++----- src/core/Identifier.go | 2 +- src/core/JumpIfFalse.go | 4 ++-- src/core/JumpIfTrue.go | 4 ++-- src/core/NewFunction.go | 12 ++++++------ src/core/Parameter.go | 4 ++-- src/core/PeriodToRegister.go | 10 +++++----- src/core/PrintInstructions.go | 4 ++-- src/core/ResolveTypes.go | 10 +++++----- src/core/ToNumber.go | 6 +++--- src/core/TokenToRegister.go | 10 +++++----- src/core/UsesRegister.go | 8 ++++---- src/cpu/Register_test.go | 4 ++-- src/cpu/State_test.go | 4 ++-- src/data/Data_test.go | 4 ++-- src/elf/ELF.go | 4 ++-- src/elf/ELF_test.go | 2 +- src/errors/Error.go | 4 ++-- src/expression/Expression.go | 2 +- src/expression/Expression_test.go | 6 +++--- src/expression/List.go | 2 +- src/expression/Operator.go | 2 +- src/expression/Parse.go | 2 +- src/expression/bench_test.go | 4 ++-- src/fs/File.go | 2 +- src/fs/Import.go | 2 +- src/fs/Walk_test.go | 4 ++-- src/fs/bench_test.go | 4 ++-- src/macho/MachO.go | 4 ++-- src/macho/MachO_test.go | 2 +- src/pe/EXE.go | 6 +++--- src/pe/EXE_test.go | 2 +- src/pe/importLibraries.go | 2 +- src/register/AddLabel.go | 2 +- src/register/DLLCall.go | 2 +- src/register/FreeRegister.go | 2 +- src/register/Jump.go | 2 +- src/register/Machine.go | 6 +++--- src/register/MemoryLabel.go | 2 +- src/register/MemoryNumber.go | 2 +- src/register/MemoryRegister.go | 4 ++-- src/register/NewRegister.go | 2 +- src/register/Number.go | 2 +- src/register/Register.go | 4 ++-- src/register/RegisterIsUsed.go | 2 +- src/register/RegisterLabel.go | 4 ++-- src/register/RegisterNumber.go | 6 +++--- src/register/RegisterRegister.go | 4 ++-- src/register/SaveRegister.go | 4 ++-- src/register/UseRegister.go | 2 +- src/register/postInstruction.go | 2 +- src/riscv/Registers.go | 2 +- src/riscv/Registers_test.go | 4 ++-- src/scanner/Scan.go | 8 ++++---- src/scanner/Scanner.go | 6 +++--- src/scanner/queueDirectory.go | 4 ++-- src/scanner/scanConst.go | 8 ++++---- src/scanner/scanExtern.go | 6 +++--- src/scanner/scanFile.go | 6 +++--- src/scanner/scanFunction.go | 6 +++--- src/scanner/scanFunctionSignature.go | 8 ++++---- src/scanner/scanImport.go | 8 ++++---- src/scanner/scanStruct.go | 8 ++++---- src/scope/Scope.go | 2 +- src/scope/Stack.go | 6 +++--- src/scope/Variable.go | 4 ++-- src/sizeof/Signed_test.go | 4 ++-- src/sizeof/Unsigned_test.go | 4 ++-- src/token/Count_test.go | 4 ++-- src/token/List_test.go | 4 ++-- src/token/Token_test.go | 4 ++-- src/token/Tokenize_test.go | 4 ++-- src/token/bench_test.go | 2 +- src/types/Field.go | 2 +- src/types/types_test.go | 4 ++-- src/x86/Add.go | 2 +- src/x86/Add_test.go | 6 +++--- src/x86/And.go | 2 +- src/x86/And_test.go | 6 +++--- src/x86/Compare.go | 2 +- src/x86/Compare_test.go | 6 +++--- src/x86/Div.go | 2 +- src/x86/Div_test.go | 6 +++--- src/x86/Jump_test.go | 4 ++-- src/x86/Load.go | 2 +- src/x86/LoadDynamic.go | 2 +- src/x86/LoadDynamic_test.go | 6 +++--- src/x86/Load_test.go | 6 +++--- src/x86/ModRM_test.go | 4 ++-- src/x86/Move.go | 4 ++-- src/x86/Move_test.go | 6 +++--- src/x86/Mul.go | 2 +- src/x86/Mul_test.go | 6 +++--- src/x86/Negate.go | 2 +- src/x86/Negate_test.go | 6 +++--- src/x86/Or.go | 2 +- src/x86/Or_test.go | 6 +++--- src/x86/Pop.go | 2 +- src/x86/Pop_test.go | 6 +++--- src/x86/Push.go | 4 ++-- src/x86/Push_test.go | 6 +++--- src/x86/REX_test.go | 4 ++-- src/x86/Registers.go | 2 +- src/x86/Registers_test.go | 4 ++-- src/x86/SIB_test.go | 4 ++-- src/x86/Shift.go | 2 +- src/x86/Shift_test.go | 6 +++--- src/x86/Store.go | 2 +- src/x86/StoreDynamic.go | 2 +- src/x86/StoreDynamic_test.go | 6 +++--- src/x86/Store_test.go | 6 +++--- src/x86/Sub.go | 2 +- src/x86/Sub_test.go | 6 +++--- src/x86/Xor.go | 2 +- src/x86/Xor_test.go | 6 +++--- src/x86/encode.go | 2 +- src/x86/encodeNum.go | 4 ++-- src/x86/memoryAccess.go | 2 +- src/x86/memoryAccessDynamic.go | 2 +- src/x86/x86_test.go | 4 ++-- tests/errors_test.go | 6 +++--- tests/examples_test.go | 6 +++--- tests/programs_test.go | 6 +++--- 211 files changed, 506 insertions(+), 506 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 842942e..ac727ad 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -12,7 +12,7 @@ A programming language that compiles down to machine code. ## Installation ```shell -git clone https://git.akyoto.dev/cli/q +git clone https://git.urbach.dev/cli/q cd q go build ``` @@ -48,13 +48,13 @@ q build examples/hello --os windows ## Status `q` is under heavy development and not ready for production yet. -Feel free to [get in touch](https://akyoto.dev/contact) if you are interested in helping out. +Feel free to [get in touch](https://urbach.dev/contact) if you are interested in helping out. The biggest obstacle right now is the lack of funding. If you want to help out financially you can [donate towards the project](https://en.liberapay.com/akyoto). ## License -Please see the [license documentation](https://akyoto.dev/license). +Please see the [license documentation](https://urbach.dev/license). ## Copyright diff --git a/go.mod b/go.mod index baf1b38..c2ac09c 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ -module git.akyoto.dev/cli/q +module git.urbach.dev/cli/q go 1.24 require ( - git.akyoto.dev/go/assert v0.1.3 - git.akyoto.dev/go/color v0.1.3 + git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf + git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d ) require golang.org/x/sys v0.30.0 // indirect diff --git a/go.sum b/go.sum index 236fc35..51f26c6 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ -git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= -git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= -git.akyoto.dev/go/color v0.1.3 h1:kqOVYaPJJDHi8qEwTZkZecQaUENBToaLmiiV1Gg++GM= -git.akyoto.dev/go/color v0.1.3/go.mod h1:e00cRnX0fzFyIYEpAA7dCR/Hlk0/2YpXpVQaMT5Zoss= +git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf h1:BQWa5GKNUsA5CSUa/+UlFWYCEVe3IDDKRbVqBLK0mAE= +git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf/go.mod h1:y9jGII9JFiF1HNIju0u87OyPCt82xKCtqnAFyEreCDo= +git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d h1:j1ARCrjUYE/c1STH/s6UYGQoOXAkxXll4AFhTb8P2GE= +git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d/go.mod h1:UE8tQTrlWeVPKhfPZ9G5QrDFRhL9EoPazcDgfgd5Xsk= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/main.go b/main.go index 60febb9..99f6f29 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,7 @@ package main import ( "os" - "git.akyoto.dev/cli/q/src/cli" + "git.urbach.dev/cli/q/src/cli" ) func main() { diff --git a/src/arm/Registers.go b/src/arm/Registers.go index f9ca6b6..86d78d2 100644 --- a/src/arm/Registers.go +++ b/src/arm/Registers.go @@ -1,6 +1,6 @@ package arm -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" const ( X0 cpu.Register = iota diff --git a/src/arm/Registers_test.go b/src/arm/Registers_test.go index 5c44c94..50cdae9 100644 --- a/src/arm/Registers_test.go +++ b/src/arm/Registers_test.go @@ -3,8 +3,8 @@ package arm_test import ( "testing" - "git.akyoto.dev/cli/q/src/arm" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/go/assert" ) func TestRegisters(t *testing.T) { diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index 3f3cb88..8e32655 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -3,7 +3,7 @@ package asm import ( "maps" - "git.akyoto.dev/cli/q/src/data" + "git.urbach.dev/cli/q/src/data" ) // Assembler contains a list of instructions. diff --git a/src/asm/CanSkip.go b/src/asm/CanSkip.go index c707990..b0999e0 100644 --- a/src/asm/CanSkip.go +++ b/src/asm/CanSkip.go @@ -1,6 +1,6 @@ package asm -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // CanSkip returns true if the register/register operation can be skipped. func (a *Assembler) CanSkip(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool { diff --git a/src/asm/Memory.go b/src/asm/Memory.go index 1076405..903ff15 100644 --- a/src/asm/Memory.go +++ b/src/asm/Memory.go @@ -6,7 +6,7 @@ import ( "strconv" "strings" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) type Memory struct { diff --git a/src/asm/MemoryRegister.go b/src/asm/MemoryRegister.go index 1c9f51b..5e76305 100644 --- a/src/asm/MemoryRegister.go +++ b/src/asm/MemoryRegister.go @@ -3,7 +3,7 @@ package asm import ( "fmt" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // MemoryRegister operates with a memory address and a number. diff --git a/src/asm/Register.go b/src/asm/Register.go index 7188752..91be5e0 100644 --- a/src/asm/Register.go +++ b/src/asm/Register.go @@ -1,7 +1,7 @@ package asm import ( - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // Register operates with a single register. diff --git a/src/asm/RegisterLabel.go b/src/asm/RegisterLabel.go index 8092d9f..402954b 100644 --- a/src/asm/RegisterLabel.go +++ b/src/asm/RegisterLabel.go @@ -3,7 +3,7 @@ package asm import ( "fmt" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // RegisterLabel operates with a register and a label. diff --git a/src/asm/RegisterNumber.go b/src/asm/RegisterNumber.go index 6addf74..f12bb81 100644 --- a/src/asm/RegisterNumber.go +++ b/src/asm/RegisterNumber.go @@ -3,7 +3,7 @@ package asm import ( "fmt" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // RegisterNumber operates with a register and a number. diff --git a/src/asm/RegisterRegister.go b/src/asm/RegisterRegister.go index c133e2c..dadafd1 100644 --- a/src/asm/RegisterRegister.go +++ b/src/asm/RegisterRegister.go @@ -3,7 +3,7 @@ package asm import ( "fmt" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // RegisterRegister operates with two registers. diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index 377b777..f73a1e8 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -1,9 +1,9 @@ package asmc import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/dll" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/dll" ) // Finalize generates the final machine code. diff --git a/src/asmc/call.go b/src/asmc/call.go index cf84156..6c78a09 100644 --- a/src/asmc/call.go +++ b/src/asmc/call.go @@ -1,8 +1,8 @@ package asmc import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/x86" ) func (c *compiler) call(x asm.Instruction) { diff --git a/src/asmc/codeOffset.go b/src/asmc/codeOffset.go index 306adc0..9142762 100644 --- a/src/asmc/codeOffset.go +++ b/src/asmc/codeOffset.go @@ -1,11 +1,11 @@ package asmc import ( - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/elf" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/macho" - "git.akyoto.dev/cli/q/src/pe" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/elf" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/macho" + "git.urbach.dev/cli/q/src/pe" ) // codeOffset returns the file offset of the code section. diff --git a/src/asmc/compile.go b/src/asmc/compile.go index 7cec3b2..dfcddca 100644 --- a/src/asmc/compile.go +++ b/src/asmc/compile.go @@ -1,8 +1,8 @@ package asmc import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/x86" ) func (c *compiler) compile(x asm.Instruction) { diff --git a/src/asmc/compiler.go b/src/asmc/compiler.go index fd17c89..b4e30e6 100644 --- a/src/asmc/compiler.go +++ b/src/asmc/compiler.go @@ -1,6 +1,6 @@ package asmc -import "git.akyoto.dev/cli/q/src/dll" +import "git.urbach.dev/cli/q/src/dll" type compiler struct { code []byte diff --git a/src/asmc/dllCall.go b/src/asmc/dllCall.go index bd37582..75523d1 100644 --- a/src/asmc/dllCall.go +++ b/src/asmc/dllCall.go @@ -3,8 +3,8 @@ package asmc import ( "strings" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/x86" ) func (c *compiler) dllCall(x asm.Instruction) { diff --git a/src/asmc/jump.go b/src/asmc/jump.go index f01f547..853bd81 100644 --- a/src/asmc/jump.go +++ b/src/asmc/jump.go @@ -1,8 +1,8 @@ package asmc import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/x86" ) func (c *compiler) jump(x asm.Instruction) { diff --git a/src/asmc/load.go b/src/asmc/load.go index e460a82..7a3a707 100644 --- a/src/asmc/load.go +++ b/src/asmc/load.go @@ -3,8 +3,8 @@ package asmc import ( "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/x86" ) func (c *compiler) load(x asm.Instruction) { diff --git a/src/asmc/move.go b/src/asmc/move.go index b4f76ae..71e71f3 100644 --- a/src/asmc/move.go +++ b/src/asmc/move.go @@ -3,9 +3,9 @@ package asmc import ( "strings" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/x86" ) func (c *compiler) move(x asm.Instruction) { diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index 112a0d1..2e6d30f 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -5,9 +5,9 @@ import ( "fmt" "slices" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/sizeof" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/sizeof" ) // resolvePointers resolves the addresses of all pointers within the code and writes the correct addresses to the code slice. diff --git a/src/asmc/store.go b/src/asmc/store.go index b5e8f0d..aa81960 100644 --- a/src/asmc/store.go +++ b/src/asmc/store.go @@ -3,9 +3,9 @@ package asmc import ( "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/x86" ) func (c *compiler) store(x asm.Instruction) { diff --git a/src/ast/AST.go b/src/ast/AST.go index 99f1ed1..8aef14c 100644 --- a/src/ast/AST.go +++ b/src/ast/AST.go @@ -1,6 +1,6 @@ package ast -import "git.akyoto.dev/cli/q/src/expression" +import "git.urbach.dev/cli/q/src/expression" // Node is an interface used for all types of AST nodes. type Node any diff --git a/src/ast/Count.go b/src/ast/Count.go index 83e7f69..c0408d9 100644 --- a/src/ast/Count.go +++ b/src/ast/Count.go @@ -1,6 +1,6 @@ package ast -import "git.akyoto.dev/cli/q/src/token" +import "git.urbach.dev/cli/q/src/token" // Count counts how often the given token appears in the AST. func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { diff --git a/src/ast/Parse.go b/src/ast/Parse.go index 84c9972..20634e4 100644 --- a/src/ast/Parse.go +++ b/src/ast/Parse.go @@ -1,8 +1,8 @@ package ast import ( - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // Parse generates an AST from a list of tokens. diff --git a/src/ast/ast_test.go b/src/ast/ast_test.go index 621ab6e..33730dc 100644 --- a/src/ast/ast_test.go +++ b/src/ast/ast_test.go @@ -3,10 +3,10 @@ package ast_test import ( "testing" - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/go/assert" ) func TestAssign(t *testing.T) { diff --git a/src/ast/block.go b/src/ast/block.go index da58e2a..36c1933 100644 --- a/src/ast/block.go +++ b/src/ast/block.go @@ -1,9 +1,9 @@ package ast import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // block retrieves the start and end position of a block. diff --git a/src/ast/eachInstruction.go b/src/ast/eachInstruction.go index b593ebc..0bed958 100644 --- a/src/ast/eachInstruction.go +++ b/src/ast/eachInstruction.go @@ -1,6 +1,6 @@ package ast -import "git.akyoto.dev/cli/q/src/token" +import "git.urbach.dev/cli/q/src/token" // eachInstruction calls the function on each AST node. func eachInstruction(tokens token.List, call func(token.List) error) error { diff --git a/src/ast/helpers.go b/src/ast/helpers.go index ffb2bf5..f21ac47 100644 --- a/src/ast/helpers.go +++ b/src/ast/helpers.go @@ -1,8 +1,8 @@ package ast import ( - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // IsAssignment returns true if the expression is an assignment. diff --git a/src/ast/parseCases.go b/src/ast/parseCases.go index da6e561..dbb5412 100644 --- a/src/ast/parseCases.go +++ b/src/ast/parseCases.go @@ -1,9 +1,9 @@ package ast import ( - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // parseCases generates the cases inside a switch statement. diff --git a/src/ast/parseInstruction.go b/src/ast/parseInstruction.go index c14db17..bffd14a 100644 --- a/src/ast/parseInstruction.go +++ b/src/ast/parseInstruction.go @@ -1,10 +1,10 @@ package ast import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // parseInstruction generates an AST node from an instruction. diff --git a/src/ast/parseKeyword.go b/src/ast/parseKeyword.go index c012e38..0ad1442 100644 --- a/src/ast/parseKeyword.go +++ b/src/ast/parseKeyword.go @@ -1,10 +1,10 @@ package ast import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // parseKeyword generates a keyword node from an instruction. diff --git a/src/build/Executable.go b/src/build/Executable.go index 4eff03e..8b62a7e 100644 --- a/src/build/Executable.go +++ b/src/build/Executable.go @@ -4,7 +4,7 @@ import ( "path/filepath" "strings" - "git.akyoto.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/config" ) // Executable returns the path to the executable. diff --git a/src/build/Run.go b/src/build/Run.go index bbc06d8..2fd33d7 100644 --- a/src/build/Run.go +++ b/src/build/Run.go @@ -1,8 +1,8 @@ package build import ( - "git.akyoto.dev/cli/q/src/compiler" - "git.akyoto.dev/cli/q/src/scanner" + "git.urbach.dev/cli/q/src/compiler" + "git.urbach.dev/cli/q/src/scanner" ) // Run compiles the input files. diff --git a/src/build/build_test.go b/src/build/build_test.go index e442e12..87600b0 100644 --- a/src/build/build_test.go +++ b/src/build/build_test.go @@ -4,9 +4,9 @@ import ( "path/filepath" "testing" - "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/build" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/go/assert" ) func TestBuildDirectory(t *testing.T) { diff --git a/src/cli/Build.go b/src/cli/Build.go index 82daf25..1614bdc 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -6,8 +6,8 @@ import ( "runtime" "strings" - "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/build" + "git.urbach.dev/cli/q/src/config" ) // Build parses the arguments and creates a build. diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index b27a964..236ef5a 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -3,9 +3,9 @@ package cli_test import ( "testing" - "git.akyoto.dev/cli/q/src/cli" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cli" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/go/assert" ) func TestCLI(t *testing.T) { diff --git a/src/cli/System.go b/src/cli/System.go index a2a6801..06e5ee2 100644 --- a/src/cli/System.go +++ b/src/cli/System.go @@ -5,7 +5,7 @@ import ( "runtime" "strconv" - "git.akyoto.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/config" ) // System shows system information. diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index cc56464..f62943c 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -1,10 +1,10 @@ package compiler import ( - "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/types" ) // Compile waits for the scan to finish and compiles all functions. diff --git a/src/compiler/CompileFunctions.go b/src/compiler/CompileFunctions.go index d4ee25b..05ab228 100644 --- a/src/compiler/CompileFunctions.go +++ b/src/compiler/CompileFunctions.go @@ -3,7 +3,7 @@ package compiler import ( "sync" - "git.akyoto.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/core" ) // CompileFunctions starts a goroutine for each function compilation and waits for completion. diff --git a/src/compiler/PrintInstructions.go b/src/compiler/PrintInstructions.go index 3d92da2..39eb9db 100644 --- a/src/compiler/PrintInstructions.go +++ b/src/compiler/PrintInstructions.go @@ -1,6 +1,6 @@ package compiler -import "git.akyoto.dev/cli/q/src/core" +import "git.urbach.dev/cli/q/src/core" // PrintInstructions prints out the generated instructions. func (r *Result) PrintInstructions() { diff --git a/src/compiler/PrintStatistics.go b/src/compiler/PrintStatistics.go index 65b9066..14c3e26 100644 --- a/src/compiler/PrintStatistics.go +++ b/src/compiler/PrintStatistics.go @@ -3,7 +3,7 @@ package compiler import ( "fmt" - "git.akyoto.dev/go/color/ansi" + "git.urbach.dev/go/color/ansi" ) // PrintStatistics shows the statistics. diff --git a/src/compiler/Result.go b/src/compiler/Result.go index ce94da8..b9bbb7b 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -1,8 +1,8 @@ package compiler import ( - "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/dll" + "git.urbach.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/dll" ) // Result contains everything we need to write an executable file to disk. diff --git a/src/compiler/Write.go b/src/compiler/Write.go index 70c1e96..bb14745 100644 --- a/src/compiler/Write.go +++ b/src/compiler/Write.go @@ -4,11 +4,11 @@ import ( "bufio" "io" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/elf" - "git.akyoto.dev/cli/q/src/macho" - "git.akyoto.dev/cli/q/src/pe" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/dll" + "git.urbach.dev/cli/q/src/elf" + "git.urbach.dev/cli/q/src/macho" + "git.urbach.dev/cli/q/src/pe" ) // Write writes the executable to the given writer. diff --git a/src/compiler/eachFunction.go b/src/compiler/eachFunction.go index 03973f4..de7fc63 100644 --- a/src/compiler/eachFunction.go +++ b/src/compiler/eachFunction.go @@ -1,8 +1,8 @@ package compiler import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/core" ) // eachFunction recursively finds all the calls to external functions. diff --git a/src/compiler/finalize.go b/src/compiler/finalize.go index e82a049..6437913 100644 --- a/src/compiler/finalize.go +++ b/src/compiler/finalize.go @@ -1,11 +1,11 @@ package compiler import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/asmc" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/asmc" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/x86" ) // finalize generates the final machine code. diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go index 924f16f..0dec215 100644 --- a/src/core/ArrayElementToRegister.go +++ b/src/core/ArrayElementToRegister.go @@ -3,12 +3,12 @@ package core import ( "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // ArrayElementToRegister moves the value of an array element into the given register. diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go index 1413a45..2b17fc1 100644 --- a/src/core/CallExtern.go +++ b/src/core/CallExtern.go @@ -4,11 +4,11 @@ import ( "fmt" "slices" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/x86" ) // CallExtern calls an external function. diff --git a/src/core/CallSafe.go b/src/core/CallSafe.go index f6756b4..20f4676 100644 --- a/src/core/CallSafe.go +++ b/src/core/CallSafe.go @@ -3,8 +3,8 @@ package core import ( "slices" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" ) // CallSafe pushes used registers to the stack, executes the call and restores the original register value. diff --git a/src/core/CallToRegister.go b/src/core/CallToRegister.go index 5b9901e..9137790 100644 --- a/src/core/CallToRegister.go +++ b/src/core/CallToRegister.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" ) // CallToRegister moves the result of a function call into the given register. diff --git a/src/core/Compare.go b/src/core/Compare.go index 0244335..731165f 100644 --- a/src/core/Compare.go +++ b/src/core/Compare.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // Compare evaluates a boolean expression. diff --git a/src/core/CompileAST.go b/src/core/CompileAST.go index a9d54f5..b509146 100644 --- a/src/core/CompileAST.go +++ b/src/core/CompileAST.go @@ -1,7 +1,7 @@ package core import ( - "git.akyoto.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/ast" ) // CompileAST compiles an abstract syntax tree. diff --git a/src/core/CompileASTNode.go b/src/core/CompileASTNode.go index a32a321..057b127 100644 --- a/src/core/CompileASTNode.go +++ b/src/core/CompileASTNode.go @@ -1,7 +1,7 @@ package core import ( - "git.akyoto.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/ast" ) // CompileASTNode compiles a node in the AST. diff --git a/src/core/CompileAssert.go b/src/core/CompileAssert.go index f3db795..a761982 100644 --- a/src/core/CompileAssert.go +++ b/src/core/CompileAssert.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" ) // CompileAssert compiles an assertion. diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index bbc237b..2ff6dfb 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // CompileAssign compiles an assign statement. diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 2e5d8be..55e33a5 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -3,10 +3,10 @@ package core import ( "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/types" ) // CompileAssignArray compiles an assign statement for array elements. diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index c93d90b..cbca794 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -1,13 +1,13 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/scope" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/scope" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/x86" ) // CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right. diff --git a/src/core/CompileAssignField.go b/src/core/CompileAssignField.go index 412be15..31310d2 100644 --- a/src/core/CompileAssignField.go +++ b/src/core/CompileAssignField.go @@ -3,10 +3,10 @@ package core import ( "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/types" ) // CompileAssignField compiles a memory write to a struct field. diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 9089335..3d4ad40 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" ) // CompileCall executes a function call. diff --git a/src/core/CompileCondition.go b/src/core/CompileCondition.go index 0a31b5a..a0c8ffe 100644 --- a/src/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // CompileCondition inserts code to jump to the start label or end label depending on the truth of the condition. diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index 6b46991..71d4b73 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // CompileDefinition compiles a variable definition. diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index f768dab..05966d9 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" ) // CompileDelete compiles a `delete` function call which deallocates a struct. diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index b9a6d7f..9f77413 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // CompileFor compiles a for loop. diff --git a/src/core/CompileIf.go b/src/core/CompileIf.go index 61fb3c6..1474eff 100644 --- a/src/core/CompileIf.go +++ b/src/core/CompileIf.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" ) // CompileIf compiles a branch instruction. diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index e7264f3..4065d76 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -3,10 +3,10 @@ package core import ( "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" ) var _len = Function{OutputTypes: []types.Type{types.Int}} diff --git a/src/core/CompileLoop.go b/src/core/CompileLoop.go index 13ccf90..923e690 100644 --- a/src/core/CompileLoop.go +++ b/src/core/CompileLoop.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" ) // CompileLoop compiles an infinite loop. diff --git a/src/core/CompileMemoryStore.go b/src/core/CompileMemoryStore.go index 1277268..a1bb856 100644 --- a/src/core/CompileMemoryStore.go +++ b/src/core/CompileMemoryStore.go @@ -3,9 +3,9 @@ package core import ( "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" ) // CompileMemoryStore compiles a variable-width store to memory. diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index 2589f3f..52a1649 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" ) // CompileNew compiles a `new` function call which allocates a struct. diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index cb2ffe6..cd15afe 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/errors" ) // CompileReturn compiles a return instruction. diff --git a/src/core/CompileSwitch.go b/src/core/CompileSwitch.go index e62b8ca..7196f8a 100644 --- a/src/core/CompileSwitch.go +++ b/src/core/CompileSwitch.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" ) // CompileSwitch compiles a multi-branch instruction. diff --git a/src/core/CompileSyscall.go b/src/core/CompileSyscall.go index a4edf16..dcfb42d 100644 --- a/src/core/CompileSyscall.go +++ b/src/core/CompileSyscall.go @@ -1,7 +1,7 @@ package core import ( - "git.akyoto.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/expression" ) // CompileSyscall executes a kernel syscall. diff --git a/src/core/CompileTokens.go b/src/core/CompileTokens.go index 2aa390c..beef1b6 100644 --- a/src/core/CompileTokens.go +++ b/src/core/CompileTokens.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/token" ) // CompileTokens compiles a token list. diff --git a/src/core/Constant.go b/src/core/Constant.go index b5bb523..0800947 100644 --- a/src/core/Constant.go +++ b/src/core/Constant.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // Constant registers a single value to be accessible under a descriptive name. diff --git a/src/core/Define.go b/src/core/Define.go index d017d85..eacd340 100644 --- a/src/core/Define.go +++ b/src/core/Define.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/scope" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/scope" + "git.urbach.dev/cli/q/src/token" ) // Define defines a new variable. diff --git a/src/core/Environment.go b/src/core/Environment.go index 87b1a29..e2a1354 100644 --- a/src/core/Environment.go +++ b/src/core/Environment.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/types" ) // Environment holds information about the entire build. diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 68da992..5e36fec 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. diff --git a/src/core/Execute.go b/src/core/Execute.go index 74132b8..8d01a4e 100644 --- a/src/core/Execute.go +++ b/src/core/Execute.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // Execute executes an operation on a register with a value operand. diff --git a/src/core/ExecuteLeaf.go b/src/core/ExecuteLeaf.go index 32feb13..06310ae 100644 --- a/src/core/ExecuteLeaf.go +++ b/src/core/ExecuteLeaf.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/token" ) // ExecuteLeaf performs an operation on a register with the given leaf operand. diff --git a/src/core/ExecuteRegister.go b/src/core/ExecuteRegister.go index 8a48bd5..6920310 100644 --- a/src/core/ExecuteRegister.go +++ b/src/core/ExecuteRegister.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/token" ) // ExecuteRegister performs an operation on a single register. diff --git a/src/core/ExecuteRegisterNumber.go b/src/core/ExecuteRegisterNumber.go index 0176f82..a3acbb5 100644 --- a/src/core/ExecuteRegisterNumber.go +++ b/src/core/ExecuteRegisterNumber.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/x86" ) // ExecuteRegisterNumber performs an operation on a register and a number. diff --git a/src/core/ExecuteRegisterRegister.go b/src/core/ExecuteRegisterRegister.go index 3e699fb..5b59667 100644 --- a/src/core/ExecuteRegisterRegister.go +++ b/src/core/ExecuteRegisterRegister.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/x86" ) // ExecuteRegisterRegister performs an operation on two registers. diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index 461cc1c..f62ba58 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // ExpressionToMemory puts the result of an expression into the specified memory address. diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 785cb91..0a3fb29 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -1,12 +1,12 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // ExpressionToRegister puts the result of an expression into the specified register. diff --git a/src/core/ExpressionsToRegisters.go b/src/core/ExpressionsToRegisters.go index f6a21f3..7156daa 100644 --- a/src/core/ExpressionsToRegisters.go +++ b/src/core/ExpressionsToRegisters.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // ExpressionsToRegisters moves multiple expressions into the specified registers and checks that the types match with the function signature. diff --git a/src/core/Fold.go b/src/core/Fold.go index 6245114..768f2ec 100644 --- a/src/core/Fold.go +++ b/src/core/Fold.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // Fold will try to precalculate the results of operations with constants. diff --git a/src/core/Function.go b/src/core/Function.go index f159815..dca269e 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/register" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/dll" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/register" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // Function represents the smallest unit of code. diff --git a/src/core/Identifier.go b/src/core/Identifier.go index 6a8a148..ab51f9e 100644 --- a/src/core/Identifier.go +++ b/src/core/Identifier.go @@ -3,7 +3,7 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/scope" + "git.urbach.dev/cli/q/src/scope" ) // Identifier looks up an identifier which can be a variable or a function. diff --git a/src/core/JumpIfFalse.go b/src/core/JumpIfFalse.go index 44eb1ba..61c649d 100644 --- a/src/core/JumpIfFalse.go +++ b/src/core/JumpIfFalse.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/token" ) // JumpIfFalse jumps to the label if the previous comparison was false. diff --git a/src/core/JumpIfTrue.go b/src/core/JumpIfTrue.go index c8dc487..5c8eddc 100644 --- a/src/core/JumpIfTrue.go +++ b/src/core/JumpIfTrue.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/token" ) // JumpIfTrue jumps to the label if the previous comparison was true. diff --git a/src/core/NewFunction.go b/src/core/NewFunction.go index 3da5ed5..dabb185 100644 --- a/src/core/NewFunction.go +++ b/src/core/NewFunction.go @@ -1,12 +1,12 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/register" - "git.akyoto.dev/cli/q/src/scope" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/register" + "git.urbach.dev/cli/q/src/scope" + "git.urbach.dev/cli/q/src/x86" ) // NewFunction creates a new function. diff --git a/src/core/Parameter.go b/src/core/Parameter.go index 1375a09..8c89249 100644 --- a/src/core/Parameter.go +++ b/src/core/Parameter.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) type Parameter struct { diff --git a/src/core/PeriodToRegister.go b/src/core/PeriodToRegister.go index c897853..2307a43 100644 --- a/src/core/PeriodToRegister.go +++ b/src/core/PeriodToRegister.go @@ -4,11 +4,11 @@ import ( "fmt" "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" ) // PeriodToRegister moves a constant or a function address into the given register. diff --git a/src/core/PrintInstructions.go b/src/core/PrintInstructions.go index 00fcf7c..69cfc26 100644 --- a/src/core/PrintInstructions.go +++ b/src/core/PrintInstructions.go @@ -4,8 +4,8 @@ import ( "bytes" "fmt" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/go/color/ansi" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/go/color/ansi" ) // PrintInstructions shows the assembly instructions. diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index 2a5bf08..42382fb 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/scope" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/scope" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/x86" ) // ResolveTypes parses the input and output types. diff --git a/src/core/ToNumber.go b/src/core/ToNumber.go index 122b04d..fc891b4 100644 --- a/src/core/ToNumber.go +++ b/src/core/ToNumber.go @@ -5,9 +5,9 @@ import ( "strings" "unicode/utf8" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // ToNumber tries to convert the token into a numeric value. diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index e8dd66e..a8128eb 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -3,11 +3,11 @@ package core import ( "encoding/binary" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // TokenToRegister moves a token into a register. diff --git a/src/core/UsesRegister.go b/src/core/UsesRegister.go index 74519bd..df66b67 100644 --- a/src/core/UsesRegister.go +++ b/src/core/UsesRegister.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // UsesRegister returns true if evaluating the expression would write or read the given register. diff --git a/src/cpu/Register_test.go b/src/cpu/Register_test.go index e14f6f0..1dc6b20 100644 --- a/src/cpu/Register_test.go +++ b/src/cpu/Register_test.go @@ -3,8 +3,8 @@ package cpu_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" ) func TestRegisterString(t *testing.T) { diff --git a/src/cpu/State_test.go b/src/cpu/State_test.go index 52b1fa9..6fdbf0b 100644 --- a/src/cpu/State_test.go +++ b/src/cpu/State_test.go @@ -3,8 +3,8 @@ package cpu_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" ) func TestRegisterState(t *testing.T) { diff --git a/src/data/Data_test.go b/src/data/Data_test.go index b8d0c60..69453e9 100644 --- a/src/data/Data_test.go +++ b/src/data/Data_test.go @@ -3,8 +3,8 @@ package data_test import ( "testing" - "git.akyoto.dev/cli/q/src/data" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/data" + "git.urbach.dev/go/assert" ) func TestInterning(t *testing.T) { diff --git a/src/elf/ELF.go b/src/elf/ELF.go index 0248c7e..d9e8d52 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -5,8 +5,8 @@ import ( "encoding/binary" "io" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/fs" ) const HeaderEnd = HeaderSize + ProgramHeaderSize*2 diff --git a/src/elf/ELF_test.go b/src/elf/ELF_test.go index c813536..74bd71c 100644 --- a/src/elf/ELF_test.go +++ b/src/elf/ELF_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/elf" + "git.urbach.dev/cli/q/src/elf" ) func TestWrite(t *testing.T) { diff --git a/src/errors/Error.go b/src/errors/Error.go index d23d7a0..f2c0629 100644 --- a/src/errors/Error.go +++ b/src/errors/Error.go @@ -5,8 +5,8 @@ import ( "os" "path/filepath" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // Error is a compiler error at a given line and column. diff --git a/src/expression/Expression.go b/src/expression/Expression.go index 2344a37..aecaa77 100644 --- a/src/expression/Expression.go +++ b/src/expression/Expression.go @@ -3,7 +3,7 @@ package expression import ( "strings" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/token" ) // Expression is a binary tree with an operator on each node. diff --git a/src/expression/Expression_test.go b/src/expression/Expression_test.go index df62868..cfd2ea9 100644 --- a/src/expression/Expression_test.go +++ b/src/expression/Expression_test.go @@ -4,9 +4,9 @@ import ( "errors" "testing" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/go/assert" ) func TestParse(t *testing.T) { diff --git a/src/expression/List.go b/src/expression/List.go index 6431451..b05058b 100644 --- a/src/expression/List.go +++ b/src/expression/List.go @@ -1,7 +1,7 @@ package expression import ( - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/token" ) // NewList generates a list of expressions from comma separated parameters. diff --git a/src/expression/Operator.go b/src/expression/Operator.go index 800277a..63f77c1 100644 --- a/src/expression/Operator.go +++ b/src/expression/Operator.go @@ -3,7 +3,7 @@ package expression import ( "math" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/token" ) // Operator represents an operator for mathematical expressions. diff --git a/src/expression/Parse.go b/src/expression/Parse.go index f839ac8..750fd29 100644 --- a/src/expression/Parse.go +++ b/src/expression/Parse.go @@ -3,7 +3,7 @@ package expression import ( "math" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/token" ) // Parse generates an expression tree from tokens. diff --git a/src/expression/bench_test.go b/src/expression/bench_test.go index 7353cce..ecbf0ff 100644 --- a/src/expression/bench_test.go +++ b/src/expression/bench_test.go @@ -3,8 +3,8 @@ package expression_test import ( "testing" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) func BenchmarkExpression(b *testing.B) { diff --git a/src/fs/File.go b/src/fs/File.go index 76a43bb..ac4685b 100644 --- a/src/fs/File.go +++ b/src/fs/File.go @@ -1,6 +1,6 @@ package fs -import "git.akyoto.dev/cli/q/src/token" +import "git.urbach.dev/cli/q/src/token" // File represents a single source file. type File struct { diff --git a/src/fs/Import.go b/src/fs/Import.go index ce9032e..368c686 100644 --- a/src/fs/Import.go +++ b/src/fs/Import.go @@ -1,6 +1,6 @@ package fs -import "git.akyoto.dev/cli/q/src/token" +import "git.urbach.dev/cli/q/src/token" // Import represents an import statement in a file. type Import struct { diff --git a/src/fs/Walk_test.go b/src/fs/Walk_test.go index c63f62b..c6e526b 100644 --- a/src/fs/Walk_test.go +++ b/src/fs/Walk_test.go @@ -3,8 +3,8 @@ package fs_test import ( "testing" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/go/assert" ) func TestWalk(t *testing.T) { diff --git a/src/fs/bench_test.go b/src/fs/bench_test.go index 3462acd..096343a 100644 --- a/src/fs/bench_test.go +++ b/src/fs/bench_test.go @@ -4,8 +4,8 @@ import ( "os" "testing" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/go/assert" ) func BenchmarkReadDir(b *testing.B) { diff --git a/src/macho/MachO.go b/src/macho/MachO.go index 17d9564..1c2cf03 100644 --- a/src/macho/MachO.go +++ b/src/macho/MachO.go @@ -5,8 +5,8 @@ import ( "encoding/binary" "io" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/fs" ) const ( diff --git a/src/macho/MachO_test.go b/src/macho/MachO_test.go index a026960..df2d04c 100644 --- a/src/macho/MachO_test.go +++ b/src/macho/MachO_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/macho" + "git.urbach.dev/cli/q/src/macho" ) func TestWrite(t *testing.T) { diff --git a/src/pe/EXE.go b/src/pe/EXE.go index bfbaef9..c34b616 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -5,9 +5,9 @@ import ( "encoding/binary" "io" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/dll" + "git.urbach.dev/cli/q/src/fs" ) const ( diff --git a/src/pe/EXE_test.go b/src/pe/EXE_test.go index 64a6356..d69d46a 100644 --- a/src/pe/EXE_test.go +++ b/src/pe/EXE_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/pe" + "git.urbach.dev/cli/q/src/pe" ) func TestWrite(t *testing.T) { diff --git a/src/pe/importLibraries.go b/src/pe/importLibraries.go index d7c8d32..dabc2a2 100644 --- a/src/pe/importLibraries.go +++ b/src/pe/importLibraries.go @@ -1,6 +1,6 @@ package pe -import "git.akyoto.dev/cli/q/src/dll" +import "git.urbach.dev/cli/q/src/dll" // importLibraries generates the import address table which contains the addresses of functions imported from DLLs. func importLibraries(dlls dll.List, importsStart int) ([]uint64, []byte, []DLLImport, int) { diff --git a/src/register/AddLabel.go b/src/register/AddLabel.go index 4667275..797bb30 100644 --- a/src/register/AddLabel.go +++ b/src/register/AddLabel.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/asm" +import "git.urbach.dev/cli/q/src/asm" func (f *Machine) AddLabel(label string) { f.Assembler.Label(asm.LABEL, label) diff --git a/src/register/DLLCall.go b/src/register/DLLCall.go index c62eb55..4b80f04 100644 --- a/src/register/DLLCall.go +++ b/src/register/DLLCall.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/asm" +import "git.urbach.dev/cli/q/src/asm" func (f *Machine) DLLCall(label string) { f.Assembler.Label(asm.DLLCALL, label) diff --git a/src/register/FreeRegister.go b/src/register/FreeRegister.go index 4fd016e..bff1630 100644 --- a/src/register/FreeRegister.go +++ b/src/register/FreeRegister.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // FreeRegister frees a register. func (f *Machine) FreeRegister(register cpu.Register) { diff --git a/src/register/Jump.go b/src/register/Jump.go index 98045fd..ccf1f7c 100644 --- a/src/register/Jump.go +++ b/src/register/Jump.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/asm" +import "git.urbach.dev/cli/q/src/asm" func (f *Machine) Jump(mnemonic asm.Mnemonic, label string) { f.Assembler.Label(mnemonic, label) diff --git a/src/register/Machine.go b/src/register/Machine.go index 07f64b2..69f307d 100644 --- a/src/register/Machine.go +++ b/src/register/Machine.go @@ -1,9 +1,9 @@ package register import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/scope" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/scope" ) // Machine is a register usage aware assembler. diff --git a/src/register/MemoryLabel.go b/src/register/MemoryLabel.go index 98deda9..520ad5e 100644 --- a/src/register/MemoryLabel.go +++ b/src/register/MemoryLabel.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/asm" +import "git.urbach.dev/cli/q/src/asm" func (f *Machine) MemoryLabel(mnemonic asm.Mnemonic, a asm.Memory, b string) { f.Assembler.MemoryLabel(mnemonic, a, b) diff --git a/src/register/MemoryNumber.go b/src/register/MemoryNumber.go index 2c1177f..6a00bf7 100644 --- a/src/register/MemoryNumber.go +++ b/src/register/MemoryNumber.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/asm" +import "git.urbach.dev/cli/q/src/asm" func (f *Machine) MemoryNumber(mnemonic asm.Mnemonic, a asm.Memory, b int) { f.Assembler.MemoryNumber(mnemonic, a, b) diff --git a/src/register/MemoryRegister.go b/src/register/MemoryRegister.go index fa13e9f..66fbe24 100644 --- a/src/register/MemoryRegister.go +++ b/src/register/MemoryRegister.go @@ -1,8 +1,8 @@ package register import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" ) func (f *Machine) MemoryRegister(mnemonic asm.Mnemonic, a asm.Memory, b cpu.Register) { diff --git a/src/register/NewRegister.go b/src/register/NewRegister.go index dbe3cc0..b515b67 100644 --- a/src/register/NewRegister.go +++ b/src/register/NewRegister.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // NewRegister reserves a register. func (f *Machine) NewRegister() cpu.Register { diff --git a/src/register/Number.go b/src/register/Number.go index b2c1188..9cf7bc0 100644 --- a/src/register/Number.go +++ b/src/register/Number.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/asm" +import "git.urbach.dev/cli/q/src/asm" func (f *Machine) Number(mnemonic asm.Mnemonic, number int) { f.Assembler.Number(mnemonic, number) diff --git a/src/register/Register.go b/src/register/Register.go index fa972a1..002ea86 100644 --- a/src/register/Register.go +++ b/src/register/Register.go @@ -1,8 +1,8 @@ package register import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" ) func (f *Machine) Register(mnemonic asm.Mnemonic, a cpu.Register) { diff --git a/src/register/RegisterIsUsed.go b/src/register/RegisterIsUsed.go index fedf149..4789639 100644 --- a/src/register/RegisterIsUsed.go +++ b/src/register/RegisterIsUsed.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // RegisterIsUsed reserves a register. func (f *Machine) RegisterIsUsed(register cpu.Register) bool { diff --git a/src/register/RegisterLabel.go b/src/register/RegisterLabel.go index a97de41..64a0b1a 100644 --- a/src/register/RegisterLabel.go +++ b/src/register/RegisterLabel.go @@ -1,8 +1,8 @@ package register import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" ) func (f *Machine) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { diff --git a/src/register/RegisterNumber.go b/src/register/RegisterNumber.go index f3986f5..e12f669 100644 --- a/src/register/RegisterNumber.go +++ b/src/register/RegisterNumber.go @@ -1,9 +1,9 @@ package register import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/sizeof" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/sizeof" ) func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { diff --git a/src/register/RegisterRegister.go b/src/register/RegisterRegister.go index 59208ae..073dc3a 100644 --- a/src/register/RegisterRegister.go +++ b/src/register/RegisterRegister.go @@ -1,8 +1,8 @@ package register import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" ) func (f *Machine) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) { diff --git a/src/register/SaveRegister.go b/src/register/SaveRegister.go index c0aa17b..9f9bd0a 100644 --- a/src/register/SaveRegister.go +++ b/src/register/SaveRegister.go @@ -3,8 +3,8 @@ package register import ( "slices" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" ) // SaveRegister attempts to move a variable occupying this register to another register. diff --git a/src/register/UseRegister.go b/src/register/UseRegister.go index 996181f..c582a9a 100644 --- a/src/register/UseRegister.go +++ b/src/register/UseRegister.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // Use marks a register to be currently in use. func (f *Machine) UseRegister(register cpu.Register) { diff --git a/src/register/postInstruction.go b/src/register/postInstruction.go index fd55acb..18520b5 100644 --- a/src/register/postInstruction.go +++ b/src/register/postInstruction.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/config" +import "git.urbach.dev/cli/q/src/config" func (f *Machine) postInstruction() { if !config.ShowAssembly { diff --git a/src/riscv/Registers.go b/src/riscv/Registers.go index 66bf189..1313390 100644 --- a/src/riscv/Registers.go +++ b/src/riscv/Registers.go @@ -1,6 +1,6 @@ package riscv -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" const ( X0 cpu.Register = iota diff --git a/src/riscv/Registers_test.go b/src/riscv/Registers_test.go index efef49d..8e86ee7 100644 --- a/src/riscv/Registers_test.go +++ b/src/riscv/Registers_test.go @@ -3,8 +3,8 @@ package riscv_test import ( "testing" - "git.akyoto.dev/cli/q/src/riscv" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/riscv" + "git.urbach.dev/go/assert" ) func TestRegisters(t *testing.T) { diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go index 4295972..c424f5d 100644 --- a/src/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -3,10 +3,10 @@ package scanner import ( "path/filepath" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/types" ) // Scan scans the list of files. diff --git a/src/scanner/Scanner.go b/src/scanner/Scanner.go index 9bb05fa..24929f8 100644 --- a/src/scanner/Scanner.go +++ b/src/scanner/Scanner.go @@ -3,9 +3,9 @@ package scanner import ( "sync" - "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/types" ) // Scanner is used to scan files before the actual compilation step. diff --git a/src/scanner/queueDirectory.go b/src/scanner/queueDirectory.go index 27b1906..d99f0db 100644 --- a/src/scanner/queueDirectory.go +++ b/src/scanner/queueDirectory.go @@ -4,8 +4,8 @@ import ( "path/filepath" "strings" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/fs" ) // queueDirectory queues an entire directory to be scanned. diff --git a/src/scanner/scanConst.go b/src/scanner/scanConst.go index 801dba4..f3223f1 100644 --- a/src/scanner/scanConst.go +++ b/src/scanner/scanConst.go @@ -1,10 +1,10 @@ package scanner import ( - "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // scanConst scans a block of constants. diff --git a/src/scanner/scanExtern.go b/src/scanner/scanExtern.go index 5f4e214..82f3b85 100644 --- a/src/scanner/scanExtern.go +++ b/src/scanner/scanExtern.go @@ -1,9 +1,9 @@ package scanner import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // scanExtern scans a block of external function declarations. diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 65601f0..04bf2b5 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -3,9 +3,9 @@ package scanner import ( "os" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // scanFile scans a single file. diff --git a/src/scanner/scanFunction.go b/src/scanner/scanFunction.go index 6d85d83..b5242f3 100644 --- a/src/scanner/scanFunction.go +++ b/src/scanner/scanFunction.go @@ -1,9 +1,9 @@ package scanner import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // scanFunction scans a function. diff --git a/src/scanner/scanFunctionSignature.go b/src/scanner/scanFunctionSignature.go index 874464e..d1f11ad 100644 --- a/src/scanner/scanFunctionSignature.go +++ b/src/scanner/scanFunctionSignature.go @@ -1,10 +1,10 @@ package scanner import ( - "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // scanFunctionSignature scans a function declaration without the body. diff --git a/src/scanner/scanImport.go b/src/scanner/scanImport.go index 47ed367..6781804 100644 --- a/src/scanner/scanImport.go +++ b/src/scanner/scanImport.go @@ -3,10 +3,10 @@ package scanner import ( "path/filepath" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // scanImport scans an import. diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index d471ce1..0de75cf 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -1,10 +1,10 @@ package scanner import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // scanStruct scans a struct. diff --git a/src/scope/Scope.go b/src/scope/Scope.go index fd1f750..20efebc 100644 --- a/src/scope/Scope.go +++ b/src/scope/Scope.go @@ -1,7 +1,7 @@ package scope import ( - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // Scope represents an independent code block. diff --git a/src/scope/Stack.go b/src/scope/Stack.go index dbfe206..16a2e62 100644 --- a/src/scope/Stack.go +++ b/src/scope/Stack.go @@ -1,9 +1,9 @@ package scope import ( - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/token" ) // Stack is a stack of scopes. diff --git a/src/scope/Variable.go b/src/scope/Variable.go index 7387ab6..79962f6 100644 --- a/src/scope/Variable.go +++ b/src/scope/Variable.go @@ -1,8 +1,8 @@ package scope import ( - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/types" ) // Variable represents a named register. diff --git a/src/sizeof/Signed_test.go b/src/sizeof/Signed_test.go index bd4d049..a815d22 100644 --- a/src/sizeof/Signed_test.go +++ b/src/sizeof/Signed_test.go @@ -4,8 +4,8 @@ import ( "math" "testing" - "git.akyoto.dev/cli/q/src/sizeof" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/sizeof" + "git.urbach.dev/go/assert" ) func TestSigned(t *testing.T) { diff --git a/src/sizeof/Unsigned_test.go b/src/sizeof/Unsigned_test.go index b4e78a2..94cdd10 100644 --- a/src/sizeof/Unsigned_test.go +++ b/src/sizeof/Unsigned_test.go @@ -4,8 +4,8 @@ import ( "math" "testing" - "git.akyoto.dev/cli/q/src/sizeof" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/sizeof" + "git.urbach.dev/go/assert" ) func TestUnsigned(t *testing.T) { diff --git a/src/token/Count_test.go b/src/token/Count_test.go index 9c8a228..39b9b4e 100644 --- a/src/token/Count_test.go +++ b/src/token/Count_test.go @@ -3,8 +3,8 @@ package token_test import ( "testing" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/go/assert" ) func TestCount(t *testing.T) { diff --git a/src/token/List_test.go b/src/token/List_test.go index 9b3638f..5ca101e 100644 --- a/src/token/List_test.go +++ b/src/token/List_test.go @@ -4,8 +4,8 @@ import ( "errors" "testing" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/go/assert" ) func TestIndexKind(t *testing.T) { diff --git a/src/token/Token_test.go b/src/token/Token_test.go index 1eb9092..0c5518c 100644 --- a/src/token/Token_test.go +++ b/src/token/Token_test.go @@ -3,8 +3,8 @@ package token_test import ( "testing" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/go/assert" ) func TestTokenEnd(t *testing.T) { diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index 3ed541a..65b8abe 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -3,8 +3,8 @@ package token_test import ( "testing" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/go/assert" ) func TestFunction(t *testing.T) { diff --git a/src/token/bench_test.go b/src/token/bench_test.go index 6004fa3..73aeba3 100644 --- a/src/token/bench_test.go +++ b/src/token/bench_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/token" ) func BenchmarkLines(b *testing.B) { diff --git a/src/types/Field.go b/src/types/Field.go index c8d4fce..1f5198d 100644 --- a/src/types/Field.go +++ b/src/types/Field.go @@ -1,6 +1,6 @@ package types -import "git.akyoto.dev/cli/q/src/token" +import "git.urbach.dev/cli/q/src/token" // Field is a memory region in a data structure. type Field struct { diff --git a/src/types/types_test.go b/src/types/types_test.go index e3cb03e..856c878 100644 --- a/src/types/types_test.go +++ b/src/types/types_test.go @@ -3,8 +3,8 @@ package types_test import ( "testing" - "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/types" + "git.urbach.dev/go/assert" ) func TestName(t *testing.T) { diff --git a/src/x86/Add.go b/src/x86/Add.go index 9569a56..e2a81ad 100644 --- a/src/x86/Add.go +++ b/src/x86/Add.go @@ -1,7 +1,7 @@ package x86 import ( - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // AddRegisterNumber adds a number to the given register. diff --git a/src/x86/Add_test.go b/src/x86/Add_test.go index 4aa285e..71097d4 100644 --- a/src/x86/Add_test.go +++ b/src/x86/Add_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestAddRegisterNumber(t *testing.T) { diff --git a/src/x86/And.go b/src/x86/And.go index 39da3a8..48f999e 100644 --- a/src/x86/And.go +++ b/src/x86/And.go @@ -1,7 +1,7 @@ package x86 import ( - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // AndRegisterNumber performs a bitwise AND using a register and a number. diff --git a/src/x86/And_test.go b/src/x86/And_test.go index a4dbd58..7fb6fd4 100644 --- a/src/x86/And_test.go +++ b/src/x86/And_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestAndRegisterNumber(t *testing.T) { diff --git a/src/x86/Compare.go b/src/x86/Compare.go index 6b67aaa..8be0e22 100644 --- a/src/x86/Compare.go +++ b/src/x86/Compare.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // Compares the register with the number and sets the status flags in the EFLAGS register. func CompareRegisterNumber(code []byte, register cpu.Register, number int) []byte { diff --git a/src/x86/Compare_test.go b/src/x86/Compare_test.go index dbf6795..141c863 100644 --- a/src/x86/Compare_test.go +++ b/src/x86/Compare_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestCompareRegisterNumber(t *testing.T) { diff --git a/src/x86/Div.go b/src/x86/Div.go index 55ff659..dc46549 100644 --- a/src/x86/Div.go +++ b/src/x86/Div.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // DivRegister divides RDX:RAX by the value in the register. func DivRegister(code []byte, divisor cpu.Register) []byte { diff --git a/src/x86/Div_test.go b/src/x86/Div_test.go index 2af2bda..5069f76 100644 --- a/src/x86/Div_test.go +++ b/src/x86/Div_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestDivRegister(t *testing.T) { diff --git a/src/x86/Jump_test.go b/src/x86/Jump_test.go index b2df363..fc1aa5a 100644 --- a/src/x86/Jump_test.go +++ b/src/x86/Jump_test.go @@ -3,8 +3,8 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestJump(t *testing.T) { diff --git a/src/x86/Load.go b/src/x86/Load.go index 1a56236..dc04a25 100644 --- a/src/x86/Load.go +++ b/src/x86/Load.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // LoadRegister loads from memory into a register. func LoadRegister(code []byte, destination cpu.Register, offset int8, length byte, source cpu.Register) []byte { diff --git a/src/x86/LoadDynamic.go b/src/x86/LoadDynamic.go index d1ad253..1c5cb30 100644 --- a/src/x86/LoadDynamic.go +++ b/src/x86/LoadDynamic.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // LoadDynamicRegister loads from memory with a register offset into a register. func LoadDynamicRegister(code []byte, destination cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { diff --git a/src/x86/LoadDynamic_test.go b/src/x86/LoadDynamic_test.go index cdd46f2..9638e80 100644 --- a/src/x86/LoadDynamic_test.go +++ b/src/x86/LoadDynamic_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestLoadDynamicRegister(t *testing.T) { diff --git a/src/x86/Load_test.go b/src/x86/Load_test.go index 32d4405..b81f6e0 100644 --- a/src/x86/Load_test.go +++ b/src/x86/Load_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestLoadRegister(t *testing.T) { diff --git a/src/x86/ModRM_test.go b/src/x86/ModRM_test.go index fd1151a..09af04a 100644 --- a/src/x86/ModRM_test.go +++ b/src/x86/ModRM_test.go @@ -3,8 +3,8 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestModRM(t *testing.T) { diff --git a/src/x86/Move.go b/src/x86/Move.go index fee16e1..efe8756 100644 --- a/src/x86/Move.go +++ b/src/x86/Move.go @@ -3,8 +3,8 @@ package x86 import ( "encoding/binary" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/sizeof" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/sizeof" ) // MoveRegisterNumber moves an integer into the given register. diff --git a/src/x86/Move_test.go b/src/x86/Move_test.go index 5c074ae..9ac8318 100644 --- a/src/x86/Move_test.go +++ b/src/x86/Move_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestMoveRegisterNumber(t *testing.T) { diff --git a/src/x86/Mul.go b/src/x86/Mul.go index f2b4dbb..cc6bc1b 100644 --- a/src/x86/Mul.go +++ b/src/x86/Mul.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // MulRegisterNumber multiplies a register with a number. func MulRegisterNumber(code []byte, register cpu.Register, number int) []byte { diff --git a/src/x86/Mul_test.go b/src/x86/Mul_test.go index 46618fe..cfde232 100644 --- a/src/x86/Mul_test.go +++ b/src/x86/Mul_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestMulRegisterNumber(t *testing.T) { diff --git a/src/x86/Negate.go b/src/x86/Negate.go index 141d105..cc55f66 100644 --- a/src/x86/Negate.go +++ b/src/x86/Negate.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // NegateRegister negates the value in the register. func NegateRegister(code []byte, register cpu.Register) []byte { diff --git a/src/x86/Negate_test.go b/src/x86/Negate_test.go index 0fcaeea..df02835 100644 --- a/src/x86/Negate_test.go +++ b/src/x86/Negate_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestNegateRegister(t *testing.T) { diff --git a/src/x86/Or.go b/src/x86/Or.go index 9ed61c4..66477d9 100644 --- a/src/x86/Or.go +++ b/src/x86/Or.go @@ -1,7 +1,7 @@ package x86 import ( - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // OrRegisterNumber performs a bitwise OR using a register and a number. diff --git a/src/x86/Or_test.go b/src/x86/Or_test.go index f26e4e7..bac71e4 100644 --- a/src/x86/Or_test.go +++ b/src/x86/Or_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestOrRegisterNumber(t *testing.T) { diff --git a/src/x86/Pop.go b/src/x86/Pop.go index c2037b4..8b8b0eb 100644 --- a/src/x86/Pop.go +++ b/src/x86/Pop.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // PopRegister pops a value from the stack and saves it into the register. func PopRegister(code []byte, register cpu.Register) []byte { diff --git a/src/x86/Pop_test.go b/src/x86/Pop_test.go index 268dcfc..65cb1aa 100644 --- a/src/x86/Pop_test.go +++ b/src/x86/Pop_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestPopRegister(t *testing.T) { diff --git a/src/x86/Push.go b/src/x86/Push.go index 15396c2..9a2c233 100644 --- a/src/x86/Push.go +++ b/src/x86/Push.go @@ -1,8 +1,8 @@ package x86 import ( - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/sizeof" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/sizeof" ) // PushNumber pushes a number onto the stack. diff --git a/src/x86/Push_test.go b/src/x86/Push_test.go index 3552fc3..30f1a68 100644 --- a/src/x86/Push_test.go +++ b/src/x86/Push_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestPushNumber(t *testing.T) { diff --git a/src/x86/REX_test.go b/src/x86/REX_test.go index 73f2290..3da7a67 100644 --- a/src/x86/REX_test.go +++ b/src/x86/REX_test.go @@ -3,8 +3,8 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestREX(t *testing.T) { diff --git a/src/x86/Registers.go b/src/x86/Registers.go index 9ed0df0..dde5260 100644 --- a/src/x86/Registers.go +++ b/src/x86/Registers.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" const ( RAX cpu.Register = iota diff --git a/src/x86/Registers_test.go b/src/x86/Registers_test.go index 54eb4d2..d1aadad 100644 --- a/src/x86/Registers_test.go +++ b/src/x86/Registers_test.go @@ -3,8 +3,8 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestRegisters(t *testing.T) { diff --git a/src/x86/SIB_test.go b/src/x86/SIB_test.go index c6dcb4b..c0437a2 100644 --- a/src/x86/SIB_test.go +++ b/src/x86/SIB_test.go @@ -3,8 +3,8 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestSIB(t *testing.T) { diff --git a/src/x86/Shift.go b/src/x86/Shift.go index 428e7aa..d5c6068 100644 --- a/src/x86/Shift.go +++ b/src/x86/Shift.go @@ -1,7 +1,7 @@ package x86 import ( - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // ShiftLeftNumber shifts the register value by `bitCount` bits to the left. diff --git a/src/x86/Shift_test.go b/src/x86/Shift_test.go index 2e80741..19fa94a 100644 --- a/src/x86/Shift_test.go +++ b/src/x86/Shift_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestShiftLeftNumber(t *testing.T) { diff --git a/src/x86/Store.go b/src/x86/Store.go index c0fc055..0f57b09 100644 --- a/src/x86/Store.go +++ b/src/x86/Store.go @@ -3,7 +3,7 @@ package x86 import ( "encoding/binary" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // StoreNumber stores a number into the memory address included in the given register. diff --git a/src/x86/StoreDynamic.go b/src/x86/StoreDynamic.go index 2cedf7e..6cb71e9 100644 --- a/src/x86/StoreDynamic.go +++ b/src/x86/StoreDynamic.go @@ -3,7 +3,7 @@ package x86 import ( "encoding/binary" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // StoreDynamicNumber stores a number into the memory address at `destination` with a register offset. diff --git a/src/x86/StoreDynamic_test.go b/src/x86/StoreDynamic_test.go index f5a957f..2e55105 100644 --- a/src/x86/StoreDynamic_test.go +++ b/src/x86/StoreDynamic_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestStoreDynamicNumber(t *testing.T) { diff --git a/src/x86/Store_test.go b/src/x86/Store_test.go index 7d34503..1e912c5 100644 --- a/src/x86/Store_test.go +++ b/src/x86/Store_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestStoreNumber(t *testing.T) { diff --git a/src/x86/Sub.go b/src/x86/Sub.go index 40bb4ac..bd5d2aa 100644 --- a/src/x86/Sub.go +++ b/src/x86/Sub.go @@ -1,7 +1,7 @@ package x86 import ( - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // SubRegisterNumber subtracts a number from the given register. diff --git a/src/x86/Sub_test.go b/src/x86/Sub_test.go index 7577b65..5502edc 100644 --- a/src/x86/Sub_test.go +++ b/src/x86/Sub_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestSubRegisterNumber(t *testing.T) { diff --git a/src/x86/Xor.go b/src/x86/Xor.go index 5c50ee6..45e8cc2 100644 --- a/src/x86/Xor.go +++ b/src/x86/Xor.go @@ -1,7 +1,7 @@ package x86 import ( - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // XorRegisterNumber performs a bitwise XOR using a register and a number. diff --git a/src/x86/Xor_test.go b/src/x86/Xor_test.go index dcf6afa..6456cb4 100644 --- a/src/x86/Xor_test.go +++ b/src/x86/Xor_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestXorRegisterNumber(t *testing.T) { diff --git a/src/x86/encode.go b/src/x86/encode.go index 1306d52..d09fd44 100644 --- a/src/x86/encode.go +++ b/src/x86/encode.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // encode is the core function that encodes an instruction. func encode(code []byte, mod AddressMode, reg cpu.Register, rm cpu.Register, numBytes byte, opCodes ...byte) []byte { diff --git a/src/x86/encodeNum.go b/src/x86/encodeNum.go index 1611604..467e26b 100644 --- a/src/x86/encodeNum.go +++ b/src/x86/encodeNum.go @@ -3,8 +3,8 @@ package x86 import ( "encoding/binary" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/sizeof" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/sizeof" ) // encodeNum encodes an instruction with up to two registers and a number parameter. diff --git a/src/x86/memoryAccess.go b/src/x86/memoryAccess.go index af526a0..3757ec4 100644 --- a/src/x86/memoryAccess.go +++ b/src/x86/memoryAccess.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // memoryAccess encodes a memory access. func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset int8, numBytes byte, source cpu.Register) []byte { diff --git a/src/x86/memoryAccessDynamic.go b/src/x86/memoryAccessDynamic.go index d47988e..8282eaa 100644 --- a/src/x86/memoryAccessDynamic.go +++ b/src/x86/memoryAccessDynamic.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // memoryAccessDynamic encodes a memory access using the value of a register as an offset. func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, destination cpu.Register, offset cpu.Register, numBytes byte, source cpu.Register) []byte { diff --git a/src/x86/x86_test.go b/src/x86/x86_test.go index 393b2fb..02e0560 100644 --- a/src/x86/x86_test.go +++ b/src/x86/x86_test.go @@ -3,8 +3,8 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestX86(t *testing.T) { diff --git a/tests/errors_test.go b/tests/errors_test.go index 781b662..c31056e 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -5,9 +5,9 @@ import ( "strings" "testing" - "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/build" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/go/assert" ) var errs = []struct { diff --git a/tests/examples_test.go b/tests/examples_test.go index 9d788f6..0bac2e9 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -4,9 +4,9 @@ import ( "path/filepath" "testing" - "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/build" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/go/assert" ) var examples = []struct { diff --git a/tests/programs_test.go b/tests/programs_test.go index 4dc257e..2650a88 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -7,9 +7,9 @@ import ( "strings" "testing" - "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/build" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/go/assert" ) var programs = []struct { From 91f34bc88f8fb7f65a45e446a0f6a0ee89c974a1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 25 Feb 2025 17:16:09 +0100 Subject: [PATCH 0830/1012] Updated module path --- docs/readme.md | 6 +++--- go.mod | 6 +++--- go.sum | 8 ++++---- main.go | 2 +- src/arm/Registers.go | 2 +- src/arm/Registers_test.go | 4 ++-- src/asm/Assembler.go | 2 +- src/asm/CanSkip.go | 2 +- src/asm/Memory.go | 2 +- src/asm/MemoryRegister.go | 2 +- src/asm/Register.go | 2 +- src/asm/RegisterLabel.go | 2 +- src/asm/RegisterNumber.go | 2 +- src/asm/RegisterRegister.go | 2 +- src/asmc/Finalize.go | 6 +++--- src/asmc/call.go | 4 ++-- src/asmc/codeOffset.go | 10 +++++----- src/asmc/compile.go | 4 ++-- src/asmc/compiler.go | 2 +- src/asmc/dllCall.go | 4 ++-- src/asmc/jump.go | 4 ++-- src/asmc/load.go | 4 ++-- src/asmc/move.go | 6 +++--- src/asmc/resolvePointers.go | 6 +++--- src/asmc/store.go | 6 +++--- src/ast/AST.go | 2 +- src/ast/Count.go | 2 +- src/ast/Parse.go | 4 ++-- src/ast/ast_test.go | 8 ++++---- src/ast/block.go | 6 +++--- src/ast/eachInstruction.go | 2 +- src/ast/helpers.go | 4 ++-- src/ast/parseCases.go | 6 +++--- src/ast/parseInstruction.go | 8 ++++---- src/ast/parseKeyword.go | 8 ++++---- src/build/Executable.go | 2 +- src/build/Run.go | 4 ++-- src/build/build_test.go | 6 +++--- src/cli/Build.go | 4 ++-- src/cli/Main_test.go | 6 +++--- src/cli/System.go | 2 +- src/compiler/Compile.go | 8 ++++---- src/compiler/CompileFunctions.go | 2 +- src/compiler/PrintInstructions.go | 2 +- src/compiler/PrintStatistics.go | 2 +- src/compiler/Result.go | 4 ++-- src/compiler/Write.go | 10 +++++----- src/compiler/eachFunction.go | 4 ++-- src/compiler/finalize.go | 10 +++++----- src/core/ArrayElementToRegister.go | 12 ++++++------ src/core/CallExtern.go | 10 +++++----- src/core/CallSafe.go | 4 ++-- src/core/CallToRegister.go | 8 ++++---- src/core/Compare.go | 8 ++++---- src/core/CompileAST.go | 2 +- src/core/CompileASTNode.go | 2 +- src/core/CompileAssert.go | 4 ++-- src/core/CompileAssign.go | 10 +++++----- src/core/CompileAssignArray.go | 8 ++++---- src/core/CompileAssignDivision.go | 14 +++++++------- src/core/CompileAssignField.go | 8 ++++---- src/core/CompileCall.go | 6 +++--- src/core/CompileCondition.go | 4 ++-- src/core/CompileDefinition.go | 10 +++++----- src/core/CompileDelete.go | 8 ++++---- src/core/CompileFor.go | 10 +++++----- src/core/CompileIf.go | 4 ++-- src/core/CompileLen.go | 8 ++++---- src/core/CompileLoop.go | 4 ++-- src/core/CompileMemoryStore.go | 6 +++--- src/core/CompileNew.go | 8 ++++---- src/core/CompileReturn.go | 4 ++-- src/core/CompileSwitch.go | 4 ++-- src/core/CompileSyscall.go | 2 +- src/core/CompileTokens.go | 4 ++-- src/core/Constant.go | 4 ++-- src/core/Define.go | 8 ++++---- src/core/Environment.go | 4 ++-- src/core/Evaluate.go | 10 +++++----- src/core/Execute.go | 8 ++++---- src/core/ExecuteLeaf.go | 6 +++--- src/core/ExecuteRegister.go | 8 ++++---- src/core/ExecuteRegisterNumber.go | 10 +++++----- src/core/ExecuteRegisterRegister.go | 10 +++++----- src/core/ExpressionToMemory.go | 10 +++++----- src/core/ExpressionToRegister.go | 12 ++++++------ src/core/ExpressionsToRegisters.go | 10 +++++----- src/core/Fold.go | 6 +++--- src/core/Function.go | 10 +++++----- src/core/Identifier.go | 2 +- src/core/JumpIfFalse.go | 4 ++-- src/core/JumpIfTrue.go | 4 ++-- src/core/NewFunction.go | 12 ++++++------ src/core/Parameter.go | 4 ++-- src/core/PeriodToRegister.go | 10 +++++----- src/core/PrintInstructions.go | 4 ++-- src/core/ResolveTypes.go | 10 +++++----- src/core/ToNumber.go | 6 +++--- src/core/TokenToRegister.go | 10 +++++----- src/core/UsesRegister.go | 8 ++++---- src/cpu/Register_test.go | 4 ++-- src/cpu/State_test.go | 4 ++-- src/data/Data_test.go | 4 ++-- src/elf/ELF.go | 4 ++-- src/elf/ELF_test.go | 2 +- src/errors/Error.go | 4 ++-- src/expression/Expression.go | 2 +- src/expression/Expression_test.go | 6 +++--- src/expression/List.go | 2 +- src/expression/Operator.go | 2 +- src/expression/Parse.go | 2 +- src/expression/bench_test.go | 4 ++-- src/fs/File.go | 2 +- src/fs/Import.go | 2 +- src/fs/Walk_test.go | 4 ++-- src/fs/bench_test.go | 4 ++-- src/macho/MachO.go | 4 ++-- src/macho/MachO_test.go | 2 +- src/pe/EXE.go | 6 +++--- src/pe/EXE_test.go | 2 +- src/pe/importLibraries.go | 2 +- src/register/AddLabel.go | 2 +- src/register/DLLCall.go | 2 +- src/register/FreeRegister.go | 2 +- src/register/Jump.go | 2 +- src/register/Machine.go | 6 +++--- src/register/MemoryLabel.go | 2 +- src/register/MemoryNumber.go | 2 +- src/register/MemoryRegister.go | 4 ++-- src/register/NewRegister.go | 2 +- src/register/Number.go | 2 +- src/register/Register.go | 4 ++-- src/register/RegisterIsUsed.go | 2 +- src/register/RegisterLabel.go | 4 ++-- src/register/RegisterNumber.go | 6 +++--- src/register/RegisterRegister.go | 4 ++-- src/register/SaveRegister.go | 4 ++-- src/register/UseRegister.go | 2 +- src/register/postInstruction.go | 2 +- src/riscv/Registers.go | 2 +- src/riscv/Registers_test.go | 4 ++-- src/scanner/Scan.go | 8 ++++---- src/scanner/Scanner.go | 6 +++--- src/scanner/queueDirectory.go | 4 ++-- src/scanner/scanConst.go | 8 ++++---- src/scanner/scanExtern.go | 6 +++--- src/scanner/scanFile.go | 6 +++--- src/scanner/scanFunction.go | 6 +++--- src/scanner/scanFunctionSignature.go | 8 ++++---- src/scanner/scanImport.go | 8 ++++---- src/scanner/scanStruct.go | 8 ++++---- src/scope/Scope.go | 2 +- src/scope/Stack.go | 6 +++--- src/scope/Variable.go | 4 ++-- src/sizeof/Signed_test.go | 4 ++-- src/sizeof/Unsigned_test.go | 4 ++-- src/token/Count_test.go | 4 ++-- src/token/List_test.go | 4 ++-- src/token/Token_test.go | 4 ++-- src/token/Tokenize_test.go | 4 ++-- src/token/bench_test.go | 2 +- src/types/Field.go | 2 +- src/types/types_test.go | 4 ++-- src/x86/Add.go | 2 +- src/x86/Add_test.go | 6 +++--- src/x86/And.go | 2 +- src/x86/And_test.go | 6 +++--- src/x86/Compare.go | 2 +- src/x86/Compare_test.go | 6 +++--- src/x86/Div.go | 2 +- src/x86/Div_test.go | 6 +++--- src/x86/Jump_test.go | 4 ++-- src/x86/Load.go | 2 +- src/x86/LoadDynamic.go | 2 +- src/x86/LoadDynamic_test.go | 6 +++--- src/x86/Load_test.go | 6 +++--- src/x86/ModRM_test.go | 4 ++-- src/x86/Move.go | 4 ++-- src/x86/Move_test.go | 6 +++--- src/x86/Mul.go | 2 +- src/x86/Mul_test.go | 6 +++--- src/x86/Negate.go | 2 +- src/x86/Negate_test.go | 6 +++--- src/x86/Or.go | 2 +- src/x86/Or_test.go | 6 +++--- src/x86/Pop.go | 2 +- src/x86/Pop_test.go | 6 +++--- src/x86/Push.go | 4 ++-- src/x86/Push_test.go | 6 +++--- src/x86/REX_test.go | 4 ++-- src/x86/Registers.go | 2 +- src/x86/Registers_test.go | 4 ++-- src/x86/SIB_test.go | 4 ++-- src/x86/Shift.go | 2 +- src/x86/Shift_test.go | 6 +++--- src/x86/Store.go | 2 +- src/x86/StoreDynamic.go | 2 +- src/x86/StoreDynamic_test.go | 6 +++--- src/x86/Store_test.go | 6 +++--- src/x86/Sub.go | 2 +- src/x86/Sub_test.go | 6 +++--- src/x86/Xor.go | 2 +- src/x86/Xor_test.go | 6 +++--- src/x86/encode.go | 2 +- src/x86/encodeNum.go | 4 ++-- src/x86/memoryAccess.go | 2 +- src/x86/memoryAccessDynamic.go | 2 +- src/x86/x86_test.go | 4 ++-- tests/errors_test.go | 6 +++--- tests/examples_test.go | 6 +++--- tests/programs_test.go | 6 +++--- 211 files changed, 506 insertions(+), 506 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 842942e..ac727ad 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -12,7 +12,7 @@ A programming language that compiles down to machine code. ## Installation ```shell -git clone https://git.akyoto.dev/cli/q +git clone https://git.urbach.dev/cli/q cd q go build ``` @@ -48,13 +48,13 @@ q build examples/hello --os windows ## Status `q` is under heavy development and not ready for production yet. -Feel free to [get in touch](https://akyoto.dev/contact) if you are interested in helping out. +Feel free to [get in touch](https://urbach.dev/contact) if you are interested in helping out. The biggest obstacle right now is the lack of funding. If you want to help out financially you can [donate towards the project](https://en.liberapay.com/akyoto). ## License -Please see the [license documentation](https://akyoto.dev/license). +Please see the [license documentation](https://urbach.dev/license). ## Copyright diff --git a/go.mod b/go.mod index baf1b38..c2ac09c 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ -module git.akyoto.dev/cli/q +module git.urbach.dev/cli/q go 1.24 require ( - git.akyoto.dev/go/assert v0.1.3 - git.akyoto.dev/go/color v0.1.3 + git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf + git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d ) require golang.org/x/sys v0.30.0 // indirect diff --git a/go.sum b/go.sum index 236fc35..51f26c6 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ -git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= -git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= -git.akyoto.dev/go/color v0.1.3 h1:kqOVYaPJJDHi8qEwTZkZecQaUENBToaLmiiV1Gg++GM= -git.akyoto.dev/go/color v0.1.3/go.mod h1:e00cRnX0fzFyIYEpAA7dCR/Hlk0/2YpXpVQaMT5Zoss= +git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf h1:BQWa5GKNUsA5CSUa/+UlFWYCEVe3IDDKRbVqBLK0mAE= +git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf/go.mod h1:y9jGII9JFiF1HNIju0u87OyPCt82xKCtqnAFyEreCDo= +git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d h1:j1ARCrjUYE/c1STH/s6UYGQoOXAkxXll4AFhTb8P2GE= +git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d/go.mod h1:UE8tQTrlWeVPKhfPZ9G5QrDFRhL9EoPazcDgfgd5Xsk= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/main.go b/main.go index 60febb9..99f6f29 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,7 @@ package main import ( "os" - "git.akyoto.dev/cli/q/src/cli" + "git.urbach.dev/cli/q/src/cli" ) func main() { diff --git a/src/arm/Registers.go b/src/arm/Registers.go index f9ca6b6..86d78d2 100644 --- a/src/arm/Registers.go +++ b/src/arm/Registers.go @@ -1,6 +1,6 @@ package arm -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" const ( X0 cpu.Register = iota diff --git a/src/arm/Registers_test.go b/src/arm/Registers_test.go index 5c44c94..50cdae9 100644 --- a/src/arm/Registers_test.go +++ b/src/arm/Registers_test.go @@ -3,8 +3,8 @@ package arm_test import ( "testing" - "git.akyoto.dev/cli/q/src/arm" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/go/assert" ) func TestRegisters(t *testing.T) { diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index 3f3cb88..8e32655 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -3,7 +3,7 @@ package asm import ( "maps" - "git.akyoto.dev/cli/q/src/data" + "git.urbach.dev/cli/q/src/data" ) // Assembler contains a list of instructions. diff --git a/src/asm/CanSkip.go b/src/asm/CanSkip.go index c707990..b0999e0 100644 --- a/src/asm/CanSkip.go +++ b/src/asm/CanSkip.go @@ -1,6 +1,6 @@ package asm -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // CanSkip returns true if the register/register operation can be skipped. func (a *Assembler) CanSkip(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool { diff --git a/src/asm/Memory.go b/src/asm/Memory.go index 1076405..903ff15 100644 --- a/src/asm/Memory.go +++ b/src/asm/Memory.go @@ -6,7 +6,7 @@ import ( "strconv" "strings" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) type Memory struct { diff --git a/src/asm/MemoryRegister.go b/src/asm/MemoryRegister.go index 1c9f51b..5e76305 100644 --- a/src/asm/MemoryRegister.go +++ b/src/asm/MemoryRegister.go @@ -3,7 +3,7 @@ package asm import ( "fmt" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // MemoryRegister operates with a memory address and a number. diff --git a/src/asm/Register.go b/src/asm/Register.go index 7188752..91be5e0 100644 --- a/src/asm/Register.go +++ b/src/asm/Register.go @@ -1,7 +1,7 @@ package asm import ( - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // Register operates with a single register. diff --git a/src/asm/RegisterLabel.go b/src/asm/RegisterLabel.go index 8092d9f..402954b 100644 --- a/src/asm/RegisterLabel.go +++ b/src/asm/RegisterLabel.go @@ -3,7 +3,7 @@ package asm import ( "fmt" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // RegisterLabel operates with a register and a label. diff --git a/src/asm/RegisterNumber.go b/src/asm/RegisterNumber.go index 6addf74..f12bb81 100644 --- a/src/asm/RegisterNumber.go +++ b/src/asm/RegisterNumber.go @@ -3,7 +3,7 @@ package asm import ( "fmt" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // RegisterNumber operates with a register and a number. diff --git a/src/asm/RegisterRegister.go b/src/asm/RegisterRegister.go index c133e2c..dadafd1 100644 --- a/src/asm/RegisterRegister.go +++ b/src/asm/RegisterRegister.go @@ -3,7 +3,7 @@ package asm import ( "fmt" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // RegisterRegister operates with two registers. diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index 377b777..f73a1e8 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -1,9 +1,9 @@ package asmc import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/dll" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/dll" ) // Finalize generates the final machine code. diff --git a/src/asmc/call.go b/src/asmc/call.go index cf84156..6c78a09 100644 --- a/src/asmc/call.go +++ b/src/asmc/call.go @@ -1,8 +1,8 @@ package asmc import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/x86" ) func (c *compiler) call(x asm.Instruction) { diff --git a/src/asmc/codeOffset.go b/src/asmc/codeOffset.go index 306adc0..9142762 100644 --- a/src/asmc/codeOffset.go +++ b/src/asmc/codeOffset.go @@ -1,11 +1,11 @@ package asmc import ( - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/elf" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/macho" - "git.akyoto.dev/cli/q/src/pe" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/elf" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/macho" + "git.urbach.dev/cli/q/src/pe" ) // codeOffset returns the file offset of the code section. diff --git a/src/asmc/compile.go b/src/asmc/compile.go index 7cec3b2..dfcddca 100644 --- a/src/asmc/compile.go +++ b/src/asmc/compile.go @@ -1,8 +1,8 @@ package asmc import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/x86" ) func (c *compiler) compile(x asm.Instruction) { diff --git a/src/asmc/compiler.go b/src/asmc/compiler.go index fd17c89..b4e30e6 100644 --- a/src/asmc/compiler.go +++ b/src/asmc/compiler.go @@ -1,6 +1,6 @@ package asmc -import "git.akyoto.dev/cli/q/src/dll" +import "git.urbach.dev/cli/q/src/dll" type compiler struct { code []byte diff --git a/src/asmc/dllCall.go b/src/asmc/dllCall.go index bd37582..75523d1 100644 --- a/src/asmc/dllCall.go +++ b/src/asmc/dllCall.go @@ -3,8 +3,8 @@ package asmc import ( "strings" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/x86" ) func (c *compiler) dllCall(x asm.Instruction) { diff --git a/src/asmc/jump.go b/src/asmc/jump.go index f01f547..853bd81 100644 --- a/src/asmc/jump.go +++ b/src/asmc/jump.go @@ -1,8 +1,8 @@ package asmc import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/x86" ) func (c *compiler) jump(x asm.Instruction) { diff --git a/src/asmc/load.go b/src/asmc/load.go index e460a82..7a3a707 100644 --- a/src/asmc/load.go +++ b/src/asmc/load.go @@ -3,8 +3,8 @@ package asmc import ( "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/x86" ) func (c *compiler) load(x asm.Instruction) { diff --git a/src/asmc/move.go b/src/asmc/move.go index b4f76ae..71e71f3 100644 --- a/src/asmc/move.go +++ b/src/asmc/move.go @@ -3,9 +3,9 @@ package asmc import ( "strings" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/x86" ) func (c *compiler) move(x asm.Instruction) { diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index 112a0d1..2e6d30f 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -5,9 +5,9 @@ import ( "fmt" "slices" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/sizeof" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/sizeof" ) // resolvePointers resolves the addresses of all pointers within the code and writes the correct addresses to the code slice. diff --git a/src/asmc/store.go b/src/asmc/store.go index b5e8f0d..aa81960 100644 --- a/src/asmc/store.go +++ b/src/asmc/store.go @@ -3,9 +3,9 @@ package asmc import ( "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/x86" ) func (c *compiler) store(x asm.Instruction) { diff --git a/src/ast/AST.go b/src/ast/AST.go index 99f1ed1..8aef14c 100644 --- a/src/ast/AST.go +++ b/src/ast/AST.go @@ -1,6 +1,6 @@ package ast -import "git.akyoto.dev/cli/q/src/expression" +import "git.urbach.dev/cli/q/src/expression" // Node is an interface used for all types of AST nodes. type Node any diff --git a/src/ast/Count.go b/src/ast/Count.go index 83e7f69..c0408d9 100644 --- a/src/ast/Count.go +++ b/src/ast/Count.go @@ -1,6 +1,6 @@ package ast -import "git.akyoto.dev/cli/q/src/token" +import "git.urbach.dev/cli/q/src/token" // Count counts how often the given token appears in the AST. func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 { diff --git a/src/ast/Parse.go b/src/ast/Parse.go index 84c9972..20634e4 100644 --- a/src/ast/Parse.go +++ b/src/ast/Parse.go @@ -1,8 +1,8 @@ package ast import ( - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // Parse generates an AST from a list of tokens. diff --git a/src/ast/ast_test.go b/src/ast/ast_test.go index 621ab6e..33730dc 100644 --- a/src/ast/ast_test.go +++ b/src/ast/ast_test.go @@ -3,10 +3,10 @@ package ast_test import ( "testing" - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/go/assert" ) func TestAssign(t *testing.T) { diff --git a/src/ast/block.go b/src/ast/block.go index da58e2a..36c1933 100644 --- a/src/ast/block.go +++ b/src/ast/block.go @@ -1,9 +1,9 @@ package ast import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // block retrieves the start and end position of a block. diff --git a/src/ast/eachInstruction.go b/src/ast/eachInstruction.go index b593ebc..0bed958 100644 --- a/src/ast/eachInstruction.go +++ b/src/ast/eachInstruction.go @@ -1,6 +1,6 @@ package ast -import "git.akyoto.dev/cli/q/src/token" +import "git.urbach.dev/cli/q/src/token" // eachInstruction calls the function on each AST node. func eachInstruction(tokens token.List, call func(token.List) error) error { diff --git a/src/ast/helpers.go b/src/ast/helpers.go index ffb2bf5..f21ac47 100644 --- a/src/ast/helpers.go +++ b/src/ast/helpers.go @@ -1,8 +1,8 @@ package ast import ( - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // IsAssignment returns true if the expression is an assignment. diff --git a/src/ast/parseCases.go b/src/ast/parseCases.go index da6e561..dbb5412 100644 --- a/src/ast/parseCases.go +++ b/src/ast/parseCases.go @@ -1,9 +1,9 @@ package ast import ( - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // parseCases generates the cases inside a switch statement. diff --git a/src/ast/parseInstruction.go b/src/ast/parseInstruction.go index c14db17..bffd14a 100644 --- a/src/ast/parseInstruction.go +++ b/src/ast/parseInstruction.go @@ -1,10 +1,10 @@ package ast import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // parseInstruction generates an AST node from an instruction. diff --git a/src/ast/parseKeyword.go b/src/ast/parseKeyword.go index c012e38..0ad1442 100644 --- a/src/ast/parseKeyword.go +++ b/src/ast/parseKeyword.go @@ -1,10 +1,10 @@ package ast import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // parseKeyword generates a keyword node from an instruction. diff --git a/src/build/Executable.go b/src/build/Executable.go index 4eff03e..8b62a7e 100644 --- a/src/build/Executable.go +++ b/src/build/Executable.go @@ -4,7 +4,7 @@ import ( "path/filepath" "strings" - "git.akyoto.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/config" ) // Executable returns the path to the executable. diff --git a/src/build/Run.go b/src/build/Run.go index bbc06d8..2fd33d7 100644 --- a/src/build/Run.go +++ b/src/build/Run.go @@ -1,8 +1,8 @@ package build import ( - "git.akyoto.dev/cli/q/src/compiler" - "git.akyoto.dev/cli/q/src/scanner" + "git.urbach.dev/cli/q/src/compiler" + "git.urbach.dev/cli/q/src/scanner" ) // Run compiles the input files. diff --git a/src/build/build_test.go b/src/build/build_test.go index e442e12..87600b0 100644 --- a/src/build/build_test.go +++ b/src/build/build_test.go @@ -4,9 +4,9 @@ import ( "path/filepath" "testing" - "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/build" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/go/assert" ) func TestBuildDirectory(t *testing.T) { diff --git a/src/cli/Build.go b/src/cli/Build.go index 82daf25..1614bdc 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -6,8 +6,8 @@ import ( "runtime" "strings" - "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/build" + "git.urbach.dev/cli/q/src/config" ) // Build parses the arguments and creates a build. diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index b27a964..236ef5a 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -3,9 +3,9 @@ package cli_test import ( "testing" - "git.akyoto.dev/cli/q/src/cli" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cli" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/go/assert" ) func TestCLI(t *testing.T) { diff --git a/src/cli/System.go b/src/cli/System.go index a2a6801..06e5ee2 100644 --- a/src/cli/System.go +++ b/src/cli/System.go @@ -5,7 +5,7 @@ import ( "runtime" "strconv" - "git.akyoto.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/config" ) // System shows system information. diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index cc56464..f62943c 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -1,10 +1,10 @@ package compiler import ( - "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/types" ) // Compile waits for the scan to finish and compiles all functions. diff --git a/src/compiler/CompileFunctions.go b/src/compiler/CompileFunctions.go index d4ee25b..05ab228 100644 --- a/src/compiler/CompileFunctions.go +++ b/src/compiler/CompileFunctions.go @@ -3,7 +3,7 @@ package compiler import ( "sync" - "git.akyoto.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/core" ) // CompileFunctions starts a goroutine for each function compilation and waits for completion. diff --git a/src/compiler/PrintInstructions.go b/src/compiler/PrintInstructions.go index 3d92da2..39eb9db 100644 --- a/src/compiler/PrintInstructions.go +++ b/src/compiler/PrintInstructions.go @@ -1,6 +1,6 @@ package compiler -import "git.akyoto.dev/cli/q/src/core" +import "git.urbach.dev/cli/q/src/core" // PrintInstructions prints out the generated instructions. func (r *Result) PrintInstructions() { diff --git a/src/compiler/PrintStatistics.go b/src/compiler/PrintStatistics.go index 65b9066..14c3e26 100644 --- a/src/compiler/PrintStatistics.go +++ b/src/compiler/PrintStatistics.go @@ -3,7 +3,7 @@ package compiler import ( "fmt" - "git.akyoto.dev/go/color/ansi" + "git.urbach.dev/go/color/ansi" ) // PrintStatistics shows the statistics. diff --git a/src/compiler/Result.go b/src/compiler/Result.go index ce94da8..b9bbb7b 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -1,8 +1,8 @@ package compiler import ( - "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/dll" + "git.urbach.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/dll" ) // Result contains everything we need to write an executable file to disk. diff --git a/src/compiler/Write.go b/src/compiler/Write.go index 70c1e96..bb14745 100644 --- a/src/compiler/Write.go +++ b/src/compiler/Write.go @@ -4,11 +4,11 @@ import ( "bufio" "io" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/elf" - "git.akyoto.dev/cli/q/src/macho" - "git.akyoto.dev/cli/q/src/pe" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/dll" + "git.urbach.dev/cli/q/src/elf" + "git.urbach.dev/cli/q/src/macho" + "git.urbach.dev/cli/q/src/pe" ) // Write writes the executable to the given writer. diff --git a/src/compiler/eachFunction.go b/src/compiler/eachFunction.go index 03973f4..de7fc63 100644 --- a/src/compiler/eachFunction.go +++ b/src/compiler/eachFunction.go @@ -1,8 +1,8 @@ package compiler import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/core" ) // eachFunction recursively finds all the calls to external functions. diff --git a/src/compiler/finalize.go b/src/compiler/finalize.go index e82a049..6437913 100644 --- a/src/compiler/finalize.go +++ b/src/compiler/finalize.go @@ -1,11 +1,11 @@ package compiler import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/asmc" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/asmc" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/x86" ) // finalize generates the final machine code. diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go index 924f16f..0dec215 100644 --- a/src/core/ArrayElementToRegister.go +++ b/src/core/ArrayElementToRegister.go @@ -3,12 +3,12 @@ package core import ( "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // ArrayElementToRegister moves the value of an array element into the given register. diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go index 1413a45..2b17fc1 100644 --- a/src/core/CallExtern.go +++ b/src/core/CallExtern.go @@ -4,11 +4,11 @@ import ( "fmt" "slices" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/x86" ) // CallExtern calls an external function. diff --git a/src/core/CallSafe.go b/src/core/CallSafe.go index f6756b4..20f4676 100644 --- a/src/core/CallSafe.go +++ b/src/core/CallSafe.go @@ -3,8 +3,8 @@ package core import ( "slices" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" ) // CallSafe pushes used registers to the stack, executes the call and restores the original register value. diff --git a/src/core/CallToRegister.go b/src/core/CallToRegister.go index 5b9901e..9137790 100644 --- a/src/core/CallToRegister.go +++ b/src/core/CallToRegister.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" ) // CallToRegister moves the result of a function call into the given register. diff --git a/src/core/Compare.go b/src/core/Compare.go index 0244335..731165f 100644 --- a/src/core/Compare.go +++ b/src/core/Compare.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // Compare evaluates a boolean expression. diff --git a/src/core/CompileAST.go b/src/core/CompileAST.go index a9d54f5..b509146 100644 --- a/src/core/CompileAST.go +++ b/src/core/CompileAST.go @@ -1,7 +1,7 @@ package core import ( - "git.akyoto.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/ast" ) // CompileAST compiles an abstract syntax tree. diff --git a/src/core/CompileASTNode.go b/src/core/CompileASTNode.go index a32a321..057b127 100644 --- a/src/core/CompileASTNode.go +++ b/src/core/CompileASTNode.go @@ -1,7 +1,7 @@ package core import ( - "git.akyoto.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/ast" ) // CompileASTNode compiles a node in the AST. diff --git a/src/core/CompileAssert.go b/src/core/CompileAssert.go index f3db795..a761982 100644 --- a/src/core/CompileAssert.go +++ b/src/core/CompileAssert.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" ) // CompileAssert compiles an assertion. diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index bbc237b..2ff6dfb 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // CompileAssign compiles an assign statement. diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 2e5d8be..55e33a5 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -3,10 +3,10 @@ package core import ( "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/types" ) // CompileAssignArray compiles an assign statement for array elements. diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index c93d90b..cbca794 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -1,13 +1,13 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/scope" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/scope" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/x86" ) // CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right. diff --git a/src/core/CompileAssignField.go b/src/core/CompileAssignField.go index 412be15..31310d2 100644 --- a/src/core/CompileAssignField.go +++ b/src/core/CompileAssignField.go @@ -3,10 +3,10 @@ package core import ( "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/types" ) // CompileAssignField compiles a memory write to a struct field. diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 9089335..3d4ad40 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" ) // CompileCall executes a function call. diff --git a/src/core/CompileCondition.go b/src/core/CompileCondition.go index 0a31b5a..a0c8ffe 100644 --- a/src/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // CompileCondition inserts code to jump to the start label or end label depending on the truth of the condition. diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index 6b46991..71d4b73 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // CompileDefinition compiles a variable definition. diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index f768dab..05966d9 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" ) // CompileDelete compiles a `delete` function call which deallocates a struct. diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index b9a6d7f..9f77413 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // CompileFor compiles a for loop. diff --git a/src/core/CompileIf.go b/src/core/CompileIf.go index 61fb3c6..1474eff 100644 --- a/src/core/CompileIf.go +++ b/src/core/CompileIf.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" ) // CompileIf compiles a branch instruction. diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index e7264f3..4065d76 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -3,10 +3,10 @@ package core import ( "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" ) var _len = Function{OutputTypes: []types.Type{types.Int}} diff --git a/src/core/CompileLoop.go b/src/core/CompileLoop.go index 13ccf90..923e690 100644 --- a/src/core/CompileLoop.go +++ b/src/core/CompileLoop.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" ) // CompileLoop compiles an infinite loop. diff --git a/src/core/CompileMemoryStore.go b/src/core/CompileMemoryStore.go index 1277268..a1bb856 100644 --- a/src/core/CompileMemoryStore.go +++ b/src/core/CompileMemoryStore.go @@ -3,9 +3,9 @@ package core import ( "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" ) // CompileMemoryStore compiles a variable-width store to memory. diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index 2589f3f..52a1649 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" ) // CompileNew compiles a `new` function call which allocates a struct. diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index cb2ffe6..cd15afe 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/errors" ) // CompileReturn compiles a return instruction. diff --git a/src/core/CompileSwitch.go b/src/core/CompileSwitch.go index e62b8ca..7196f8a 100644 --- a/src/core/CompileSwitch.go +++ b/src/core/CompileSwitch.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/ast" ) // CompileSwitch compiles a multi-branch instruction. diff --git a/src/core/CompileSyscall.go b/src/core/CompileSyscall.go index a4edf16..dcfb42d 100644 --- a/src/core/CompileSyscall.go +++ b/src/core/CompileSyscall.go @@ -1,7 +1,7 @@ package core import ( - "git.akyoto.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/expression" ) // CompileSyscall executes a kernel syscall. diff --git a/src/core/CompileTokens.go b/src/core/CompileTokens.go index 2aa390c..beef1b6 100644 --- a/src/core/CompileTokens.go +++ b/src/core/CompileTokens.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/token" ) // CompileTokens compiles a token list. diff --git a/src/core/Constant.go b/src/core/Constant.go index b5bb523..0800947 100644 --- a/src/core/Constant.go +++ b/src/core/Constant.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // Constant registers a single value to be accessible under a descriptive name. diff --git a/src/core/Define.go b/src/core/Define.go index d017d85..eacd340 100644 --- a/src/core/Define.go +++ b/src/core/Define.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/scope" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/scope" + "git.urbach.dev/cli/q/src/token" ) // Define defines a new variable. diff --git a/src/core/Environment.go b/src/core/Environment.go index 87b1a29..e2a1354 100644 --- a/src/core/Environment.go +++ b/src/core/Environment.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/types" ) // Environment holds information about the entire build. diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 68da992..5e36fec 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. diff --git a/src/core/Execute.go b/src/core/Execute.go index 74132b8..8d01a4e 100644 --- a/src/core/Execute.go +++ b/src/core/Execute.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // Execute executes an operation on a register with a value operand. diff --git a/src/core/ExecuteLeaf.go b/src/core/ExecuteLeaf.go index 32feb13..06310ae 100644 --- a/src/core/ExecuteLeaf.go +++ b/src/core/ExecuteLeaf.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/token" ) // ExecuteLeaf performs an operation on a register with the given leaf operand. diff --git a/src/core/ExecuteRegister.go b/src/core/ExecuteRegister.go index 8a48bd5..6920310 100644 --- a/src/core/ExecuteRegister.go +++ b/src/core/ExecuteRegister.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/token" ) // ExecuteRegister performs an operation on a single register. diff --git a/src/core/ExecuteRegisterNumber.go b/src/core/ExecuteRegisterNumber.go index 0176f82..a3acbb5 100644 --- a/src/core/ExecuteRegisterNumber.go +++ b/src/core/ExecuteRegisterNumber.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/x86" ) // ExecuteRegisterNumber performs an operation on a register and a number. diff --git a/src/core/ExecuteRegisterRegister.go b/src/core/ExecuteRegisterRegister.go index 3e699fb..5b59667 100644 --- a/src/core/ExecuteRegisterRegister.go +++ b/src/core/ExecuteRegisterRegister.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/x86" ) // ExecuteRegisterRegister performs an operation on two registers. diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index 461cc1c..f62ba58 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // ExpressionToMemory puts the result of an expression into the specified memory address. diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 785cb91..0a3fb29 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -1,12 +1,12 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // ExpressionToRegister puts the result of an expression into the specified register. diff --git a/src/core/ExpressionsToRegisters.go b/src/core/ExpressionsToRegisters.go index f6a21f3..7156daa 100644 --- a/src/core/ExpressionsToRegisters.go +++ b/src/core/ExpressionsToRegisters.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // ExpressionsToRegisters moves multiple expressions into the specified registers and checks that the types match with the function signature. diff --git a/src/core/Fold.go b/src/core/Fold.go index 6245114..768f2ec 100644 --- a/src/core/Fold.go +++ b/src/core/Fold.go @@ -1,9 +1,9 @@ package core import ( - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // Fold will try to precalculate the results of operations with constants. diff --git a/src/core/Function.go b/src/core/Function.go index f159815..dca269e 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/register" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/dll" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/register" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // Function represents the smallest unit of code. diff --git a/src/core/Identifier.go b/src/core/Identifier.go index 6a8a148..ab51f9e 100644 --- a/src/core/Identifier.go +++ b/src/core/Identifier.go @@ -3,7 +3,7 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/scope" + "git.urbach.dev/cli/q/src/scope" ) // Identifier looks up an identifier which can be a variable or a function. diff --git a/src/core/JumpIfFalse.go b/src/core/JumpIfFalse.go index 44eb1ba..61c649d 100644 --- a/src/core/JumpIfFalse.go +++ b/src/core/JumpIfFalse.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/token" ) // JumpIfFalse jumps to the label if the previous comparison was false. diff --git a/src/core/JumpIfTrue.go b/src/core/JumpIfTrue.go index c8dc487..5c8eddc 100644 --- a/src/core/JumpIfTrue.go +++ b/src/core/JumpIfTrue.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/token" ) // JumpIfTrue jumps to the label if the previous comparison was true. diff --git a/src/core/NewFunction.go b/src/core/NewFunction.go index 3da5ed5..dabb185 100644 --- a/src/core/NewFunction.go +++ b/src/core/NewFunction.go @@ -1,12 +1,12 @@ package core import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/register" - "git.akyoto.dev/cli/q/src/scope" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/register" + "git.urbach.dev/cli/q/src/scope" + "git.urbach.dev/cli/q/src/x86" ) // NewFunction creates a new function. diff --git a/src/core/Parameter.go b/src/core/Parameter.go index 1375a09..8c89249 100644 --- a/src/core/Parameter.go +++ b/src/core/Parameter.go @@ -1,8 +1,8 @@ package core import ( - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) type Parameter struct { diff --git a/src/core/PeriodToRegister.go b/src/core/PeriodToRegister.go index c897853..2307a43 100644 --- a/src/core/PeriodToRegister.go +++ b/src/core/PeriodToRegister.go @@ -4,11 +4,11 @@ import ( "fmt" "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" ) // PeriodToRegister moves a constant or a function address into the given register. diff --git a/src/core/PrintInstructions.go b/src/core/PrintInstructions.go index 00fcf7c..69cfc26 100644 --- a/src/core/PrintInstructions.go +++ b/src/core/PrintInstructions.go @@ -4,8 +4,8 @@ import ( "bytes" "fmt" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/go/color/ansi" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/go/color/ansi" ) // PrintInstructions shows the assembly instructions. diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index 2a5bf08..42382fb 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -1,11 +1,11 @@ package core import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/scope" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/cli/q/src/x86" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/scope" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/x86" ) // ResolveTypes parses the input and output types. diff --git a/src/core/ToNumber.go b/src/core/ToNumber.go index 122b04d..fc891b4 100644 --- a/src/core/ToNumber.go +++ b/src/core/ToNumber.go @@ -5,9 +5,9 @@ import ( "strings" "unicode/utf8" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // ToNumber tries to convert the token into a numeric value. diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index e8dd66e..a8128eb 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -3,11 +3,11 @@ package core import ( "encoding/binary" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // TokenToRegister moves a token into a register. diff --git a/src/core/UsesRegister.go b/src/core/UsesRegister.go index 74519bd..df66b67 100644 --- a/src/core/UsesRegister.go +++ b/src/core/UsesRegister.go @@ -1,10 +1,10 @@ package core import ( - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) // UsesRegister returns true if evaluating the expression would write or read the given register. diff --git a/src/cpu/Register_test.go b/src/cpu/Register_test.go index e14f6f0..1dc6b20 100644 --- a/src/cpu/Register_test.go +++ b/src/cpu/Register_test.go @@ -3,8 +3,8 @@ package cpu_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" ) func TestRegisterString(t *testing.T) { diff --git a/src/cpu/State_test.go b/src/cpu/State_test.go index 52b1fa9..6fdbf0b 100644 --- a/src/cpu/State_test.go +++ b/src/cpu/State_test.go @@ -3,8 +3,8 @@ package cpu_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" ) func TestRegisterState(t *testing.T) { diff --git a/src/data/Data_test.go b/src/data/Data_test.go index b8d0c60..69453e9 100644 --- a/src/data/Data_test.go +++ b/src/data/Data_test.go @@ -3,8 +3,8 @@ package data_test import ( "testing" - "git.akyoto.dev/cli/q/src/data" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/data" + "git.urbach.dev/go/assert" ) func TestInterning(t *testing.T) { diff --git a/src/elf/ELF.go b/src/elf/ELF.go index 0248c7e..d9e8d52 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -5,8 +5,8 @@ import ( "encoding/binary" "io" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/fs" ) const HeaderEnd = HeaderSize + ProgramHeaderSize*2 diff --git a/src/elf/ELF_test.go b/src/elf/ELF_test.go index c813536..74bd71c 100644 --- a/src/elf/ELF_test.go +++ b/src/elf/ELF_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/elf" + "git.urbach.dev/cli/q/src/elf" ) func TestWrite(t *testing.T) { diff --git a/src/errors/Error.go b/src/errors/Error.go index d23d7a0..f2c0629 100644 --- a/src/errors/Error.go +++ b/src/errors/Error.go @@ -5,8 +5,8 @@ import ( "os" "path/filepath" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // Error is a compiler error at a given line and column. diff --git a/src/expression/Expression.go b/src/expression/Expression.go index 2344a37..aecaa77 100644 --- a/src/expression/Expression.go +++ b/src/expression/Expression.go @@ -3,7 +3,7 @@ package expression import ( "strings" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/token" ) // Expression is a binary tree with an operator on each node. diff --git a/src/expression/Expression_test.go b/src/expression/Expression_test.go index df62868..cfd2ea9 100644 --- a/src/expression/Expression_test.go +++ b/src/expression/Expression_test.go @@ -4,9 +4,9 @@ import ( "errors" "testing" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/go/assert" ) func TestParse(t *testing.T) { diff --git a/src/expression/List.go b/src/expression/List.go index 6431451..b05058b 100644 --- a/src/expression/List.go +++ b/src/expression/List.go @@ -1,7 +1,7 @@ package expression import ( - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/token" ) // NewList generates a list of expressions from comma separated parameters. diff --git a/src/expression/Operator.go b/src/expression/Operator.go index 800277a..63f77c1 100644 --- a/src/expression/Operator.go +++ b/src/expression/Operator.go @@ -3,7 +3,7 @@ package expression import ( "math" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/token" ) // Operator represents an operator for mathematical expressions. diff --git a/src/expression/Parse.go b/src/expression/Parse.go index f839ac8..750fd29 100644 --- a/src/expression/Parse.go +++ b/src/expression/Parse.go @@ -3,7 +3,7 @@ package expression import ( "math" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/token" ) // Parse generates an expression tree from tokens. diff --git a/src/expression/bench_test.go b/src/expression/bench_test.go index 7353cce..ecbf0ff 100644 --- a/src/expression/bench_test.go +++ b/src/expression/bench_test.go @@ -3,8 +3,8 @@ package expression_test import ( "testing" - "git.akyoto.dev/cli/q/src/expression" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" ) func BenchmarkExpression(b *testing.B) { diff --git a/src/fs/File.go b/src/fs/File.go index 76a43bb..ac4685b 100644 --- a/src/fs/File.go +++ b/src/fs/File.go @@ -1,6 +1,6 @@ package fs -import "git.akyoto.dev/cli/q/src/token" +import "git.urbach.dev/cli/q/src/token" // File represents a single source file. type File struct { diff --git a/src/fs/Import.go b/src/fs/Import.go index ce9032e..368c686 100644 --- a/src/fs/Import.go +++ b/src/fs/Import.go @@ -1,6 +1,6 @@ package fs -import "git.akyoto.dev/cli/q/src/token" +import "git.urbach.dev/cli/q/src/token" // Import represents an import statement in a file. type Import struct { diff --git a/src/fs/Walk_test.go b/src/fs/Walk_test.go index c63f62b..c6e526b 100644 --- a/src/fs/Walk_test.go +++ b/src/fs/Walk_test.go @@ -3,8 +3,8 @@ package fs_test import ( "testing" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/go/assert" ) func TestWalk(t *testing.T) { diff --git a/src/fs/bench_test.go b/src/fs/bench_test.go index 3462acd..096343a 100644 --- a/src/fs/bench_test.go +++ b/src/fs/bench_test.go @@ -4,8 +4,8 @@ import ( "os" "testing" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/go/assert" ) func BenchmarkReadDir(b *testing.B) { diff --git a/src/macho/MachO.go b/src/macho/MachO.go index 17d9564..1c2cf03 100644 --- a/src/macho/MachO.go +++ b/src/macho/MachO.go @@ -5,8 +5,8 @@ import ( "encoding/binary" "io" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/fs" ) const ( diff --git a/src/macho/MachO_test.go b/src/macho/MachO_test.go index a026960..df2d04c 100644 --- a/src/macho/MachO_test.go +++ b/src/macho/MachO_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/macho" + "git.urbach.dev/cli/q/src/macho" ) func TestWrite(t *testing.T) { diff --git a/src/pe/EXE.go b/src/pe/EXE.go index bfbaef9..c34b616 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -5,9 +5,9 @@ import ( "encoding/binary" "io" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/dll" - "git.akyoto.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/dll" + "git.urbach.dev/cli/q/src/fs" ) const ( diff --git a/src/pe/EXE_test.go b/src/pe/EXE_test.go index 64a6356..d69d46a 100644 --- a/src/pe/EXE_test.go +++ b/src/pe/EXE_test.go @@ -4,7 +4,7 @@ import ( "io" "testing" - "git.akyoto.dev/cli/q/src/pe" + "git.urbach.dev/cli/q/src/pe" ) func TestWrite(t *testing.T) { diff --git a/src/pe/importLibraries.go b/src/pe/importLibraries.go index d7c8d32..dabc2a2 100644 --- a/src/pe/importLibraries.go +++ b/src/pe/importLibraries.go @@ -1,6 +1,6 @@ package pe -import "git.akyoto.dev/cli/q/src/dll" +import "git.urbach.dev/cli/q/src/dll" // importLibraries generates the import address table which contains the addresses of functions imported from DLLs. func importLibraries(dlls dll.List, importsStart int) ([]uint64, []byte, []DLLImport, int) { diff --git a/src/register/AddLabel.go b/src/register/AddLabel.go index 4667275..797bb30 100644 --- a/src/register/AddLabel.go +++ b/src/register/AddLabel.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/asm" +import "git.urbach.dev/cli/q/src/asm" func (f *Machine) AddLabel(label string) { f.Assembler.Label(asm.LABEL, label) diff --git a/src/register/DLLCall.go b/src/register/DLLCall.go index c62eb55..4b80f04 100644 --- a/src/register/DLLCall.go +++ b/src/register/DLLCall.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/asm" +import "git.urbach.dev/cli/q/src/asm" func (f *Machine) DLLCall(label string) { f.Assembler.Label(asm.DLLCALL, label) diff --git a/src/register/FreeRegister.go b/src/register/FreeRegister.go index 4fd016e..bff1630 100644 --- a/src/register/FreeRegister.go +++ b/src/register/FreeRegister.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // FreeRegister frees a register. func (f *Machine) FreeRegister(register cpu.Register) { diff --git a/src/register/Jump.go b/src/register/Jump.go index 98045fd..ccf1f7c 100644 --- a/src/register/Jump.go +++ b/src/register/Jump.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/asm" +import "git.urbach.dev/cli/q/src/asm" func (f *Machine) Jump(mnemonic asm.Mnemonic, label string) { f.Assembler.Label(mnemonic, label) diff --git a/src/register/Machine.go b/src/register/Machine.go index 07f64b2..69f307d 100644 --- a/src/register/Machine.go +++ b/src/register/Machine.go @@ -1,9 +1,9 @@ package register import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/scope" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/scope" ) // Machine is a register usage aware assembler. diff --git a/src/register/MemoryLabel.go b/src/register/MemoryLabel.go index 98deda9..520ad5e 100644 --- a/src/register/MemoryLabel.go +++ b/src/register/MemoryLabel.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/asm" +import "git.urbach.dev/cli/q/src/asm" func (f *Machine) MemoryLabel(mnemonic asm.Mnemonic, a asm.Memory, b string) { f.Assembler.MemoryLabel(mnemonic, a, b) diff --git a/src/register/MemoryNumber.go b/src/register/MemoryNumber.go index 2c1177f..6a00bf7 100644 --- a/src/register/MemoryNumber.go +++ b/src/register/MemoryNumber.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/asm" +import "git.urbach.dev/cli/q/src/asm" func (f *Machine) MemoryNumber(mnemonic asm.Mnemonic, a asm.Memory, b int) { f.Assembler.MemoryNumber(mnemonic, a, b) diff --git a/src/register/MemoryRegister.go b/src/register/MemoryRegister.go index fa13e9f..66fbe24 100644 --- a/src/register/MemoryRegister.go +++ b/src/register/MemoryRegister.go @@ -1,8 +1,8 @@ package register import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" ) func (f *Machine) MemoryRegister(mnemonic asm.Mnemonic, a asm.Memory, b cpu.Register) { diff --git a/src/register/NewRegister.go b/src/register/NewRegister.go index dbe3cc0..b515b67 100644 --- a/src/register/NewRegister.go +++ b/src/register/NewRegister.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // NewRegister reserves a register. func (f *Machine) NewRegister() cpu.Register { diff --git a/src/register/Number.go b/src/register/Number.go index b2c1188..9cf7bc0 100644 --- a/src/register/Number.go +++ b/src/register/Number.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/asm" +import "git.urbach.dev/cli/q/src/asm" func (f *Machine) Number(mnemonic asm.Mnemonic, number int) { f.Assembler.Number(mnemonic, number) diff --git a/src/register/Register.go b/src/register/Register.go index fa972a1..002ea86 100644 --- a/src/register/Register.go +++ b/src/register/Register.go @@ -1,8 +1,8 @@ package register import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" ) func (f *Machine) Register(mnemonic asm.Mnemonic, a cpu.Register) { diff --git a/src/register/RegisterIsUsed.go b/src/register/RegisterIsUsed.go index fedf149..4789639 100644 --- a/src/register/RegisterIsUsed.go +++ b/src/register/RegisterIsUsed.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // RegisterIsUsed reserves a register. func (f *Machine) RegisterIsUsed(register cpu.Register) bool { diff --git a/src/register/RegisterLabel.go b/src/register/RegisterLabel.go index a97de41..64a0b1a 100644 --- a/src/register/RegisterLabel.go +++ b/src/register/RegisterLabel.go @@ -1,8 +1,8 @@ package register import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" ) func (f *Machine) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { diff --git a/src/register/RegisterNumber.go b/src/register/RegisterNumber.go index f3986f5..e12f669 100644 --- a/src/register/RegisterNumber.go +++ b/src/register/RegisterNumber.go @@ -1,9 +1,9 @@ package register import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/sizeof" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/sizeof" ) func (f *Machine) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { diff --git a/src/register/RegisterRegister.go b/src/register/RegisterRegister.go index 59208ae..073dc3a 100644 --- a/src/register/RegisterRegister.go +++ b/src/register/RegisterRegister.go @@ -1,8 +1,8 @@ package register import ( - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" ) func (f *Machine) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) { diff --git a/src/register/SaveRegister.go b/src/register/SaveRegister.go index c0aa17b..9f9bd0a 100644 --- a/src/register/SaveRegister.go +++ b/src/register/SaveRegister.go @@ -3,8 +3,8 @@ package register import ( "slices" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" ) // SaveRegister attempts to move a variable occupying this register to another register. diff --git a/src/register/UseRegister.go b/src/register/UseRegister.go index 996181f..c582a9a 100644 --- a/src/register/UseRegister.go +++ b/src/register/UseRegister.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // Use marks a register to be currently in use. func (f *Machine) UseRegister(register cpu.Register) { diff --git a/src/register/postInstruction.go b/src/register/postInstruction.go index fd55acb..18520b5 100644 --- a/src/register/postInstruction.go +++ b/src/register/postInstruction.go @@ -1,6 +1,6 @@ package register -import "git.akyoto.dev/cli/q/src/config" +import "git.urbach.dev/cli/q/src/config" func (f *Machine) postInstruction() { if !config.ShowAssembly { diff --git a/src/riscv/Registers.go b/src/riscv/Registers.go index 66bf189..1313390 100644 --- a/src/riscv/Registers.go +++ b/src/riscv/Registers.go @@ -1,6 +1,6 @@ package riscv -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" const ( X0 cpu.Register = iota diff --git a/src/riscv/Registers_test.go b/src/riscv/Registers_test.go index efef49d..8e86ee7 100644 --- a/src/riscv/Registers_test.go +++ b/src/riscv/Registers_test.go @@ -3,8 +3,8 @@ package riscv_test import ( "testing" - "git.akyoto.dev/cli/q/src/riscv" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/riscv" + "git.urbach.dev/go/assert" ) func TestRegisters(t *testing.T) { diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go index 4295972..c424f5d 100644 --- a/src/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -3,10 +3,10 @@ package scanner import ( "path/filepath" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/types" ) // Scan scans the list of files. diff --git a/src/scanner/Scanner.go b/src/scanner/Scanner.go index 9bb05fa..24929f8 100644 --- a/src/scanner/Scanner.go +++ b/src/scanner/Scanner.go @@ -3,9 +3,9 @@ package scanner import ( "sync" - "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/types" ) // Scanner is used to scan files before the actual compilation step. diff --git a/src/scanner/queueDirectory.go b/src/scanner/queueDirectory.go index 27b1906..d99f0db 100644 --- a/src/scanner/queueDirectory.go +++ b/src/scanner/queueDirectory.go @@ -4,8 +4,8 @@ import ( "path/filepath" "strings" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/fs" ) // queueDirectory queues an entire directory to be scanned. diff --git a/src/scanner/scanConst.go b/src/scanner/scanConst.go index 801dba4..f3223f1 100644 --- a/src/scanner/scanConst.go +++ b/src/scanner/scanConst.go @@ -1,10 +1,10 @@ package scanner import ( - "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // scanConst scans a block of constants. diff --git a/src/scanner/scanExtern.go b/src/scanner/scanExtern.go index 5f4e214..82f3b85 100644 --- a/src/scanner/scanExtern.go +++ b/src/scanner/scanExtern.go @@ -1,9 +1,9 @@ package scanner import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // scanExtern scans a block of external function declarations. diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 65601f0..04bf2b5 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -3,9 +3,9 @@ package scanner import ( "os" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // scanFile scans a single file. diff --git a/src/scanner/scanFunction.go b/src/scanner/scanFunction.go index 6d85d83..b5242f3 100644 --- a/src/scanner/scanFunction.go +++ b/src/scanner/scanFunction.go @@ -1,9 +1,9 @@ package scanner import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // scanFunction scans a function. diff --git a/src/scanner/scanFunctionSignature.go b/src/scanner/scanFunctionSignature.go index 874464e..d1f11ad 100644 --- a/src/scanner/scanFunctionSignature.go +++ b/src/scanner/scanFunctionSignature.go @@ -1,10 +1,10 @@ package scanner import ( - "git.akyoto.dev/cli/q/src/core" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/core" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // scanFunctionSignature scans a function declaration without the body. diff --git a/src/scanner/scanImport.go b/src/scanner/scanImport.go index 47ed367..6781804 100644 --- a/src/scanner/scanImport.go +++ b/src/scanner/scanImport.go @@ -3,10 +3,10 @@ package scanner import ( "path/filepath" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" ) // scanImport scans an import. diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index d471ce1..0de75cf 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -1,10 +1,10 @@ package scanner import ( - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/cli/q/src/fs" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // scanStruct scans a struct. diff --git a/src/scope/Scope.go b/src/scope/Scope.go index fd1f750..20efebc 100644 --- a/src/scope/Scope.go +++ b/src/scope/Scope.go @@ -1,7 +1,7 @@ package scope import ( - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // Scope represents an independent code block. diff --git a/src/scope/Stack.go b/src/scope/Stack.go index dbfe206..16a2e62 100644 --- a/src/scope/Stack.go +++ b/src/scope/Stack.go @@ -1,9 +1,9 @@ package scope import ( - "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/ast" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/token" ) // Stack is a stack of scopes. diff --git a/src/scope/Variable.go b/src/scope/Variable.go index 7387ab6..79962f6 100644 --- a/src/scope/Variable.go +++ b/src/scope/Variable.go @@ -1,8 +1,8 @@ package scope import ( - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/types" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/types" ) // Variable represents a named register. diff --git a/src/sizeof/Signed_test.go b/src/sizeof/Signed_test.go index bd4d049..a815d22 100644 --- a/src/sizeof/Signed_test.go +++ b/src/sizeof/Signed_test.go @@ -4,8 +4,8 @@ import ( "math" "testing" - "git.akyoto.dev/cli/q/src/sizeof" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/sizeof" + "git.urbach.dev/go/assert" ) func TestSigned(t *testing.T) { diff --git a/src/sizeof/Unsigned_test.go b/src/sizeof/Unsigned_test.go index b4e78a2..94cdd10 100644 --- a/src/sizeof/Unsigned_test.go +++ b/src/sizeof/Unsigned_test.go @@ -4,8 +4,8 @@ import ( "math" "testing" - "git.akyoto.dev/cli/q/src/sizeof" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/sizeof" + "git.urbach.dev/go/assert" ) func TestUnsigned(t *testing.T) { diff --git a/src/token/Count_test.go b/src/token/Count_test.go index 9c8a228..39b9b4e 100644 --- a/src/token/Count_test.go +++ b/src/token/Count_test.go @@ -3,8 +3,8 @@ package token_test import ( "testing" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/go/assert" ) func TestCount(t *testing.T) { diff --git a/src/token/List_test.go b/src/token/List_test.go index 9b3638f..5ca101e 100644 --- a/src/token/List_test.go +++ b/src/token/List_test.go @@ -4,8 +4,8 @@ import ( "errors" "testing" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/go/assert" ) func TestIndexKind(t *testing.T) { diff --git a/src/token/Token_test.go b/src/token/Token_test.go index 1eb9092..0c5518c 100644 --- a/src/token/Token_test.go +++ b/src/token/Token_test.go @@ -3,8 +3,8 @@ package token_test import ( "testing" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/go/assert" ) func TestTokenEnd(t *testing.T) { diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index 3ed541a..65b8abe 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -3,8 +3,8 @@ package token_test import ( "testing" - "git.akyoto.dev/cli/q/src/token" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/go/assert" ) func TestFunction(t *testing.T) { diff --git a/src/token/bench_test.go b/src/token/bench_test.go index 6004fa3..73aeba3 100644 --- a/src/token/bench_test.go +++ b/src/token/bench_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "git.akyoto.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/token" ) func BenchmarkLines(b *testing.B) { diff --git a/src/types/Field.go b/src/types/Field.go index c8d4fce..1f5198d 100644 --- a/src/types/Field.go +++ b/src/types/Field.go @@ -1,6 +1,6 @@ package types -import "git.akyoto.dev/cli/q/src/token" +import "git.urbach.dev/cli/q/src/token" // Field is a memory region in a data structure. type Field struct { diff --git a/src/types/types_test.go b/src/types/types_test.go index e3cb03e..856c878 100644 --- a/src/types/types_test.go +++ b/src/types/types_test.go @@ -3,8 +3,8 @@ package types_test import ( "testing" - "git.akyoto.dev/cli/q/src/types" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/types" + "git.urbach.dev/go/assert" ) func TestName(t *testing.T) { diff --git a/src/x86/Add.go b/src/x86/Add.go index 9569a56..e2a81ad 100644 --- a/src/x86/Add.go +++ b/src/x86/Add.go @@ -1,7 +1,7 @@ package x86 import ( - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // AddRegisterNumber adds a number to the given register. diff --git a/src/x86/Add_test.go b/src/x86/Add_test.go index 4aa285e..71097d4 100644 --- a/src/x86/Add_test.go +++ b/src/x86/Add_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestAddRegisterNumber(t *testing.T) { diff --git a/src/x86/And.go b/src/x86/And.go index 39da3a8..48f999e 100644 --- a/src/x86/And.go +++ b/src/x86/And.go @@ -1,7 +1,7 @@ package x86 import ( - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // AndRegisterNumber performs a bitwise AND using a register and a number. diff --git a/src/x86/And_test.go b/src/x86/And_test.go index a4dbd58..7fb6fd4 100644 --- a/src/x86/And_test.go +++ b/src/x86/And_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestAndRegisterNumber(t *testing.T) { diff --git a/src/x86/Compare.go b/src/x86/Compare.go index 6b67aaa..8be0e22 100644 --- a/src/x86/Compare.go +++ b/src/x86/Compare.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // Compares the register with the number and sets the status flags in the EFLAGS register. func CompareRegisterNumber(code []byte, register cpu.Register, number int) []byte { diff --git a/src/x86/Compare_test.go b/src/x86/Compare_test.go index dbf6795..141c863 100644 --- a/src/x86/Compare_test.go +++ b/src/x86/Compare_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestCompareRegisterNumber(t *testing.T) { diff --git a/src/x86/Div.go b/src/x86/Div.go index 55ff659..dc46549 100644 --- a/src/x86/Div.go +++ b/src/x86/Div.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // DivRegister divides RDX:RAX by the value in the register. func DivRegister(code []byte, divisor cpu.Register) []byte { diff --git a/src/x86/Div_test.go b/src/x86/Div_test.go index 2af2bda..5069f76 100644 --- a/src/x86/Div_test.go +++ b/src/x86/Div_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestDivRegister(t *testing.T) { diff --git a/src/x86/Jump_test.go b/src/x86/Jump_test.go index b2df363..fc1aa5a 100644 --- a/src/x86/Jump_test.go +++ b/src/x86/Jump_test.go @@ -3,8 +3,8 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestJump(t *testing.T) { diff --git a/src/x86/Load.go b/src/x86/Load.go index 1a56236..dc04a25 100644 --- a/src/x86/Load.go +++ b/src/x86/Load.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // LoadRegister loads from memory into a register. func LoadRegister(code []byte, destination cpu.Register, offset int8, length byte, source cpu.Register) []byte { diff --git a/src/x86/LoadDynamic.go b/src/x86/LoadDynamic.go index d1ad253..1c5cb30 100644 --- a/src/x86/LoadDynamic.go +++ b/src/x86/LoadDynamic.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // LoadDynamicRegister loads from memory with a register offset into a register. func LoadDynamicRegister(code []byte, destination cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { diff --git a/src/x86/LoadDynamic_test.go b/src/x86/LoadDynamic_test.go index cdd46f2..9638e80 100644 --- a/src/x86/LoadDynamic_test.go +++ b/src/x86/LoadDynamic_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestLoadDynamicRegister(t *testing.T) { diff --git a/src/x86/Load_test.go b/src/x86/Load_test.go index 32d4405..b81f6e0 100644 --- a/src/x86/Load_test.go +++ b/src/x86/Load_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestLoadRegister(t *testing.T) { diff --git a/src/x86/ModRM_test.go b/src/x86/ModRM_test.go index fd1151a..09af04a 100644 --- a/src/x86/ModRM_test.go +++ b/src/x86/ModRM_test.go @@ -3,8 +3,8 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestModRM(t *testing.T) { diff --git a/src/x86/Move.go b/src/x86/Move.go index fee16e1..efe8756 100644 --- a/src/x86/Move.go +++ b/src/x86/Move.go @@ -3,8 +3,8 @@ package x86 import ( "encoding/binary" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/sizeof" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/sizeof" ) // MoveRegisterNumber moves an integer into the given register. diff --git a/src/x86/Move_test.go b/src/x86/Move_test.go index 5c074ae..9ac8318 100644 --- a/src/x86/Move_test.go +++ b/src/x86/Move_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestMoveRegisterNumber(t *testing.T) { diff --git a/src/x86/Mul.go b/src/x86/Mul.go index f2b4dbb..cc6bc1b 100644 --- a/src/x86/Mul.go +++ b/src/x86/Mul.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // MulRegisterNumber multiplies a register with a number. func MulRegisterNumber(code []byte, register cpu.Register, number int) []byte { diff --git a/src/x86/Mul_test.go b/src/x86/Mul_test.go index 46618fe..cfde232 100644 --- a/src/x86/Mul_test.go +++ b/src/x86/Mul_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestMulRegisterNumber(t *testing.T) { diff --git a/src/x86/Negate.go b/src/x86/Negate.go index 141d105..cc55f66 100644 --- a/src/x86/Negate.go +++ b/src/x86/Negate.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // NegateRegister negates the value in the register. func NegateRegister(code []byte, register cpu.Register) []byte { diff --git a/src/x86/Negate_test.go b/src/x86/Negate_test.go index 0fcaeea..df02835 100644 --- a/src/x86/Negate_test.go +++ b/src/x86/Negate_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestNegateRegister(t *testing.T) { diff --git a/src/x86/Or.go b/src/x86/Or.go index 9ed61c4..66477d9 100644 --- a/src/x86/Or.go +++ b/src/x86/Or.go @@ -1,7 +1,7 @@ package x86 import ( - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // OrRegisterNumber performs a bitwise OR using a register and a number. diff --git a/src/x86/Or_test.go b/src/x86/Or_test.go index f26e4e7..bac71e4 100644 --- a/src/x86/Or_test.go +++ b/src/x86/Or_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestOrRegisterNumber(t *testing.T) { diff --git a/src/x86/Pop.go b/src/x86/Pop.go index c2037b4..8b8b0eb 100644 --- a/src/x86/Pop.go +++ b/src/x86/Pop.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // PopRegister pops a value from the stack and saves it into the register. func PopRegister(code []byte, register cpu.Register) []byte { diff --git a/src/x86/Pop_test.go b/src/x86/Pop_test.go index 268dcfc..65cb1aa 100644 --- a/src/x86/Pop_test.go +++ b/src/x86/Pop_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestPopRegister(t *testing.T) { diff --git a/src/x86/Push.go b/src/x86/Push.go index 15396c2..9a2c233 100644 --- a/src/x86/Push.go +++ b/src/x86/Push.go @@ -1,8 +1,8 @@ package x86 import ( - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/sizeof" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/sizeof" ) // PushNumber pushes a number onto the stack. diff --git a/src/x86/Push_test.go b/src/x86/Push_test.go index 3552fc3..30f1a68 100644 --- a/src/x86/Push_test.go +++ b/src/x86/Push_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestPushNumber(t *testing.T) { diff --git a/src/x86/REX_test.go b/src/x86/REX_test.go index 73f2290..3da7a67 100644 --- a/src/x86/REX_test.go +++ b/src/x86/REX_test.go @@ -3,8 +3,8 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestREX(t *testing.T) { diff --git a/src/x86/Registers.go b/src/x86/Registers.go index 9ed0df0..dde5260 100644 --- a/src/x86/Registers.go +++ b/src/x86/Registers.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" const ( RAX cpu.Register = iota diff --git a/src/x86/Registers_test.go b/src/x86/Registers_test.go index 54eb4d2..d1aadad 100644 --- a/src/x86/Registers_test.go +++ b/src/x86/Registers_test.go @@ -3,8 +3,8 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestRegisters(t *testing.T) { diff --git a/src/x86/SIB_test.go b/src/x86/SIB_test.go index c6dcb4b..c0437a2 100644 --- a/src/x86/SIB_test.go +++ b/src/x86/SIB_test.go @@ -3,8 +3,8 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestSIB(t *testing.T) { diff --git a/src/x86/Shift.go b/src/x86/Shift.go index 428e7aa..d5c6068 100644 --- a/src/x86/Shift.go +++ b/src/x86/Shift.go @@ -1,7 +1,7 @@ package x86 import ( - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // ShiftLeftNumber shifts the register value by `bitCount` bits to the left. diff --git a/src/x86/Shift_test.go b/src/x86/Shift_test.go index 2e80741..19fa94a 100644 --- a/src/x86/Shift_test.go +++ b/src/x86/Shift_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestShiftLeftNumber(t *testing.T) { diff --git a/src/x86/Store.go b/src/x86/Store.go index c0fc055..0f57b09 100644 --- a/src/x86/Store.go +++ b/src/x86/Store.go @@ -3,7 +3,7 @@ package x86 import ( "encoding/binary" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // StoreNumber stores a number into the memory address included in the given register. diff --git a/src/x86/StoreDynamic.go b/src/x86/StoreDynamic.go index 2cedf7e..6cb71e9 100644 --- a/src/x86/StoreDynamic.go +++ b/src/x86/StoreDynamic.go @@ -3,7 +3,7 @@ package x86 import ( "encoding/binary" - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // StoreDynamicNumber stores a number into the memory address at `destination` with a register offset. diff --git a/src/x86/StoreDynamic_test.go b/src/x86/StoreDynamic_test.go index f5a957f..2e55105 100644 --- a/src/x86/StoreDynamic_test.go +++ b/src/x86/StoreDynamic_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestStoreDynamicNumber(t *testing.T) { diff --git a/src/x86/Store_test.go b/src/x86/Store_test.go index 7d34503..1e912c5 100644 --- a/src/x86/Store_test.go +++ b/src/x86/Store_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestStoreNumber(t *testing.T) { diff --git a/src/x86/Sub.go b/src/x86/Sub.go index 40bb4ac..bd5d2aa 100644 --- a/src/x86/Sub.go +++ b/src/x86/Sub.go @@ -1,7 +1,7 @@ package x86 import ( - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // SubRegisterNumber subtracts a number from the given register. diff --git a/src/x86/Sub_test.go b/src/x86/Sub_test.go index 7577b65..5502edc 100644 --- a/src/x86/Sub_test.go +++ b/src/x86/Sub_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestSubRegisterNumber(t *testing.T) { diff --git a/src/x86/Xor.go b/src/x86/Xor.go index 5c50ee6..45e8cc2 100644 --- a/src/x86/Xor.go +++ b/src/x86/Xor.go @@ -1,7 +1,7 @@ package x86 import ( - "git.akyoto.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/cpu" ) // XorRegisterNumber performs a bitwise XOR using a register and a number. diff --git a/src/x86/Xor_test.go b/src/x86/Xor_test.go index dcf6afa..6456cb4 100644 --- a/src/x86/Xor_test.go +++ b/src/x86/Xor_test.go @@ -3,9 +3,9 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestXorRegisterNumber(t *testing.T) { diff --git a/src/x86/encode.go b/src/x86/encode.go index 1306d52..d09fd44 100644 --- a/src/x86/encode.go +++ b/src/x86/encode.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // encode is the core function that encodes an instruction. func encode(code []byte, mod AddressMode, reg cpu.Register, rm cpu.Register, numBytes byte, opCodes ...byte) []byte { diff --git a/src/x86/encodeNum.go b/src/x86/encodeNum.go index 1611604..467e26b 100644 --- a/src/x86/encodeNum.go +++ b/src/x86/encodeNum.go @@ -3,8 +3,8 @@ package x86 import ( "encoding/binary" - "git.akyoto.dev/cli/q/src/cpu" - "git.akyoto.dev/cli/q/src/sizeof" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/sizeof" ) // encodeNum encodes an instruction with up to two registers and a number parameter. diff --git a/src/x86/memoryAccess.go b/src/x86/memoryAccess.go index af526a0..3757ec4 100644 --- a/src/x86/memoryAccess.go +++ b/src/x86/memoryAccess.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // memoryAccess encodes a memory access. func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset int8, numBytes byte, source cpu.Register) []byte { diff --git a/src/x86/memoryAccessDynamic.go b/src/x86/memoryAccessDynamic.go index d47988e..8282eaa 100644 --- a/src/x86/memoryAccessDynamic.go +++ b/src/x86/memoryAccessDynamic.go @@ -1,6 +1,6 @@ package x86 -import "git.akyoto.dev/cli/q/src/cpu" +import "git.urbach.dev/cli/q/src/cpu" // memoryAccessDynamic encodes a memory access using the value of a register as an offset. func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, destination cpu.Register, offset cpu.Register, numBytes byte, source cpu.Register) []byte { diff --git a/src/x86/x86_test.go b/src/x86/x86_test.go index 393b2fb..02e0560 100644 --- a/src/x86/x86_test.go +++ b/src/x86/x86_test.go @@ -3,8 +3,8 @@ package x86_test import ( "testing" - "git.akyoto.dev/cli/q/src/x86" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" ) func TestX86(t *testing.T) { diff --git a/tests/errors_test.go b/tests/errors_test.go index 781b662..c31056e 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -5,9 +5,9 @@ import ( "strings" "testing" - "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/errors" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/build" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/go/assert" ) var errs = []struct { diff --git a/tests/examples_test.go b/tests/examples_test.go index 9d788f6..0bac2e9 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -4,9 +4,9 @@ import ( "path/filepath" "testing" - "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/build" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/go/assert" ) var examples = []struct { diff --git a/tests/programs_test.go b/tests/programs_test.go index 4dc257e..2650a88 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -7,9 +7,9 @@ import ( "strings" "testing" - "git.akyoto.dev/cli/q/src/build" - "git.akyoto.dev/cli/q/src/config" - "git.akyoto.dev/go/assert" + "git.urbach.dev/cli/q/src/build" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/go/assert" ) var programs = []struct { From bf5110edd4c458236106f9f641845bc77c9f3775 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Feb 2025 14:33:41 +0100 Subject: [PATCH 0831/1012] Implemented boolean literals --- examples/prime/prime.q | 10 +++++----- lib/mem/free_unix.q | 4 ++-- lib/mem/free_windows.q | 4 ++-- src/core/CompileCondition.go | 27 ++++++++++++++++++++++++++- src/core/Evaluate.go | 11 +++++++++++ src/core/TokenToRegister.go | 11 +++++++++++ src/errors/Common.go | 1 + src/types/Common.go | 2 +- src/types/Is.go | 8 ++++++-- tests/errors/InvalidCondition.q | 3 +++ tests/errors_test.go | 1 + 11 files changed, 69 insertions(+), 13 deletions(-) create mode 100644 tests/errors/InvalidCondition.q diff --git a/examples/prime/prime.q b/examples/prime/prime.q index 7d639a2..f29c701 100644 --- a/examples/prime/prime.q +++ b/examples/prime/prime.q @@ -9,7 +9,7 @@ main() { return } - if isPrime(i) == 1 { + if isPrime(i) { if i != 2 { io.out(" ") } @@ -23,22 +23,22 @@ main() { isPrime(x int) -> bool { if x == 2 { - return 1 + return true } if x % 2 == 0 { - return 0 + return false } i := 3 loop { if i * i > x { - return 1 + return true } if x % i == 0 { - return 0 + return false } i += 2 diff --git a/lib/mem/free_unix.q b/lib/mem/free_unix.q index 2c61b78..8acf0df 100644 --- a/lib/mem/free_unix.q +++ b/lib/mem/free_unix.q @@ -1,5 +1,5 @@ import sys -free(address []any) -> int { - return sys.munmap(address-8, len(address)+8) +free(address []any) { + sys.munmap(address-8, len(address)+8) } \ No newline at end of file diff --git a/lib/mem/free_windows.q b/lib/mem/free_windows.q index 1bb258a..3cf8a91 100644 --- a/lib/mem/free_windows.q +++ b/lib/mem/free_windows.q @@ -2,6 +2,6 @@ extern kernel32 { VirtualFree(address *any, size uint, type uint32) -> bool } -free(address []any) -> int { - return kernel32.VirtualFree(address-8, len(address)+8, mem.decommit) +free(address []any) { + kernel32.VirtualFree(address-8, len(address)+8, mem.decommit) } \ No newline at end of file diff --git a/src/core/CompileCondition.go b/src/core/CompileCondition.go index a0c8ffe..0e41f82 100644 --- a/src/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -1,8 +1,11 @@ package core import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // CompileCondition inserts code to jump to the start label or end label depending on the truth of the condition. @@ -62,7 +65,26 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab return err - default: + case token.Call: + typ, err := f.CompileCall(condition) + + if err != nil { + return err + } + + if len(typ) == 0 { + return errors.New(errors.UntypedExpression, f.File, condition.Token.Position) + } + + if !types.Is(typ[0], types.Bool) { + return errors.New(&errors.TypeMismatch{Encountered: typ[0].Name(), Expected: types.Bool.Name()}, f.File, condition.Token.Position) + } + + f.RegisterNumber(asm.COMPARE, f.CPU.Output[0], 0) + f.Jump(asm.JE, failLabel) + return nil + + case token.Equal, token.NotEqual, token.Greater, token.Less, token.GreaterEqual, token.LessEqual: err := f.Compare(condition) if condition.Parent == nil { @@ -70,5 +92,8 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab } return err + + default: + return errors.New(errors.InvalidCondition, f.File, condition.Token.Position) } } diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 5e36fec..7189cf6 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -1,6 +1,7 @@ package core import ( + "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" @@ -24,6 +25,16 @@ func (f *Function) Evaluate(expr *expression.Expression) (types.Type, cpu.Regist } } + if ast.IsFunctionCall(expr) { + types, err := f.CompileCall(expr) + + if err != nil { + return nil, 0, false, err + } + + return types[0], f.CPU.Output[0], false, nil + } + tmp := f.NewRegister() typ, err := f.ExpressionToRegister(expr, tmp) return typ, tmp, true, err diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index a8128eb..e05d9e8 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -16,6 +16,17 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. switch t.Kind { case token.Identifier: name := t.Text(f.File.Bytes) + + if name == "true" { + f.RegisterNumber(asm.MOVE, register, 1) + return types.Bool, nil + } + + if name == "false" { + f.RegisterNumber(asm.MOVE, register, 0) + return types.Bool, nil + } + variable, function := f.Identifier(name) if variable != nil { diff --git a/src/errors/Common.go b/src/errors/Common.go index 3b1d665..f5f4f50 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -10,6 +10,7 @@ var ( ExpectedStructName = &Base{"Expected struct name"} ExpectedDLLName = &Base{"Expected DLL name"} InvalidNumber = &Base{"Invalid number"} + InvalidCondition = &Base{"Invalid condition"} InvalidExpression = &Base{"Invalid expression"} InvalidRune = &Base{"Invalid rune"} MissingBlockStart = &Base{"Missing '{'"} diff --git a/src/types/Common.go b/src/types/Common.go index 3d2a870..8cab280 100644 --- a/src/types/Common.go +++ b/src/types/Common.go @@ -4,6 +4,7 @@ var ( Any = &Base{name: "any", size: 0} AnyArray = &Array{Of: Any} AnyPointer = &Pointer{To: Any} + Bool = &Base{name: "bool", size: 1} Int64 = &Base{name: "int64", size: 8} Int32 = &Base{name: "int32", size: 4} Int16 = &Base{name: "int16", size: 2} @@ -14,7 +15,6 @@ var ( ) var ( - Bool = Int Byte = UInt8 Int = Int64 Float = Float64 diff --git a/src/types/Is.go b/src/types/Is.go index 4134701..1629454 100644 --- a/src/types/Is.go +++ b/src/types/Is.go @@ -25,8 +25,12 @@ func Is(a Type, b Type) bool { return true } - // Temporary hack for implicit casts - if a.Size() > b.Size() { + // Temporary hacks + if a == Int32 && b == Int64 { + return true + } + + if a == Int64 && b == Int32 { return true } diff --git a/tests/errors/InvalidCondition.q b/tests/errors/InvalidCondition.q new file mode 100644 index 0000000..8d76be8 --- /dev/null +++ b/tests/errors/InvalidCondition.q @@ -0,0 +1,3 @@ +main() { + if 42 {} +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index c31056e..6b10e3f 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -22,6 +22,7 @@ var errs = []struct { {"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse}, {"ExpectedStructName.q", errors.ExpectedStructName}, {"ExpectedPackageName.q", errors.ExpectedPackageName}, + {"InvalidCondition.q", errors.InvalidCondition}, {"InvalidInstructionCall.q", &errors.InvalidInstruction{Instruction: "sys.write"}}, {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "2+3"}}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, From 5f46a6f14e854e2e8ba56183554435282a45a9ab Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Feb 2025 14:33:41 +0100 Subject: [PATCH 0832/1012] Implemented boolean literals --- examples/prime/prime.q | 10 +++++----- lib/mem/free_unix.q | 4 ++-- lib/mem/free_windows.q | 4 ++-- src/core/CompileCondition.go | 27 ++++++++++++++++++++++++++- src/core/Evaluate.go | 11 +++++++++++ src/core/TokenToRegister.go | 11 +++++++++++ src/errors/Common.go | 1 + src/types/Common.go | 2 +- src/types/Is.go | 8 ++++++-- tests/errors/InvalidCondition.q | 3 +++ tests/errors_test.go | 1 + 11 files changed, 69 insertions(+), 13 deletions(-) create mode 100644 tests/errors/InvalidCondition.q diff --git a/examples/prime/prime.q b/examples/prime/prime.q index 7d639a2..f29c701 100644 --- a/examples/prime/prime.q +++ b/examples/prime/prime.q @@ -9,7 +9,7 @@ main() { return } - if isPrime(i) == 1 { + if isPrime(i) { if i != 2 { io.out(" ") } @@ -23,22 +23,22 @@ main() { isPrime(x int) -> bool { if x == 2 { - return 1 + return true } if x % 2 == 0 { - return 0 + return false } i := 3 loop { if i * i > x { - return 1 + return true } if x % i == 0 { - return 0 + return false } i += 2 diff --git a/lib/mem/free_unix.q b/lib/mem/free_unix.q index 2c61b78..8acf0df 100644 --- a/lib/mem/free_unix.q +++ b/lib/mem/free_unix.q @@ -1,5 +1,5 @@ import sys -free(address []any) -> int { - return sys.munmap(address-8, len(address)+8) +free(address []any) { + sys.munmap(address-8, len(address)+8) } \ No newline at end of file diff --git a/lib/mem/free_windows.q b/lib/mem/free_windows.q index 1bb258a..3cf8a91 100644 --- a/lib/mem/free_windows.q +++ b/lib/mem/free_windows.q @@ -2,6 +2,6 @@ extern kernel32 { VirtualFree(address *any, size uint, type uint32) -> bool } -free(address []any) -> int { - return kernel32.VirtualFree(address-8, len(address)+8, mem.decommit) +free(address []any) { + kernel32.VirtualFree(address-8, len(address)+8, mem.decommit) } \ No newline at end of file diff --git a/src/core/CompileCondition.go b/src/core/CompileCondition.go index a0c8ffe..0e41f82 100644 --- a/src/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -1,8 +1,11 @@ package core import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // CompileCondition inserts code to jump to the start label or end label depending on the truth of the condition. @@ -62,7 +65,26 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab return err - default: + case token.Call: + typ, err := f.CompileCall(condition) + + if err != nil { + return err + } + + if len(typ) == 0 { + return errors.New(errors.UntypedExpression, f.File, condition.Token.Position) + } + + if !types.Is(typ[0], types.Bool) { + return errors.New(&errors.TypeMismatch{Encountered: typ[0].Name(), Expected: types.Bool.Name()}, f.File, condition.Token.Position) + } + + f.RegisterNumber(asm.COMPARE, f.CPU.Output[0], 0) + f.Jump(asm.JE, failLabel) + return nil + + case token.Equal, token.NotEqual, token.Greater, token.Less, token.GreaterEqual, token.LessEqual: err := f.Compare(condition) if condition.Parent == nil { @@ -70,5 +92,8 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab } return err + + default: + return errors.New(errors.InvalidCondition, f.File, condition.Token.Position) } } diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 5e36fec..7189cf6 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -1,6 +1,7 @@ package core import ( + "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" @@ -24,6 +25,16 @@ func (f *Function) Evaluate(expr *expression.Expression) (types.Type, cpu.Regist } } + if ast.IsFunctionCall(expr) { + types, err := f.CompileCall(expr) + + if err != nil { + return nil, 0, false, err + } + + return types[0], f.CPU.Output[0], false, nil + } + tmp := f.NewRegister() typ, err := f.ExpressionToRegister(expr, tmp) return typ, tmp, true, err diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index a8128eb..e05d9e8 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -16,6 +16,17 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. switch t.Kind { case token.Identifier: name := t.Text(f.File.Bytes) + + if name == "true" { + f.RegisterNumber(asm.MOVE, register, 1) + return types.Bool, nil + } + + if name == "false" { + f.RegisterNumber(asm.MOVE, register, 0) + return types.Bool, nil + } + variable, function := f.Identifier(name) if variable != nil { diff --git a/src/errors/Common.go b/src/errors/Common.go index 3b1d665..f5f4f50 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -10,6 +10,7 @@ var ( ExpectedStructName = &Base{"Expected struct name"} ExpectedDLLName = &Base{"Expected DLL name"} InvalidNumber = &Base{"Invalid number"} + InvalidCondition = &Base{"Invalid condition"} InvalidExpression = &Base{"Invalid expression"} InvalidRune = &Base{"Invalid rune"} MissingBlockStart = &Base{"Missing '{'"} diff --git a/src/types/Common.go b/src/types/Common.go index 3d2a870..8cab280 100644 --- a/src/types/Common.go +++ b/src/types/Common.go @@ -4,6 +4,7 @@ var ( Any = &Base{name: "any", size: 0} AnyArray = &Array{Of: Any} AnyPointer = &Pointer{To: Any} + Bool = &Base{name: "bool", size: 1} Int64 = &Base{name: "int64", size: 8} Int32 = &Base{name: "int32", size: 4} Int16 = &Base{name: "int16", size: 2} @@ -14,7 +15,6 @@ var ( ) var ( - Bool = Int Byte = UInt8 Int = Int64 Float = Float64 diff --git a/src/types/Is.go b/src/types/Is.go index 4134701..1629454 100644 --- a/src/types/Is.go +++ b/src/types/Is.go @@ -25,8 +25,12 @@ func Is(a Type, b Type) bool { return true } - // Temporary hack for implicit casts - if a.Size() > b.Size() { + // Temporary hacks + if a == Int32 && b == Int64 { + return true + } + + if a == Int64 && b == Int32 { return true } diff --git a/tests/errors/InvalidCondition.q b/tests/errors/InvalidCondition.q new file mode 100644 index 0000000..8d76be8 --- /dev/null +++ b/tests/errors/InvalidCondition.q @@ -0,0 +1,3 @@ +main() { + if 42 {} +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index c31056e..6b10e3f 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -22,6 +22,7 @@ var errs = []struct { {"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse}, {"ExpectedStructName.q", errors.ExpectedStructName}, {"ExpectedPackageName.q", errors.ExpectedPackageName}, + {"InvalidCondition.q", errors.InvalidCondition}, {"InvalidInstructionCall.q", &errors.InvalidInstruction{Instruction: "sys.write"}}, {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "2+3"}}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, From 818477f5c339527fee6db8eeb2396db018341ae6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Feb 2025 14:47:55 +0100 Subject: [PATCH 0833/1012] Updated documentation --- docs/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index ac727ad..b57de8f 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -1,8 +1,8 @@ # q -A programming language that compiles down to machine code. +A programming language that quickly compiles to machine code without gigantic dependencies like LLVM. -## Features +## Goals - Fast compilation - High performance From ebe53b3b50e6023c2617b95f2731c6d8e978e848 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Feb 2025 14:47:55 +0100 Subject: [PATCH 0834/1012] Updated documentation --- docs/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index ac727ad..b57de8f 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -1,8 +1,8 @@ # q -A programming language that compiles down to machine code. +A programming language that quickly compiles to machine code without gigantic dependencies like LLVM. -## Features +## Goals - Fast compilation - High performance From f87cda97e288c07b27c22229b67fd4ceead0c821 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Feb 2025 20:02:53 +0100 Subject: [PATCH 0835/1012] Simplified constant folding --- src/core/Fold.go | 24 ++---------------------- src/core/FoldConstant.go | 23 +++++++++++++++++++++++ src/core/FoldLeaf.go | 17 +++++++++++++++++ src/expression/Expression.go | 5 ++--- src/expression/Foldable.go | 7 +++++++ 5 files changed, 51 insertions(+), 25 deletions(-) create mode 100644 src/core/FoldConstant.go create mode 100644 src/core/FoldLeaf.go create mode 100644 src/expression/Foldable.go diff --git a/src/core/Fold.go b/src/core/Fold.go index 768f2ec..11ae861 100644 --- a/src/core/Fold.go +++ b/src/core/Fold.go @@ -17,31 +17,11 @@ func (f *Function) Fold(expr *expression.Expression) error { } if expr.IsLeaf() { - if expr.Token.IsNumeric() { - value, err := f.ToNumber(expr.Token) - expr.Value = value - expr.IsFolded = true - return err - } - - return nil + return f.FoldLeaf(expr) } if expr.Token.Kind == token.Period { - left := expr.Children[0] - leftText := left.Token.Text(f.File.Bytes) - right := expr.Children[1] - rightText := right.Token.Text(f.File.Bytes) - constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] - - if !isConst { - return nil - } - - value, err := ToNumber(constant.Token, constant.File) - expr.Value = value - expr.IsFolded = true - return err + return f.FoldConstant(expr) } canFold := true diff --git a/src/core/FoldConstant.go b/src/core/FoldConstant.go new file mode 100644 index 0000000..39c0c23 --- /dev/null +++ b/src/core/FoldConstant.go @@ -0,0 +1,23 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/expression" +) + +// FoldConstant tries to find a constant that can be folded. +func (f *Function) FoldConstant(expr *expression.Expression) error { + left := expr.Children[0] + right := expr.Children[1] + leftText := left.Token.Text(f.File.Bytes) + rightText := right.Token.Text(f.File.Bytes) + constant, exists := f.All.Constants[f.Package+"."+leftText+"."+rightText] + + if !exists { + return nil + } + + value, err := ToNumber(constant.Token, constant.File) + expr.Value = value + expr.IsFolded = true + return err +} diff --git a/src/core/FoldLeaf.go b/src/core/FoldLeaf.go new file mode 100644 index 0000000..c989bf7 --- /dev/null +++ b/src/core/FoldLeaf.go @@ -0,0 +1,17 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/expression" +) + +// FoldLeaf tries to fold a leaf expression. +func (f *Function) FoldLeaf(expr *expression.Expression) error { + if !expr.Token.IsNumeric() { + return nil + } + + value, err := f.ToNumber(expr.Token) + expr.Value = value + expr.IsFolded = true + return err +} diff --git a/src/expression/Expression.go b/src/expression/Expression.go index aecaa77..a76569a 100644 --- a/src/expression/Expression.go +++ b/src/expression/Expression.go @@ -11,9 +11,8 @@ type Expression struct { Parent *Expression Children []*Expression Token token.Token - Value int Precedence int8 - IsFolded bool + Foldable } // New creates a new expression. @@ -62,8 +61,8 @@ func (expr *Expression) Reset() { } expr.Token.Reset() - expr.Value = 0 expr.Precedence = 0 + expr.Value = 0 expr.IsFolded = false } diff --git a/src/expression/Foldable.go b/src/expression/Foldable.go new file mode 100644 index 0000000..c225d80 --- /dev/null +++ b/src/expression/Foldable.go @@ -0,0 +1,7 @@ +package expression + +// Foldable has optional fields that are used for constant folding later. +type Foldable struct { + Value int + IsFolded bool +} From bbf2970c4ec1fd29ce1b2b3c78dc67dce81caeaa Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 26 Feb 2025 20:02:53 +0100 Subject: [PATCH 0836/1012] Simplified constant folding --- src/core/Fold.go | 24 ++---------------------- src/core/FoldConstant.go | 23 +++++++++++++++++++++++ src/core/FoldLeaf.go | 17 +++++++++++++++++ src/expression/Expression.go | 5 ++--- src/expression/Foldable.go | 7 +++++++ 5 files changed, 51 insertions(+), 25 deletions(-) create mode 100644 src/core/FoldConstant.go create mode 100644 src/core/FoldLeaf.go create mode 100644 src/expression/Foldable.go diff --git a/src/core/Fold.go b/src/core/Fold.go index 768f2ec..11ae861 100644 --- a/src/core/Fold.go +++ b/src/core/Fold.go @@ -17,31 +17,11 @@ func (f *Function) Fold(expr *expression.Expression) error { } if expr.IsLeaf() { - if expr.Token.IsNumeric() { - value, err := f.ToNumber(expr.Token) - expr.Value = value - expr.IsFolded = true - return err - } - - return nil + return f.FoldLeaf(expr) } if expr.Token.Kind == token.Period { - left := expr.Children[0] - leftText := left.Token.Text(f.File.Bytes) - right := expr.Children[1] - rightText := right.Token.Text(f.File.Bytes) - constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] - - if !isConst { - return nil - } - - value, err := ToNumber(constant.Token, constant.File) - expr.Value = value - expr.IsFolded = true - return err + return f.FoldConstant(expr) } canFold := true diff --git a/src/core/FoldConstant.go b/src/core/FoldConstant.go new file mode 100644 index 0000000..39c0c23 --- /dev/null +++ b/src/core/FoldConstant.go @@ -0,0 +1,23 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/expression" +) + +// FoldConstant tries to find a constant that can be folded. +func (f *Function) FoldConstant(expr *expression.Expression) error { + left := expr.Children[0] + right := expr.Children[1] + leftText := left.Token.Text(f.File.Bytes) + rightText := right.Token.Text(f.File.Bytes) + constant, exists := f.All.Constants[f.Package+"."+leftText+"."+rightText] + + if !exists { + return nil + } + + value, err := ToNumber(constant.Token, constant.File) + expr.Value = value + expr.IsFolded = true + return err +} diff --git a/src/core/FoldLeaf.go b/src/core/FoldLeaf.go new file mode 100644 index 0000000..c989bf7 --- /dev/null +++ b/src/core/FoldLeaf.go @@ -0,0 +1,17 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/expression" +) + +// FoldLeaf tries to fold a leaf expression. +func (f *Function) FoldLeaf(expr *expression.Expression) error { + if !expr.Token.IsNumeric() { + return nil + } + + value, err := f.ToNumber(expr.Token) + expr.Value = value + expr.IsFolded = true + return err +} diff --git a/src/expression/Expression.go b/src/expression/Expression.go index aecaa77..a76569a 100644 --- a/src/expression/Expression.go +++ b/src/expression/Expression.go @@ -11,9 +11,8 @@ type Expression struct { Parent *Expression Children []*Expression Token token.Token - Value int Precedence int8 - IsFolded bool + Foldable } // New creates a new expression. @@ -62,8 +61,8 @@ func (expr *Expression) Reset() { } expr.Token.Reset() - expr.Value = 0 expr.Precedence = 0 + expr.Value = 0 expr.IsFolded = false } diff --git a/src/expression/Foldable.go b/src/expression/Foldable.go new file mode 100644 index 0000000..c225d80 --- /dev/null +++ b/src/expression/Foldable.go @@ -0,0 +1,7 @@ +package expression + +// Foldable has optional fields that are used for constant folding later. +type Foldable struct { + Value int + IsFolded bool +} From a905710982ce7fd13bdc05899d39068b2e2ac32c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Feb 2025 00:18:34 +0100 Subject: [PATCH 0837/1012] Added placeholders for type casts --- examples/server/server.q | 3 --- lib/net/net_linux.q | 2 +- lib/sys/sys_windows.q | 8 ++++---- lib/thread/thread_linux.q | 6 ++---- src/core/ArrayElementToRegister.go | 8 ++++---- src/core/CompileAssignArray.go | 4 ++-- src/core/CompileCall.go | 11 +++++++++++ src/core/CompileDefinition.go | 5 +++++ src/core/CompileLen.go | 2 +- src/core/ExpressionToMemory.go | 2 +- src/core/ExpressionToRegister.go | 2 +- src/core/PeriodToRegister.go | 4 ++-- src/core/TokenToRegister.go | 2 +- src/types/Common.go | 17 +++++++++-------- src/types/Is.go | 12 +++++------- src/types/types_test.go | 2 +- tests/errors_test.go | 4 ++-- tests/programs/cast.q | 8 ++++++++ tests/programs_test.go | 1 + 19 files changed, 61 insertions(+), 42 deletions(-) create mode 100644 tests/programs/cast.q diff --git a/examples/server/server.q b/examples/server/server.q index 6ac4ee3..f90c1c7 100644 --- a/examples/server/server.q +++ b/examples/server/server.q @@ -1,6 +1,3 @@ -// Open server and client in 2 terminals: -// [1] q run examples/server -// [2] curl http://127.0.0.1:8080 import io import net import sys diff --git a/lib/net/net_linux.q b/lib/net/net_linux.q index 4a025e3..168c8a2 100644 --- a/lib/net/net_linux.q +++ b/lib/net/net_linux.q @@ -1,6 +1,6 @@ import sys -bind(socket int, port int) -> int { +bind(socket int, port uint16) -> int { addr := new(sys.sockaddr_in) addr.sin_family = 2 addr.sin_port = htons(port) diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q index 2523338..59e6769 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/sys_windows.q @@ -1,12 +1,12 @@ -read(fd int64, buffer *byte, length int64) -> int64 { +read(fd int, buffer *byte, length int) -> int { fd = kernel32.GetStdHandle(-10 - fd) - kernel32.ReadConsole(fd, buffer, length, 0) + kernel32.ReadConsole(fd, buffer, uint32(length), 0) return length } -write(fd int64, buffer *byte, length int64) -> int64 { +write(fd int, buffer *byte, length int) -> int { fd = kernel32.GetStdHandle(-10 - fd) - kernel32.WriteConsoleA(fd, buffer, length, 0) + kernel32.WriteConsoleA(fd, buffer, uint32(length), 0) return length } diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index cf4d75a..592207b 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -12,10 +12,8 @@ const clone { } create(func *any) -> int { - size := 4096 - stack := sys.mmap(0, size, 0x1|0x2, 0x02|0x20|0x100|0x20000) - stack += size - stack -= 8 + stack := sys.mmap(0, 4096, 0x1|0x2, 0x02|0x20|0x100) + stack += 4096 - 8 store(stack, 8, core.exit) stack -= 8 store(stack, 8, func) diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go index 0dec215..9e58f3e 100644 --- a/src/core/ArrayElementToRegister.go +++ b/src/core/ArrayElementToRegister.go @@ -50,8 +50,8 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register defer f.UseVariable(indexVariable) - if !types.Is(indexVariable.Type, types.Int) { - return nil, errors.New(&errors.TypeMismatch{Encountered: indexVariable.Type.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) + if !types.Is(indexVariable.Type, types.AnyInt) { + return nil, errors.New(&errors.TypeMismatch{Encountered: indexVariable.Type.Name(), Expected: types.AnyInt.Name()}, f.File, index.Token.Position) } memory.OffsetRegister = indexVariable.Register @@ -63,8 +63,8 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register return nil, err } - if !types.Is(typ, types.Int) { - return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) + if !types.Is(typ, types.AnyInt) { + return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.AnyInt.Name()}, f.File, index.Token.Position) } memory.OffsetRegister = register diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 55e33a5..fff34ca 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -47,8 +47,8 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { return err } - if !types.Is(typ, types.Int) { - return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) + if !types.Is(typ, types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.AnyInt.Name()}, f.File, index.Token.Position) } memory.OffsetRegister = indexRegister diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 3d4ad40..e9c94b2 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -52,6 +52,17 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error fn, exists = f.All.Functions[pkg+"."+name] if !exists { + typ := types.ByName(name, f.Package, f.All.Structs) + + if typ != nil { + if len(root.Children) != 2 { + return nil, errors.New(&errors.ParameterCountMismatch{Function: name, Count: len(root.Children), ExpectedCount: 1}, f.File, nameNode.Token.End()) + } + + _, err := f.ExpressionToRegister(root.Children[1], f.CPU.Output[0]) + return []types.Type{typ}, err + } + return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) } diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index 71d4b73..af96b46 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -6,6 +6,7 @@ import ( "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // CompileDefinition compiles a variable definition. @@ -30,6 +31,10 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return errors.New(errors.UntypedExpression, f.File, node.Expression.Token.End()) } + if typ == types.AnyInt { + typ = types.Int + } + variable.Type = typ f.AddVariable(variable) return nil diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index 4065d76..4571c38 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -9,7 +9,7 @@ import ( "git.urbach.dev/cli/q/src/types" ) -var _len = Function{OutputTypes: []types.Type{types.Int}} +var _len = Function{OutputTypes: []types.Type{types.AnyInt}} // CompileLen returns the length of a slice. func (f *Function) CompileLen(root *expression.Expression) error { diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index f62ba58..8886f6e 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -48,7 +48,7 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me // } f.MemoryNumber(asm.STORE, memory, number) - return types.Int, nil + return types.AnyInt, nil } } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 0a3fb29..54d82b2 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -13,7 +13,7 @@ import ( func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { if node.IsFolded { f.RegisterNumber(asm.MOVE, register, node.Value) - return types.Int, nil + return types.AnyInt, nil } if node.IsLeaf() { diff --git a/src/core/PeriodToRegister.go b/src/core/PeriodToRegister.go index 2307a43..aeb7675 100644 --- a/src/core/PeriodToRegister.go +++ b/src/core/PeriodToRegister.go @@ -14,8 +14,8 @@ import ( // PeriodToRegister moves a constant or a function address into the given register. func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { left := node.Children[0] - leftText := left.Token.Text(f.File.Bytes) right := node.Children[1] + leftText := left.Token.Text(f.File.Bytes) rightText := right.Token.Text(f.File.Bytes) variable := f.VariableByName(leftText) @@ -44,7 +44,7 @@ func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Re f.SaveRegister(register) f.RegisterNumber(asm.MOVE, register, number) - return types.Int, nil + return types.AnyInt, nil } uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index e05d9e8..fce74e1 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -53,7 +53,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. f.SaveRegister(register) f.RegisterNumber(asm.MOVE, register, number) - return types.Int, nil + return types.AnyInt, nil case token.String: data := t.Bytes(f.File.Bytes) diff --git a/src/types/Common.go b/src/types/Common.go index 8cab280..124a6bc 100644 --- a/src/types/Common.go +++ b/src/types/Common.go @@ -3,6 +3,7 @@ package types var ( Any = &Base{name: "any", size: 0} AnyArray = &Array{Of: Any} + AnyInt = &Base{name: "int"} AnyPointer = &Pointer{To: Any} Bool = &Base{name: "bool", size: 1} Int64 = &Base{name: "int64", size: 8} @@ -12,15 +13,15 @@ var ( Float64 = &Base{name: "float64", size: 8} Float32 = &Base{name: "float32", size: 4} String = &Array{Of: Byte} + UInt64 = &Base{name: "uint64", size: 8} + UInt32 = &Base{name: "uint32", size: 4} + UInt16 = &Base{name: "uint16", size: 2} + UInt8 = &Base{name: "uint8", size: 1} ) var ( - Byte = UInt8 - Int = Int64 - Float = Float64 - UInt = Int - UInt64 = Int64 - UInt32 = Int32 - UInt16 = Int16 - UInt8 = Int8 + Byte = UInt8 + Float = Float64 + Int = Int64 + UInt = UInt64 ) diff --git a/src/types/Is.go b/src/types/Is.go index 1629454..875df1a 100644 --- a/src/types/Is.go +++ b/src/types/Is.go @@ -25,13 +25,11 @@ func Is(a Type, b Type) bool { return true } - // Temporary hacks - if a == Int32 && b == Int64 { - return true - } - - if a == Int64 && b == Int32 { - return true + if a == AnyInt { + switch b { + case Int64, Int32, Int16, Int8, UInt64, UInt32, UInt16, UInt8: + return true + } } return false diff --git a/src/types/types_test.go b/src/types/types_test.go index 856c878..e43bcee 100644 --- a/src/types/types_test.go +++ b/src/types/types_test.go @@ -13,7 +13,7 @@ func TestName(t *testing.T) { assert.Equal(t, types.AnyPointer.Name(), "*any") assert.Equal(t, (&types.Pointer{To: types.Int}).Name(), "*int64") assert.Equal(t, (&types.Array{Of: types.Int}).Name(), "[]int64") - assert.Equal(t, types.String.Name(), "[]int8") + assert.Equal(t, types.String.Name(), "[]uint8") } func TestSize(t *testing.T) { diff --git a/tests/errors_test.go b/tests/errors_test.go index 6b10e3f..e672684 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -50,8 +50,8 @@ var errs = []struct { {"MissingParameter3.q", errors.MissingParameter}, {"MissingType.q", errors.MissingType}, {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, - {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*any", Encountered: "int64", ParameterName: "p"}}, - {"TypeMismatch2.q", &errors.TypeMismatch{Expected: "[]any", Encountered: "int64", ParameterName: "array"}}, + {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*any", Encountered: "int", ParameterName: "p"}}, + {"TypeMismatch2.q", &errors.TypeMismatch{Expected: "[]any", Encountered: "int", ParameterName: "array"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, diff --git a/tests/programs/cast.q b/tests/programs/cast.q new file mode 100644 index 0000000..8eb8ae4 --- /dev/null +++ b/tests/programs/cast.q @@ -0,0 +1,8 @@ +main() { + x := byte(42) + assert f(x) == 42 +} + +f(x byte) -> byte { + return x +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 2650a88..2f15d5f 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -67,6 +67,7 @@ var programs = []struct { {"index-dynamic", 0}, {"struct", 0}, {"len", 0}, + {"cast", 0}, } func TestPrograms(t *testing.T) { From ae6530aadbc83462555daf3a0aa6b0645eeb3367 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Feb 2025 00:18:34 +0100 Subject: [PATCH 0838/1012] Added placeholders for type casts --- examples/server/server.q | 3 --- lib/net/net_linux.q | 2 +- lib/sys/sys_windows.q | 8 ++++---- lib/thread/thread_linux.q | 6 ++---- src/core/ArrayElementToRegister.go | 8 ++++---- src/core/CompileAssignArray.go | 4 ++-- src/core/CompileCall.go | 11 +++++++++++ src/core/CompileDefinition.go | 5 +++++ src/core/CompileLen.go | 2 +- src/core/ExpressionToMemory.go | 2 +- src/core/ExpressionToRegister.go | 2 +- src/core/PeriodToRegister.go | 4 ++-- src/core/TokenToRegister.go | 2 +- src/types/Common.go | 17 +++++++++-------- src/types/Is.go | 12 +++++------- src/types/types_test.go | 2 +- tests/errors_test.go | 4 ++-- tests/programs/cast.q | 8 ++++++++ tests/programs_test.go | 1 + 19 files changed, 61 insertions(+), 42 deletions(-) create mode 100644 tests/programs/cast.q diff --git a/examples/server/server.q b/examples/server/server.q index 6ac4ee3..f90c1c7 100644 --- a/examples/server/server.q +++ b/examples/server/server.q @@ -1,6 +1,3 @@ -// Open server and client in 2 terminals: -// [1] q run examples/server -// [2] curl http://127.0.0.1:8080 import io import net import sys diff --git a/lib/net/net_linux.q b/lib/net/net_linux.q index 4a025e3..168c8a2 100644 --- a/lib/net/net_linux.q +++ b/lib/net/net_linux.q @@ -1,6 +1,6 @@ import sys -bind(socket int, port int) -> int { +bind(socket int, port uint16) -> int { addr := new(sys.sockaddr_in) addr.sin_family = 2 addr.sin_port = htons(port) diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q index 2523338..59e6769 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/sys_windows.q @@ -1,12 +1,12 @@ -read(fd int64, buffer *byte, length int64) -> int64 { +read(fd int, buffer *byte, length int) -> int { fd = kernel32.GetStdHandle(-10 - fd) - kernel32.ReadConsole(fd, buffer, length, 0) + kernel32.ReadConsole(fd, buffer, uint32(length), 0) return length } -write(fd int64, buffer *byte, length int64) -> int64 { +write(fd int, buffer *byte, length int) -> int { fd = kernel32.GetStdHandle(-10 - fd) - kernel32.WriteConsoleA(fd, buffer, length, 0) + kernel32.WriteConsoleA(fd, buffer, uint32(length), 0) return length } diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index cf4d75a..592207b 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -12,10 +12,8 @@ const clone { } create(func *any) -> int { - size := 4096 - stack := sys.mmap(0, size, 0x1|0x2, 0x02|0x20|0x100|0x20000) - stack += size - stack -= 8 + stack := sys.mmap(0, 4096, 0x1|0x2, 0x02|0x20|0x100) + stack += 4096 - 8 store(stack, 8, core.exit) stack -= 8 store(stack, 8, func) diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go index 0dec215..9e58f3e 100644 --- a/src/core/ArrayElementToRegister.go +++ b/src/core/ArrayElementToRegister.go @@ -50,8 +50,8 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register defer f.UseVariable(indexVariable) - if !types.Is(indexVariable.Type, types.Int) { - return nil, errors.New(&errors.TypeMismatch{Encountered: indexVariable.Type.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) + if !types.Is(indexVariable.Type, types.AnyInt) { + return nil, errors.New(&errors.TypeMismatch{Encountered: indexVariable.Type.Name(), Expected: types.AnyInt.Name()}, f.File, index.Token.Position) } memory.OffsetRegister = indexVariable.Register @@ -63,8 +63,8 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register return nil, err } - if !types.Is(typ, types.Int) { - return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) + if !types.Is(typ, types.AnyInt) { + return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.AnyInt.Name()}, f.File, index.Token.Position) } memory.OffsetRegister = register diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 55e33a5..fff34ca 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -47,8 +47,8 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { return err } - if !types.Is(typ, types.Int) { - return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) + if !types.Is(typ, types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.AnyInt.Name()}, f.File, index.Token.Position) } memory.OffsetRegister = indexRegister diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 3d4ad40..e9c94b2 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -52,6 +52,17 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error fn, exists = f.All.Functions[pkg+"."+name] if !exists { + typ := types.ByName(name, f.Package, f.All.Structs) + + if typ != nil { + if len(root.Children) != 2 { + return nil, errors.New(&errors.ParameterCountMismatch{Function: name, Count: len(root.Children), ExpectedCount: 1}, f.File, nameNode.Token.End()) + } + + _, err := f.ExpressionToRegister(root.Children[1], f.CPU.Output[0]) + return []types.Type{typ}, err + } + return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) } diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index 71d4b73..af96b46 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -6,6 +6,7 @@ import ( "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // CompileDefinition compiles a variable definition. @@ -30,6 +31,10 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return errors.New(errors.UntypedExpression, f.File, node.Expression.Token.End()) } + if typ == types.AnyInt { + typ = types.Int + } + variable.Type = typ f.AddVariable(variable) return nil diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index 4065d76..4571c38 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -9,7 +9,7 @@ import ( "git.urbach.dev/cli/q/src/types" ) -var _len = Function{OutputTypes: []types.Type{types.Int}} +var _len = Function{OutputTypes: []types.Type{types.AnyInt}} // CompileLen returns the length of a slice. func (f *Function) CompileLen(root *expression.Expression) error { diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index f62ba58..8886f6e 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -48,7 +48,7 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me // } f.MemoryNumber(asm.STORE, memory, number) - return types.Int, nil + return types.AnyInt, nil } } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 0a3fb29..54d82b2 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -13,7 +13,7 @@ import ( func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { if node.IsFolded { f.RegisterNumber(asm.MOVE, register, node.Value) - return types.Int, nil + return types.AnyInt, nil } if node.IsLeaf() { diff --git a/src/core/PeriodToRegister.go b/src/core/PeriodToRegister.go index 2307a43..aeb7675 100644 --- a/src/core/PeriodToRegister.go +++ b/src/core/PeriodToRegister.go @@ -14,8 +14,8 @@ import ( // PeriodToRegister moves a constant or a function address into the given register. func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { left := node.Children[0] - leftText := left.Token.Text(f.File.Bytes) right := node.Children[1] + leftText := left.Token.Text(f.File.Bytes) rightText := right.Token.Text(f.File.Bytes) variable := f.VariableByName(leftText) @@ -44,7 +44,7 @@ func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Re f.SaveRegister(register) f.RegisterNumber(asm.MOVE, register, number) - return types.Int, nil + return types.AnyInt, nil } uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index e05d9e8..fce74e1 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -53,7 +53,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. f.SaveRegister(register) f.RegisterNumber(asm.MOVE, register, number) - return types.Int, nil + return types.AnyInt, nil case token.String: data := t.Bytes(f.File.Bytes) diff --git a/src/types/Common.go b/src/types/Common.go index 8cab280..124a6bc 100644 --- a/src/types/Common.go +++ b/src/types/Common.go @@ -3,6 +3,7 @@ package types var ( Any = &Base{name: "any", size: 0} AnyArray = &Array{Of: Any} + AnyInt = &Base{name: "int"} AnyPointer = &Pointer{To: Any} Bool = &Base{name: "bool", size: 1} Int64 = &Base{name: "int64", size: 8} @@ -12,15 +13,15 @@ var ( Float64 = &Base{name: "float64", size: 8} Float32 = &Base{name: "float32", size: 4} String = &Array{Of: Byte} + UInt64 = &Base{name: "uint64", size: 8} + UInt32 = &Base{name: "uint32", size: 4} + UInt16 = &Base{name: "uint16", size: 2} + UInt8 = &Base{name: "uint8", size: 1} ) var ( - Byte = UInt8 - Int = Int64 - Float = Float64 - UInt = Int - UInt64 = Int64 - UInt32 = Int32 - UInt16 = Int16 - UInt8 = Int8 + Byte = UInt8 + Float = Float64 + Int = Int64 + UInt = UInt64 ) diff --git a/src/types/Is.go b/src/types/Is.go index 1629454..875df1a 100644 --- a/src/types/Is.go +++ b/src/types/Is.go @@ -25,13 +25,11 @@ func Is(a Type, b Type) bool { return true } - // Temporary hacks - if a == Int32 && b == Int64 { - return true - } - - if a == Int64 && b == Int32 { - return true + if a == AnyInt { + switch b { + case Int64, Int32, Int16, Int8, UInt64, UInt32, UInt16, UInt8: + return true + } } return false diff --git a/src/types/types_test.go b/src/types/types_test.go index 856c878..e43bcee 100644 --- a/src/types/types_test.go +++ b/src/types/types_test.go @@ -13,7 +13,7 @@ func TestName(t *testing.T) { assert.Equal(t, types.AnyPointer.Name(), "*any") assert.Equal(t, (&types.Pointer{To: types.Int}).Name(), "*int64") assert.Equal(t, (&types.Array{Of: types.Int}).Name(), "[]int64") - assert.Equal(t, types.String.Name(), "[]int8") + assert.Equal(t, types.String.Name(), "[]uint8") } func TestSize(t *testing.T) { diff --git a/tests/errors_test.go b/tests/errors_test.go index 6b10e3f..e672684 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -50,8 +50,8 @@ var errs = []struct { {"MissingParameter3.q", errors.MissingParameter}, {"MissingType.q", errors.MissingType}, {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, - {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*any", Encountered: "int64", ParameterName: "p"}}, - {"TypeMismatch2.q", &errors.TypeMismatch{Expected: "[]any", Encountered: "int64", ParameterName: "array"}}, + {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*any", Encountered: "int", ParameterName: "p"}}, + {"TypeMismatch2.q", &errors.TypeMismatch{Expected: "[]any", Encountered: "int", ParameterName: "array"}}, {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, diff --git a/tests/programs/cast.q b/tests/programs/cast.q new file mode 100644 index 0000000..8eb8ae4 --- /dev/null +++ b/tests/programs/cast.q @@ -0,0 +1,8 @@ +main() { + x := byte(42) + assert f(x) == 42 +} + +f(x byte) -> byte { + return x +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 2650a88..2f15d5f 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -67,6 +67,7 @@ var programs = []struct { {"index-dynamic", 0}, {"struct", 0}, {"len", 0}, + {"cast", 0}, } func TestPrograms(t *testing.T) { From da6dcc44330e0845fe92bae3464aa148e96283ae Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Feb 2025 14:16:25 +0100 Subject: [PATCH 0839/1012] Added Value type --- src/core/CompileAssignArray.go | 18 +++++++++--------- src/core/CompileAssignDivision.go | 12 ++++++++---- src/core/CompileFor.go | 12 +++++++++--- src/core/CompileLen.go | 10 +++++----- src/core/Evaluate.go | 14 ++++++-------- src/core/ExpressionToMemory.go | 8 ++++---- src/core/Value.go | 12 ++++++++++++ src/types/Is.go | 13 ++++++++++++- 8 files changed, 65 insertions(+), 34 deletions(-) create mode 100644 src/core/Value.go diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index fff34ca..217e010 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -30,31 +30,31 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { Length: byte(1), } - index := left.Children[1] + indexExpr := left.Children[1] - if index.Token.IsNumeric() { - offset, err := f.ToNumber(index.Token) + if indexExpr.Token.IsNumeric() { + index, err := f.ToNumber(indexExpr.Token) if err != nil { return err } - memory.Offset = int8(offset) + memory.Offset = int8(index) } else { - typ, indexRegister, isTemporary, err := f.Evaluate(index) + index, isTemporary, err := f.Evaluate(indexExpr) if err != nil { return err } - if !types.Is(typ, types.AnyInt) { - return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.AnyInt.Name()}, f.File, index.Token.Position) + if !types.Is(index.Type, types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: index.Type.Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) } - memory.OffsetRegister = indexRegister + memory.OffsetRegister = index.Register if isTemporary { - defer f.FreeRegister(indexRegister) + defer f.FreeRegister(index.Register) } } diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index cbca794..bf483eb 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -58,20 +58,24 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { defer f.UseVariable(remainderVariable) } - dividend := right.Children[0] - _, dividendRegister, isTemporary, err := f.Evaluate(dividend) + dividendExpr := right.Children[0] + dividend, isTemporary, err := f.Evaluate(dividendExpr) if err != nil { return err } + if !types.Is(dividend.Type, types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: dividend.Type.Name(), Expected: types.AnyInt.Name()}, f.File, dividendExpr.Token.Position) + } + divisor := right.Children[1] - err = f.Execute(right.Token, dividendRegister, divisor) + err = f.Execute(right.Token, dividend.Register, divisor) f.RegisterRegister(asm.MOVE, quotientVariable.Register, x86.RAX) f.RegisterRegister(asm.MOVE, remainderVariable.Register, x86.RDX) if isTemporary { - f.FreeRegister(dividendRegister) + f.FreeRegister(dividend.Register) } return err diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index 9f77413..d528dd7 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -4,8 +4,10 @@ import ( "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // CompileFor compiles a for loop. @@ -66,18 +68,22 @@ func (f *Function) CompileFor(loop *ast.For) error { f.AddLabel(label) f.RegisterNumber(asm.COMPARE, counter, number) } else { - _, register, isTemporary, err := f.Evaluate(to) + value, isTemporary, err := f.Evaluate(to) if err != nil { return err } + if !types.Is(value.Type, types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) + } + if isTemporary { - defer f.FreeRegister(register) + defer f.FreeRegister(value.Register) } f.AddLabel(label) - f.RegisterRegister(asm.COMPARE, counter, register) + f.RegisterRegister(asm.COMPARE, counter, value.Register) } f.Jump(asm.JGE, labelEnd) diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index 4571c38..9eaeb40 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -13,21 +13,21 @@ var _len = Function{OutputTypes: []types.Type{types.AnyInt}} // CompileLen returns the length of a slice. func (f *Function) CompileLen(root *expression.Expression) error { - typ, register, isTemporary, err := f.Evaluate(root.Children[1]) + value, isTemporary, err := f.Evaluate(root.Children[1]) if err != nil { return err } - if !types.Is(typ, types.AnyArray) { - return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.AnyArray.Name(), ParameterName: "array"}, f.File, root.Children[1].Token.Position) + if !types.Is(value.Type, types.AnyArray) { + return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyArray.Name(), ParameterName: "array"}, f.File, root.Children[1].Token.Position) } f.SaveRegister(f.CPU.Output[0]) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: register, Offset: -8, OffsetRegister: math.MaxUint8, Length: 8}, f.CPU.Output[0]) + f.MemoryRegister(asm.LOAD, asm.Memory{Base: value.Register, Offset: -8, OffsetRegister: math.MaxUint8, Length: 8}, f.CPU.Output[0]) if isTemporary { - f.FreeRegister(register) + f.FreeRegister(value.Register) } return nil diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 7189cf6..5f11a75 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -2,26 +2,24 @@ package core import ( "git.urbach.dev/cli/q/src/ast" - "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" - "git.urbach.dev/cli/q/src/types" ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. -func (f *Function) Evaluate(expr *expression.Expression) (types.Type, cpu.Register, bool, error) { +func (f *Function) Evaluate(expr *expression.Expression) (Value, bool, error) { if expr.Token.Kind == token.Identifier { name := expr.Token.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { - return nil, 0, false, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) + return Value{}, false, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) } if variable.Alive == 1 { f.UseVariable(variable) - return variable.Type, variable.Register, false, nil + return Value{variable.Type, variable.Register}, false, nil } } @@ -29,13 +27,13 @@ func (f *Function) Evaluate(expr *expression.Expression) (types.Type, cpu.Regist types, err := f.CompileCall(expr) if err != nil { - return nil, 0, false, err + return Value{}, false, err } - return types[0], f.CPU.Output[0], false, nil + return Value{types[0], f.CPU.Output[0]}, false, nil } tmp := f.NewRegister() typ, err := f.ExpressionToRegister(expr, tmp) - return typ, tmp, true, err + return Value{typ, tmp}, true, err } diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index 8886f6e..63ba85b 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -52,17 +52,17 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me } } - typ, register, isTemporary, err := f.Evaluate(node) + value, isTemporary, err := f.Evaluate(node) if err != nil { return nil, err } - f.MemoryRegister(asm.STORE, memory, register) + f.MemoryRegister(asm.STORE, memory, value.Register) if isTemporary { - f.FreeRegister(register) + f.FreeRegister(value.Register) } - return typ, err + return value.Type, err } diff --git a/src/core/Value.go b/src/core/Value.go new file mode 100644 index 0000000..28b9f81 --- /dev/null +++ b/src/core/Value.go @@ -0,0 +1,12 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/types" +) + +// Value combines a register with its data type. +type Value struct { + Type types.Type + Register cpu.Register +} diff --git a/src/types/Is.go b/src/types/Is.go index 875df1a..4728d6a 100644 --- a/src/types/Is.go +++ b/src/types/Is.go @@ -27,8 +27,19 @@ func Is(a Type, b Type) bool { if a == AnyInt { switch b { - case Int64, Int32, Int16, Int8, UInt64, UInt32, UInt16, UInt8: + case Int64, Int32, Int16, Int8, UInt64, UInt32, UInt16, UInt8, AnyInt: return true + default: + return false + } + } + + if b == AnyInt { + switch a { + case Int64, Int32, Int16, Int8, UInt64, UInt32, UInt16, UInt8, AnyInt: + return true + default: + return false } } From 9f78733d5d7fdf5eb24b2b9324823460ccc40761 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Feb 2025 14:16:25 +0100 Subject: [PATCH 0840/1012] Added Value type --- src/core/CompileAssignArray.go | 18 +++++++++--------- src/core/CompileAssignDivision.go | 12 ++++++++---- src/core/CompileFor.go | 12 +++++++++--- src/core/CompileLen.go | 10 +++++----- src/core/Evaluate.go | 14 ++++++-------- src/core/ExpressionToMemory.go | 8 ++++---- src/core/Value.go | 12 ++++++++++++ src/types/Is.go | 13 ++++++++++++- 8 files changed, 65 insertions(+), 34 deletions(-) create mode 100644 src/core/Value.go diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index fff34ca..217e010 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -30,31 +30,31 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { Length: byte(1), } - index := left.Children[1] + indexExpr := left.Children[1] - if index.Token.IsNumeric() { - offset, err := f.ToNumber(index.Token) + if indexExpr.Token.IsNumeric() { + index, err := f.ToNumber(indexExpr.Token) if err != nil { return err } - memory.Offset = int8(offset) + memory.Offset = int8(index) } else { - typ, indexRegister, isTemporary, err := f.Evaluate(index) + index, isTemporary, err := f.Evaluate(indexExpr) if err != nil { return err } - if !types.Is(typ, types.AnyInt) { - return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.AnyInt.Name()}, f.File, index.Token.Position) + if !types.Is(index.Type, types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: index.Type.Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) } - memory.OffsetRegister = indexRegister + memory.OffsetRegister = index.Register if isTemporary { - defer f.FreeRegister(indexRegister) + defer f.FreeRegister(index.Register) } } diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index cbca794..bf483eb 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -58,20 +58,24 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { defer f.UseVariable(remainderVariable) } - dividend := right.Children[0] - _, dividendRegister, isTemporary, err := f.Evaluate(dividend) + dividendExpr := right.Children[0] + dividend, isTemporary, err := f.Evaluate(dividendExpr) if err != nil { return err } + if !types.Is(dividend.Type, types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: dividend.Type.Name(), Expected: types.AnyInt.Name()}, f.File, dividendExpr.Token.Position) + } + divisor := right.Children[1] - err = f.Execute(right.Token, dividendRegister, divisor) + err = f.Execute(right.Token, dividend.Register, divisor) f.RegisterRegister(asm.MOVE, quotientVariable.Register, x86.RAX) f.RegisterRegister(asm.MOVE, remainderVariable.Register, x86.RDX) if isTemporary { - f.FreeRegister(dividendRegister) + f.FreeRegister(dividend.Register) } return err diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index 9f77413..d528dd7 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -4,8 +4,10 @@ import ( "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // CompileFor compiles a for loop. @@ -66,18 +68,22 @@ func (f *Function) CompileFor(loop *ast.For) error { f.AddLabel(label) f.RegisterNumber(asm.COMPARE, counter, number) } else { - _, register, isTemporary, err := f.Evaluate(to) + value, isTemporary, err := f.Evaluate(to) if err != nil { return err } + if !types.Is(value.Type, types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) + } + if isTemporary { - defer f.FreeRegister(register) + defer f.FreeRegister(value.Register) } f.AddLabel(label) - f.RegisterRegister(asm.COMPARE, counter, register) + f.RegisterRegister(asm.COMPARE, counter, value.Register) } f.Jump(asm.JGE, labelEnd) diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index 4571c38..9eaeb40 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -13,21 +13,21 @@ var _len = Function{OutputTypes: []types.Type{types.AnyInt}} // CompileLen returns the length of a slice. func (f *Function) CompileLen(root *expression.Expression) error { - typ, register, isTemporary, err := f.Evaluate(root.Children[1]) + value, isTemporary, err := f.Evaluate(root.Children[1]) if err != nil { return err } - if !types.Is(typ, types.AnyArray) { - return errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.AnyArray.Name(), ParameterName: "array"}, f.File, root.Children[1].Token.Position) + if !types.Is(value.Type, types.AnyArray) { + return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyArray.Name(), ParameterName: "array"}, f.File, root.Children[1].Token.Position) } f.SaveRegister(f.CPU.Output[0]) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: register, Offset: -8, OffsetRegister: math.MaxUint8, Length: 8}, f.CPU.Output[0]) + f.MemoryRegister(asm.LOAD, asm.Memory{Base: value.Register, Offset: -8, OffsetRegister: math.MaxUint8, Length: 8}, f.CPU.Output[0]) if isTemporary { - f.FreeRegister(register) + f.FreeRegister(value.Register) } return nil diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 7189cf6..5f11a75 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -2,26 +2,24 @@ package core import ( "git.urbach.dev/cli/q/src/ast" - "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" - "git.urbach.dev/cli/q/src/types" ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. -func (f *Function) Evaluate(expr *expression.Expression) (types.Type, cpu.Register, bool, error) { +func (f *Function) Evaluate(expr *expression.Expression) (Value, bool, error) { if expr.Token.Kind == token.Identifier { name := expr.Token.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { - return nil, 0, false, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) + return Value{}, false, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) } if variable.Alive == 1 { f.UseVariable(variable) - return variable.Type, variable.Register, false, nil + return Value{variable.Type, variable.Register}, false, nil } } @@ -29,13 +27,13 @@ func (f *Function) Evaluate(expr *expression.Expression) (types.Type, cpu.Regist types, err := f.CompileCall(expr) if err != nil { - return nil, 0, false, err + return Value{}, false, err } - return types[0], f.CPU.Output[0], false, nil + return Value{types[0], f.CPU.Output[0]}, false, nil } tmp := f.NewRegister() typ, err := f.ExpressionToRegister(expr, tmp) - return typ, tmp, true, err + return Value{typ, tmp}, true, err } diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index 8886f6e..63ba85b 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -52,17 +52,17 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me } } - typ, register, isTemporary, err := f.Evaluate(node) + value, isTemporary, err := f.Evaluate(node) if err != nil { return nil, err } - f.MemoryRegister(asm.STORE, memory, register) + f.MemoryRegister(asm.STORE, memory, value.Register) if isTemporary { - f.FreeRegister(register) + f.FreeRegister(value.Register) } - return typ, err + return value.Type, err } diff --git a/src/core/Value.go b/src/core/Value.go new file mode 100644 index 0000000..28b9f81 --- /dev/null +++ b/src/core/Value.go @@ -0,0 +1,12 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/types" +) + +// Value combines a register with its data type. +type Value struct { + Type types.Type + Register cpu.Register +} diff --git a/src/types/Is.go b/src/types/Is.go index 875df1a..4728d6a 100644 --- a/src/types/Is.go +++ b/src/types/Is.go @@ -27,8 +27,19 @@ func Is(a Type, b Type) bool { if a == AnyInt { switch b { - case Int64, Int32, Int16, Int8, UInt64, UInt32, UInt16, UInt8: + case Int64, Int32, Int16, Int8, UInt64, UInt32, UInt16, UInt8, AnyInt: return true + default: + return false + } + } + + if b == AnyInt { + switch a { + case Int64, Int32, Int16, Int8, UInt64, UInt32, UInt16, UInt8, AnyInt: + return true + default: + return false } } From 891b4d9f9030ce1589ba9400a0c94f27cda883cd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Feb 2025 15:30:44 +0100 Subject: [PATCH 0841/1012] Simplified Evaluate function --- src/core/CompileAssignArray.go | 7 ++----- src/core/CompileAssignDivision.go | 8 ++------ src/core/CompileFor.go | 7 ++----- src/core/CompileLen.go | 8 ++------ src/core/Define.go | 8 +++++--- src/core/Evaluate.go | 26 ++++++++++++++++++++------ src/core/ExpressionToMemory.go | 8 ++------ src/core/ResolveTypes.go | 10 ++++++---- src/core/Value.go | 12 ------------ src/scope/Stack.go | 10 ++++++---- src/scope/Value.go | 27 +++++++++++++++++++++++++++ src/scope/Variable.go | 27 +++------------------------ tests/programs/for.q | 20 ++++++++++++++++++++ tests/programs/loop-for.q | 14 -------------- tests/programs_test.go | 2 +- 15 files changed, 98 insertions(+), 96 deletions(-) delete mode 100644 src/core/Value.go create mode 100644 src/scope/Value.go create mode 100644 tests/programs/for.q delete mode 100644 tests/programs/loop-for.q diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 217e010..ab379e4 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -41,7 +41,7 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { memory.Offset = int8(index) } else { - index, isTemporary, err := f.Evaluate(indexExpr) + index, err := f.Evaluate(indexExpr) if err != nil { return err @@ -52,10 +52,7 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { } memory.OffsetRegister = index.Register - - if isTemporary { - defer f.FreeRegister(index.Register) - } + defer f.FreeRegister(index.Register) } _, err := f.ExpressionToMemory(right, memory) diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index bf483eb..619e19b 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -59,7 +59,7 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { } dividendExpr := right.Children[0] - dividend, isTemporary, err := f.Evaluate(dividendExpr) + dividend, err := f.Evaluate(dividendExpr) if err != nil { return err @@ -73,10 +73,6 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { err = f.Execute(right.Token, dividend.Register, divisor) f.RegisterRegister(asm.MOVE, quotientVariable.Register, x86.RAX) f.RegisterRegister(asm.MOVE, remainderVariable.Register, x86.RDX) - - if isTemporary { - f.FreeRegister(dividend.Register) - } - + f.FreeRegister(dividend.Register) return err } diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index d528dd7..98a63d9 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -68,7 +68,7 @@ func (f *Function) CompileFor(loop *ast.For) error { f.AddLabel(label) f.RegisterNumber(asm.COMPARE, counter, number) } else { - value, isTemporary, err := f.Evaluate(to) + value, err := f.Evaluate(to) if err != nil { return err @@ -78,12 +78,9 @@ func (f *Function) CompileFor(loop *ast.For) error { return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) } - if isTemporary { - defer f.FreeRegister(value.Register) - } - f.AddLabel(label) f.RegisterRegister(asm.COMPARE, counter, value.Register) + defer f.FreeRegister(value.Register) } f.Jump(asm.JGE, labelEnd) diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index 9eaeb40..076bcc1 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -13,7 +13,7 @@ var _len = Function{OutputTypes: []types.Type{types.AnyInt}} // CompileLen returns the length of a slice. func (f *Function) CompileLen(root *expression.Expression) error { - value, isTemporary, err := f.Evaluate(root.Children[1]) + value, err := f.Evaluate(root.Children[1]) if err != nil { return err @@ -25,10 +25,6 @@ func (f *Function) CompileLen(root *expression.Expression) error { f.SaveRegister(f.CPU.Output[0]) f.MemoryRegister(asm.LOAD, asm.Memory{Base: value.Register, Offset: -8, OffsetRegister: math.MaxUint8, Length: 8}, f.CPU.Output[0]) - - if isTemporary { - f.FreeRegister(value.Register) - } - + f.FreeRegister(value.Register) return nil } diff --git a/src/core/Define.go b/src/core/Define.go index eacd340..d56208d 100644 --- a/src/core/Define.go +++ b/src/core/Define.go @@ -23,9 +23,11 @@ func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) } variable = &scope.Variable{ - Name: name, - Register: f.NewRegister(), - Alive: uses, + Name: name, + Value: scope.Value{ + Register: f.NewRegister(), + Alive: uses, + }, } return variable, nil diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 5f11a75..44adb18 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -4,22 +4,23 @@ import ( "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/scope" "git.urbach.dev/cli/q/src/token" ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. -func (f *Function) Evaluate(expr *expression.Expression) (Value, bool, error) { +func (f *Function) Evaluate(expr *expression.Expression) (scope.Value, error) { if expr.Token.Kind == token.Identifier { name := expr.Token.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { - return Value{}, false, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) + return scope.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) } if variable.Alive == 1 { f.UseVariable(variable) - return Value{variable.Type, variable.Register}, false, nil + return variable.Value, nil } } @@ -27,13 +28,26 @@ func (f *Function) Evaluate(expr *expression.Expression) (Value, bool, error) { types, err := f.CompileCall(expr) if err != nil { - return Value{}, false, err + return scope.Value{}, err } - return Value{types[0], f.CPU.Output[0]}, false, nil + value := scope.Value{ + Type: types[0], + Register: f.CPU.Output[0], + Alive: 1, + } + + return value, nil } tmp := f.NewRegister() typ, err := f.ExpressionToRegister(expr, tmp) - return Value{typ, tmp}, true, err + + value := scope.Value{ + Type: typ, + Register: tmp, + Alive: 1, + } + + return value, err } diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index 63ba85b..d807371 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -52,17 +52,13 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me } } - value, isTemporary, err := f.Evaluate(node) + value, err := f.Evaluate(node) if err != nil { return nil, err } f.MemoryRegister(asm.STORE, memory, value.Register) - - if isTemporary { - f.FreeRegister(value.Register) - } - + f.FreeRegister(value.Register) return value.Type, err } diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index 42382fb..cf17430 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -30,10 +30,12 @@ func (f *Function) ResolveTypes() error { } f.AddVariable(&scope.Variable{ - Name: param.name, - Type: param.typ, - Register: x86.InputRegisters[i], - Alive: uses, + Name: param.name, + Value: scope.Value{ + Type: param.typ, + Register: x86.InputRegisters[i], + Alive: uses, + }, }) } diff --git a/src/core/Value.go b/src/core/Value.go deleted file mode 100644 index 28b9f81..0000000 --- a/src/core/Value.go +++ /dev/null @@ -1,12 +0,0 @@ -package core - -import ( - "git.urbach.dev/cli/q/src/cpu" - "git.urbach.dev/cli/q/src/types" -) - -// Value combines a register with its data type. -type Value struct { - Type types.Type - Register cpu.Register -} diff --git a/src/scope/Stack.go b/src/scope/Stack.go index 16a2e62..5037f45 100644 --- a/src/scope/Stack.go +++ b/src/scope/Stack.go @@ -44,10 +44,12 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { } s.Variables = append(s.Variables, &Variable{ - Name: v.Name, - Register: v.Register, - Alive: count, - Type: v.Type, + Name: v.Name, + Value: Value{ + Register: v.Register, + Alive: count, + Type: v.Type, + }, }) } } diff --git a/src/scope/Value.go b/src/scope/Value.go new file mode 100644 index 0000000..93a3774 --- /dev/null +++ b/src/scope/Value.go @@ -0,0 +1,27 @@ +package scope + +import ( + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/types" +) + +// Value combines a register with its data type. +type Value struct { + Type types.Type + Register cpu.Register + Alive uint8 +} + +// IsAlive returns true if the Value is still alive. +func (v *Value) IsAlive() bool { + return v.Alive > 0 +} + +// Use reduces the lifetime counter by one. +func (v *Value) Use() { + if v.Alive == 0 { + panic("incorrect number of value use calls") + } + + v.Alive-- +} diff --git a/src/scope/Variable.go b/src/scope/Variable.go index 79962f6..4a387ae 100644 --- a/src/scope/Variable.go +++ b/src/scope/Variable.go @@ -1,28 +1,7 @@ package scope -import ( - "git.urbach.dev/cli/q/src/cpu" - "git.urbach.dev/cli/q/src/types" -) - -// Variable represents a named register. +// Variable is a named value. type Variable struct { - Type types.Type - Name string - Alive uint8 - Register cpu.Register -} - -// IsAlive returns true if the variable is still alive. -func (v *Variable) IsAlive() bool { - return v.Alive > 0 -} - -// Use reduces the lifetime counter by one. -func (v *Variable) Use() { - if v.Alive == 0 { - panic("incorrect number of variable use calls") - } - - v.Alive-- + Value + Name string } diff --git a/tests/programs/for.q b/tests/programs/for.q new file mode 100644 index 0000000..e41e08f --- /dev/null +++ b/tests/programs/for.q @@ -0,0 +1,20 @@ +main() { + total := 0 + + for 0..10 { + total += 1 + } + + assert total == 10 + + for 0..total { + total -= 1 + } + + assert total == 5 + + for i := 0..10 { + assert i >= 0 + assert i < 10 + } +} \ No newline at end of file diff --git a/tests/programs/loop-for.q b/tests/programs/loop-for.q deleted file mode 100644 index cc85bac..0000000 --- a/tests/programs/loop-for.q +++ /dev/null @@ -1,14 +0,0 @@ -main() { - x := 0 - - for 0..5 { - x += 1 - } - - assert x == 5 - - for i := 0..5 { - assert i >= 0 - assert i < 5 - } -} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 2f15d5f..3979fc9 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -60,7 +60,7 @@ var programs = []struct { {"switch", 0}, {"loop-infinite", 0}, {"loop-lifetime", 0}, - {"loop-for", 0}, + {"for", 0}, {"memory-free", 0}, {"out-of-memory", 0}, {"index-static", 0}, From efb3089211a7f7e54e005943443efb310c2c824a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Feb 2025 15:30:44 +0100 Subject: [PATCH 0842/1012] Simplified Evaluate function --- src/core/CompileAssignArray.go | 7 ++----- src/core/CompileAssignDivision.go | 8 ++------ src/core/CompileFor.go | 7 ++----- src/core/CompileLen.go | 8 ++------ src/core/Define.go | 8 +++++--- src/core/Evaluate.go | 26 ++++++++++++++++++++------ src/core/ExpressionToMemory.go | 8 ++------ src/core/ResolveTypes.go | 10 ++++++---- src/core/Value.go | 12 ------------ src/scope/Stack.go | 10 ++++++---- src/scope/Value.go | 27 +++++++++++++++++++++++++++ src/scope/Variable.go | 27 +++------------------------ tests/programs/for.q | 20 ++++++++++++++++++++ tests/programs/loop-for.q | 14 -------------- tests/programs_test.go | 2 +- 15 files changed, 98 insertions(+), 96 deletions(-) delete mode 100644 src/core/Value.go create mode 100644 src/scope/Value.go create mode 100644 tests/programs/for.q delete mode 100644 tests/programs/loop-for.q diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 217e010..ab379e4 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -41,7 +41,7 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { memory.Offset = int8(index) } else { - index, isTemporary, err := f.Evaluate(indexExpr) + index, err := f.Evaluate(indexExpr) if err != nil { return err @@ -52,10 +52,7 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { } memory.OffsetRegister = index.Register - - if isTemporary { - defer f.FreeRegister(index.Register) - } + defer f.FreeRegister(index.Register) } _, err := f.ExpressionToMemory(right, memory) diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index bf483eb..619e19b 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -59,7 +59,7 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { } dividendExpr := right.Children[0] - dividend, isTemporary, err := f.Evaluate(dividendExpr) + dividend, err := f.Evaluate(dividendExpr) if err != nil { return err @@ -73,10 +73,6 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { err = f.Execute(right.Token, dividend.Register, divisor) f.RegisterRegister(asm.MOVE, quotientVariable.Register, x86.RAX) f.RegisterRegister(asm.MOVE, remainderVariable.Register, x86.RDX) - - if isTemporary { - f.FreeRegister(dividend.Register) - } - + f.FreeRegister(dividend.Register) return err } diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index d528dd7..98a63d9 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -68,7 +68,7 @@ func (f *Function) CompileFor(loop *ast.For) error { f.AddLabel(label) f.RegisterNumber(asm.COMPARE, counter, number) } else { - value, isTemporary, err := f.Evaluate(to) + value, err := f.Evaluate(to) if err != nil { return err @@ -78,12 +78,9 @@ func (f *Function) CompileFor(loop *ast.For) error { return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) } - if isTemporary { - defer f.FreeRegister(value.Register) - } - f.AddLabel(label) f.RegisterRegister(asm.COMPARE, counter, value.Register) + defer f.FreeRegister(value.Register) } f.Jump(asm.JGE, labelEnd) diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index 9eaeb40..076bcc1 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -13,7 +13,7 @@ var _len = Function{OutputTypes: []types.Type{types.AnyInt}} // CompileLen returns the length of a slice. func (f *Function) CompileLen(root *expression.Expression) error { - value, isTemporary, err := f.Evaluate(root.Children[1]) + value, err := f.Evaluate(root.Children[1]) if err != nil { return err @@ -25,10 +25,6 @@ func (f *Function) CompileLen(root *expression.Expression) error { f.SaveRegister(f.CPU.Output[0]) f.MemoryRegister(asm.LOAD, asm.Memory{Base: value.Register, Offset: -8, OffsetRegister: math.MaxUint8, Length: 8}, f.CPU.Output[0]) - - if isTemporary { - f.FreeRegister(value.Register) - } - + f.FreeRegister(value.Register) return nil } diff --git a/src/core/Define.go b/src/core/Define.go index eacd340..d56208d 100644 --- a/src/core/Define.go +++ b/src/core/Define.go @@ -23,9 +23,11 @@ func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) } variable = &scope.Variable{ - Name: name, - Register: f.NewRegister(), - Alive: uses, + Name: name, + Value: scope.Value{ + Register: f.NewRegister(), + Alive: uses, + }, } return variable, nil diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 5f11a75..44adb18 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -4,22 +4,23 @@ import ( "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/scope" "git.urbach.dev/cli/q/src/token" ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. -func (f *Function) Evaluate(expr *expression.Expression) (Value, bool, error) { +func (f *Function) Evaluate(expr *expression.Expression) (scope.Value, error) { if expr.Token.Kind == token.Identifier { name := expr.Token.Text(f.File.Bytes) variable := f.VariableByName(name) if variable == nil { - return Value{}, false, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) + return scope.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) } if variable.Alive == 1 { f.UseVariable(variable) - return Value{variable.Type, variable.Register}, false, nil + return variable.Value, nil } } @@ -27,13 +28,26 @@ func (f *Function) Evaluate(expr *expression.Expression) (Value, bool, error) { types, err := f.CompileCall(expr) if err != nil { - return Value{}, false, err + return scope.Value{}, err } - return Value{types[0], f.CPU.Output[0]}, false, nil + value := scope.Value{ + Type: types[0], + Register: f.CPU.Output[0], + Alive: 1, + } + + return value, nil } tmp := f.NewRegister() typ, err := f.ExpressionToRegister(expr, tmp) - return Value{typ, tmp}, true, err + + value := scope.Value{ + Type: typ, + Register: tmp, + Alive: 1, + } + + return value, err } diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index 63ba85b..d807371 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -52,17 +52,13 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me } } - value, isTemporary, err := f.Evaluate(node) + value, err := f.Evaluate(node) if err != nil { return nil, err } f.MemoryRegister(asm.STORE, memory, value.Register) - - if isTemporary { - f.FreeRegister(value.Register) - } - + f.FreeRegister(value.Register) return value.Type, err } diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index 42382fb..cf17430 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -30,10 +30,12 @@ func (f *Function) ResolveTypes() error { } f.AddVariable(&scope.Variable{ - Name: param.name, - Type: param.typ, - Register: x86.InputRegisters[i], - Alive: uses, + Name: param.name, + Value: scope.Value{ + Type: param.typ, + Register: x86.InputRegisters[i], + Alive: uses, + }, }) } diff --git a/src/core/Value.go b/src/core/Value.go deleted file mode 100644 index 28b9f81..0000000 --- a/src/core/Value.go +++ /dev/null @@ -1,12 +0,0 @@ -package core - -import ( - "git.urbach.dev/cli/q/src/cpu" - "git.urbach.dev/cli/q/src/types" -) - -// Value combines a register with its data type. -type Value struct { - Type types.Type - Register cpu.Register -} diff --git a/src/scope/Stack.go b/src/scope/Stack.go index 16a2e62..5037f45 100644 --- a/src/scope/Stack.go +++ b/src/scope/Stack.go @@ -44,10 +44,12 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { } s.Variables = append(s.Variables, &Variable{ - Name: v.Name, - Register: v.Register, - Alive: count, - Type: v.Type, + Name: v.Name, + Value: Value{ + Register: v.Register, + Alive: count, + Type: v.Type, + }, }) } } diff --git a/src/scope/Value.go b/src/scope/Value.go new file mode 100644 index 0000000..93a3774 --- /dev/null +++ b/src/scope/Value.go @@ -0,0 +1,27 @@ +package scope + +import ( + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/types" +) + +// Value combines a register with its data type. +type Value struct { + Type types.Type + Register cpu.Register + Alive uint8 +} + +// IsAlive returns true if the Value is still alive. +func (v *Value) IsAlive() bool { + return v.Alive > 0 +} + +// Use reduces the lifetime counter by one. +func (v *Value) Use() { + if v.Alive == 0 { + panic("incorrect number of value use calls") + } + + v.Alive-- +} diff --git a/src/scope/Variable.go b/src/scope/Variable.go index 79962f6..4a387ae 100644 --- a/src/scope/Variable.go +++ b/src/scope/Variable.go @@ -1,28 +1,7 @@ package scope -import ( - "git.urbach.dev/cli/q/src/cpu" - "git.urbach.dev/cli/q/src/types" -) - -// Variable represents a named register. +// Variable is a named value. type Variable struct { - Type types.Type - Name string - Alive uint8 - Register cpu.Register -} - -// IsAlive returns true if the variable is still alive. -func (v *Variable) IsAlive() bool { - return v.Alive > 0 -} - -// Use reduces the lifetime counter by one. -func (v *Variable) Use() { - if v.Alive == 0 { - panic("incorrect number of variable use calls") - } - - v.Alive-- + Value + Name string } diff --git a/tests/programs/for.q b/tests/programs/for.q new file mode 100644 index 0000000..e41e08f --- /dev/null +++ b/tests/programs/for.q @@ -0,0 +1,20 @@ +main() { + total := 0 + + for 0..10 { + total += 1 + } + + assert total == 10 + + for 0..total { + total -= 1 + } + + assert total == 5 + + for i := 0..10 { + assert i >= 0 + assert i < 10 + } +} \ No newline at end of file diff --git a/tests/programs/loop-for.q b/tests/programs/loop-for.q deleted file mode 100644 index cc85bac..0000000 --- a/tests/programs/loop-for.q +++ /dev/null @@ -1,14 +0,0 @@ -main() { - x := 0 - - for 0..5 { - x += 1 - } - - assert x == 5 - - for i := 0..5 { - assert i >= 0 - assert i < 5 - } -} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 2f15d5f..3979fc9 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -60,7 +60,7 @@ var programs = []struct { {"switch", 0}, {"loop-infinite", 0}, {"loop-lifetime", 0}, - {"loop-for", 0}, + {"for", 0}, {"memory-free", 0}, {"out-of-memory", 0}, {"index-static", 0}, From 3d294eb35c5c511649324608ada51efe7e35dd01 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Feb 2025 19:45:18 +0100 Subject: [PATCH 0843/1012] Added eval package --- src/core/CompileAssign.go | 26 +----- src/core/CompileAssignArray.go | 43 +++++----- src/core/CompileAssignDivision.go | 34 +++++--- src/core/CompileCondition.go | 24 ++++-- src/core/CompileFor.go | 34 ++++---- src/core/Define.go | 4 +- src/core/Evaluate.go | 127 ++++++++++++++++++++++++++---- src/core/ExpressionToMemory.go | 66 +++++----------- src/core/MultiAssign.go | 31 ++++++++ src/core/ResolveTypes.go | 4 +- src/eval/Kind.go | 11 +++ src/{scope => eval}/Value.go | 7 +- src/readme.md | 1 + src/scope/Stack.go | 4 +- src/scope/Variable.go | 4 +- tests/programs/for.q | 2 +- 16 files changed, 274 insertions(+), 148 deletions(-) create mode 100644 src/core/MultiAssign.go create mode 100644 src/eval/Kind.go rename src/{scope => eval}/Value.go (80%) diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 2ff6dfb..9773db9 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -1,16 +1,13 @@ package core import ( - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/errors" - "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" ) // CompileAssign compiles an assign statement. func (f *Function) CompileAssign(node *ast.Assign) error { - operator := node.Expression.Token left := node.Expression.Children[0] right := node.Expression.Children[1] @@ -23,7 +20,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { } defer f.UseVariable(variable) - return f.Execute(operator, variable.Register, right) + return f.Execute(node.Expression.Token, variable.Register, right) } if left.Token.Kind == token.Period { @@ -42,24 +39,5 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return errors.New(errors.NotImplemented, f.File, node.Expression.Token.Position) } - count := 0 - _, err := f.CompileCall(right) - - if err != nil { - return err - } - - return left.EachLeaf(func(leaf *expression.Expression) error { - name := leaf.Token.Text(f.File.Bytes) - variable := f.VariableByName(name) - - if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) - } - - f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) - f.UseVariable(variable) - count++ - return nil - }) + return f.MultiAssign(left, right) } diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index ab379e4..740c42b 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -1,11 +1,13 @@ package core import ( + "fmt" "math" "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/types" ) @@ -13,7 +15,6 @@ import ( func (f *Function) CompileAssignArray(node *ast.Assign) error { left := node.Expression.Children[0] right := node.Expression.Children[1] - name := left.Children[0].Token.Text(f.File.Bytes) variable := f.VariableByName(name) @@ -31,30 +32,26 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { } indexExpr := left.Children[1] + index, err := f.Evaluate(indexExpr) - if indexExpr.Token.IsNumeric() { - index, err := f.ToNumber(indexExpr.Token) - - if err != nil { - return err - } - - memory.Offset = int8(index) - } else { - index, err := f.Evaluate(indexExpr) - - if err != nil { - return err - } - - if !types.Is(index.Type, types.AnyInt) { - return errors.New(&errors.TypeMismatch{Encountered: index.Type.Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) - } - - memory.OffsetRegister = index.Register - defer f.FreeRegister(index.Register) + if err != nil { + return err } - _, err := f.ExpressionToMemory(right, memory) + if !types.Is(index.Type, types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: index.Type.Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) + } + + switch index.Kind { + case eval.Number: + memory.Offset = int8(index.Number) + case eval.Register: + memory.OffsetRegister = index.Register + defer f.FreeRegister(index.Register) + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, index.Kind)) + } + + _, err = f.ExpressionToMemory(right, memory) return err } diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index 619e19b..fd16573 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -1,8 +1,11 @@ package core import ( + "fmt" + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/scope" "git.urbach.dev/cli/q/src/token" @@ -13,21 +16,21 @@ import ( // CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right. func (f *Function) CompileAssignDivision(expr *expression.Expression) error { var ( - left = expr.Children[0] - right = expr.Children[1] + variables = expr.Children[0] + division = expr.Children[1] quotientVariable *scope.Variable remainderVariable *scope.Variable err error ) if expr.Token.Kind == token.Define { - quotientVariable, err = f.Define(left.Children[0]) + quotientVariable, err = f.Define(variables.Children[0]) if err != nil { return err } - remainderVariable, err = f.Define(left.Children[1]) + remainderVariable, err = f.Define(variables.Children[1]) if err != nil { return err @@ -38,7 +41,7 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { f.AddVariable(quotientVariable) f.AddVariable(remainderVariable) } else { - quotient := left.Children[0] + quotient := variables.Children[0] name := quotient.Token.Text(f.File.Bytes) quotientVariable = f.VariableByName(name) @@ -46,7 +49,7 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, quotient.Token.Position) } - remainder := left.Children[1] + remainder := variables.Children[1] name = remainder.Token.Text(f.File.Bytes) remainderVariable = f.VariableByName(name) @@ -58,7 +61,7 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { defer f.UseVariable(remainderVariable) } - dividendExpr := right.Children[0] + dividendExpr := division.Children[0] dividend, err := f.Evaluate(dividendExpr) if err != nil { @@ -69,10 +72,21 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { return errors.New(&errors.TypeMismatch{Encountered: dividend.Type.Name(), Expected: types.AnyInt.Name()}, f.File, dividendExpr.Token.Position) } - divisor := right.Children[1] - err = f.Execute(right.Token, dividend.Register, divisor) + divisor := division.Children[1] + + switch dividend.Kind { + case eval.Number: + f.SaveRegister(x86.RAX) + f.RegisterNumber(asm.MOVE, x86.RAX, dividend.Number) + err = f.Execute(division.Token, x86.RAX, divisor) + case eval.Register: + err = f.Execute(division.Token, dividend.Register, divisor) + defer f.FreeRegister(dividend.Register) + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, dividend.Kind)) + } + f.RegisterRegister(asm.MOVE, quotientVariable.Register, x86.RAX) f.RegisterRegister(asm.MOVE, remainderVariable.Register, x86.RDX) - f.FreeRegister(dividend.Register) return err } diff --git a/src/core/CompileCondition.go b/src/core/CompileCondition.go index 0e41f82..087731e 100644 --- a/src/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -1,8 +1,11 @@ package core import ( + "fmt" + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" @@ -66,22 +69,29 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab return err case token.Call: - typ, err := f.CompileCall(condition) + value, err := f.Evaluate(condition) if err != nil { return err } - if len(typ) == 0 { - return errors.New(errors.UntypedExpression, f.File, condition.Token.Position) + if !types.Is(value.Type, types.Bool) { + return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.Bool.Name()}, f.File, condition.Token.Position) } - if !types.Is(typ[0], types.Bool) { - return errors.New(&errors.TypeMismatch{Encountered: typ[0].Name(), Expected: types.Bool.Name()}, f.File, condition.Token.Position) + switch value.Kind { + case eval.Number: + if value.Number == 0 { + f.Jump(asm.JUMP, failLabel) + } + case eval.Register: + f.RegisterNumber(asm.COMPARE, value.Register, 0) + f.FreeRegister(value.Register) + f.Jump(asm.JE, failLabel) + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) } - f.RegisterNumber(asm.COMPARE, f.CPU.Output[0], 0) - f.Jump(asm.JE, failLabel) return nil case token.Equal, token.NotEqual, token.Greater, token.Less, token.GreaterEqual, token.LessEqual: diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index 98a63d9..1eafd68 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -1,10 +1,13 @@ package core import ( + "fmt" + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" @@ -58,29 +61,26 @@ func (f *Function) CompileFor(loop *ast.For) error { return err } - if to.Token.IsNumeric() { - number, err := f.ToNumber(to.Token) + value, err := f.Evaluate(to) - if err != nil { - return err - } + if err != nil { + return err + } - f.AddLabel(label) - f.RegisterNumber(asm.COMPARE, counter, number) - } else { - value, err := f.Evaluate(to) + if !types.Is(value.Type, types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) + } - if err != nil { - return err - } + f.AddLabel(label) - if !types.Is(value.Type, types.AnyInt) { - return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) - } - - f.AddLabel(label) + switch value.Kind { + case eval.Number: + f.RegisterNumber(asm.COMPARE, counter, value.Number) + case eval.Register: f.RegisterRegister(asm.COMPARE, counter, value.Register) defer f.FreeRegister(value.Register) + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) } f.Jump(asm.JGE, labelEnd) diff --git a/src/core/Define.go b/src/core/Define.go index d56208d..16ccdd6 100644 --- a/src/core/Define.go +++ b/src/core/Define.go @@ -2,6 +2,7 @@ package core import ( "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/scope" "git.urbach.dev/cli/q/src/token" @@ -24,7 +25,8 @@ func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) variable = &scope.Variable{ Name: name, - Value: scope.Value{ + Value: eval.Value{ + Kind: eval.Register, Register: f.NewRegister(), Alive: uses, }, diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 44adb18..a2af299 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -1,37 +1,88 @@ package core import ( - "git.urbach.dev/cli/q/src/ast" + "math" + + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" - "git.urbach.dev/cli/q/src/scope" "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. -func (f *Function) Evaluate(expr *expression.Expression) (scope.Value, error) { - if expr.Token.Kind == token.Identifier { - name := expr.Token.Text(f.File.Bytes) - variable := f.VariableByName(name) +func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { + if expr.IsLeaf() { + if expr.Token.IsNumeric() { + number, err := f.ToNumber(expr.Token) - if variable == nil { - return scope.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) + if err != nil { + return eval.Value{}, err + } + + value := eval.Value{ + Kind: eval.Number, + Type: types.AnyInt, + Number: number, + Alive: 1, + } + + return value, nil } - if variable.Alive == 1 { - f.UseVariable(variable) - return variable.Value, nil + if expr.Token.Kind == token.Identifier { + name := expr.Token.Text(f.File.Bytes) + variable, function := f.Identifier(name) + + if variable != nil { + f.UseVariable(variable) + + if variable.Alive == 0 { + return variable.Value, nil + } + + tmp := f.NewRegister() + f.RegisterRegister(asm.MOVE, tmp, variable.Register) + + value := eval.Value{ + Kind: eval.Register, + Type: variable.Type, + Register: tmp, + Alive: 1, + } + + return value, nil + } + + if function != nil { + value := eval.Value{ + Kind: eval.Label, + Type: types.AnyPointer, + Label: function.UniqueName, + Alive: 1, + } + + return value, nil + } + + return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) } } - if ast.IsFunctionCall(expr) { + if expr.Token.Kind == token.Call { types, err := f.CompileCall(expr) if err != nil { - return scope.Value{}, err + return eval.Value{}, err } - value := scope.Value{ + if len(types) == 0 { + return eval.Value{}, errors.New(errors.UntypedExpression, f.File, expr.Token.Position) + } + + value := eval.Value{ + Kind: eval.Register, Type: types[0], Register: f.CPU.Output[0], Alive: 1, @@ -40,10 +91,56 @@ func (f *Function) Evaluate(expr *expression.Expression) (scope.Value, error) { return value, nil } + if expr.Token.Kind == token.Period { + left := expr.Children[0] + right := expr.Children[1] + leftText := left.Token.Text(f.File.Bytes) + rightText := right.Token.Text(f.File.Bytes) + variable := f.VariableByName(leftText) + + if variable != nil { + field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + + value := eval.Value{ + Kind: eval.Memory, + Type: field.Type, + Alive: 1, + Memory: asm.Memory{ + Base: variable.Register, + Offset: int8(field.Offset), + OffsetRegister: math.MaxUint8, + Length: byte(field.Type.Size()), + }, + } + + return value, nil + } + + constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] + + if isConst { + number, err := ToNumber(constant.Token, constant.File) + + if err != nil { + return eval.Value{}, err + } + + value := eval.Value{ + Kind: eval.Number, + Type: types.AnyInt, + Number: number, + Alive: 1, + } + + return value, nil + } + } + tmp := f.NewRegister() typ, err := f.ExpressionToRegister(expr, tmp) - value := scope.Value{ + value := eval.Value{ + Kind: eval.Register, Type: typ, Register: tmp, Alive: 1, diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index d807371..be9811f 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -1,64 +1,38 @@ package core import ( + "fmt" + "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" - "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" ) // ExpressionToMemory puts the result of an expression into the specified memory address. func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (types.Type, error) { - if node.IsLeaf() { - if node.Token.Kind == token.Identifier { - name := node.Token.Text(f.File.Bytes) - variable, function := f.Identifier(name) - - if variable != nil { - f.MemoryRegister(asm.STORE, memory, variable.Register) - f.UseVariable(variable) - return types.AnyPointer, nil - } - - if function != nil { - f.MemoryLabel(asm.STORE, memory, function.UniqueName) - return types.AnyPointer, nil - } - - if name == "_exit" { - f.MemoryLabel(asm.STORE, memory, "_exit") - return types.AnyPointer, nil - } - - return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Token.Position) - } - - if node.Token.IsNumeric() { - number, err := f.ToNumber(node.Token) - - if err != nil { - return nil, err - } - - // size := byte(sizeof.Signed(int64(number))) - - // if size > memory.Length { - // return nil, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) - // } - - f.MemoryNumber(asm.STORE, memory, number) - return types.AnyInt, nil - } - } - value, err := f.Evaluate(node) if err != nil { return nil, err } - f.MemoryRegister(asm.STORE, memory, value.Register) - f.FreeRegister(value.Register) + switch value.Kind { + case eval.Number: + f.MemoryNumber(asm.STORE, memory, value.Number) + case eval.Register: + f.MemoryRegister(asm.STORE, memory, value.Register) + f.FreeRegister(value.Register) + case eval.Memory: + tmp := f.NewRegister() + f.MemoryRegister(asm.LOAD, value.Memory, tmp) + f.MemoryRegister(asm.STORE, memory, tmp) + f.FreeRegister(tmp) + case eval.Label: + f.MemoryLabel(asm.STORE, memory, value.Label) + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) + } + return value.Type, err } diff --git a/src/core/MultiAssign.go b/src/core/MultiAssign.go new file mode 100644 index 0000000..e6c3f13 --- /dev/null +++ b/src/core/MultiAssign.go @@ -0,0 +1,31 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" +) + +// MultiAssign assigns multiple return values to local variables. +func (f *Function) MultiAssign(left *expression.Expression, right *expression.Expression) error { + count := 0 + _, err := f.CompileCall(right) + + if err != nil { + return err + } + + return left.EachLeaf(func(leaf *expression.Expression) error { + name := leaf.Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) + } + + f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) + f.UseVariable(variable) + count++ + return nil + }) +} diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index cf17430..7db5a6c 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -2,6 +2,7 @@ package core import ( "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/scope" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" @@ -31,7 +32,8 @@ func (f *Function) ResolveTypes() error { f.AddVariable(&scope.Variable{ Name: param.name, - Value: scope.Value{ + Value: eval.Value{ + Kind: eval.Register, Type: param.typ, Register: x86.InputRegisters[i], Alive: uses, diff --git a/src/eval/Kind.go b/src/eval/Kind.go new file mode 100644 index 0000000..4a41f05 --- /dev/null +++ b/src/eval/Kind.go @@ -0,0 +1,11 @@ +package eval + +type Kind uint8 + +const ( + Invalid Kind = iota // Invalid is an invalid value. + Number // Number is an immediately encoded value stored together with the instruction. + Register // Register is a CPU register. + Memory // Memory is an area in the RAM. + Label // Label is a reference to a name that can only be resolved once the program is fully compiled. +) diff --git a/src/scope/Value.go b/src/eval/Value.go similarity index 80% rename from src/scope/Value.go rename to src/eval/Value.go index 93a3774..1205a98 100644 --- a/src/scope/Value.go +++ b/src/eval/Value.go @@ -1,6 +1,7 @@ -package scope +package eval import ( + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/types" ) @@ -8,8 +9,12 @@ import ( // Value combines a register with its data type. type Value struct { Type types.Type + Label string + Number int + Memory asm.Memory Register cpu.Register Alive uint8 + Kind Kind } // IsAlive returns true if the Value is still alive. diff --git a/src/readme.md b/src/readme.md index 3dc207b..99ce70e 100644 --- a/src/readme.md +++ b/src/readme.md @@ -14,6 +14,7 @@ - [dll](dll) - DLL support for Windows systems (w.i.p.) - [elf](elf) - ELF format for Linux executables - [errors](errors) - Error types +- [eval](eval) - Evaluates expressions - [expression](expression) - Expression parser generating trees with the `Parse` function - [fs](fs) - File system access - [macho](macho) - MachO format for Mac executables diff --git a/src/scope/Stack.go b/src/scope/Stack.go index 5037f45..c2c1e9d 100644 --- a/src/scope/Stack.go +++ b/src/scope/Stack.go @@ -3,6 +3,7 @@ package scope import ( "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/token" ) @@ -45,7 +46,8 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { s.Variables = append(s.Variables, &Variable{ Name: v.Name, - Value: Value{ + Value: eval.Value{ + Kind: eval.Register, Register: v.Register, Alive: count, Type: v.Type, diff --git a/src/scope/Variable.go b/src/scope/Variable.go index 4a387ae..72ea61b 100644 --- a/src/scope/Variable.go +++ b/src/scope/Variable.go @@ -1,7 +1,9 @@ package scope +import "git.urbach.dev/cli/q/src/eval" + // Variable is a named value. type Variable struct { - Value Name string + eval.Value } diff --git a/tests/programs/for.q b/tests/programs/for.q index e41e08f..894751f 100644 --- a/tests/programs/for.q +++ b/tests/programs/for.q @@ -11,7 +11,7 @@ main() { total -= 1 } - assert total == 5 + assert total == 0 for i := 0..10 { assert i >= 0 From e7afb2dab5a4e4953996361439314e0b0726b93b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Feb 2025 19:45:18 +0100 Subject: [PATCH 0844/1012] Added eval package --- src/core/CompileAssign.go | 26 +----- src/core/CompileAssignArray.go | 43 +++++----- src/core/CompileAssignDivision.go | 34 +++++--- src/core/CompileCondition.go | 24 ++++-- src/core/CompileFor.go | 34 ++++---- src/core/Define.go | 4 +- src/core/Evaluate.go | 127 ++++++++++++++++++++++++++---- src/core/ExpressionToMemory.go | 66 +++++----------- src/core/MultiAssign.go | 31 ++++++++ src/core/ResolveTypes.go | 4 +- src/eval/Kind.go | 11 +++ src/{scope => eval}/Value.go | 7 +- src/readme.md | 1 + src/scope/Stack.go | 4 +- src/scope/Variable.go | 4 +- tests/programs/for.q | 2 +- 16 files changed, 274 insertions(+), 148 deletions(-) create mode 100644 src/core/MultiAssign.go create mode 100644 src/eval/Kind.go rename src/{scope => eval}/Value.go (80%) diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 2ff6dfb..9773db9 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -1,16 +1,13 @@ package core import ( - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/errors" - "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" ) // CompileAssign compiles an assign statement. func (f *Function) CompileAssign(node *ast.Assign) error { - operator := node.Expression.Token left := node.Expression.Children[0] right := node.Expression.Children[1] @@ -23,7 +20,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { } defer f.UseVariable(variable) - return f.Execute(operator, variable.Register, right) + return f.Execute(node.Expression.Token, variable.Register, right) } if left.Token.Kind == token.Period { @@ -42,24 +39,5 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return errors.New(errors.NotImplemented, f.File, node.Expression.Token.Position) } - count := 0 - _, err := f.CompileCall(right) - - if err != nil { - return err - } - - return left.EachLeaf(func(leaf *expression.Expression) error { - name := leaf.Token.Text(f.File.Bytes) - variable := f.VariableByName(name) - - if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) - } - - f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) - f.UseVariable(variable) - count++ - return nil - }) + return f.MultiAssign(left, right) } diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index ab379e4..740c42b 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -1,11 +1,13 @@ package core import ( + "fmt" "math" "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/types" ) @@ -13,7 +15,6 @@ import ( func (f *Function) CompileAssignArray(node *ast.Assign) error { left := node.Expression.Children[0] right := node.Expression.Children[1] - name := left.Children[0].Token.Text(f.File.Bytes) variable := f.VariableByName(name) @@ -31,30 +32,26 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { } indexExpr := left.Children[1] + index, err := f.Evaluate(indexExpr) - if indexExpr.Token.IsNumeric() { - index, err := f.ToNumber(indexExpr.Token) - - if err != nil { - return err - } - - memory.Offset = int8(index) - } else { - index, err := f.Evaluate(indexExpr) - - if err != nil { - return err - } - - if !types.Is(index.Type, types.AnyInt) { - return errors.New(&errors.TypeMismatch{Encountered: index.Type.Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) - } - - memory.OffsetRegister = index.Register - defer f.FreeRegister(index.Register) + if err != nil { + return err } - _, err := f.ExpressionToMemory(right, memory) + if !types.Is(index.Type, types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: index.Type.Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) + } + + switch index.Kind { + case eval.Number: + memory.Offset = int8(index.Number) + case eval.Register: + memory.OffsetRegister = index.Register + defer f.FreeRegister(index.Register) + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, index.Kind)) + } + + _, err = f.ExpressionToMemory(right, memory) return err } diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index 619e19b..fd16573 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -1,8 +1,11 @@ package core import ( + "fmt" + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/scope" "git.urbach.dev/cli/q/src/token" @@ -13,21 +16,21 @@ import ( // CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right. func (f *Function) CompileAssignDivision(expr *expression.Expression) error { var ( - left = expr.Children[0] - right = expr.Children[1] + variables = expr.Children[0] + division = expr.Children[1] quotientVariable *scope.Variable remainderVariable *scope.Variable err error ) if expr.Token.Kind == token.Define { - quotientVariable, err = f.Define(left.Children[0]) + quotientVariable, err = f.Define(variables.Children[0]) if err != nil { return err } - remainderVariable, err = f.Define(left.Children[1]) + remainderVariable, err = f.Define(variables.Children[1]) if err != nil { return err @@ -38,7 +41,7 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { f.AddVariable(quotientVariable) f.AddVariable(remainderVariable) } else { - quotient := left.Children[0] + quotient := variables.Children[0] name := quotient.Token.Text(f.File.Bytes) quotientVariable = f.VariableByName(name) @@ -46,7 +49,7 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, quotient.Token.Position) } - remainder := left.Children[1] + remainder := variables.Children[1] name = remainder.Token.Text(f.File.Bytes) remainderVariable = f.VariableByName(name) @@ -58,7 +61,7 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { defer f.UseVariable(remainderVariable) } - dividendExpr := right.Children[0] + dividendExpr := division.Children[0] dividend, err := f.Evaluate(dividendExpr) if err != nil { @@ -69,10 +72,21 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { return errors.New(&errors.TypeMismatch{Encountered: dividend.Type.Name(), Expected: types.AnyInt.Name()}, f.File, dividendExpr.Token.Position) } - divisor := right.Children[1] - err = f.Execute(right.Token, dividend.Register, divisor) + divisor := division.Children[1] + + switch dividend.Kind { + case eval.Number: + f.SaveRegister(x86.RAX) + f.RegisterNumber(asm.MOVE, x86.RAX, dividend.Number) + err = f.Execute(division.Token, x86.RAX, divisor) + case eval.Register: + err = f.Execute(division.Token, dividend.Register, divisor) + defer f.FreeRegister(dividend.Register) + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, dividend.Kind)) + } + f.RegisterRegister(asm.MOVE, quotientVariable.Register, x86.RAX) f.RegisterRegister(asm.MOVE, remainderVariable.Register, x86.RDX) - f.FreeRegister(dividend.Register) return err } diff --git a/src/core/CompileCondition.go b/src/core/CompileCondition.go index 0e41f82..087731e 100644 --- a/src/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -1,8 +1,11 @@ package core import ( + "fmt" + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" @@ -66,22 +69,29 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab return err case token.Call: - typ, err := f.CompileCall(condition) + value, err := f.Evaluate(condition) if err != nil { return err } - if len(typ) == 0 { - return errors.New(errors.UntypedExpression, f.File, condition.Token.Position) + if !types.Is(value.Type, types.Bool) { + return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.Bool.Name()}, f.File, condition.Token.Position) } - if !types.Is(typ[0], types.Bool) { - return errors.New(&errors.TypeMismatch{Encountered: typ[0].Name(), Expected: types.Bool.Name()}, f.File, condition.Token.Position) + switch value.Kind { + case eval.Number: + if value.Number == 0 { + f.Jump(asm.JUMP, failLabel) + } + case eval.Register: + f.RegisterNumber(asm.COMPARE, value.Register, 0) + f.FreeRegister(value.Register) + f.Jump(asm.JE, failLabel) + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) } - f.RegisterNumber(asm.COMPARE, f.CPU.Output[0], 0) - f.Jump(asm.JE, failLabel) return nil case token.Equal, token.NotEqual, token.Greater, token.Less, token.GreaterEqual, token.LessEqual: diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index 98a63d9..1eafd68 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -1,10 +1,13 @@ package core import ( + "fmt" + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" @@ -58,29 +61,26 @@ func (f *Function) CompileFor(loop *ast.For) error { return err } - if to.Token.IsNumeric() { - number, err := f.ToNumber(to.Token) + value, err := f.Evaluate(to) - if err != nil { - return err - } + if err != nil { + return err + } - f.AddLabel(label) - f.RegisterNumber(asm.COMPARE, counter, number) - } else { - value, err := f.Evaluate(to) + if !types.Is(value.Type, types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) + } - if err != nil { - return err - } + f.AddLabel(label) - if !types.Is(value.Type, types.AnyInt) { - return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) - } - - f.AddLabel(label) + switch value.Kind { + case eval.Number: + f.RegisterNumber(asm.COMPARE, counter, value.Number) + case eval.Register: f.RegisterRegister(asm.COMPARE, counter, value.Register) defer f.FreeRegister(value.Register) + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) } f.Jump(asm.JGE, labelEnd) diff --git a/src/core/Define.go b/src/core/Define.go index d56208d..16ccdd6 100644 --- a/src/core/Define.go +++ b/src/core/Define.go @@ -2,6 +2,7 @@ package core import ( "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/scope" "git.urbach.dev/cli/q/src/token" @@ -24,7 +25,8 @@ func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) variable = &scope.Variable{ Name: name, - Value: scope.Value{ + Value: eval.Value{ + Kind: eval.Register, Register: f.NewRegister(), Alive: uses, }, diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 44adb18..a2af299 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -1,37 +1,88 @@ package core import ( - "git.urbach.dev/cli/q/src/ast" + "math" + + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" - "git.urbach.dev/cli/q/src/scope" "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // Evaluate evaluates an expression and returns a register that contains the value of the expression. -func (f *Function) Evaluate(expr *expression.Expression) (scope.Value, error) { - if expr.Token.Kind == token.Identifier { - name := expr.Token.Text(f.File.Bytes) - variable := f.VariableByName(name) +func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { + if expr.IsLeaf() { + if expr.Token.IsNumeric() { + number, err := f.ToNumber(expr.Token) - if variable == nil { - return scope.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) + if err != nil { + return eval.Value{}, err + } + + value := eval.Value{ + Kind: eval.Number, + Type: types.AnyInt, + Number: number, + Alive: 1, + } + + return value, nil } - if variable.Alive == 1 { - f.UseVariable(variable) - return variable.Value, nil + if expr.Token.Kind == token.Identifier { + name := expr.Token.Text(f.File.Bytes) + variable, function := f.Identifier(name) + + if variable != nil { + f.UseVariable(variable) + + if variable.Alive == 0 { + return variable.Value, nil + } + + tmp := f.NewRegister() + f.RegisterRegister(asm.MOVE, tmp, variable.Register) + + value := eval.Value{ + Kind: eval.Register, + Type: variable.Type, + Register: tmp, + Alive: 1, + } + + return value, nil + } + + if function != nil { + value := eval.Value{ + Kind: eval.Label, + Type: types.AnyPointer, + Label: function.UniqueName, + Alive: 1, + } + + return value, nil + } + + return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) } } - if ast.IsFunctionCall(expr) { + if expr.Token.Kind == token.Call { types, err := f.CompileCall(expr) if err != nil { - return scope.Value{}, err + return eval.Value{}, err } - value := scope.Value{ + if len(types) == 0 { + return eval.Value{}, errors.New(errors.UntypedExpression, f.File, expr.Token.Position) + } + + value := eval.Value{ + Kind: eval.Register, Type: types[0], Register: f.CPU.Output[0], Alive: 1, @@ -40,10 +91,56 @@ func (f *Function) Evaluate(expr *expression.Expression) (scope.Value, error) { return value, nil } + if expr.Token.Kind == token.Period { + left := expr.Children[0] + right := expr.Children[1] + leftText := left.Token.Text(f.File.Bytes) + rightText := right.Token.Text(f.File.Bytes) + variable := f.VariableByName(leftText) + + if variable != nil { + field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + + value := eval.Value{ + Kind: eval.Memory, + Type: field.Type, + Alive: 1, + Memory: asm.Memory{ + Base: variable.Register, + Offset: int8(field.Offset), + OffsetRegister: math.MaxUint8, + Length: byte(field.Type.Size()), + }, + } + + return value, nil + } + + constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] + + if isConst { + number, err := ToNumber(constant.Token, constant.File) + + if err != nil { + return eval.Value{}, err + } + + value := eval.Value{ + Kind: eval.Number, + Type: types.AnyInt, + Number: number, + Alive: 1, + } + + return value, nil + } + } + tmp := f.NewRegister() typ, err := f.ExpressionToRegister(expr, tmp) - value := scope.Value{ + value := eval.Value{ + Kind: eval.Register, Type: typ, Register: tmp, Alive: 1, diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index d807371..be9811f 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -1,64 +1,38 @@ package core import ( + "fmt" + "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" - "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" ) // ExpressionToMemory puts the result of an expression into the specified memory address. func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Memory) (types.Type, error) { - if node.IsLeaf() { - if node.Token.Kind == token.Identifier { - name := node.Token.Text(f.File.Bytes) - variable, function := f.Identifier(name) - - if variable != nil { - f.MemoryRegister(asm.STORE, memory, variable.Register) - f.UseVariable(variable) - return types.AnyPointer, nil - } - - if function != nil { - f.MemoryLabel(asm.STORE, memory, function.UniqueName) - return types.AnyPointer, nil - } - - if name == "_exit" { - f.MemoryLabel(asm.STORE, memory, "_exit") - return types.AnyPointer, nil - } - - return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Token.Position) - } - - if node.Token.IsNumeric() { - number, err := f.ToNumber(node.Token) - - if err != nil { - return nil, err - } - - // size := byte(sizeof.Signed(int64(number))) - - // if size > memory.Length { - // return nil, errors.New(&errors.NumberExceedsBounds{Number: number, ExpectedSize: memory.Length, Size: size}, f.File, node.Token.Position) - // } - - f.MemoryNumber(asm.STORE, memory, number) - return types.AnyInt, nil - } - } - value, err := f.Evaluate(node) if err != nil { return nil, err } - f.MemoryRegister(asm.STORE, memory, value.Register) - f.FreeRegister(value.Register) + switch value.Kind { + case eval.Number: + f.MemoryNumber(asm.STORE, memory, value.Number) + case eval.Register: + f.MemoryRegister(asm.STORE, memory, value.Register) + f.FreeRegister(value.Register) + case eval.Memory: + tmp := f.NewRegister() + f.MemoryRegister(asm.LOAD, value.Memory, tmp) + f.MemoryRegister(asm.STORE, memory, tmp) + f.FreeRegister(tmp) + case eval.Label: + f.MemoryLabel(asm.STORE, memory, value.Label) + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) + } + return value.Type, err } diff --git a/src/core/MultiAssign.go b/src/core/MultiAssign.go new file mode 100644 index 0000000..e6c3f13 --- /dev/null +++ b/src/core/MultiAssign.go @@ -0,0 +1,31 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/expression" +) + +// MultiAssign assigns multiple return values to local variables. +func (f *Function) MultiAssign(left *expression.Expression, right *expression.Expression) error { + count := 0 + _, err := f.CompileCall(right) + + if err != nil { + return err + } + + return left.EachLeaf(func(leaf *expression.Expression) error { + name := leaf.Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + + if variable == nil { + return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) + } + + f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) + f.UseVariable(variable) + count++ + return nil + }) +} diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index cf17430..7db5a6c 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -2,6 +2,7 @@ package core import ( "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/scope" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" @@ -31,7 +32,8 @@ func (f *Function) ResolveTypes() error { f.AddVariable(&scope.Variable{ Name: param.name, - Value: scope.Value{ + Value: eval.Value{ + Kind: eval.Register, Type: param.typ, Register: x86.InputRegisters[i], Alive: uses, diff --git a/src/eval/Kind.go b/src/eval/Kind.go new file mode 100644 index 0000000..4a41f05 --- /dev/null +++ b/src/eval/Kind.go @@ -0,0 +1,11 @@ +package eval + +type Kind uint8 + +const ( + Invalid Kind = iota // Invalid is an invalid value. + Number // Number is an immediately encoded value stored together with the instruction. + Register // Register is a CPU register. + Memory // Memory is an area in the RAM. + Label // Label is a reference to a name that can only be resolved once the program is fully compiled. +) diff --git a/src/scope/Value.go b/src/eval/Value.go similarity index 80% rename from src/scope/Value.go rename to src/eval/Value.go index 93a3774..1205a98 100644 --- a/src/scope/Value.go +++ b/src/eval/Value.go @@ -1,6 +1,7 @@ -package scope +package eval import ( + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/types" ) @@ -8,8 +9,12 @@ import ( // Value combines a register with its data type. type Value struct { Type types.Type + Label string + Number int + Memory asm.Memory Register cpu.Register Alive uint8 + Kind Kind } // IsAlive returns true if the Value is still alive. diff --git a/src/readme.md b/src/readme.md index 3dc207b..99ce70e 100644 --- a/src/readme.md +++ b/src/readme.md @@ -14,6 +14,7 @@ - [dll](dll) - DLL support for Windows systems (w.i.p.) - [elf](elf) - ELF format for Linux executables - [errors](errors) - Error types +- [eval](eval) - Evaluates expressions - [expression](expression) - Expression parser generating trees with the `Parse` function - [fs](fs) - File system access - [macho](macho) - MachO format for Mac executables diff --git a/src/scope/Stack.go b/src/scope/Stack.go index 5037f45..c2c1e9d 100644 --- a/src/scope/Stack.go +++ b/src/scope/Stack.go @@ -3,6 +3,7 @@ package scope import ( "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/token" ) @@ -45,7 +46,8 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { s.Variables = append(s.Variables, &Variable{ Name: v.Name, - Value: Value{ + Value: eval.Value{ + Kind: eval.Register, Register: v.Register, Alive: count, Type: v.Type, diff --git a/src/scope/Variable.go b/src/scope/Variable.go index 4a387ae..72ea61b 100644 --- a/src/scope/Variable.go +++ b/src/scope/Variable.go @@ -1,7 +1,9 @@ package scope +import "git.urbach.dev/cli/q/src/eval" + // Variable is a named value. type Variable struct { - Value Name string + eval.Value } diff --git a/tests/programs/for.q b/tests/programs/for.q index e41e08f..894751f 100644 --- a/tests/programs/for.q +++ b/tests/programs/for.q @@ -11,7 +11,7 @@ main() { total -= 1 } - assert total == 5 + assert total == 0 for i := 0..10 { assert i >= 0 From fbcadae26864d1ff8ef6d694976b94898b5579b4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Feb 2025 23:28:17 +0100 Subject: [PATCH 0845/1012] Simplified Evaluate function --- src/core/CompileAssign.go | 2 +- src/core/CompileLen.go | 24 +++- .../{PeriodToRegister.go => DotToRegister.go} | 6 +- src/core/Evaluate.go | 133 ++---------------- src/core/EvaluateArray.go | 60 ++++++++ src/core/EvaluateCall.go | 28 ++++ src/core/EvaluateDot.go | 73 ++++++++++ src/core/EvaluateLeaf.go | 107 ++++++++++++++ src/core/ExpressionToRegister.go | 4 +- src/core/Fold.go | 2 +- src/expression/Operator.go | 2 +- src/token/Kind.go | 2 +- src/token/Tokenize_test.go | 6 +- src/token/operator.go | 2 +- 14 files changed, 318 insertions(+), 133 deletions(-) rename src/core/{PeriodToRegister.go => DotToRegister.go} (81%) create mode 100644 src/core/EvaluateArray.go create mode 100644 src/core/EvaluateCall.go create mode 100644 src/core/EvaluateDot.go create mode 100644 src/core/EvaluateLeaf.go diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 9773db9..910cda0 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -23,7 +23,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return f.Execute(node.Expression.Token, variable.Register, right) } - if left.Token.Kind == token.Period { + if left.Token.Kind == token.Dot { return f.CompileAssignField(node) } diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index 076bcc1..b1d117c 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -1,10 +1,12 @@ package core import ( + "fmt" "math" "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/types" ) @@ -23,8 +25,26 @@ func (f *Function) CompileLen(root *expression.Expression) error { return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyArray.Name(), ParameterName: "array"}, f.File, root.Children[1].Token.Position) } - f.SaveRegister(f.CPU.Output[0]) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: value.Register, Offset: -8, OffsetRegister: math.MaxUint8, Length: 8}, f.CPU.Output[0]) + memory := asm.Memory{ + Offset: -8, + OffsetRegister: math.MaxUint8, + Length: 8, + } + + output := f.CPU.Output[0] + f.SaveRegister(output) + + switch value.Kind { + case eval.Register: + memory.Base = value.Register + case eval.Label: + f.RegisterLabel(asm.MOVE, output, value.Label) + memory.Base = output + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) + } + + f.MemoryRegister(asm.LOAD, memory, output) f.FreeRegister(value.Register) return nil } diff --git a/src/core/PeriodToRegister.go b/src/core/DotToRegister.go similarity index 81% rename from src/core/PeriodToRegister.go rename to src/core/DotToRegister.go index aeb7675..d76ac81 100644 --- a/src/core/PeriodToRegister.go +++ b/src/core/DotToRegister.go @@ -11,8 +11,8 @@ import ( "git.urbach.dev/cli/q/src/types" ) -// PeriodToRegister moves a constant or a function address into the given register. -func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { +// DotToRegister moves a constant or a function address into the given register. +func (f *Function) DotToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { left := node.Children[0] right := node.Children[1] leftText := left.Token.Text(f.File.Bytes) @@ -56,5 +56,5 @@ func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Re return types.AnyPointer, nil } - return nil, errors.New(&errors.UnknownIdentifier{Name: leftText}, f.File, left.Token.Position) + return nil, errors.New(&errors.UnknownIdentifier{Name: uniqueName}, f.File, left.Token.Position) } diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index a2af299..05e5a54 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -1,139 +1,37 @@ package core import ( - "math" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" ) -// Evaluate evaluates an expression and returns a register that contains the value of the expression. +// Evaluate evaluates an expression and returns a value. func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { - if expr.IsLeaf() { - if expr.Token.IsNumeric() { - number, err := f.ToNumber(expr.Token) - - if err != nil { - return eval.Value{}, err - } - - value := eval.Value{ - Kind: eval.Number, - Type: types.AnyInt, - Number: number, - Alive: 1, - } - - return value, nil - } - - if expr.Token.Kind == token.Identifier { - name := expr.Token.Text(f.File.Bytes) - variable, function := f.Identifier(name) - - if variable != nil { - f.UseVariable(variable) - - if variable.Alive == 0 { - return variable.Value, nil - } - - tmp := f.NewRegister() - f.RegisterRegister(asm.MOVE, tmp, variable.Register) - - value := eval.Value{ - Kind: eval.Register, - Type: variable.Type, - Register: tmp, - Alive: 1, - } - - return value, nil - } - - if function != nil { - value := eval.Value{ - Kind: eval.Label, - Type: types.AnyPointer, - Label: function.UniqueName, - Alive: 1, - } - - return value, nil - } - - return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) - } - } - - if expr.Token.Kind == token.Call { - types, err := f.CompileCall(expr) - - if err != nil { - return eval.Value{}, err - } - - if len(types) == 0 { - return eval.Value{}, errors.New(errors.UntypedExpression, f.File, expr.Token.Position) - } - + if expr.IsFolded { value := eval.Value{ - Kind: eval.Register, - Type: types[0], - Register: f.CPU.Output[0], - Alive: 1, + Kind: eval.Number, + Type: types.AnyInt, + Number: expr.Value, } return value, nil } - if expr.Token.Kind == token.Period { - left := expr.Children[0] - right := expr.Children[1] - leftText := left.Token.Text(f.File.Bytes) - rightText := right.Token.Text(f.File.Bytes) - variable := f.VariableByName(leftText) + if expr.IsLeaf() { + return f.EvaluateLeaf(expr) + } - if variable != nil { - field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + switch expr.Token.Kind { + case token.Call: + return f.EvaluateCall(expr) - value := eval.Value{ - Kind: eval.Memory, - Type: field.Type, - Alive: 1, - Memory: asm.Memory{ - Base: variable.Register, - Offset: int8(field.Offset), - OffsetRegister: math.MaxUint8, - Length: byte(field.Type.Size()), - }, - } + case token.Dot: + return f.EvaluateDot(expr) - return value, nil - } - - constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] - - if isConst { - number, err := ToNumber(constant.Token, constant.File) - - if err != nil { - return eval.Value{}, err - } - - value := eval.Value{ - Kind: eval.Number, - Type: types.AnyInt, - Number: number, - Alive: 1, - } - - return value, nil - } + case token.Array: + return f.EvaluateArray(expr) } tmp := f.NewRegister() @@ -143,7 +41,6 @@ func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { Kind: eval.Register, Type: typ, Register: tmp, - Alive: 1, } return value, err diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go new file mode 100644 index 0000000..8457dcd --- /dev/null +++ b/src/core/EvaluateArray.go @@ -0,0 +1,60 @@ +package core + +import ( + "fmt" + "math" + + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" +) + +// EvaluateArray evaluates a function call. +func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Value, error) { + name := expr.Children[0].Token.Text(f.File.Bytes) + array := f.VariableByName(name) + + if array == nil { + return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) + } + + defer f.UseVariable(array) + + memory := asm.Memory{ + Base: array.Register, + Offset: 0, + OffsetRegister: math.MaxUint8, + Length: byte(1), + } + + indexExpr := expr.Children[1] + index, err := f.Evaluate(indexExpr) + + if err != nil { + return eval.Value{}, err + } + + if !types.Is(index.Type, types.AnyInt) { + return eval.Value{}, errors.New(&errors.TypeMismatch{Encountered: index.Type.Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) + } + + switch index.Kind { + case eval.Number: + memory.Offset = int8(index.Number) + case eval.Register: + memory.OffsetRegister = index.Register + defer f.FreeRegister(index.Register) + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, index.Kind)) + } + + value := eval.Value{ + Kind: eval.Memory, + Type: array.Type.(*types.Array).Of, + Memory: memory, + } + + return value, nil +} diff --git a/src/core/EvaluateCall.go b/src/core/EvaluateCall.go new file mode 100644 index 0000000..e5a1eef --- /dev/null +++ b/src/core/EvaluateCall.go @@ -0,0 +1,28 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" + "git.urbach.dev/cli/q/src/expression" +) + +// EvaluateCall evaluates a function call. +func (f *Function) EvaluateCall(expr *expression.Expression) (eval.Value, error) { + types, err := f.CompileCall(expr) + + if err != nil { + return eval.Value{}, err + } + + if len(types) == 0 { + return eval.Value{}, errors.New(errors.UntypedExpression, f.File, expr.Token.Position) + } + + value := eval.Value{ + Kind: eval.Register, + Type: types[0], + Register: f.CPU.Output[0], + } + + return value, nil +} diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go new file mode 100644 index 0000000..46b68ec --- /dev/null +++ b/src/core/EvaluateDot.go @@ -0,0 +1,73 @@ +package core + +import ( + "fmt" + "math" + + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" +) + +// EvaluateDot evaluates an access with the dot operator. +func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) { + left := expr.Children[0] + right := expr.Children[1] + leftText := left.Token.Text(f.File.Bytes) + rightText := right.Token.Text(f.File.Bytes) + variable := f.VariableByName(leftText) + + if variable != nil { + field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + + value := eval.Value{ + Kind: eval.Memory, + Type: field.Type, + Memory: asm.Memory{ + Base: variable.Register, + Offset: int8(field.Offset), + OffsetRegister: math.MaxUint8, + Length: byte(field.Type.Size()), + }, + } + + return value, nil + } + + constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] + + if isConst { + number, err := ToNumber(constant.Token, constant.File) + + if err != nil { + return eval.Value{}, err + } + + value := eval.Value{ + Kind: eval.Number, + Type: types.AnyInt, + Number: number, + } + + return value, nil + } + + uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) + function, exists := f.All.Functions[uniqueName] + + if exists { + f.File.Imports[leftText].Used = true + + value := eval.Value{ + Kind: eval.Label, + Type: types.AnyPointer, + Label: function.UniqueName, + } + + return value, nil + } + + return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: uniqueName}, f.File, left.Token.Position) +} diff --git a/src/core/EvaluateLeaf.go b/src/core/EvaluateLeaf.go new file mode 100644 index 0000000..af9446e --- /dev/null +++ b/src/core/EvaluateLeaf.go @@ -0,0 +1,107 @@ +package core + +import ( + "encoding/binary" + + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" +) + +// EvaluateLeaf evaluates a leaf expression. +func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) { + switch expr.Token.Kind { + case token.Identifier: + name := expr.Token.Text(f.File.Bytes) + + if name == "true" { + value := eval.Value{ + Kind: eval.Number, + Type: types.Bool, + Number: 1, + } + + return value, nil + } + + if name == "false" { + value := eval.Value{ + Kind: eval.Number, + Type: types.Bool, + Number: 0, + } + + return value, nil + } + + variable, function := f.Identifier(name) + + if variable != nil { + f.UseVariable(variable) + + if variable.Alive == 0 { + return variable.Value, nil + } + + tmp := f.NewRegister() + f.RegisterRegister(asm.MOVE, tmp, variable.Register) + + value := eval.Value{ + Kind: eval.Register, + Type: variable.Type, + Register: tmp, + } + + return value, nil + } + + if function != nil { + value := eval.Value{ + Kind: eval.Label, + Type: types.AnyPointer, + Label: function.UniqueName, + } + + return value, nil + } + + return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) + + case token.Number, token.Rune: + number, err := f.ToNumber(expr.Token) + + if err != nil { + return eval.Value{}, err + } + + value := eval.Value{ + Kind: eval.Number, + Type: types.AnyInt, + Number: number, + } + + return value, nil + + case token.String: + data := expr.Token.Bytes(f.File.Bytes) + data = String(data) + + slice := make([]byte, len(data)+8+1) + binary.LittleEndian.PutUint64(slice, uint64(len(data))) + copy(slice[8:], data) + label := f.AddBytes(slice) + + value := eval.Value{ + Kind: eval.Label, + Type: types.String, + Label: label, + } + + return value, nil + } + + return eval.Value{}, errors.New(errors.InvalidExpression, f.File, expr.Token.Position) +} diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 54d82b2..b81d5cd 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -25,8 +25,8 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return f.CallToRegister(node, register) case token.Array: return f.ArrayElementToRegister(node, register) - case token.Period: - return f.PeriodToRegister(node, register) + case token.Dot: + return f.DotToRegister(node, register) } if len(node.Children) == 1 { diff --git a/src/core/Fold.go b/src/core/Fold.go index 11ae861..bb1419a 100644 --- a/src/core/Fold.go +++ b/src/core/Fold.go @@ -20,7 +20,7 @@ func (f *Function) Fold(expr *expression.Expression) error { return f.FoldLeaf(expr) } - if expr.Token.Kind == token.Period { + if expr.Token.Kind == token.Dot { return f.FoldConstant(expr) } diff --git a/src/expression/Operator.go b/src/expression/Operator.go index 63f77c1..cd626d4 100644 --- a/src/expression/Operator.go +++ b/src/expression/Operator.go @@ -16,7 +16,7 @@ type Operator struct { // Operators defines the operators used in the language. // The number corresponds to the operator priority and can not be zero. var Operators = [64]Operator{ - token.Period: {".", 13, 2}, + token.Dot: {".", 13, 2}, token.Call: {"λ", 12, 1}, token.Array: {"@", 12, 2}, token.Negate: {"-", 11, 1}, diff --git a/src/token/Kind.go b/src/token/Kind.go index 2e0d764..881bd18 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -33,7 +33,7 @@ const ( LogicalAnd // && LogicalOr // || Define // := - Period // . + Dot // . Range // .. Call // x() Array // [x] diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index 65b8abe..19ebf16 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -216,14 +216,14 @@ func TestDefine(t *testing.T) { } } -func TestPeriod(t *testing.T) { +func TestDot(t *testing.T) { tokens := token.Tokenize([]byte(`a.b.c`)) expected := []token.Kind{ token.Identifier, - token.Period, + token.Dot, token.Identifier, - token.Period, + token.Dot, token.Identifier, token.EOF, } diff --git a/src/token/operator.go b/src/token/operator.go index 5d74059..3352f9e 100644 --- a/src/token/operator.go +++ b/src/token/operator.go @@ -35,7 +35,7 @@ func operator(tokens List, buffer []byte, i Position) (List, Position) { case "+=": kind = AddAssign case ".": - kind = Period + kind = Dot case "..": kind = Range case ":=": From 31423ccc08aacff538753d445d3777469d07ef01 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Feb 2025 23:28:17 +0100 Subject: [PATCH 0846/1012] Simplified Evaluate function --- src/core/CompileAssign.go | 2 +- src/core/CompileLen.go | 24 +++- .../{PeriodToRegister.go => DotToRegister.go} | 6 +- src/core/Evaluate.go | 133 ++---------------- src/core/EvaluateArray.go | 60 ++++++++ src/core/EvaluateCall.go | 28 ++++ src/core/EvaluateDot.go | 73 ++++++++++ src/core/EvaluateLeaf.go | 107 ++++++++++++++ src/core/ExpressionToRegister.go | 4 +- src/core/Fold.go | 2 +- src/expression/Operator.go | 2 +- src/token/Kind.go | 2 +- src/token/Tokenize_test.go | 6 +- src/token/operator.go | 2 +- 14 files changed, 318 insertions(+), 133 deletions(-) rename src/core/{PeriodToRegister.go => DotToRegister.go} (81%) create mode 100644 src/core/EvaluateArray.go create mode 100644 src/core/EvaluateCall.go create mode 100644 src/core/EvaluateDot.go create mode 100644 src/core/EvaluateLeaf.go diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 9773db9..910cda0 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -23,7 +23,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return f.Execute(node.Expression.Token, variable.Register, right) } - if left.Token.Kind == token.Period { + if left.Token.Kind == token.Dot { return f.CompileAssignField(node) } diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index 076bcc1..b1d117c 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -1,10 +1,12 @@ package core import ( + "fmt" "math" "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/types" ) @@ -23,8 +25,26 @@ func (f *Function) CompileLen(root *expression.Expression) error { return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyArray.Name(), ParameterName: "array"}, f.File, root.Children[1].Token.Position) } - f.SaveRegister(f.CPU.Output[0]) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: value.Register, Offset: -8, OffsetRegister: math.MaxUint8, Length: 8}, f.CPU.Output[0]) + memory := asm.Memory{ + Offset: -8, + OffsetRegister: math.MaxUint8, + Length: 8, + } + + output := f.CPU.Output[0] + f.SaveRegister(output) + + switch value.Kind { + case eval.Register: + memory.Base = value.Register + case eval.Label: + f.RegisterLabel(asm.MOVE, output, value.Label) + memory.Base = output + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) + } + + f.MemoryRegister(asm.LOAD, memory, output) f.FreeRegister(value.Register) return nil } diff --git a/src/core/PeriodToRegister.go b/src/core/DotToRegister.go similarity index 81% rename from src/core/PeriodToRegister.go rename to src/core/DotToRegister.go index aeb7675..d76ac81 100644 --- a/src/core/PeriodToRegister.go +++ b/src/core/DotToRegister.go @@ -11,8 +11,8 @@ import ( "git.urbach.dev/cli/q/src/types" ) -// PeriodToRegister moves a constant or a function address into the given register. -func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { +// DotToRegister moves a constant or a function address into the given register. +func (f *Function) DotToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { left := node.Children[0] right := node.Children[1] leftText := left.Token.Text(f.File.Bytes) @@ -56,5 +56,5 @@ func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Re return types.AnyPointer, nil } - return nil, errors.New(&errors.UnknownIdentifier{Name: leftText}, f.File, left.Token.Position) + return nil, errors.New(&errors.UnknownIdentifier{Name: uniqueName}, f.File, left.Token.Position) } diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index a2af299..05e5a54 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -1,139 +1,37 @@ package core import ( - "math" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" ) -// Evaluate evaluates an expression and returns a register that contains the value of the expression. +// Evaluate evaluates an expression and returns a value. func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { - if expr.IsLeaf() { - if expr.Token.IsNumeric() { - number, err := f.ToNumber(expr.Token) - - if err != nil { - return eval.Value{}, err - } - - value := eval.Value{ - Kind: eval.Number, - Type: types.AnyInt, - Number: number, - Alive: 1, - } - - return value, nil - } - - if expr.Token.Kind == token.Identifier { - name := expr.Token.Text(f.File.Bytes) - variable, function := f.Identifier(name) - - if variable != nil { - f.UseVariable(variable) - - if variable.Alive == 0 { - return variable.Value, nil - } - - tmp := f.NewRegister() - f.RegisterRegister(asm.MOVE, tmp, variable.Register) - - value := eval.Value{ - Kind: eval.Register, - Type: variable.Type, - Register: tmp, - Alive: 1, - } - - return value, nil - } - - if function != nil { - value := eval.Value{ - Kind: eval.Label, - Type: types.AnyPointer, - Label: function.UniqueName, - Alive: 1, - } - - return value, nil - } - - return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) - } - } - - if expr.Token.Kind == token.Call { - types, err := f.CompileCall(expr) - - if err != nil { - return eval.Value{}, err - } - - if len(types) == 0 { - return eval.Value{}, errors.New(errors.UntypedExpression, f.File, expr.Token.Position) - } - + if expr.IsFolded { value := eval.Value{ - Kind: eval.Register, - Type: types[0], - Register: f.CPU.Output[0], - Alive: 1, + Kind: eval.Number, + Type: types.AnyInt, + Number: expr.Value, } return value, nil } - if expr.Token.Kind == token.Period { - left := expr.Children[0] - right := expr.Children[1] - leftText := left.Token.Text(f.File.Bytes) - rightText := right.Token.Text(f.File.Bytes) - variable := f.VariableByName(leftText) + if expr.IsLeaf() { + return f.EvaluateLeaf(expr) + } - if variable != nil { - field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + switch expr.Token.Kind { + case token.Call: + return f.EvaluateCall(expr) - value := eval.Value{ - Kind: eval.Memory, - Type: field.Type, - Alive: 1, - Memory: asm.Memory{ - Base: variable.Register, - Offset: int8(field.Offset), - OffsetRegister: math.MaxUint8, - Length: byte(field.Type.Size()), - }, - } + case token.Dot: + return f.EvaluateDot(expr) - return value, nil - } - - constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] - - if isConst { - number, err := ToNumber(constant.Token, constant.File) - - if err != nil { - return eval.Value{}, err - } - - value := eval.Value{ - Kind: eval.Number, - Type: types.AnyInt, - Number: number, - Alive: 1, - } - - return value, nil - } + case token.Array: + return f.EvaluateArray(expr) } tmp := f.NewRegister() @@ -143,7 +41,6 @@ func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { Kind: eval.Register, Type: typ, Register: tmp, - Alive: 1, } return value, err diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go new file mode 100644 index 0000000..8457dcd --- /dev/null +++ b/src/core/EvaluateArray.go @@ -0,0 +1,60 @@ +package core + +import ( + "fmt" + "math" + + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" +) + +// EvaluateArray evaluates a function call. +func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Value, error) { + name := expr.Children[0].Token.Text(f.File.Bytes) + array := f.VariableByName(name) + + if array == nil { + return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) + } + + defer f.UseVariable(array) + + memory := asm.Memory{ + Base: array.Register, + Offset: 0, + OffsetRegister: math.MaxUint8, + Length: byte(1), + } + + indexExpr := expr.Children[1] + index, err := f.Evaluate(indexExpr) + + if err != nil { + return eval.Value{}, err + } + + if !types.Is(index.Type, types.AnyInt) { + return eval.Value{}, errors.New(&errors.TypeMismatch{Encountered: index.Type.Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) + } + + switch index.Kind { + case eval.Number: + memory.Offset = int8(index.Number) + case eval.Register: + memory.OffsetRegister = index.Register + defer f.FreeRegister(index.Register) + default: + panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, index.Kind)) + } + + value := eval.Value{ + Kind: eval.Memory, + Type: array.Type.(*types.Array).Of, + Memory: memory, + } + + return value, nil +} diff --git a/src/core/EvaluateCall.go b/src/core/EvaluateCall.go new file mode 100644 index 0000000..e5a1eef --- /dev/null +++ b/src/core/EvaluateCall.go @@ -0,0 +1,28 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" + "git.urbach.dev/cli/q/src/expression" +) + +// EvaluateCall evaluates a function call. +func (f *Function) EvaluateCall(expr *expression.Expression) (eval.Value, error) { + types, err := f.CompileCall(expr) + + if err != nil { + return eval.Value{}, err + } + + if len(types) == 0 { + return eval.Value{}, errors.New(errors.UntypedExpression, f.File, expr.Token.Position) + } + + value := eval.Value{ + Kind: eval.Register, + Type: types[0], + Register: f.CPU.Output[0], + } + + return value, nil +} diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go new file mode 100644 index 0000000..46b68ec --- /dev/null +++ b/src/core/EvaluateDot.go @@ -0,0 +1,73 @@ +package core + +import ( + "fmt" + "math" + + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/types" +) + +// EvaluateDot evaluates an access with the dot operator. +func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) { + left := expr.Children[0] + right := expr.Children[1] + leftText := left.Token.Text(f.File.Bytes) + rightText := right.Token.Text(f.File.Bytes) + variable := f.VariableByName(leftText) + + if variable != nil { + field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + + value := eval.Value{ + Kind: eval.Memory, + Type: field.Type, + Memory: asm.Memory{ + Base: variable.Register, + Offset: int8(field.Offset), + OffsetRegister: math.MaxUint8, + Length: byte(field.Type.Size()), + }, + } + + return value, nil + } + + constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] + + if isConst { + number, err := ToNumber(constant.Token, constant.File) + + if err != nil { + return eval.Value{}, err + } + + value := eval.Value{ + Kind: eval.Number, + Type: types.AnyInt, + Number: number, + } + + return value, nil + } + + uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) + function, exists := f.All.Functions[uniqueName] + + if exists { + f.File.Imports[leftText].Used = true + + value := eval.Value{ + Kind: eval.Label, + Type: types.AnyPointer, + Label: function.UniqueName, + } + + return value, nil + } + + return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: uniqueName}, f.File, left.Token.Position) +} diff --git a/src/core/EvaluateLeaf.go b/src/core/EvaluateLeaf.go new file mode 100644 index 0000000..af9446e --- /dev/null +++ b/src/core/EvaluateLeaf.go @@ -0,0 +1,107 @@ +package core + +import ( + "encoding/binary" + + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" +) + +// EvaluateLeaf evaluates a leaf expression. +func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) { + switch expr.Token.Kind { + case token.Identifier: + name := expr.Token.Text(f.File.Bytes) + + if name == "true" { + value := eval.Value{ + Kind: eval.Number, + Type: types.Bool, + Number: 1, + } + + return value, nil + } + + if name == "false" { + value := eval.Value{ + Kind: eval.Number, + Type: types.Bool, + Number: 0, + } + + return value, nil + } + + variable, function := f.Identifier(name) + + if variable != nil { + f.UseVariable(variable) + + if variable.Alive == 0 { + return variable.Value, nil + } + + tmp := f.NewRegister() + f.RegisterRegister(asm.MOVE, tmp, variable.Register) + + value := eval.Value{ + Kind: eval.Register, + Type: variable.Type, + Register: tmp, + } + + return value, nil + } + + if function != nil { + value := eval.Value{ + Kind: eval.Label, + Type: types.AnyPointer, + Label: function.UniqueName, + } + + return value, nil + } + + return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) + + case token.Number, token.Rune: + number, err := f.ToNumber(expr.Token) + + if err != nil { + return eval.Value{}, err + } + + value := eval.Value{ + Kind: eval.Number, + Type: types.AnyInt, + Number: number, + } + + return value, nil + + case token.String: + data := expr.Token.Bytes(f.File.Bytes) + data = String(data) + + slice := make([]byte, len(data)+8+1) + binary.LittleEndian.PutUint64(slice, uint64(len(data))) + copy(slice[8:], data) + label := f.AddBytes(slice) + + value := eval.Value{ + Kind: eval.Label, + Type: types.String, + Label: label, + } + + return value, nil + } + + return eval.Value{}, errors.New(errors.InvalidExpression, f.File, expr.Token.Position) +} diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 54d82b2..b81d5cd 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -25,8 +25,8 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return f.CallToRegister(node, register) case token.Array: return f.ArrayElementToRegister(node, register) - case token.Period: - return f.PeriodToRegister(node, register) + case token.Dot: + return f.DotToRegister(node, register) } if len(node.Children) == 1 { diff --git a/src/core/Fold.go b/src/core/Fold.go index 11ae861..bb1419a 100644 --- a/src/core/Fold.go +++ b/src/core/Fold.go @@ -20,7 +20,7 @@ func (f *Function) Fold(expr *expression.Expression) error { return f.FoldLeaf(expr) } - if expr.Token.Kind == token.Period { + if expr.Token.Kind == token.Dot { return f.FoldConstant(expr) } diff --git a/src/expression/Operator.go b/src/expression/Operator.go index 63f77c1..cd626d4 100644 --- a/src/expression/Operator.go +++ b/src/expression/Operator.go @@ -16,7 +16,7 @@ type Operator struct { // Operators defines the operators used in the language. // The number corresponds to the operator priority and can not be zero. var Operators = [64]Operator{ - token.Period: {".", 13, 2}, + token.Dot: {".", 13, 2}, token.Call: {"λ", 12, 1}, token.Array: {"@", 12, 2}, token.Negate: {"-", 11, 1}, diff --git a/src/token/Kind.go b/src/token/Kind.go index 2e0d764..881bd18 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -33,7 +33,7 @@ const ( LogicalAnd // && LogicalOr // || Define // := - Period // . + Dot // . Range // .. Call // x() Array // [x] diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index 65b8abe..19ebf16 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -216,14 +216,14 @@ func TestDefine(t *testing.T) { } } -func TestPeriod(t *testing.T) { +func TestDot(t *testing.T) { tokens := token.Tokenize([]byte(`a.b.c`)) expected := []token.Kind{ token.Identifier, - token.Period, + token.Dot, token.Identifier, - token.Period, + token.Dot, token.Identifier, token.EOF, } diff --git a/src/token/operator.go b/src/token/operator.go index 5d74059..3352f9e 100644 --- a/src/token/operator.go +++ b/src/token/operator.go @@ -35,7 +35,7 @@ func operator(tokens List, buffer []byte, i Position) (List, Position) { case "+=": kind = AddAssign case ".": - kind = Period + kind = Dot case "..": kind = Range case ":=": From 9cfca571111be377dc4e2b300cdf87b24b6c637a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Feb 2025 12:15:19 +0100 Subject: [PATCH 0847/1012] Implemented Value interface --- src/core/ArrayElementToRegister.go | 8 +++--- src/core/Compare.go | 2 +- src/core/CompileAssign.go | 2 +- src/core/CompileAssignArray.go | 10 ++++---- src/core/CompileAssignDivision.go | 16 ++++++------ src/core/CompileAssignField.go | 4 +-- src/core/CompileCondition.go | 8 +++--- src/core/CompileDefinition.go | 8 +++--- src/core/CompileDelete.go | 4 +-- src/core/CompileFor.go | 10 ++++---- src/core/CompileLen.go | 10 ++++---- src/core/CompileMemoryStore.go | 2 +- src/core/Define.go | 3 +-- src/core/DotToRegister.go | 4 +-- src/core/Evaluate.go | 10 +++----- src/core/EvaluateArray.go | 19 +++++++------- src/core/EvaluateCall.go | 9 +++---- src/core/EvaluateDot.go | 23 ++++++++--------- src/core/EvaluateLeaf.go | 40 +++++++++++++----------------- src/core/ExecuteLeaf.go | 2 +- src/core/ExpressionToMemory.go | 6 ++--- src/core/ExpressionToRegister.go | 2 +- src/core/MultiAssign.go | 2 +- src/core/ResolveTypes.go | 5 ++-- src/core/TokenToRegister.go | 4 +-- src/core/UsesRegister.go | 2 +- src/eval/Kind.go | 11 -------- src/eval/Label.go | 17 +++++++++++++ src/eval/Memory.go | 20 +++++++++++++++ src/eval/Number.go | 17 +++++++++++++ src/eval/Register.go | 21 ++++++++++++++++ src/eval/Value.go | 21 ++++++---------- src/register/SaveRegister.go | 4 +-- src/scope/Scope.go | 2 +- src/scope/Stack.go | 15 ++++++----- src/scope/Variable.go | 4 +-- 36 files changed, 194 insertions(+), 153 deletions(-) delete mode 100644 src/eval/Kind.go create mode 100644 src/eval/Label.go create mode 100644 src/eval/Memory.go create mode 100644 src/eval/Number.go create mode 100644 src/eval/Register.go diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go index 9e58f3e..8274a97 100644 --- a/src/core/ArrayElementToRegister.go +++ b/src/core/ArrayElementToRegister.go @@ -24,7 +24,7 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register index := node.Children[1] memory := asm.Memory{ - Base: array.Register, + Base: array.Value.Register, Offset: 0, OffsetRegister: math.MaxUint8, Length: byte(1), @@ -50,11 +50,11 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register defer f.UseVariable(indexVariable) - if !types.Is(indexVariable.Type, types.AnyInt) { - return nil, errors.New(&errors.TypeMismatch{Encountered: indexVariable.Type.Name(), Expected: types.AnyInt.Name()}, f.File, index.Token.Position) + if !types.Is(indexVariable.Value.Typ, types.AnyInt) { + return nil, errors.New(&errors.TypeMismatch{Encountered: indexVariable.Value.Typ.Name(), Expected: types.AnyInt.Name()}, f.File, index.Token.Position) } - memory.OffsetRegister = indexVariable.Register + memory.OffsetRegister = indexVariable.Value.Register default: typ, err := f.ExpressionToRegister(index, register) diff --git a/src/core/Compare.go b/src/core/Compare.go index 731165f..f367020 100644 --- a/src/core/Compare.go +++ b/src/core/Compare.go @@ -21,7 +21,7 @@ func (f *Function) Compare(comparison *expression.Expression) error { } defer f.UseVariable(variable) - return f.Execute(comparison.Token, variable.Register, right) + return f.Execute(comparison.Token, variable.Value.Register, right) } if ast.IsFunctionCall(left) && right.IsLeaf() { diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 910cda0..2126f45 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -20,7 +20,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { } defer f.UseVariable(variable) - return f.Execute(node.Expression.Token, variable.Register, right) + return f.Execute(node.Expression.Token, variable.Value.Register, right) } if left.Token.Kind == token.Dot { diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 740c42b..46a02e0 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -25,7 +25,7 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { defer f.UseVariable(variable) memory := asm.Memory{ - Base: variable.Register, + Base: variable.Value.Register, Offset: 0, OffsetRegister: math.MaxUint8, Length: byte(1), @@ -38,18 +38,18 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { return err } - if !types.Is(index.Type, types.AnyInt) { - return errors.New(&errors.TypeMismatch{Encountered: index.Type.Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) + if !types.Is(index.Type(), types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: index.Type().Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) } - switch index.Kind { + switch index := index.(type) { case eval.Number: memory.Offset = int8(index.Number) case eval.Register: memory.OffsetRegister = index.Register defer f.FreeRegister(index.Register) default: - panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, index.Kind)) + panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, index)) } _, err = f.ExpressionToMemory(right, memory) diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index fd16573..cf4be7b 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -36,8 +36,8 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { return err } - quotientVariable.Type = types.Int - remainderVariable.Type = types.Int + quotientVariable.Value.Typ = types.Int + remainderVariable.Value.Typ = types.Int f.AddVariable(quotientVariable) f.AddVariable(remainderVariable) } else { @@ -68,13 +68,13 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { return err } - if !types.Is(dividend.Type, types.AnyInt) { - return errors.New(&errors.TypeMismatch{Encountered: dividend.Type.Name(), Expected: types.AnyInt.Name()}, f.File, dividendExpr.Token.Position) + if !types.Is(dividend.Type(), types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: dividend.Type().Name(), Expected: types.AnyInt.Name()}, f.File, dividendExpr.Token.Position) } divisor := division.Children[1] - switch dividend.Kind { + switch dividend := dividend.(type) { case eval.Number: f.SaveRegister(x86.RAX) f.RegisterNumber(asm.MOVE, x86.RAX, dividend.Number) @@ -83,10 +83,10 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { err = f.Execute(division.Token, dividend.Register, divisor) defer f.FreeRegister(dividend.Register) default: - panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, dividend.Kind)) + panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, dividend)) } - f.RegisterRegister(asm.MOVE, quotientVariable.Register, x86.RAX) - f.RegisterRegister(asm.MOVE, remainderVariable.Register, x86.RDX) + f.RegisterRegister(asm.MOVE, quotientVariable.Value.Register, x86.RAX) + f.RegisterRegister(asm.MOVE, remainderVariable.Value.Register, x86.RDX) return err } diff --git a/src/core/CompileAssignField.go b/src/core/CompileAssignField.go index 31310d2..bc965c6 100644 --- a/src/core/CompileAssignField.go +++ b/src/core/CompileAssignField.go @@ -22,7 +22,7 @@ func (f *Function) CompileAssignField(node *ast.Assign) error { } defer f.UseVariable(variable) - pointer := variable.Type.(*types.Pointer) + pointer := variable.Value.Typ.(*types.Pointer) structure := pointer.To.(*types.Struct) field := structure.FieldByName(fieldName) @@ -31,7 +31,7 @@ func (f *Function) CompileAssignField(node *ast.Assign) error { } memory := asm.Memory{ - Base: variable.Register, + Base: variable.Value.Register, Offset: int8(field.Offset), OffsetRegister: math.MaxUint8, Length: byte(field.Type.Size()), diff --git a/src/core/CompileCondition.go b/src/core/CompileCondition.go index 087731e..19451a9 100644 --- a/src/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -75,11 +75,11 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab return err } - if !types.Is(value.Type, types.Bool) { - return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.Bool.Name()}, f.File, condition.Token.Position) + if !types.Is(value.Type(), types.Bool) { + return errors.New(&errors.TypeMismatch{Encountered: value.Type().Name(), Expected: types.Bool.Name()}, f.File, condition.Token.Position) } - switch value.Kind { + switch value := value.(type) { case eval.Number: if value.Number == 0 { f.Jump(asm.JUMP, failLabel) @@ -89,7 +89,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab f.FreeRegister(value.Register) f.Jump(asm.JE, failLabel) default: - panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) + panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, value)) } return nil diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index af96b46..58b89be 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -21,7 +21,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return err } - typ, err := f.ExpressionToRegister(right, variable.Register) + typ, err := f.ExpressionToRegister(right, variable.Value.Register) if err != nil { return err @@ -35,7 +35,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { typ = types.Int } - variable.Type = typ + variable.Value.Typ = typ f.AddVariable(variable) return nil } @@ -63,10 +63,10 @@ func (f *Function) CompileDefinition(node *ast.Define) error { } if count < len(types) { - variable.Type = types[count] + variable.Value.Typ = types[count] } - f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) + f.RegisterRegister(asm.MOVE, variable.Value.Register, f.CPU.Output[count]) f.AddVariable(variable) count++ return nil diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index 05966d9..ad01ee7 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -20,8 +20,8 @@ func (f *Function) CompileDelete(root *expression.Expression) error { defer f.UseVariable(variable) f.SaveRegister(f.CPU.Input[0]) f.SaveRegister(f.CPU.Input[1]) - f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Register) - f.RegisterNumber(asm.MOVE, f.CPU.Input[1], variable.Type.(*types.Pointer).To.Size()) + f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Value.Register) + f.RegisterNumber(asm.MOVE, f.CPU.Input[1], variable.Value.Typ.(*types.Pointer).To.Size()) f.CallSafe(f.All.Functions["mem.free"], f.CPU.Input[:2]) return nil } diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index 1eafd68..414a166 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -40,7 +40,7 @@ func (f *Function) CompileFor(loop *ast.For) error { return err } - counter = variable.Register + counter = variable.Value.Register from = loop.Head.Children[1].Children[0] to = loop.Head.Children[1].Children[1] f.AddVariable(variable) @@ -67,20 +67,20 @@ func (f *Function) CompileFor(loop *ast.For) error { return err } - if !types.Is(value.Type, types.AnyInt) { - return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) + if !types.Is(value.Type(), types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: value.Type().Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) } f.AddLabel(label) - switch value.Kind { + switch value := value.(type) { case eval.Number: f.RegisterNumber(asm.COMPARE, counter, value.Number) case eval.Register: f.RegisterRegister(asm.COMPARE, counter, value.Register) defer f.FreeRegister(value.Register) default: - panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) + panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, value)) } f.Jump(asm.JGE, labelEnd) diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index b1d117c..3a0fb79 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -21,8 +21,8 @@ func (f *Function) CompileLen(root *expression.Expression) error { return err } - if !types.Is(value.Type, types.AnyArray) { - return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyArray.Name(), ParameterName: "array"}, f.File, root.Children[1].Token.Position) + if !types.Is(value.Type(), types.AnyArray) { + return errors.New(&errors.TypeMismatch{Encountered: value.Type().Name(), Expected: types.AnyArray.Name(), ParameterName: "array"}, f.File, root.Children[1].Token.Position) } memory := asm.Memory{ @@ -34,17 +34,17 @@ func (f *Function) CompileLen(root *expression.Expression) error { output := f.CPU.Output[0] f.SaveRegister(output) - switch value.Kind { + switch value := value.(type) { case eval.Register: memory.Base = value.Register + defer f.FreeRegister(value.Register) case eval.Label: f.RegisterLabel(asm.MOVE, output, value.Label) memory.Base = output default: - panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) + panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, value)) } f.MemoryRegister(asm.LOAD, memory, output) - f.FreeRegister(value.Register) return nil } diff --git a/src/core/CompileMemoryStore.go b/src/core/CompileMemoryStore.go index a1bb856..ff6d59f 100644 --- a/src/core/CompileMemoryStore.go +++ b/src/core/CompileMemoryStore.go @@ -28,7 +28,7 @@ func (f *Function) CompileMemoryStore(root *expression.Expression) error { defer f.UseVariable(variable) memory := asm.Memory{ - Base: variable.Register, + Base: variable.Value.Register, OffsetRegister: math.MaxUint8, Length: byte(numBytes), } diff --git a/src/core/Define.go b/src/core/Define.go index 16ccdd6..c5c6cef 100644 --- a/src/core/Define.go +++ b/src/core/Define.go @@ -25,8 +25,7 @@ func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) variable = &scope.Variable{ Name: name, - Value: eval.Value{ - Kind: eval.Register, + Value: eval.Register{ Register: f.NewRegister(), Alive: uses, }, diff --git a/src/core/DotToRegister.go b/src/core/DotToRegister.go index d76ac81..b3c617c 100644 --- a/src/core/DotToRegister.go +++ b/src/core/DotToRegister.go @@ -20,10 +20,10 @@ func (f *Function) DotToRegister(node *expression.Expression, register cpu.Regis variable := f.VariableByName(leftText) if variable != nil { - field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + field := variable.Value.Typ.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) memory := asm.Memory{ - Base: variable.Register, + Base: variable.Value.Register, Offset: int8(field.Offset), OffsetRegister: math.MaxUint8, Length: byte(field.Type.Size()), diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 05e5a54..b13cd17 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -10,9 +10,8 @@ import ( // Evaluate evaluates an expression and returns a value. func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { if expr.IsFolded { - value := eval.Value{ - Kind: eval.Number, - Type: types.AnyInt, + value := eval.Number{ + Typ: types.AnyInt, Number: expr.Value, } @@ -37,9 +36,8 @@ func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { tmp := f.NewRegister() typ, err := f.ExpressionToRegister(expr, tmp) - value := eval.Value{ - Kind: eval.Register, - Type: typ, + value := eval.Register{ + Typ: typ, Register: tmp, } diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go index 8457dcd..857b542 100644 --- a/src/core/EvaluateArray.go +++ b/src/core/EvaluateArray.go @@ -17,13 +17,13 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Value, error array := f.VariableByName(name) if array == nil { - return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) } defer f.UseVariable(array) memory := asm.Memory{ - Base: array.Register, + Base: array.Value.Register, Offset: 0, OffsetRegister: math.MaxUint8, Length: byte(1), @@ -33,26 +33,25 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Value, error index, err := f.Evaluate(indexExpr) if err != nil { - return eval.Value{}, err + return nil, err } - if !types.Is(index.Type, types.AnyInt) { - return eval.Value{}, errors.New(&errors.TypeMismatch{Encountered: index.Type.Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) + if !types.Is(index.Type(), types.AnyInt) { + return nil, errors.New(&errors.TypeMismatch{Encountered: index.Type().Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) } - switch index.Kind { + switch index := index.(type) { case eval.Number: memory.Offset = int8(index.Number) case eval.Register: memory.OffsetRegister = index.Register defer f.FreeRegister(index.Register) default: - panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, index.Kind)) + panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, index)) } - value := eval.Value{ - Kind: eval.Memory, - Type: array.Type.(*types.Array).Of, + value := eval.Memory{ + Typ: array.Value.Typ.(*types.Array).Of, Memory: memory, } diff --git a/src/core/EvaluateCall.go b/src/core/EvaluateCall.go index e5a1eef..a35c2f0 100644 --- a/src/core/EvaluateCall.go +++ b/src/core/EvaluateCall.go @@ -11,16 +11,15 @@ func (f *Function) EvaluateCall(expr *expression.Expression) (eval.Value, error) types, err := f.CompileCall(expr) if err != nil { - return eval.Value{}, err + return nil, err } if len(types) == 0 { - return eval.Value{}, errors.New(errors.UntypedExpression, f.File, expr.Token.Position) + return nil, errors.New(errors.UntypedExpression, f.File, expr.Token.Position) } - value := eval.Value{ - Kind: eval.Register, - Type: types[0], + value := eval.Register{ + Typ: types[0], Register: f.CPU.Output[0], } diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go index 46b68ec..71aeebe 100644 --- a/src/core/EvaluateDot.go +++ b/src/core/EvaluateDot.go @@ -20,13 +20,12 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) variable := f.VariableByName(leftText) if variable != nil { - field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + field := variable.Value.Typ.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) - value := eval.Value{ - Kind: eval.Memory, - Type: field.Type, + value := eval.Memory{ + Typ: field.Type, Memory: asm.Memory{ - Base: variable.Register, + Base: variable.Value.Register, Offset: int8(field.Offset), OffsetRegister: math.MaxUint8, Length: byte(field.Type.Size()), @@ -42,12 +41,11 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) number, err := ToNumber(constant.Token, constant.File) if err != nil { - return eval.Value{}, err + return nil, err } - value := eval.Value{ - Kind: eval.Number, - Type: types.AnyInt, + value := eval.Number{ + Typ: types.AnyInt, Number: number, } @@ -60,14 +58,13 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) if exists { f.File.Imports[leftText].Used = true - value := eval.Value{ - Kind: eval.Label, - Type: types.AnyPointer, + value := eval.Label{ + Typ: types.AnyPointer, Label: function.UniqueName, } return value, nil } - return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: uniqueName}, f.File, left.Token.Position) + return nil, errors.New(&errors.UnknownIdentifier{Name: uniqueName}, f.File, left.Token.Position) } diff --git a/src/core/EvaluateLeaf.go b/src/core/EvaluateLeaf.go index af9446e..86b3629 100644 --- a/src/core/EvaluateLeaf.go +++ b/src/core/EvaluateLeaf.go @@ -18,9 +18,8 @@ func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) name := expr.Token.Text(f.File.Bytes) if name == "true" { - value := eval.Value{ - Kind: eval.Number, - Type: types.Bool, + value := eval.Number{ + Typ: types.Bool, Number: 1, } @@ -28,9 +27,8 @@ func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) } if name == "false" { - value := eval.Value{ - Kind: eval.Number, - Type: types.Bool, + value := eval.Number{ + Typ: types.Bool, Number: 0, } @@ -42,16 +40,15 @@ func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) if variable != nil { f.UseVariable(variable) - if variable.Alive == 0 { + if variable.Value.Alive == 0 { return variable.Value, nil } tmp := f.NewRegister() - f.RegisterRegister(asm.MOVE, tmp, variable.Register) + f.RegisterRegister(asm.MOVE, tmp, variable.Value.Register) - value := eval.Value{ - Kind: eval.Register, - Type: variable.Type, + value := eval.Register{ + Typ: variable.Value.Typ, Register: tmp, } @@ -59,27 +56,25 @@ func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) } if function != nil { - value := eval.Value{ - Kind: eval.Label, - Type: types.AnyPointer, + value := eval.Label{ + Typ: types.AnyPointer, Label: function.UniqueName, } return value, nil } - return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) case token.Number, token.Rune: number, err := f.ToNumber(expr.Token) if err != nil { - return eval.Value{}, err + return nil, err } - value := eval.Value{ - Kind: eval.Number, - Type: types.AnyInt, + value := eval.Number{ + Typ: types.AnyInt, Number: number, } @@ -94,14 +89,13 @@ func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) copy(slice[8:], data) label := f.AddBytes(slice) - value := eval.Value{ - Kind: eval.Label, - Type: types.String, + value := eval.Label{ + Typ: types.String, Label: label, } return value, nil } - return eval.Value{}, errors.New(errors.InvalidExpression, f.File, expr.Token.Position) + return nil, errors.New(errors.InvalidExpression, f.File, expr.Token.Position) } diff --git a/src/core/ExecuteLeaf.go b/src/core/ExecuteLeaf.go index 06310ae..6ee59af 100644 --- a/src/core/ExecuteLeaf.go +++ b/src/core/ExecuteLeaf.go @@ -18,7 +18,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope } defer f.UseVariable(variable) - return f.ExecuteRegisterRegister(operation, register, variable.Register) + return f.ExecuteRegisterRegister(operation, register, variable.Value.Register) case token.Number, token.Rune: number, err := f.ToNumber(operand) diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index be9811f..8ec2efa 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -17,7 +17,7 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me return nil, err } - switch value.Kind { + switch value := value.(type) { case eval.Number: f.MemoryNumber(asm.STORE, memory, value.Number) case eval.Register: @@ -31,8 +31,8 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me case eval.Label: f.MemoryLabel(asm.STORE, memory, value.Label) default: - panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) + panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, value)) } - return value.Type, err + return value.Type(), err } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index b81d5cd..a5bfcdc 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -71,7 +71,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } else if right.Token.Kind == token.Identifier { rightVariable := f.VariableByName(right.Token.Text(f.File.Bytes)) leftPointer, leftIsPointer := typ.(*types.Pointer) - rightPointer, rightIsPointer := rightVariable.Type.(*types.Pointer) + rightPointer, rightIsPointer := rightVariable.Value.Typ.(*types.Pointer) if leftIsPointer && rightIsPointer && leftPointer.To == rightPointer.To { typ = types.Int diff --git a/src/core/MultiAssign.go b/src/core/MultiAssign.go index e6c3f13..fab3d6c 100644 --- a/src/core/MultiAssign.go +++ b/src/core/MultiAssign.go @@ -23,7 +23,7 @@ func (f *Function) MultiAssign(left *expression.Expression, right *expression.Ex return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) } - f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) + f.RegisterRegister(asm.MOVE, variable.Value.Register, f.CPU.Output[count]) f.UseVariable(variable) count++ return nil diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index 7db5a6c..6df48c1 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -32,9 +32,8 @@ func (f *Function) ResolveTypes() error { f.AddVariable(&scope.Variable{ Name: param.name, - Value: eval.Value{ - Kind: eval.Register, - Type: param.typ, + Value: eval.Register{ + Typ: param.typ, Register: x86.InputRegisters[i], Alive: uses, }, diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index fce74e1..70989e1 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -32,8 +32,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. if variable != nil { f.UseVariable(variable) f.SaveRegister(register) - f.RegisterRegister(asm.MOVE, register, variable.Register) - return variable.Type, nil + f.RegisterRegister(asm.MOVE, register, variable.Value.Register) + return variable.Value.Typ, nil } if function != nil { diff --git a/src/core/UsesRegister.go b/src/core/UsesRegister.go index df66b67..cd4ab79 100644 --- a/src/core/UsesRegister.go +++ b/src/core/UsesRegister.go @@ -16,7 +16,7 @@ func (f *Function) UsesRegister(expr *expression.Expression, register cpu.Regist variable := f.VariableByName(expr.Token.Text(f.File.Bytes)) - if variable == nil || variable.Register != register { + if variable == nil || variable.Value.Register != register { return false } diff --git a/src/eval/Kind.go b/src/eval/Kind.go deleted file mode 100644 index 4a41f05..0000000 --- a/src/eval/Kind.go +++ /dev/null @@ -1,11 +0,0 @@ -package eval - -type Kind uint8 - -const ( - Invalid Kind = iota // Invalid is an invalid value. - Number // Number is an immediately encoded value stored together with the instruction. - Register // Register is a CPU register. - Memory // Memory is an area in the RAM. - Label // Label is a reference to a name that can only be resolved once the program is fully compiled. -) diff --git a/src/eval/Label.go b/src/eval/Label.go new file mode 100644 index 0000000..a74afe0 --- /dev/null +++ b/src/eval/Label.go @@ -0,0 +1,17 @@ +package eval + +import "git.urbach.dev/cli/q/src/types" + +// Label is a named pointer to a code or data section. +type Label struct { + Typ types.Type + Label string +} + +func (v Label) String() string { + return "Label" +} + +func (v Label) Type() types.Type { + return v.Typ +} diff --git a/src/eval/Memory.go b/src/eval/Memory.go new file mode 100644 index 0000000..0afd09e --- /dev/null +++ b/src/eval/Memory.go @@ -0,0 +1,20 @@ +package eval + +import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/types" +) + +// Memory is a region in memory that can be addressed by an instruction. +type Memory struct { + Typ types.Type + Memory asm.Memory +} + +func (v Memory) String() string { + return "Memory" +} + +func (v Memory) Type() types.Type { + return v.Typ +} diff --git a/src/eval/Number.go b/src/eval/Number.go new file mode 100644 index 0000000..e9dc2d7 --- /dev/null +++ b/src/eval/Number.go @@ -0,0 +1,17 @@ +package eval + +import "git.urbach.dev/cli/q/src/types" + +// Number is an immediate value that is stored next to the instruction. +type Number struct { + Typ types.Type + Number int +} + +func (v Number) String() string { + return "Number" +} + +func (v Number) Type() types.Type { + return v.Typ +} diff --git a/src/eval/Register.go b/src/eval/Register.go new file mode 100644 index 0000000..088593f --- /dev/null +++ b/src/eval/Register.go @@ -0,0 +1,21 @@ +package eval + +import ( + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/types" +) + +// Register is a value that is stored inside a CPU register. +type Register struct { + Typ types.Type + Alive uint8 + Register cpu.Register +} + +func (v Register) String() string { + return "Register" +} + +func (v Register) Type() types.Type { + return v.Typ +} diff --git a/src/eval/Value.go b/src/eval/Value.go index 1205a98..681cc18 100644 --- a/src/eval/Value.go +++ b/src/eval/Value.go @@ -1,29 +1,22 @@ package eval import ( - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/types" ) -// Value combines a register with its data type. -type Value struct { - Type types.Type - Label string - Number int - Memory asm.Memory - Register cpu.Register - Alive uint8 - Kind Kind +// Value abstracts all data storage types like immediates, registers and memory. +type Value interface { + String() string + Type() types.Type } -// IsAlive returns true if the Value is still alive. -func (v *Value) IsAlive() bool { +// IsAlive returns true if the register value is still alive. +func (v *Register) IsAlive() bool { return v.Alive > 0 } // Use reduces the lifetime counter by one. -func (v *Value) Use() { +func (v *Register) Use() { if v.Alive == 0 { panic("incorrect number of value use calls") } diff --git a/src/register/SaveRegister.go b/src/register/SaveRegister.go index 9f9bd0a..1e4ec20 100644 --- a/src/register/SaveRegister.go +++ b/src/register/SaveRegister.go @@ -19,12 +19,12 @@ func (f *Machine) SaveRegister(register cpu.Register) { variable := f.VariableByRegister(register) - if variable == nil || variable.Alive == 0 { + if variable == nil || variable.Value.Alive == 0 { return } newRegister := f.NewRegister() f.RegisterRegister(asm.MOVE, newRegister, register) - variable.Register = newRegister + variable.Value.Register = newRegister f.FreeRegister(register) } diff --git a/src/scope/Scope.go b/src/scope/Scope.go index 20efebc..490ae2c 100644 --- a/src/scope/Scope.go +++ b/src/scope/Scope.go @@ -15,7 +15,7 @@ type Scope struct { // AddVariable adds a new variable to the current scope. func (s *Scope) AddVariable(variable *Variable) { s.Variables = append(s.Variables, variable) - s.Use(variable.Register) + s.Use(variable.Value.Register) } // VariableByName returns the variable with the given name or `nil` if it doesn't exist. diff --git a/src/scope/Stack.go b/src/scope/Stack.go index c2c1e9d..9b06334 100644 --- a/src/scope/Stack.go +++ b/src/scope/Stack.go @@ -46,11 +46,10 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { s.Variables = append(s.Variables, &Variable{ Name: v.Name, - Value: eval.Value{ - Kind: eval.Register, - Register: v.Register, + Value: eval.Register{ + Typ: v.Value.Typ, Alive: count, - Type: v.Type, + Register: v.Value.Register, }, }) } @@ -73,10 +72,10 @@ func (stack *Stack) UseVariable(variable *Variable) { continue } - local.Use() + local.Value.Use() - if !local.IsAlive() { - scope.Free(local.Register) + if !local.Value.IsAlive() { + scope.Free(local.Value.Register) } } } @@ -89,7 +88,7 @@ func (stack *Stack) VariableByName(name string) *Variable { // VariableByRegister returns the variable that occupies the given register or `nil` if none occupy the register. func (stack *Stack) VariableByRegister(register cpu.Register) *Variable { for _, v := range stack.CurrentScope().Variables { - if v.Register == register { + if v.Value.Register == register { return v } } diff --git a/src/scope/Variable.go b/src/scope/Variable.go index 72ea61b..522e2f4 100644 --- a/src/scope/Variable.go +++ b/src/scope/Variable.go @@ -4,6 +4,6 @@ import "git.urbach.dev/cli/q/src/eval" // Variable is a named value. type Variable struct { - Name string - eval.Value + Name string + Value eval.Register } From b67361c03503f090c4fe9350b03b5abb0e906a6d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Feb 2025 12:15:19 +0100 Subject: [PATCH 0848/1012] Implemented Value interface --- src/core/ArrayElementToRegister.go | 8 +++--- src/core/Compare.go | 2 +- src/core/CompileAssign.go | 2 +- src/core/CompileAssignArray.go | 10 ++++---- src/core/CompileAssignDivision.go | 16 ++++++------ src/core/CompileAssignField.go | 4 +-- src/core/CompileCondition.go | 8 +++--- src/core/CompileDefinition.go | 8 +++--- src/core/CompileDelete.go | 4 +-- src/core/CompileFor.go | 10 ++++---- src/core/CompileLen.go | 10 ++++---- src/core/CompileMemoryStore.go | 2 +- src/core/Define.go | 3 +-- src/core/DotToRegister.go | 4 +-- src/core/Evaluate.go | 10 +++----- src/core/EvaluateArray.go | 19 +++++++------- src/core/EvaluateCall.go | 9 +++---- src/core/EvaluateDot.go | 23 ++++++++--------- src/core/EvaluateLeaf.go | 40 +++++++++++++----------------- src/core/ExecuteLeaf.go | 2 +- src/core/ExpressionToMemory.go | 6 ++--- src/core/ExpressionToRegister.go | 2 +- src/core/MultiAssign.go | 2 +- src/core/ResolveTypes.go | 5 ++-- src/core/TokenToRegister.go | 4 +-- src/core/UsesRegister.go | 2 +- src/eval/Kind.go | 11 -------- src/eval/Label.go | 17 +++++++++++++ src/eval/Memory.go | 20 +++++++++++++++ src/eval/Number.go | 17 +++++++++++++ src/eval/Register.go | 21 ++++++++++++++++ src/eval/Value.go | 21 ++++++---------- src/register/SaveRegister.go | 4 +-- src/scope/Scope.go | 2 +- src/scope/Stack.go | 15 ++++++----- src/scope/Variable.go | 4 +-- 36 files changed, 194 insertions(+), 153 deletions(-) delete mode 100644 src/eval/Kind.go create mode 100644 src/eval/Label.go create mode 100644 src/eval/Memory.go create mode 100644 src/eval/Number.go create mode 100644 src/eval/Register.go diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go index 9e58f3e..8274a97 100644 --- a/src/core/ArrayElementToRegister.go +++ b/src/core/ArrayElementToRegister.go @@ -24,7 +24,7 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register index := node.Children[1] memory := asm.Memory{ - Base: array.Register, + Base: array.Value.Register, Offset: 0, OffsetRegister: math.MaxUint8, Length: byte(1), @@ -50,11 +50,11 @@ func (f *Function) ArrayElementToRegister(node *expression.Expression, register defer f.UseVariable(indexVariable) - if !types.Is(indexVariable.Type, types.AnyInt) { - return nil, errors.New(&errors.TypeMismatch{Encountered: indexVariable.Type.Name(), Expected: types.AnyInt.Name()}, f.File, index.Token.Position) + if !types.Is(indexVariable.Value.Typ, types.AnyInt) { + return nil, errors.New(&errors.TypeMismatch{Encountered: indexVariable.Value.Typ.Name(), Expected: types.AnyInt.Name()}, f.File, index.Token.Position) } - memory.OffsetRegister = indexVariable.Register + memory.OffsetRegister = indexVariable.Value.Register default: typ, err := f.ExpressionToRegister(index, register) diff --git a/src/core/Compare.go b/src/core/Compare.go index 731165f..f367020 100644 --- a/src/core/Compare.go +++ b/src/core/Compare.go @@ -21,7 +21,7 @@ func (f *Function) Compare(comparison *expression.Expression) error { } defer f.UseVariable(variable) - return f.Execute(comparison.Token, variable.Register, right) + return f.Execute(comparison.Token, variable.Value.Register, right) } if ast.IsFunctionCall(left) && right.IsLeaf() { diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 910cda0..2126f45 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -20,7 +20,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { } defer f.UseVariable(variable) - return f.Execute(node.Expression.Token, variable.Register, right) + return f.Execute(node.Expression.Token, variable.Value.Register, right) } if left.Token.Kind == token.Dot { diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 740c42b..46a02e0 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -25,7 +25,7 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { defer f.UseVariable(variable) memory := asm.Memory{ - Base: variable.Register, + Base: variable.Value.Register, Offset: 0, OffsetRegister: math.MaxUint8, Length: byte(1), @@ -38,18 +38,18 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { return err } - if !types.Is(index.Type, types.AnyInt) { - return errors.New(&errors.TypeMismatch{Encountered: index.Type.Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) + if !types.Is(index.Type(), types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: index.Type().Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) } - switch index.Kind { + switch index := index.(type) { case eval.Number: memory.Offset = int8(index.Number) case eval.Register: memory.OffsetRegister = index.Register defer f.FreeRegister(index.Register) default: - panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, index.Kind)) + panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, index)) } _, err = f.ExpressionToMemory(right, memory) diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index fd16573..cf4be7b 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -36,8 +36,8 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { return err } - quotientVariable.Type = types.Int - remainderVariable.Type = types.Int + quotientVariable.Value.Typ = types.Int + remainderVariable.Value.Typ = types.Int f.AddVariable(quotientVariable) f.AddVariable(remainderVariable) } else { @@ -68,13 +68,13 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { return err } - if !types.Is(dividend.Type, types.AnyInt) { - return errors.New(&errors.TypeMismatch{Encountered: dividend.Type.Name(), Expected: types.AnyInt.Name()}, f.File, dividendExpr.Token.Position) + if !types.Is(dividend.Type(), types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: dividend.Type().Name(), Expected: types.AnyInt.Name()}, f.File, dividendExpr.Token.Position) } divisor := division.Children[1] - switch dividend.Kind { + switch dividend := dividend.(type) { case eval.Number: f.SaveRegister(x86.RAX) f.RegisterNumber(asm.MOVE, x86.RAX, dividend.Number) @@ -83,10 +83,10 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { err = f.Execute(division.Token, dividend.Register, divisor) defer f.FreeRegister(dividend.Register) default: - panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, dividend.Kind)) + panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, dividend)) } - f.RegisterRegister(asm.MOVE, quotientVariable.Register, x86.RAX) - f.RegisterRegister(asm.MOVE, remainderVariable.Register, x86.RDX) + f.RegisterRegister(asm.MOVE, quotientVariable.Value.Register, x86.RAX) + f.RegisterRegister(asm.MOVE, remainderVariable.Value.Register, x86.RDX) return err } diff --git a/src/core/CompileAssignField.go b/src/core/CompileAssignField.go index 31310d2..bc965c6 100644 --- a/src/core/CompileAssignField.go +++ b/src/core/CompileAssignField.go @@ -22,7 +22,7 @@ func (f *Function) CompileAssignField(node *ast.Assign) error { } defer f.UseVariable(variable) - pointer := variable.Type.(*types.Pointer) + pointer := variable.Value.Typ.(*types.Pointer) structure := pointer.To.(*types.Struct) field := structure.FieldByName(fieldName) @@ -31,7 +31,7 @@ func (f *Function) CompileAssignField(node *ast.Assign) error { } memory := asm.Memory{ - Base: variable.Register, + Base: variable.Value.Register, Offset: int8(field.Offset), OffsetRegister: math.MaxUint8, Length: byte(field.Type.Size()), diff --git a/src/core/CompileCondition.go b/src/core/CompileCondition.go index 087731e..19451a9 100644 --- a/src/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -75,11 +75,11 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab return err } - if !types.Is(value.Type, types.Bool) { - return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.Bool.Name()}, f.File, condition.Token.Position) + if !types.Is(value.Type(), types.Bool) { + return errors.New(&errors.TypeMismatch{Encountered: value.Type().Name(), Expected: types.Bool.Name()}, f.File, condition.Token.Position) } - switch value.Kind { + switch value := value.(type) { case eval.Number: if value.Number == 0 { f.Jump(asm.JUMP, failLabel) @@ -89,7 +89,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab f.FreeRegister(value.Register) f.Jump(asm.JE, failLabel) default: - panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) + panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, value)) } return nil diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index af96b46..58b89be 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -21,7 +21,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return err } - typ, err := f.ExpressionToRegister(right, variable.Register) + typ, err := f.ExpressionToRegister(right, variable.Value.Register) if err != nil { return err @@ -35,7 +35,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { typ = types.Int } - variable.Type = typ + variable.Value.Typ = typ f.AddVariable(variable) return nil } @@ -63,10 +63,10 @@ func (f *Function) CompileDefinition(node *ast.Define) error { } if count < len(types) { - variable.Type = types[count] + variable.Value.Typ = types[count] } - f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) + f.RegisterRegister(asm.MOVE, variable.Value.Register, f.CPU.Output[count]) f.AddVariable(variable) count++ return nil diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index 05966d9..ad01ee7 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -20,8 +20,8 @@ func (f *Function) CompileDelete(root *expression.Expression) error { defer f.UseVariable(variable) f.SaveRegister(f.CPU.Input[0]) f.SaveRegister(f.CPU.Input[1]) - f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Register) - f.RegisterNumber(asm.MOVE, f.CPU.Input[1], variable.Type.(*types.Pointer).To.Size()) + f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Value.Register) + f.RegisterNumber(asm.MOVE, f.CPU.Input[1], variable.Value.Typ.(*types.Pointer).To.Size()) f.CallSafe(f.All.Functions["mem.free"], f.CPU.Input[:2]) return nil } diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index 1eafd68..414a166 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -40,7 +40,7 @@ func (f *Function) CompileFor(loop *ast.For) error { return err } - counter = variable.Register + counter = variable.Value.Register from = loop.Head.Children[1].Children[0] to = loop.Head.Children[1].Children[1] f.AddVariable(variable) @@ -67,20 +67,20 @@ func (f *Function) CompileFor(loop *ast.For) error { return err } - if !types.Is(value.Type, types.AnyInt) { - return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) + if !types.Is(value.Type(), types.AnyInt) { + return errors.New(&errors.TypeMismatch{Encountered: value.Type().Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) } f.AddLabel(label) - switch value.Kind { + switch value := value.(type) { case eval.Number: f.RegisterNumber(asm.COMPARE, counter, value.Number) case eval.Register: f.RegisterRegister(asm.COMPARE, counter, value.Register) defer f.FreeRegister(value.Register) default: - panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) + panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, value)) } f.Jump(asm.JGE, labelEnd) diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index b1d117c..3a0fb79 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -21,8 +21,8 @@ func (f *Function) CompileLen(root *expression.Expression) error { return err } - if !types.Is(value.Type, types.AnyArray) { - return errors.New(&errors.TypeMismatch{Encountered: value.Type.Name(), Expected: types.AnyArray.Name(), ParameterName: "array"}, f.File, root.Children[1].Token.Position) + if !types.Is(value.Type(), types.AnyArray) { + return errors.New(&errors.TypeMismatch{Encountered: value.Type().Name(), Expected: types.AnyArray.Name(), ParameterName: "array"}, f.File, root.Children[1].Token.Position) } memory := asm.Memory{ @@ -34,17 +34,17 @@ func (f *Function) CompileLen(root *expression.Expression) error { output := f.CPU.Output[0] f.SaveRegister(output) - switch value.Kind { + switch value := value.(type) { case eval.Register: memory.Base = value.Register + defer f.FreeRegister(value.Register) case eval.Label: f.RegisterLabel(asm.MOVE, output, value.Label) memory.Base = output default: - panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) + panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, value)) } f.MemoryRegister(asm.LOAD, memory, output) - f.FreeRegister(value.Register) return nil } diff --git a/src/core/CompileMemoryStore.go b/src/core/CompileMemoryStore.go index a1bb856..ff6d59f 100644 --- a/src/core/CompileMemoryStore.go +++ b/src/core/CompileMemoryStore.go @@ -28,7 +28,7 @@ func (f *Function) CompileMemoryStore(root *expression.Expression) error { defer f.UseVariable(variable) memory := asm.Memory{ - Base: variable.Register, + Base: variable.Value.Register, OffsetRegister: math.MaxUint8, Length: byte(numBytes), } diff --git a/src/core/Define.go b/src/core/Define.go index 16ccdd6..c5c6cef 100644 --- a/src/core/Define.go +++ b/src/core/Define.go @@ -25,8 +25,7 @@ func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) variable = &scope.Variable{ Name: name, - Value: eval.Value{ - Kind: eval.Register, + Value: eval.Register{ Register: f.NewRegister(), Alive: uses, }, diff --git a/src/core/DotToRegister.go b/src/core/DotToRegister.go index d76ac81..b3c617c 100644 --- a/src/core/DotToRegister.go +++ b/src/core/DotToRegister.go @@ -20,10 +20,10 @@ func (f *Function) DotToRegister(node *expression.Expression, register cpu.Regis variable := f.VariableByName(leftText) if variable != nil { - field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + field := variable.Value.Typ.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) memory := asm.Memory{ - Base: variable.Register, + Base: variable.Value.Register, Offset: int8(field.Offset), OffsetRegister: math.MaxUint8, Length: byte(field.Type.Size()), diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 05e5a54..b13cd17 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -10,9 +10,8 @@ import ( // Evaluate evaluates an expression and returns a value. func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { if expr.IsFolded { - value := eval.Value{ - Kind: eval.Number, - Type: types.AnyInt, + value := eval.Number{ + Typ: types.AnyInt, Number: expr.Value, } @@ -37,9 +36,8 @@ func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { tmp := f.NewRegister() typ, err := f.ExpressionToRegister(expr, tmp) - value := eval.Value{ - Kind: eval.Register, - Type: typ, + value := eval.Register{ + Typ: typ, Register: tmp, } diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go index 8457dcd..857b542 100644 --- a/src/core/EvaluateArray.go +++ b/src/core/EvaluateArray.go @@ -17,13 +17,13 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Value, error array := f.VariableByName(name) if array == nil { - return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) } defer f.UseVariable(array) memory := asm.Memory{ - Base: array.Register, + Base: array.Value.Register, Offset: 0, OffsetRegister: math.MaxUint8, Length: byte(1), @@ -33,26 +33,25 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Value, error index, err := f.Evaluate(indexExpr) if err != nil { - return eval.Value{}, err + return nil, err } - if !types.Is(index.Type, types.AnyInt) { - return eval.Value{}, errors.New(&errors.TypeMismatch{Encountered: index.Type.Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) + if !types.Is(index.Type(), types.AnyInt) { + return nil, errors.New(&errors.TypeMismatch{Encountered: index.Type().Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) } - switch index.Kind { + switch index := index.(type) { case eval.Number: memory.Offset = int8(index.Number) case eval.Register: memory.OffsetRegister = index.Register defer f.FreeRegister(index.Register) default: - panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, index.Kind)) + panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, index)) } - value := eval.Value{ - Kind: eval.Memory, - Type: array.Type.(*types.Array).Of, + value := eval.Memory{ + Typ: array.Value.Typ.(*types.Array).Of, Memory: memory, } diff --git a/src/core/EvaluateCall.go b/src/core/EvaluateCall.go index e5a1eef..a35c2f0 100644 --- a/src/core/EvaluateCall.go +++ b/src/core/EvaluateCall.go @@ -11,16 +11,15 @@ func (f *Function) EvaluateCall(expr *expression.Expression) (eval.Value, error) types, err := f.CompileCall(expr) if err != nil { - return eval.Value{}, err + return nil, err } if len(types) == 0 { - return eval.Value{}, errors.New(errors.UntypedExpression, f.File, expr.Token.Position) + return nil, errors.New(errors.UntypedExpression, f.File, expr.Token.Position) } - value := eval.Value{ - Kind: eval.Register, - Type: types[0], + value := eval.Register{ + Typ: types[0], Register: f.CPU.Output[0], } diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go index 46b68ec..71aeebe 100644 --- a/src/core/EvaluateDot.go +++ b/src/core/EvaluateDot.go @@ -20,13 +20,12 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) variable := f.VariableByName(leftText) if variable != nil { - field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + field := variable.Value.Typ.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) - value := eval.Value{ - Kind: eval.Memory, - Type: field.Type, + value := eval.Memory{ + Typ: field.Type, Memory: asm.Memory{ - Base: variable.Register, + Base: variable.Value.Register, Offset: int8(field.Offset), OffsetRegister: math.MaxUint8, Length: byte(field.Type.Size()), @@ -42,12 +41,11 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) number, err := ToNumber(constant.Token, constant.File) if err != nil { - return eval.Value{}, err + return nil, err } - value := eval.Value{ - Kind: eval.Number, - Type: types.AnyInt, + value := eval.Number{ + Typ: types.AnyInt, Number: number, } @@ -60,14 +58,13 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) if exists { f.File.Imports[leftText].Used = true - value := eval.Value{ - Kind: eval.Label, - Type: types.AnyPointer, + value := eval.Label{ + Typ: types.AnyPointer, Label: function.UniqueName, } return value, nil } - return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: uniqueName}, f.File, left.Token.Position) + return nil, errors.New(&errors.UnknownIdentifier{Name: uniqueName}, f.File, left.Token.Position) } diff --git a/src/core/EvaluateLeaf.go b/src/core/EvaluateLeaf.go index af9446e..86b3629 100644 --- a/src/core/EvaluateLeaf.go +++ b/src/core/EvaluateLeaf.go @@ -18,9 +18,8 @@ func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) name := expr.Token.Text(f.File.Bytes) if name == "true" { - value := eval.Value{ - Kind: eval.Number, - Type: types.Bool, + value := eval.Number{ + Typ: types.Bool, Number: 1, } @@ -28,9 +27,8 @@ func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) } if name == "false" { - value := eval.Value{ - Kind: eval.Number, - Type: types.Bool, + value := eval.Number{ + Typ: types.Bool, Number: 0, } @@ -42,16 +40,15 @@ func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) if variable != nil { f.UseVariable(variable) - if variable.Alive == 0 { + if variable.Value.Alive == 0 { return variable.Value, nil } tmp := f.NewRegister() - f.RegisterRegister(asm.MOVE, tmp, variable.Register) + f.RegisterRegister(asm.MOVE, tmp, variable.Value.Register) - value := eval.Value{ - Kind: eval.Register, - Type: variable.Type, + value := eval.Register{ + Typ: variable.Value.Typ, Register: tmp, } @@ -59,27 +56,25 @@ func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) } if function != nil { - value := eval.Value{ - Kind: eval.Label, - Type: types.AnyPointer, + value := eval.Label{ + Typ: types.AnyPointer, Label: function.UniqueName, } return value, nil } - return eval.Value{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) case token.Number, token.Rune: number, err := f.ToNumber(expr.Token) if err != nil { - return eval.Value{}, err + return nil, err } - value := eval.Value{ - Kind: eval.Number, - Type: types.AnyInt, + value := eval.Number{ + Typ: types.AnyInt, Number: number, } @@ -94,14 +89,13 @@ func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) copy(slice[8:], data) label := f.AddBytes(slice) - value := eval.Value{ - Kind: eval.Label, - Type: types.String, + value := eval.Label{ + Typ: types.String, Label: label, } return value, nil } - return eval.Value{}, errors.New(errors.InvalidExpression, f.File, expr.Token.Position) + return nil, errors.New(errors.InvalidExpression, f.File, expr.Token.Position) } diff --git a/src/core/ExecuteLeaf.go b/src/core/ExecuteLeaf.go index 06310ae..6ee59af 100644 --- a/src/core/ExecuteLeaf.go +++ b/src/core/ExecuteLeaf.go @@ -18,7 +18,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope } defer f.UseVariable(variable) - return f.ExecuteRegisterRegister(operation, register, variable.Register) + return f.ExecuteRegisterRegister(operation, register, variable.Value.Register) case token.Number, token.Rune: number, err := f.ToNumber(operand) diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index be9811f..8ec2efa 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -17,7 +17,7 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me return nil, err } - switch value.Kind { + switch value := value.(type) { case eval.Number: f.MemoryNumber(asm.STORE, memory, value.Number) case eval.Register: @@ -31,8 +31,8 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me case eval.Label: f.MemoryLabel(asm.STORE, memory, value.Label) default: - panic(fmt.Errorf("%s: not implemented: %d", f.UniqueName, value.Kind)) + panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, value)) } - return value.Type, err + return value.Type(), err } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index b81d5cd..a5bfcdc 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -71,7 +71,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } else if right.Token.Kind == token.Identifier { rightVariable := f.VariableByName(right.Token.Text(f.File.Bytes)) leftPointer, leftIsPointer := typ.(*types.Pointer) - rightPointer, rightIsPointer := rightVariable.Type.(*types.Pointer) + rightPointer, rightIsPointer := rightVariable.Value.Typ.(*types.Pointer) if leftIsPointer && rightIsPointer && leftPointer.To == rightPointer.To { typ = types.Int diff --git a/src/core/MultiAssign.go b/src/core/MultiAssign.go index e6c3f13..fab3d6c 100644 --- a/src/core/MultiAssign.go +++ b/src/core/MultiAssign.go @@ -23,7 +23,7 @@ func (f *Function) MultiAssign(left *expression.Expression, right *expression.Ex return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) } - f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count]) + f.RegisterRegister(asm.MOVE, variable.Value.Register, f.CPU.Output[count]) f.UseVariable(variable) count++ return nil diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index 7db5a6c..6df48c1 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -32,9 +32,8 @@ func (f *Function) ResolveTypes() error { f.AddVariable(&scope.Variable{ Name: param.name, - Value: eval.Value{ - Kind: eval.Register, - Type: param.typ, + Value: eval.Register{ + Typ: param.typ, Register: x86.InputRegisters[i], Alive: uses, }, diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go index fce74e1..70989e1 100644 --- a/src/core/TokenToRegister.go +++ b/src/core/TokenToRegister.go @@ -32,8 +32,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types. if variable != nil { f.UseVariable(variable) f.SaveRegister(register) - f.RegisterRegister(asm.MOVE, register, variable.Register) - return variable.Type, nil + f.RegisterRegister(asm.MOVE, register, variable.Value.Register) + return variable.Value.Typ, nil } if function != nil { diff --git a/src/core/UsesRegister.go b/src/core/UsesRegister.go index df66b67..cd4ab79 100644 --- a/src/core/UsesRegister.go +++ b/src/core/UsesRegister.go @@ -16,7 +16,7 @@ func (f *Function) UsesRegister(expr *expression.Expression, register cpu.Regist variable := f.VariableByName(expr.Token.Text(f.File.Bytes)) - if variable == nil || variable.Register != register { + if variable == nil || variable.Value.Register != register { return false } diff --git a/src/eval/Kind.go b/src/eval/Kind.go deleted file mode 100644 index 4a41f05..0000000 --- a/src/eval/Kind.go +++ /dev/null @@ -1,11 +0,0 @@ -package eval - -type Kind uint8 - -const ( - Invalid Kind = iota // Invalid is an invalid value. - Number // Number is an immediately encoded value stored together with the instruction. - Register // Register is a CPU register. - Memory // Memory is an area in the RAM. - Label // Label is a reference to a name that can only be resolved once the program is fully compiled. -) diff --git a/src/eval/Label.go b/src/eval/Label.go new file mode 100644 index 0000000..a74afe0 --- /dev/null +++ b/src/eval/Label.go @@ -0,0 +1,17 @@ +package eval + +import "git.urbach.dev/cli/q/src/types" + +// Label is a named pointer to a code or data section. +type Label struct { + Typ types.Type + Label string +} + +func (v Label) String() string { + return "Label" +} + +func (v Label) Type() types.Type { + return v.Typ +} diff --git a/src/eval/Memory.go b/src/eval/Memory.go new file mode 100644 index 0000000..0afd09e --- /dev/null +++ b/src/eval/Memory.go @@ -0,0 +1,20 @@ +package eval + +import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/types" +) + +// Memory is a region in memory that can be addressed by an instruction. +type Memory struct { + Typ types.Type + Memory asm.Memory +} + +func (v Memory) String() string { + return "Memory" +} + +func (v Memory) Type() types.Type { + return v.Typ +} diff --git a/src/eval/Number.go b/src/eval/Number.go new file mode 100644 index 0000000..e9dc2d7 --- /dev/null +++ b/src/eval/Number.go @@ -0,0 +1,17 @@ +package eval + +import "git.urbach.dev/cli/q/src/types" + +// Number is an immediate value that is stored next to the instruction. +type Number struct { + Typ types.Type + Number int +} + +func (v Number) String() string { + return "Number" +} + +func (v Number) Type() types.Type { + return v.Typ +} diff --git a/src/eval/Register.go b/src/eval/Register.go new file mode 100644 index 0000000..088593f --- /dev/null +++ b/src/eval/Register.go @@ -0,0 +1,21 @@ +package eval + +import ( + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/types" +) + +// Register is a value that is stored inside a CPU register. +type Register struct { + Typ types.Type + Alive uint8 + Register cpu.Register +} + +func (v Register) String() string { + return "Register" +} + +func (v Register) Type() types.Type { + return v.Typ +} diff --git a/src/eval/Value.go b/src/eval/Value.go index 1205a98..681cc18 100644 --- a/src/eval/Value.go +++ b/src/eval/Value.go @@ -1,29 +1,22 @@ package eval import ( - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/types" ) -// Value combines a register with its data type. -type Value struct { - Type types.Type - Label string - Number int - Memory asm.Memory - Register cpu.Register - Alive uint8 - Kind Kind +// Value abstracts all data storage types like immediates, registers and memory. +type Value interface { + String() string + Type() types.Type } -// IsAlive returns true if the Value is still alive. -func (v *Value) IsAlive() bool { +// IsAlive returns true if the register value is still alive. +func (v *Register) IsAlive() bool { return v.Alive > 0 } // Use reduces the lifetime counter by one. -func (v *Value) Use() { +func (v *Register) Use() { if v.Alive == 0 { panic("incorrect number of value use calls") } diff --git a/src/register/SaveRegister.go b/src/register/SaveRegister.go index 9f9bd0a..1e4ec20 100644 --- a/src/register/SaveRegister.go +++ b/src/register/SaveRegister.go @@ -19,12 +19,12 @@ func (f *Machine) SaveRegister(register cpu.Register) { variable := f.VariableByRegister(register) - if variable == nil || variable.Alive == 0 { + if variable == nil || variable.Value.Alive == 0 { return } newRegister := f.NewRegister() f.RegisterRegister(asm.MOVE, newRegister, register) - variable.Register = newRegister + variable.Value.Register = newRegister f.FreeRegister(register) } diff --git a/src/scope/Scope.go b/src/scope/Scope.go index 20efebc..490ae2c 100644 --- a/src/scope/Scope.go +++ b/src/scope/Scope.go @@ -15,7 +15,7 @@ type Scope struct { // AddVariable adds a new variable to the current scope. func (s *Scope) AddVariable(variable *Variable) { s.Variables = append(s.Variables, variable) - s.Use(variable.Register) + s.Use(variable.Value.Register) } // VariableByName returns the variable with the given name or `nil` if it doesn't exist. diff --git a/src/scope/Stack.go b/src/scope/Stack.go index c2c1e9d..9b06334 100644 --- a/src/scope/Stack.go +++ b/src/scope/Stack.go @@ -46,11 +46,10 @@ func (stack *Stack) PushScope(body ast.AST, buffer []byte) *Scope { s.Variables = append(s.Variables, &Variable{ Name: v.Name, - Value: eval.Value{ - Kind: eval.Register, - Register: v.Register, + Value: eval.Register{ + Typ: v.Value.Typ, Alive: count, - Type: v.Type, + Register: v.Value.Register, }, }) } @@ -73,10 +72,10 @@ func (stack *Stack) UseVariable(variable *Variable) { continue } - local.Use() + local.Value.Use() - if !local.IsAlive() { - scope.Free(local.Register) + if !local.Value.IsAlive() { + scope.Free(local.Value.Register) } } } @@ -89,7 +88,7 @@ func (stack *Stack) VariableByName(name string) *Variable { // VariableByRegister returns the variable that occupies the given register or `nil` if none occupy the register. func (stack *Stack) VariableByRegister(register cpu.Register) *Variable { for _, v := range stack.CurrentScope().Variables { - if v.Register == register { + if v.Value.Register == register { return v } } diff --git a/src/scope/Variable.go b/src/scope/Variable.go index 72ea61b..522e2f4 100644 --- a/src/scope/Variable.go +++ b/src/scope/Variable.go @@ -4,6 +4,6 @@ import "git.urbach.dev/cli/q/src/eval" // Variable is a named value. type Variable struct { - Name string - eval.Value + Name string + Value eval.Register } From efecfc3b7c66f864243fc4d9a2592692d02df4d1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Feb 2025 16:07:10 +0100 Subject: [PATCH 0849/1012] Simplified expression evaluation --- src/core/ArrayElementToRegister.go | 75 ------------------- src/core/CallToRegister.go | 27 ------- src/core/Compare.go | 2 +- src/core/CompileDefinition.go | 26 +------ src/core/DotToRegister.go | 60 --------------- src/core/Evaluate.go | 2 +- src/core/EvaluateArray.go | 10 +-- src/core/EvaluateCall.go | 16 ++-- .../{EvaluateLeaf.go => EvaluateToken.go} | 17 ++--- src/core/Execute.go | 12 +-- src/core/{ExecuteLeaf.go => ExecuteToken.go} | 12 ++- src/core/ExpressionToMemory.go | 23 +----- src/core/ExpressionToRegister.go | 40 ++++++++-- src/core/MultiDefine.go | 33 ++++++++ src/core/TokenToRegister.go | 74 ------------------ src/core/ValueToMemory.go | 24 ++++++ src/core/ValueToRegister.go | 22 ++++++ 17 files changed, 153 insertions(+), 322 deletions(-) delete mode 100644 src/core/ArrayElementToRegister.go delete mode 100644 src/core/CallToRegister.go delete mode 100644 src/core/DotToRegister.go rename src/core/{EvaluateLeaf.go => EvaluateToken.go} (78%) rename src/core/{ExecuteLeaf.go => ExecuteToken.go} (72%) create mode 100644 src/core/MultiDefine.go delete mode 100644 src/core/TokenToRegister.go create mode 100644 src/core/ValueToMemory.go create mode 100644 src/core/ValueToRegister.go diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go deleted file mode 100644 index 8274a97..0000000 --- a/src/core/ArrayElementToRegister.go +++ /dev/null @@ -1,75 +0,0 @@ -package core - -import ( - "math" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/cpu" - "git.urbach.dev/cli/q/src/errors" - "git.urbach.dev/cli/q/src/expression" - "git.urbach.dev/cli/q/src/token" - "git.urbach.dev/cli/q/src/types" -) - -// ArrayElementToRegister moves the value of an array element into the given register. -func (f *Function) ArrayElementToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { - name := node.Children[0].Token.Text(f.File.Bytes) - array := f.VariableByName(name) - - if array == nil { - return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Children[0].Token.Position) - } - - defer f.UseVariable(array) - index := node.Children[1] - - memory := asm.Memory{ - Base: array.Value.Register, - Offset: 0, - OffsetRegister: math.MaxUint8, - Length: byte(1), - } - - switch { - case index.Token.IsNumeric(): - offset, err := f.ToNumber(index.Token) - - if err != nil { - return nil, err - } - - memory.Offset = int8(offset) - - case index.Token.Kind == token.Identifier: - indexName := index.Token.Text(f.File.Bytes) - indexVariable := f.VariableByName(indexName) - - if indexVariable == nil { - return nil, errors.New(&errors.UnknownIdentifier{Name: indexName}, f.File, index.Token.Position) - } - - defer f.UseVariable(indexVariable) - - if !types.Is(indexVariable.Value.Typ, types.AnyInt) { - return nil, errors.New(&errors.TypeMismatch{Encountered: indexVariable.Value.Typ.Name(), Expected: types.AnyInt.Name()}, f.File, index.Token.Position) - } - - memory.OffsetRegister = indexVariable.Value.Register - - default: - typ, err := f.ExpressionToRegister(index, register) - - if err != nil { - return nil, err - } - - if !types.Is(typ, types.AnyInt) { - return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.AnyInt.Name()}, f.File, index.Token.Position) - } - - memory.OffsetRegister = register - } - - f.MemoryRegister(asm.LOAD, memory, register) - return types.Int, nil -} diff --git a/src/core/CallToRegister.go b/src/core/CallToRegister.go deleted file mode 100644 index 9137790..0000000 --- a/src/core/CallToRegister.go +++ /dev/null @@ -1,27 +0,0 @@ -package core - -import ( - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/cpu" - "git.urbach.dev/cli/q/src/expression" - "git.urbach.dev/cli/q/src/types" -) - -// CallToRegister moves the result of a function call into the given register. -func (f *Function) CallToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { - types, err := f.CompileCall(node) - - if err != nil { - return nil, err - } - - if register != f.CPU.Output[0] { - f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) - } - - if len(types) == 0 { - return nil, nil - } - - return types[0], err -} diff --git a/src/core/Compare.go b/src/core/Compare.go index f367020..f9bc25e 100644 --- a/src/core/Compare.go +++ b/src/core/Compare.go @@ -31,7 +31,7 @@ func (f *Function) Compare(comparison *expression.Expression) error { return err } - return f.ExecuteLeaf(comparison.Token, f.CPU.Output[0], right.Token) + return f.ExecuteToken(comparison.Token, f.CPU.Output[0], right.Token) } tmp := f.NewRegister() diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index 58b89be..1ea9564 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -1,10 +1,8 @@ package core import ( - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/errors" - "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" ) @@ -48,27 +46,5 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return errors.New(errors.NotImplemented, f.File, node.Expression.Token.Position) } - count := 0 - types, err := f.CompileCall(right) - - if err != nil { - return err - } - - return left.EachLeaf(func(leaf *expression.Expression) error { - variable, err := f.Define(leaf) - - if err != nil { - return err - } - - if count < len(types) { - variable.Value.Typ = types[count] - } - - f.RegisterRegister(asm.MOVE, variable.Value.Register, f.CPU.Output[count]) - f.AddVariable(variable) - count++ - return nil - }) + return f.MultiDefine(left, right) } diff --git a/src/core/DotToRegister.go b/src/core/DotToRegister.go deleted file mode 100644 index b3c617c..0000000 --- a/src/core/DotToRegister.go +++ /dev/null @@ -1,60 +0,0 @@ -package core - -import ( - "fmt" - "math" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/cpu" - "git.urbach.dev/cli/q/src/errors" - "git.urbach.dev/cli/q/src/expression" - "git.urbach.dev/cli/q/src/types" -) - -// DotToRegister moves a constant or a function address into the given register. -func (f *Function) DotToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { - left := node.Children[0] - right := node.Children[1] - leftText := left.Token.Text(f.File.Bytes) - rightText := right.Token.Text(f.File.Bytes) - variable := f.VariableByName(leftText) - - if variable != nil { - field := variable.Value.Typ.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) - - memory := asm.Memory{ - Base: variable.Value.Register, - Offset: int8(field.Offset), - OffsetRegister: math.MaxUint8, - Length: byte(field.Type.Size()), - } - - f.MemoryRegister(asm.LOAD, memory, register) - return field.Type, nil - } - - constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] - - if isConst { - number, err := ToNumber(constant.Token, constant.File) - - if err != nil { - return nil, err - } - - f.SaveRegister(register) - f.RegisterNumber(asm.MOVE, register, number) - return types.AnyInt, nil - } - - uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) - function, exists := f.All.Functions[uniqueName] - - if exists { - f.File.Imports[leftText].Used = true - f.RegisterLabel(asm.MOVE, register, function.UniqueName) - return types.AnyPointer, nil - } - - return nil, errors.New(&errors.UnknownIdentifier{Name: uniqueName}, f.File, left.Token.Position) -} diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index b13cd17..574b51b 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -19,7 +19,7 @@ func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { } if expr.IsLeaf() { - return f.EvaluateLeaf(expr) + return f.EvaluateToken(expr.Token) } switch expr.Token.Kind { diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go index 857b542..bd32ebd 100644 --- a/src/core/EvaluateArray.go +++ b/src/core/EvaluateArray.go @@ -11,13 +11,13 @@ import ( "git.urbach.dev/cli/q/src/types" ) -// EvaluateArray evaluates a function call. -func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Value, error) { +// EvaluateArray evaluates an array access. +func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Memory, error) { name := expr.Children[0].Token.Text(f.File.Bytes) array := f.VariableByName(name) if array == nil { - return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) + return eval.Memory{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) } defer f.UseVariable(array) @@ -33,11 +33,11 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Value, error index, err := f.Evaluate(indexExpr) if err != nil { - return nil, err + return eval.Memory{}, err } if !types.Is(index.Type(), types.AnyInt) { - return nil, errors.New(&errors.TypeMismatch{Encountered: index.Type().Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) + return eval.Memory{}, errors.New(&errors.TypeMismatch{Encountered: index.Type().Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) } switch index := index.(type) { diff --git a/src/core/EvaluateCall.go b/src/core/EvaluateCall.go index a35c2f0..3f338b1 100644 --- a/src/core/EvaluateCall.go +++ b/src/core/EvaluateCall.go @@ -1,26 +1,22 @@ package core import ( - "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" ) // EvaluateCall evaluates a function call. -func (f *Function) EvaluateCall(expr *expression.Expression) (eval.Value, error) { - types, err := f.CompileCall(expr) +func (f *Function) EvaluateCall(expr *expression.Expression) (eval.Register, error) { + typ, err := f.CompileCall(expr) if err != nil { - return nil, err + return eval.Register{}, err } - if len(types) == 0 { - return nil, errors.New(errors.UntypedExpression, f.File, expr.Token.Position) - } + value := eval.Register{Register: f.CPU.Output[0]} - value := eval.Register{ - Typ: types[0], - Register: f.CPU.Output[0], + if len(typ) > 0 { + value.Typ = typ[0] } return value, nil diff --git a/src/core/EvaluateLeaf.go b/src/core/EvaluateToken.go similarity index 78% rename from src/core/EvaluateLeaf.go rename to src/core/EvaluateToken.go index 86b3629..6a14446 100644 --- a/src/core/EvaluateLeaf.go +++ b/src/core/EvaluateToken.go @@ -6,16 +6,15 @@ import ( "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" - "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" ) -// EvaluateLeaf evaluates a leaf expression. -func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) { - switch expr.Token.Kind { +// EvaluateToken evaluates a single token. +func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { + switch t.Kind { case token.Identifier: - name := expr.Token.Text(f.File.Bytes) + name := t.Text(f.File.Bytes) if name == "true" { value := eval.Number{ @@ -64,10 +63,10 @@ func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) return value, nil } - return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) case token.Number, token.Rune: - number, err := f.ToNumber(expr.Token) + number, err := f.ToNumber(t) if err != nil { return nil, err @@ -81,7 +80,7 @@ func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) return value, nil case token.String: - data := expr.Token.Bytes(f.File.Bytes) + data := t.Bytes(f.File.Bytes) data = String(data) slice := make([]byte, len(data)+8+1) @@ -97,5 +96,5 @@ func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) return value, nil } - return nil, errors.New(errors.InvalidExpression, f.File, expr.Token.Position) + return nil, errors.New(errors.InvalidExpression, f.File, t.Position) } diff --git a/src/core/Execute.go b/src/core/Execute.go index 8d01a4e..22b6904 100644 --- a/src/core/Execute.go +++ b/src/core/Execute.go @@ -8,13 +8,13 @@ import ( ) // Execute executes an operation on a register with a value operand. -func (f *Function) Execute(operation token.Token, register cpu.Register, value *expression.Expression) error { - if value.IsLeaf() { - return f.ExecuteLeaf(operation, register, value.Token) +func (f *Function) Execute(operation token.Token, register cpu.Register, expr *expression.Expression) error { + if expr.IsLeaf() { + return f.ExecuteToken(operation, register, expr.Token) } - if ast.IsFunctionCall(value) { - _, err := f.CompileCall(value) + if ast.IsFunctionCall(expr) { + _, err := f.CompileCall(expr) if err != nil { return err @@ -26,7 +26,7 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * tmp := f.NewRegister() defer f.FreeRegister(tmp) - _, err := f.ExpressionToRegister(value, tmp) + _, err := f.ExpressionToRegister(expr, tmp) if err != nil { return err diff --git a/src/core/ExecuteLeaf.go b/src/core/ExecuteToken.go similarity index 72% rename from src/core/ExecuteLeaf.go rename to src/core/ExecuteToken.go index 6ee59af..77b22b9 100644 --- a/src/core/ExecuteLeaf.go +++ b/src/core/ExecuteToken.go @@ -6,8 +6,8 @@ import ( "git.urbach.dev/cli/q/src/token" ) -// ExecuteLeaf performs an operation on a register with the given leaf operand. -func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error { +// ExecuteToken performs an operation on a register with the given leaf operand. +func (f *Function) ExecuteToken(operation token.Token, register cpu.Register, operand token.Token) error { switch operand.Kind { case token.Identifier: name := operand.Text(f.File.Bytes) @@ -31,7 +31,13 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope case token.String: if operation.Kind == token.Assign { - _, err := f.TokenToRegister(operand, register) + value, err := f.EvaluateToken(operand) + + if err != nil { + return err + } + + f.ValueToRegister(value, register) return err } } diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index 8ec2efa..75b6e23 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -1,10 +1,7 @@ package core import ( - "fmt" - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/types" ) @@ -17,22 +14,6 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me return nil, err } - switch value := value.(type) { - case eval.Number: - f.MemoryNumber(asm.STORE, memory, value.Number) - case eval.Register: - f.MemoryRegister(asm.STORE, memory, value.Register) - f.FreeRegister(value.Register) - case eval.Memory: - tmp := f.NewRegister() - f.MemoryRegister(asm.LOAD, value.Memory, tmp) - f.MemoryRegister(asm.STORE, memory, tmp) - f.FreeRegister(tmp) - case eval.Label: - f.MemoryLabel(asm.STORE, memory, value.Label) - default: - panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, value)) - } - - return value.Type(), err + f.ValueToMemory(value, memory) + return value.Type(), nil } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index a5bfcdc..bd40f05 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -17,16 +17,46 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } if node.IsLeaf() { - return f.TokenToRegister(node.Token, register) + value, err := f.EvaluateToken(node.Token) + + if err != nil { + return nil, err + } + + f.ValueToRegister(value, register) + return value.Type(), nil } switch node.Token.Kind { - case token.Call: - return f.CallToRegister(node, register) case token.Array: - return f.ArrayElementToRegister(node, register) + value, err := f.EvaluateArray(node) + + if err != nil { + return nil, err + } + + f.ValueToRegister(value, register) + return value.Type(), nil + case token.Dot: - return f.DotToRegister(node, register) + value, err := f.EvaluateDot(node) + + if err != nil { + return nil, err + } + + f.ValueToRegister(value, register) + return value.Type(), nil + + case token.Call: + value, err := f.EvaluateCall(node) + + if err != nil { + return nil, err + } + + f.ValueToRegister(value, register) + return value.Type(), nil } if len(node.Children) == 1 { diff --git a/src/core/MultiDefine.go b/src/core/MultiDefine.go new file mode 100644 index 0000000..045afb6 --- /dev/null +++ b/src/core/MultiDefine.go @@ -0,0 +1,33 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/expression" +) + +// MultiDefine defines multiple variables at once. +func (f *Function) MultiDefine(left *expression.Expression, right *expression.Expression) error { + count := 0 + types, err := f.CompileCall(right) + + if err != nil { + return err + } + + return left.EachLeaf(func(leaf *expression.Expression) error { + variable, err := f.Define(leaf) + + if err != nil { + return err + } + + if count < len(types) { + variable.Value.Typ = types[count] + } + + f.RegisterRegister(asm.MOVE, variable.Value.Register, f.CPU.Output[count]) + f.AddVariable(variable) + count++ + return nil + }) +} diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go deleted file mode 100644 index 70989e1..0000000 --- a/src/core/TokenToRegister.go +++ /dev/null @@ -1,74 +0,0 @@ -package core - -import ( - "encoding/binary" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/cpu" - "git.urbach.dev/cli/q/src/errors" - "git.urbach.dev/cli/q/src/token" - "git.urbach.dev/cli/q/src/types" -) - -// TokenToRegister moves a token into a register. -// It only works with identifiers, numbers and strings. -func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types.Type, error) { - switch t.Kind { - case token.Identifier: - name := t.Text(f.File.Bytes) - - if name == "true" { - f.RegisterNumber(asm.MOVE, register, 1) - return types.Bool, nil - } - - if name == "false" { - f.RegisterNumber(asm.MOVE, register, 0) - return types.Bool, nil - } - - variable, function := f.Identifier(name) - - if variable != nil { - f.UseVariable(variable) - f.SaveRegister(register) - f.RegisterRegister(asm.MOVE, register, variable.Value.Register) - return variable.Value.Typ, nil - } - - if function != nil { - f.SaveRegister(register) - f.RegisterLabel(asm.MOVE, register, function.UniqueName) - return types.AnyPointer, nil - } - - return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) - - case token.Number, token.Rune: - number, err := f.ToNumber(t) - - if err != nil { - return nil, err - } - - f.SaveRegister(register) - f.RegisterNumber(asm.MOVE, register, number) - return types.AnyInt, nil - - case token.String: - data := t.Bytes(f.File.Bytes) - data = String(data) - - slice := make([]byte, len(data)+8+1) - binary.LittleEndian.PutUint64(slice, uint64(len(data))) - copy(slice[8:], data) - - label := f.AddBytes(slice) - f.SaveRegister(register) - f.RegisterLabel(asm.MOVE, register, label) - return types.String, nil - - default: - return nil, errors.New(errors.InvalidExpression, f.File, t.Position) - } -} diff --git a/src/core/ValueToMemory.go b/src/core/ValueToMemory.go new file mode 100644 index 0000000..7380b73 --- /dev/null +++ b/src/core/ValueToMemory.go @@ -0,0 +1,24 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/eval" +) + +// ValueToMemory moves a value into a memory region. +func (f *Function) ValueToMemory(value eval.Value, memory asm.Memory) { + switch value := value.(type) { + case eval.Number: + f.MemoryNumber(asm.STORE, memory, value.Number) + case eval.Register: + f.MemoryRegister(asm.STORE, memory, value.Register) + f.FreeRegister(value.Register) + case eval.Memory: + tmp := f.NewRegister() + f.MemoryRegister(asm.LOAD, value.Memory, tmp) + f.MemoryRegister(asm.STORE, memory, tmp) + f.FreeRegister(tmp) + case eval.Label: + f.MemoryLabel(asm.STORE, memory, value.Label) + } +} diff --git a/src/core/ValueToRegister.go b/src/core/ValueToRegister.go new file mode 100644 index 0000000..94da6cd --- /dev/null +++ b/src/core/ValueToRegister.go @@ -0,0 +1,22 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/eval" +) + +// ValueToRegister moves a value into a register. +func (f *Function) ValueToRegister(value eval.Value, register cpu.Register) { + switch value := value.(type) { + case eval.Number: + f.RegisterNumber(asm.MOVE, register, value.Number) + case eval.Register: + f.RegisterRegister(asm.MOVE, register, value.Register) + f.FreeRegister(value.Register) + case eval.Memory: + f.MemoryRegister(asm.LOAD, value.Memory, register) + case eval.Label: + f.RegisterLabel(asm.MOVE, register, value.Label) + } +} From a5a8f0f5035fd34c65e6eebb8709dcb6b87d70f9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Feb 2025 16:07:10 +0100 Subject: [PATCH 0850/1012] Simplified expression evaluation --- src/core/ArrayElementToRegister.go | 75 ------------------- src/core/CallToRegister.go | 27 ------- src/core/Compare.go | 2 +- src/core/CompileDefinition.go | 26 +------ src/core/DotToRegister.go | 60 --------------- src/core/Evaluate.go | 2 +- src/core/EvaluateArray.go | 10 +-- src/core/EvaluateCall.go | 16 ++-- .../{EvaluateLeaf.go => EvaluateToken.go} | 17 ++--- src/core/Execute.go | 12 +-- src/core/{ExecuteLeaf.go => ExecuteToken.go} | 12 ++- src/core/ExpressionToMemory.go | 23 +----- src/core/ExpressionToRegister.go | 40 ++++++++-- src/core/MultiDefine.go | 33 ++++++++ src/core/TokenToRegister.go | 74 ------------------ src/core/ValueToMemory.go | 24 ++++++ src/core/ValueToRegister.go | 22 ++++++ 17 files changed, 153 insertions(+), 322 deletions(-) delete mode 100644 src/core/ArrayElementToRegister.go delete mode 100644 src/core/CallToRegister.go delete mode 100644 src/core/DotToRegister.go rename src/core/{EvaluateLeaf.go => EvaluateToken.go} (78%) rename src/core/{ExecuteLeaf.go => ExecuteToken.go} (72%) create mode 100644 src/core/MultiDefine.go delete mode 100644 src/core/TokenToRegister.go create mode 100644 src/core/ValueToMemory.go create mode 100644 src/core/ValueToRegister.go diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go deleted file mode 100644 index 8274a97..0000000 --- a/src/core/ArrayElementToRegister.go +++ /dev/null @@ -1,75 +0,0 @@ -package core - -import ( - "math" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/cpu" - "git.urbach.dev/cli/q/src/errors" - "git.urbach.dev/cli/q/src/expression" - "git.urbach.dev/cli/q/src/token" - "git.urbach.dev/cli/q/src/types" -) - -// ArrayElementToRegister moves the value of an array element into the given register. -func (f *Function) ArrayElementToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { - name := node.Children[0].Token.Text(f.File.Bytes) - array := f.VariableByName(name) - - if array == nil { - return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Children[0].Token.Position) - } - - defer f.UseVariable(array) - index := node.Children[1] - - memory := asm.Memory{ - Base: array.Value.Register, - Offset: 0, - OffsetRegister: math.MaxUint8, - Length: byte(1), - } - - switch { - case index.Token.IsNumeric(): - offset, err := f.ToNumber(index.Token) - - if err != nil { - return nil, err - } - - memory.Offset = int8(offset) - - case index.Token.Kind == token.Identifier: - indexName := index.Token.Text(f.File.Bytes) - indexVariable := f.VariableByName(indexName) - - if indexVariable == nil { - return nil, errors.New(&errors.UnknownIdentifier{Name: indexName}, f.File, index.Token.Position) - } - - defer f.UseVariable(indexVariable) - - if !types.Is(indexVariable.Value.Typ, types.AnyInt) { - return nil, errors.New(&errors.TypeMismatch{Encountered: indexVariable.Value.Typ.Name(), Expected: types.AnyInt.Name()}, f.File, index.Token.Position) - } - - memory.OffsetRegister = indexVariable.Value.Register - - default: - typ, err := f.ExpressionToRegister(index, register) - - if err != nil { - return nil, err - } - - if !types.Is(typ, types.AnyInt) { - return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.AnyInt.Name()}, f.File, index.Token.Position) - } - - memory.OffsetRegister = register - } - - f.MemoryRegister(asm.LOAD, memory, register) - return types.Int, nil -} diff --git a/src/core/CallToRegister.go b/src/core/CallToRegister.go deleted file mode 100644 index 9137790..0000000 --- a/src/core/CallToRegister.go +++ /dev/null @@ -1,27 +0,0 @@ -package core - -import ( - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/cpu" - "git.urbach.dev/cli/q/src/expression" - "git.urbach.dev/cli/q/src/types" -) - -// CallToRegister moves the result of a function call into the given register. -func (f *Function) CallToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { - types, err := f.CompileCall(node) - - if err != nil { - return nil, err - } - - if register != f.CPU.Output[0] { - f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) - } - - if len(types) == 0 { - return nil, nil - } - - return types[0], err -} diff --git a/src/core/Compare.go b/src/core/Compare.go index f367020..f9bc25e 100644 --- a/src/core/Compare.go +++ b/src/core/Compare.go @@ -31,7 +31,7 @@ func (f *Function) Compare(comparison *expression.Expression) error { return err } - return f.ExecuteLeaf(comparison.Token, f.CPU.Output[0], right.Token) + return f.ExecuteToken(comparison.Token, f.CPU.Output[0], right.Token) } tmp := f.NewRegister() diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index 58b89be..1ea9564 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -1,10 +1,8 @@ package core import ( - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/errors" - "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" ) @@ -48,27 +46,5 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return errors.New(errors.NotImplemented, f.File, node.Expression.Token.Position) } - count := 0 - types, err := f.CompileCall(right) - - if err != nil { - return err - } - - return left.EachLeaf(func(leaf *expression.Expression) error { - variable, err := f.Define(leaf) - - if err != nil { - return err - } - - if count < len(types) { - variable.Value.Typ = types[count] - } - - f.RegisterRegister(asm.MOVE, variable.Value.Register, f.CPU.Output[count]) - f.AddVariable(variable) - count++ - return nil - }) + return f.MultiDefine(left, right) } diff --git a/src/core/DotToRegister.go b/src/core/DotToRegister.go deleted file mode 100644 index b3c617c..0000000 --- a/src/core/DotToRegister.go +++ /dev/null @@ -1,60 +0,0 @@ -package core - -import ( - "fmt" - "math" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/cpu" - "git.urbach.dev/cli/q/src/errors" - "git.urbach.dev/cli/q/src/expression" - "git.urbach.dev/cli/q/src/types" -) - -// DotToRegister moves a constant or a function address into the given register. -func (f *Function) DotToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { - left := node.Children[0] - right := node.Children[1] - leftText := left.Token.Text(f.File.Bytes) - rightText := right.Token.Text(f.File.Bytes) - variable := f.VariableByName(leftText) - - if variable != nil { - field := variable.Value.Typ.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) - - memory := asm.Memory{ - Base: variable.Value.Register, - Offset: int8(field.Offset), - OffsetRegister: math.MaxUint8, - Length: byte(field.Type.Size()), - } - - f.MemoryRegister(asm.LOAD, memory, register) - return field.Type, nil - } - - constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] - - if isConst { - number, err := ToNumber(constant.Token, constant.File) - - if err != nil { - return nil, err - } - - f.SaveRegister(register) - f.RegisterNumber(asm.MOVE, register, number) - return types.AnyInt, nil - } - - uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) - function, exists := f.All.Functions[uniqueName] - - if exists { - f.File.Imports[leftText].Used = true - f.RegisterLabel(asm.MOVE, register, function.UniqueName) - return types.AnyPointer, nil - } - - return nil, errors.New(&errors.UnknownIdentifier{Name: uniqueName}, f.File, left.Token.Position) -} diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index b13cd17..574b51b 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -19,7 +19,7 @@ func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { } if expr.IsLeaf() { - return f.EvaluateLeaf(expr) + return f.EvaluateToken(expr.Token) } switch expr.Token.Kind { diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go index 857b542..bd32ebd 100644 --- a/src/core/EvaluateArray.go +++ b/src/core/EvaluateArray.go @@ -11,13 +11,13 @@ import ( "git.urbach.dev/cli/q/src/types" ) -// EvaluateArray evaluates a function call. -func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Value, error) { +// EvaluateArray evaluates an array access. +func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Memory, error) { name := expr.Children[0].Token.Text(f.File.Bytes) array := f.VariableByName(name) if array == nil { - return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) + return eval.Memory{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) } defer f.UseVariable(array) @@ -33,11 +33,11 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Value, error index, err := f.Evaluate(indexExpr) if err != nil { - return nil, err + return eval.Memory{}, err } if !types.Is(index.Type(), types.AnyInt) { - return nil, errors.New(&errors.TypeMismatch{Encountered: index.Type().Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) + return eval.Memory{}, errors.New(&errors.TypeMismatch{Encountered: index.Type().Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) } switch index := index.(type) { diff --git a/src/core/EvaluateCall.go b/src/core/EvaluateCall.go index a35c2f0..3f338b1 100644 --- a/src/core/EvaluateCall.go +++ b/src/core/EvaluateCall.go @@ -1,26 +1,22 @@ package core import ( - "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" ) // EvaluateCall evaluates a function call. -func (f *Function) EvaluateCall(expr *expression.Expression) (eval.Value, error) { - types, err := f.CompileCall(expr) +func (f *Function) EvaluateCall(expr *expression.Expression) (eval.Register, error) { + typ, err := f.CompileCall(expr) if err != nil { - return nil, err + return eval.Register{}, err } - if len(types) == 0 { - return nil, errors.New(errors.UntypedExpression, f.File, expr.Token.Position) - } + value := eval.Register{Register: f.CPU.Output[0]} - value := eval.Register{ - Typ: types[0], - Register: f.CPU.Output[0], + if len(typ) > 0 { + value.Typ = typ[0] } return value, nil diff --git a/src/core/EvaluateLeaf.go b/src/core/EvaluateToken.go similarity index 78% rename from src/core/EvaluateLeaf.go rename to src/core/EvaluateToken.go index 86b3629..6a14446 100644 --- a/src/core/EvaluateLeaf.go +++ b/src/core/EvaluateToken.go @@ -6,16 +6,15 @@ import ( "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" - "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" ) -// EvaluateLeaf evaluates a leaf expression. -func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) { - switch expr.Token.Kind { +// EvaluateToken evaluates a single token. +func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { + switch t.Kind { case token.Identifier: - name := expr.Token.Text(f.File.Bytes) + name := t.Text(f.File.Bytes) if name == "true" { value := eval.Number{ @@ -64,10 +63,10 @@ func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) return value, nil } - return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Token.Position) + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) case token.Number, token.Rune: - number, err := f.ToNumber(expr.Token) + number, err := f.ToNumber(t) if err != nil { return nil, err @@ -81,7 +80,7 @@ func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) return value, nil case token.String: - data := expr.Token.Bytes(f.File.Bytes) + data := t.Bytes(f.File.Bytes) data = String(data) slice := make([]byte, len(data)+8+1) @@ -97,5 +96,5 @@ func (f *Function) EvaluateLeaf(expr *expression.Expression) (eval.Value, error) return value, nil } - return nil, errors.New(errors.InvalidExpression, f.File, expr.Token.Position) + return nil, errors.New(errors.InvalidExpression, f.File, t.Position) } diff --git a/src/core/Execute.go b/src/core/Execute.go index 8d01a4e..22b6904 100644 --- a/src/core/Execute.go +++ b/src/core/Execute.go @@ -8,13 +8,13 @@ import ( ) // Execute executes an operation on a register with a value operand. -func (f *Function) Execute(operation token.Token, register cpu.Register, value *expression.Expression) error { - if value.IsLeaf() { - return f.ExecuteLeaf(operation, register, value.Token) +func (f *Function) Execute(operation token.Token, register cpu.Register, expr *expression.Expression) error { + if expr.IsLeaf() { + return f.ExecuteToken(operation, register, expr.Token) } - if ast.IsFunctionCall(value) { - _, err := f.CompileCall(value) + if ast.IsFunctionCall(expr) { + _, err := f.CompileCall(expr) if err != nil { return err @@ -26,7 +26,7 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * tmp := f.NewRegister() defer f.FreeRegister(tmp) - _, err := f.ExpressionToRegister(value, tmp) + _, err := f.ExpressionToRegister(expr, tmp) if err != nil { return err diff --git a/src/core/ExecuteLeaf.go b/src/core/ExecuteToken.go similarity index 72% rename from src/core/ExecuteLeaf.go rename to src/core/ExecuteToken.go index 6ee59af..77b22b9 100644 --- a/src/core/ExecuteLeaf.go +++ b/src/core/ExecuteToken.go @@ -6,8 +6,8 @@ import ( "git.urbach.dev/cli/q/src/token" ) -// ExecuteLeaf performs an operation on a register with the given leaf operand. -func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error { +// ExecuteToken performs an operation on a register with the given leaf operand. +func (f *Function) ExecuteToken(operation token.Token, register cpu.Register, operand token.Token) error { switch operand.Kind { case token.Identifier: name := operand.Text(f.File.Bytes) @@ -31,7 +31,13 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope case token.String: if operation.Kind == token.Assign { - _, err := f.TokenToRegister(operand, register) + value, err := f.EvaluateToken(operand) + + if err != nil { + return err + } + + f.ValueToRegister(value, register) return err } } diff --git a/src/core/ExpressionToMemory.go b/src/core/ExpressionToMemory.go index 8ec2efa..75b6e23 100644 --- a/src/core/ExpressionToMemory.go +++ b/src/core/ExpressionToMemory.go @@ -1,10 +1,7 @@ package core import ( - "fmt" - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/types" ) @@ -17,22 +14,6 @@ func (f *Function) ExpressionToMemory(node *expression.Expression, memory asm.Me return nil, err } - switch value := value.(type) { - case eval.Number: - f.MemoryNumber(asm.STORE, memory, value.Number) - case eval.Register: - f.MemoryRegister(asm.STORE, memory, value.Register) - f.FreeRegister(value.Register) - case eval.Memory: - tmp := f.NewRegister() - f.MemoryRegister(asm.LOAD, value.Memory, tmp) - f.MemoryRegister(asm.STORE, memory, tmp) - f.FreeRegister(tmp) - case eval.Label: - f.MemoryLabel(asm.STORE, memory, value.Label) - default: - panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, value)) - } - - return value.Type(), err + f.ValueToMemory(value, memory) + return value.Type(), nil } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index a5bfcdc..bd40f05 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -17,16 +17,46 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } if node.IsLeaf() { - return f.TokenToRegister(node.Token, register) + value, err := f.EvaluateToken(node.Token) + + if err != nil { + return nil, err + } + + f.ValueToRegister(value, register) + return value.Type(), nil } switch node.Token.Kind { - case token.Call: - return f.CallToRegister(node, register) case token.Array: - return f.ArrayElementToRegister(node, register) + value, err := f.EvaluateArray(node) + + if err != nil { + return nil, err + } + + f.ValueToRegister(value, register) + return value.Type(), nil + case token.Dot: - return f.DotToRegister(node, register) + value, err := f.EvaluateDot(node) + + if err != nil { + return nil, err + } + + f.ValueToRegister(value, register) + return value.Type(), nil + + case token.Call: + value, err := f.EvaluateCall(node) + + if err != nil { + return nil, err + } + + f.ValueToRegister(value, register) + return value.Type(), nil } if len(node.Children) == 1 { diff --git a/src/core/MultiDefine.go b/src/core/MultiDefine.go new file mode 100644 index 0000000..045afb6 --- /dev/null +++ b/src/core/MultiDefine.go @@ -0,0 +1,33 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/expression" +) + +// MultiDefine defines multiple variables at once. +func (f *Function) MultiDefine(left *expression.Expression, right *expression.Expression) error { + count := 0 + types, err := f.CompileCall(right) + + if err != nil { + return err + } + + return left.EachLeaf(func(leaf *expression.Expression) error { + variable, err := f.Define(leaf) + + if err != nil { + return err + } + + if count < len(types) { + variable.Value.Typ = types[count] + } + + f.RegisterRegister(asm.MOVE, variable.Value.Register, f.CPU.Output[count]) + f.AddVariable(variable) + count++ + return nil + }) +} diff --git a/src/core/TokenToRegister.go b/src/core/TokenToRegister.go deleted file mode 100644 index 70989e1..0000000 --- a/src/core/TokenToRegister.go +++ /dev/null @@ -1,74 +0,0 @@ -package core - -import ( - "encoding/binary" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/cpu" - "git.urbach.dev/cli/q/src/errors" - "git.urbach.dev/cli/q/src/token" - "git.urbach.dev/cli/q/src/types" -) - -// TokenToRegister moves a token into a register. -// It only works with identifiers, numbers and strings. -func (f *Function) TokenToRegister(t token.Token, register cpu.Register) (types.Type, error) { - switch t.Kind { - case token.Identifier: - name := t.Text(f.File.Bytes) - - if name == "true" { - f.RegisterNumber(asm.MOVE, register, 1) - return types.Bool, nil - } - - if name == "false" { - f.RegisterNumber(asm.MOVE, register, 0) - return types.Bool, nil - } - - variable, function := f.Identifier(name) - - if variable != nil { - f.UseVariable(variable) - f.SaveRegister(register) - f.RegisterRegister(asm.MOVE, register, variable.Value.Register) - return variable.Value.Typ, nil - } - - if function != nil { - f.SaveRegister(register) - f.RegisterLabel(asm.MOVE, register, function.UniqueName) - return types.AnyPointer, nil - } - - return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) - - case token.Number, token.Rune: - number, err := f.ToNumber(t) - - if err != nil { - return nil, err - } - - f.SaveRegister(register) - f.RegisterNumber(asm.MOVE, register, number) - return types.AnyInt, nil - - case token.String: - data := t.Bytes(f.File.Bytes) - data = String(data) - - slice := make([]byte, len(data)+8+1) - binary.LittleEndian.PutUint64(slice, uint64(len(data))) - copy(slice[8:], data) - - label := f.AddBytes(slice) - f.SaveRegister(register) - f.RegisterLabel(asm.MOVE, register, label) - return types.String, nil - - default: - return nil, errors.New(errors.InvalidExpression, f.File, t.Position) - } -} diff --git a/src/core/ValueToMemory.go b/src/core/ValueToMemory.go new file mode 100644 index 0000000..7380b73 --- /dev/null +++ b/src/core/ValueToMemory.go @@ -0,0 +1,24 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/eval" +) + +// ValueToMemory moves a value into a memory region. +func (f *Function) ValueToMemory(value eval.Value, memory asm.Memory) { + switch value := value.(type) { + case eval.Number: + f.MemoryNumber(asm.STORE, memory, value.Number) + case eval.Register: + f.MemoryRegister(asm.STORE, memory, value.Register) + f.FreeRegister(value.Register) + case eval.Memory: + tmp := f.NewRegister() + f.MemoryRegister(asm.LOAD, value.Memory, tmp) + f.MemoryRegister(asm.STORE, memory, tmp) + f.FreeRegister(tmp) + case eval.Label: + f.MemoryLabel(asm.STORE, memory, value.Label) + } +} diff --git a/src/core/ValueToRegister.go b/src/core/ValueToRegister.go new file mode 100644 index 0000000..94da6cd --- /dev/null +++ b/src/core/ValueToRegister.go @@ -0,0 +1,22 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/eval" +) + +// ValueToRegister moves a value into a register. +func (f *Function) ValueToRegister(value eval.Value, register cpu.Register) { + switch value := value.(type) { + case eval.Number: + f.RegisterNumber(asm.MOVE, register, value.Number) + case eval.Register: + f.RegisterRegister(asm.MOVE, register, value.Register) + f.FreeRegister(value.Register) + case eval.Memory: + f.MemoryRegister(asm.LOAD, value.Memory, register) + case eval.Label: + f.RegisterLabel(asm.MOVE, register, value.Label) + } +} From 6fef3bccf6f2927aaddbcf92c60d8329432a304a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Feb 2025 19:37:06 +0100 Subject: [PATCH 0851/1012] Fixed inconsistent lifetimes --- src/core/CompileAssignArray.go | 1 - src/core/CompileAssignDivision.go | 10 ++++++-- src/core/CompileCondition.go | 1 - src/core/CompileFor.go | 38 +++++++++++++++++++------------ src/core/CompileLen.go | 1 - src/core/EvaluateArray.go | 1 - src/core/EvaluateToken.go | 16 +------------ src/core/ValueToMemory.go | 1 - src/core/ValueToRegister.go | 1 - tests/programs/for.q | 8 +++++++ 10 files changed, 41 insertions(+), 37 deletions(-) diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 46a02e0..05244e0 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -47,7 +47,6 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { memory.Offset = int8(index.Number) case eval.Register: memory.OffsetRegister = index.Register - defer f.FreeRegister(index.Register) default: panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, index)) } diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index cf4be7b..1569ae1 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -80,8 +80,14 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { f.RegisterNumber(asm.MOVE, x86.RAX, dividend.Number) err = f.Execute(division.Token, x86.RAX, divisor) case eval.Register: - err = f.Execute(division.Token, dividend.Register, divisor) - defer f.FreeRegister(dividend.Register) + if dividend.Register != quotientVariable.Value.Register && dividend.IsAlive() { + tmp := f.NewRegister() + f.RegisterRegister(asm.MOVE, tmp, dividend.Register) + err = f.Execute(division.Token, tmp, divisor) + f.FreeRegister(tmp) + } else { + err = f.Execute(division.Token, dividend.Register, divisor) + } default: panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, dividend)) } diff --git a/src/core/CompileCondition.go b/src/core/CompileCondition.go index 19451a9..983c275 100644 --- a/src/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -86,7 +86,6 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab } case eval.Register: f.RegisterNumber(asm.COMPARE, value.Register, 0) - f.FreeRegister(value.Register) f.Jump(asm.JE, failLabel) default: panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, value)) diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index 414a166..a07702b 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -29,21 +29,10 @@ func (f *Function) CompileFor(loop *ast.For) error { to *expression.Expression ) - scope := f.PushScope(loop.Body, f.File.Bytes) - scope.InLoop = true - switch loop.Head.Token.Kind { case token.Define: - variable, err := f.Define(loop.Head.Children[0]) - - if err != nil { - return err - } - - counter = variable.Value.Register from = loop.Head.Children[1].Children[0] to = loop.Head.Children[1].Children[1] - f.AddVariable(variable) case token.Range: counter = f.NewRegister() @@ -71,14 +60,35 @@ func (f *Function) CompileFor(loop *ast.For) error { return errors.New(&errors.TypeMismatch{Encountered: value.Type().Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) } - f.AddLabel(label) + scope := f.PushScope(loop.Body, f.File.Bytes) + scope.InLoop = true + + if loop.Head.Token.Kind == token.Define { + variable, err := f.Define(loop.Head.Children[0]) + + if err != nil { + return err + } + + counter = variable.Value.Register + f.AddVariable(variable) + } switch value := value.(type) { case eval.Number: + f.AddLabel(label) f.RegisterNumber(asm.COMPARE, counter, value.Number) case eval.Register: - f.RegisterRegister(asm.COMPARE, counter, value.Register) - defer f.FreeRegister(value.Register) + if value.IsAlive() { + tmp := f.NewRegister() + f.RegisterRegister(asm.MOVE, tmp, value.Register) + defer f.FreeRegister(tmp) + f.AddLabel(label) + f.RegisterRegister(asm.COMPARE, counter, tmp) + } else { + f.AddLabel(label) + f.RegisterRegister(asm.COMPARE, counter, value.Register) + } default: panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, value)) } diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index 3a0fb79..bf44e1a 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -37,7 +37,6 @@ func (f *Function) CompileLen(root *expression.Expression) error { switch value := value.(type) { case eval.Register: memory.Base = value.Register - defer f.FreeRegister(value.Register) case eval.Label: f.RegisterLabel(asm.MOVE, output, value.Label) memory.Base = output diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go index bd32ebd..72ef764 100644 --- a/src/core/EvaluateArray.go +++ b/src/core/EvaluateArray.go @@ -45,7 +45,6 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Memory, erro memory.Offset = int8(index.Number) case eval.Register: memory.OffsetRegister = index.Register - defer f.FreeRegister(index.Register) default: panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, index)) } diff --git a/src/core/EvaluateToken.go b/src/core/EvaluateToken.go index 6a14446..3d5d18f 100644 --- a/src/core/EvaluateToken.go +++ b/src/core/EvaluateToken.go @@ -3,7 +3,6 @@ package core import ( "encoding/binary" - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/token" @@ -38,20 +37,7 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { if variable != nil { f.UseVariable(variable) - - if variable.Value.Alive == 0 { - return variable.Value, nil - } - - tmp := f.NewRegister() - f.RegisterRegister(asm.MOVE, tmp, variable.Value.Register) - - value := eval.Register{ - Typ: variable.Value.Typ, - Register: tmp, - } - - return value, nil + return variable.Value, nil } if function != nil { diff --git a/src/core/ValueToMemory.go b/src/core/ValueToMemory.go index 7380b73..07c30e3 100644 --- a/src/core/ValueToMemory.go +++ b/src/core/ValueToMemory.go @@ -12,7 +12,6 @@ func (f *Function) ValueToMemory(value eval.Value, memory asm.Memory) { f.MemoryNumber(asm.STORE, memory, value.Number) case eval.Register: f.MemoryRegister(asm.STORE, memory, value.Register) - f.FreeRegister(value.Register) case eval.Memory: tmp := f.NewRegister() f.MemoryRegister(asm.LOAD, value.Memory, tmp) diff --git a/src/core/ValueToRegister.go b/src/core/ValueToRegister.go index 94da6cd..51d0656 100644 --- a/src/core/ValueToRegister.go +++ b/src/core/ValueToRegister.go @@ -13,7 +13,6 @@ func (f *Function) ValueToRegister(value eval.Value, register cpu.Register) { f.RegisterNumber(asm.MOVE, register, value.Number) case eval.Register: f.RegisterRegister(asm.MOVE, register, value.Register) - f.FreeRegister(value.Register) case eval.Memory: f.MemoryRegister(asm.LOAD, value.Memory, register) case eval.Label: diff --git a/tests/programs/for.q b/tests/programs/for.q index 894751f..a8f5256 100644 --- a/tests/programs/for.q +++ b/tests/programs/for.q @@ -17,4 +17,12 @@ main() { assert i >= 0 assert i < 10 } + + ten := 10 + + for 0..ten { + total += 1 + } + + assert total == ten } \ No newline at end of file From be6eafddf59800f931406e8edbb54a9d9d1bd8f0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Feb 2025 19:37:06 +0100 Subject: [PATCH 0852/1012] Fixed inconsistent lifetimes --- src/core/CompileAssignArray.go | 1 - src/core/CompileAssignDivision.go | 10 ++++++-- src/core/CompileCondition.go | 1 - src/core/CompileFor.go | 38 +++++++++++++++++++------------ src/core/CompileLen.go | 1 - src/core/EvaluateArray.go | 1 - src/core/EvaluateToken.go | 16 +------------ src/core/ValueToMemory.go | 1 - src/core/ValueToRegister.go | 1 - tests/programs/for.q | 8 +++++++ 10 files changed, 41 insertions(+), 37 deletions(-) diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index 46a02e0..05244e0 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -47,7 +47,6 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { memory.Offset = int8(index.Number) case eval.Register: memory.OffsetRegister = index.Register - defer f.FreeRegister(index.Register) default: panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, index)) } diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index cf4be7b..1569ae1 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -80,8 +80,14 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { f.RegisterNumber(asm.MOVE, x86.RAX, dividend.Number) err = f.Execute(division.Token, x86.RAX, divisor) case eval.Register: - err = f.Execute(division.Token, dividend.Register, divisor) - defer f.FreeRegister(dividend.Register) + if dividend.Register != quotientVariable.Value.Register && dividend.IsAlive() { + tmp := f.NewRegister() + f.RegisterRegister(asm.MOVE, tmp, dividend.Register) + err = f.Execute(division.Token, tmp, divisor) + f.FreeRegister(tmp) + } else { + err = f.Execute(division.Token, dividend.Register, divisor) + } default: panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, dividend)) } diff --git a/src/core/CompileCondition.go b/src/core/CompileCondition.go index 19451a9..983c275 100644 --- a/src/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -86,7 +86,6 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab } case eval.Register: f.RegisterNumber(asm.COMPARE, value.Register, 0) - f.FreeRegister(value.Register) f.Jump(asm.JE, failLabel) default: panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, value)) diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index 414a166..a07702b 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -29,21 +29,10 @@ func (f *Function) CompileFor(loop *ast.For) error { to *expression.Expression ) - scope := f.PushScope(loop.Body, f.File.Bytes) - scope.InLoop = true - switch loop.Head.Token.Kind { case token.Define: - variable, err := f.Define(loop.Head.Children[0]) - - if err != nil { - return err - } - - counter = variable.Value.Register from = loop.Head.Children[1].Children[0] to = loop.Head.Children[1].Children[1] - f.AddVariable(variable) case token.Range: counter = f.NewRegister() @@ -71,14 +60,35 @@ func (f *Function) CompileFor(loop *ast.For) error { return errors.New(&errors.TypeMismatch{Encountered: value.Type().Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) } - f.AddLabel(label) + scope := f.PushScope(loop.Body, f.File.Bytes) + scope.InLoop = true + + if loop.Head.Token.Kind == token.Define { + variable, err := f.Define(loop.Head.Children[0]) + + if err != nil { + return err + } + + counter = variable.Value.Register + f.AddVariable(variable) + } switch value := value.(type) { case eval.Number: + f.AddLabel(label) f.RegisterNumber(asm.COMPARE, counter, value.Number) case eval.Register: - f.RegisterRegister(asm.COMPARE, counter, value.Register) - defer f.FreeRegister(value.Register) + if value.IsAlive() { + tmp := f.NewRegister() + f.RegisterRegister(asm.MOVE, tmp, value.Register) + defer f.FreeRegister(tmp) + f.AddLabel(label) + f.RegisterRegister(asm.COMPARE, counter, tmp) + } else { + f.AddLabel(label) + f.RegisterRegister(asm.COMPARE, counter, value.Register) + } default: panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, value)) } diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index 3a0fb79..bf44e1a 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -37,7 +37,6 @@ func (f *Function) CompileLen(root *expression.Expression) error { switch value := value.(type) { case eval.Register: memory.Base = value.Register - defer f.FreeRegister(value.Register) case eval.Label: f.RegisterLabel(asm.MOVE, output, value.Label) memory.Base = output diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go index bd32ebd..72ef764 100644 --- a/src/core/EvaluateArray.go +++ b/src/core/EvaluateArray.go @@ -45,7 +45,6 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Memory, erro memory.Offset = int8(index.Number) case eval.Register: memory.OffsetRegister = index.Register - defer f.FreeRegister(index.Register) default: panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, index)) } diff --git a/src/core/EvaluateToken.go b/src/core/EvaluateToken.go index 6a14446..3d5d18f 100644 --- a/src/core/EvaluateToken.go +++ b/src/core/EvaluateToken.go @@ -3,7 +3,6 @@ package core import ( "encoding/binary" - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/token" @@ -38,20 +37,7 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { if variable != nil { f.UseVariable(variable) - - if variable.Value.Alive == 0 { - return variable.Value, nil - } - - tmp := f.NewRegister() - f.RegisterRegister(asm.MOVE, tmp, variable.Value.Register) - - value := eval.Register{ - Typ: variable.Value.Typ, - Register: tmp, - } - - return value, nil + return variable.Value, nil } if function != nil { diff --git a/src/core/ValueToMemory.go b/src/core/ValueToMemory.go index 7380b73..07c30e3 100644 --- a/src/core/ValueToMemory.go +++ b/src/core/ValueToMemory.go @@ -12,7 +12,6 @@ func (f *Function) ValueToMemory(value eval.Value, memory asm.Memory) { f.MemoryNumber(asm.STORE, memory, value.Number) case eval.Register: f.MemoryRegister(asm.STORE, memory, value.Register) - f.FreeRegister(value.Register) case eval.Memory: tmp := f.NewRegister() f.MemoryRegister(asm.LOAD, value.Memory, tmp) diff --git a/src/core/ValueToRegister.go b/src/core/ValueToRegister.go index 94da6cd..51d0656 100644 --- a/src/core/ValueToRegister.go +++ b/src/core/ValueToRegister.go @@ -13,7 +13,6 @@ func (f *Function) ValueToRegister(value eval.Value, register cpu.Register) { f.RegisterNumber(asm.MOVE, register, value.Number) case eval.Register: f.RegisterRegister(asm.MOVE, register, value.Register) - f.FreeRegister(value.Register) case eval.Memory: f.MemoryRegister(asm.LOAD, value.Memory, register) case eval.Label: diff --git a/tests/programs/for.q b/tests/programs/for.q index 894751f..a8f5256 100644 --- a/tests/programs/for.q +++ b/tests/programs/for.q @@ -17,4 +17,12 @@ main() { assert i >= 0 assert i < 10 } + + ten := 10 + + for 0..ten { + total += 1 + } + + assert total == ten } \ No newline at end of file From 485efe57273a35d9079ab9c3a9282d5fa362d805 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Feb 2025 19:52:21 +0100 Subject: [PATCH 0853/1012] Added more tests --- tests/programs/for.q | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/programs/for.q b/tests/programs/for.q index a8f5256..4a68ff0 100644 --- a/tests/programs/for.q +++ b/tests/programs/for.q @@ -13,6 +13,11 @@ main() { assert total == 0 + for i := total..10 { + assert i >= 0 + assert i < 10 + } + for i := 0..10 { assert i >= 0 assert i < 10 @@ -24,5 +29,5 @@ main() { total += 1 } - assert total == ten + assert total == 10 } \ No newline at end of file From c1427eb7f6836a2792aa4a09440a50e36d96583e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Feb 2025 19:52:21 +0100 Subject: [PATCH 0854/1012] Added more tests --- tests/programs/for.q | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/programs/for.q b/tests/programs/for.q index a8f5256..4a68ff0 100644 --- a/tests/programs/for.q +++ b/tests/programs/for.q @@ -13,6 +13,11 @@ main() { assert total == 0 + for i := total..10 { + assert i >= 0 + assert i < 10 + } + for i := 0..10 { assert i >= 0 assert i < 10 @@ -24,5 +29,5 @@ main() { total += 1 } - assert total == ten + assert total == 10 } \ No newline at end of file From a27b8efb36a5675715f56bc27810b87e5fbce930 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 1 Mar 2025 11:34:36 +0100 Subject: [PATCH 0855/1012] Improved implementation of assign operators --- src/core/CompileAssign.go | 71 +++++++++++++++++++++++----------- src/core/CompileAssignArray.go | 56 --------------------------- src/core/CompileAssignField.go | 42 -------------------- src/core/EvaluateArray.go | 33 ++++++++++++---- src/core/EvaluateDot.go | 9 ++++- 5 files changed, 81 insertions(+), 130 deletions(-) delete mode 100644 src/core/CompileAssignArray.go delete mode 100644 src/core/CompileAssignField.go diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 2126f45..5d2263b 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -1,8 +1,12 @@ package core import ( + "fmt" + + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/token" ) @@ -11,33 +15,54 @@ func (f *Function) CompileAssign(node *ast.Assign) error { left := node.Expression.Children[0] right := node.Expression.Children[1] - if left.IsLeaf() { - name := left.Token.Text(f.File.Bytes) - variable := f.VariableByName(name) - - if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) + if left.Token.Kind == token.Separator { + if right.Token.Kind == token.Div { + return f.CompileAssignDivision(node.Expression) } - defer f.UseVariable(variable) - return f.Execute(node.Expression.Token, variable.Value.Register, right) - } + if right.Token.Kind == token.Call { + return f.MultiAssign(left, right) + } - if left.Token.Kind == token.Dot { - return f.CompileAssignField(node) - } - - if left.Token.Kind == token.Array { - return f.CompileAssignArray(node) - } - - if left.Token.Kind == token.Separator && right.Token.Kind == token.Div { - return f.CompileAssignDivision(node.Expression) - } - - if !ast.IsFunctionCall(right) { return errors.New(errors.NotImplemented, f.File, node.Expression.Token.Position) } - return f.MultiAssign(left, right) + leftValue, err := f.Evaluate(left) + + if err != nil { + return err + } + + operation := node.Expression.Token + + switch leftValue := leftValue.(type) { + case eval.Register: + f.Execute(operation, leftValue.Register, right) + case eval.Memory: + if operation.Kind == token.Assign { + rightValue, err := f.Evaluate(right) + + if err != nil { + return err + } + + f.ValueToMemory(rightValue, leftValue.Memory) + return nil + } + + tmp := f.NewRegister() + f.ValueToRegister(leftValue, tmp) + err := f.Execute(operation, tmp, right) + + if err != nil { + return err + } + + f.MemoryRegister(asm.STORE, leftValue.Memory, tmp) + f.FreeRegister(tmp) + default: + panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, leftValue)) + } + + return nil } diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go deleted file mode 100644 index 05244e0..0000000 --- a/src/core/CompileAssignArray.go +++ /dev/null @@ -1,56 +0,0 @@ -package core - -import ( - "fmt" - "math" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/ast" - "git.urbach.dev/cli/q/src/errors" - "git.urbach.dev/cli/q/src/eval" - "git.urbach.dev/cli/q/src/types" -) - -// CompileAssignArray compiles an assign statement for array elements. -func (f *Function) CompileAssignArray(node *ast.Assign) error { - left := node.Expression.Children[0] - right := node.Expression.Children[1] - name := left.Children[0].Token.Text(f.File.Bytes) - variable := f.VariableByName(name) - - if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position) - } - - defer f.UseVariable(variable) - - memory := asm.Memory{ - Base: variable.Value.Register, - Offset: 0, - OffsetRegister: math.MaxUint8, - Length: byte(1), - } - - indexExpr := left.Children[1] - index, err := f.Evaluate(indexExpr) - - if err != nil { - return err - } - - if !types.Is(index.Type(), types.AnyInt) { - return errors.New(&errors.TypeMismatch{Encountered: index.Type().Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) - } - - switch index := index.(type) { - case eval.Number: - memory.Offset = int8(index.Number) - case eval.Register: - memory.OffsetRegister = index.Register - default: - panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, index)) - } - - _, err = f.ExpressionToMemory(right, memory) - return err -} diff --git a/src/core/CompileAssignField.go b/src/core/CompileAssignField.go deleted file mode 100644 index bc965c6..0000000 --- a/src/core/CompileAssignField.go +++ /dev/null @@ -1,42 +0,0 @@ -package core - -import ( - "math" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/ast" - "git.urbach.dev/cli/q/src/errors" - "git.urbach.dev/cli/q/src/types" -) - -// CompileAssignField compiles a memory write to a struct field. -func (f *Function) CompileAssignField(node *ast.Assign) error { - destination := node.Expression.Children[0] - value := node.Expression.Children[1] - name := destination.Children[0].Token.Text(f.File.Bytes) - fieldName := destination.Children[1].Token.Text(f.File.Bytes) - variable := f.VariableByName(name) - - if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, destination.Children[0].Token.Position) - } - - defer f.UseVariable(variable) - pointer := variable.Value.Typ.(*types.Pointer) - structure := pointer.To.(*types.Struct) - field := structure.FieldByName(fieldName) - - if field == nil { - return errors.New(&errors.UnknownStructField{StructName: structure.Name(), FieldName: fieldName}, f.File, destination.Children[1].Token.Position) - } - - memory := asm.Memory{ - Base: variable.Value.Register, - Offset: int8(field.Offset), - OffsetRegister: math.MaxUint8, - Length: byte(field.Type.Size()), - } - - _, err := f.ExpressionToMemory(value, memory) - return err -} diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go index 72ef764..5844015 100644 --- a/src/core/EvaluateArray.go +++ b/src/core/EvaluateArray.go @@ -14,16 +14,16 @@ import ( // EvaluateArray evaluates an array access. func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Memory, error) { name := expr.Children[0].Token.Text(f.File.Bytes) - array := f.VariableByName(name) + base := f.VariableByName(name) - if array == nil { + if base == nil { return eval.Memory{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) } - defer f.UseVariable(array) + defer f.UseVariable(base) memory := asm.Memory{ - Base: array.Value.Register, + Base: base.Value.Register, Offset: 0, OffsetRegister: math.MaxUint8, Length: byte(1), @@ -49,10 +49,27 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Memory, erro panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, index)) } - value := eval.Memory{ - Typ: array.Value.Typ.(*types.Array).Of, - Memory: memory, + array, isArray := base.Value.Typ.(*types.Array) + + if isArray { + value := eval.Memory{ + Typ: array.Of, + Memory: memory, + } + + return value, nil } - return value, nil + pointer, isPointer := base.Value.Typ.(*types.Pointer) + + if isPointer { + value := eval.Memory{ + Typ: pointer.To, + Memory: memory, + } + + return value, nil + } + + panic("invalid type") } diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go index 71aeebe..ca73d88 100644 --- a/src/core/EvaluateDot.go +++ b/src/core/EvaluateDot.go @@ -20,7 +20,14 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) variable := f.VariableByName(leftText) if variable != nil { - field := variable.Value.Typ.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + f.UseVariable(variable) + pointer := variable.Value.Typ.(*types.Pointer) + structure := pointer.To.(*types.Struct) + field := structure.FieldByName(rightText) + + if field == nil { + return nil, errors.New(&errors.UnknownStructField{StructName: structure.Name(), FieldName: rightText}, f.File, right.Token.Position) + } value := eval.Memory{ Typ: field.Type, From 6c0ab72f8f915eb9c891e58707412f2dc07b2ef6 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 1 Mar 2025 11:34:36 +0100 Subject: [PATCH 0856/1012] Improved implementation of assign operators --- src/core/CompileAssign.go | 71 +++++++++++++++++++++++----------- src/core/CompileAssignArray.go | 56 --------------------------- src/core/CompileAssignField.go | 42 -------------------- src/core/EvaluateArray.go | 33 ++++++++++++---- src/core/EvaluateDot.go | 9 ++++- 5 files changed, 81 insertions(+), 130 deletions(-) delete mode 100644 src/core/CompileAssignArray.go delete mode 100644 src/core/CompileAssignField.go diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 2126f45..5d2263b 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -1,8 +1,12 @@ package core import ( + "fmt" + + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/ast" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/token" ) @@ -11,33 +15,54 @@ func (f *Function) CompileAssign(node *ast.Assign) error { left := node.Expression.Children[0] right := node.Expression.Children[1] - if left.IsLeaf() { - name := left.Token.Text(f.File.Bytes) - variable := f.VariableByName(name) - - if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) + if left.Token.Kind == token.Separator { + if right.Token.Kind == token.Div { + return f.CompileAssignDivision(node.Expression) } - defer f.UseVariable(variable) - return f.Execute(node.Expression.Token, variable.Value.Register, right) - } + if right.Token.Kind == token.Call { + return f.MultiAssign(left, right) + } - if left.Token.Kind == token.Dot { - return f.CompileAssignField(node) - } - - if left.Token.Kind == token.Array { - return f.CompileAssignArray(node) - } - - if left.Token.Kind == token.Separator && right.Token.Kind == token.Div { - return f.CompileAssignDivision(node.Expression) - } - - if !ast.IsFunctionCall(right) { return errors.New(errors.NotImplemented, f.File, node.Expression.Token.Position) } - return f.MultiAssign(left, right) + leftValue, err := f.Evaluate(left) + + if err != nil { + return err + } + + operation := node.Expression.Token + + switch leftValue := leftValue.(type) { + case eval.Register: + f.Execute(operation, leftValue.Register, right) + case eval.Memory: + if operation.Kind == token.Assign { + rightValue, err := f.Evaluate(right) + + if err != nil { + return err + } + + f.ValueToMemory(rightValue, leftValue.Memory) + return nil + } + + tmp := f.NewRegister() + f.ValueToRegister(leftValue, tmp) + err := f.Execute(operation, tmp, right) + + if err != nil { + return err + } + + f.MemoryRegister(asm.STORE, leftValue.Memory, tmp) + f.FreeRegister(tmp) + default: + panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, leftValue)) + } + + return nil } diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go deleted file mode 100644 index 05244e0..0000000 --- a/src/core/CompileAssignArray.go +++ /dev/null @@ -1,56 +0,0 @@ -package core - -import ( - "fmt" - "math" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/ast" - "git.urbach.dev/cli/q/src/errors" - "git.urbach.dev/cli/q/src/eval" - "git.urbach.dev/cli/q/src/types" -) - -// CompileAssignArray compiles an assign statement for array elements. -func (f *Function) CompileAssignArray(node *ast.Assign) error { - left := node.Expression.Children[0] - right := node.Expression.Children[1] - name := left.Children[0].Token.Text(f.File.Bytes) - variable := f.VariableByName(name) - - if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position) - } - - defer f.UseVariable(variable) - - memory := asm.Memory{ - Base: variable.Value.Register, - Offset: 0, - OffsetRegister: math.MaxUint8, - Length: byte(1), - } - - indexExpr := left.Children[1] - index, err := f.Evaluate(indexExpr) - - if err != nil { - return err - } - - if !types.Is(index.Type(), types.AnyInt) { - return errors.New(&errors.TypeMismatch{Encountered: index.Type().Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) - } - - switch index := index.(type) { - case eval.Number: - memory.Offset = int8(index.Number) - case eval.Register: - memory.OffsetRegister = index.Register - default: - panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, index)) - } - - _, err = f.ExpressionToMemory(right, memory) - return err -} diff --git a/src/core/CompileAssignField.go b/src/core/CompileAssignField.go deleted file mode 100644 index bc965c6..0000000 --- a/src/core/CompileAssignField.go +++ /dev/null @@ -1,42 +0,0 @@ -package core - -import ( - "math" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/ast" - "git.urbach.dev/cli/q/src/errors" - "git.urbach.dev/cli/q/src/types" -) - -// CompileAssignField compiles a memory write to a struct field. -func (f *Function) CompileAssignField(node *ast.Assign) error { - destination := node.Expression.Children[0] - value := node.Expression.Children[1] - name := destination.Children[0].Token.Text(f.File.Bytes) - fieldName := destination.Children[1].Token.Text(f.File.Bytes) - variable := f.VariableByName(name) - - if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, destination.Children[0].Token.Position) - } - - defer f.UseVariable(variable) - pointer := variable.Value.Typ.(*types.Pointer) - structure := pointer.To.(*types.Struct) - field := structure.FieldByName(fieldName) - - if field == nil { - return errors.New(&errors.UnknownStructField{StructName: structure.Name(), FieldName: fieldName}, f.File, destination.Children[1].Token.Position) - } - - memory := asm.Memory{ - Base: variable.Value.Register, - Offset: int8(field.Offset), - OffsetRegister: math.MaxUint8, - Length: byte(field.Type.Size()), - } - - _, err := f.ExpressionToMemory(value, memory) - return err -} diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go index 72ef764..5844015 100644 --- a/src/core/EvaluateArray.go +++ b/src/core/EvaluateArray.go @@ -14,16 +14,16 @@ import ( // EvaluateArray evaluates an array access. func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Memory, error) { name := expr.Children[0].Token.Text(f.File.Bytes) - array := f.VariableByName(name) + base := f.VariableByName(name) - if array == nil { + if base == nil { return eval.Memory{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) } - defer f.UseVariable(array) + defer f.UseVariable(base) memory := asm.Memory{ - Base: array.Value.Register, + Base: base.Value.Register, Offset: 0, OffsetRegister: math.MaxUint8, Length: byte(1), @@ -49,10 +49,27 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Memory, erro panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, index)) } - value := eval.Memory{ - Typ: array.Value.Typ.(*types.Array).Of, - Memory: memory, + array, isArray := base.Value.Typ.(*types.Array) + + if isArray { + value := eval.Memory{ + Typ: array.Of, + Memory: memory, + } + + return value, nil } - return value, nil + pointer, isPointer := base.Value.Typ.(*types.Pointer) + + if isPointer { + value := eval.Memory{ + Typ: pointer.To, + Memory: memory, + } + + return value, nil + } + + panic("invalid type") } diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go index 71aeebe..ca73d88 100644 --- a/src/core/EvaluateDot.go +++ b/src/core/EvaluateDot.go @@ -20,7 +20,14 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) variable := f.VariableByName(leftText) if variable != nil { - field := variable.Value.Typ.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + f.UseVariable(variable) + pointer := variable.Value.Typ.(*types.Pointer) + structure := pointer.To.(*types.Struct) + field := structure.FieldByName(rightText) + + if field == nil { + return nil, errors.New(&errors.UnknownStructField{StructName: structure.Name(), FieldName: rightText}, f.File, right.Token.Position) + } value := eval.Memory{ Typ: field.Type, From 6698cd95adff7b91fd996e148b5893733b0ff287 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 1 Mar 2025 12:19:51 +0100 Subject: [PATCH 0857/1012] Added more tests --- src/core/Evaluate.go | 1 + tests/programs/struct.q | 66 +++++++++++++++++++++++++++-------------- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 574b51b..e13f741 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -41,5 +41,6 @@ func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { Register: tmp, } + f.FreeRegister(tmp) return value, err } diff --git a/tests/programs/struct.q b/tests/programs/struct.q index ad438c4..dd23388 100644 --- a/tests/programs/struct.q +++ b/tests/programs/struct.q @@ -4,31 +4,53 @@ struct Point { } main() { - p := new(Point) - assert p.x == 0 - assert p.y == 0 - assert p.x == p.y + a := new(Point) + assert a.x == 0 + assert a.y == 0 + assert a.x == a.y - p.x = 1 - p.y = 2 - assert p.x == 1 - assert p.y == 2 - assert p.x != p.y + a.x = 1 + a.y = 2 + assert a.x == 1 + assert a.y == 2 + assert a.x != a.y - p.x = p.y - assert p.x == 2 - assert p.y == 2 - assert p.x == p.y + a.x = a.y + assert a.x == 2 + assert a.y == 2 + assert a.x == a.y - p.x = p.y + 1 - assert p.x == 3 - assert p.y == 2 - assert p.x != p.y + a.x = a.y + 1 + assert a.x == 3 + assert a.y == 2 + assert a.x != a.y - p.y = p.x - assert p.x == 3 - assert p.y == 3 - assert p.x == p.y + a.y += 1 + assert a.x == 3 + assert a.y == 3 + assert a.x == a.y - delete(p) + b := new(Point) + assert b.x == 0 + assert b.y == 0 + + b.x = -3 + b.y = -3 + assert b.x == -3 + assert b.y == -3 + + c := new(Point) + assert c.x == 0 + assert c.y == 0 + + c.x = a.x + b.x + c.y = a.y + b.y + assert c.x == a.x + b.x + assert c.y == a.y + b.y + assert c.x == 0 + assert c.y == 0 + + delete(a) + delete(b) + delete(c) } \ No newline at end of file From e032733a92ed8f9fb65a51453c7ee63a68c580e5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 1 Mar 2025 12:19:51 +0100 Subject: [PATCH 0858/1012] Added more tests --- src/core/Evaluate.go | 1 + tests/programs/struct.q | 66 +++++++++++++++++++++++++++-------------- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 574b51b..e13f741 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -41,5 +41,6 @@ func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { Register: tmp, } + f.FreeRegister(tmp) return value, err } diff --git a/tests/programs/struct.q b/tests/programs/struct.q index ad438c4..dd23388 100644 --- a/tests/programs/struct.q +++ b/tests/programs/struct.q @@ -4,31 +4,53 @@ struct Point { } main() { - p := new(Point) - assert p.x == 0 - assert p.y == 0 - assert p.x == p.y + a := new(Point) + assert a.x == 0 + assert a.y == 0 + assert a.x == a.y - p.x = 1 - p.y = 2 - assert p.x == 1 - assert p.y == 2 - assert p.x != p.y + a.x = 1 + a.y = 2 + assert a.x == 1 + assert a.y == 2 + assert a.x != a.y - p.x = p.y - assert p.x == 2 - assert p.y == 2 - assert p.x == p.y + a.x = a.y + assert a.x == 2 + assert a.y == 2 + assert a.x == a.y - p.x = p.y + 1 - assert p.x == 3 - assert p.y == 2 - assert p.x != p.y + a.x = a.y + 1 + assert a.x == 3 + assert a.y == 2 + assert a.x != a.y - p.y = p.x - assert p.x == 3 - assert p.y == 3 - assert p.x == p.y + a.y += 1 + assert a.x == 3 + assert a.y == 3 + assert a.x == a.y - delete(p) + b := new(Point) + assert b.x == 0 + assert b.y == 0 + + b.x = -3 + b.y = -3 + assert b.x == -3 + assert b.y == -3 + + c := new(Point) + assert c.x == 0 + assert c.y == 0 + + c.x = a.x + b.x + c.y = a.y + b.y + assert c.x == a.x + b.x + assert c.y == a.y + b.y + assert c.x == 0 + assert c.y == 0 + + delete(a) + delete(b) + delete(c) } \ No newline at end of file From 32d81ce98d2cb4b40b072bdb09cc242ddab8a644 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 1 Mar 2025 12:48:34 +0100 Subject: [PATCH 0859/1012] Fixed server example for Mac --- lib/net/net_mac.q | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/net/net_mac.q b/lib/net/net_mac.q index ce2de78..9dd1e23 100644 --- a/lib/net/net_mac.q +++ b/lib/net/net_mac.q @@ -1,6 +1,6 @@ import sys -bind(socket int, port int) -> int { +bind(socket int, port uint16) -> int { addr := new(sys.sockaddr_in_bsd) addr.sin_family = 2 addr.sin_port = htons(port) From 8ff6faa31051b964bc7313a2693197ec2c850897 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 1 Mar 2025 12:48:34 +0100 Subject: [PATCH 0860/1012] Fixed server example for Mac --- lib/net/net_mac.q | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/net/net_mac.q b/lib/net/net_mac.q index ce2de78..9dd1e23 100644 --- a/lib/net/net_mac.q +++ b/lib/net/net_mac.q @@ -1,6 +1,6 @@ import sys -bind(socket int, port int) -> int { +bind(socket int, port uint16) -> int { addr := new(sys.sockaddr_in_bsd) addr.sin_family = 2 addr.sin_port = htons(port) From d185bd9cc05850a0b951f9dae98c0f1360abd6d0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 1 Mar 2025 18:38:00 +0100 Subject: [PATCH 0861/1012] Switched to pointer receivers for values --- src/core/CompileAssign.go | 4 ++-- src/core/CompileAssignDivision.go | 4 ++-- src/core/CompileCondition.go | 4 ++-- src/core/CompileFor.go | 4 ++-- src/core/CompileLen.go | 4 ++-- src/core/Evaluate.go | 4 ++-- src/core/EvaluateArray.go | 16 ++++++++-------- src/core/EvaluateCall.go | 6 +++--- src/core/EvaluateDot.go | 6 +++--- src/core/EvaluateToken.go | 12 ++++++------ src/core/ValueToMemory.go | 8 ++++---- src/core/ValueToRegister.go | 8 ++++---- src/eval/Label.go | 4 ++-- src/eval/Memory.go | 4 ++-- src/eval/Number.go | 4 ++-- src/eval/Register.go | 18 ++++++++++++++++-- src/eval/Value.go | 14 -------------- 17 files changed, 62 insertions(+), 62 deletions(-) diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 5d2263b..25b3c0a 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -36,9 +36,9 @@ func (f *Function) CompileAssign(node *ast.Assign) error { operation := node.Expression.Token switch leftValue := leftValue.(type) { - case eval.Register: + case *eval.Register: f.Execute(operation, leftValue.Register, right) - case eval.Memory: + case *eval.Memory: if operation.Kind == token.Assign { rightValue, err := f.Evaluate(right) diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index 1569ae1..c75026a 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -75,11 +75,11 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { divisor := division.Children[1] switch dividend := dividend.(type) { - case eval.Number: + case *eval.Number: f.SaveRegister(x86.RAX) f.RegisterNumber(asm.MOVE, x86.RAX, dividend.Number) err = f.Execute(division.Token, x86.RAX, divisor) - case eval.Register: + case *eval.Register: if dividend.Register != quotientVariable.Value.Register && dividend.IsAlive() { tmp := f.NewRegister() f.RegisterRegister(asm.MOVE, tmp, dividend.Register) diff --git a/src/core/CompileCondition.go b/src/core/CompileCondition.go index 983c275..3e14d57 100644 --- a/src/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -80,11 +80,11 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab } switch value := value.(type) { - case eval.Number: + case *eval.Number: if value.Number == 0 { f.Jump(asm.JUMP, failLabel) } - case eval.Register: + case *eval.Register: f.RegisterNumber(asm.COMPARE, value.Register, 0) f.Jump(asm.JE, failLabel) default: diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index a07702b..973c6f6 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -75,10 +75,10 @@ func (f *Function) CompileFor(loop *ast.For) error { } switch value := value.(type) { - case eval.Number: + case *eval.Number: f.AddLabel(label) f.RegisterNumber(asm.COMPARE, counter, value.Number) - case eval.Register: + case *eval.Register: if value.IsAlive() { tmp := f.NewRegister() f.RegisterRegister(asm.MOVE, tmp, value.Register) diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index bf44e1a..64f3cf5 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -35,9 +35,9 @@ func (f *Function) CompileLen(root *expression.Expression) error { f.SaveRegister(output) switch value := value.(type) { - case eval.Register: + case *eval.Register: memory.Base = value.Register - case eval.Label: + case *eval.Label: f.RegisterLabel(asm.MOVE, output, value.Label) memory.Base = output default: diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index e13f741..112da15 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -10,7 +10,7 @@ import ( // Evaluate evaluates an expression and returns a value. func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { if expr.IsFolded { - value := eval.Number{ + value := &eval.Number{ Typ: types.AnyInt, Number: expr.Value, } @@ -36,7 +36,7 @@ func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { tmp := f.NewRegister() typ, err := f.ExpressionToRegister(expr, tmp) - value := eval.Register{ + value := &eval.Register{ Typ: typ, Register: tmp, } diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go index 5844015..39cb01a 100644 --- a/src/core/EvaluateArray.go +++ b/src/core/EvaluateArray.go @@ -12,12 +12,12 @@ import ( ) // EvaluateArray evaluates an array access. -func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Memory, error) { +func (f *Function) EvaluateArray(expr *expression.Expression) (*eval.Memory, error) { name := expr.Children[0].Token.Text(f.File.Bytes) base := f.VariableByName(name) if base == nil { - return eval.Memory{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) } defer f.UseVariable(base) @@ -33,17 +33,17 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Memory, erro index, err := f.Evaluate(indexExpr) if err != nil { - return eval.Memory{}, err + return nil, err } if !types.Is(index.Type(), types.AnyInt) { - return eval.Memory{}, errors.New(&errors.TypeMismatch{Encountered: index.Type().Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) + return nil, errors.New(&errors.TypeMismatch{Encountered: index.Type().Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) } switch index := index.(type) { - case eval.Number: + case *eval.Number: memory.Offset = int8(index.Number) - case eval.Register: + case *eval.Register: memory.OffsetRegister = index.Register default: panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, index)) @@ -52,7 +52,7 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Memory, erro array, isArray := base.Value.Typ.(*types.Array) if isArray { - value := eval.Memory{ + value := &eval.Memory{ Typ: array.Of, Memory: memory, } @@ -63,7 +63,7 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Memory, erro pointer, isPointer := base.Value.Typ.(*types.Pointer) if isPointer { - value := eval.Memory{ + value := &eval.Memory{ Typ: pointer.To, Memory: memory, } diff --git a/src/core/EvaluateCall.go b/src/core/EvaluateCall.go index 3f338b1..6defe4f 100644 --- a/src/core/EvaluateCall.go +++ b/src/core/EvaluateCall.go @@ -6,14 +6,14 @@ import ( ) // EvaluateCall evaluates a function call. -func (f *Function) EvaluateCall(expr *expression.Expression) (eval.Register, error) { +func (f *Function) EvaluateCall(expr *expression.Expression) (*eval.Register, error) { typ, err := f.CompileCall(expr) if err != nil { - return eval.Register{}, err + return nil, err } - value := eval.Register{Register: f.CPU.Output[0]} + value := &eval.Register{Register: f.CPU.Output[0]} if len(typ) > 0 { value.Typ = typ[0] diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go index ca73d88..944ea02 100644 --- a/src/core/EvaluateDot.go +++ b/src/core/EvaluateDot.go @@ -29,7 +29,7 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) return nil, errors.New(&errors.UnknownStructField{StructName: structure.Name(), FieldName: rightText}, f.File, right.Token.Position) } - value := eval.Memory{ + value := &eval.Memory{ Typ: field.Type, Memory: asm.Memory{ Base: variable.Value.Register, @@ -51,7 +51,7 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) return nil, err } - value := eval.Number{ + value := &eval.Number{ Typ: types.AnyInt, Number: number, } @@ -65,7 +65,7 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) if exists { f.File.Imports[leftText].Used = true - value := eval.Label{ + value := &eval.Label{ Typ: types.AnyPointer, Label: function.UniqueName, } diff --git a/src/core/EvaluateToken.go b/src/core/EvaluateToken.go index 3d5d18f..f2e7c9d 100644 --- a/src/core/EvaluateToken.go +++ b/src/core/EvaluateToken.go @@ -16,7 +16,7 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { name := t.Text(f.File.Bytes) if name == "true" { - value := eval.Number{ + value := &eval.Number{ Typ: types.Bool, Number: 1, } @@ -25,7 +25,7 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { } if name == "false" { - value := eval.Number{ + value := &eval.Number{ Typ: types.Bool, Number: 0, } @@ -37,11 +37,11 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { if variable != nil { f.UseVariable(variable) - return variable.Value, nil + return &variable.Value, nil } if function != nil { - value := eval.Label{ + value := &eval.Label{ Typ: types.AnyPointer, Label: function.UniqueName, } @@ -58,7 +58,7 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { return nil, err } - value := eval.Number{ + value := &eval.Number{ Typ: types.AnyInt, Number: number, } @@ -74,7 +74,7 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { copy(slice[8:], data) label := f.AddBytes(slice) - value := eval.Label{ + value := &eval.Label{ Typ: types.String, Label: label, } diff --git a/src/core/ValueToMemory.go b/src/core/ValueToMemory.go index 07c30e3..36831a9 100644 --- a/src/core/ValueToMemory.go +++ b/src/core/ValueToMemory.go @@ -8,16 +8,16 @@ import ( // ValueToMemory moves a value into a memory region. func (f *Function) ValueToMemory(value eval.Value, memory asm.Memory) { switch value := value.(type) { - case eval.Number: + case *eval.Number: f.MemoryNumber(asm.STORE, memory, value.Number) - case eval.Register: + case *eval.Register: f.MemoryRegister(asm.STORE, memory, value.Register) - case eval.Memory: + case *eval.Memory: tmp := f.NewRegister() f.MemoryRegister(asm.LOAD, value.Memory, tmp) f.MemoryRegister(asm.STORE, memory, tmp) f.FreeRegister(tmp) - case eval.Label: + case *eval.Label: f.MemoryLabel(asm.STORE, memory, value.Label) } } diff --git a/src/core/ValueToRegister.go b/src/core/ValueToRegister.go index 51d0656..811fc34 100644 --- a/src/core/ValueToRegister.go +++ b/src/core/ValueToRegister.go @@ -9,13 +9,13 @@ import ( // ValueToRegister moves a value into a register. func (f *Function) ValueToRegister(value eval.Value, register cpu.Register) { switch value := value.(type) { - case eval.Number: + case *eval.Number: f.RegisterNumber(asm.MOVE, register, value.Number) - case eval.Register: + case *eval.Register: f.RegisterRegister(asm.MOVE, register, value.Register) - case eval.Memory: + case *eval.Memory: f.MemoryRegister(asm.LOAD, value.Memory, register) - case eval.Label: + case *eval.Label: f.RegisterLabel(asm.MOVE, register, value.Label) } } diff --git a/src/eval/Label.go b/src/eval/Label.go index a74afe0..e88dbd3 100644 --- a/src/eval/Label.go +++ b/src/eval/Label.go @@ -8,10 +8,10 @@ type Label struct { Label string } -func (v Label) String() string { +func (v *Label) String() string { return "Label" } -func (v Label) Type() types.Type { +func (v *Label) Type() types.Type { return v.Typ } diff --git a/src/eval/Memory.go b/src/eval/Memory.go index 0afd09e..4b53711 100644 --- a/src/eval/Memory.go +++ b/src/eval/Memory.go @@ -11,10 +11,10 @@ type Memory struct { Memory asm.Memory } -func (v Memory) String() string { +func (v *Memory) String() string { return "Memory" } -func (v Memory) Type() types.Type { +func (v *Memory) Type() types.Type { return v.Typ } diff --git a/src/eval/Number.go b/src/eval/Number.go index e9dc2d7..e525a9e 100644 --- a/src/eval/Number.go +++ b/src/eval/Number.go @@ -8,10 +8,10 @@ type Number struct { Number int } -func (v Number) String() string { +func (v *Number) String() string { return "Number" } -func (v Number) Type() types.Type { +func (v *Number) Type() types.Type { return v.Typ } diff --git a/src/eval/Register.go b/src/eval/Register.go index 088593f..b31547e 100644 --- a/src/eval/Register.go +++ b/src/eval/Register.go @@ -12,10 +12,24 @@ type Register struct { Register cpu.Register } -func (v Register) String() string { +func (v *Register) String() string { return "Register" } -func (v Register) Type() types.Type { +func (v *Register) Type() types.Type { return v.Typ } + +// IsAlive returns true if the register value is still alive. +func (v *Register) IsAlive() bool { + return v.Alive > 0 +} + +// Use reduces the lifetime counter by one. +func (v *Register) Use() { + if v.Alive == 0 { + panic("incorrect number of value use calls") + } + + v.Alive-- +} diff --git a/src/eval/Value.go b/src/eval/Value.go index 681cc18..33b6db6 100644 --- a/src/eval/Value.go +++ b/src/eval/Value.go @@ -9,17 +9,3 @@ type Value interface { String() string Type() types.Type } - -// IsAlive returns true if the register value is still alive. -func (v *Register) IsAlive() bool { - return v.Alive > 0 -} - -// Use reduces the lifetime counter by one. -func (v *Register) Use() { - if v.Alive == 0 { - panic("incorrect number of value use calls") - } - - v.Alive-- -} From 4428b09de2dc897a967de758cce602450887da09 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 1 Mar 2025 18:38:00 +0100 Subject: [PATCH 0862/1012] Switched to pointer receivers for values --- src/core/CompileAssign.go | 4 ++-- src/core/CompileAssignDivision.go | 4 ++-- src/core/CompileCondition.go | 4 ++-- src/core/CompileFor.go | 4 ++-- src/core/CompileLen.go | 4 ++-- src/core/Evaluate.go | 4 ++-- src/core/EvaluateArray.go | 16 ++++++++-------- src/core/EvaluateCall.go | 6 +++--- src/core/EvaluateDot.go | 6 +++--- src/core/EvaluateToken.go | 12 ++++++------ src/core/ValueToMemory.go | 8 ++++---- src/core/ValueToRegister.go | 8 ++++---- src/eval/Label.go | 4 ++-- src/eval/Memory.go | 4 ++-- src/eval/Number.go | 4 ++-- src/eval/Register.go | 18 ++++++++++++++++-- src/eval/Value.go | 14 -------------- 17 files changed, 62 insertions(+), 62 deletions(-) diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 5d2263b..25b3c0a 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -36,9 +36,9 @@ func (f *Function) CompileAssign(node *ast.Assign) error { operation := node.Expression.Token switch leftValue := leftValue.(type) { - case eval.Register: + case *eval.Register: f.Execute(operation, leftValue.Register, right) - case eval.Memory: + case *eval.Memory: if operation.Kind == token.Assign { rightValue, err := f.Evaluate(right) diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index 1569ae1..c75026a 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -75,11 +75,11 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { divisor := division.Children[1] switch dividend := dividend.(type) { - case eval.Number: + case *eval.Number: f.SaveRegister(x86.RAX) f.RegisterNumber(asm.MOVE, x86.RAX, dividend.Number) err = f.Execute(division.Token, x86.RAX, divisor) - case eval.Register: + case *eval.Register: if dividend.Register != quotientVariable.Value.Register && dividend.IsAlive() { tmp := f.NewRegister() f.RegisterRegister(asm.MOVE, tmp, dividend.Register) diff --git a/src/core/CompileCondition.go b/src/core/CompileCondition.go index 983c275..3e14d57 100644 --- a/src/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -80,11 +80,11 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab } switch value := value.(type) { - case eval.Number: + case *eval.Number: if value.Number == 0 { f.Jump(asm.JUMP, failLabel) } - case eval.Register: + case *eval.Register: f.RegisterNumber(asm.COMPARE, value.Register, 0) f.Jump(asm.JE, failLabel) default: diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index a07702b..973c6f6 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -75,10 +75,10 @@ func (f *Function) CompileFor(loop *ast.For) error { } switch value := value.(type) { - case eval.Number: + case *eval.Number: f.AddLabel(label) f.RegisterNumber(asm.COMPARE, counter, value.Number) - case eval.Register: + case *eval.Register: if value.IsAlive() { tmp := f.NewRegister() f.RegisterRegister(asm.MOVE, tmp, value.Register) diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index bf44e1a..64f3cf5 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -35,9 +35,9 @@ func (f *Function) CompileLen(root *expression.Expression) error { f.SaveRegister(output) switch value := value.(type) { - case eval.Register: + case *eval.Register: memory.Base = value.Register - case eval.Label: + case *eval.Label: f.RegisterLabel(asm.MOVE, output, value.Label) memory.Base = output default: diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index e13f741..112da15 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -10,7 +10,7 @@ import ( // Evaluate evaluates an expression and returns a value. func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { if expr.IsFolded { - value := eval.Number{ + value := &eval.Number{ Typ: types.AnyInt, Number: expr.Value, } @@ -36,7 +36,7 @@ func (f *Function) Evaluate(expr *expression.Expression) (eval.Value, error) { tmp := f.NewRegister() typ, err := f.ExpressionToRegister(expr, tmp) - value := eval.Register{ + value := &eval.Register{ Typ: typ, Register: tmp, } diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go index 5844015..39cb01a 100644 --- a/src/core/EvaluateArray.go +++ b/src/core/EvaluateArray.go @@ -12,12 +12,12 @@ import ( ) // EvaluateArray evaluates an array access. -func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Memory, error) { +func (f *Function) EvaluateArray(expr *expression.Expression) (*eval.Memory, error) { name := expr.Children[0].Token.Text(f.File.Bytes) base := f.VariableByName(name) if base == nil { - return eval.Memory{}, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) } defer f.UseVariable(base) @@ -33,17 +33,17 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Memory, erro index, err := f.Evaluate(indexExpr) if err != nil { - return eval.Memory{}, err + return nil, err } if !types.Is(index.Type(), types.AnyInt) { - return eval.Memory{}, errors.New(&errors.TypeMismatch{Encountered: index.Type().Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) + return nil, errors.New(&errors.TypeMismatch{Encountered: index.Type().Name(), Expected: types.AnyInt.Name()}, f.File, indexExpr.Token.Position) } switch index := index.(type) { - case eval.Number: + case *eval.Number: memory.Offset = int8(index.Number) - case eval.Register: + case *eval.Register: memory.OffsetRegister = index.Register default: panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, index)) @@ -52,7 +52,7 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Memory, erro array, isArray := base.Value.Typ.(*types.Array) if isArray { - value := eval.Memory{ + value := &eval.Memory{ Typ: array.Of, Memory: memory, } @@ -63,7 +63,7 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (eval.Memory, erro pointer, isPointer := base.Value.Typ.(*types.Pointer) if isPointer { - value := eval.Memory{ + value := &eval.Memory{ Typ: pointer.To, Memory: memory, } diff --git a/src/core/EvaluateCall.go b/src/core/EvaluateCall.go index 3f338b1..6defe4f 100644 --- a/src/core/EvaluateCall.go +++ b/src/core/EvaluateCall.go @@ -6,14 +6,14 @@ import ( ) // EvaluateCall evaluates a function call. -func (f *Function) EvaluateCall(expr *expression.Expression) (eval.Register, error) { +func (f *Function) EvaluateCall(expr *expression.Expression) (*eval.Register, error) { typ, err := f.CompileCall(expr) if err != nil { - return eval.Register{}, err + return nil, err } - value := eval.Register{Register: f.CPU.Output[0]} + value := &eval.Register{Register: f.CPU.Output[0]} if len(typ) > 0 { value.Typ = typ[0] diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go index ca73d88..944ea02 100644 --- a/src/core/EvaluateDot.go +++ b/src/core/EvaluateDot.go @@ -29,7 +29,7 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) return nil, errors.New(&errors.UnknownStructField{StructName: structure.Name(), FieldName: rightText}, f.File, right.Token.Position) } - value := eval.Memory{ + value := &eval.Memory{ Typ: field.Type, Memory: asm.Memory{ Base: variable.Value.Register, @@ -51,7 +51,7 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) return nil, err } - value := eval.Number{ + value := &eval.Number{ Typ: types.AnyInt, Number: number, } @@ -65,7 +65,7 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) if exists { f.File.Imports[leftText].Used = true - value := eval.Label{ + value := &eval.Label{ Typ: types.AnyPointer, Label: function.UniqueName, } diff --git a/src/core/EvaluateToken.go b/src/core/EvaluateToken.go index 3d5d18f..f2e7c9d 100644 --- a/src/core/EvaluateToken.go +++ b/src/core/EvaluateToken.go @@ -16,7 +16,7 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { name := t.Text(f.File.Bytes) if name == "true" { - value := eval.Number{ + value := &eval.Number{ Typ: types.Bool, Number: 1, } @@ -25,7 +25,7 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { } if name == "false" { - value := eval.Number{ + value := &eval.Number{ Typ: types.Bool, Number: 0, } @@ -37,11 +37,11 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { if variable != nil { f.UseVariable(variable) - return variable.Value, nil + return &variable.Value, nil } if function != nil { - value := eval.Label{ + value := &eval.Label{ Typ: types.AnyPointer, Label: function.UniqueName, } @@ -58,7 +58,7 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { return nil, err } - value := eval.Number{ + value := &eval.Number{ Typ: types.AnyInt, Number: number, } @@ -74,7 +74,7 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { copy(slice[8:], data) label := f.AddBytes(slice) - value := eval.Label{ + value := &eval.Label{ Typ: types.String, Label: label, } diff --git a/src/core/ValueToMemory.go b/src/core/ValueToMemory.go index 07c30e3..36831a9 100644 --- a/src/core/ValueToMemory.go +++ b/src/core/ValueToMemory.go @@ -8,16 +8,16 @@ import ( // ValueToMemory moves a value into a memory region. func (f *Function) ValueToMemory(value eval.Value, memory asm.Memory) { switch value := value.(type) { - case eval.Number: + case *eval.Number: f.MemoryNumber(asm.STORE, memory, value.Number) - case eval.Register: + case *eval.Register: f.MemoryRegister(asm.STORE, memory, value.Register) - case eval.Memory: + case *eval.Memory: tmp := f.NewRegister() f.MemoryRegister(asm.LOAD, value.Memory, tmp) f.MemoryRegister(asm.STORE, memory, tmp) f.FreeRegister(tmp) - case eval.Label: + case *eval.Label: f.MemoryLabel(asm.STORE, memory, value.Label) } } diff --git a/src/core/ValueToRegister.go b/src/core/ValueToRegister.go index 51d0656..811fc34 100644 --- a/src/core/ValueToRegister.go +++ b/src/core/ValueToRegister.go @@ -9,13 +9,13 @@ import ( // ValueToRegister moves a value into a register. func (f *Function) ValueToRegister(value eval.Value, register cpu.Register) { switch value := value.(type) { - case eval.Number: + case *eval.Number: f.RegisterNumber(asm.MOVE, register, value.Number) - case eval.Register: + case *eval.Register: f.RegisterRegister(asm.MOVE, register, value.Register) - case eval.Memory: + case *eval.Memory: f.MemoryRegister(asm.LOAD, value.Memory, register) - case eval.Label: + case *eval.Label: f.RegisterLabel(asm.MOVE, register, value.Label) } } diff --git a/src/eval/Label.go b/src/eval/Label.go index a74afe0..e88dbd3 100644 --- a/src/eval/Label.go +++ b/src/eval/Label.go @@ -8,10 +8,10 @@ type Label struct { Label string } -func (v Label) String() string { +func (v *Label) String() string { return "Label" } -func (v Label) Type() types.Type { +func (v *Label) Type() types.Type { return v.Typ } diff --git a/src/eval/Memory.go b/src/eval/Memory.go index 0afd09e..4b53711 100644 --- a/src/eval/Memory.go +++ b/src/eval/Memory.go @@ -11,10 +11,10 @@ type Memory struct { Memory asm.Memory } -func (v Memory) String() string { +func (v *Memory) String() string { return "Memory" } -func (v Memory) Type() types.Type { +func (v *Memory) Type() types.Type { return v.Typ } diff --git a/src/eval/Number.go b/src/eval/Number.go index e9dc2d7..e525a9e 100644 --- a/src/eval/Number.go +++ b/src/eval/Number.go @@ -8,10 +8,10 @@ type Number struct { Number int } -func (v Number) String() string { +func (v *Number) String() string { return "Number" } -func (v Number) Type() types.Type { +func (v *Number) Type() types.Type { return v.Typ } diff --git a/src/eval/Register.go b/src/eval/Register.go index 088593f..b31547e 100644 --- a/src/eval/Register.go +++ b/src/eval/Register.go @@ -12,10 +12,24 @@ type Register struct { Register cpu.Register } -func (v Register) String() string { +func (v *Register) String() string { return "Register" } -func (v Register) Type() types.Type { +func (v *Register) Type() types.Type { return v.Typ } + +// IsAlive returns true if the register value is still alive. +func (v *Register) IsAlive() bool { + return v.Alive > 0 +} + +// Use reduces the lifetime counter by one. +func (v *Register) Use() { + if v.Alive == 0 { + panic("incorrect number of value use calls") + } + + v.Alive-- +} diff --git a/src/eval/Value.go b/src/eval/Value.go index 681cc18..33b6db6 100644 --- a/src/eval/Value.go +++ b/src/eval/Value.go @@ -9,17 +9,3 @@ type Value interface { String() string Type() types.Type } - -// IsAlive returns true if the register value is still alive. -func (v *Register) IsAlive() bool { - return v.Alive > 0 -} - -// Use reduces the lifetime counter by one. -func (v *Register) Use() { - if v.Alive == 0 { - panic("incorrect number of value use calls") - } - - v.Alive-- -} From 0483176e56872b614127a415ea57988c9869bf6b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 1 Mar 2025 23:04:37 +0100 Subject: [PATCH 0863/1012] Fixed missing values of casts --- src/core/CompileCall.go | 11 -------- src/core/EvaluateCall.go | 46 +++++++++++++++++++++++++++++++- src/core/ExpressionToRegister.go | 8 +++--- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index e9c94b2..3d4ad40 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -52,17 +52,6 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error fn, exists = f.All.Functions[pkg+"."+name] if !exists { - typ := types.ByName(name, f.Package, f.All.Structs) - - if typ != nil { - if len(root.Children) != 2 { - return nil, errors.New(&errors.ParameterCountMismatch{Function: name, Count: len(root.Children), ExpectedCount: 1}, f.File, nameNode.Token.End()) - } - - _, err := f.ExpressionToRegister(root.Children[1], f.CPU.Output[0]) - return []types.Type{typ}, err - } - return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) } diff --git a/src/core/EvaluateCall.go b/src/core/EvaluateCall.go index 6defe4f..ad092a0 100644 --- a/src/core/EvaluateCall.go +++ b/src/core/EvaluateCall.go @@ -1,12 +1,56 @@ package core import ( + "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // EvaluateCall evaluates a function call. -func (f *Function) EvaluateCall(expr *expression.Expression) (*eval.Register, error) { +func (f *Function) EvaluateCall(expr *expression.Expression) (eval.Value, error) { + if expr.Children[0].Token.Kind == token.Identifier { + nameNode := expr.Children[0] + name := nameNode.String(f.File.Bytes) + typ := types.ByName(name, f.Package, f.All.Structs) + + if typ != nil { + if len(expr.Children) != 2 { + return nil, errors.New(&errors.ParameterCountMismatch{Function: name, Count: len(expr.Children), ExpectedCount: 1}, f.File, nameNode.Token.End()) + } + + value, err := f.Evaluate(expr.Children[1]) + + if err != nil { + return nil, err + } + + switch value := value.(type) { + case *eval.Register: + if value.IsAlive() { + value = &eval.Register{ + Typ: typ, + Register: value.Register, + Alive: value.Alive, + } + + return value, nil + } + + value.Typ = typ + case *eval.Number: + value.Typ = typ + case *eval.Memory: + value.Typ = typ + case *eval.Label: + value.Typ = typ + } + + return value, nil + } + } + typ, err := f.CompileCall(expr) if err != nil { diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index bd40f05..1efbfa8 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -28,8 +28,8 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } switch node.Token.Kind { - case token.Array: - value, err := f.EvaluateArray(node) + case token.Call: + value, err := f.EvaluateCall(node) if err != nil { return nil, err @@ -48,8 +48,8 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp f.ValueToRegister(value, register) return value.Type(), nil - case token.Call: - value, err := f.EvaluateCall(node) + case token.Array: + value, err := f.EvaluateArray(node) if err != nil { return nil, err From d7f30d83198d543feafb4d3f8759af5fac957c5c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 1 Mar 2025 23:04:37 +0100 Subject: [PATCH 0864/1012] Fixed missing values of casts --- src/core/CompileCall.go | 11 -------- src/core/EvaluateCall.go | 46 +++++++++++++++++++++++++++++++- src/core/ExpressionToRegister.go | 8 +++--- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index e9c94b2..3d4ad40 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -52,17 +52,6 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error fn, exists = f.All.Functions[pkg+"."+name] if !exists { - typ := types.ByName(name, f.Package, f.All.Structs) - - if typ != nil { - if len(root.Children) != 2 { - return nil, errors.New(&errors.ParameterCountMismatch{Function: name, Count: len(root.Children), ExpectedCount: 1}, f.File, nameNode.Token.End()) - } - - _, err := f.ExpressionToRegister(root.Children[1], f.CPU.Output[0]) - return []types.Type{typ}, err - } - return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) } diff --git a/src/core/EvaluateCall.go b/src/core/EvaluateCall.go index 6defe4f..ad092a0 100644 --- a/src/core/EvaluateCall.go +++ b/src/core/EvaluateCall.go @@ -1,12 +1,56 @@ package core import ( + "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // EvaluateCall evaluates a function call. -func (f *Function) EvaluateCall(expr *expression.Expression) (*eval.Register, error) { +func (f *Function) EvaluateCall(expr *expression.Expression) (eval.Value, error) { + if expr.Children[0].Token.Kind == token.Identifier { + nameNode := expr.Children[0] + name := nameNode.String(f.File.Bytes) + typ := types.ByName(name, f.Package, f.All.Structs) + + if typ != nil { + if len(expr.Children) != 2 { + return nil, errors.New(&errors.ParameterCountMismatch{Function: name, Count: len(expr.Children), ExpectedCount: 1}, f.File, nameNode.Token.End()) + } + + value, err := f.Evaluate(expr.Children[1]) + + if err != nil { + return nil, err + } + + switch value := value.(type) { + case *eval.Register: + if value.IsAlive() { + value = &eval.Register{ + Typ: typ, + Register: value.Register, + Alive: value.Alive, + } + + return value, nil + } + + value.Typ = typ + case *eval.Number: + value.Typ = typ + case *eval.Memory: + value.Typ = typ + case *eval.Label: + value.Typ = typ + } + + return value, nil + } + } + typ, err := f.CompileCall(expr) if err != nil { diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index bd40f05..1efbfa8 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -28,8 +28,8 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp } switch node.Token.Kind { - case token.Array: - value, err := f.EvaluateArray(node) + case token.Call: + value, err := f.EvaluateCall(node) if err != nil { return nil, err @@ -48,8 +48,8 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp f.ValueToRegister(value, register) return value.Type(), nil - case token.Call: - value, err := f.EvaluateCall(node) + case token.Array: + value, err := f.EvaluateArray(node) if err != nil { return nil, err From c777d73bbbb592d7d7b4035b1a77bbe9439774e9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 2 Mar 2025 17:53:18 +0100 Subject: [PATCH 0865/1012] Implemented register calls --- src/asm/CanSkip.go | 4 +-- src/asm/Instructions.go | 10 -------- src/asmc/call.go | 41 +++++++++++++++++-------------- src/compiler/eachFunction.go | 9 +++++-- src/core/CallSafe.go | 2 +- src/core/CompileCall.go | 10 +++++++- src/register/Call.go | 7 ------ src/register/Label.go | 8 ++++++ src/x86/Call.go | 23 +++++++++++++++-- src/x86/Call_test.go | 39 +++++++++++++++++++++++++++++ tests/errors/UnknownType.q | 5 ++++ tests/errors/UnknownType2.q | 5 ++++ tests/errors/UnusedVariable2.q | 3 +++ tests/errors_test.go | 3 +++ tests/programs/function-pointer.q | 6 +++++ tests/programs_test.go | 1 + 16 files changed, 133 insertions(+), 43 deletions(-) delete mode 100644 src/register/Call.go create mode 100644 src/register/Label.go create mode 100644 src/x86/Call_test.go create mode 100644 tests/errors/UnknownType.q create mode 100644 tests/errors/UnknownType2.q create mode 100644 tests/errors/UnusedVariable2.q create mode 100644 tests/programs/function-pointer.q diff --git a/src/asm/CanSkip.go b/src/asm/CanSkip.go index b0999e0..ab58c65 100644 --- a/src/asm/CanSkip.go +++ b/src/asm/CanSkip.go @@ -21,11 +21,11 @@ func (a *Assembler) CanSkip(mnemonic Mnemonic, left cpu.Register, right cpu.Regi return false } - if lastData.Destination == left && lastData.Source == right { + if lastData.Destination == right && lastData.Source == left { return true } - if lastData.Destination == right && lastData.Source == left { + if lastData.Destination == left && lastData.Source == right { return true } } diff --git a/src/asm/Instructions.go b/src/asm/Instructions.go index ab6a510..e203e3f 100644 --- a/src/asm/Instructions.go +++ b/src/asm/Instructions.go @@ -10,16 +10,6 @@ func (a *Assembler) Comment(text string) { }) } -// Call calls a function whose position is identified by a label. -func (a *Assembler) Call(name string) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: CALL, - Data: &Label{ - Name: name, - }, - }) -} - // DLLCall calls a function in a DLL file. func (a *Assembler) DLLCall(name string) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/asmc/call.go b/src/asmc/call.go index 6c78a09..e5d08a7 100644 --- a/src/asmc/call.go +++ b/src/asmc/call.go @@ -6,26 +6,31 @@ import ( ) func (c *compiler) call(x asm.Instruction) { - c.code = x86.Call(c.code, 0x00_00_00_00) - size := 4 - label := x.Data.(*asm.Label) + switch data := x.Data.(type) { + case *asm.Label: + c.code = x86.Call(c.code, 0x00_00_00_00) + size := 4 - pointer := &pointer{ - Position: Address(len(c.code) - size), - OpSize: 1, - Size: uint8(size), - } - - pointer.Resolve = func() Address { - destination, exists := c.codeLabels[label.Name] - - if !exists { - panic("unknown jump label") + pointer := &pointer{ + Position: Address(len(c.code) - size), + OpSize: 1, + Size: uint8(size), } - distance := destination - (pointer.Position + Address(pointer.Size)) - return distance - } + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[data.Name] - c.codePointers = append(c.codePointers, pointer) + if !exists { + panic("unknown jump label") + } + + distance := destination - (pointer.Position + Address(pointer.Size)) + return distance + } + + c.codePointers = append(c.codePointers, pointer) + + case *asm.Register: + c.code = x86.CallRegister(c.code, data.Register) + } } diff --git a/src/compiler/eachFunction.go b/src/compiler/eachFunction.go index de7fc63..f3d6215 100644 --- a/src/compiler/eachFunction.go +++ b/src/compiler/eachFunction.go @@ -16,8 +16,13 @@ func (r *Result) eachFunction(caller *core.Function, traversed map[*core.Functio continue } - name := x.Data.(*asm.Label).Name - callee, exists := r.Functions[name] + label, isLabel := x.Data.(*asm.Label) + + if !isLabel { + continue + } + + callee, exists := r.Functions[label.Name] if !exists { continue diff --git a/src/core/CallSafe.go b/src/core/CallSafe.go index 20f4676..924d209 100644 --- a/src/core/CallSafe.go +++ b/src/core/CallSafe.go @@ -19,7 +19,7 @@ func (f *Function) CallSafe(fn *Function, registers []cpu.Register) { } } - f.Call(fn.UniqueName) + f.Label(asm.CALL, fn.UniqueName) for _, register := range slices.Backward(f.CPU.General) { if f.RegisterIsUsed(register) { diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 3d4ad40..fec090a 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -1,6 +1,7 @@ package core import ( + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/types" @@ -45,13 +46,20 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error } if f.UniqueName == "core.init" && pkg == "main" && name == "main" { - f.Call("main.main") + f.Label(asm.CALL, "main.main") return nil, nil } fn, exists = f.All.Functions[pkg+"."+name] if !exists { + variable := f.VariableByName(name) + + if variable != nil { + f.Register(asm.CALL, variable.Value.Register) + return nil, nil + } + return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) } diff --git a/src/register/Call.go b/src/register/Call.go deleted file mode 100644 index 6008dbd..0000000 --- a/src/register/Call.go +++ /dev/null @@ -1,7 +0,0 @@ -package register - -func (f *Machine) Call(label string) { - f.Assembler.Call(label) - f.UseRegister(f.CPU.Output[0]) - f.postInstruction() -} diff --git a/src/register/Label.go b/src/register/Label.go new file mode 100644 index 0000000..4dcea1a --- /dev/null +++ b/src/register/Label.go @@ -0,0 +1,8 @@ +package register + +import "git.urbach.dev/cli/q/src/asm" + +func (f *Machine) Label(mnemonic asm.Mnemonic, label string) { + f.Assembler.Label(mnemonic, label) + f.postInstruction() +} diff --git a/src/x86/Call.go b/src/x86/Call.go index 812b484..b6c9194 100644 --- a/src/x86/Call.go +++ b/src/x86/Call.go @@ -1,5 +1,7 @@ package x86 +import "git.urbach.dev/cli/q/src/cpu" + // Call places the return address on the top of the stack and continues // program flow at the new address. // The address is relative to the next instruction. @@ -14,8 +16,25 @@ func Call(code []byte, address uint32) []byte { ) } -// CallAtAddress places the return address on the top of the stack and -// continues program flow at the address stored at the given memory address. +// Calls a function whose address is stored in the given register. +func CallRegister(code []byte, register cpu.Register) []byte { + if register > 0b111 { + return append( + code, + 0x41, + 0xFF, + 0xD0+byte(register&0b111), + ) + } + + return append( + code, + 0xFF, + 0xD0+byte(register), + ) +} + +// CallAtAddress calls a function at the address stored at the given memory address. // The memory address is relative to the next instruction. func CallAtAddress(code []byte, address uint32) []byte { return append( diff --git a/src/x86/Call_test.go b/src/x86/Call_test.go new file mode 100644 index 0000000..5aae357 --- /dev/null +++ b/src/x86/Call_test.go @@ -0,0 +1,39 @@ +package x86_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" +) + +func TestCallRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x86.RAX, []byte{0xFF, 0xD0}}, + {x86.RCX, []byte{0xFF, 0xD1}}, + {x86.RDX, []byte{0xFF, 0xD2}}, + {x86.RBX, []byte{0xFF, 0xD3}}, + {x86.RSP, []byte{0xFF, 0xD4}}, + {x86.RBP, []byte{0xFF, 0xD5}}, + {x86.RSI, []byte{0xFF, 0xD6}}, + {x86.RDI, []byte{0xFF, 0xD7}}, + {x86.R8, []byte{0x41, 0xFF, 0xD0}}, + {x86.R9, []byte{0x41, 0xFF, 0xD1}}, + {x86.R10, []byte{0x41, 0xFF, 0xD2}}, + {x86.R11, []byte{0x41, 0xFF, 0xD3}}, + {x86.R12, []byte{0x41, 0xFF, 0xD4}}, + {x86.R13, []byte{0x41, 0xFF, 0xD5}}, + {x86.R14, []byte{0x41, 0xFF, 0xD6}}, + {x86.R15, []byte{0x41, 0xFF, 0xD7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("call %s", pattern.Register) + code := x86.CallRegister(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/tests/errors/UnknownType.q b/tests/errors/UnknownType.q new file mode 100644 index 0000000..6b4f652 --- /dev/null +++ b/tests/errors/UnknownType.q @@ -0,0 +1,5 @@ +main() {} + +f(x unknown) -> int { + return x +} \ No newline at end of file diff --git a/tests/errors/UnknownType2.q b/tests/errors/UnknownType2.q new file mode 100644 index 0000000..260b358 --- /dev/null +++ b/tests/errors/UnknownType2.q @@ -0,0 +1,5 @@ +main() {} + +f(x int) -> unknown { + return x +} \ No newline at end of file diff --git a/tests/errors/UnusedVariable2.q b/tests/errors/UnusedVariable2.q new file mode 100644 index 0000000..d1d7272 --- /dev/null +++ b/tests/errors/UnusedVariable2.q @@ -0,0 +1,3 @@ +main() {} + +f(x int) {} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index e672684..95a5424 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -58,10 +58,13 @@ var errs = []struct { {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownPackage.q", &errors.UnknownPackage{Name: "sys"}}, + {"UnknownType.q", &errors.UnknownType{Name: "unknown"}}, + {"UnknownType2.q", &errors.UnknownType{Name: "unknown"}}, {"UnknownStructField.q", &errors.UnknownStructField{StructName: "A", FieldName: "x"}}, {"UntypedExpression.q", errors.UntypedExpression}, {"UnusedImport.q", &errors.UnusedImport{Package: "sys"}}, {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, + {"UnusedVariable2.q", &errors.UnusedVariable{Name: "x"}}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, } diff --git a/tests/programs/function-pointer.q b/tests/programs/function-pointer.q new file mode 100644 index 0000000..20f8c8a --- /dev/null +++ b/tests/programs/function-pointer.q @@ -0,0 +1,6 @@ +import core + +main() { + exit := core.exit + exit() +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 3979fc9..369c4af 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -68,6 +68,7 @@ var programs = []struct { {"struct", 0}, {"len", 0}, {"cast", 0}, + {"function-pointer", 0}, } func TestPrograms(t *testing.T) { From c3054369e323724192d486c8a2cd55231f046dca Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 2 Mar 2025 17:53:18 +0100 Subject: [PATCH 0866/1012] Implemented register calls --- src/asm/CanSkip.go | 4 +-- src/asm/Instructions.go | 10 -------- src/asmc/call.go | 41 +++++++++++++++++-------------- src/compiler/eachFunction.go | 9 +++++-- src/core/CallSafe.go | 2 +- src/core/CompileCall.go | 10 +++++++- src/register/Call.go | 7 ------ src/register/Label.go | 8 ++++++ src/x86/Call.go | 23 +++++++++++++++-- src/x86/Call_test.go | 39 +++++++++++++++++++++++++++++ tests/errors/UnknownType.q | 5 ++++ tests/errors/UnknownType2.q | 5 ++++ tests/errors/UnusedVariable2.q | 3 +++ tests/errors_test.go | 3 +++ tests/programs/function-pointer.q | 6 +++++ tests/programs_test.go | 1 + 16 files changed, 133 insertions(+), 43 deletions(-) delete mode 100644 src/register/Call.go create mode 100644 src/register/Label.go create mode 100644 src/x86/Call_test.go create mode 100644 tests/errors/UnknownType.q create mode 100644 tests/errors/UnknownType2.q create mode 100644 tests/errors/UnusedVariable2.q create mode 100644 tests/programs/function-pointer.q diff --git a/src/asm/CanSkip.go b/src/asm/CanSkip.go index b0999e0..ab58c65 100644 --- a/src/asm/CanSkip.go +++ b/src/asm/CanSkip.go @@ -21,11 +21,11 @@ func (a *Assembler) CanSkip(mnemonic Mnemonic, left cpu.Register, right cpu.Regi return false } - if lastData.Destination == left && lastData.Source == right { + if lastData.Destination == right && lastData.Source == left { return true } - if lastData.Destination == right && lastData.Source == left { + if lastData.Destination == left && lastData.Source == right { return true } } diff --git a/src/asm/Instructions.go b/src/asm/Instructions.go index ab6a510..e203e3f 100644 --- a/src/asm/Instructions.go +++ b/src/asm/Instructions.go @@ -10,16 +10,6 @@ func (a *Assembler) Comment(text string) { }) } -// Call calls a function whose position is identified by a label. -func (a *Assembler) Call(name string) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: CALL, - Data: &Label{ - Name: name, - }, - }) -} - // DLLCall calls a function in a DLL file. func (a *Assembler) DLLCall(name string) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/asmc/call.go b/src/asmc/call.go index 6c78a09..e5d08a7 100644 --- a/src/asmc/call.go +++ b/src/asmc/call.go @@ -6,26 +6,31 @@ import ( ) func (c *compiler) call(x asm.Instruction) { - c.code = x86.Call(c.code, 0x00_00_00_00) - size := 4 - label := x.Data.(*asm.Label) + switch data := x.Data.(type) { + case *asm.Label: + c.code = x86.Call(c.code, 0x00_00_00_00) + size := 4 - pointer := &pointer{ - Position: Address(len(c.code) - size), - OpSize: 1, - Size: uint8(size), - } - - pointer.Resolve = func() Address { - destination, exists := c.codeLabels[label.Name] - - if !exists { - panic("unknown jump label") + pointer := &pointer{ + Position: Address(len(c.code) - size), + OpSize: 1, + Size: uint8(size), } - distance := destination - (pointer.Position + Address(pointer.Size)) - return distance - } + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[data.Name] - c.codePointers = append(c.codePointers, pointer) + if !exists { + panic("unknown jump label") + } + + distance := destination - (pointer.Position + Address(pointer.Size)) + return distance + } + + c.codePointers = append(c.codePointers, pointer) + + case *asm.Register: + c.code = x86.CallRegister(c.code, data.Register) + } } diff --git a/src/compiler/eachFunction.go b/src/compiler/eachFunction.go index de7fc63..f3d6215 100644 --- a/src/compiler/eachFunction.go +++ b/src/compiler/eachFunction.go @@ -16,8 +16,13 @@ func (r *Result) eachFunction(caller *core.Function, traversed map[*core.Functio continue } - name := x.Data.(*asm.Label).Name - callee, exists := r.Functions[name] + label, isLabel := x.Data.(*asm.Label) + + if !isLabel { + continue + } + + callee, exists := r.Functions[label.Name] if !exists { continue diff --git a/src/core/CallSafe.go b/src/core/CallSafe.go index 20f4676..924d209 100644 --- a/src/core/CallSafe.go +++ b/src/core/CallSafe.go @@ -19,7 +19,7 @@ func (f *Function) CallSafe(fn *Function, registers []cpu.Register) { } } - f.Call(fn.UniqueName) + f.Label(asm.CALL, fn.UniqueName) for _, register := range slices.Backward(f.CPU.General) { if f.RegisterIsUsed(register) { diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 3d4ad40..fec090a 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -1,6 +1,7 @@ package core import ( + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/types" @@ -45,13 +46,20 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error } if f.UniqueName == "core.init" && pkg == "main" && name == "main" { - f.Call("main.main") + f.Label(asm.CALL, "main.main") return nil, nil } fn, exists = f.All.Functions[pkg+"."+name] if !exists { + variable := f.VariableByName(name) + + if variable != nil { + f.Register(asm.CALL, variable.Value.Register) + return nil, nil + } + return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) } diff --git a/src/register/Call.go b/src/register/Call.go deleted file mode 100644 index 6008dbd..0000000 --- a/src/register/Call.go +++ /dev/null @@ -1,7 +0,0 @@ -package register - -func (f *Machine) Call(label string) { - f.Assembler.Call(label) - f.UseRegister(f.CPU.Output[0]) - f.postInstruction() -} diff --git a/src/register/Label.go b/src/register/Label.go new file mode 100644 index 0000000..4dcea1a --- /dev/null +++ b/src/register/Label.go @@ -0,0 +1,8 @@ +package register + +import "git.urbach.dev/cli/q/src/asm" + +func (f *Machine) Label(mnemonic asm.Mnemonic, label string) { + f.Assembler.Label(mnemonic, label) + f.postInstruction() +} diff --git a/src/x86/Call.go b/src/x86/Call.go index 812b484..b6c9194 100644 --- a/src/x86/Call.go +++ b/src/x86/Call.go @@ -1,5 +1,7 @@ package x86 +import "git.urbach.dev/cli/q/src/cpu" + // Call places the return address on the top of the stack and continues // program flow at the new address. // The address is relative to the next instruction. @@ -14,8 +16,25 @@ func Call(code []byte, address uint32) []byte { ) } -// CallAtAddress places the return address on the top of the stack and -// continues program flow at the address stored at the given memory address. +// Calls a function whose address is stored in the given register. +func CallRegister(code []byte, register cpu.Register) []byte { + if register > 0b111 { + return append( + code, + 0x41, + 0xFF, + 0xD0+byte(register&0b111), + ) + } + + return append( + code, + 0xFF, + 0xD0+byte(register), + ) +} + +// CallAtAddress calls a function at the address stored at the given memory address. // The memory address is relative to the next instruction. func CallAtAddress(code []byte, address uint32) []byte { return append( diff --git a/src/x86/Call_test.go b/src/x86/Call_test.go new file mode 100644 index 0000000..5aae357 --- /dev/null +++ b/src/x86/Call_test.go @@ -0,0 +1,39 @@ +package x86_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" +) + +func TestCallRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x86.RAX, []byte{0xFF, 0xD0}}, + {x86.RCX, []byte{0xFF, 0xD1}}, + {x86.RDX, []byte{0xFF, 0xD2}}, + {x86.RBX, []byte{0xFF, 0xD3}}, + {x86.RSP, []byte{0xFF, 0xD4}}, + {x86.RBP, []byte{0xFF, 0xD5}}, + {x86.RSI, []byte{0xFF, 0xD6}}, + {x86.RDI, []byte{0xFF, 0xD7}}, + {x86.R8, []byte{0x41, 0xFF, 0xD0}}, + {x86.R9, []byte{0x41, 0xFF, 0xD1}}, + {x86.R10, []byte{0x41, 0xFF, 0xD2}}, + {x86.R11, []byte{0x41, 0xFF, 0xD3}}, + {x86.R12, []byte{0x41, 0xFF, 0xD4}}, + {x86.R13, []byte{0x41, 0xFF, 0xD5}}, + {x86.R14, []byte{0x41, 0xFF, 0xD6}}, + {x86.R15, []byte{0x41, 0xFF, 0xD7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("call %s", pattern.Register) + code := x86.CallRegister(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/tests/errors/UnknownType.q b/tests/errors/UnknownType.q new file mode 100644 index 0000000..6b4f652 --- /dev/null +++ b/tests/errors/UnknownType.q @@ -0,0 +1,5 @@ +main() {} + +f(x unknown) -> int { + return x +} \ No newline at end of file diff --git a/tests/errors/UnknownType2.q b/tests/errors/UnknownType2.q new file mode 100644 index 0000000..260b358 --- /dev/null +++ b/tests/errors/UnknownType2.q @@ -0,0 +1,5 @@ +main() {} + +f(x int) -> unknown { + return x +} \ No newline at end of file diff --git a/tests/errors/UnusedVariable2.q b/tests/errors/UnusedVariable2.q new file mode 100644 index 0000000..d1d7272 --- /dev/null +++ b/tests/errors/UnusedVariable2.q @@ -0,0 +1,3 @@ +main() {} + +f(x int) {} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index e672684..95a5424 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -58,10 +58,13 @@ var errs = []struct { {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownPackage.q", &errors.UnknownPackage{Name: "sys"}}, + {"UnknownType.q", &errors.UnknownType{Name: "unknown"}}, + {"UnknownType2.q", &errors.UnknownType{Name: "unknown"}}, {"UnknownStructField.q", &errors.UnknownStructField{StructName: "A", FieldName: "x"}}, {"UntypedExpression.q", errors.UntypedExpression}, {"UnusedImport.q", &errors.UnusedImport{Package: "sys"}}, {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, + {"UnusedVariable2.q", &errors.UnusedVariable{Name: "x"}}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, } diff --git a/tests/programs/function-pointer.q b/tests/programs/function-pointer.q new file mode 100644 index 0000000..20f8c8a --- /dev/null +++ b/tests/programs/function-pointer.q @@ -0,0 +1,6 @@ +import core + +main() { + exit := core.exit + exit() +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 3979fc9..369c4af 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -68,6 +68,7 @@ var programs = []struct { {"struct", 0}, {"len", 0}, {"cast", 0}, + {"function-pointer", 0}, } func TestPrograms(t *testing.T) { From 225d78e2d8176eff1c933e5d03bc095c43c42d6f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 2 Mar 2025 21:36:23 +0100 Subject: [PATCH 0867/1012] Simplified compilation of function calls --- src/compiler/Compile.go | 5 + src/core/CompileCall.go | 112 ++++++++---------- src/core/Environment.go | 1 + src/core/EvaluateDot.go | 25 ++-- src/errors/UnknownFunction.go | 18 --- tests/errors/MissingBlockStart2.q | 3 + tests/errors/UnknownFunction2.q | 3 - ...UnknownFunction.q => UnknownIdentifier4.q} | 0 tests/errors/UnknownIdentifier5.q | 3 + tests/errors_test.go | 5 +- tests/programs/function-pointer-field.q | 11 ++ tests/programs/loop-in-loop.q | 21 ++++ tests/programs/{loop-infinite.q => loop.q} | 0 tests/programs_test.go | 4 +- 14 files changed, 114 insertions(+), 97 deletions(-) delete mode 100644 src/errors/UnknownFunction.go create mode 100644 tests/errors/MissingBlockStart2.q delete mode 100644 tests/errors/UnknownFunction2.q rename tests/errors/{UnknownFunction.q => UnknownIdentifier4.q} (100%) create mode 100644 tests/errors/UnknownIdentifier5.q create mode 100644 tests/programs/function-pointer-field.q create mode 100644 tests/programs/loop-in-loop.q rename tests/programs/{loop-infinite.q => loop.q} (100%) diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index f62943c..ae3c437 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -11,6 +11,7 @@ import ( func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Struct, errs <-chan error) (Result, error) { all := core.Environment{ Files: make([]*fs.File, 0, 8), + Extern: make(map[string]struct{}, 0), Functions: make(map[string]*core.Function, 32), Structs: make(map[string]*types.Struct, 8), Constants: make(map[string]*core.Constant, 8), @@ -29,6 +30,10 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < function.All = &all all.Functions[function.UniqueName] = function + if function.IsExtern() { + all.Extern[function.Package] = struct{}{} + } + case structure, ok := <-structs: if !ok { structs = nil diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index fec090a..7e47f48 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -3,7 +3,9 @@ package core import ( "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" ) @@ -12,17 +14,8 @@ import ( // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error) { - var ( - pkg = f.Package - pkgNode *expression.Expression - name string - nameNode = root.Children[0] - fn *Function - exists bool - ) - - if nameNode.IsLeaf() { - name = nameNode.Token.Text(f.File.Bytes) + if root.Children[0].Token.Kind == token.Identifier { + name := root.Children[0].Token.Text(f.File.Bytes) switch name { case "len": @@ -37,63 +30,56 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error case "store": return nil, f.CompileMemoryStore(root) } - } else { - pkgNode = nameNode.Children[0] - nameNode = nameNode.Children[1] - - pkg = pkgNode.Token.Text(f.File.Bytes) - name = nameNode.Token.Text(f.File.Bytes) } - if f.UniqueName == "core.init" && pkg == "main" && name == "main" { - f.Label(asm.CALL, "main.main") - return nil, nil - } - - fn, exists = f.All.Functions[pkg+"."+name] - - if !exists { - variable := f.VariableByName(name) - - if variable != nil { - f.Register(asm.CALL, variable.Value.Register) - return nil, nil - } - - return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) - } - - if pkg != f.File.Package && !fn.IsExtern() { - if f.File.Imports == nil { - return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, pkgNode.Token.Position) - } - - imp, exists := f.File.Imports[pkg] - - if !exists { - return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, pkgNode.Token.Position) - } - - imp.Used = true - } - - parameters := root.Children[1:] - - if len(parameters) != len(fn.Input) { - return nil, errors.New(&errors.ParameterCountMismatch{Function: fn.Name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, nameNode.Token.End()) - } - - if fn.IsExtern() { - return f.CallExtern(fn, parameters) - } - - registers := f.CPU.Input[:len(parameters)] - err := f.ExpressionsToRegisters(parameters, registers, fn.Input, true) + value, err := f.Evaluate(root.Children[0]) if err != nil { return nil, err } - f.CallSafe(fn, registers) - return fn.OutputTypes, nil + parameters := root.Children[1:] + registers := f.CPU.Input[:len(parameters)] + + switch value := value.(type) { + case *eval.Label: + fn, exists := f.All.Functions[value.Label] + + if !exists { + if value.Label == "main.main" && f.UniqueName == "core.init" { + f.Label(asm.CALL, "main.main") + return nil, nil + } + + return nil, errors.New(&errors.UnknownIdentifier{Name: value.Label}, f.File, root.Children[0].Token.Position) + } + + if len(parameters) != len(fn.Input) { + return nil, errors.New(&errors.ParameterCountMismatch{Function: fn.Name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, root.Children[0].Token.End()) + } + + if fn.IsExtern() { + return f.CallExtern(fn, parameters) + } + + err := f.ExpressionsToRegisters(parameters, registers, fn.Input, true) + + if err != nil { + return nil, err + } + + f.CallSafe(fn, registers) + return fn.OutputTypes, nil + + case *eval.Register: + f.Register(asm.CALL, value.Register) + + case *eval.Memory: + tmp := f.NewRegister() + f.MemoryRegister(asm.LOAD, value.Memory, tmp) + f.Register(asm.CALL, tmp) + f.FreeRegister(tmp) + } + + return nil, nil } diff --git a/src/core/Environment.go b/src/core/Environment.go index e2a1354..c27828a 100644 --- a/src/core/Environment.go +++ b/src/core/Environment.go @@ -8,6 +8,7 @@ import ( // Environment holds information about the entire build. type Environment struct { Constants map[string]*Constant + Extern map[string]struct{} Functions map[string]*Function Structs map[string]*types.Struct Files []*fs.File diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go index 944ea02..69f078c 100644 --- a/src/core/EvaluateDot.go +++ b/src/core/EvaluateDot.go @@ -59,19 +59,24 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) return value, nil } - uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) - function, exists := f.All.Functions[uniqueName] + if leftText != "main" { + imp, exists := f.File.Imports[leftText] - if exists { - f.File.Imports[leftText].Used = true + if exists { + imp.Used = true + } else { + _, exists := f.All.Extern[leftText] - value := &eval.Label{ - Typ: types.AnyPointer, - Label: function.UniqueName, + if !exists { + return nil, errors.New(&errors.UnknownPackage{Name: leftText}, f.File, left.Token.Position) + } } - - return value, nil } - return nil, errors.New(&errors.UnknownIdentifier{Name: uniqueName}, f.File, left.Token.Position) + value := &eval.Label{ + Typ: types.AnyPointer, + Label: fmt.Sprintf("%s.%s", leftText, rightText), + } + + return value, nil } diff --git a/src/errors/UnknownFunction.go b/src/errors/UnknownFunction.go deleted file mode 100644 index a2b7e4e..0000000 --- a/src/errors/UnknownFunction.go +++ /dev/null @@ -1,18 +0,0 @@ -package errors - -import "fmt" - -// UnknownFunction represents unknown function errors. -type UnknownFunction struct { - Name string - CorrectName string -} - -// Error generates the string representation. -func (err *UnknownFunction) Error() string { - if err.CorrectName != "" { - return fmt.Sprintf("Unknown function '%s', did you mean '%s'?", err.Name, err.CorrectName) - } - - return fmt.Sprintf("Unknown function '%s'", err.Name) -} diff --git a/tests/errors/MissingBlockStart2.q b/tests/errors/MissingBlockStart2.q new file mode 100644 index 0000000..7e12b77 --- /dev/null +++ b/tests/errors/MissingBlockStart2.q @@ -0,0 +1,3 @@ +main() { + loop +} \ No newline at end of file diff --git a/tests/errors/UnknownFunction2.q b/tests/errors/UnknownFunction2.q deleted file mode 100644 index c31b354..0000000 --- a/tests/errors/UnknownFunction2.q +++ /dev/null @@ -1,3 +0,0 @@ -main() { - x := 1 + f(x) -} \ No newline at end of file diff --git a/tests/errors/UnknownFunction.q b/tests/errors/UnknownIdentifier4.q similarity index 100% rename from tests/errors/UnknownFunction.q rename to tests/errors/UnknownIdentifier4.q diff --git a/tests/errors/UnknownIdentifier5.q b/tests/errors/UnknownIdentifier5.q new file mode 100644 index 0000000..5d38d8e --- /dev/null +++ b/tests/errors/UnknownIdentifier5.q @@ -0,0 +1,3 @@ +main() { + x := 1 + unknown(x) +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 95a5424..9403bfc 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -39,6 +39,7 @@ var errs = []struct { {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockEnd2.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, + {"MissingBlockStart2.q", errors.MissingBlockStart}, {"MissingExpression.q", errors.MissingExpression}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, {"MissingGroupStart.q", errors.MissingGroupStart}, @@ -52,11 +53,11 @@ var errs = []struct { {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*any", Encountered: "int", ParameterName: "p"}}, {"TypeMismatch2.q", &errors.TypeMismatch{Expected: "[]any", Encountered: "int", ParameterName: "array"}}, - {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, - {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}}, + {"UnknownIdentifier4.q", &errors.UnknownIdentifier{Name: "unknown"}}, + {"UnknownIdentifier5.q", &errors.UnknownIdentifier{Name: "unknown"}}, {"UnknownPackage.q", &errors.UnknownPackage{Name: "sys"}}, {"UnknownType.q", &errors.UnknownType{Name: "unknown"}}, {"UnknownType2.q", &errors.UnknownType{Name: "unknown"}}, diff --git a/tests/programs/function-pointer-field.q b/tests/programs/function-pointer-field.q new file mode 100644 index 0000000..89894b9 --- /dev/null +++ b/tests/programs/function-pointer-field.q @@ -0,0 +1,11 @@ +import core + +struct Struct { + func *any +} + +main() { + s := new(Struct) + s.func = core.exit + s.func() +} \ No newline at end of file diff --git a/tests/programs/loop-in-loop.q b/tests/programs/loop-in-loop.q new file mode 100644 index 0000000..969ace6 --- /dev/null +++ b/tests/programs/loop-in-loop.q @@ -0,0 +1,21 @@ +import sys + +main() { + x := 0 + + loop { + for 0..10 { + x += 1 + } + + assert x == 10 + + loop { + x -= 1 + + if x == 0 { + sys.exit(0) + } + } + } +} \ No newline at end of file diff --git a/tests/programs/loop-infinite.q b/tests/programs/loop.q similarity index 100% rename from tests/programs/loop-infinite.q rename to tests/programs/loop.q diff --git a/tests/programs_test.go b/tests/programs_test.go index 369c4af..a1e76ce 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -58,8 +58,9 @@ var programs = []struct { {"branch-save", 0}, {"jump-near", 0}, {"switch", 0}, - {"loop-infinite", 0}, + {"loop", 0}, {"loop-lifetime", 0}, + {"loop-in-loop", 0}, {"for", 0}, {"memory-free", 0}, {"out-of-memory", 0}, @@ -69,6 +70,7 @@ var programs = []struct { {"len", 0}, {"cast", 0}, {"function-pointer", 0}, + {"function-pointer-field", 0}, } func TestPrograms(t *testing.T) { From ea233d789d38010d1b73be25d8c525a4f6405d0a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 2 Mar 2025 21:36:23 +0100 Subject: [PATCH 0868/1012] Simplified compilation of function calls --- src/compiler/Compile.go | 5 + src/core/CompileCall.go | 112 ++++++++---------- src/core/Environment.go | 1 + src/core/EvaluateDot.go | 25 ++-- src/errors/UnknownFunction.go | 18 --- tests/errors/MissingBlockStart2.q | 3 + tests/errors/UnknownFunction2.q | 3 - ...UnknownFunction.q => UnknownIdentifier4.q} | 0 tests/errors/UnknownIdentifier5.q | 3 + tests/errors_test.go | 5 +- tests/programs/function-pointer-field.q | 11 ++ tests/programs/loop-in-loop.q | 21 ++++ tests/programs/{loop-infinite.q => loop.q} | 0 tests/programs_test.go | 4 +- 14 files changed, 114 insertions(+), 97 deletions(-) delete mode 100644 src/errors/UnknownFunction.go create mode 100644 tests/errors/MissingBlockStart2.q delete mode 100644 tests/errors/UnknownFunction2.q rename tests/errors/{UnknownFunction.q => UnknownIdentifier4.q} (100%) create mode 100644 tests/errors/UnknownIdentifier5.q create mode 100644 tests/programs/function-pointer-field.q create mode 100644 tests/programs/loop-in-loop.q rename tests/programs/{loop-infinite.q => loop.q} (100%) diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index f62943c..ae3c437 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -11,6 +11,7 @@ import ( func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions <-chan *core.Function, structs <-chan *types.Struct, errs <-chan error) (Result, error) { all := core.Environment{ Files: make([]*fs.File, 0, 8), + Extern: make(map[string]struct{}, 0), Functions: make(map[string]*core.Function, 32), Structs: make(map[string]*types.Struct, 8), Constants: make(map[string]*core.Constant, 8), @@ -29,6 +30,10 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < function.All = &all all.Functions[function.UniqueName] = function + if function.IsExtern() { + all.Extern[function.Package] = struct{}{} + } + case structure, ok := <-structs: if !ok { structs = nil diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index fec090a..7e47f48 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -3,7 +3,9 @@ package core import ( "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" + "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" ) @@ -12,17 +14,8 @@ import ( // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error) { - var ( - pkg = f.Package - pkgNode *expression.Expression - name string - nameNode = root.Children[0] - fn *Function - exists bool - ) - - if nameNode.IsLeaf() { - name = nameNode.Token.Text(f.File.Bytes) + if root.Children[0].Token.Kind == token.Identifier { + name := root.Children[0].Token.Text(f.File.Bytes) switch name { case "len": @@ -37,63 +30,56 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error case "store": return nil, f.CompileMemoryStore(root) } - } else { - pkgNode = nameNode.Children[0] - nameNode = nameNode.Children[1] - - pkg = pkgNode.Token.Text(f.File.Bytes) - name = nameNode.Token.Text(f.File.Bytes) } - if f.UniqueName == "core.init" && pkg == "main" && name == "main" { - f.Label(asm.CALL, "main.main") - return nil, nil - } - - fn, exists = f.All.Functions[pkg+"."+name] - - if !exists { - variable := f.VariableByName(name) - - if variable != nil { - f.Register(asm.CALL, variable.Value.Register) - return nil, nil - } - - return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) - } - - if pkg != f.File.Package && !fn.IsExtern() { - if f.File.Imports == nil { - return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, pkgNode.Token.Position) - } - - imp, exists := f.File.Imports[pkg] - - if !exists { - return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, pkgNode.Token.Position) - } - - imp.Used = true - } - - parameters := root.Children[1:] - - if len(parameters) != len(fn.Input) { - return nil, errors.New(&errors.ParameterCountMismatch{Function: fn.Name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, nameNode.Token.End()) - } - - if fn.IsExtern() { - return f.CallExtern(fn, parameters) - } - - registers := f.CPU.Input[:len(parameters)] - err := f.ExpressionsToRegisters(parameters, registers, fn.Input, true) + value, err := f.Evaluate(root.Children[0]) if err != nil { return nil, err } - f.CallSafe(fn, registers) - return fn.OutputTypes, nil + parameters := root.Children[1:] + registers := f.CPU.Input[:len(parameters)] + + switch value := value.(type) { + case *eval.Label: + fn, exists := f.All.Functions[value.Label] + + if !exists { + if value.Label == "main.main" && f.UniqueName == "core.init" { + f.Label(asm.CALL, "main.main") + return nil, nil + } + + return nil, errors.New(&errors.UnknownIdentifier{Name: value.Label}, f.File, root.Children[0].Token.Position) + } + + if len(parameters) != len(fn.Input) { + return nil, errors.New(&errors.ParameterCountMismatch{Function: fn.Name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, root.Children[0].Token.End()) + } + + if fn.IsExtern() { + return f.CallExtern(fn, parameters) + } + + err := f.ExpressionsToRegisters(parameters, registers, fn.Input, true) + + if err != nil { + return nil, err + } + + f.CallSafe(fn, registers) + return fn.OutputTypes, nil + + case *eval.Register: + f.Register(asm.CALL, value.Register) + + case *eval.Memory: + tmp := f.NewRegister() + f.MemoryRegister(asm.LOAD, value.Memory, tmp) + f.Register(asm.CALL, tmp) + f.FreeRegister(tmp) + } + + return nil, nil } diff --git a/src/core/Environment.go b/src/core/Environment.go index e2a1354..c27828a 100644 --- a/src/core/Environment.go +++ b/src/core/Environment.go @@ -8,6 +8,7 @@ import ( // Environment holds information about the entire build. type Environment struct { Constants map[string]*Constant + Extern map[string]struct{} Functions map[string]*Function Structs map[string]*types.Struct Files []*fs.File diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go index 944ea02..69f078c 100644 --- a/src/core/EvaluateDot.go +++ b/src/core/EvaluateDot.go @@ -59,19 +59,24 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) return value, nil } - uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) - function, exists := f.All.Functions[uniqueName] + if leftText != "main" { + imp, exists := f.File.Imports[leftText] - if exists { - f.File.Imports[leftText].Used = true + if exists { + imp.Used = true + } else { + _, exists := f.All.Extern[leftText] - value := &eval.Label{ - Typ: types.AnyPointer, - Label: function.UniqueName, + if !exists { + return nil, errors.New(&errors.UnknownPackage{Name: leftText}, f.File, left.Token.Position) + } } - - return value, nil } - return nil, errors.New(&errors.UnknownIdentifier{Name: uniqueName}, f.File, left.Token.Position) + value := &eval.Label{ + Typ: types.AnyPointer, + Label: fmt.Sprintf("%s.%s", leftText, rightText), + } + + return value, nil } diff --git a/src/errors/UnknownFunction.go b/src/errors/UnknownFunction.go deleted file mode 100644 index a2b7e4e..0000000 --- a/src/errors/UnknownFunction.go +++ /dev/null @@ -1,18 +0,0 @@ -package errors - -import "fmt" - -// UnknownFunction represents unknown function errors. -type UnknownFunction struct { - Name string - CorrectName string -} - -// Error generates the string representation. -func (err *UnknownFunction) Error() string { - if err.CorrectName != "" { - return fmt.Sprintf("Unknown function '%s', did you mean '%s'?", err.Name, err.CorrectName) - } - - return fmt.Sprintf("Unknown function '%s'", err.Name) -} diff --git a/tests/errors/MissingBlockStart2.q b/tests/errors/MissingBlockStart2.q new file mode 100644 index 0000000..7e12b77 --- /dev/null +++ b/tests/errors/MissingBlockStart2.q @@ -0,0 +1,3 @@ +main() { + loop +} \ No newline at end of file diff --git a/tests/errors/UnknownFunction2.q b/tests/errors/UnknownFunction2.q deleted file mode 100644 index c31b354..0000000 --- a/tests/errors/UnknownFunction2.q +++ /dev/null @@ -1,3 +0,0 @@ -main() { - x := 1 + f(x) -} \ No newline at end of file diff --git a/tests/errors/UnknownFunction.q b/tests/errors/UnknownIdentifier4.q similarity index 100% rename from tests/errors/UnknownFunction.q rename to tests/errors/UnknownIdentifier4.q diff --git a/tests/errors/UnknownIdentifier5.q b/tests/errors/UnknownIdentifier5.q new file mode 100644 index 0000000..5d38d8e --- /dev/null +++ b/tests/errors/UnknownIdentifier5.q @@ -0,0 +1,3 @@ +main() { + x := 1 + unknown(x) +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 95a5424..9403bfc 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -39,6 +39,7 @@ var errs = []struct { {"MissingBlockEnd.q", errors.MissingBlockEnd}, {"MissingBlockEnd2.q", errors.MissingBlockEnd}, {"MissingBlockStart.q", errors.MissingBlockStart}, + {"MissingBlockStart2.q", errors.MissingBlockStart}, {"MissingExpression.q", errors.MissingExpression}, {"MissingGroupEnd.q", errors.MissingGroupEnd}, {"MissingGroupStart.q", errors.MissingGroupStart}, @@ -52,11 +53,11 @@ var errs = []struct { {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*any", Encountered: "int", ParameterName: "p"}}, {"TypeMismatch2.q", &errors.TypeMismatch{Expected: "[]any", Encountered: "int", ParameterName: "array"}}, - {"UnknownFunction.q", &errors.UnknownFunction{Name: "unknown"}}, - {"UnknownFunction2.q", &errors.UnknownFunction{Name: "f"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}}, + {"UnknownIdentifier4.q", &errors.UnknownIdentifier{Name: "unknown"}}, + {"UnknownIdentifier5.q", &errors.UnknownIdentifier{Name: "unknown"}}, {"UnknownPackage.q", &errors.UnknownPackage{Name: "sys"}}, {"UnknownType.q", &errors.UnknownType{Name: "unknown"}}, {"UnknownType2.q", &errors.UnknownType{Name: "unknown"}}, diff --git a/tests/programs/function-pointer-field.q b/tests/programs/function-pointer-field.q new file mode 100644 index 0000000..89894b9 --- /dev/null +++ b/tests/programs/function-pointer-field.q @@ -0,0 +1,11 @@ +import core + +struct Struct { + func *any +} + +main() { + s := new(Struct) + s.func = core.exit + s.func() +} \ No newline at end of file diff --git a/tests/programs/loop-in-loop.q b/tests/programs/loop-in-loop.q new file mode 100644 index 0000000..969ace6 --- /dev/null +++ b/tests/programs/loop-in-loop.q @@ -0,0 +1,21 @@ +import sys + +main() { + x := 0 + + loop { + for 0..10 { + x += 1 + } + + assert x == 10 + + loop { + x -= 1 + + if x == 0 { + sys.exit(0) + } + } + } +} \ No newline at end of file diff --git a/tests/programs/loop-infinite.q b/tests/programs/loop.q similarity index 100% rename from tests/programs/loop-infinite.q rename to tests/programs/loop.q diff --git a/tests/programs_test.go b/tests/programs_test.go index 369c4af..a1e76ce 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -58,8 +58,9 @@ var programs = []struct { {"branch-save", 0}, {"jump-near", 0}, {"switch", 0}, - {"loop-infinite", 0}, + {"loop", 0}, {"loop-lifetime", 0}, + {"loop-in-loop", 0}, {"for", 0}, {"memory-free", 0}, {"out-of-memory", 0}, @@ -69,6 +70,7 @@ var programs = []struct { {"len", 0}, {"cast", 0}, {"function-pointer", 0}, + {"function-pointer-field", 0}, } func TestPrograms(t *testing.T) { From acfa6de1d4fe27d24129e1a24b3f1d5fd6687162 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 3 Mar 2025 00:53:41 +0100 Subject: [PATCH 0869/1012] Implemented calls using memory addresses --- src/asm/Memory.go | 22 +++++++++++++++++-- src/asmc/call.go | 3 +++ src/asmc/dllCall.go | 2 +- src/core/CompileCall.go | 5 +---- src/register/Memory.go | 8 +++++++ src/x86/Call.go | 34 +++++++++++++++++++++++++++-- src/x86/Call_test.go | 48 +++++++++++++++++++++++++++++++++++++++++ src/x86/x86_test.go | 2 +- 8 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 src/register/Memory.go diff --git a/src/asm/Memory.go b/src/asm/Memory.go index 903ff15..0af7c38 100644 --- a/src/asm/Memory.go +++ b/src/asm/Memory.go @@ -35,9 +35,27 @@ func (mem *Memory) Format(custom string) string { tmp.WriteString(strconv.Itoa(int(mem.Offset))) } - tmp.WriteString("], ") - tmp.WriteString(custom) + tmp.WriteString("]") + + if custom != "" { + tmp.WriteString(", ") + tmp.WriteString(custom) + } + tmp.WriteString(", ") tmp.WriteString(strconv.Itoa(int(mem.Length))) return tmp.String() } + +// String returns a human readable version. +func (mem *Memory) String() string { + return mem.Format("") +} + +// Memory adds an instruction with a memory address. +func (a *Assembler) Memory(mnemonic Mnemonic, address Memory) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &address, + }) +} diff --git a/src/asmc/call.go b/src/asmc/call.go index e5d08a7..7a4f51a 100644 --- a/src/asmc/call.go +++ b/src/asmc/call.go @@ -32,5 +32,8 @@ func (c *compiler) call(x asm.Instruction) { case *asm.Register: c.code = x86.CallRegister(c.code, data.Register) + + case *asm.Memory: + c.code = x86.CallAtMemory(c.code, data.Base, data.Offset) } } diff --git a/src/asmc/dllCall.go b/src/asmc/dllCall.go index 75523d1..71d1e0e 100644 --- a/src/asmc/dllCall.go +++ b/src/asmc/dllCall.go @@ -9,7 +9,7 @@ import ( func (c *compiler) dllCall(x asm.Instruction) { size := 4 - c.code = x86.CallAtAddress(c.code, 0x00_00_00_00) + c.code = x86.CallAt(c.code, 0x00_00_00_00) position := len(c.code) - size label := x.Data.(*asm.Label) diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 7e47f48..9d94c2b 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -75,10 +75,7 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error f.Register(asm.CALL, value.Register) case *eval.Memory: - tmp := f.NewRegister() - f.MemoryRegister(asm.LOAD, value.Memory, tmp) - f.Register(asm.CALL, tmp) - f.FreeRegister(tmp) + f.Memory(asm.CALL, value.Memory) } return nil, nil diff --git a/src/register/Memory.go b/src/register/Memory.go new file mode 100644 index 0000000..60c62db --- /dev/null +++ b/src/register/Memory.go @@ -0,0 +1,8 @@ +package register + +import "git.urbach.dev/cli/q/src/asm" + +func (f *Machine) Memory(mnemonic asm.Mnemonic, memory asm.Memory) { + f.Assembler.Memory(mnemonic, memory) + f.postInstruction() +} diff --git a/src/x86/Call.go b/src/x86/Call.go index b6c9194..a19c68e 100644 --- a/src/x86/Call.go +++ b/src/x86/Call.go @@ -34,9 +34,9 @@ func CallRegister(code []byte, register cpu.Register) []byte { ) } -// CallAtAddress calls a function at the address stored at the given memory address. +// CallAt calls a function at the address stored at the given memory address. // The memory address is relative to the next instruction. -func CallAtAddress(code []byte, address uint32) []byte { +func CallAt(code []byte, address uint32) []byte { return append( code, 0xFF, @@ -47,3 +47,33 @@ func CallAtAddress(code []byte, address uint32) []byte { byte(address>>24), ) } + +// CallAtMemory calls a function at the address stored at the given memory address. +// The memory address is relative to the next instruction. +func CallAtMemory(code []byte, register cpu.Register, offset int8) []byte { + mod := AddressMemory + + if offset != 0 || register == RBP || register == R13 { + mod = AddressMemoryOffset8 + } + + reg := byte(0b010) + rm := register + + if rm > 0b111 { + code = append(code, 0x41) + rm &= 0b111 + } + + code = append(code, 0xFF, ModRM(mod, reg, byte(rm))) + + if register == RSP || register == R12 { + code = append(code, SIB(Scale1, 0b100, 0b100)) + } + + if mod == AddressMemoryOffset8 { + code = append(code, byte(offset)) + } + + return code +} diff --git a/src/x86/Call_test.go b/src/x86/Call_test.go index 5aae357..033ee64 100644 --- a/src/x86/Call_test.go +++ b/src/x86/Call_test.go @@ -37,3 +37,51 @@ func TestCallRegister(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestCallAtMemory(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Offset int8 + Code []byte + }{ + {x86.RAX, 0, []byte{0xFF, 0x10}}, + {x86.RCX, 0, []byte{0xFF, 0x11}}, + {x86.RDX, 0, []byte{0xFF, 0x12}}, + {x86.RBX, 0, []byte{0xFF, 0x13}}, + {x86.RSP, 0, []byte{0xFF, 0x14, 0x24}}, + {x86.RBP, 0, []byte{0xFF, 0x55, 0x00}}, + {x86.RSI, 0, []byte{0xFF, 0x16}}, + {x86.RDI, 0, []byte{0xFF, 0x17}}, + {x86.R8, 0, []byte{0x41, 0xFF, 0x10}}, + {x86.R9, 0, []byte{0x41, 0xFF, 0x11}}, + {x86.R10, 0, []byte{0x41, 0xFF, 0x12}}, + {x86.R11, 0, []byte{0x41, 0xFF, 0x13}}, + {x86.R12, 0, []byte{0x41, 0xFF, 0x14, 0x24}}, + {x86.R13, 0, []byte{0x41, 0xFF, 0x55, 0x00}}, + {x86.R14, 0, []byte{0x41, 0xFF, 0x16}}, + {x86.R15, 0, []byte{0x41, 0xFF, 0x17}}, + + {x86.RAX, 1, []byte{0xFF, 0x50, 0x01}}, + {x86.RCX, 1, []byte{0xFF, 0x51, 0x01}}, + {x86.RDX, 1, []byte{0xFF, 0x52, 0x01}}, + {x86.RBX, 1, []byte{0xFF, 0x53, 0x01}}, + {x86.RSP, 1, []byte{0xFF, 0x54, 0x24, 0x01}}, + {x86.RBP, 1, []byte{0xFF, 0x55, 0x01}}, + {x86.RSI, 1, []byte{0xFF, 0x56, 0x01}}, + {x86.RDI, 1, []byte{0xFF, 0x57, 0x01}}, + {x86.R8, 1, []byte{0x41, 0xFF, 0x50, 0x01}}, + {x86.R9, 1, []byte{0x41, 0xFF, 0x51, 0x01}}, + {x86.R10, 1, []byte{0x41, 0xFF, 0x52, 0x01}}, + {x86.R11, 1, []byte{0x41, 0xFF, 0x53, 0x01}}, + {x86.R12, 1, []byte{0x41, 0xFF, 0x54, 0x24, 0x01}}, + {x86.R13, 1, []byte{0x41, 0xFF, 0x55, 0x01}}, + {x86.R14, 1, []byte{0x41, 0xFF, 0x56, 0x01}}, + {x86.R15, 1, []byte{0x41, 0xFF, 0x57, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("call [%s+%d]", pattern.Register, pattern.Offset) + code := x86.CallAtMemory(nil, pattern.Register, pattern.Offset) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x86/x86_test.go b/src/x86/x86_test.go index 02e0560..9d6b3bc 100644 --- a/src/x86/x86_test.go +++ b/src/x86/x86_test.go @@ -9,7 +9,7 @@ import ( func TestX86(t *testing.T) { assert.DeepEqual(t, x86.Call(nil, 1), []byte{0xE8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x86.CallAtAddress(nil, 1), []byte{0xFF, 0x15, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x86.CallAt(nil, 1), []byte{0xFF, 0x15, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x86.ExtendRAXToRDX(nil), []byte{0x48, 0x99}) assert.DeepEqual(t, x86.MoveRegisterNumber(nil, 0, 1), []byte{0xB8, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x86.MoveRegisterNumber(nil, 1, 1), []byte{0xB9, 0x01, 0x00, 0x00, 0x00}) From 08660ad845a62464a37f0bdcc78871843b86827b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 3 Mar 2025 00:53:41 +0100 Subject: [PATCH 0870/1012] Implemented calls using memory addresses --- src/asm/Memory.go | 22 +++++++++++++++++-- src/asmc/call.go | 3 +++ src/asmc/dllCall.go | 2 +- src/core/CompileCall.go | 5 +---- src/register/Memory.go | 8 +++++++ src/x86/Call.go | 34 +++++++++++++++++++++++++++-- src/x86/Call_test.go | 48 +++++++++++++++++++++++++++++++++++++++++ src/x86/x86_test.go | 2 +- 8 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 src/register/Memory.go diff --git a/src/asm/Memory.go b/src/asm/Memory.go index 903ff15..0af7c38 100644 --- a/src/asm/Memory.go +++ b/src/asm/Memory.go @@ -35,9 +35,27 @@ func (mem *Memory) Format(custom string) string { tmp.WriteString(strconv.Itoa(int(mem.Offset))) } - tmp.WriteString("], ") - tmp.WriteString(custom) + tmp.WriteString("]") + + if custom != "" { + tmp.WriteString(", ") + tmp.WriteString(custom) + } + tmp.WriteString(", ") tmp.WriteString(strconv.Itoa(int(mem.Length))) return tmp.String() } + +// String returns a human readable version. +func (mem *Memory) String() string { + return mem.Format("") +} + +// Memory adds an instruction with a memory address. +func (a *Assembler) Memory(mnemonic Mnemonic, address Memory) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &address, + }) +} diff --git a/src/asmc/call.go b/src/asmc/call.go index e5d08a7..7a4f51a 100644 --- a/src/asmc/call.go +++ b/src/asmc/call.go @@ -32,5 +32,8 @@ func (c *compiler) call(x asm.Instruction) { case *asm.Register: c.code = x86.CallRegister(c.code, data.Register) + + case *asm.Memory: + c.code = x86.CallAtMemory(c.code, data.Base, data.Offset) } } diff --git a/src/asmc/dllCall.go b/src/asmc/dllCall.go index 75523d1..71d1e0e 100644 --- a/src/asmc/dllCall.go +++ b/src/asmc/dllCall.go @@ -9,7 +9,7 @@ import ( func (c *compiler) dllCall(x asm.Instruction) { size := 4 - c.code = x86.CallAtAddress(c.code, 0x00_00_00_00) + c.code = x86.CallAt(c.code, 0x00_00_00_00) position := len(c.code) - size label := x.Data.(*asm.Label) diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 7e47f48..9d94c2b 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -75,10 +75,7 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error f.Register(asm.CALL, value.Register) case *eval.Memory: - tmp := f.NewRegister() - f.MemoryRegister(asm.LOAD, value.Memory, tmp) - f.Register(asm.CALL, tmp) - f.FreeRegister(tmp) + f.Memory(asm.CALL, value.Memory) } return nil, nil diff --git a/src/register/Memory.go b/src/register/Memory.go new file mode 100644 index 0000000..60c62db --- /dev/null +++ b/src/register/Memory.go @@ -0,0 +1,8 @@ +package register + +import "git.urbach.dev/cli/q/src/asm" + +func (f *Machine) Memory(mnemonic asm.Mnemonic, memory asm.Memory) { + f.Assembler.Memory(mnemonic, memory) + f.postInstruction() +} diff --git a/src/x86/Call.go b/src/x86/Call.go index b6c9194..a19c68e 100644 --- a/src/x86/Call.go +++ b/src/x86/Call.go @@ -34,9 +34,9 @@ func CallRegister(code []byte, register cpu.Register) []byte { ) } -// CallAtAddress calls a function at the address stored at the given memory address. +// CallAt calls a function at the address stored at the given memory address. // The memory address is relative to the next instruction. -func CallAtAddress(code []byte, address uint32) []byte { +func CallAt(code []byte, address uint32) []byte { return append( code, 0xFF, @@ -47,3 +47,33 @@ func CallAtAddress(code []byte, address uint32) []byte { byte(address>>24), ) } + +// CallAtMemory calls a function at the address stored at the given memory address. +// The memory address is relative to the next instruction. +func CallAtMemory(code []byte, register cpu.Register, offset int8) []byte { + mod := AddressMemory + + if offset != 0 || register == RBP || register == R13 { + mod = AddressMemoryOffset8 + } + + reg := byte(0b010) + rm := register + + if rm > 0b111 { + code = append(code, 0x41) + rm &= 0b111 + } + + code = append(code, 0xFF, ModRM(mod, reg, byte(rm))) + + if register == RSP || register == R12 { + code = append(code, SIB(Scale1, 0b100, 0b100)) + } + + if mod == AddressMemoryOffset8 { + code = append(code, byte(offset)) + } + + return code +} diff --git a/src/x86/Call_test.go b/src/x86/Call_test.go index 5aae357..033ee64 100644 --- a/src/x86/Call_test.go +++ b/src/x86/Call_test.go @@ -37,3 +37,51 @@ func TestCallRegister(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestCallAtMemory(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Offset int8 + Code []byte + }{ + {x86.RAX, 0, []byte{0xFF, 0x10}}, + {x86.RCX, 0, []byte{0xFF, 0x11}}, + {x86.RDX, 0, []byte{0xFF, 0x12}}, + {x86.RBX, 0, []byte{0xFF, 0x13}}, + {x86.RSP, 0, []byte{0xFF, 0x14, 0x24}}, + {x86.RBP, 0, []byte{0xFF, 0x55, 0x00}}, + {x86.RSI, 0, []byte{0xFF, 0x16}}, + {x86.RDI, 0, []byte{0xFF, 0x17}}, + {x86.R8, 0, []byte{0x41, 0xFF, 0x10}}, + {x86.R9, 0, []byte{0x41, 0xFF, 0x11}}, + {x86.R10, 0, []byte{0x41, 0xFF, 0x12}}, + {x86.R11, 0, []byte{0x41, 0xFF, 0x13}}, + {x86.R12, 0, []byte{0x41, 0xFF, 0x14, 0x24}}, + {x86.R13, 0, []byte{0x41, 0xFF, 0x55, 0x00}}, + {x86.R14, 0, []byte{0x41, 0xFF, 0x16}}, + {x86.R15, 0, []byte{0x41, 0xFF, 0x17}}, + + {x86.RAX, 1, []byte{0xFF, 0x50, 0x01}}, + {x86.RCX, 1, []byte{0xFF, 0x51, 0x01}}, + {x86.RDX, 1, []byte{0xFF, 0x52, 0x01}}, + {x86.RBX, 1, []byte{0xFF, 0x53, 0x01}}, + {x86.RSP, 1, []byte{0xFF, 0x54, 0x24, 0x01}}, + {x86.RBP, 1, []byte{0xFF, 0x55, 0x01}}, + {x86.RSI, 1, []byte{0xFF, 0x56, 0x01}}, + {x86.RDI, 1, []byte{0xFF, 0x57, 0x01}}, + {x86.R8, 1, []byte{0x41, 0xFF, 0x50, 0x01}}, + {x86.R9, 1, []byte{0x41, 0xFF, 0x51, 0x01}}, + {x86.R10, 1, []byte{0x41, 0xFF, 0x52, 0x01}}, + {x86.R11, 1, []byte{0x41, 0xFF, 0x53, 0x01}}, + {x86.R12, 1, []byte{0x41, 0xFF, 0x54, 0x24, 0x01}}, + {x86.R13, 1, []byte{0x41, 0xFF, 0x55, 0x01}}, + {x86.R14, 1, []byte{0x41, 0xFF, 0x56, 0x01}}, + {x86.R15, 1, []byte{0x41, 0xFF, 0x57, 0x01}}, + } + + for _, pattern := range usagePatterns { + t.Logf("call [%s+%d]", pattern.Register, pattern.Offset) + code := x86.CallAtMemory(nil, pattern.Register, pattern.Offset) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/x86/x86_test.go b/src/x86/x86_test.go index 02e0560..9d6b3bc 100644 --- a/src/x86/x86_test.go +++ b/src/x86/x86_test.go @@ -9,7 +9,7 @@ import ( func TestX86(t *testing.T) { assert.DeepEqual(t, x86.Call(nil, 1), []byte{0xE8, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x86.CallAtAddress(nil, 1), []byte{0xFF, 0x15, 0x01, 0x00, 0x00, 0x00}) + assert.DeepEqual(t, x86.CallAt(nil, 1), []byte{0xFF, 0x15, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x86.ExtendRAXToRDX(nil), []byte{0x48, 0x99}) assert.DeepEqual(t, x86.MoveRegisterNumber(nil, 0, 1), []byte{0xB8, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x86.MoveRegisterNumber(nil, 1, 1), []byte{0xB9, 0x01, 0x00, 0x00, 0x00}) From b095a9502183b10ec3eda9d1a4ddfbb73358f33c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 3 Mar 2025 12:14:53 +0100 Subject: [PATCH 0871/1012] Implemented dependency tracking --- src/asmc/call.go | 4 +++- src/asmc/jump.go | 4 +++- src/compiler/Compile.go | 28 ++++++++++++------------- src/compiler/eachFunction.go | 25 ++++------------------ src/core/CompileCall.go | 11 +--------- src/core/CompileDelete.go | 4 +++- src/core/CompileNew.go | 4 +++- src/core/EvaluateDot.go | 19 ++++++++++++----- src/core/EvaluateToken.go | 2 ++ src/core/Function.go | 27 ++++++++++++------------ tests/programs/function-pointer-field.q | 9 ++++++-- tests/programs/function-pointer.q | 7 +++++++ 12 files changed, 75 insertions(+), 69 deletions(-) diff --git a/src/asmc/call.go b/src/asmc/call.go index 7a4f51a..3bdc411 100644 --- a/src/asmc/call.go +++ b/src/asmc/call.go @@ -1,6 +1,8 @@ package asmc import ( + "fmt" + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/x86" ) @@ -21,7 +23,7 @@ func (c *compiler) call(x asm.Instruction) { destination, exists := c.codeLabels[data.Name] if !exists { - panic("unknown jump label") + panic(fmt.Sprintf("unknown jump label %s", data.Name)) } distance := destination - (pointer.Position + Address(pointer.Size)) diff --git a/src/asmc/jump.go b/src/asmc/jump.go index 853bd81..9964c8b 100644 --- a/src/asmc/jump.go +++ b/src/asmc/jump.go @@ -1,6 +1,8 @@ package asmc import ( + "fmt" + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/x86" ) @@ -36,7 +38,7 @@ func (c *compiler) jump(x asm.Instruction) { destination, exists := c.codeLabels[label.Name] if !exists { - panic("unknown jump label") + panic(fmt.Sprintf("unknown jump label %s", label.Name)) } distance := destination - (pointer.Position + Address(pointer.Size)) diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index ae3c437..6fd88c1 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -82,6 +82,20 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < } } + // Check for existence of `init` + init, exists := all.Functions["core.init"] + + if !exists { + return result, errors.MissingInitFunction + } + + // Check for existence of `main` + main, exists := all.Functions["main.main"] + + if !exists { + return result, errors.MissingMainFunction + } + // Start parallel compilation CompileFunctions(all.Functions) @@ -104,20 +118,6 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < } } - // Check for existence of `init` - init, exists := all.Functions["core.init"] - - if !exists { - return result, errors.MissingInitFunction - } - - // Check for existence of `main` - main, exists := all.Functions["main.main"] - - if !exists { - return result, errors.MissingMainFunction - } - result.Init = init result.Main = main result.Functions = all.Functions diff --git a/src/compiler/eachFunction.go b/src/compiler/eachFunction.go index f3d6215..44bc00e 100644 --- a/src/compiler/eachFunction.go +++ b/src/compiler/eachFunction.go @@ -1,37 +1,20 @@ package compiler import ( - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/core" ) -// eachFunction recursively finds all the calls to external functions. +// eachFunction recursively finds all the calls to other functions. // It avoids calling the same function twice with the help of a hashmap. func (r *Result) eachFunction(caller *core.Function, traversed map[*core.Function]bool, call func(*core.Function)) { call(caller) traversed[caller] = true - for _, x := range caller.Assembler.Instructions { - if x.Mnemonic != asm.CALL { + for _, function := range caller.Dependencies { + if traversed[function] { continue } - label, isLabel := x.Data.(*asm.Label) - - if !isLabel { - continue - } - - callee, exists := r.Functions[label.Name] - - if !exists { - continue - } - - if traversed[callee] { - continue - } - - r.eachFunction(callee, traversed, call) + r.eachFunction(function, traversed, call) } } diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 9d94c2b..de531e7 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -43,16 +43,7 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error switch value := value.(type) { case *eval.Label: - fn, exists := f.All.Functions[value.Label] - - if !exists { - if value.Label == "main.main" && f.UniqueName == "core.init" { - f.Label(asm.CALL, "main.main") - return nil, nil - } - - return nil, errors.New(&errors.UnknownIdentifier{Name: value.Label}, f.File, root.Children[0].Token.Position) - } + fn := f.All.Functions[value.Label] if len(parameters) != len(fn.Input) { return nil, errors.New(&errors.ParameterCountMismatch{Function: fn.Name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, root.Children[0].Token.End()) diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index ad01ee7..8100b6f 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -22,6 +22,8 @@ func (f *Function) CompileDelete(root *expression.Expression) error { f.SaveRegister(f.CPU.Input[1]) f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Value.Register) f.RegisterNumber(asm.MOVE, f.CPU.Input[1], variable.Value.Typ.(*types.Pointer).To.Size()) - f.CallSafe(f.All.Functions["mem.free"], f.CPU.Input[:2]) + free := f.All.Functions["mem.free"] + f.CallSafe(free, f.CPU.Input[:2]) + f.Dependencies = append(f.Dependencies, free) return nil } diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index 52a1649..26a47ac 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -45,6 +45,8 @@ func (f *Function) CompileNew(root *expression.Expression) (types.Type, error) { f.SaveRegister(f.CPU.Input[0]) f.RegisterNumber(asm.MOVE, f.CPU.Input[0], typ.Size()) - f.CallSafe(f.All.Functions["mem.alloc"], f.CPU.Input[:1]) + alloc := f.All.Functions["mem.alloc"] + f.CallSafe(alloc, f.CPU.Input[:1]) + f.Dependencies = append(f.Dependencies, alloc) return &types.Pointer{To: typ}, nil } diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go index 69f078c..52738b3 100644 --- a/src/core/EvaluateDot.go +++ b/src/core/EvaluateDot.go @@ -42,7 +42,8 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) return value, nil } - constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] + label := fmt.Sprintf("%s.%s", leftText, rightText) + constant, isConst := f.All.Constants[f.Package+"."+label] if isConst { number, err := ToNumber(constant.Token, constant.File) @@ -73,10 +74,18 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) } } - value := &eval.Label{ - Typ: types.AnyPointer, - Label: fmt.Sprintf("%s.%s", leftText, rightText), + function, exists := f.All.Functions[label] + + if exists { + f.Dependencies = append(f.Dependencies, function) + + value := &eval.Label{ + Typ: types.AnyPointer, + Label: label, + } + + return value, nil } - return value, nil + return nil, errors.New(&errors.UnknownIdentifier{Name: label}, f.File, left.Token.Position) } diff --git a/src/core/EvaluateToken.go b/src/core/EvaluateToken.go index f2e7c9d..459aa2a 100644 --- a/src/core/EvaluateToken.go +++ b/src/core/EvaluateToken.go @@ -41,6 +41,8 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { } if function != nil { + f.Dependencies = append(f.Dependencies, function) + value := &eval.Label{ Typ: types.AnyPointer, Label: function.UniqueName, diff --git a/src/core/Function.go b/src/core/Function.go index dca269e..ac09a62 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -11,17 +11,18 @@ import ( // Function represents the smallest unit of code. type Function struct { register.Machine - Package string - Name string - UniqueName string - All *Environment - File *fs.File - Body token.List - Input []*Parameter - Output []*Parameter - OutputTypes []types.Type - DLLs dll.List - Err error - deferred []func() - count count + Package string + Name string + UniqueName string + All *Environment + File *fs.File + Body token.List + Input []*Parameter + Output []*Parameter + OutputTypes []types.Type + Dependencies []*Function + DLLs dll.List + Err error + deferred []func() + count count } diff --git a/tests/programs/function-pointer-field.q b/tests/programs/function-pointer-field.q index 89894b9..8a8611a 100644 --- a/tests/programs/function-pointer-field.q +++ b/tests/programs/function-pointer-field.q @@ -1,4 +1,4 @@ -import core +import sys struct Struct { func *any @@ -6,6 +6,11 @@ struct Struct { main() { s := new(Struct) - s.func = core.exit + s.func = f s.func() + sys.exit(1) +} + +f() { + sys.exit(0) } \ No newline at end of file diff --git a/tests/programs/function-pointer.q b/tests/programs/function-pointer.q index 20f8c8a..784fa85 100644 --- a/tests/programs/function-pointer.q +++ b/tests/programs/function-pointer.q @@ -1,6 +1,13 @@ import core +import sys main() { + func := f + func() + sys.exit(1) +} + +f() { exit := core.exit exit() } \ No newline at end of file From 751614e7c0e0f20668ac8ca361019106c47c7ac1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 3 Mar 2025 12:14:53 +0100 Subject: [PATCH 0872/1012] Implemented dependency tracking --- src/asmc/call.go | 4 +++- src/asmc/jump.go | 4 +++- src/compiler/Compile.go | 28 ++++++++++++------------- src/compiler/eachFunction.go | 25 ++++------------------ src/core/CompileCall.go | 11 +--------- src/core/CompileDelete.go | 4 +++- src/core/CompileNew.go | 4 +++- src/core/EvaluateDot.go | 19 ++++++++++++----- src/core/EvaluateToken.go | 2 ++ src/core/Function.go | 27 ++++++++++++------------ tests/programs/function-pointer-field.q | 9 ++++++-- tests/programs/function-pointer.q | 7 +++++++ 12 files changed, 75 insertions(+), 69 deletions(-) diff --git a/src/asmc/call.go b/src/asmc/call.go index 7a4f51a..3bdc411 100644 --- a/src/asmc/call.go +++ b/src/asmc/call.go @@ -1,6 +1,8 @@ package asmc import ( + "fmt" + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/x86" ) @@ -21,7 +23,7 @@ func (c *compiler) call(x asm.Instruction) { destination, exists := c.codeLabels[data.Name] if !exists { - panic("unknown jump label") + panic(fmt.Sprintf("unknown jump label %s", data.Name)) } distance := destination - (pointer.Position + Address(pointer.Size)) diff --git a/src/asmc/jump.go b/src/asmc/jump.go index 853bd81..9964c8b 100644 --- a/src/asmc/jump.go +++ b/src/asmc/jump.go @@ -1,6 +1,8 @@ package asmc import ( + "fmt" + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/x86" ) @@ -36,7 +38,7 @@ func (c *compiler) jump(x asm.Instruction) { destination, exists := c.codeLabels[label.Name] if !exists { - panic("unknown jump label") + panic(fmt.Sprintf("unknown jump label %s", label.Name)) } distance := destination - (pointer.Position + Address(pointer.Size)) diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index ae3c437..6fd88c1 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -82,6 +82,20 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < } } + // Check for existence of `init` + init, exists := all.Functions["core.init"] + + if !exists { + return result, errors.MissingInitFunction + } + + // Check for existence of `main` + main, exists := all.Functions["main.main"] + + if !exists { + return result, errors.MissingMainFunction + } + // Start parallel compilation CompileFunctions(all.Functions) @@ -104,20 +118,6 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < } } - // Check for existence of `init` - init, exists := all.Functions["core.init"] - - if !exists { - return result, errors.MissingInitFunction - } - - // Check for existence of `main` - main, exists := all.Functions["main.main"] - - if !exists { - return result, errors.MissingMainFunction - } - result.Init = init result.Main = main result.Functions = all.Functions diff --git a/src/compiler/eachFunction.go b/src/compiler/eachFunction.go index f3d6215..44bc00e 100644 --- a/src/compiler/eachFunction.go +++ b/src/compiler/eachFunction.go @@ -1,37 +1,20 @@ package compiler import ( - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/core" ) -// eachFunction recursively finds all the calls to external functions. +// eachFunction recursively finds all the calls to other functions. // It avoids calling the same function twice with the help of a hashmap. func (r *Result) eachFunction(caller *core.Function, traversed map[*core.Function]bool, call func(*core.Function)) { call(caller) traversed[caller] = true - for _, x := range caller.Assembler.Instructions { - if x.Mnemonic != asm.CALL { + for _, function := range caller.Dependencies { + if traversed[function] { continue } - label, isLabel := x.Data.(*asm.Label) - - if !isLabel { - continue - } - - callee, exists := r.Functions[label.Name] - - if !exists { - continue - } - - if traversed[callee] { - continue - } - - r.eachFunction(callee, traversed, call) + r.eachFunction(function, traversed, call) } } diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 9d94c2b..de531e7 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -43,16 +43,7 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error switch value := value.(type) { case *eval.Label: - fn, exists := f.All.Functions[value.Label] - - if !exists { - if value.Label == "main.main" && f.UniqueName == "core.init" { - f.Label(asm.CALL, "main.main") - return nil, nil - } - - return nil, errors.New(&errors.UnknownIdentifier{Name: value.Label}, f.File, root.Children[0].Token.Position) - } + fn := f.All.Functions[value.Label] if len(parameters) != len(fn.Input) { return nil, errors.New(&errors.ParameterCountMismatch{Function: fn.Name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, root.Children[0].Token.End()) diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index ad01ee7..8100b6f 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -22,6 +22,8 @@ func (f *Function) CompileDelete(root *expression.Expression) error { f.SaveRegister(f.CPU.Input[1]) f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Value.Register) f.RegisterNumber(asm.MOVE, f.CPU.Input[1], variable.Value.Typ.(*types.Pointer).To.Size()) - f.CallSafe(f.All.Functions["mem.free"], f.CPU.Input[:2]) + free := f.All.Functions["mem.free"] + f.CallSafe(free, f.CPU.Input[:2]) + f.Dependencies = append(f.Dependencies, free) return nil } diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index 52a1649..26a47ac 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -45,6 +45,8 @@ func (f *Function) CompileNew(root *expression.Expression) (types.Type, error) { f.SaveRegister(f.CPU.Input[0]) f.RegisterNumber(asm.MOVE, f.CPU.Input[0], typ.Size()) - f.CallSafe(f.All.Functions["mem.alloc"], f.CPU.Input[:1]) + alloc := f.All.Functions["mem.alloc"] + f.CallSafe(alloc, f.CPU.Input[:1]) + f.Dependencies = append(f.Dependencies, alloc) return &types.Pointer{To: typ}, nil } diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go index 69f078c..52738b3 100644 --- a/src/core/EvaluateDot.go +++ b/src/core/EvaluateDot.go @@ -42,7 +42,8 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) return value, nil } - constant, isConst := f.All.Constants[f.Package+"."+leftText+"."+rightText] + label := fmt.Sprintf("%s.%s", leftText, rightText) + constant, isConst := f.All.Constants[f.Package+"."+label] if isConst { number, err := ToNumber(constant.Token, constant.File) @@ -73,10 +74,18 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) } } - value := &eval.Label{ - Typ: types.AnyPointer, - Label: fmt.Sprintf("%s.%s", leftText, rightText), + function, exists := f.All.Functions[label] + + if exists { + f.Dependencies = append(f.Dependencies, function) + + value := &eval.Label{ + Typ: types.AnyPointer, + Label: label, + } + + return value, nil } - return value, nil + return nil, errors.New(&errors.UnknownIdentifier{Name: label}, f.File, left.Token.Position) } diff --git a/src/core/EvaluateToken.go b/src/core/EvaluateToken.go index f2e7c9d..459aa2a 100644 --- a/src/core/EvaluateToken.go +++ b/src/core/EvaluateToken.go @@ -41,6 +41,8 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { } if function != nil { + f.Dependencies = append(f.Dependencies, function) + value := &eval.Label{ Typ: types.AnyPointer, Label: function.UniqueName, diff --git a/src/core/Function.go b/src/core/Function.go index dca269e..ac09a62 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -11,17 +11,18 @@ import ( // Function represents the smallest unit of code. type Function struct { register.Machine - Package string - Name string - UniqueName string - All *Environment - File *fs.File - Body token.List - Input []*Parameter - Output []*Parameter - OutputTypes []types.Type - DLLs dll.List - Err error - deferred []func() - count count + Package string + Name string + UniqueName string + All *Environment + File *fs.File + Body token.List + Input []*Parameter + Output []*Parameter + OutputTypes []types.Type + Dependencies []*Function + DLLs dll.List + Err error + deferred []func() + count count } diff --git a/tests/programs/function-pointer-field.q b/tests/programs/function-pointer-field.q index 89894b9..8a8611a 100644 --- a/tests/programs/function-pointer-field.q +++ b/tests/programs/function-pointer-field.q @@ -1,4 +1,4 @@ -import core +import sys struct Struct { func *any @@ -6,6 +6,11 @@ struct Struct { main() { s := new(Struct) - s.func = core.exit + s.func = f s.func() + sys.exit(1) +} + +f() { + sys.exit(0) } \ No newline at end of file diff --git a/tests/programs/function-pointer.q b/tests/programs/function-pointer.q index 20f8c8a..784fa85 100644 --- a/tests/programs/function-pointer.q +++ b/tests/programs/function-pointer.q @@ -1,6 +1,13 @@ import core +import sys main() { + func := f + func() + sys.exit(1) +} + +f() { exit := core.exit exit() } \ No newline at end of file From 9a018f29e73a549d69811ccf9afe53a4da7eec10 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 3 Mar 2025 14:32:11 +0100 Subject: [PATCH 0873/1012] Implemented parameter passing for function pointers --- src/core/AfterCall.go | 21 ++++++++++++++++ src/core/BeforeCall.go | 18 ++++++++++++++ src/core/CallSafe.go | 33 ------------------------- src/core/CompileCall.go | 20 ++++++++++++++- src/core/CompileDelete.go | 4 ++- src/core/CompileNew.go | 4 ++- tests/programs/function-pointer-field.q | 6 ++--- tests/programs/function-pointer.q | 9 +++---- 8 files changed, 71 insertions(+), 44 deletions(-) create mode 100644 src/core/AfterCall.go create mode 100644 src/core/BeforeCall.go delete mode 100644 src/core/CallSafe.go diff --git a/src/core/AfterCall.go b/src/core/AfterCall.go new file mode 100644 index 0000000..0b074f0 --- /dev/null +++ b/src/core/AfterCall.go @@ -0,0 +1,21 @@ +package core + +import ( + "slices" + + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" +) + +// AfterCall pops used register values back from the stack. +func (f *Function) AfterCall(registers []cpu.Register) { + for _, register := range slices.Backward(f.CPU.General) { + if f.RegisterIsUsed(register) { + f.Register(asm.POP, register) + } + } + + for _, register := range registers { + f.FreeRegister(register) + } +} diff --git a/src/core/BeforeCall.go b/src/core/BeforeCall.go new file mode 100644 index 0000000..0d1af3d --- /dev/null +++ b/src/core/BeforeCall.go @@ -0,0 +1,18 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/asm" +) + +// BeforeCall pushes used registers to the stack. +func (f *Function) BeforeCall() { + for _, register := range f.CPU.Output { + f.SaveRegister(register) + } + + for _, register := range f.CPU.General { + if f.RegisterIsUsed(register) { + f.Register(asm.PUSH, register) + } + } +} diff --git a/src/core/CallSafe.go b/src/core/CallSafe.go deleted file mode 100644 index 924d209..0000000 --- a/src/core/CallSafe.go +++ /dev/null @@ -1,33 +0,0 @@ -package core - -import ( - "slices" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/cpu" -) - -// CallSafe pushes used registers to the stack, executes the call and restores the original register value. -func (f *Function) CallSafe(fn *Function, registers []cpu.Register) { - for _, register := range f.CPU.Output { - f.SaveRegister(register) - } - - for _, register := range f.CPU.General { - if f.RegisterIsUsed(register) { - f.Register(asm.PUSH, register) - } - } - - f.Label(asm.CALL, fn.UniqueName) - - for _, register := range slices.Backward(f.CPU.General) { - if f.RegisterIsUsed(register) { - f.Register(asm.POP, register) - } - } - - for _, register := range registers { - f.FreeRegister(register) - } -} diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index de531e7..ccd86ce 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -59,14 +59,32 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error return nil, err } - f.CallSafe(fn, registers) + f.BeforeCall() + f.Label(asm.CALL, value.Label) + f.AfterCall(registers) return fn.OutputTypes, nil case *eval.Register: + err := f.ExpressionsToRegisters(parameters, registers, nil, true) + + if err != nil { + return nil, err + } + + f.BeforeCall() f.Register(asm.CALL, value.Register) + f.AfterCall(registers) case *eval.Memory: + err := f.ExpressionsToRegisters(parameters, registers, nil, true) + + if err != nil { + return nil, err + } + + f.BeforeCall() f.Memory(asm.CALL, value.Memory) + f.AfterCall(registers) } return nil, nil diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index 8100b6f..5036e38 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -23,7 +23,9 @@ func (f *Function) CompileDelete(root *expression.Expression) error { f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Value.Register) f.RegisterNumber(asm.MOVE, f.CPU.Input[1], variable.Value.Typ.(*types.Pointer).To.Size()) free := f.All.Functions["mem.free"] - f.CallSafe(free, f.CPU.Input[:2]) + f.BeforeCall() + f.Label(asm.CALL, "mem.free") + f.AfterCall(f.CPU.Input[:2]) f.Dependencies = append(f.Dependencies, free) return nil } diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index 26a47ac..0a99a43 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -46,7 +46,9 @@ func (f *Function) CompileNew(root *expression.Expression) (types.Type, error) { f.SaveRegister(f.CPU.Input[0]) f.RegisterNumber(asm.MOVE, f.CPU.Input[0], typ.Size()) alloc := f.All.Functions["mem.alloc"] - f.CallSafe(alloc, f.CPU.Input[:1]) + f.BeforeCall() + f.Label(asm.CALL, "mem.alloc") + f.AfterCall(f.CPU.Input[:1]) f.Dependencies = append(f.Dependencies, alloc) return &types.Pointer{To: typ}, nil } diff --git a/tests/programs/function-pointer-field.q b/tests/programs/function-pointer-field.q index 8a8611a..1484842 100644 --- a/tests/programs/function-pointer-field.q +++ b/tests/programs/function-pointer-field.q @@ -7,10 +7,10 @@ struct Struct { main() { s := new(Struct) s.func = f - s.func() + s.func(0) sys.exit(1) } -f() { - sys.exit(0) +f(code int) { + sys.exit(code) } \ No newline at end of file diff --git a/tests/programs/function-pointer.q b/tests/programs/function-pointer.q index 784fa85..242a5ca 100644 --- a/tests/programs/function-pointer.q +++ b/tests/programs/function-pointer.q @@ -1,13 +1,12 @@ -import core import sys main() { func := f - func() + func(0) sys.exit(1) } -f() { - exit := core.exit - exit() +f(code int) { + exit := sys.exit + exit(code) } \ No newline at end of file From df6f7d5a57c0e5b1e7e637a18b99860eb0cb2d9d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 3 Mar 2025 14:32:11 +0100 Subject: [PATCH 0874/1012] Implemented parameter passing for function pointers --- src/core/AfterCall.go | 21 ++++++++++++++++ src/core/BeforeCall.go | 18 ++++++++++++++ src/core/CallSafe.go | 33 ------------------------- src/core/CompileCall.go | 20 ++++++++++++++- src/core/CompileDelete.go | 4 ++- src/core/CompileNew.go | 4 ++- tests/programs/function-pointer-field.q | 6 ++--- tests/programs/function-pointer.q | 9 +++---- 8 files changed, 71 insertions(+), 44 deletions(-) create mode 100644 src/core/AfterCall.go create mode 100644 src/core/BeforeCall.go delete mode 100644 src/core/CallSafe.go diff --git a/src/core/AfterCall.go b/src/core/AfterCall.go new file mode 100644 index 0000000..0b074f0 --- /dev/null +++ b/src/core/AfterCall.go @@ -0,0 +1,21 @@ +package core + +import ( + "slices" + + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/cpu" +) + +// AfterCall pops used register values back from the stack. +func (f *Function) AfterCall(registers []cpu.Register) { + for _, register := range slices.Backward(f.CPU.General) { + if f.RegisterIsUsed(register) { + f.Register(asm.POP, register) + } + } + + for _, register := range registers { + f.FreeRegister(register) + } +} diff --git a/src/core/BeforeCall.go b/src/core/BeforeCall.go new file mode 100644 index 0000000..0d1af3d --- /dev/null +++ b/src/core/BeforeCall.go @@ -0,0 +1,18 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/asm" +) + +// BeforeCall pushes used registers to the stack. +func (f *Function) BeforeCall() { + for _, register := range f.CPU.Output { + f.SaveRegister(register) + } + + for _, register := range f.CPU.General { + if f.RegisterIsUsed(register) { + f.Register(asm.PUSH, register) + } + } +} diff --git a/src/core/CallSafe.go b/src/core/CallSafe.go deleted file mode 100644 index 924d209..0000000 --- a/src/core/CallSafe.go +++ /dev/null @@ -1,33 +0,0 @@ -package core - -import ( - "slices" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/cpu" -) - -// CallSafe pushes used registers to the stack, executes the call and restores the original register value. -func (f *Function) CallSafe(fn *Function, registers []cpu.Register) { - for _, register := range f.CPU.Output { - f.SaveRegister(register) - } - - for _, register := range f.CPU.General { - if f.RegisterIsUsed(register) { - f.Register(asm.PUSH, register) - } - } - - f.Label(asm.CALL, fn.UniqueName) - - for _, register := range slices.Backward(f.CPU.General) { - if f.RegisterIsUsed(register) { - f.Register(asm.POP, register) - } - } - - for _, register := range registers { - f.FreeRegister(register) - } -} diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index de531e7..ccd86ce 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -59,14 +59,32 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error return nil, err } - f.CallSafe(fn, registers) + f.BeforeCall() + f.Label(asm.CALL, value.Label) + f.AfterCall(registers) return fn.OutputTypes, nil case *eval.Register: + err := f.ExpressionsToRegisters(parameters, registers, nil, true) + + if err != nil { + return nil, err + } + + f.BeforeCall() f.Register(asm.CALL, value.Register) + f.AfterCall(registers) case *eval.Memory: + err := f.ExpressionsToRegisters(parameters, registers, nil, true) + + if err != nil { + return nil, err + } + + f.BeforeCall() f.Memory(asm.CALL, value.Memory) + f.AfterCall(registers) } return nil, nil diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index 8100b6f..5036e38 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -23,7 +23,9 @@ func (f *Function) CompileDelete(root *expression.Expression) error { f.RegisterRegister(asm.MOVE, f.CPU.Input[0], variable.Value.Register) f.RegisterNumber(asm.MOVE, f.CPU.Input[1], variable.Value.Typ.(*types.Pointer).To.Size()) free := f.All.Functions["mem.free"] - f.CallSafe(free, f.CPU.Input[:2]) + f.BeforeCall() + f.Label(asm.CALL, "mem.free") + f.AfterCall(f.CPU.Input[:2]) f.Dependencies = append(f.Dependencies, free) return nil } diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index 26a47ac..0a99a43 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -46,7 +46,9 @@ func (f *Function) CompileNew(root *expression.Expression) (types.Type, error) { f.SaveRegister(f.CPU.Input[0]) f.RegisterNumber(asm.MOVE, f.CPU.Input[0], typ.Size()) alloc := f.All.Functions["mem.alloc"] - f.CallSafe(alloc, f.CPU.Input[:1]) + f.BeforeCall() + f.Label(asm.CALL, "mem.alloc") + f.AfterCall(f.CPU.Input[:1]) f.Dependencies = append(f.Dependencies, alloc) return &types.Pointer{To: typ}, nil } diff --git a/tests/programs/function-pointer-field.q b/tests/programs/function-pointer-field.q index 8a8611a..1484842 100644 --- a/tests/programs/function-pointer-field.q +++ b/tests/programs/function-pointer-field.q @@ -7,10 +7,10 @@ struct Struct { main() { s := new(Struct) s.func = f - s.func() + s.func(0) sys.exit(1) } -f() { - sys.exit(0) +f(code int) { + sys.exit(code) } \ No newline at end of file diff --git a/tests/programs/function-pointer.q b/tests/programs/function-pointer.q index 784fa85..242a5ca 100644 --- a/tests/programs/function-pointer.q +++ b/tests/programs/function-pointer.q @@ -1,13 +1,12 @@ -import core import sys main() { func := f - func() + func(0) sys.exit(1) } -f() { - exit := core.exit - exit() +f(code int) { + exit := sys.exit + exit(code) } \ No newline at end of file From ada7aaa1e201a4121d7586df3b9ac55dd24c7499 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 4 Mar 2025 13:35:55 +0100 Subject: [PATCH 0875/1012] Removed dead code --- lib/thread/thread_linux.q | 18 +++++++++--------- lib/thread/thread_windows.q | 8 ++++---- src/core/CallExtern.go | 5 ++--- src/core/CompileCall.go | 25 ++++++++++++++++++++----- src/core/CompileLen.go | 2 -- src/core/EvaluateCall.go | 6 +++--- src/core/EvaluateToken.go | 8 ++++++-- src/core/Function.go | 4 +--- src/core/Identifier.go | 25 ------------------------- src/core/MultiDefine.go | 6 +++--- src/core/Parameter.go | 4 ++++ src/core/ResolveTypes.go | 2 -- src/core/TypeByName.go | 1 - src/core/UsesRegister.go | 3 ++- 14 files changed, 54 insertions(+), 63 deletions(-) delete mode 100644 src/core/Identifier.go delete mode 100644 src/core/TypeByName.go diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index 592207b..7d3429c 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -1,6 +1,15 @@ import core import sys +create(func *any) -> int { + stack := sys.mmap(0, 4096, 0x1|0x2, 0x02|0x20|0x100) + stack += 4096 - 8 + store(stack, 8, core.exit) + stack -= 8 + store(stack, 8, func) + return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0) +} + const clone { vm 0x100 fs 0x200 @@ -9,13 +18,4 @@ const clone { parent 0x8000 thread 0x10000 io 0x80000000 -} - -create(func *any) -> int { - stack := sys.mmap(0, 4096, 0x1|0x2, 0x02|0x20|0x100) - stack += 4096 - 8 - store(stack, 8, core.exit) - stack -= 8 - store(stack, 8, func) - return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0) } \ No newline at end of file diff --git a/lib/thread/thread_windows.q b/lib/thread/thread_windows.q index 0885bd1..7bb565f 100644 --- a/lib/thread/thread_windows.q +++ b/lib/thread/thread_windows.q @@ -1,7 +1,7 @@ -extern kernel32 { - CreateThread(attributes int, stackSize int, address *any, parameter int) -> int -} - create(func *any) -> int { return kernel32.CreateThread(0, 4096, func, 0) +} + +extern kernel32 { + CreateThread(attributes int, stackSize int, address *any, parameter int) -> int } \ No newline at end of file diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go index 2b17fc1..588761f 100644 --- a/src/core/CallExtern.go +++ b/src/core/CallExtern.go @@ -7,12 +7,11 @@ import ( "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/expression" - "git.urbach.dev/cli/q/src/types" "git.urbach.dev/cli/q/src/x86" ) // CallExtern calls an external function. -func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) ([]types.Type, error) { +func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) ([]*Parameter, error) { f.DLLs = f.DLLs.Append(fn.Package, fn.Name) var pushedRegisters []cpu.Register @@ -50,5 +49,5 @@ func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) f.Register(asm.POP, register) } - return fn.OutputTypes, nil + return fn.Output, nil } diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index ccd86ce..004bf72 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -13,20 +13,35 @@ import ( // All call registers must hold the correct parameter values before the function invocation. // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. -func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error) { +func (f *Function) CompileCall(root *expression.Expression) ([]*Parameter, error) { if root.Children[0].Token.Kind == token.Identifier { name := root.Children[0].Token.Text(f.File.Bytes) switch name { - case "len": - return _len.OutputTypes, f.CompileLen(root) case "syscall": return nil, f.CompileSyscall(root) + + case "len": + output := []*Parameter{{ + name: "length", + typ: types.AnyInt, + }} + + return output, f.CompileLen(root) + case "new": typ, err := f.CompileNew(root) - return []types.Type{typ}, err + + output := []*Parameter{{ + name: "address", + typ: typ, + }} + + return output, err + case "delete": return nil, f.CompileDelete(root) + case "store": return nil, f.CompileMemoryStore(root) } @@ -62,7 +77,7 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error f.BeforeCall() f.Label(asm.CALL, value.Label) f.AfterCall(registers) - return fn.OutputTypes, nil + return fn.Output, nil case *eval.Register: err := f.ExpressionsToRegisters(parameters, registers, nil, true) diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index 64f3cf5..db20d71 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -11,8 +11,6 @@ import ( "git.urbach.dev/cli/q/src/types" ) -var _len = Function{OutputTypes: []types.Type{types.AnyInt}} - // CompileLen returns the length of a slice. func (f *Function) CompileLen(root *expression.Expression) error { value, err := f.Evaluate(root.Children[1]) diff --git a/src/core/EvaluateCall.go b/src/core/EvaluateCall.go index ad092a0..4995cd1 100644 --- a/src/core/EvaluateCall.go +++ b/src/core/EvaluateCall.go @@ -51,7 +51,7 @@ func (f *Function) EvaluateCall(expr *expression.Expression) (eval.Value, error) } } - typ, err := f.CompileCall(expr) + output, err := f.CompileCall(expr) if err != nil { return nil, err @@ -59,8 +59,8 @@ func (f *Function) EvaluateCall(expr *expression.Expression) (eval.Value, error) value := &eval.Register{Register: f.CPU.Output[0]} - if len(typ) > 0 { - value.Typ = typ[0] + if len(output) > 0 { + value.Typ = output[0].typ } return value, nil diff --git a/src/core/EvaluateToken.go b/src/core/EvaluateToken.go index 459aa2a..f3125c8 100644 --- a/src/core/EvaluateToken.go +++ b/src/core/EvaluateToken.go @@ -2,6 +2,7 @@ package core import ( "encoding/binary" + "fmt" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" @@ -33,14 +34,17 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { return value, nil } - variable, function := f.Identifier(name) + variable := f.VariableByName(name) if variable != nil { f.UseVariable(variable) return &variable.Value, nil } - if function != nil { + uniqueName := fmt.Sprintf("%s.%s", f.Package, name) + function, exists := f.All.Functions[uniqueName] + + if exists { f.Dependencies = append(f.Dependencies, function) value := &eval.Label{ diff --git a/src/core/Function.go b/src/core/Function.go index ac09a62..32de2cf 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -5,10 +5,9 @@ import ( "git.urbach.dev/cli/q/src/fs" "git.urbach.dev/cli/q/src/register" "git.urbach.dev/cli/q/src/token" - "git.urbach.dev/cli/q/src/types" ) -// Function represents the smallest unit of code. +// Function is the smallest unit of code. type Function struct { register.Machine Package string @@ -19,7 +18,6 @@ type Function struct { Body token.List Input []*Parameter Output []*Parameter - OutputTypes []types.Type Dependencies []*Function DLLs dll.List Err error diff --git a/src/core/Identifier.go b/src/core/Identifier.go deleted file mode 100644 index ab51f9e..0000000 --- a/src/core/Identifier.go +++ /dev/null @@ -1,25 +0,0 @@ -package core - -import ( - "fmt" - - "git.urbach.dev/cli/q/src/scope" -) - -// Identifier looks up an identifier which can be a variable or a function. -func (f *Function) Identifier(name string) (*scope.Variable, *Function) { - variable := f.VariableByName(name) - - if variable != nil { - return variable, nil - } - - uniqueName := fmt.Sprintf("%s.%s", f.Package, name) - function, exists := f.All.Functions[uniqueName] - - if exists { - return nil, function - } - - return nil, nil -} diff --git a/src/core/MultiDefine.go b/src/core/MultiDefine.go index 045afb6..6240c69 100644 --- a/src/core/MultiDefine.go +++ b/src/core/MultiDefine.go @@ -8,7 +8,7 @@ import ( // MultiDefine defines multiple variables at once. func (f *Function) MultiDefine(left *expression.Expression, right *expression.Expression) error { count := 0 - types, err := f.CompileCall(right) + output, err := f.CompileCall(right) if err != nil { return err @@ -21,8 +21,8 @@ func (f *Function) MultiDefine(left *expression.Expression, right *expression.Ex return err } - if count < len(types) { - variable.Value.Typ = types[count] + if count < len(output) { + variable.Value.Typ = output[count].typ } f.RegisterRegister(asm.MOVE, variable.Value.Register, f.CPU.Output[count]) diff --git a/src/core/Parameter.go b/src/core/Parameter.go index 8c89249..91a5b07 100644 --- a/src/core/Parameter.go +++ b/src/core/Parameter.go @@ -5,20 +5,24 @@ import ( "git.urbach.dev/cli/q/src/types" ) +// Parameter is an input or output parameter in a function. type Parameter struct { name string typ types.Type tokens token.List } +// NewParameter creates a new parameter with the given list of tokens. func NewParameter(tokens token.List) *Parameter { return &Parameter{tokens: tokens} } +// Name returns the name of the parameter. func (p *Parameter) Name() string { return p.name } +// Type returns the type of the parameter. func (p *Parameter) Type() types.Type { return p.typ } diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index 6df48c1..e10e41e 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -47,8 +47,6 @@ func (f *Function) ResolveTypes() error { if param.typ == nil { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[0].Position) } - - f.OutputTypes = append(f.OutputTypes, param.typ) } return nil diff --git a/src/core/TypeByName.go b/src/core/TypeByName.go deleted file mode 100644 index 9a8bc95..0000000 --- a/src/core/TypeByName.go +++ /dev/null @@ -1 +0,0 @@ -package core diff --git a/src/core/UsesRegister.go b/src/core/UsesRegister.go index cd4ab79..aabfa40 100644 --- a/src/core/UsesRegister.go +++ b/src/core/UsesRegister.go @@ -14,7 +14,8 @@ func (f *Function) UsesRegister(expr *expression.Expression, register cpu.Regist return false } - variable := f.VariableByName(expr.Token.Text(f.File.Bytes)) + name := expr.Token.Text(f.File.Bytes) + variable := f.VariableByName(name) if variable == nil || variable.Value.Register != register { return false From e5f0123eea6c5ab8155f3e74838e9972a8151f8a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 4 Mar 2025 13:35:55 +0100 Subject: [PATCH 0876/1012] Removed dead code --- lib/thread/thread_linux.q | 18 +++++++++--------- lib/thread/thread_windows.q | 8 ++++---- src/core/CallExtern.go | 5 ++--- src/core/CompileCall.go | 25 ++++++++++++++++++++----- src/core/CompileLen.go | 2 -- src/core/EvaluateCall.go | 6 +++--- src/core/EvaluateToken.go | 8 ++++++-- src/core/Function.go | 4 +--- src/core/Identifier.go | 25 ------------------------- src/core/MultiDefine.go | 6 +++--- src/core/Parameter.go | 4 ++++ src/core/ResolveTypes.go | 2 -- src/core/TypeByName.go | 1 - src/core/UsesRegister.go | 3 ++- 14 files changed, 54 insertions(+), 63 deletions(-) delete mode 100644 src/core/Identifier.go delete mode 100644 src/core/TypeByName.go diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index 592207b..7d3429c 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -1,6 +1,15 @@ import core import sys +create(func *any) -> int { + stack := sys.mmap(0, 4096, 0x1|0x2, 0x02|0x20|0x100) + stack += 4096 - 8 + store(stack, 8, core.exit) + stack -= 8 + store(stack, 8, func) + return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0) +} + const clone { vm 0x100 fs 0x200 @@ -9,13 +18,4 @@ const clone { parent 0x8000 thread 0x10000 io 0x80000000 -} - -create(func *any) -> int { - stack := sys.mmap(0, 4096, 0x1|0x2, 0x02|0x20|0x100) - stack += 4096 - 8 - store(stack, 8, core.exit) - stack -= 8 - store(stack, 8, func) - return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0) } \ No newline at end of file diff --git a/lib/thread/thread_windows.q b/lib/thread/thread_windows.q index 0885bd1..7bb565f 100644 --- a/lib/thread/thread_windows.q +++ b/lib/thread/thread_windows.q @@ -1,7 +1,7 @@ -extern kernel32 { - CreateThread(attributes int, stackSize int, address *any, parameter int) -> int -} - create(func *any) -> int { return kernel32.CreateThread(0, 4096, func, 0) +} + +extern kernel32 { + CreateThread(attributes int, stackSize int, address *any, parameter int) -> int } \ No newline at end of file diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go index 2b17fc1..588761f 100644 --- a/src/core/CallExtern.go +++ b/src/core/CallExtern.go @@ -7,12 +7,11 @@ import ( "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/expression" - "git.urbach.dev/cli/q/src/types" "git.urbach.dev/cli/q/src/x86" ) // CallExtern calls an external function. -func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) ([]types.Type, error) { +func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) ([]*Parameter, error) { f.DLLs = f.DLLs.Append(fn.Package, fn.Name) var pushedRegisters []cpu.Register @@ -50,5 +49,5 @@ func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) f.Register(asm.POP, register) } - return fn.OutputTypes, nil + return fn.Output, nil } diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index ccd86ce..004bf72 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -13,20 +13,35 @@ import ( // All call registers must hold the correct parameter values before the function invocation. // Registers that are in use must be saved if they are modified by the function. // After the function call, they must be restored in reverse order. -func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error) { +func (f *Function) CompileCall(root *expression.Expression) ([]*Parameter, error) { if root.Children[0].Token.Kind == token.Identifier { name := root.Children[0].Token.Text(f.File.Bytes) switch name { - case "len": - return _len.OutputTypes, f.CompileLen(root) case "syscall": return nil, f.CompileSyscall(root) + + case "len": + output := []*Parameter{{ + name: "length", + typ: types.AnyInt, + }} + + return output, f.CompileLen(root) + case "new": typ, err := f.CompileNew(root) - return []types.Type{typ}, err + + output := []*Parameter{{ + name: "address", + typ: typ, + }} + + return output, err + case "delete": return nil, f.CompileDelete(root) + case "store": return nil, f.CompileMemoryStore(root) } @@ -62,7 +77,7 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error f.BeforeCall() f.Label(asm.CALL, value.Label) f.AfterCall(registers) - return fn.OutputTypes, nil + return fn.Output, nil case *eval.Register: err := f.ExpressionsToRegisters(parameters, registers, nil, true) diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index 64f3cf5..db20d71 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -11,8 +11,6 @@ import ( "git.urbach.dev/cli/q/src/types" ) -var _len = Function{OutputTypes: []types.Type{types.AnyInt}} - // CompileLen returns the length of a slice. func (f *Function) CompileLen(root *expression.Expression) error { value, err := f.Evaluate(root.Children[1]) diff --git a/src/core/EvaluateCall.go b/src/core/EvaluateCall.go index ad092a0..4995cd1 100644 --- a/src/core/EvaluateCall.go +++ b/src/core/EvaluateCall.go @@ -51,7 +51,7 @@ func (f *Function) EvaluateCall(expr *expression.Expression) (eval.Value, error) } } - typ, err := f.CompileCall(expr) + output, err := f.CompileCall(expr) if err != nil { return nil, err @@ -59,8 +59,8 @@ func (f *Function) EvaluateCall(expr *expression.Expression) (eval.Value, error) value := &eval.Register{Register: f.CPU.Output[0]} - if len(typ) > 0 { - value.Typ = typ[0] + if len(output) > 0 { + value.Typ = output[0].typ } return value, nil diff --git a/src/core/EvaluateToken.go b/src/core/EvaluateToken.go index 459aa2a..f3125c8 100644 --- a/src/core/EvaluateToken.go +++ b/src/core/EvaluateToken.go @@ -2,6 +2,7 @@ package core import ( "encoding/binary" + "fmt" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" @@ -33,14 +34,17 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { return value, nil } - variable, function := f.Identifier(name) + variable := f.VariableByName(name) if variable != nil { f.UseVariable(variable) return &variable.Value, nil } - if function != nil { + uniqueName := fmt.Sprintf("%s.%s", f.Package, name) + function, exists := f.All.Functions[uniqueName] + + if exists { f.Dependencies = append(f.Dependencies, function) value := &eval.Label{ diff --git a/src/core/Function.go b/src/core/Function.go index ac09a62..32de2cf 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -5,10 +5,9 @@ import ( "git.urbach.dev/cli/q/src/fs" "git.urbach.dev/cli/q/src/register" "git.urbach.dev/cli/q/src/token" - "git.urbach.dev/cli/q/src/types" ) -// Function represents the smallest unit of code. +// Function is the smallest unit of code. type Function struct { register.Machine Package string @@ -19,7 +18,6 @@ type Function struct { Body token.List Input []*Parameter Output []*Parameter - OutputTypes []types.Type Dependencies []*Function DLLs dll.List Err error diff --git a/src/core/Identifier.go b/src/core/Identifier.go deleted file mode 100644 index ab51f9e..0000000 --- a/src/core/Identifier.go +++ /dev/null @@ -1,25 +0,0 @@ -package core - -import ( - "fmt" - - "git.urbach.dev/cli/q/src/scope" -) - -// Identifier looks up an identifier which can be a variable or a function. -func (f *Function) Identifier(name string) (*scope.Variable, *Function) { - variable := f.VariableByName(name) - - if variable != nil { - return variable, nil - } - - uniqueName := fmt.Sprintf("%s.%s", f.Package, name) - function, exists := f.All.Functions[uniqueName] - - if exists { - return nil, function - } - - return nil, nil -} diff --git a/src/core/MultiDefine.go b/src/core/MultiDefine.go index 045afb6..6240c69 100644 --- a/src/core/MultiDefine.go +++ b/src/core/MultiDefine.go @@ -8,7 +8,7 @@ import ( // MultiDefine defines multiple variables at once. func (f *Function) MultiDefine(left *expression.Expression, right *expression.Expression) error { count := 0 - types, err := f.CompileCall(right) + output, err := f.CompileCall(right) if err != nil { return err @@ -21,8 +21,8 @@ func (f *Function) MultiDefine(left *expression.Expression, right *expression.Ex return err } - if count < len(types) { - variable.Value.Typ = types[count] + if count < len(output) { + variable.Value.Typ = output[count].typ } f.RegisterRegister(asm.MOVE, variable.Value.Register, f.CPU.Output[count]) diff --git a/src/core/Parameter.go b/src/core/Parameter.go index 8c89249..91a5b07 100644 --- a/src/core/Parameter.go +++ b/src/core/Parameter.go @@ -5,20 +5,24 @@ import ( "git.urbach.dev/cli/q/src/types" ) +// Parameter is an input or output parameter in a function. type Parameter struct { name string typ types.Type tokens token.List } +// NewParameter creates a new parameter with the given list of tokens. func NewParameter(tokens token.List) *Parameter { return &Parameter{tokens: tokens} } +// Name returns the name of the parameter. func (p *Parameter) Name() string { return p.name } +// Type returns the type of the parameter. func (p *Parameter) Type() types.Type { return p.typ } diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index 6df48c1..e10e41e 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -47,8 +47,6 @@ func (f *Function) ResolveTypes() error { if param.typ == nil { return errors.New(&errors.UnknownType{Name: typeName}, f.File, param.tokens[0].Position) } - - f.OutputTypes = append(f.OutputTypes, param.typ) } return nil diff --git a/src/core/TypeByName.go b/src/core/TypeByName.go deleted file mode 100644 index 9a8bc95..0000000 --- a/src/core/TypeByName.go +++ /dev/null @@ -1 +0,0 @@ -package core diff --git a/src/core/UsesRegister.go b/src/core/UsesRegister.go index cd4ab79..aabfa40 100644 --- a/src/core/UsesRegister.go +++ b/src/core/UsesRegister.go @@ -14,7 +14,8 @@ func (f *Function) UsesRegister(expr *expression.Expression, register cpu.Regist return false } - variable := f.VariableByName(expr.Token.Text(f.File.Bytes)) + name := expr.Token.Text(f.File.Bytes) + variable := f.VariableByName(name) if variable == nil || variable.Value.Register != register { return false From bfa8f9c7c4aab78d1080a05dfd732be5fcafb452 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 4 Mar 2025 16:54:17 +0100 Subject: [PATCH 0877/1012] Improved performance of the data finalizer --- src/asmc/compile.go | 2 +- src/core/CallExtern.go | 3 +-- src/data/Finalize.go | 7 +++++-- src/data/bench_test.go | 21 +++++++++++++++++++++ src/x86/Push.go | 6 +----- src/x86/Push_test.go | 2 +- 6 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 src/data/bench_test.go diff --git a/src/asmc/compile.go b/src/asmc/compile.go index dfcddca..41169c9 100644 --- a/src/asmc/compile.go +++ b/src/asmc/compile.go @@ -121,7 +121,7 @@ func (c *compiler) compile(x asm.Instruction) { case asm.PUSH: switch operands := x.Data.(type) { case *asm.Number: - c.code = x86.PushNumber(c.code, operands.Number) + c.code = x86.PushNumber(c.code, int32(operands.Number)) case *asm.Register: c.code = x86.PushRegister(c.code, operands.Register) } diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go index 588761f..241c15a 100644 --- a/src/core/CallExtern.go +++ b/src/core/CallExtern.go @@ -1,7 +1,6 @@ package core import ( - "fmt" "slices" "git.urbach.dev/cli/q/src/asm" @@ -37,7 +36,7 @@ func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) f.Number(asm.PUSH, 0) f.Number(asm.PUSH, 0) f.RegisterNumber(asm.SUB, x86.RSP, 32) - f.DLLCall(fmt.Sprintf("%s.%s", fn.Package, fn.Name)) + f.DLLCall(fn.UniqueName) f.RegisterRegister(asm.MOVE, x86.RSP, x86.RBP) f.Register(asm.POP, x86.RBP) diff --git a/src/data/Finalize.go b/src/data/Finalize.go index b890ae0..aa0787a 100644 --- a/src/data/Finalize.go +++ b/src/data/Finalize.go @@ -9,19 +9,22 @@ import ( // It will try to reuse existing data whenever possible. func (data Data) Finalize() ([]byte, map[string]int32) { var ( - final []byte keys = make([]string, 0, len(data)) positions = make(map[string]int32, len(data)) + capacity = 0 ) - for key := range data { + for key, value := range data { keys = append(keys, key) + capacity += len(value) } sort.SliceStable(keys, func(i, j int) bool { return len(data[keys[i]]) > len(data[keys[j]]) }) + final := make([]byte, 0, capacity) + for _, key := range keys { raw := data[key] position := bytes.Index(final, raw) diff --git a/src/data/bench_test.go b/src/data/bench_test.go new file mode 100644 index 0000000..3c6987e --- /dev/null +++ b/src/data/bench_test.go @@ -0,0 +1,21 @@ +package data_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/data" +) + +func BenchmarkFinalize(b *testing.B) { + d := data.Data{} + d.Insert("1", []byte("Beautiful is better than ugly.")) + d.Insert("2", []byte("Explicit is better than implicit.")) + d.Insert("3", []byte("Simple is better than complex.")) + d.Insert("4", []byte("Complex is better than complicated.")) + d.Insert("5", []byte("Flat is better than nested.")) + d.Insert("6", []byte("Sparse is better than dense.")) + + for b.Loop() { + d.Finalize() + } +} diff --git a/src/x86/Push.go b/src/x86/Push.go index 9a2c233..0ef49e8 100644 --- a/src/x86/Push.go +++ b/src/x86/Push.go @@ -6,13 +6,9 @@ import ( ) // PushNumber pushes a number onto the stack. -func PushNumber(code []byte, number int) []byte { +func PushNumber(code []byte, number int32) []byte { length := sizeof.Signed(number) - if length >= 8 { - panic("x86 does not support pushing 64-bit numbers") - } - if length >= 2 { return append( code, diff --git a/src/x86/Push_test.go b/src/x86/Push_test.go index 30f1a68..138478d 100644 --- a/src/x86/Push_test.go +++ b/src/x86/Push_test.go @@ -10,7 +10,7 @@ import ( func TestPushNumber(t *testing.T) { usagePatterns := []struct { - Number int + Number int32 Code []byte }{ {0, []byte{0x6A, 0x00}}, From f2db22368498a001837fad61f3a1d8ec318ed917 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 4 Mar 2025 16:54:17 +0100 Subject: [PATCH 0878/1012] Improved performance of the data finalizer --- src/asmc/compile.go | 2 +- src/core/CallExtern.go | 3 +-- src/data/Finalize.go | 7 +++++-- src/data/bench_test.go | 21 +++++++++++++++++++++ src/x86/Push.go | 6 +----- src/x86/Push_test.go | 2 +- 6 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 src/data/bench_test.go diff --git a/src/asmc/compile.go b/src/asmc/compile.go index dfcddca..41169c9 100644 --- a/src/asmc/compile.go +++ b/src/asmc/compile.go @@ -121,7 +121,7 @@ func (c *compiler) compile(x asm.Instruction) { case asm.PUSH: switch operands := x.Data.(type) { case *asm.Number: - c.code = x86.PushNumber(c.code, operands.Number) + c.code = x86.PushNumber(c.code, int32(operands.Number)) case *asm.Register: c.code = x86.PushRegister(c.code, operands.Register) } diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go index 588761f..241c15a 100644 --- a/src/core/CallExtern.go +++ b/src/core/CallExtern.go @@ -1,7 +1,6 @@ package core import ( - "fmt" "slices" "git.urbach.dev/cli/q/src/asm" @@ -37,7 +36,7 @@ func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) f.Number(asm.PUSH, 0) f.Number(asm.PUSH, 0) f.RegisterNumber(asm.SUB, x86.RSP, 32) - f.DLLCall(fmt.Sprintf("%s.%s", fn.Package, fn.Name)) + f.DLLCall(fn.UniqueName) f.RegisterRegister(asm.MOVE, x86.RSP, x86.RBP) f.Register(asm.POP, x86.RBP) diff --git a/src/data/Finalize.go b/src/data/Finalize.go index b890ae0..aa0787a 100644 --- a/src/data/Finalize.go +++ b/src/data/Finalize.go @@ -9,19 +9,22 @@ import ( // It will try to reuse existing data whenever possible. func (data Data) Finalize() ([]byte, map[string]int32) { var ( - final []byte keys = make([]string, 0, len(data)) positions = make(map[string]int32, len(data)) + capacity = 0 ) - for key := range data { + for key, value := range data { keys = append(keys, key) + capacity += len(value) } sort.SliceStable(keys, func(i, j int) bool { return len(data[keys[i]]) > len(data[keys[j]]) }) + final := make([]byte, 0, capacity) + for _, key := range keys { raw := data[key] position := bytes.Index(final, raw) diff --git a/src/data/bench_test.go b/src/data/bench_test.go new file mode 100644 index 0000000..3c6987e --- /dev/null +++ b/src/data/bench_test.go @@ -0,0 +1,21 @@ +package data_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/data" +) + +func BenchmarkFinalize(b *testing.B) { + d := data.Data{} + d.Insert("1", []byte("Beautiful is better than ugly.")) + d.Insert("2", []byte("Explicit is better than implicit.")) + d.Insert("3", []byte("Simple is better than complex.")) + d.Insert("4", []byte("Complex is better than complicated.")) + d.Insert("5", []byte("Flat is better than nested.")) + d.Insert("6", []byte("Sparse is better than dense.")) + + for b.Loop() { + d.Finalize() + } +} diff --git a/src/x86/Push.go b/src/x86/Push.go index 9a2c233..0ef49e8 100644 --- a/src/x86/Push.go +++ b/src/x86/Push.go @@ -6,13 +6,9 @@ import ( ) // PushNumber pushes a number onto the stack. -func PushNumber(code []byte, number int) []byte { +func PushNumber(code []byte, number int32) []byte { length := sizeof.Signed(number) - if length >= 8 { - panic("x86 does not support pushing 64-bit numbers") - } - if length >= 2 { return append( code, diff --git a/src/x86/Push_test.go b/src/x86/Push_test.go index 30f1a68..138478d 100644 --- a/src/x86/Push_test.go +++ b/src/x86/Push_test.go @@ -10,7 +10,7 @@ import ( func TestPushNumber(t *testing.T) { usagePatterns := []struct { - Number int + Number int32 Code []byte }{ {0, []byte{0x6A, 0x00}}, From cc1c990dc8b492f6e76aca0ca06430dce6b7fcdd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 5 Mar 2025 14:41:58 +0100 Subject: [PATCH 0879/1012] Removed unnecessary list of registers --- src/core/NewFunction.go | 2 +- src/core/PrintInstructions.go | 2 +- src/cpu/CPU.go | 2 +- src/x86/Registers.go | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/core/NewFunction.go b/src/core/NewFunction.go index dabb185..1450660 100644 --- a/src/core/NewFunction.go +++ b/src/core/NewFunction.go @@ -24,12 +24,12 @@ func NewFunction(pkg string, name string, file *fs.File) *Function { Scopes: []*scope.Scope{{}}, }, CPU: cpu.CPU{ - All: x86.AllRegisters, General: x86.GeneralRegisters, Input: x86.InputRegisters, Output: x86.OutputRegisters, SyscallInput: x86.SyscallInputRegisters, SyscallOutput: x86.SyscallOutputRegisters, + NumRegisters: 16, }, }, } diff --git a/src/core/PrintInstructions.go b/src/core/PrintInstructions.go index 69cfc26..cc81953 100644 --- a/src/core/PrintInstructions.go +++ b/src/core/PrintInstructions.go @@ -44,7 +44,7 @@ func (f *Function) PrintInstructions() { registers := bytes.Buffer{} used := f.RegisterHistory[i] - for _, reg := range f.CPU.All { + for reg := range f.CPU.NumRegisters { if used&(1< Date: Wed, 5 Mar 2025 14:41:58 +0100 Subject: [PATCH 0880/1012] Removed unnecessary list of registers --- src/core/NewFunction.go | 2 +- src/core/PrintInstructions.go | 2 +- src/cpu/CPU.go | 2 +- src/x86/Registers.go | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/core/NewFunction.go b/src/core/NewFunction.go index dabb185..1450660 100644 --- a/src/core/NewFunction.go +++ b/src/core/NewFunction.go @@ -24,12 +24,12 @@ func NewFunction(pkg string, name string, file *fs.File) *Function { Scopes: []*scope.Scope{{}}, }, CPU: cpu.CPU{ - All: x86.AllRegisters, General: x86.GeneralRegisters, Input: x86.InputRegisters, Output: x86.OutputRegisters, SyscallInput: x86.SyscallInputRegisters, SyscallOutput: x86.SyscallOutputRegisters, + NumRegisters: 16, }, }, } diff --git a/src/core/PrintInstructions.go b/src/core/PrintInstructions.go index 69cfc26..cc81953 100644 --- a/src/core/PrintInstructions.go +++ b/src/core/PrintInstructions.go @@ -44,7 +44,7 @@ func (f *Function) PrintInstructions() { registers := bytes.Buffer{} used := f.RegisterHistory[i] - for _, reg := range f.CPU.All { + for reg := range f.CPU.NumRegisters { if used&(1< Date: Thu, 6 Mar 2025 13:40:17 +0100 Subject: [PATCH 0881/1012] Added basic support for arm64 --- src/arm/Call.go | 10 ++++++ src/arm/Move.go | 29 +++++++++++++++++ src/arm/Move_test.go | 43 ++++++++++++++++++++++++++ src/arm/Nop.go | 6 ++++ src/arm/Registers.go | 13 ++++++++ src/arm/Return.go | 6 ++++ src/arm/Syscall.go | 6 ++++ src/arm/arm_test.go | 16 ++++++++++ src/asmc/Finalize.go | 17 ++++++++-- src/asmc/compileARM.go | 25 +++++++++++++++ src/asmc/{compile.go => compileX86.go} | 2 +- src/cli/Build.go | 11 +++++-- src/cli/Help.go | 2 +- src/config/arch.go | 9 ++++++ src/config/config.go | 24 +++++++++----- src/config/os.go | 2 +- src/core/NewFunction.go | 20 +++++++----- src/elf/Constants.go | 1 + src/elf/ELF.go | 10 +++++- src/macho/Constants.go | 5 +++ src/macho/MachO.go | 15 +++++++-- src/pe/EXE.go | 10 +++++- src/register/Machine.go | 2 +- src/x86/Call.go | 10 +++--- src/x86/Registers.go | 9 ++++++ 25 files changed, 270 insertions(+), 33 deletions(-) create mode 100644 src/arm/Call.go create mode 100644 src/arm/Move.go create mode 100644 src/arm/Move_test.go create mode 100644 src/arm/Nop.go create mode 100644 src/arm/Return.go create mode 100644 src/arm/Syscall.go create mode 100644 src/arm/arm_test.go create mode 100644 src/asmc/compileARM.go rename src/asmc/{compile.go => compileX86.go} (98%) create mode 100644 src/config/arch.go diff --git a/src/arm/Call.go b/src/arm/Call.go new file mode 100644 index 0000000..1a4fd3f --- /dev/null +++ b/src/arm/Call.go @@ -0,0 +1,10 @@ +package arm + +import "encoding/binary" + +// Call branches to a PC-relative offset, setting the register X30 to PC+4. +// The offset starts from the address of this instruction and is encoded as "imm26" times 4. +// This instruction is also known as BL (branch with link). +func Call(code []byte, offset uint32) []byte { + return binary.LittleEndian.AppendUint32(code, uint32(0b100101<<26)|offset) +} diff --git a/src/arm/Move.go b/src/arm/Move.go new file mode 100644 index 0000000..f26744f --- /dev/null +++ b/src/arm/Move.go @@ -0,0 +1,29 @@ +package arm + +import ( + "encoding/binary" + + "git.urbach.dev/cli/q/src/cpu" +) + +// MoveRegisterNumber moves an integer into the given register. +func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + return MoveZero(code, destination, 0, uint16(number)) +} + +// MoveKeep moves a 16-bit integer into the given register and keeps all other bits. +func MoveKeep(code []byte, destination cpu.Register, halfword int, number uint16) []byte { + x := mov(0b11, halfword, number, destination) + return binary.LittleEndian.AppendUint32(code, x) +} + +// MoveZero moves a 16-bit integer into the given register and clears all other bits to zero. +func MoveZero(code []byte, destination cpu.Register, halfword int, number uint16) []byte { + x := mov(0b10, halfword, number, destination) + return binary.LittleEndian.AppendUint32(code, x) +} + +// mov encodes a generic move instruction. +func mov(opCode uint32, halfword int, number uint16, destination cpu.Register) uint32 { + return (1 << 31) | (opCode << 29) | (0b100101 << 23) | uint32(halfword<<21) | uint32(number<<5) | uint32(destination) +} diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go new file mode 100644 index 0000000..fb7f7d3 --- /dev/null +++ b/src/arm/Move_test.go @@ -0,0 +1,43 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestMoveKeep(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number uint16 + Code []byte + }{ + {arm.X0, 0, []byte{0x00, 0x00, 0x80, 0xF2}}, + {arm.X0, 1, []byte{0x20, 0x00, 0x80, 0xF2}}, + } + + for _, pattern := range usagePatterns { + t.Logf("movk %s, %x", pattern.Register, pattern.Number) + code := arm.MoveKeep(nil, pattern.Register, 0, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestMoveZero(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number uint16 + Code []byte + }{ + {arm.X0, 0, []byte{0x00, 0x00, 0x80, 0xD2}}, + {arm.X0, 1, []byte{0x20, 0x00, 0x80, 0xD2}}, + } + + for _, pattern := range usagePatterns { + t.Logf("movz %s, %x", pattern.Register, pattern.Number) + code := arm.MoveZero(nil, pattern.Register, 0, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Nop.go b/src/arm/Nop.go new file mode 100644 index 0000000..8152293 --- /dev/null +++ b/src/arm/Nop.go @@ -0,0 +1,6 @@ +package arm + +// Nop does nothing. This can be used for alignment purposes. +func Nop(code []byte) []byte { + return append(code, 0x1F, 0x20, 0x03, 0xD5) +} diff --git a/src/arm/Registers.go b/src/arm/Registers.go index 86d78d2..62c7814 100644 --- a/src/arm/Registers.go +++ b/src/arm/Registers.go @@ -38,7 +38,20 @@ const ( ) var ( + GeneralRegisters = []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28} + InputRegisters = SyscallInputRegisters + OutputRegisters = SyscallInputRegisters SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} + SyscallOutputRegisters = []cpu.Register{X0, X1} WindowsInputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5, X6, X7} WindowsOutputRegisters = []cpu.Register{X0, X1} + + CPU = cpu.CPU{ + General: GeneralRegisters, + Input: InputRegisters, + Output: OutputRegisters, + SyscallInput: SyscallInputRegisters, + SyscallOutput: SyscallOutputRegisters, + NumRegisters: 32, + } ) diff --git a/src/arm/Return.go b/src/arm/Return.go new file mode 100644 index 0000000..b98c038 --- /dev/null +++ b/src/arm/Return.go @@ -0,0 +1,6 @@ +package arm + +// Return transfers program control to the caller. +func Return(code []byte) []byte { + return append(code, 0xC0, 0x03, 0x5F, 0xD6) +} diff --git a/src/arm/Syscall.go b/src/arm/Syscall.go new file mode 100644 index 0000000..2792926 --- /dev/null +++ b/src/arm/Syscall.go @@ -0,0 +1,6 @@ +package arm + +// Syscall is the primary way to communicate with the OS kernel. +func Syscall(code []byte) []byte { + return append(code, 0x01, 0x00, 0x00, 0xD4) +} diff --git a/src/arm/arm_test.go b/src/arm/arm_test.go new file mode 100644 index 0000000..b4918fc --- /dev/null +++ b/src/arm/arm_test.go @@ -0,0 +1,16 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/go/assert" +) + +func TestARM(t *testing.T) { + assert.DeepEqual(t, arm.Call(nil, 0), []byte{0x00, 0x00, 0x00, 0x94}) + assert.DeepEqual(t, arm.MoveRegisterNumber(nil, arm.X0, 42), arm.MoveZero(nil, arm.X0, 0, 42)) + assert.DeepEqual(t, arm.Nop(nil), []byte{0x1F, 0x20, 0x03, 0xD5}) + assert.DeepEqual(t, arm.Return(nil), []byte{0xC0, 0x03, 0x5F, 0xD6}) + assert.DeepEqual(t, arm.Syscall(nil), []byte{0x01, 0x00, 0x00, 0xD4}) +} diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index f73a1e8..2619905 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -1,6 +1,7 @@ package asmc import ( + "git.urbach.dev/cli/q/src/arm" "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/dll" @@ -24,8 +25,20 @@ func Finalize(a asm.Assembler, dlls dll.List) ([]byte, []byte) { dlls: dlls, } - for _, x := range a.Instructions { - c.compile(x) + switch config.TargetArch { + case config.ARM: + for _, x := range a.Instructions { + c.compileARM(x) + } + + c.code = arm.MoveRegisterNumber(c.code, arm.X0, 0) + c.code = arm.MoveRegisterNumber(c.code, arm.X8, 0x5D) + c.code = arm.Syscall(c.code) + + case config.X86: + for _, x := range a.Instructions { + c.compileX86(x) + } } c.resolvePointers() diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go new file mode 100644 index 0000000..3c89d8d --- /dev/null +++ b/src/asmc/compileARM.go @@ -0,0 +1,25 @@ +package asmc + +import ( + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/asm" +) + +func (c *compiler) compileARM(x asm.Instruction) { + switch x.Mnemonic { + // case asm.MOVE: + // switch operands := x.Data.(type) { + // case *asm.RegisterNumber: + // c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) + // } + + // case asm.RETURN: + // c.code = arm.Return(c.code) + + // case asm.SYSCALL: + // c.code = arm.Syscall(c.code) + + default: + c.code = arm.Nop(c.code) + } +} diff --git a/src/asmc/compile.go b/src/asmc/compileX86.go similarity index 98% rename from src/asmc/compile.go rename to src/asmc/compileX86.go index 41169c9..b4279de 100644 --- a/src/asmc/compile.go +++ b/src/asmc/compileX86.go @@ -5,7 +5,7 @@ import ( "git.urbach.dev/cli/q/src/x86" ) -func (c *compiler) compile(x asm.Instruction) { +func (c *compiler) compileX86(x asm.Instruction) { switch x.Mnemonic { case asm.ADD: switch operands := x.Data.(type) { diff --git a/src/cli/Build.go b/src/cli/Build.go index 1614bdc..c9aa8ec 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -48,7 +48,14 @@ func buildExecutable(args []string) (*build.Build, error) { return b, &ExpectedParameterError{Parameter: "arch"} } - config.TargetArch = args[i] + switch args[i] { + case "arm": + config.TargetArch = config.ARM + case "x86": + config.TargetArch = config.X86 + default: + return b, &InvalidValueError{Value: args[i], Parameter: "arch"} + } case "--os": i++ @@ -77,7 +84,7 @@ func buildExecutable(args []string) (*build.Build, error) { } } - if config.TargetOS == config.Unknown { + if config.TargetOS == config.UnknownOS { return b, &InvalidValueError{Value: runtime.GOOS, Parameter: "os"} } diff --git a/src/cli/Help.go b/src/cli/Help.go index 03dfd9c..c96c6f0 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -14,7 +14,7 @@ func Help(w io.Writer, code int) int { Commands: build [directory | file] build an executable from a file or directory - --arch [arch] cross-compile for another CPU architecture [x86|arm|riscv] + --arch [arch] cross-compile for another CPU architecture [x86|arm] --assembly, -a show assembly instructions --dry, -d skip writing the executable to disk --os [os] cross-compile for another OS [linux|mac|windows] diff --git a/src/config/arch.go b/src/config/arch.go new file mode 100644 index 0000000..33eacf7 --- /dev/null +++ b/src/config/arch.go @@ -0,0 +1,9 @@ +package config + +type Arch uint8 + +const ( + UnknownArch Arch = iota + ARM + X86 +) diff --git a/src/config/config.go b/src/config/config.go index b603eed..f3f5164 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -3,12 +3,12 @@ package config import "runtime" var ( - ConstantFold bool // Calculates the result of operations on constants at compile time. - Dry bool // Skips writing the executable to disk. - ShowAssembly bool // Shows assembly instructions at the end. - ShowStatistics bool // Shows statistics at the end. - TargetArch string // Target architecture. - TargetOS OS // Target platform. + ConstantFold bool // Calculates the result of operations on constants at compile time. + Dry bool // Skips writing the executable to disk. + ShowAssembly bool // Shows assembly instructions at the end. + ShowStatistics bool // Shows statistics at the end. + TargetArch Arch // Target architecture. + TargetOS OS // Target platform. ) // Reset resets the configuration to its default values. @@ -16,7 +16,15 @@ func Reset() { ShowAssembly = false ShowStatistics = false Dry = false - TargetArch = runtime.GOARCH + + switch runtime.GOARCH { + case "amd64": + TargetArch = X86 + case "arm": + TargetArch = ARM + default: + TargetArch = UnknownArch + } switch runtime.GOOS { case "linux": @@ -26,7 +34,7 @@ func Reset() { case "windows": TargetOS = Windows default: - TargetOS = Unknown + TargetOS = UnknownOS } Optimize(true) diff --git a/src/config/os.go b/src/config/os.go index d6893e2..48ed743 100644 --- a/src/config/os.go +++ b/src/config/os.go @@ -3,7 +3,7 @@ package config type OS uint8 const ( - Unknown OS = iota + UnknownOS OS = iota Linux Mac Windows diff --git a/src/core/NewFunction.go b/src/core/NewFunction.go index 1450660..abb26ab 100644 --- a/src/core/NewFunction.go +++ b/src/core/NewFunction.go @@ -1,7 +1,9 @@ package core import ( + "git.urbach.dev/cli/q/src/arm" "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/fs" "git.urbach.dev/cli/q/src/register" @@ -11,6 +13,15 @@ import ( // NewFunction creates a new function. func NewFunction(pkg string, name string, file *fs.File) *Function { + var cpu *cpu.CPU + + switch config.TargetArch { + case config.ARM: + cpu = &arm.CPU + case config.X86: + cpu = &x86.CPU + } + return &Function{ Package: pkg, Name: name, @@ -23,14 +34,7 @@ func NewFunction(pkg string, name string, file *fs.File) *Function { Stack: scope.Stack{ Scopes: []*scope.Scope{{}}, }, - CPU: cpu.CPU{ - General: x86.GeneralRegisters, - Input: x86.InputRegisters, - Output: x86.OutputRegisters, - SyscallInput: x86.SyscallInputRegisters, - SyscallOutput: x86.SyscallOutputRegisters, - NumRegisters: 16, - }, + CPU: cpu, }, } } diff --git a/src/elf/Constants.go b/src/elf/Constants.go index bd4600f..e33331f 100644 --- a/src/elf/Constants.go +++ b/src/elf/Constants.go @@ -4,6 +4,7 @@ const ( LittleEndian = 1 TypeExecutable = 2 ArchitectureAMD64 = 0x3E + ArchitectureARM64 = 0xB7 ) type ProgramType int32 diff --git a/src/elf/ELF.go b/src/elf/ELF.go index d9e8d52..e914620 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -23,8 +23,16 @@ func Write(writer io.Writer, code []byte, data []byte) { var ( codeStart, codePadding = fs.Align(HeaderEnd, config.Align) dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) + arch int16 ) + switch config.TargetArch { + case config.ARM: + arch = ArchitectureARM64 + case config.X86: + arch = ArchitectureAMD64 + } + elf := &ELF{ Header: Header{ Magic: [4]byte{0x7F, 'E', 'L', 'F'}, @@ -34,7 +42,7 @@ func Write(writer io.Writer, code []byte, data []byte) { OSABI: 0, ABIVersion: 0, Type: TypeExecutable, - Architecture: ArchitectureAMD64, + Architecture: arch, FileVersion: 1, EntryPointInMemory: int64(config.BaseAddress + codeStart), ProgramHeaderOffset: HeaderSize, diff --git a/src/macho/Constants.go b/src/macho/Constants.go index eb9e320..9afc171 100644 --- a/src/macho/Constants.go +++ b/src/macho/Constants.go @@ -9,6 +9,11 @@ const ( CPU_ARM_64 CPU = CPU_ARM | 0x01000000 ) +const ( + CPU_SUBTYPE_ARM64_ALL = 0 + CPU_SUBTYPE_X86_64_ALL = 3 +) + type Prot uint32 const ( diff --git a/src/macho/MachO.go b/src/macho/MachO.go index 1c2cf03..4e6ecb7 100644 --- a/src/macho/MachO.go +++ b/src/macho/MachO.go @@ -28,13 +28,24 @@ func Write(writer io.Writer, code []byte, data []byte) { var ( codeStart, codePadding = fs.Align(HeaderEnd, config.Align) dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) + arch CPU + microArch uint32 ) + switch config.TargetArch { + case config.ARM: + arch = CPU_ARM_64 + microArch = CPU_SUBTYPE_ARM64_ALL | 0x80000000 + case config.X86: + arch = CPU_X86_64 + microArch = CPU_SUBTYPE_X86_64_ALL | 0x80000000 + } + m := &MachO{ Header: Header{ Magic: 0xFEEDFACF, - Architecture: CPU_X86_64, - MicroArchitecture: 3 | 0x80000000, + Architecture: arch, + MicroArchitecture: microArch, Type: TypeExecute, NumCommands: 4, SizeCommands: SizeCommands, diff --git a/src/pe/EXE.go b/src/pe/EXE.go index c34b616..579d6a0 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -35,12 +35,20 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { importDirectorySize = DLLImportSize * len(dllImports) importSectionSize = len(imports)*8 + len(dllData) + importDirectorySize imageSize, _ = fs.Align(importsStart+importSectionSize, config.Align) + arch uint16 ) if dlls.Contains("user32") { subSystem = IMAGE_SUBSYSTEM_WINDOWS_GUI } + switch config.TargetArch { + case config.ARM: + arch = IMAGE_FILE_MACHINE_ARM64 + case config.X86: + arch = IMAGE_FILE_MACHINE_AMD64 + } + pe := &EXE{ DOSHeader: DOSHeader{ Magic: [4]byte{'M', 'Z', 0, 0}, @@ -48,7 +56,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { }, NTHeader: NTHeader{ Signature: [4]byte{'P', 'E', 0, 0}, - Machine: IMAGE_FILE_MACHINE_AMD64, + Machine: arch, NumberOfSections: uint16(NumSections), TimeDateStamp: 0, PointerToSymbolTable: 0, diff --git a/src/register/Machine.go b/src/register/Machine.go index 69f307d..04d398a 100644 --- a/src/register/Machine.go +++ b/src/register/Machine.go @@ -10,6 +10,6 @@ import ( type Machine struct { scope.Stack Assembler asm.Assembler - CPU cpu.CPU + CPU *cpu.CPU RegisterHistory []uint64 } diff --git a/src/x86/Call.go b/src/x86/Call.go index a19c68e..6815b24 100644 --- a/src/x86/Call.go +++ b/src/x86/Call.go @@ -5,14 +5,14 @@ import "git.urbach.dev/cli/q/src/cpu" // Call places the return address on the top of the stack and continues // program flow at the new address. // The address is relative to the next instruction. -func Call(code []byte, address uint32) []byte { +func Call(code []byte, offset uint32) []byte { return append( code, 0xE8, - byte(address), - byte(address>>8), - byte(address>>16), - byte(address>>24), + byte(offset), + byte(offset>>8), + byte(offset>>16), + byte(offset>>24), ) } diff --git a/src/x86/Registers.go b/src/x86/Registers.go index c5c8f18..793be1c 100644 --- a/src/x86/Registers.go +++ b/src/x86/Registers.go @@ -30,4 +30,13 @@ var ( WindowsInputRegisters = []cpu.Register{RCX, RDX, R8, R9} WindowsOutputRegisters = []cpu.Register{RAX} WindowsVolatileRegisters = []cpu.Register{RCX, RDX, R8, R9, R10, R11} + + CPU = cpu.CPU{ + General: GeneralRegisters, + Input: InputRegisters, + Output: OutputRegisters, + SyscallInput: SyscallInputRegisters, + SyscallOutput: SyscallOutputRegisters, + NumRegisters: 16, + } ) From 2f09b96f343ad3fc5af38d98a640ad07a7a4e40f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Mar 2025 13:40:17 +0100 Subject: [PATCH 0882/1012] Added basic support for arm64 --- src/arm/Call.go | 10 ++++++ src/arm/Move.go | 29 +++++++++++++++++ src/arm/Move_test.go | 43 ++++++++++++++++++++++++++ src/arm/Nop.go | 6 ++++ src/arm/Registers.go | 13 ++++++++ src/arm/Return.go | 6 ++++ src/arm/Syscall.go | 6 ++++ src/arm/arm_test.go | 16 ++++++++++ src/asmc/Finalize.go | 17 ++++++++-- src/asmc/compileARM.go | 25 +++++++++++++++ src/asmc/{compile.go => compileX86.go} | 2 +- src/cli/Build.go | 11 +++++-- src/cli/Help.go | 2 +- src/config/arch.go | 9 ++++++ src/config/config.go | 24 +++++++++----- src/config/os.go | 2 +- src/core/NewFunction.go | 20 +++++++----- src/elf/Constants.go | 1 + src/elf/ELF.go | 10 +++++- src/macho/Constants.go | 5 +++ src/macho/MachO.go | 15 +++++++-- src/pe/EXE.go | 10 +++++- src/register/Machine.go | 2 +- src/x86/Call.go | 10 +++--- src/x86/Registers.go | 9 ++++++ 25 files changed, 270 insertions(+), 33 deletions(-) create mode 100644 src/arm/Call.go create mode 100644 src/arm/Move.go create mode 100644 src/arm/Move_test.go create mode 100644 src/arm/Nop.go create mode 100644 src/arm/Return.go create mode 100644 src/arm/Syscall.go create mode 100644 src/arm/arm_test.go create mode 100644 src/asmc/compileARM.go rename src/asmc/{compile.go => compileX86.go} (98%) create mode 100644 src/config/arch.go diff --git a/src/arm/Call.go b/src/arm/Call.go new file mode 100644 index 0000000..1a4fd3f --- /dev/null +++ b/src/arm/Call.go @@ -0,0 +1,10 @@ +package arm + +import "encoding/binary" + +// Call branches to a PC-relative offset, setting the register X30 to PC+4. +// The offset starts from the address of this instruction and is encoded as "imm26" times 4. +// This instruction is also known as BL (branch with link). +func Call(code []byte, offset uint32) []byte { + return binary.LittleEndian.AppendUint32(code, uint32(0b100101<<26)|offset) +} diff --git a/src/arm/Move.go b/src/arm/Move.go new file mode 100644 index 0000000..f26744f --- /dev/null +++ b/src/arm/Move.go @@ -0,0 +1,29 @@ +package arm + +import ( + "encoding/binary" + + "git.urbach.dev/cli/q/src/cpu" +) + +// MoveRegisterNumber moves an integer into the given register. +func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + return MoveZero(code, destination, 0, uint16(number)) +} + +// MoveKeep moves a 16-bit integer into the given register and keeps all other bits. +func MoveKeep(code []byte, destination cpu.Register, halfword int, number uint16) []byte { + x := mov(0b11, halfword, number, destination) + return binary.LittleEndian.AppendUint32(code, x) +} + +// MoveZero moves a 16-bit integer into the given register and clears all other bits to zero. +func MoveZero(code []byte, destination cpu.Register, halfword int, number uint16) []byte { + x := mov(0b10, halfword, number, destination) + return binary.LittleEndian.AppendUint32(code, x) +} + +// mov encodes a generic move instruction. +func mov(opCode uint32, halfword int, number uint16, destination cpu.Register) uint32 { + return (1 << 31) | (opCode << 29) | (0b100101 << 23) | uint32(halfword<<21) | uint32(number<<5) | uint32(destination) +} diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go new file mode 100644 index 0000000..fb7f7d3 --- /dev/null +++ b/src/arm/Move_test.go @@ -0,0 +1,43 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestMoveKeep(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number uint16 + Code []byte + }{ + {arm.X0, 0, []byte{0x00, 0x00, 0x80, 0xF2}}, + {arm.X0, 1, []byte{0x20, 0x00, 0x80, 0xF2}}, + } + + for _, pattern := range usagePatterns { + t.Logf("movk %s, %x", pattern.Register, pattern.Number) + code := arm.MoveKeep(nil, pattern.Register, 0, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestMoveZero(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number uint16 + Code []byte + }{ + {arm.X0, 0, []byte{0x00, 0x00, 0x80, 0xD2}}, + {arm.X0, 1, []byte{0x20, 0x00, 0x80, 0xD2}}, + } + + for _, pattern := range usagePatterns { + t.Logf("movz %s, %x", pattern.Register, pattern.Number) + code := arm.MoveZero(nil, pattern.Register, 0, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Nop.go b/src/arm/Nop.go new file mode 100644 index 0000000..8152293 --- /dev/null +++ b/src/arm/Nop.go @@ -0,0 +1,6 @@ +package arm + +// Nop does nothing. This can be used for alignment purposes. +func Nop(code []byte) []byte { + return append(code, 0x1F, 0x20, 0x03, 0xD5) +} diff --git a/src/arm/Registers.go b/src/arm/Registers.go index 86d78d2..62c7814 100644 --- a/src/arm/Registers.go +++ b/src/arm/Registers.go @@ -38,7 +38,20 @@ const ( ) var ( + GeneralRegisters = []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28} + InputRegisters = SyscallInputRegisters + OutputRegisters = SyscallInputRegisters SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} + SyscallOutputRegisters = []cpu.Register{X0, X1} WindowsInputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5, X6, X7} WindowsOutputRegisters = []cpu.Register{X0, X1} + + CPU = cpu.CPU{ + General: GeneralRegisters, + Input: InputRegisters, + Output: OutputRegisters, + SyscallInput: SyscallInputRegisters, + SyscallOutput: SyscallOutputRegisters, + NumRegisters: 32, + } ) diff --git a/src/arm/Return.go b/src/arm/Return.go new file mode 100644 index 0000000..b98c038 --- /dev/null +++ b/src/arm/Return.go @@ -0,0 +1,6 @@ +package arm + +// Return transfers program control to the caller. +func Return(code []byte) []byte { + return append(code, 0xC0, 0x03, 0x5F, 0xD6) +} diff --git a/src/arm/Syscall.go b/src/arm/Syscall.go new file mode 100644 index 0000000..2792926 --- /dev/null +++ b/src/arm/Syscall.go @@ -0,0 +1,6 @@ +package arm + +// Syscall is the primary way to communicate with the OS kernel. +func Syscall(code []byte) []byte { + return append(code, 0x01, 0x00, 0x00, 0xD4) +} diff --git a/src/arm/arm_test.go b/src/arm/arm_test.go new file mode 100644 index 0000000..b4918fc --- /dev/null +++ b/src/arm/arm_test.go @@ -0,0 +1,16 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/go/assert" +) + +func TestARM(t *testing.T) { + assert.DeepEqual(t, arm.Call(nil, 0), []byte{0x00, 0x00, 0x00, 0x94}) + assert.DeepEqual(t, arm.MoveRegisterNumber(nil, arm.X0, 42), arm.MoveZero(nil, arm.X0, 0, 42)) + assert.DeepEqual(t, arm.Nop(nil), []byte{0x1F, 0x20, 0x03, 0xD5}) + assert.DeepEqual(t, arm.Return(nil), []byte{0xC0, 0x03, 0x5F, 0xD6}) + assert.DeepEqual(t, arm.Syscall(nil), []byte{0x01, 0x00, 0x00, 0xD4}) +} diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index f73a1e8..2619905 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -1,6 +1,7 @@ package asmc import ( + "git.urbach.dev/cli/q/src/arm" "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/dll" @@ -24,8 +25,20 @@ func Finalize(a asm.Assembler, dlls dll.List) ([]byte, []byte) { dlls: dlls, } - for _, x := range a.Instructions { - c.compile(x) + switch config.TargetArch { + case config.ARM: + for _, x := range a.Instructions { + c.compileARM(x) + } + + c.code = arm.MoveRegisterNumber(c.code, arm.X0, 0) + c.code = arm.MoveRegisterNumber(c.code, arm.X8, 0x5D) + c.code = arm.Syscall(c.code) + + case config.X86: + for _, x := range a.Instructions { + c.compileX86(x) + } } c.resolvePointers() diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go new file mode 100644 index 0000000..3c89d8d --- /dev/null +++ b/src/asmc/compileARM.go @@ -0,0 +1,25 @@ +package asmc + +import ( + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/asm" +) + +func (c *compiler) compileARM(x asm.Instruction) { + switch x.Mnemonic { + // case asm.MOVE: + // switch operands := x.Data.(type) { + // case *asm.RegisterNumber: + // c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) + // } + + // case asm.RETURN: + // c.code = arm.Return(c.code) + + // case asm.SYSCALL: + // c.code = arm.Syscall(c.code) + + default: + c.code = arm.Nop(c.code) + } +} diff --git a/src/asmc/compile.go b/src/asmc/compileX86.go similarity index 98% rename from src/asmc/compile.go rename to src/asmc/compileX86.go index 41169c9..b4279de 100644 --- a/src/asmc/compile.go +++ b/src/asmc/compileX86.go @@ -5,7 +5,7 @@ import ( "git.urbach.dev/cli/q/src/x86" ) -func (c *compiler) compile(x asm.Instruction) { +func (c *compiler) compileX86(x asm.Instruction) { switch x.Mnemonic { case asm.ADD: switch operands := x.Data.(type) { diff --git a/src/cli/Build.go b/src/cli/Build.go index 1614bdc..c9aa8ec 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -48,7 +48,14 @@ func buildExecutable(args []string) (*build.Build, error) { return b, &ExpectedParameterError{Parameter: "arch"} } - config.TargetArch = args[i] + switch args[i] { + case "arm": + config.TargetArch = config.ARM + case "x86": + config.TargetArch = config.X86 + default: + return b, &InvalidValueError{Value: args[i], Parameter: "arch"} + } case "--os": i++ @@ -77,7 +84,7 @@ func buildExecutable(args []string) (*build.Build, error) { } } - if config.TargetOS == config.Unknown { + if config.TargetOS == config.UnknownOS { return b, &InvalidValueError{Value: runtime.GOOS, Parameter: "os"} } diff --git a/src/cli/Help.go b/src/cli/Help.go index 03dfd9c..c96c6f0 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -14,7 +14,7 @@ func Help(w io.Writer, code int) int { Commands: build [directory | file] build an executable from a file or directory - --arch [arch] cross-compile for another CPU architecture [x86|arm|riscv] + --arch [arch] cross-compile for another CPU architecture [x86|arm] --assembly, -a show assembly instructions --dry, -d skip writing the executable to disk --os [os] cross-compile for another OS [linux|mac|windows] diff --git a/src/config/arch.go b/src/config/arch.go new file mode 100644 index 0000000..33eacf7 --- /dev/null +++ b/src/config/arch.go @@ -0,0 +1,9 @@ +package config + +type Arch uint8 + +const ( + UnknownArch Arch = iota + ARM + X86 +) diff --git a/src/config/config.go b/src/config/config.go index b603eed..f3f5164 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -3,12 +3,12 @@ package config import "runtime" var ( - ConstantFold bool // Calculates the result of operations on constants at compile time. - Dry bool // Skips writing the executable to disk. - ShowAssembly bool // Shows assembly instructions at the end. - ShowStatistics bool // Shows statistics at the end. - TargetArch string // Target architecture. - TargetOS OS // Target platform. + ConstantFold bool // Calculates the result of operations on constants at compile time. + Dry bool // Skips writing the executable to disk. + ShowAssembly bool // Shows assembly instructions at the end. + ShowStatistics bool // Shows statistics at the end. + TargetArch Arch // Target architecture. + TargetOS OS // Target platform. ) // Reset resets the configuration to its default values. @@ -16,7 +16,15 @@ func Reset() { ShowAssembly = false ShowStatistics = false Dry = false - TargetArch = runtime.GOARCH + + switch runtime.GOARCH { + case "amd64": + TargetArch = X86 + case "arm": + TargetArch = ARM + default: + TargetArch = UnknownArch + } switch runtime.GOOS { case "linux": @@ -26,7 +34,7 @@ func Reset() { case "windows": TargetOS = Windows default: - TargetOS = Unknown + TargetOS = UnknownOS } Optimize(true) diff --git a/src/config/os.go b/src/config/os.go index d6893e2..48ed743 100644 --- a/src/config/os.go +++ b/src/config/os.go @@ -3,7 +3,7 @@ package config type OS uint8 const ( - Unknown OS = iota + UnknownOS OS = iota Linux Mac Windows diff --git a/src/core/NewFunction.go b/src/core/NewFunction.go index 1450660..abb26ab 100644 --- a/src/core/NewFunction.go +++ b/src/core/NewFunction.go @@ -1,7 +1,9 @@ package core import ( + "git.urbach.dev/cli/q/src/arm" "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/fs" "git.urbach.dev/cli/q/src/register" @@ -11,6 +13,15 @@ import ( // NewFunction creates a new function. func NewFunction(pkg string, name string, file *fs.File) *Function { + var cpu *cpu.CPU + + switch config.TargetArch { + case config.ARM: + cpu = &arm.CPU + case config.X86: + cpu = &x86.CPU + } + return &Function{ Package: pkg, Name: name, @@ -23,14 +34,7 @@ func NewFunction(pkg string, name string, file *fs.File) *Function { Stack: scope.Stack{ Scopes: []*scope.Scope{{}}, }, - CPU: cpu.CPU{ - General: x86.GeneralRegisters, - Input: x86.InputRegisters, - Output: x86.OutputRegisters, - SyscallInput: x86.SyscallInputRegisters, - SyscallOutput: x86.SyscallOutputRegisters, - NumRegisters: 16, - }, + CPU: cpu, }, } } diff --git a/src/elf/Constants.go b/src/elf/Constants.go index bd4600f..e33331f 100644 --- a/src/elf/Constants.go +++ b/src/elf/Constants.go @@ -4,6 +4,7 @@ const ( LittleEndian = 1 TypeExecutable = 2 ArchitectureAMD64 = 0x3E + ArchitectureARM64 = 0xB7 ) type ProgramType int32 diff --git a/src/elf/ELF.go b/src/elf/ELF.go index d9e8d52..e914620 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -23,8 +23,16 @@ func Write(writer io.Writer, code []byte, data []byte) { var ( codeStart, codePadding = fs.Align(HeaderEnd, config.Align) dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) + arch int16 ) + switch config.TargetArch { + case config.ARM: + arch = ArchitectureARM64 + case config.X86: + arch = ArchitectureAMD64 + } + elf := &ELF{ Header: Header{ Magic: [4]byte{0x7F, 'E', 'L', 'F'}, @@ -34,7 +42,7 @@ func Write(writer io.Writer, code []byte, data []byte) { OSABI: 0, ABIVersion: 0, Type: TypeExecutable, - Architecture: ArchitectureAMD64, + Architecture: arch, FileVersion: 1, EntryPointInMemory: int64(config.BaseAddress + codeStart), ProgramHeaderOffset: HeaderSize, diff --git a/src/macho/Constants.go b/src/macho/Constants.go index eb9e320..9afc171 100644 --- a/src/macho/Constants.go +++ b/src/macho/Constants.go @@ -9,6 +9,11 @@ const ( CPU_ARM_64 CPU = CPU_ARM | 0x01000000 ) +const ( + CPU_SUBTYPE_ARM64_ALL = 0 + CPU_SUBTYPE_X86_64_ALL = 3 +) + type Prot uint32 const ( diff --git a/src/macho/MachO.go b/src/macho/MachO.go index 1c2cf03..4e6ecb7 100644 --- a/src/macho/MachO.go +++ b/src/macho/MachO.go @@ -28,13 +28,24 @@ func Write(writer io.Writer, code []byte, data []byte) { var ( codeStart, codePadding = fs.Align(HeaderEnd, config.Align) dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) + arch CPU + microArch uint32 ) + switch config.TargetArch { + case config.ARM: + arch = CPU_ARM_64 + microArch = CPU_SUBTYPE_ARM64_ALL | 0x80000000 + case config.X86: + arch = CPU_X86_64 + microArch = CPU_SUBTYPE_X86_64_ALL | 0x80000000 + } + m := &MachO{ Header: Header{ Magic: 0xFEEDFACF, - Architecture: CPU_X86_64, - MicroArchitecture: 3 | 0x80000000, + Architecture: arch, + MicroArchitecture: microArch, Type: TypeExecute, NumCommands: 4, SizeCommands: SizeCommands, diff --git a/src/pe/EXE.go b/src/pe/EXE.go index c34b616..579d6a0 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -35,12 +35,20 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { importDirectorySize = DLLImportSize * len(dllImports) importSectionSize = len(imports)*8 + len(dllData) + importDirectorySize imageSize, _ = fs.Align(importsStart+importSectionSize, config.Align) + arch uint16 ) if dlls.Contains("user32") { subSystem = IMAGE_SUBSYSTEM_WINDOWS_GUI } + switch config.TargetArch { + case config.ARM: + arch = IMAGE_FILE_MACHINE_ARM64 + case config.X86: + arch = IMAGE_FILE_MACHINE_AMD64 + } + pe := &EXE{ DOSHeader: DOSHeader{ Magic: [4]byte{'M', 'Z', 0, 0}, @@ -48,7 +56,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { }, NTHeader: NTHeader{ Signature: [4]byte{'P', 'E', 0, 0}, - Machine: IMAGE_FILE_MACHINE_AMD64, + Machine: arch, NumberOfSections: uint16(NumSections), TimeDateStamp: 0, PointerToSymbolTable: 0, diff --git a/src/register/Machine.go b/src/register/Machine.go index 69f307d..04d398a 100644 --- a/src/register/Machine.go +++ b/src/register/Machine.go @@ -10,6 +10,6 @@ import ( type Machine struct { scope.Stack Assembler asm.Assembler - CPU cpu.CPU + CPU *cpu.CPU RegisterHistory []uint64 } diff --git a/src/x86/Call.go b/src/x86/Call.go index a19c68e..6815b24 100644 --- a/src/x86/Call.go +++ b/src/x86/Call.go @@ -5,14 +5,14 @@ import "git.urbach.dev/cli/q/src/cpu" // Call places the return address on the top of the stack and continues // program flow at the new address. // The address is relative to the next instruction. -func Call(code []byte, address uint32) []byte { +func Call(code []byte, offset uint32) []byte { return append( code, 0xE8, - byte(address), - byte(address>>8), - byte(address>>16), - byte(address>>24), + byte(offset), + byte(offset>>8), + byte(offset>>16), + byte(offset>>24), ) } diff --git a/src/x86/Registers.go b/src/x86/Registers.go index c5c8f18..793be1c 100644 --- a/src/x86/Registers.go +++ b/src/x86/Registers.go @@ -30,4 +30,13 @@ var ( WindowsInputRegisters = []cpu.Register{RCX, RDX, R8, R9} WindowsOutputRegisters = []cpu.Register{RAX} WindowsVolatileRegisters = []cpu.Register{RCX, RDX, R8, R9, R10, R11} + + CPU = cpu.CPU{ + General: GeneralRegisters, + Input: InputRegisters, + Output: OutputRegisters, + SyscallInput: SyscallInputRegisters, + SyscallOutput: SyscallOutputRegisters, + NumRegisters: 16, + } ) From e3957fc3184e903ea14541879048b9af2272a3a3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Mar 2025 14:17:09 +0100 Subject: [PATCH 0883/1012] Fixed incorrect architecture detection --- src/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/config.go b/src/config/config.go index f3f5164..98bcdb4 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -20,7 +20,7 @@ func Reset() { switch runtime.GOARCH { case "amd64": TargetArch = X86 - case "arm": + case "arm64": TargetArch = ARM default: TargetArch = UnknownArch From 5f522b519ad02ba369e161bb8f0062db610627c1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Mar 2025 14:17:09 +0100 Subject: [PATCH 0884/1012] Fixed incorrect architecture detection --- src/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/config.go b/src/config/config.go index f3f5164..98bcdb4 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -20,7 +20,7 @@ func Reset() { switch runtime.GOARCH { case "amd64": TargetArch = X86 - case "arm": + case "arm64": TargetArch = ARM default: TargetArch = UnknownArch From fee1b27bfed2fa9e8560df784df425439159e86d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Mar 2025 16:54:28 +0100 Subject: [PATCH 0885/1012] Enabled arm64 encoding --- lib/core/core_linux_arm.q | 8 ++++ lib/core/{core_linux.q => core_linux_x86.q} | 0 src/arm/Call.go | 7 ++- src/arm/Registers.go | 4 +- src/asmc/compileARM.go | 49 +++++++++++++++---- src/asmc/pointer.go | 2 +- src/asmc/resolvePointers.go | 2 +- src/data/Finalize.go | 8 ++-- src/scanner/queueDirectory.go | 52 ++++++++++++++++----- 9 files changed, 103 insertions(+), 29 deletions(-) create mode 100644 lib/core/core_linux_arm.q rename lib/core/{core_linux.q => core_linux_x86.q} (100%) diff --git a/lib/core/core_linux_arm.q b/lib/core/core_linux_arm.q new file mode 100644 index 0000000..939ba41 --- /dev/null +++ b/lib/core/core_linux_arm.q @@ -0,0 +1,8 @@ +init() { + main.main() + exit() +} + +exit() { + syscall(93, 0) +} \ No newline at end of file diff --git a/lib/core/core_linux.q b/lib/core/core_linux_x86.q similarity index 100% rename from lib/core/core_linux.q rename to lib/core/core_linux_x86.q diff --git a/src/arm/Call.go b/src/arm/Call.go index 1a4fd3f..5122e02 100644 --- a/src/arm/Call.go +++ b/src/arm/Call.go @@ -6,5 +6,10 @@ import "encoding/binary" // The offset starts from the address of this instruction and is encoded as "imm26" times 4. // This instruction is also known as BL (branch with link). func Call(code []byte, offset uint32) []byte { - return binary.LittleEndian.AppendUint32(code, uint32(0b100101<<26)|offset) + return binary.LittleEndian.AppendUint32(code, EncodeCall(offset)) +} + +// EncodeCall returns the raw encoding of a call with the given offset. +func EncodeCall(offset uint32) uint32 { + return uint32(0b100101<<26) | offset } diff --git a/src/arm/Registers.go b/src/arm/Registers.go index 62c7814..388dc0d 100644 --- a/src/arm/Registers.go +++ b/src/arm/Registers.go @@ -39,8 +39,8 @@ const ( var ( GeneralRegisters = []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28} - InputRegisters = SyscallInputRegisters - OutputRegisters = SyscallInputRegisters + InputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5} + OutputRegisters = InputRegisters SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} SyscallOutputRegisters = []cpu.Register{X0, X1} WindowsInputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5, X6, X7} diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 3c89d8d..06c2417 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -1,23 +1,54 @@ package asmc import ( + "fmt" + "git.urbach.dev/cli/q/src/arm" "git.urbach.dev/cli/q/src/asm" ) func (c *compiler) compileARM(x asm.Instruction) { switch x.Mnemonic { - // case asm.MOVE: - // switch operands := x.Data.(type) { - // case *asm.RegisterNumber: - // c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) - // } + case asm.CALL: + switch data := x.Data.(type) { + case *asm.Label: + position := len(c.code) + c.code = arm.Call(c.code, 0) - // case asm.RETURN: - // c.code = arm.Return(c.code) + pointer := &pointer{ + Position: Address(position), + OpSize: 0, + Size: 4, + } - // case asm.SYSCALL: - // c.code = arm.Syscall(c.code) + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[data.Name] + + if !exists { + panic(fmt.Sprintf("unknown jump label %s", data.Name)) + } + + distance := (destination - pointer.Position) / 4 + return arm.EncodeCall(distance) + } + + c.codePointers = append(c.codePointers, pointer) + } + + case asm.LABEL: + c.codeLabels[x.Data.(*asm.Label).Name] = Address(len(c.code)) + + case asm.MOVE: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) + } + + case asm.RETURN: + c.code = arm.Return(c.code) + + case asm.SYSCALL: + c.code = arm.Syscall(c.code) default: c.code = arm.Nop(c.code) diff --git a/src/asmc/pointer.go b/src/asmc/pointer.go index f299758..bfd950c 100644 --- a/src/asmc/pointer.go +++ b/src/asmc/pointer.go @@ -1,7 +1,7 @@ package asmc // Address represents a memory address. -type Address = int32 +type Address = uint32 // pointer stores a relative memory address that we can later turn into an absolute one. // Position: The machine code offset where the address was inserted. diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index 2e6d30f..ea8aef2 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -16,7 +16,7 @@ restart: for i, pointer := range c.codePointers { address := pointer.Resolve() - if sizeof.Signed(address) > int(pointer.Size) { + if sizeof.Signed(int32(address)) > int(pointer.Size) { left := c.code[:pointer.Position-Address(pointer.OpSize)] right := c.code[pointer.Position+Address(pointer.Size):] size := pointer.Size + pointer.OpSize diff --git a/src/data/Finalize.go b/src/data/Finalize.go index aa0787a..a4567a2 100644 --- a/src/data/Finalize.go +++ b/src/data/Finalize.go @@ -7,10 +7,10 @@ import ( // Finalize returns the final raw data slice and a map of labels with their respective indices. // It will try to reuse existing data whenever possible. -func (data Data) Finalize() ([]byte, map[string]int32) { +func (data Data) Finalize() ([]byte, map[string]uint32) { var ( keys = make([]string, 0, len(data)) - positions = make(map[string]int32, len(data)) + positions = make(map[string]uint32, len(data)) capacity = 0 ) @@ -30,9 +30,9 @@ func (data Data) Finalize() ([]byte, map[string]int32) { position := bytes.Index(final, raw) if position != -1 { - positions[key] = int32(position) + positions[key] = uint32(position) } else { - positions[key] = int32(len(final)) + positions[key] = uint32(len(final)) final = append(final, raw...) } } diff --git a/src/scanner/queueDirectory.go b/src/scanner/queueDirectory.go index d99f0db..d5b0a47 100644 --- a/src/scanner/queueDirectory.go +++ b/src/scanner/queueDirectory.go @@ -21,20 +21,50 @@ func (s *Scanner) queueDirectory(directory string, pkg string) { return } - if strings.HasSuffix(name, "_linux.q") && config.TargetOS != config.Linux { - return - } + tmp := name[:len(name)-2] - if strings.HasSuffix(name, "_mac.q") && config.TargetOS != config.Mac { - return - } + for { + underscore := strings.LastIndexByte(tmp, '_') - if strings.HasSuffix(name, "_unix.q") && config.TargetOS != config.Linux && config.TargetOS != config.Mac { - return - } + if underscore == -1 { + break + } - if strings.HasSuffix(name, "_windows.q") && config.TargetOS != config.Windows { - return + condition := tmp[underscore+1:] + + switch condition { + case "linux": + if config.TargetOS != config.Linux { + return + } + + case "mac": + if config.TargetOS != config.Mac { + return + } + + case "unix": + if config.TargetOS != config.Linux && config.TargetOS != config.Mac { + return + } + + case "windows": + if config.TargetOS != config.Windows { + return + } + + case "x86": + if config.TargetArch != config.X86 { + return + } + + case "arm": + if config.TargetArch != config.ARM { + return + } + } + + tmp = tmp[:underscore] } fullPath := filepath.Join(directory, name) From 7798eca0740a12242253ecdd21ae0050430f02de Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Mar 2025 16:54:28 +0100 Subject: [PATCH 0886/1012] Enabled arm64 encoding --- lib/core/core_linux_arm.q | 8 ++++ lib/core/{core_linux.q => core_linux_x86.q} | 0 src/arm/Call.go | 7 ++- src/arm/Registers.go | 4 +- src/asmc/compileARM.go | 49 +++++++++++++++---- src/asmc/pointer.go | 2 +- src/asmc/resolvePointers.go | 2 +- src/data/Finalize.go | 8 ++-- src/scanner/queueDirectory.go | 52 ++++++++++++++++----- 9 files changed, 103 insertions(+), 29 deletions(-) create mode 100644 lib/core/core_linux_arm.q rename lib/core/{core_linux.q => core_linux_x86.q} (100%) diff --git a/lib/core/core_linux_arm.q b/lib/core/core_linux_arm.q new file mode 100644 index 0000000..939ba41 --- /dev/null +++ b/lib/core/core_linux_arm.q @@ -0,0 +1,8 @@ +init() { + main.main() + exit() +} + +exit() { + syscall(93, 0) +} \ No newline at end of file diff --git a/lib/core/core_linux.q b/lib/core/core_linux_x86.q similarity index 100% rename from lib/core/core_linux.q rename to lib/core/core_linux_x86.q diff --git a/src/arm/Call.go b/src/arm/Call.go index 1a4fd3f..5122e02 100644 --- a/src/arm/Call.go +++ b/src/arm/Call.go @@ -6,5 +6,10 @@ import "encoding/binary" // The offset starts from the address of this instruction and is encoded as "imm26" times 4. // This instruction is also known as BL (branch with link). func Call(code []byte, offset uint32) []byte { - return binary.LittleEndian.AppendUint32(code, uint32(0b100101<<26)|offset) + return binary.LittleEndian.AppendUint32(code, EncodeCall(offset)) +} + +// EncodeCall returns the raw encoding of a call with the given offset. +func EncodeCall(offset uint32) uint32 { + return uint32(0b100101<<26) | offset } diff --git a/src/arm/Registers.go b/src/arm/Registers.go index 62c7814..388dc0d 100644 --- a/src/arm/Registers.go +++ b/src/arm/Registers.go @@ -39,8 +39,8 @@ const ( var ( GeneralRegisters = []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28} - InputRegisters = SyscallInputRegisters - OutputRegisters = SyscallInputRegisters + InputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5} + OutputRegisters = InputRegisters SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} SyscallOutputRegisters = []cpu.Register{X0, X1} WindowsInputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5, X6, X7} diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 3c89d8d..06c2417 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -1,23 +1,54 @@ package asmc import ( + "fmt" + "git.urbach.dev/cli/q/src/arm" "git.urbach.dev/cli/q/src/asm" ) func (c *compiler) compileARM(x asm.Instruction) { switch x.Mnemonic { - // case asm.MOVE: - // switch operands := x.Data.(type) { - // case *asm.RegisterNumber: - // c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) - // } + case asm.CALL: + switch data := x.Data.(type) { + case *asm.Label: + position := len(c.code) + c.code = arm.Call(c.code, 0) - // case asm.RETURN: - // c.code = arm.Return(c.code) + pointer := &pointer{ + Position: Address(position), + OpSize: 0, + Size: 4, + } - // case asm.SYSCALL: - // c.code = arm.Syscall(c.code) + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[data.Name] + + if !exists { + panic(fmt.Sprintf("unknown jump label %s", data.Name)) + } + + distance := (destination - pointer.Position) / 4 + return arm.EncodeCall(distance) + } + + c.codePointers = append(c.codePointers, pointer) + } + + case asm.LABEL: + c.codeLabels[x.Data.(*asm.Label).Name] = Address(len(c.code)) + + case asm.MOVE: + switch operands := x.Data.(type) { + case *asm.RegisterNumber: + c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) + } + + case asm.RETURN: + c.code = arm.Return(c.code) + + case asm.SYSCALL: + c.code = arm.Syscall(c.code) default: c.code = arm.Nop(c.code) diff --git a/src/asmc/pointer.go b/src/asmc/pointer.go index f299758..bfd950c 100644 --- a/src/asmc/pointer.go +++ b/src/asmc/pointer.go @@ -1,7 +1,7 @@ package asmc // Address represents a memory address. -type Address = int32 +type Address = uint32 // pointer stores a relative memory address that we can later turn into an absolute one. // Position: The machine code offset where the address was inserted. diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index 2e6d30f..ea8aef2 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -16,7 +16,7 @@ restart: for i, pointer := range c.codePointers { address := pointer.Resolve() - if sizeof.Signed(address) > int(pointer.Size) { + if sizeof.Signed(int32(address)) > int(pointer.Size) { left := c.code[:pointer.Position-Address(pointer.OpSize)] right := c.code[pointer.Position+Address(pointer.Size):] size := pointer.Size + pointer.OpSize diff --git a/src/data/Finalize.go b/src/data/Finalize.go index aa0787a..a4567a2 100644 --- a/src/data/Finalize.go +++ b/src/data/Finalize.go @@ -7,10 +7,10 @@ import ( // Finalize returns the final raw data slice and a map of labels with their respective indices. // It will try to reuse existing data whenever possible. -func (data Data) Finalize() ([]byte, map[string]int32) { +func (data Data) Finalize() ([]byte, map[string]uint32) { var ( keys = make([]string, 0, len(data)) - positions = make(map[string]int32, len(data)) + positions = make(map[string]uint32, len(data)) capacity = 0 ) @@ -30,9 +30,9 @@ func (data Data) Finalize() ([]byte, map[string]int32) { position := bytes.Index(final, raw) if position != -1 { - positions[key] = int32(position) + positions[key] = uint32(position) } else { - positions[key] = int32(len(final)) + positions[key] = uint32(len(final)) final = append(final, raw...) } } diff --git a/src/scanner/queueDirectory.go b/src/scanner/queueDirectory.go index d99f0db..d5b0a47 100644 --- a/src/scanner/queueDirectory.go +++ b/src/scanner/queueDirectory.go @@ -21,20 +21,50 @@ func (s *Scanner) queueDirectory(directory string, pkg string) { return } - if strings.HasSuffix(name, "_linux.q") && config.TargetOS != config.Linux { - return - } + tmp := name[:len(name)-2] - if strings.HasSuffix(name, "_mac.q") && config.TargetOS != config.Mac { - return - } + for { + underscore := strings.LastIndexByte(tmp, '_') - if strings.HasSuffix(name, "_unix.q") && config.TargetOS != config.Linux && config.TargetOS != config.Mac { - return - } + if underscore == -1 { + break + } - if strings.HasSuffix(name, "_windows.q") && config.TargetOS != config.Windows { - return + condition := tmp[underscore+1:] + + switch condition { + case "linux": + if config.TargetOS != config.Linux { + return + } + + case "mac": + if config.TargetOS != config.Mac { + return + } + + case "unix": + if config.TargetOS != config.Linux && config.TargetOS != config.Mac { + return + } + + case "windows": + if config.TargetOS != config.Windows { + return + } + + case "x86": + if config.TargetArch != config.X86 { + return + } + + case "arm": + if config.TargetArch != config.ARM { + return + } + } + + tmp = tmp[:underscore] } fullPath := filepath.Join(directory, name) From 9dfff1fd4ee7455a329cc0eaf1d9d4d4cdd9a963 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Mar 2025 17:02:16 +0100 Subject: [PATCH 0887/1012] Updated donation links --- docs/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/readme.md b/docs/readme.md index b57de8f..7637a38 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -50,7 +50,7 @@ q build examples/hello --os windows `q` is under heavy development and not ready for production yet. Feel free to [get in touch](https://urbach.dev/contact) if you are interested in helping out. -The biggest obstacle right now is the lack of funding. If you want to help out financially you can [donate towards the project](https://en.liberapay.com/akyoto). +The biggest obstacle right now is the lack of funding. If you want to help out financially you can donate via [Kofi](https://ko-fi.com/akyoto) or [Open Collective](https://opencollective.com/qlang). ## License From 7fd29ea249ab78008c5501c28885e2ef87bf4cce Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Mar 2025 17:02:16 +0100 Subject: [PATCH 0888/1012] Updated donation links --- docs/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/readme.md b/docs/readme.md index b57de8f..7637a38 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -50,7 +50,7 @@ q build examples/hello --os windows `q` is under heavy development and not ready for production yet. Feel free to [get in touch](https://urbach.dev/contact) if you are interested in helping out. -The biggest obstacle right now is the lack of funding. If you want to help out financially you can [donate towards the project](https://en.liberapay.com/akyoto). +The biggest obstacle right now is the lack of funding. If you want to help out financially you can donate via [Kofi](https://ko-fi.com/akyoto) or [Open Collective](https://opencollective.com/qlang). ## License From 1832d2c73bd02d54d3be6bef23de504817e52931 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Mar 2025 17:05:33 +0100 Subject: [PATCH 0889/1012] Updated readme --- docs/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/readme.md b/docs/readme.md index 7637a38..1ef10e1 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -50,7 +50,7 @@ q build examples/hello --os windows `q` is under heavy development and not ready for production yet. Feel free to [get in touch](https://urbach.dev/contact) if you are interested in helping out. -The biggest obstacle right now is the lack of funding. If you want to help out financially you can donate via [Kofi](https://ko-fi.com/akyoto) or [Open Collective](https://opencollective.com/qlang). +The biggest obstacle right now is the lack of funding. You can help by donating via [Kofi](https://ko-fi.com/akyoto) or [Open Collective](https://opencollective.com/qlang). ## License From cb908e7b31820321618f24e9792c29518e4302cf Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Mar 2025 17:05:33 +0100 Subject: [PATCH 0890/1012] Updated readme --- docs/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/readme.md b/docs/readme.md index 7637a38..1ef10e1 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -50,7 +50,7 @@ q build examples/hello --os windows `q` is under heavy development and not ready for production yet. Feel free to [get in touch](https://urbach.dev/contact) if you are interested in helping out. -The biggest obstacle right now is the lack of funding. If you want to help out financially you can donate via [Kofi](https://ko-fi.com/akyoto) or [Open Collective](https://opencollective.com/qlang). +The biggest obstacle right now is the lack of funding. You can help by donating via [Kofi](https://ko-fi.com/akyoto) or [Open Collective](https://opencollective.com/qlang). ## License From 014f161633007c278988ba442097e5d834c5530e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Mar 2025 23:13:14 +0100 Subject: [PATCH 0891/1012] Implemented more arm64 instructions --- lib/sys/sys_linux_arm.q | 15 ++++++ lib/sys/{sys_linux.q => sys_linux_x86.q} | 0 src/arm/Call.go | 9 +--- src/arm/Load.go | 13 +++++ src/arm/LoadAddress.go | 10 ++++ src/arm/LoadAddress_test.go | 26 +++++++++ src/arm/Load_test.go | 31 +++++++++++ src/arm/Move.go | 21 ++++---- src/arm/Move_test.go | 33 +++++++++--- src/arm/Nop.go | 4 +- src/arm/Return.go | 4 +- src/arm/Syscall.go | 4 +- src/arm/arm_test.go | 10 ++-- src/asm/CanSkipReturn.go | 12 ++++- src/asmc/Finalize.go | 5 -- src/asmc/compileARM.go | 68 ++++++++++++++++++++---- src/asmc/compiler.go | 1 + src/asmc/load.go | 4 +- src/asmc/move.go | 2 +- src/asmc/resolvePointers.go | 8 +-- src/core/ResolveTypes.go | 3 +- src/x86/Load.go | 4 +- src/x86/LoadDynamic.go | 4 +- src/x86/LoadDynamic_test.go | 6 +-- src/x86/Load_test.go | 6 +-- src/x86/Move.go | 2 +- 26 files changed, 232 insertions(+), 73 deletions(-) create mode 100644 lib/sys/sys_linux_arm.q rename lib/sys/{sys_linux.q => sys_linux_x86.q} (100%) create mode 100644 src/arm/Load.go create mode 100644 src/arm/LoadAddress.go create mode 100644 src/arm/LoadAddress_test.go create mode 100644 src/arm/Load_test.go diff --git a/lib/sys/sys_linux_arm.q b/lib/sys/sys_linux_arm.q new file mode 100644 index 0000000..cfc351e --- /dev/null +++ b/lib/sys/sys_linux_arm.q @@ -0,0 +1,15 @@ +read(fd int, buffer *byte, length int) -> int { + return syscall(63, fd, buffer, length) +} + +write(fd int, buffer *byte, length int) -> int { + return syscall(64, fd, buffer, length) +} + +mmap(address int, length uint, protection int, flags int) -> *any { + return syscall(222, address, length, protection, flags) +} + +munmap(address *any, length uint) -> int { + return syscall(215, address, length) +} \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux_x86.q similarity index 100% rename from lib/sys/sys_linux.q rename to lib/sys/sys_linux_x86.q diff --git a/src/arm/Call.go b/src/arm/Call.go index 5122e02..8c1901f 100644 --- a/src/arm/Call.go +++ b/src/arm/Call.go @@ -1,15 +1,8 @@ package arm -import "encoding/binary" - // Call branches to a PC-relative offset, setting the register X30 to PC+4. // The offset starts from the address of this instruction and is encoded as "imm26" times 4. // This instruction is also known as BL (branch with link). -func Call(code []byte, offset uint32) []byte { - return binary.LittleEndian.AppendUint32(code, EncodeCall(offset)) -} - -// EncodeCall returns the raw encoding of a call with the given offset. -func EncodeCall(offset uint32) uint32 { +func Call(offset uint32) uint32 { return uint32(0b100101<<26) | offset } diff --git a/src/arm/Load.go b/src/arm/Load.go new file mode 100644 index 0000000..e1d09e1 --- /dev/null +++ b/src/arm/Load.go @@ -0,0 +1,13 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// LoadRegister loads from memory into a register. +func LoadRegister(destination cpu.Register, base cpu.Register, offset int16, length byte) uint32 { + if offset < 0 { + offset &= 0xFF + offset |= 1 << 8 + } + + return 0b11111000010<<21 | uint32(offset)<<12 | uint32(base)<<5 | uint32(destination) +} diff --git a/src/arm/LoadAddress.go b/src/arm/LoadAddress.go new file mode 100644 index 0000000..00284f5 --- /dev/null +++ b/src/arm/LoadAddress.go @@ -0,0 +1,10 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// LoadAddress calculates the address with the PC-relative offset and writes the result to the destination register. +func LoadAddress(destination cpu.Register, offset int) uint32 { + hi := uint32(offset) >> 2 + lo := uint32(offset) & 0b11 + return lo<<29 | 0b10000<<24 | hi<<5 | uint32(destination) +} diff --git a/src/arm/LoadAddress_test.go b/src/arm/LoadAddress_test.go new file mode 100644 index 0000000..3b3c05f --- /dev/null +++ b/src/arm/LoadAddress_test.go @@ -0,0 +1,26 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestLoadAddress(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Number int + Code uint32 + }{ + {arm.X0, 56, 0x100001C0}, + {arm.X1, 80, 0x10000281}, + } + + for _, pattern := range usagePatterns { + t.Logf("adr %s, %d", pattern.Destination, pattern.Number) + code := arm.LoadAddress(pattern.Destination, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Load_test.go b/src/arm/Load_test.go new file mode 100644 index 0000000..c04f60a --- /dev/null +++ b/src/arm/Load_test.go @@ -0,0 +1,31 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestLoadRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Base cpu.Register + Offset int16 + Length byte + Code uint32 + }{ + {arm.X2, arm.X1, -8, 8, 0xF85F8022}, + {arm.X2, arm.X1, 0, 8, 0xF8400022}, + {arm.X2, arm.X1, 8, 8, 0xF8408022}, + {arm.X2, arm.X1, -256, 8, 0xF8500022}, + {arm.X2, arm.X1, 255, 8, 0xF84FF022}, + } + + for _, pattern := range usagePatterns { + t.Logf("ldur %s, [%s, %d] %db", pattern.Destination, pattern.Base, pattern.Offset, pattern.Length) + code := arm.LoadRegister(pattern.Destination, pattern.Base, pattern.Offset, pattern.Length) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Move.go b/src/arm/Move.go index f26744f..e61ff8f 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -1,26 +1,27 @@ package arm import ( - "encoding/binary" - "git.urbach.dev/cli/q/src/cpu" ) +// MoveRegisterRegister copies a register to another register. +func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 { + return 0b10101010<<24 | uint32(source)<<16 | 0b11111<<5 | uint32(destination) +} + // MoveRegisterNumber moves an integer into the given register. -func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return MoveZero(code, destination, 0, uint16(number)) +func MoveRegisterNumber(destination cpu.Register, number int) uint32 { + return MoveZero(destination, 0, uint16(number)) } // MoveKeep moves a 16-bit integer into the given register and keeps all other bits. -func MoveKeep(code []byte, destination cpu.Register, halfword int, number uint16) []byte { - x := mov(0b11, halfword, number, destination) - return binary.LittleEndian.AppendUint32(code, x) +func MoveKeep(destination cpu.Register, halfword int, number uint16) uint32 { + return mov(0b11, halfword, number, destination) } // MoveZero moves a 16-bit integer into the given register and clears all other bits to zero. -func MoveZero(code []byte, destination cpu.Register, halfword int, number uint16) []byte { - x := mov(0b10, halfword, number, destination) - return binary.LittleEndian.AppendUint32(code, x) +func MoveZero(destination cpu.Register, halfword int, number uint16) uint32 { + return mov(0b10, halfword, number, destination) } // mov encodes a generic move instruction. diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go index fb7f7d3..9811e37 100644 --- a/src/arm/Move_test.go +++ b/src/arm/Move_test.go @@ -8,19 +8,36 @@ import ( "git.urbach.dev/go/assert" ) +func TestMoveRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, 0xAA0103E0}, + {arm.X1, arm.X0, 0xAA0003E1}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, %s", pattern.Destination, pattern.Source) + code := arm.MoveRegisterRegister(pattern.Destination, pattern.Source) + assert.DeepEqual(t, code, pattern.Code) + } +} + func TestMoveKeep(t *testing.T) { usagePatterns := []struct { Register cpu.Register Number uint16 - Code []byte + Code uint32 }{ - {arm.X0, 0, []byte{0x00, 0x00, 0x80, 0xF2}}, - {arm.X0, 1, []byte{0x20, 0x00, 0x80, 0xF2}}, + {arm.X0, 0, 0xF2800000}, + {arm.X0, 1, 0xF2800020}, } for _, pattern := range usagePatterns { t.Logf("movk %s, %x", pattern.Register, pattern.Number) - code := arm.MoveKeep(nil, pattern.Register, 0, pattern.Number) + code := arm.MoveKeep(pattern.Register, 0, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } @@ -29,15 +46,15 @@ func TestMoveZero(t *testing.T) { usagePatterns := []struct { Register cpu.Register Number uint16 - Code []byte + Code uint32 }{ - {arm.X0, 0, []byte{0x00, 0x00, 0x80, 0xD2}}, - {arm.X0, 1, []byte{0x20, 0x00, 0x80, 0xD2}}, + {arm.X0, 0, 0xD2800000}, + {arm.X0, 1, 0xD2800020}, } for _, pattern := range usagePatterns { t.Logf("movz %s, %x", pattern.Register, pattern.Number) - code := arm.MoveZero(nil, pattern.Register, 0, pattern.Number) + code := arm.MoveZero(pattern.Register, 0, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/arm/Nop.go b/src/arm/Nop.go index 8152293..6f518f4 100644 --- a/src/arm/Nop.go +++ b/src/arm/Nop.go @@ -1,6 +1,6 @@ package arm // Nop does nothing. This can be used for alignment purposes. -func Nop(code []byte) []byte { - return append(code, 0x1F, 0x20, 0x03, 0xD5) +func Nop() uint32 { + return 0xD503201F } diff --git a/src/arm/Return.go b/src/arm/Return.go index b98c038..898c6e7 100644 --- a/src/arm/Return.go +++ b/src/arm/Return.go @@ -1,6 +1,6 @@ package arm // Return transfers program control to the caller. -func Return(code []byte) []byte { - return append(code, 0xC0, 0x03, 0x5F, 0xD6) +func Return() uint32 { + return 0xD65F03C0 } diff --git a/src/arm/Syscall.go b/src/arm/Syscall.go index 2792926..e9227d6 100644 --- a/src/arm/Syscall.go +++ b/src/arm/Syscall.go @@ -1,6 +1,6 @@ package arm // Syscall is the primary way to communicate with the OS kernel. -func Syscall(code []byte) []byte { - return append(code, 0x01, 0x00, 0x00, 0xD4) +func Syscall() uint32 { + return 0xD4000001 } diff --git a/src/arm/arm_test.go b/src/arm/arm_test.go index b4918fc..948f869 100644 --- a/src/arm/arm_test.go +++ b/src/arm/arm_test.go @@ -8,9 +8,9 @@ import ( ) func TestARM(t *testing.T) { - assert.DeepEqual(t, arm.Call(nil, 0), []byte{0x00, 0x00, 0x00, 0x94}) - assert.DeepEqual(t, arm.MoveRegisterNumber(nil, arm.X0, 42), arm.MoveZero(nil, arm.X0, 0, 42)) - assert.DeepEqual(t, arm.Nop(nil), []byte{0x1F, 0x20, 0x03, 0xD5}) - assert.DeepEqual(t, arm.Return(nil), []byte{0xC0, 0x03, 0x5F, 0xD6}) - assert.DeepEqual(t, arm.Syscall(nil), []byte{0x01, 0x00, 0x00, 0xD4}) + assert.DeepEqual(t, arm.Call(0), 0x94000000) + assert.DeepEqual(t, arm.MoveRegisterNumber(arm.X0, 42), arm.MoveZero(arm.X0, 0, 42)) + assert.DeepEqual(t, arm.Nop(), 0xD503201F) + assert.DeepEqual(t, arm.Return(), 0xD65F03C0) + assert.DeepEqual(t, arm.Syscall(), 0xD4000001) } diff --git a/src/asm/CanSkipReturn.go b/src/asm/CanSkipReturn.go index 0ce31cb..56e5096 100644 --- a/src/asm/CanSkipReturn.go +++ b/src/asm/CanSkipReturn.go @@ -6,11 +6,19 @@ func (a *Assembler) CanSkipReturn() bool { return false } - lastMnemonic := a.Instructions[len(a.Instructions)-1].Mnemonic + last := a.Instructions[len(a.Instructions)-1] - if lastMnemonic == RETURN || lastMnemonic == JUMP { + if last.Mnemonic == RETURN || last.Mnemonic == JUMP { return true } + if last.Mnemonic == CALL { + label, isLabel := last.Data.(*Label) + + if isLabel && label.String() == "core.exit" { + return true + } + } + return false } diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index 2619905..708f207 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -1,7 +1,6 @@ package asmc import ( - "git.urbach.dev/cli/q/src/arm" "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/dll" @@ -31,10 +30,6 @@ func Finalize(a asm.Assembler, dlls dll.List) ([]byte, []byte) { c.compileARM(x) } - c.code = arm.MoveRegisterNumber(c.code, arm.X0, 0) - c.code = arm.MoveRegisterNumber(c.code, arm.X8, 0x5D) - c.code = arm.Syscall(c.code) - case config.X86: for _, x := range a.Instructions { c.compileX86(x) diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 06c2417..058f1f0 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -1,7 +1,10 @@ package asmc import ( + "encoding/binary" "fmt" + "math" + "strings" "git.urbach.dev/cli/q/src/arm" "git.urbach.dev/cli/q/src/asm" @@ -12,11 +15,11 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.CALL: switch data := x.Data.(type) { case *asm.Label: - position := len(c.code) - c.code = arm.Call(c.code, 0) + position := Address(len(c.code)) + c.append(arm.Call(0)) pointer := &pointer{ - Position: Address(position), + Position: position, OpSize: 0, Size: 4, } @@ -28,8 +31,8 @@ func (c *compiler) compileARM(x asm.Instruction) { panic(fmt.Sprintf("unknown jump label %s", data.Name)) } - distance := (destination - pointer.Position) / 4 - return arm.EncodeCall(distance) + distance := (destination - position) / 4 + return arm.Call(distance) } c.codePointers = append(c.codePointers, pointer) @@ -37,20 +40,67 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.LABEL: c.codeLabels[x.Data.(*asm.Label).Name] = Address(len(c.code)) + c.append(0xa9be7bfd) + c.append(0x910003fd) + + case asm.LOAD: + switch operands := x.Data.(type) { + case *asm.MemoryRegister: + if operands.Address.OffsetRegister == math.MaxUint8 { + c.append(arm.LoadRegister(operands.Register, operands.Address.Base, int16(operands.Address.Offset), operands.Address.Length)) + } else { + // TODO: LoadDynamicRegister + panic("not implemented") + } + } case asm.MOVE: switch operands := x.Data.(type) { + case *asm.RegisterRegister: + c.append(arm.MoveRegisterRegister(operands.Destination, operands.Source)) + case *asm.RegisterNumber: - c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) + c.append(arm.MoveRegisterNumber(operands.Register, operands.Number)) + + case *asm.RegisterLabel: + position := Address(len(c.code)) + c.append(arm.LoadAddress(operands.Register, 0)) + + if strings.HasPrefix(operands.Label, "data ") { + c.dataPointers = append(c.dataPointers, &pointer{ + Position: position, + OpSize: 0, + Size: 4, + Resolve: func() Address { + destination, exists := c.dataLabels[operands.Label] + + if !exists { + panic("unknown label") + } + + destination += c.dataStart - c.codeStart + distance := destination - position + 8 + return arm.LoadAddress(operands.Register, int(distance)) + }, + }) + } else { + panic("not implemented") + } } case asm.RETURN: - c.code = arm.Return(c.code) + c.append(0xa8c27bfd) + c.append(0xd65f03c0) + c.append(arm.Return()) case asm.SYSCALL: - c.code = arm.Syscall(c.code) + c.append(arm.Syscall()) default: - c.code = arm.Nop(c.code) + panic("unknown mnemonic: " + x.Mnemonic.String()) } } + +func (c *compiler) append(code uint32) { + c.code = binary.LittleEndian.AppendUint32(c.code, code) +} diff --git a/src/asmc/compiler.go b/src/asmc/compiler.go index b4e30e6..041e590 100644 --- a/src/asmc/compiler.go +++ b/src/asmc/compiler.go @@ -12,4 +12,5 @@ type compiler struct { dllPointers []*pointer dlls dll.List codeStart Address + dataStart Address } diff --git a/src/asmc/load.go b/src/asmc/load.go index 7a3a707..8b28fa8 100644 --- a/src/asmc/load.go +++ b/src/asmc/load.go @@ -11,9 +11,9 @@ func (c *compiler) load(x asm.Instruction) { switch operands := x.Data.(type) { case *asm.MemoryRegister: if operands.Address.OffsetRegister == math.MaxUint8 { - c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) + c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Base, operands.Address.Offset, operands.Address.Length) } else { - c.code = x86.LoadDynamicRegister(c.code, operands.Register, operands.Address.OffsetRegister, operands.Address.Length, operands.Address.Base) + c.code = x86.LoadDynamicRegister(c.code, operands.Register, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length) } } } diff --git a/src/asmc/move.go b/src/asmc/move.go index 71e71f3..056872c 100644 --- a/src/asmc/move.go +++ b/src/asmc/move.go @@ -35,7 +35,7 @@ func (c *compiler) move(x asm.Instruction) { panic("unknown label") } - return destination + return config.BaseAddress + c.dataStart + destination + 8 }, }) } else { diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index ea8aef2..6e3ac9d 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -77,16 +77,16 @@ restart: } } - dataStart, _ := fs.Align(c.codeStart+Address(len(c.code)), config.Align) + c.dataStart, _ = fs.Align(c.codeStart+Address(len(c.code)), config.Align) for _, pointer := range c.dataPointers { - address := config.BaseAddress + dataStart + pointer.Resolve() + 8 - slice := c.code[pointer.Position : pointer.Position+4] + address := pointer.Resolve() + slice := c.code[pointer.Position : pointer.Position+Address(pointer.Size)] binary.LittleEndian.PutUint32(slice, uint32(address)) } if config.TargetOS == config.Windows { - importsStart, _ := fs.Align(dataStart+Address(len(c.data)), config.Align) + importsStart, _ := fs.Align(c.dataStart+Address(len(c.data)), config.Align) for _, pointer := range c.dllPointers { destination := importsStart + pointer.Resolve() diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index e10e41e..306309e 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -6,7 +6,6 @@ import ( "git.urbach.dev/cli/q/src/scope" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" - "git.urbach.dev/cli/q/src/x86" ) // ResolveTypes parses the input and output types. @@ -34,7 +33,7 @@ func (f *Function) ResolveTypes() error { Name: param.name, Value: eval.Register{ Typ: param.typ, - Register: x86.InputRegisters[i], + Register: f.CPU.Input[i], Alive: uses, }, }) diff --git a/src/x86/Load.go b/src/x86/Load.go index dc04a25..f29a08e 100644 --- a/src/x86/Load.go +++ b/src/x86/Load.go @@ -3,6 +3,6 @@ package x86 import "git.urbach.dev/cli/q/src/cpu" // LoadRegister loads from memory into a register. -func LoadRegister(code []byte, destination cpu.Register, offset int8, length byte, source cpu.Register) []byte { - return memoryAccess(code, 0x8A, 0x8B, source, offset, length, destination) +func LoadRegister(code []byte, destination cpu.Register, base cpu.Register, offset int8, length byte) []byte { + return memoryAccess(code, 0x8A, 0x8B, base, offset, length, destination) } diff --git a/src/x86/LoadDynamic.go b/src/x86/LoadDynamic.go index 1c5cb30..c06878e 100644 --- a/src/x86/LoadDynamic.go +++ b/src/x86/LoadDynamic.go @@ -3,6 +3,6 @@ package x86 import "git.urbach.dev/cli/q/src/cpu" // LoadDynamicRegister loads from memory with a register offset into a register. -func LoadDynamicRegister(code []byte, destination cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { - return memoryAccessDynamic(code, 0x8A, 0x8B, source, offset, length, destination) +func LoadDynamicRegister(code []byte, destination cpu.Register, base cpu.Register, offset cpu.Register, length byte) []byte { + return memoryAccessDynamic(code, 0x8A, 0x8B, base, offset, length, destination) } diff --git a/src/x86/LoadDynamic_test.go b/src/x86/LoadDynamic_test.go index 9638e80..4cc3935 100644 --- a/src/x86/LoadDynamic_test.go +++ b/src/x86/LoadDynamic_test.go @@ -12,7 +12,7 @@ func TestLoadDynamicRegister(t *testing.T) { usagePatterns := []struct { Destination cpu.Register Length byte - Source cpu.Register + Base cpu.Register OffsetRegister cpu.Register Code []byte }{ @@ -83,8 +83,8 @@ func TestLoadDynamicRegister(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("load %dB %s, [%s+%s]", pattern.Length, pattern.Destination, pattern.Source, pattern.OffsetRegister) - code := x86.LoadDynamicRegister(nil, pattern.Destination, pattern.OffsetRegister, pattern.Length, pattern.Source) + t.Logf("load %dB %s, [%s+%s]", pattern.Length, pattern.Destination, pattern.Base, pattern.OffsetRegister) + code := x86.LoadDynamicRegister(nil, pattern.Destination, pattern.Base, pattern.OffsetRegister, pattern.Length) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/x86/Load_test.go b/src/x86/Load_test.go index b81f6e0..3d5240b 100644 --- a/src/x86/Load_test.go +++ b/src/x86/Load_test.go @@ -11,7 +11,7 @@ import ( func TestLoadRegister(t *testing.T) { usagePatterns := []struct { Destination cpu.Register - Source cpu.Register + Base cpu.Register Offset int8 Length byte Code []byte @@ -150,8 +150,8 @@ func TestLoadRegister(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("load %dB %s, [%s+%d]", pattern.Length, pattern.Destination, pattern.Source, pattern.Offset) - code := x86.LoadRegister(nil, pattern.Destination, pattern.Offset, pattern.Length, pattern.Source) + t.Logf("load %dB %s, [%s+%d]", pattern.Length, pattern.Destination, pattern.Base, pattern.Offset) + code := x86.LoadRegister(nil, pattern.Destination, pattern.Base, pattern.Offset, pattern.Length) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/x86/Move.go b/src/x86/Move.go index efe8756..7e3b103 100644 --- a/src/x86/Move.go +++ b/src/x86/Move.go @@ -54,7 +54,7 @@ func MoveRegisterNumber32(code []byte, destination cpu.Register, number int) []b return binary.LittleEndian.AppendUint32(code, uint32(number)) } -// MoveRegisterRegister moves a register value into another register. +// MoveRegisterRegister copies a register to another register. func MoveRegisterRegister(code []byte, destination cpu.Register, source cpu.Register) []byte { return encode(code, AddressDirect, source, destination, 8, 0x89) } From 0ac7fc9a853a715d751833cb8ceeebc56a8a4771 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 6 Mar 2025 23:13:14 +0100 Subject: [PATCH 0892/1012] Implemented more arm64 instructions --- lib/sys/sys_linux_arm.q | 15 ++++++ lib/sys/{sys_linux.q => sys_linux_x86.q} | 0 src/arm/Call.go | 9 +--- src/arm/Load.go | 13 +++++ src/arm/LoadAddress.go | 10 ++++ src/arm/LoadAddress_test.go | 26 +++++++++ src/arm/Load_test.go | 31 +++++++++++ src/arm/Move.go | 21 ++++---- src/arm/Move_test.go | 33 +++++++++--- src/arm/Nop.go | 4 +- src/arm/Return.go | 4 +- src/arm/Syscall.go | 4 +- src/arm/arm_test.go | 10 ++-- src/asm/CanSkipReturn.go | 12 ++++- src/asmc/Finalize.go | 5 -- src/asmc/compileARM.go | 68 ++++++++++++++++++++---- src/asmc/compiler.go | 1 + src/asmc/load.go | 4 +- src/asmc/move.go | 2 +- src/asmc/resolvePointers.go | 8 +-- src/core/ResolveTypes.go | 3 +- src/x86/Load.go | 4 +- src/x86/LoadDynamic.go | 4 +- src/x86/LoadDynamic_test.go | 6 +-- src/x86/Load_test.go | 6 +-- src/x86/Move.go | 2 +- 26 files changed, 232 insertions(+), 73 deletions(-) create mode 100644 lib/sys/sys_linux_arm.q rename lib/sys/{sys_linux.q => sys_linux_x86.q} (100%) create mode 100644 src/arm/Load.go create mode 100644 src/arm/LoadAddress.go create mode 100644 src/arm/LoadAddress_test.go create mode 100644 src/arm/Load_test.go diff --git a/lib/sys/sys_linux_arm.q b/lib/sys/sys_linux_arm.q new file mode 100644 index 0000000..cfc351e --- /dev/null +++ b/lib/sys/sys_linux_arm.q @@ -0,0 +1,15 @@ +read(fd int, buffer *byte, length int) -> int { + return syscall(63, fd, buffer, length) +} + +write(fd int, buffer *byte, length int) -> int { + return syscall(64, fd, buffer, length) +} + +mmap(address int, length uint, protection int, flags int) -> *any { + return syscall(222, address, length, protection, flags) +} + +munmap(address *any, length uint) -> int { + return syscall(215, address, length) +} \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux_x86.q similarity index 100% rename from lib/sys/sys_linux.q rename to lib/sys/sys_linux_x86.q diff --git a/src/arm/Call.go b/src/arm/Call.go index 5122e02..8c1901f 100644 --- a/src/arm/Call.go +++ b/src/arm/Call.go @@ -1,15 +1,8 @@ package arm -import "encoding/binary" - // Call branches to a PC-relative offset, setting the register X30 to PC+4. // The offset starts from the address of this instruction and is encoded as "imm26" times 4. // This instruction is also known as BL (branch with link). -func Call(code []byte, offset uint32) []byte { - return binary.LittleEndian.AppendUint32(code, EncodeCall(offset)) -} - -// EncodeCall returns the raw encoding of a call with the given offset. -func EncodeCall(offset uint32) uint32 { +func Call(offset uint32) uint32 { return uint32(0b100101<<26) | offset } diff --git a/src/arm/Load.go b/src/arm/Load.go new file mode 100644 index 0000000..e1d09e1 --- /dev/null +++ b/src/arm/Load.go @@ -0,0 +1,13 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// LoadRegister loads from memory into a register. +func LoadRegister(destination cpu.Register, base cpu.Register, offset int16, length byte) uint32 { + if offset < 0 { + offset &= 0xFF + offset |= 1 << 8 + } + + return 0b11111000010<<21 | uint32(offset)<<12 | uint32(base)<<5 | uint32(destination) +} diff --git a/src/arm/LoadAddress.go b/src/arm/LoadAddress.go new file mode 100644 index 0000000..00284f5 --- /dev/null +++ b/src/arm/LoadAddress.go @@ -0,0 +1,10 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// LoadAddress calculates the address with the PC-relative offset and writes the result to the destination register. +func LoadAddress(destination cpu.Register, offset int) uint32 { + hi := uint32(offset) >> 2 + lo := uint32(offset) & 0b11 + return lo<<29 | 0b10000<<24 | hi<<5 | uint32(destination) +} diff --git a/src/arm/LoadAddress_test.go b/src/arm/LoadAddress_test.go new file mode 100644 index 0000000..3b3c05f --- /dev/null +++ b/src/arm/LoadAddress_test.go @@ -0,0 +1,26 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestLoadAddress(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Number int + Code uint32 + }{ + {arm.X0, 56, 0x100001C0}, + {arm.X1, 80, 0x10000281}, + } + + for _, pattern := range usagePatterns { + t.Logf("adr %s, %d", pattern.Destination, pattern.Number) + code := arm.LoadAddress(pattern.Destination, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Load_test.go b/src/arm/Load_test.go new file mode 100644 index 0000000..c04f60a --- /dev/null +++ b/src/arm/Load_test.go @@ -0,0 +1,31 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestLoadRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Base cpu.Register + Offset int16 + Length byte + Code uint32 + }{ + {arm.X2, arm.X1, -8, 8, 0xF85F8022}, + {arm.X2, arm.X1, 0, 8, 0xF8400022}, + {arm.X2, arm.X1, 8, 8, 0xF8408022}, + {arm.X2, arm.X1, -256, 8, 0xF8500022}, + {arm.X2, arm.X1, 255, 8, 0xF84FF022}, + } + + for _, pattern := range usagePatterns { + t.Logf("ldur %s, [%s, %d] %db", pattern.Destination, pattern.Base, pattern.Offset, pattern.Length) + code := arm.LoadRegister(pattern.Destination, pattern.Base, pattern.Offset, pattern.Length) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Move.go b/src/arm/Move.go index f26744f..e61ff8f 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -1,26 +1,27 @@ package arm import ( - "encoding/binary" - "git.urbach.dev/cli/q/src/cpu" ) +// MoveRegisterRegister copies a register to another register. +func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 { + return 0b10101010<<24 | uint32(source)<<16 | 0b11111<<5 | uint32(destination) +} + // MoveRegisterNumber moves an integer into the given register. -func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte { - return MoveZero(code, destination, 0, uint16(number)) +func MoveRegisterNumber(destination cpu.Register, number int) uint32 { + return MoveZero(destination, 0, uint16(number)) } // MoveKeep moves a 16-bit integer into the given register and keeps all other bits. -func MoveKeep(code []byte, destination cpu.Register, halfword int, number uint16) []byte { - x := mov(0b11, halfword, number, destination) - return binary.LittleEndian.AppendUint32(code, x) +func MoveKeep(destination cpu.Register, halfword int, number uint16) uint32 { + return mov(0b11, halfword, number, destination) } // MoveZero moves a 16-bit integer into the given register and clears all other bits to zero. -func MoveZero(code []byte, destination cpu.Register, halfword int, number uint16) []byte { - x := mov(0b10, halfword, number, destination) - return binary.LittleEndian.AppendUint32(code, x) +func MoveZero(destination cpu.Register, halfword int, number uint16) uint32 { + return mov(0b10, halfword, number, destination) } // mov encodes a generic move instruction. diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go index fb7f7d3..9811e37 100644 --- a/src/arm/Move_test.go +++ b/src/arm/Move_test.go @@ -8,19 +8,36 @@ import ( "git.urbach.dev/go/assert" ) +func TestMoveRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, 0xAA0103E0}, + {arm.X1, arm.X0, 0xAA0003E1}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, %s", pattern.Destination, pattern.Source) + code := arm.MoveRegisterRegister(pattern.Destination, pattern.Source) + assert.DeepEqual(t, code, pattern.Code) + } +} + func TestMoveKeep(t *testing.T) { usagePatterns := []struct { Register cpu.Register Number uint16 - Code []byte + Code uint32 }{ - {arm.X0, 0, []byte{0x00, 0x00, 0x80, 0xF2}}, - {arm.X0, 1, []byte{0x20, 0x00, 0x80, 0xF2}}, + {arm.X0, 0, 0xF2800000}, + {arm.X0, 1, 0xF2800020}, } for _, pattern := range usagePatterns { t.Logf("movk %s, %x", pattern.Register, pattern.Number) - code := arm.MoveKeep(nil, pattern.Register, 0, pattern.Number) + code := arm.MoveKeep(pattern.Register, 0, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } @@ -29,15 +46,15 @@ func TestMoveZero(t *testing.T) { usagePatterns := []struct { Register cpu.Register Number uint16 - Code []byte + Code uint32 }{ - {arm.X0, 0, []byte{0x00, 0x00, 0x80, 0xD2}}, - {arm.X0, 1, []byte{0x20, 0x00, 0x80, 0xD2}}, + {arm.X0, 0, 0xD2800000}, + {arm.X0, 1, 0xD2800020}, } for _, pattern := range usagePatterns { t.Logf("movz %s, %x", pattern.Register, pattern.Number) - code := arm.MoveZero(nil, pattern.Register, 0, pattern.Number) + code := arm.MoveZero(pattern.Register, 0, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/arm/Nop.go b/src/arm/Nop.go index 8152293..6f518f4 100644 --- a/src/arm/Nop.go +++ b/src/arm/Nop.go @@ -1,6 +1,6 @@ package arm // Nop does nothing. This can be used for alignment purposes. -func Nop(code []byte) []byte { - return append(code, 0x1F, 0x20, 0x03, 0xD5) +func Nop() uint32 { + return 0xD503201F } diff --git a/src/arm/Return.go b/src/arm/Return.go index b98c038..898c6e7 100644 --- a/src/arm/Return.go +++ b/src/arm/Return.go @@ -1,6 +1,6 @@ package arm // Return transfers program control to the caller. -func Return(code []byte) []byte { - return append(code, 0xC0, 0x03, 0x5F, 0xD6) +func Return() uint32 { + return 0xD65F03C0 } diff --git a/src/arm/Syscall.go b/src/arm/Syscall.go index 2792926..e9227d6 100644 --- a/src/arm/Syscall.go +++ b/src/arm/Syscall.go @@ -1,6 +1,6 @@ package arm // Syscall is the primary way to communicate with the OS kernel. -func Syscall(code []byte) []byte { - return append(code, 0x01, 0x00, 0x00, 0xD4) +func Syscall() uint32 { + return 0xD4000001 } diff --git a/src/arm/arm_test.go b/src/arm/arm_test.go index b4918fc..948f869 100644 --- a/src/arm/arm_test.go +++ b/src/arm/arm_test.go @@ -8,9 +8,9 @@ import ( ) func TestARM(t *testing.T) { - assert.DeepEqual(t, arm.Call(nil, 0), []byte{0x00, 0x00, 0x00, 0x94}) - assert.DeepEqual(t, arm.MoveRegisterNumber(nil, arm.X0, 42), arm.MoveZero(nil, arm.X0, 0, 42)) - assert.DeepEqual(t, arm.Nop(nil), []byte{0x1F, 0x20, 0x03, 0xD5}) - assert.DeepEqual(t, arm.Return(nil), []byte{0xC0, 0x03, 0x5F, 0xD6}) - assert.DeepEqual(t, arm.Syscall(nil), []byte{0x01, 0x00, 0x00, 0xD4}) + assert.DeepEqual(t, arm.Call(0), 0x94000000) + assert.DeepEqual(t, arm.MoveRegisterNumber(arm.X0, 42), arm.MoveZero(arm.X0, 0, 42)) + assert.DeepEqual(t, arm.Nop(), 0xD503201F) + assert.DeepEqual(t, arm.Return(), 0xD65F03C0) + assert.DeepEqual(t, arm.Syscall(), 0xD4000001) } diff --git a/src/asm/CanSkipReturn.go b/src/asm/CanSkipReturn.go index 0ce31cb..56e5096 100644 --- a/src/asm/CanSkipReturn.go +++ b/src/asm/CanSkipReturn.go @@ -6,11 +6,19 @@ func (a *Assembler) CanSkipReturn() bool { return false } - lastMnemonic := a.Instructions[len(a.Instructions)-1].Mnemonic + last := a.Instructions[len(a.Instructions)-1] - if lastMnemonic == RETURN || lastMnemonic == JUMP { + if last.Mnemonic == RETURN || last.Mnemonic == JUMP { return true } + if last.Mnemonic == CALL { + label, isLabel := last.Data.(*Label) + + if isLabel && label.String() == "core.exit" { + return true + } + } + return false } diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index 2619905..708f207 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -1,7 +1,6 @@ package asmc import ( - "git.urbach.dev/cli/q/src/arm" "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/dll" @@ -31,10 +30,6 @@ func Finalize(a asm.Assembler, dlls dll.List) ([]byte, []byte) { c.compileARM(x) } - c.code = arm.MoveRegisterNumber(c.code, arm.X0, 0) - c.code = arm.MoveRegisterNumber(c.code, arm.X8, 0x5D) - c.code = arm.Syscall(c.code) - case config.X86: for _, x := range a.Instructions { c.compileX86(x) diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 06c2417..058f1f0 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -1,7 +1,10 @@ package asmc import ( + "encoding/binary" "fmt" + "math" + "strings" "git.urbach.dev/cli/q/src/arm" "git.urbach.dev/cli/q/src/asm" @@ -12,11 +15,11 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.CALL: switch data := x.Data.(type) { case *asm.Label: - position := len(c.code) - c.code = arm.Call(c.code, 0) + position := Address(len(c.code)) + c.append(arm.Call(0)) pointer := &pointer{ - Position: Address(position), + Position: position, OpSize: 0, Size: 4, } @@ -28,8 +31,8 @@ func (c *compiler) compileARM(x asm.Instruction) { panic(fmt.Sprintf("unknown jump label %s", data.Name)) } - distance := (destination - pointer.Position) / 4 - return arm.EncodeCall(distance) + distance := (destination - position) / 4 + return arm.Call(distance) } c.codePointers = append(c.codePointers, pointer) @@ -37,20 +40,67 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.LABEL: c.codeLabels[x.Data.(*asm.Label).Name] = Address(len(c.code)) + c.append(0xa9be7bfd) + c.append(0x910003fd) + + case asm.LOAD: + switch operands := x.Data.(type) { + case *asm.MemoryRegister: + if operands.Address.OffsetRegister == math.MaxUint8 { + c.append(arm.LoadRegister(operands.Register, operands.Address.Base, int16(operands.Address.Offset), operands.Address.Length)) + } else { + // TODO: LoadDynamicRegister + panic("not implemented") + } + } case asm.MOVE: switch operands := x.Data.(type) { + case *asm.RegisterRegister: + c.append(arm.MoveRegisterRegister(operands.Destination, operands.Source)) + case *asm.RegisterNumber: - c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) + c.append(arm.MoveRegisterNumber(operands.Register, operands.Number)) + + case *asm.RegisterLabel: + position := Address(len(c.code)) + c.append(arm.LoadAddress(operands.Register, 0)) + + if strings.HasPrefix(operands.Label, "data ") { + c.dataPointers = append(c.dataPointers, &pointer{ + Position: position, + OpSize: 0, + Size: 4, + Resolve: func() Address { + destination, exists := c.dataLabels[operands.Label] + + if !exists { + panic("unknown label") + } + + destination += c.dataStart - c.codeStart + distance := destination - position + 8 + return arm.LoadAddress(operands.Register, int(distance)) + }, + }) + } else { + panic("not implemented") + } } case asm.RETURN: - c.code = arm.Return(c.code) + c.append(0xa8c27bfd) + c.append(0xd65f03c0) + c.append(arm.Return()) case asm.SYSCALL: - c.code = arm.Syscall(c.code) + c.append(arm.Syscall()) default: - c.code = arm.Nop(c.code) + panic("unknown mnemonic: " + x.Mnemonic.String()) } } + +func (c *compiler) append(code uint32) { + c.code = binary.LittleEndian.AppendUint32(c.code, code) +} diff --git a/src/asmc/compiler.go b/src/asmc/compiler.go index b4e30e6..041e590 100644 --- a/src/asmc/compiler.go +++ b/src/asmc/compiler.go @@ -12,4 +12,5 @@ type compiler struct { dllPointers []*pointer dlls dll.List codeStart Address + dataStart Address } diff --git a/src/asmc/load.go b/src/asmc/load.go index 7a3a707..8b28fa8 100644 --- a/src/asmc/load.go +++ b/src/asmc/load.go @@ -11,9 +11,9 @@ func (c *compiler) load(x asm.Instruction) { switch operands := x.Data.(type) { case *asm.MemoryRegister: if operands.Address.OffsetRegister == math.MaxUint8 { - c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) + c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Base, operands.Address.Offset, operands.Address.Length) } else { - c.code = x86.LoadDynamicRegister(c.code, operands.Register, operands.Address.OffsetRegister, operands.Address.Length, operands.Address.Base) + c.code = x86.LoadDynamicRegister(c.code, operands.Register, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length) } } } diff --git a/src/asmc/move.go b/src/asmc/move.go index 71e71f3..056872c 100644 --- a/src/asmc/move.go +++ b/src/asmc/move.go @@ -35,7 +35,7 @@ func (c *compiler) move(x asm.Instruction) { panic("unknown label") } - return destination + return config.BaseAddress + c.dataStart + destination + 8 }, }) } else { diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index ea8aef2..6e3ac9d 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -77,16 +77,16 @@ restart: } } - dataStart, _ := fs.Align(c.codeStart+Address(len(c.code)), config.Align) + c.dataStart, _ = fs.Align(c.codeStart+Address(len(c.code)), config.Align) for _, pointer := range c.dataPointers { - address := config.BaseAddress + dataStart + pointer.Resolve() + 8 - slice := c.code[pointer.Position : pointer.Position+4] + address := pointer.Resolve() + slice := c.code[pointer.Position : pointer.Position+Address(pointer.Size)] binary.LittleEndian.PutUint32(slice, uint32(address)) } if config.TargetOS == config.Windows { - importsStart, _ := fs.Align(dataStart+Address(len(c.data)), config.Align) + importsStart, _ := fs.Align(c.dataStart+Address(len(c.data)), config.Align) for _, pointer := range c.dllPointers { destination := importsStart + pointer.Resolve() diff --git a/src/core/ResolveTypes.go b/src/core/ResolveTypes.go index e10e41e..306309e 100644 --- a/src/core/ResolveTypes.go +++ b/src/core/ResolveTypes.go @@ -6,7 +6,6 @@ import ( "git.urbach.dev/cli/q/src/scope" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" - "git.urbach.dev/cli/q/src/x86" ) // ResolveTypes parses the input and output types. @@ -34,7 +33,7 @@ func (f *Function) ResolveTypes() error { Name: param.name, Value: eval.Register{ Typ: param.typ, - Register: x86.InputRegisters[i], + Register: f.CPU.Input[i], Alive: uses, }, }) diff --git a/src/x86/Load.go b/src/x86/Load.go index dc04a25..f29a08e 100644 --- a/src/x86/Load.go +++ b/src/x86/Load.go @@ -3,6 +3,6 @@ package x86 import "git.urbach.dev/cli/q/src/cpu" // LoadRegister loads from memory into a register. -func LoadRegister(code []byte, destination cpu.Register, offset int8, length byte, source cpu.Register) []byte { - return memoryAccess(code, 0x8A, 0x8B, source, offset, length, destination) +func LoadRegister(code []byte, destination cpu.Register, base cpu.Register, offset int8, length byte) []byte { + return memoryAccess(code, 0x8A, 0x8B, base, offset, length, destination) } diff --git a/src/x86/LoadDynamic.go b/src/x86/LoadDynamic.go index 1c5cb30..c06878e 100644 --- a/src/x86/LoadDynamic.go +++ b/src/x86/LoadDynamic.go @@ -3,6 +3,6 @@ package x86 import "git.urbach.dev/cli/q/src/cpu" // LoadDynamicRegister loads from memory with a register offset into a register. -func LoadDynamicRegister(code []byte, destination cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { - return memoryAccessDynamic(code, 0x8A, 0x8B, source, offset, length, destination) +func LoadDynamicRegister(code []byte, destination cpu.Register, base cpu.Register, offset cpu.Register, length byte) []byte { + return memoryAccessDynamic(code, 0x8A, 0x8B, base, offset, length, destination) } diff --git a/src/x86/LoadDynamic_test.go b/src/x86/LoadDynamic_test.go index 9638e80..4cc3935 100644 --- a/src/x86/LoadDynamic_test.go +++ b/src/x86/LoadDynamic_test.go @@ -12,7 +12,7 @@ func TestLoadDynamicRegister(t *testing.T) { usagePatterns := []struct { Destination cpu.Register Length byte - Source cpu.Register + Base cpu.Register OffsetRegister cpu.Register Code []byte }{ @@ -83,8 +83,8 @@ func TestLoadDynamicRegister(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("load %dB %s, [%s+%s]", pattern.Length, pattern.Destination, pattern.Source, pattern.OffsetRegister) - code := x86.LoadDynamicRegister(nil, pattern.Destination, pattern.OffsetRegister, pattern.Length, pattern.Source) + t.Logf("load %dB %s, [%s+%s]", pattern.Length, pattern.Destination, pattern.Base, pattern.OffsetRegister) + code := x86.LoadDynamicRegister(nil, pattern.Destination, pattern.Base, pattern.OffsetRegister, pattern.Length) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/x86/Load_test.go b/src/x86/Load_test.go index b81f6e0..3d5240b 100644 --- a/src/x86/Load_test.go +++ b/src/x86/Load_test.go @@ -11,7 +11,7 @@ import ( func TestLoadRegister(t *testing.T) { usagePatterns := []struct { Destination cpu.Register - Source cpu.Register + Base cpu.Register Offset int8 Length byte Code []byte @@ -150,8 +150,8 @@ func TestLoadRegister(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("load %dB %s, [%s+%d]", pattern.Length, pattern.Destination, pattern.Source, pattern.Offset) - code := x86.LoadRegister(nil, pattern.Destination, pattern.Offset, pattern.Length, pattern.Source) + t.Logf("load %dB %s, [%s+%d]", pattern.Length, pattern.Destination, pattern.Base, pattern.Offset) + code := x86.LoadRegister(nil, pattern.Destination, pattern.Base, pattern.Offset, pattern.Length) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/x86/Move.go b/src/x86/Move.go index efe8756..7e3b103 100644 --- a/src/x86/Move.go +++ b/src/x86/Move.go @@ -54,7 +54,7 @@ func MoveRegisterNumber32(code []byte, destination cpu.Register, number int) []b return binary.LittleEndian.AppendUint32(code, uint32(number)) } -// MoveRegisterRegister moves a register value into another register. +// MoveRegisterRegister copies a register to another register. func MoveRegisterRegister(code []byte, destination cpu.Register, source cpu.Register) []byte { return encode(code, AddressDirect, source, destination, 8, 0x89) } From 07653518911f1960d86257149e6b6f70dd27f075 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 7 Mar 2025 11:39:13 +0100 Subject: [PATCH 0893/1012] Implemented position independent addresses for x86 --- src/asmc/move.go | 28 ++++++++++++++------------ src/x86/LoadAddress.go | 13 ++++++++++++ src/x86/LoadAddress_test.go | 40 +++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 src/x86/LoadAddress.go create mode 100644 src/x86/LoadAddress_test.go diff --git a/src/asmc/move.go b/src/asmc/move.go index 056872c..c4f60d7 100644 --- a/src/asmc/move.go +++ b/src/asmc/move.go @@ -4,7 +4,6 @@ import ( "strings" "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/x86" ) @@ -17,40 +16,43 @@ func (c *compiler) move(x asm.Instruction) { c.code = x86.MoveRegisterRegister(c.code, operands.Destination, operands.Source) case *asm.RegisterLabel: - start := len(c.code) - c.code = x86.MoveRegisterNumber(c.code, operands.Register, 0x00_00_00_00) - size := 4 - opSize := len(c.code) - size - start - regLabel := x.Data.(*asm.RegisterLabel) + start := Address(len(c.code)) + c.code = x86.LoadAddress(c.code, operands.Register, 0x00_00_00_00) + end := Address(len(c.code)) + size := uint32(4) + position := end - size + opSize := position - start - if strings.HasPrefix(regLabel.Label, "data ") { + if strings.HasPrefix(operands.Label, "data ") { c.dataPointers = append(c.dataPointers, &pointer{ - Position: Address(len(c.code) - size), + Position: position, OpSize: uint8(opSize), Size: uint8(size), Resolve: func() Address { - destination, exists := c.dataLabels[regLabel.Label] + destination, exists := c.dataLabels[operands.Label] if !exists { panic("unknown label") } - return config.BaseAddress + c.dataStart + destination + 8 + destination += c.dataStart - c.codeStart + distance := destination - end + return distance + 8 }, }) } else { c.codePointers = append(c.codePointers, &pointer{ - Position: Address(len(c.code) - size), + Position: position, OpSize: uint8(opSize), Size: uint8(size), Resolve: func() Address { - destination, exists := c.codeLabels[regLabel.Label] + destination, exists := c.codeLabels[operands.Label] if !exists { panic("unknown label") } - return config.BaseAddress + c.codeStart + destination + return destination - end }, }) } diff --git a/src/x86/LoadAddress.go b/src/x86/LoadAddress.go new file mode 100644 index 0000000..fd58889 --- /dev/null +++ b/src/x86/LoadAddress.go @@ -0,0 +1,13 @@ +package x86 + +import ( + "encoding/binary" + + "git.urbach.dev/cli/q/src/cpu" +) + +// LoadAddress calculates the address with the RIP-relative offset and writes the result to the destination register. +func LoadAddress(code []byte, destination cpu.Register, offset int) []byte { + code = encode(code, AddressMemory, destination, 0b101, 8, 0x8D) + return binary.LittleEndian.AppendUint32(code, uint32(offset)) +} diff --git a/src/x86/LoadAddress_test.go b/src/x86/LoadAddress_test.go new file mode 100644 index 0000000..cedf23f --- /dev/null +++ b/src/x86/LoadAddress_test.go @@ -0,0 +1,40 @@ +package x86_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" +) + +func TestLoadAddress(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Offset int + Code []byte + }{ + {x86.RAX, 0, []byte{0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00}}, + {x86.RCX, 0, []byte{0x48, 0x8D, 0x0D, 0x00, 0x00, 0x00, 0x00}}, + {x86.RDX, 0, []byte{0x48, 0x8D, 0x15, 0x00, 0x00, 0x00, 0x00}}, + {x86.RBX, 0, []byte{0x48, 0x8D, 0x1D, 0x00, 0x00, 0x00, 0x00}}, + {x86.RSP, 0, []byte{0x48, 0x8D, 0x25, 0x00, 0x00, 0x00, 0x00}}, + {x86.RBP, 0, []byte{0x48, 0x8D, 0x2D, 0x00, 0x00, 0x00, 0x00}}, + {x86.RSI, 0, []byte{0x48, 0x8D, 0x35, 0x00, 0x00, 0x00, 0x00}}, + {x86.RDI, 0, []byte{0x48, 0x8D, 0x3D, 0x00, 0x00, 0x00, 0x00}}, + {x86.R8, 0, []byte{0x4C, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00}}, + {x86.R9, 0, []byte{0x4C, 0x8D, 0x0D, 0x00, 0x00, 0x00, 0x00}}, + {x86.R10, 0, []byte{0x4C, 0x8D, 0x15, 0x00, 0x00, 0x00, 0x00}}, + {x86.R11, 0, []byte{0x4C, 0x8D, 0x1D, 0x00, 0x00, 0x00, 0x00}}, + {x86.R12, 0, []byte{0x4C, 0x8D, 0x25, 0x00, 0x00, 0x00, 0x00}}, + {x86.R13, 0, []byte{0x4C, 0x8D, 0x2D, 0x00, 0x00, 0x00, 0x00}}, + {x86.R14, 0, []byte{0x4C, 0x8D, 0x35, 0x00, 0x00, 0x00, 0x00}}, + {x86.R15, 0, []byte{0x4C, 0x8D, 0x3D, 0x00, 0x00, 0x00, 0x00}}, + } + + for _, pattern := range usagePatterns { + t.Logf("lea %s, [rip+%d]", pattern.Destination, pattern.Offset) + code := x86.LoadAddress(nil, pattern.Destination, pattern.Offset) + assert.DeepEqual(t, code, pattern.Code) + } +} From 0bf299d007ebccdacc76774248ce844dbb41415a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 7 Mar 2025 11:39:13 +0100 Subject: [PATCH 0894/1012] Implemented position independent addresses for x86 --- src/asmc/move.go | 28 ++++++++++++++------------ src/x86/LoadAddress.go | 13 ++++++++++++ src/x86/LoadAddress_test.go | 40 +++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 src/x86/LoadAddress.go create mode 100644 src/x86/LoadAddress_test.go diff --git a/src/asmc/move.go b/src/asmc/move.go index 056872c..c4f60d7 100644 --- a/src/asmc/move.go +++ b/src/asmc/move.go @@ -4,7 +4,6 @@ import ( "strings" "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/x86" ) @@ -17,40 +16,43 @@ func (c *compiler) move(x asm.Instruction) { c.code = x86.MoveRegisterRegister(c.code, operands.Destination, operands.Source) case *asm.RegisterLabel: - start := len(c.code) - c.code = x86.MoveRegisterNumber(c.code, operands.Register, 0x00_00_00_00) - size := 4 - opSize := len(c.code) - size - start - regLabel := x.Data.(*asm.RegisterLabel) + start := Address(len(c.code)) + c.code = x86.LoadAddress(c.code, operands.Register, 0x00_00_00_00) + end := Address(len(c.code)) + size := uint32(4) + position := end - size + opSize := position - start - if strings.HasPrefix(regLabel.Label, "data ") { + if strings.HasPrefix(operands.Label, "data ") { c.dataPointers = append(c.dataPointers, &pointer{ - Position: Address(len(c.code) - size), + Position: position, OpSize: uint8(opSize), Size: uint8(size), Resolve: func() Address { - destination, exists := c.dataLabels[regLabel.Label] + destination, exists := c.dataLabels[operands.Label] if !exists { panic("unknown label") } - return config.BaseAddress + c.dataStart + destination + 8 + destination += c.dataStart - c.codeStart + distance := destination - end + return distance + 8 }, }) } else { c.codePointers = append(c.codePointers, &pointer{ - Position: Address(len(c.code) - size), + Position: position, OpSize: uint8(opSize), Size: uint8(size), Resolve: func() Address { - destination, exists := c.codeLabels[regLabel.Label] + destination, exists := c.codeLabels[operands.Label] if !exists { panic("unknown label") } - return config.BaseAddress + c.codeStart + destination + return destination - end }, }) } diff --git a/src/x86/LoadAddress.go b/src/x86/LoadAddress.go new file mode 100644 index 0000000..fd58889 --- /dev/null +++ b/src/x86/LoadAddress.go @@ -0,0 +1,13 @@ +package x86 + +import ( + "encoding/binary" + + "git.urbach.dev/cli/q/src/cpu" +) + +// LoadAddress calculates the address with the RIP-relative offset and writes the result to the destination register. +func LoadAddress(code []byte, destination cpu.Register, offset int) []byte { + code = encode(code, AddressMemory, destination, 0b101, 8, 0x8D) + return binary.LittleEndian.AppendUint32(code, uint32(offset)) +} diff --git a/src/x86/LoadAddress_test.go b/src/x86/LoadAddress_test.go new file mode 100644 index 0000000..cedf23f --- /dev/null +++ b/src/x86/LoadAddress_test.go @@ -0,0 +1,40 @@ +package x86_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" +) + +func TestLoadAddress(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Offset int + Code []byte + }{ + {x86.RAX, 0, []byte{0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00}}, + {x86.RCX, 0, []byte{0x48, 0x8D, 0x0D, 0x00, 0x00, 0x00, 0x00}}, + {x86.RDX, 0, []byte{0x48, 0x8D, 0x15, 0x00, 0x00, 0x00, 0x00}}, + {x86.RBX, 0, []byte{0x48, 0x8D, 0x1D, 0x00, 0x00, 0x00, 0x00}}, + {x86.RSP, 0, []byte{0x48, 0x8D, 0x25, 0x00, 0x00, 0x00, 0x00}}, + {x86.RBP, 0, []byte{0x48, 0x8D, 0x2D, 0x00, 0x00, 0x00, 0x00}}, + {x86.RSI, 0, []byte{0x48, 0x8D, 0x35, 0x00, 0x00, 0x00, 0x00}}, + {x86.RDI, 0, []byte{0x48, 0x8D, 0x3D, 0x00, 0x00, 0x00, 0x00}}, + {x86.R8, 0, []byte{0x4C, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00}}, + {x86.R9, 0, []byte{0x4C, 0x8D, 0x0D, 0x00, 0x00, 0x00, 0x00}}, + {x86.R10, 0, []byte{0x4C, 0x8D, 0x15, 0x00, 0x00, 0x00, 0x00}}, + {x86.R11, 0, []byte{0x4C, 0x8D, 0x1D, 0x00, 0x00, 0x00, 0x00}}, + {x86.R12, 0, []byte{0x4C, 0x8D, 0x25, 0x00, 0x00, 0x00, 0x00}}, + {x86.R13, 0, []byte{0x4C, 0x8D, 0x2D, 0x00, 0x00, 0x00, 0x00}}, + {x86.R14, 0, []byte{0x4C, 0x8D, 0x35, 0x00, 0x00, 0x00, 0x00}}, + {x86.R15, 0, []byte{0x4C, 0x8D, 0x3D, 0x00, 0x00, 0x00, 0x00}}, + } + + for _, pattern := range usagePatterns { + t.Logf("lea %s, [rip+%d]", pattern.Destination, pattern.Offset) + code := x86.LoadAddress(nil, pattern.Destination, pattern.Offset) + assert.DeepEqual(t, code, pattern.Code) + } +} From 955461d2de2b3f0b72eddc827a0c0c4536db0fb8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 7 Mar 2025 16:55:30 +0100 Subject: [PATCH 0895/1012] Added optional section headers --- src/cli/Build.go | 11 +++++++---- src/cli/Help.go | 9 +++++---- src/cli/Main_test.go | 2 +- src/config/config.go | 2 ++ src/elf/AddSections.go | 35 +++++++++++++++++++++++++++++++++++ src/elf/ELF.go | 18 +++++++++++++----- 6 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 src/elf/AddSections.go diff --git a/src/cli/Build.go b/src/cli/Build.go index c9aa8ec..75f4377 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -28,16 +28,19 @@ func buildExecutable(args []string) (*build.Build, error) { for i := 0; i < len(args); i++ { switch args[i] { - case "-a", "--assembly": + case "--assembly", "-a": config.ShowAssembly = true - case "-d", "--dry": + case "--dry": config.Dry = true - case "-s", "--statistics": + case "--sections": + config.Sections = true + + case "--statistics": config.ShowStatistics = true - case "-v", "--verbose": + case "--verbose", "-v": config.ShowAssembly = true config.ShowStatistics = true diff --git a/src/cli/Help.go b/src/cli/Help.go index c96c6f0..eeba43e 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -14,11 +14,12 @@ func Help(w io.Writer, code int) int { Commands: build [directory | file] build an executable from a file or directory - --arch [arch] cross-compile for another CPU architecture [x86|arm] + --arch [arch] cross-compile for CPU: [x86|arm] --assembly, -a show assembly instructions - --dry, -d skip writing the executable to disk - --os [os] cross-compile for another OS [linux|mac|windows] - --statistics, -s show statistics + --dry skip writing the executable to disk + --os [os] cross-compile for OS: [linux|mac|windows] + --sections add sections to the executable + --statistics show statistics --verbose, -v show everything run [directory | file] build and run the executable diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index 236ef5a..b19cbfa 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -28,7 +28,7 @@ func TestCLI(t *testing.T) { {[]string{"build", "../../examples/hello", "--dry", "--os", "mac"}, 0}, {[]string{"build", "../../examples/hello", "--dry", "--os", "windows"}, 0}, {[]string{"build", "../../examples/hello/hello.q", "--dry"}, 0}, - {[]string{"build", "../../examples/hello"}, 0}, + {[]string{"build", "../../examples/hello", "--sections"}, 0}, {[]string{"run", "../../examples/hello", "--invalid"}, 2}, {[]string{"run", "../../examples/hello"}, 0}, } diff --git a/src/config/config.go b/src/config/config.go index 98bcdb4..b031375 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -7,6 +7,7 @@ var ( Dry bool // Skips writing the executable to disk. ShowAssembly bool // Shows assembly instructions at the end. ShowStatistics bool // Shows statistics at the end. + Sections bool // Adds section headers to the executable. TargetArch Arch // Target architecture. TargetOS OS // Target platform. ) @@ -15,6 +16,7 @@ var ( func Reset() { ShowAssembly = false ShowStatistics = false + Sections = false Dry = false switch runtime.GOARCH { diff --git a/src/elf/AddSections.go b/src/elf/AddSections.go new file mode 100644 index 0000000..22d8e1f --- /dev/null +++ b/src/elf/AddSections.go @@ -0,0 +1,35 @@ +package elf + +// AddSections adds section headers to the ELF file. +func (elf *ELF) AddSections() { + elf.StringTable = []byte("\000.text\000.shstrtab\000") + stringTableStart := elf.DataHeader.Offset + elf.DataHeader.SizeInFile + sectionHeaderStart := stringTableStart + int64(len(elf.StringTable)) + + elf.SectionHeaders = []SectionHeader{ + { + Type: SectionTypeNULL, + }, + { + NameIndex: 1, + Type: SectionTypePROGBITS, + Flags: SectionFlagsAllocate | SectionFlagsExecutable, + VirtualAddress: elf.CodeHeader.VirtualAddress, + Offset: elf.CodeHeader.Offset, + SizeInFile: elf.CodeHeader.SizeInFile, + Align: elf.CodeHeader.Align, + }, + { + NameIndex: 7, + Type: SectionTypeSTRTAB, + Offset: int64(stringTableStart), + SizeInFile: int64(len(elf.StringTable)), + Align: 1, + }, + } + + elf.SectionHeaderEntrySize = SectionHeaderSize + elf.SectionHeaderEntryCount = int16(len(elf.SectionHeaders)) + elf.SectionHeaderOffset = int64(sectionHeaderStart) + elf.SectionNameStringTableIndex = 2 +} diff --git a/src/elf/ELF.go b/src/elf/ELF.go index e914620..4a36abe 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -14,8 +14,10 @@ const HeaderEnd = HeaderSize + ProgramHeaderSize*2 // ELF represents an ELF file. type ELF struct { Header - CodeHeader ProgramHeader - DataHeader ProgramHeader + CodeHeader ProgramHeader + DataHeader ProgramHeader + SectionHeaders []SectionHeader + StringTable []byte } // Write writes the ELF64 format to the given writer. @@ -77,14 +79,20 @@ func Write(writer io.Writer, code []byte, data []byte) { }, } + if config.Sections { + elf.AddSections() + } + binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.CodeHeader) binary.Write(writer, binary.LittleEndian, &elf.DataHeader) writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) writer.Write(code) + writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) + writer.Write(data) - if len(data) > 0 { - writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) - writer.Write(data) + if config.Sections { + writer.Write(elf.StringTable) + binary.Write(writer, binary.LittleEndian, &elf.SectionHeaders) } } From 919d94e0f456e8ef3c196b0eb65ba3d39f4b2a9d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 7 Mar 2025 16:55:30 +0100 Subject: [PATCH 0896/1012] Added optional section headers --- src/cli/Build.go | 11 +++++++---- src/cli/Help.go | 9 +++++---- src/cli/Main_test.go | 2 +- src/config/config.go | 2 ++ src/elf/AddSections.go | 35 +++++++++++++++++++++++++++++++++++ src/elf/ELF.go | 18 +++++++++++++----- 6 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 src/elf/AddSections.go diff --git a/src/cli/Build.go b/src/cli/Build.go index c9aa8ec..75f4377 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -28,16 +28,19 @@ func buildExecutable(args []string) (*build.Build, error) { for i := 0; i < len(args); i++ { switch args[i] { - case "-a", "--assembly": + case "--assembly", "-a": config.ShowAssembly = true - case "-d", "--dry": + case "--dry": config.Dry = true - case "-s", "--statistics": + case "--sections": + config.Sections = true + + case "--statistics": config.ShowStatistics = true - case "-v", "--verbose": + case "--verbose", "-v": config.ShowAssembly = true config.ShowStatistics = true diff --git a/src/cli/Help.go b/src/cli/Help.go index c96c6f0..eeba43e 100644 --- a/src/cli/Help.go +++ b/src/cli/Help.go @@ -14,11 +14,12 @@ func Help(w io.Writer, code int) int { Commands: build [directory | file] build an executable from a file or directory - --arch [arch] cross-compile for another CPU architecture [x86|arm] + --arch [arch] cross-compile for CPU: [x86|arm] --assembly, -a show assembly instructions - --dry, -d skip writing the executable to disk - --os [os] cross-compile for another OS [linux|mac|windows] - --statistics, -s show statistics + --dry skip writing the executable to disk + --os [os] cross-compile for OS: [linux|mac|windows] + --sections add sections to the executable + --statistics show statistics --verbose, -v show everything run [directory | file] build and run the executable diff --git a/src/cli/Main_test.go b/src/cli/Main_test.go index 236ef5a..b19cbfa 100644 --- a/src/cli/Main_test.go +++ b/src/cli/Main_test.go @@ -28,7 +28,7 @@ func TestCLI(t *testing.T) { {[]string{"build", "../../examples/hello", "--dry", "--os", "mac"}, 0}, {[]string{"build", "../../examples/hello", "--dry", "--os", "windows"}, 0}, {[]string{"build", "../../examples/hello/hello.q", "--dry"}, 0}, - {[]string{"build", "../../examples/hello"}, 0}, + {[]string{"build", "../../examples/hello", "--sections"}, 0}, {[]string{"run", "../../examples/hello", "--invalid"}, 2}, {[]string{"run", "../../examples/hello"}, 0}, } diff --git a/src/config/config.go b/src/config/config.go index 98bcdb4..b031375 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -7,6 +7,7 @@ var ( Dry bool // Skips writing the executable to disk. ShowAssembly bool // Shows assembly instructions at the end. ShowStatistics bool // Shows statistics at the end. + Sections bool // Adds section headers to the executable. TargetArch Arch // Target architecture. TargetOS OS // Target platform. ) @@ -15,6 +16,7 @@ var ( func Reset() { ShowAssembly = false ShowStatistics = false + Sections = false Dry = false switch runtime.GOARCH { diff --git a/src/elf/AddSections.go b/src/elf/AddSections.go new file mode 100644 index 0000000..22d8e1f --- /dev/null +++ b/src/elf/AddSections.go @@ -0,0 +1,35 @@ +package elf + +// AddSections adds section headers to the ELF file. +func (elf *ELF) AddSections() { + elf.StringTable = []byte("\000.text\000.shstrtab\000") + stringTableStart := elf.DataHeader.Offset + elf.DataHeader.SizeInFile + sectionHeaderStart := stringTableStart + int64(len(elf.StringTable)) + + elf.SectionHeaders = []SectionHeader{ + { + Type: SectionTypeNULL, + }, + { + NameIndex: 1, + Type: SectionTypePROGBITS, + Flags: SectionFlagsAllocate | SectionFlagsExecutable, + VirtualAddress: elf.CodeHeader.VirtualAddress, + Offset: elf.CodeHeader.Offset, + SizeInFile: elf.CodeHeader.SizeInFile, + Align: elf.CodeHeader.Align, + }, + { + NameIndex: 7, + Type: SectionTypeSTRTAB, + Offset: int64(stringTableStart), + SizeInFile: int64(len(elf.StringTable)), + Align: 1, + }, + } + + elf.SectionHeaderEntrySize = SectionHeaderSize + elf.SectionHeaderEntryCount = int16(len(elf.SectionHeaders)) + elf.SectionHeaderOffset = int64(sectionHeaderStart) + elf.SectionNameStringTableIndex = 2 +} diff --git a/src/elf/ELF.go b/src/elf/ELF.go index e914620..4a36abe 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -14,8 +14,10 @@ const HeaderEnd = HeaderSize + ProgramHeaderSize*2 // ELF represents an ELF file. type ELF struct { Header - CodeHeader ProgramHeader - DataHeader ProgramHeader + CodeHeader ProgramHeader + DataHeader ProgramHeader + SectionHeaders []SectionHeader + StringTable []byte } // Write writes the ELF64 format to the given writer. @@ -77,14 +79,20 @@ func Write(writer io.Writer, code []byte, data []byte) { }, } + if config.Sections { + elf.AddSections() + } + binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.CodeHeader) binary.Write(writer, binary.LittleEndian, &elf.DataHeader) writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) writer.Write(code) + writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) + writer.Write(data) - if len(data) > 0 { - writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) - writer.Write(data) + if config.Sections { + writer.Write(elf.StringTable) + binary.Write(writer, binary.LittleEndian, &elf.SectionHeaders) } } From 4faab9606c0323ee70f5e97608f0e8dbc46ec4ba Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Mar 2025 22:14:24 +0100 Subject: [PATCH 0897/1012] Added exe package to manage sections --- src/asmc/codeOffset.go | 5 +- src/asmc/compiler.go | 1 + src/asmc/dllCall.go | 10 ++-- src/asmc/resolvePointers.go | 17 +++---- src/config/const.go | 7 ++- src/elf/ELF.go | 60 ++++++++++-------------- src/elf/cpu.go | 15 ++++++ src/exe/Align.go | 12 +++++ src/exe/Executable.go | 31 +++++++++++++ src/exe/Section.go | 9 ++++ src/fs/Align.go | 7 --- src/macho/MachO.go | 45 +++++++----------- src/macho/cpu.go | 15 ++++++ src/pe/EXE.go | 91 ++++++++++++++++++------------------- src/pe/cpu.go | 15 ++++++ src/readme.md | 1 + 16 files changed, 207 insertions(+), 134 deletions(-) create mode 100644 src/elf/cpu.go create mode 100644 src/exe/Align.go create mode 100644 src/exe/Executable.go create mode 100644 src/exe/Section.go delete mode 100644 src/fs/Align.go create mode 100644 src/macho/cpu.go create mode 100644 src/pe/cpu.go diff --git a/src/asmc/codeOffset.go b/src/asmc/codeOffset.go index 9142762..95c03ae 100644 --- a/src/asmc/codeOffset.go +++ b/src/asmc/codeOffset.go @@ -3,7 +3,7 @@ package asmc import ( "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/elf" - "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/exe" "git.urbach.dev/cli/q/src/macho" "git.urbach.dev/cli/q/src/pe" ) @@ -21,6 +21,5 @@ func codeOffset() Address { headerEnd = pe.HeaderEnd } - offset, _ := fs.Align(headerEnd, config.Align) - return offset + return exe.Align(headerEnd, config.FileAlign) } diff --git a/src/asmc/compiler.go b/src/asmc/compiler.go index 041e590..056704b 100644 --- a/src/asmc/compiler.go +++ b/src/asmc/compiler.go @@ -13,4 +13,5 @@ type compiler struct { dlls dll.List codeStart Address dataStart Address + importsStart Address } diff --git a/src/asmc/dllCall.go b/src/asmc/dllCall.go index 71d1e0e..22492e8 100644 --- a/src/asmc/dllCall.go +++ b/src/asmc/dllCall.go @@ -8,15 +8,15 @@ import ( ) func (c *compiler) dllCall(x asm.Instruction) { - size := 4 c.code = x86.CallAt(c.code, 0x00_00_00_00) - position := len(c.code) - size + next := Address(len(c.code)) + position := next - 4 label := x.Data.(*asm.Label) pointer := &pointer{ Position: Address(position), OpSize: 2, - Size: uint8(size), + Size: 4, } pointer.Resolve = func() Address { @@ -29,7 +29,9 @@ func (c *compiler) dllCall(x asm.Instruction) { panic("unknown DLL function " + label.Name) } - return Address(index * 8) + destination := c.importsStart + Address(index*8) + from := c.codeStart + next + return destination - from } c.dllPointers = append(c.dllPointers, pointer) diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index 6e3ac9d..f42c805 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -6,7 +6,7 @@ import ( "slices" "git.urbach.dev/cli/q/src/config" - "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/exe" "git.urbach.dev/cli/q/src/sizeof" ) @@ -77,7 +77,9 @@ restart: } } - c.dataStart, _ = fs.Align(c.codeStart+Address(len(c.code)), config.Align) + sections := exe.MakeSections(int(codeOffset()), c.code, c.data, nil) + data := sections[1] + c.dataStart = Address(data.MemoryOffset) for _, pointer := range c.dataPointers { address := pointer.Resolve() @@ -86,14 +88,13 @@ restart: } if config.TargetOS == config.Windows { - importsStart, _ := fs.Align(c.dataStart+Address(len(c.data)), config.Align) + imports := sections[2] + c.importsStart = Address(imports.MemoryOffset) for _, pointer := range c.dllPointers { - destination := importsStart + pointer.Resolve() - from := c.codeStart + pointer.Position + Address(pointer.Size) - offset := destination - from - slice := c.code[pointer.Position : pointer.Position+4] - binary.LittleEndian.PutUint32(slice, uint32(offset)) + address := pointer.Resolve() + slice := c.code[pointer.Position : pointer.Position+Address(pointer.Size)] + binary.LittleEndian.PutUint32(slice, uint32(address)) } } } diff --git a/src/config/const.go b/src/config/const.go index 09d9946..2e683c2 100644 --- a/src/config/const.go +++ b/src/config/const.go @@ -7,6 +7,9 @@ const ( // The base address is the virtual address for our ELF file. BaseAddress = 0x40 * MinAddress - // Align is the alignment of the sections and it must be a multiple of the page size. - Align = 0x1000 + // FileAlign is the alignment of the sections in the file. + FileAlign = 0x4000 + + // MemoryAlign is the alignment of the sections in memory and it must be a multiple of the page size. + MemoryAlign = 0x4000 ) diff --git a/src/elf/ELF.go b/src/elf/ELF.go index 4a36abe..b80978e 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -6,7 +6,7 @@ import ( "io" "git.urbach.dev/cli/q/src/config" - "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/exe" ) const HeaderEnd = HeaderSize + ProgramHeaderSize*2 @@ -21,19 +21,11 @@ type ELF struct { } // Write writes the ELF64 format to the given writer. -func Write(writer io.Writer, code []byte, data []byte) { - var ( - codeStart, codePadding = fs.Align(HeaderEnd, config.Align) - dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) - arch int16 - ) - - switch config.TargetArch { - case config.ARM: - arch = ArchitectureARM64 - case config.X86: - arch = ArchitectureAMD64 - } +func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { + sections := exe.MakeSections(HeaderEnd, codeBytes, dataBytes) + code := sections[0] + data := sections[1] + arch := cpu() elf := &ELF{ Header: Header{ @@ -46,7 +38,7 @@ func Write(writer io.Writer, code []byte, data []byte) { Type: TypeExecutable, Architecture: arch, FileVersion: 1, - EntryPointInMemory: int64(config.BaseAddress + codeStart), + EntryPointInMemory: int64(config.BaseAddress + code.MemoryOffset), ProgramHeaderOffset: HeaderSize, SectionHeaderOffset: 0, Flags: 0, @@ -58,24 +50,22 @@ func Write(writer io.Writer, code []byte, data []byte) { SectionNameStringTableIndex: 0, }, CodeHeader: ProgramHeader{ - Type: ProgramTypeLOAD, - Flags: ProgramFlagsExecutable | ProgramFlagsReadable, - Offset: int64(codeStart), - VirtualAddress: int64(config.BaseAddress + codeStart), - PhysicalAddress: int64(config.BaseAddress + codeStart), - SizeInFile: int64(len(code)), - SizeInMemory: int64(len(code)), - Align: config.Align, + Type: ProgramTypeLOAD, + Flags: ProgramFlagsExecutable | ProgramFlagsReadable, + Offset: int64(code.FileOffset), + VirtualAddress: int64(config.BaseAddress + code.MemoryOffset), + SizeInFile: int64(len(code.Bytes)), + SizeInMemory: int64(len(code.Bytes)), + Align: config.MemoryAlign, }, DataHeader: ProgramHeader{ - Type: ProgramTypeLOAD, - Flags: ProgramFlagsReadable, - Offset: int64(dataStart), - VirtualAddress: int64(config.BaseAddress + dataStart), - PhysicalAddress: int64(config.BaseAddress + dataStart), - SizeInFile: int64(len(data)), - SizeInMemory: int64(len(data)), - Align: config.Align, + Type: ProgramTypeLOAD, + Flags: ProgramFlagsReadable, + Offset: int64(data.FileOffset), + VirtualAddress: int64(config.BaseAddress + data.MemoryOffset), + SizeInFile: int64(len(data.Bytes)), + SizeInMemory: int64(len(data.Bytes)), + Align: config.MemoryAlign, }, } @@ -86,10 +76,10 @@ func Write(writer io.Writer, code []byte, data []byte) { binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.CodeHeader) binary.Write(writer, binary.LittleEndian, &elf.DataHeader) - writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) - writer.Write(code) - writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) - writer.Write(data) + writer.Write(bytes.Repeat([]byte{0x00}, code.Padding)) + writer.Write(code.Bytes) + writer.Write(bytes.Repeat([]byte{0x00}, data.Padding)) + writer.Write(data.Bytes) if config.Sections { writer.Write(elf.StringTable) diff --git a/src/elf/cpu.go b/src/elf/cpu.go new file mode 100644 index 0000000..53c1e85 --- /dev/null +++ b/src/elf/cpu.go @@ -0,0 +1,15 @@ +package elf + +import "git.urbach.dev/cli/q/src/config" + +// cpu returns the CPU architecture used in the ELF header. +func cpu() int16 { + switch config.TargetArch { + case config.ARM: + return ArchitectureARM64 + case config.X86: + return ArchitectureAMD64 + default: + panic("unknown architecture") + } +} diff --git a/src/exe/Align.go b/src/exe/Align.go new file mode 100644 index 0000000..f7843bd --- /dev/null +++ b/src/exe/Align.go @@ -0,0 +1,12 @@ +package exe + +// Align calculates the next aligned address. +func Align[T int | uint | int64 | uint64 | int32 | uint32](n T, alignment T) T { + return (n + (alignment - 1)) & ^(alignment - 1) +} + +// AlignPad calculates the next aligned address and the padding needed. +func AlignPad[T int | uint | int64 | uint64 | int32 | uint32](n T, alignment T) (T, T) { + aligned := Align(n, alignment) + return aligned, aligned - n +} diff --git a/src/exe/Executable.go b/src/exe/Executable.go new file mode 100644 index 0000000..80e173d --- /dev/null +++ b/src/exe/Executable.go @@ -0,0 +1,31 @@ +package exe + +import "git.urbach.dev/cli/q/src/config" + +// Executable is a generic definition of the binary that later gets translated to OS-specific formats. +type Executable []*Section + +// MakeSections creates new sections. +func MakeSections(headerEnd int, raw ...[]byte) Executable { + sections := make(Executable, len(raw)) + + for i, data := range raw { + sections[i] = &Section{Bytes: data} + } + + sections.Update(headerEnd) + return sections +} + +// Update recalculates all section offsets. +func (sections Executable) Update(headerEnd int) { + first := sections[0] + first.FileOffset, first.Padding = AlignPad(headerEnd, config.FileAlign) + first.MemoryOffset = Align(headerEnd, config.MemoryAlign) + + for i, section := range sections[1:] { + previous := sections[i] + section.FileOffset, section.Padding = AlignPad(previous.FileOffset+len(previous.Bytes), config.FileAlign) + section.MemoryOffset = Align(previous.MemoryOffset+len(previous.Bytes), config.MemoryAlign) + } +} diff --git a/src/exe/Section.go b/src/exe/Section.go new file mode 100644 index 0000000..ba3d384 --- /dev/null +++ b/src/exe/Section.go @@ -0,0 +1,9 @@ +package exe + +// Section represents some data within the executable that will also be loaded into memory. +type Section struct { + Bytes []byte + FileOffset int + Padding int + MemoryOffset int +} diff --git a/src/fs/Align.go b/src/fs/Align.go deleted file mode 100644 index 1ae4f0b..0000000 --- a/src/fs/Align.go +++ /dev/null @@ -1,7 +0,0 @@ -package fs - -// Align calculates the next aligned address and the padding needed. -func Align[T int | uint | int64 | uint64 | int32 | uint32](n T, alignment T) (T, T) { - aligned := (n + (alignment - 1)) & ^(alignment - 1) - return aligned, aligned - n -} diff --git a/src/macho/MachO.go b/src/macho/MachO.go index 4e6ecb7..b305b6a 100644 --- a/src/macho/MachO.go +++ b/src/macho/MachO.go @@ -6,7 +6,7 @@ import ( "io" "git.urbach.dev/cli/q/src/config" - "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/exe" ) const ( @@ -24,22 +24,11 @@ type MachO struct { } // Write writes the Mach-O format to the given writer. -func Write(writer io.Writer, code []byte, data []byte) { - var ( - codeStart, codePadding = fs.Align(HeaderEnd, config.Align) - dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) - arch CPU - microArch uint32 - ) - - switch config.TargetArch { - case config.ARM: - arch = CPU_ARM_64 - microArch = CPU_SUBTYPE_ARM64_ALL | 0x80000000 - case config.X86: - arch = CPU_X86_64 - microArch = CPU_SUBTYPE_X86_64_ALL | 0x80000000 - } +func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { + sections := exe.MakeSections(HeaderEnd, codeBytes, dataBytes) + code := sections[0] + data := sections[1] + arch, microArch := cpu() m := &MachO{ Header: Header{ @@ -70,9 +59,9 @@ func Write(writer io.Writer, code []byte, data []byte) { Length: Segment64Size, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, Address: config.BaseAddress, - SizeInMemory: uint64(codeStart + len(code)), + SizeInMemory: uint64(code.MemoryOffset + len(code.Bytes)), Offset: 0, - SizeInFile: uint64(codeStart + len(code)), + SizeInFile: uint64(code.FileOffset + len(code.Bytes)), NumSections: 0, Flag: 0, MaxProt: ProtReadable | ProtExecutable, @@ -82,10 +71,10 @@ func Write(writer io.Writer, code []byte, data []byte) { LoadCommand: LcSegment64, Length: Segment64Size, Name: [16]byte{'_', '_', 'D', 'A', 'T', 'A'}, - Address: uint64(config.BaseAddress + dataStart), - SizeInMemory: uint64(len(data)), - Offset: uint64(dataStart), - SizeInFile: uint64(len(data)), + Address: uint64(config.BaseAddress + data.MemoryOffset), + SizeInMemory: uint64(len(data.Bytes)), + Offset: uint64(data.FileOffset), + SizeInFile: uint64(len(data.Bytes)), NumSections: 0, Flag: 0, MaxProt: ProtReadable, @@ -113,7 +102,7 @@ func Write(writer io.Writer, code []byte, data []byte) { 0, 0, 0, 0, 0, 0, - uint32(config.BaseAddress + codeStart), 0, + uint32(config.BaseAddress + code.MemoryOffset), 0, 0, 0, 0, 0, 0, 0, @@ -127,8 +116,8 @@ func Write(writer io.Writer, code []byte, data []byte) { binary.Write(writer, binary.LittleEndian, &m.CodeHeader) binary.Write(writer, binary.LittleEndian, &m.DataHeader) binary.Write(writer, binary.LittleEndian, &m.UnixThread) - writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) - writer.Write(code) - writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) - writer.Write(data) + writer.Write(bytes.Repeat([]byte{0x00}, code.Padding)) + writer.Write(code.Bytes) + writer.Write(bytes.Repeat([]byte{0x00}, data.Padding)) + writer.Write(data.Bytes) } diff --git a/src/macho/cpu.go b/src/macho/cpu.go new file mode 100644 index 0000000..35df5c2 --- /dev/null +++ b/src/macho/cpu.go @@ -0,0 +1,15 @@ +package macho + +import "git.urbach.dev/cli/q/src/config" + +// cpu returns the CPU architecture used in the Mach-O header. +func cpu() (CPU, uint32) { + switch config.TargetArch { + case config.ARM: + return CPU_ARM_64, CPU_SUBTYPE_ARM64_ALL | 0x80000000 + case config.X86: + return CPU_X86_64, CPU_SUBTYPE_X86_64_ALL | 0x80000000 + default: + panic("unknown architecture") + } +} diff --git a/src/pe/EXE.go b/src/pe/EXE.go index 579d6a0..8c9473c 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -7,7 +7,7 @@ import ( "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/dll" - "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/exe" ) const ( @@ -24,31 +24,30 @@ type EXE struct { } // Write writes the EXE file to the given writer. -func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { +func Write(writer io.Writer, codeBytes []byte, dataBytes []byte, dlls dll.List) { var ( - codeStart, codePadding = fs.Align(HeaderEnd, config.Align) - dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) - importsStart, importsPadding = fs.Align(dataStart+len(data), config.Align) - subSystem = IMAGE_SUBSYSTEM_WINDOWS_CUI - imports, dllData, dllImports, dllDataStart = importLibraries(dlls, importsStart) - importDirectoryStart = dllDataStart + len(dllData) - importDirectorySize = DLLImportSize * len(dllImports) - importSectionSize = len(imports)*8 + len(dllData) + importDirectorySize - imageSize, _ = fs.Align(importsStart+importSectionSize, config.Align) - arch uint16 + sections = exe.MakeSections(HeaderEnd, codeBytes, dataBytes, nil) + code = sections[0] + data = sections[1] + imports = sections[2] + subSystem = IMAGE_SUBSYSTEM_WINDOWS_CUI + arch = cpu() ) + importsData, dllData, dllImports, dllDataStart := importLibraries(dlls, imports.FileOffset) + buffer := bytes.Buffer{} + binary.Write(&buffer, binary.LittleEndian, &importsData) + binary.Write(&buffer, binary.LittleEndian, &dllData) + binary.Write(&buffer, binary.LittleEndian, &dllImports) + imports.Bytes = buffer.Bytes() + importDirectoryStart := dllDataStart + len(dllData) + importDirectorySize := DLLImportSize * len(dllImports) + imageSize := exe.Align(imports.MemoryOffset+len(imports.Bytes), config.MemoryAlign) + if dlls.Contains("user32") { subSystem = IMAGE_SUBSYSTEM_WINDOWS_GUI } - switch config.TargetArch { - case config.ARM: - arch = IMAGE_FILE_MACHINE_ARM64 - case config.X86: - arch = IMAGE_FILE_MACHINE_AMD64 - } - pe := &EXE{ DOSHeader: DOSHeader{ Magic: [4]byte{'M', 'Z', 0, 0}, @@ -68,14 +67,14 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { Magic: 0x020B, // PE32+ / 64-bit executable MajorLinkerVersion: 0x0E, MinorLinkerVersion: 0x16, - SizeOfCode: uint32(len(code)), + SizeOfCode: uint32(len(code.Bytes)), SizeOfInitializedData: 0, SizeOfUninitializedData: 0, - AddressOfEntryPoint: uint32(codeStart), - BaseOfCode: uint32(codeStart), + AddressOfEntryPoint: uint32(code.MemoryOffset), + BaseOfCode: uint32(code.MemoryOffset), ImageBase: config.BaseAddress, - SectionAlignment: config.Align, // power of 2, must be greater than or equal to FileAlignment - FileAlignment: config.Align, // power of 2 + SectionAlignment: config.MemoryAlign, // power of 2, must be greater than or equal to FileAlignment + FileAlignment: config.FileAlign, // power of 2 MajorOperatingSystemVersion: 0x06, MinorOperatingSystemVersion: 0, MajorImageVersion: 0, @@ -83,8 +82,8 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { MajorSubsystemVersion: 0x06, MinorSubsystemVersion: 0, Win32VersionValue: 0, - SizeOfImage: uint32(imageSize), - SizeOfHeaders: uint32(codeStart), // section bodies begin here + SizeOfImage: uint32(imageSize), // a multiple of SectionAlignment + SizeOfHeaders: uint32(code.FileOffset), // section bodies begin here CheckSum: 0, Subsystem: uint16(subSystem), DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, // IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE @@ -107,7 +106,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, - {VirtualAddress: uint32(importsStart), Size: uint32(len(imports) * 8)}, // RVA of the import address table + {VirtualAddress: uint32(imports.MemoryOffset), Size: uint32(len(importsData) * 8)}, // RVA of the import address table {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, @@ -116,26 +115,26 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { Sections: []SectionHeader{ { Name: [8]byte{'.', 't', 'e', 'x', 't'}, - VirtualSize: uint32(len(code)), - VirtualAddress: uint32(codeStart), - RawSize: uint32(len(code)), // must be a multiple of FileAlignment - RawAddress: uint32(codeStart), // must be a multiple of FileAlignment + VirtualSize: uint32(len(code.Bytes)), + VirtualAddress: uint32(code.MemoryOffset), + RawSize: uint32(len(code.Bytes)), // must be a multiple of FileAlignment + RawAddress: uint32(code.FileOffset), // must be a multiple of FileAlignment Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ, }, { Name: [8]byte{'.', 'r', 'd', 'a', 't', 'a'}, - VirtualSize: uint32(len(data)), - VirtualAddress: uint32(dataStart), - RawSize: uint32(len(data)), - RawAddress: uint32(dataStart), + VirtualSize: uint32(len(data.Bytes)), + VirtualAddress: uint32(data.MemoryOffset), + RawSize: uint32(len(data.Bytes)), + RawAddress: uint32(data.FileOffset), Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, }, { Name: [8]byte{'.', 'i', 'd', 'a', 't', 'a'}, - VirtualSize: uint32(importSectionSize), - VirtualAddress: uint32(importsStart), - RawSize: uint32(importSectionSize), - RawAddress: uint32(importsStart), + VirtualSize: uint32(len(imports.Bytes)), + VirtualAddress: uint32(imports.MemoryOffset), + RawSize: uint32(len(imports.Bytes)), + RawAddress: uint32(imports.FileOffset), Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, }, }, @@ -145,12 +144,10 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { binary.Write(writer, binary.LittleEndian, &pe.NTHeader) binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) binary.Write(writer, binary.LittleEndian, &pe.Sections) - writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) - writer.Write(code) - writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) - writer.Write(data) - writer.Write(bytes.Repeat([]byte{0x00}, importsPadding)) - binary.Write(writer, binary.LittleEndian, &imports) - binary.Write(writer, binary.LittleEndian, &dllData) - binary.Write(writer, binary.LittleEndian, &dllImports) + writer.Write(bytes.Repeat([]byte{0x00}, code.Padding)) + writer.Write(code.Bytes) + writer.Write(bytes.Repeat([]byte{0x00}, data.Padding)) + writer.Write(data.Bytes) + writer.Write(bytes.Repeat([]byte{0x00}, imports.Padding)) + writer.Write(imports.Bytes) } diff --git a/src/pe/cpu.go b/src/pe/cpu.go new file mode 100644 index 0000000..9f52560 --- /dev/null +++ b/src/pe/cpu.go @@ -0,0 +1,15 @@ +package pe + +import "git.urbach.dev/cli/q/src/config" + +// cpu returns the CPU architecture used in the PE header. +func cpu() uint16 { + switch config.TargetArch { + case config.ARM: + return IMAGE_FILE_MACHINE_ARM64 + case config.X86: + return IMAGE_FILE_MACHINE_AMD64 + default: + panic("unknown architecture") + } +} diff --git a/src/readme.md b/src/readme.md index 99ce70e..63ab66d 100644 --- a/src/readme.md +++ b/src/readme.md @@ -13,6 +13,7 @@ - [data](data) - Data container that can re-use existing data (e.g. the `Hello` in `Hello World`) - [dll](dll) - DLL support for Windows systems (w.i.p.) - [elf](elf) - ELF format for Linux executables +- [exe](exe) - Generic executable format to calculate section offsets - [errors](errors) - Error types - [eval](eval) - Evaluates expressions - [expression](expression) - Expression parser generating trees with the `Parse` function From b67f3b09775829d608891cc82f8fa6258230682d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 8 Mar 2025 22:14:24 +0100 Subject: [PATCH 0898/1012] Added exe package to manage sections --- src/asmc/codeOffset.go | 5 +- src/asmc/compiler.go | 1 + src/asmc/dllCall.go | 10 ++-- src/asmc/resolvePointers.go | 17 +++---- src/config/const.go | 7 ++- src/elf/ELF.go | 60 ++++++++++-------------- src/elf/cpu.go | 15 ++++++ src/exe/Align.go | 12 +++++ src/exe/Executable.go | 31 +++++++++++++ src/exe/Section.go | 9 ++++ src/fs/Align.go | 7 --- src/macho/MachO.go | 45 +++++++----------- src/macho/cpu.go | 15 ++++++ src/pe/EXE.go | 91 ++++++++++++++++++------------------- src/pe/cpu.go | 15 ++++++ src/readme.md | 1 + 16 files changed, 207 insertions(+), 134 deletions(-) create mode 100644 src/elf/cpu.go create mode 100644 src/exe/Align.go create mode 100644 src/exe/Executable.go create mode 100644 src/exe/Section.go delete mode 100644 src/fs/Align.go create mode 100644 src/macho/cpu.go create mode 100644 src/pe/cpu.go diff --git a/src/asmc/codeOffset.go b/src/asmc/codeOffset.go index 9142762..95c03ae 100644 --- a/src/asmc/codeOffset.go +++ b/src/asmc/codeOffset.go @@ -3,7 +3,7 @@ package asmc import ( "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/elf" - "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/exe" "git.urbach.dev/cli/q/src/macho" "git.urbach.dev/cli/q/src/pe" ) @@ -21,6 +21,5 @@ func codeOffset() Address { headerEnd = pe.HeaderEnd } - offset, _ := fs.Align(headerEnd, config.Align) - return offset + return exe.Align(headerEnd, config.FileAlign) } diff --git a/src/asmc/compiler.go b/src/asmc/compiler.go index 041e590..056704b 100644 --- a/src/asmc/compiler.go +++ b/src/asmc/compiler.go @@ -13,4 +13,5 @@ type compiler struct { dlls dll.List codeStart Address dataStart Address + importsStart Address } diff --git a/src/asmc/dllCall.go b/src/asmc/dllCall.go index 71d1e0e..22492e8 100644 --- a/src/asmc/dllCall.go +++ b/src/asmc/dllCall.go @@ -8,15 +8,15 @@ import ( ) func (c *compiler) dllCall(x asm.Instruction) { - size := 4 c.code = x86.CallAt(c.code, 0x00_00_00_00) - position := len(c.code) - size + next := Address(len(c.code)) + position := next - 4 label := x.Data.(*asm.Label) pointer := &pointer{ Position: Address(position), OpSize: 2, - Size: uint8(size), + Size: 4, } pointer.Resolve = func() Address { @@ -29,7 +29,9 @@ func (c *compiler) dllCall(x asm.Instruction) { panic("unknown DLL function " + label.Name) } - return Address(index * 8) + destination := c.importsStart + Address(index*8) + from := c.codeStart + next + return destination - from } c.dllPointers = append(c.dllPointers, pointer) diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index 6e3ac9d..f42c805 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -6,7 +6,7 @@ import ( "slices" "git.urbach.dev/cli/q/src/config" - "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/exe" "git.urbach.dev/cli/q/src/sizeof" ) @@ -77,7 +77,9 @@ restart: } } - c.dataStart, _ = fs.Align(c.codeStart+Address(len(c.code)), config.Align) + sections := exe.MakeSections(int(codeOffset()), c.code, c.data, nil) + data := sections[1] + c.dataStart = Address(data.MemoryOffset) for _, pointer := range c.dataPointers { address := pointer.Resolve() @@ -86,14 +88,13 @@ restart: } if config.TargetOS == config.Windows { - importsStart, _ := fs.Align(c.dataStart+Address(len(c.data)), config.Align) + imports := sections[2] + c.importsStart = Address(imports.MemoryOffset) for _, pointer := range c.dllPointers { - destination := importsStart + pointer.Resolve() - from := c.codeStart + pointer.Position + Address(pointer.Size) - offset := destination - from - slice := c.code[pointer.Position : pointer.Position+4] - binary.LittleEndian.PutUint32(slice, uint32(offset)) + address := pointer.Resolve() + slice := c.code[pointer.Position : pointer.Position+Address(pointer.Size)] + binary.LittleEndian.PutUint32(slice, uint32(address)) } } } diff --git a/src/config/const.go b/src/config/const.go index 09d9946..2e683c2 100644 --- a/src/config/const.go +++ b/src/config/const.go @@ -7,6 +7,9 @@ const ( // The base address is the virtual address for our ELF file. BaseAddress = 0x40 * MinAddress - // Align is the alignment of the sections and it must be a multiple of the page size. - Align = 0x1000 + // FileAlign is the alignment of the sections in the file. + FileAlign = 0x4000 + + // MemoryAlign is the alignment of the sections in memory and it must be a multiple of the page size. + MemoryAlign = 0x4000 ) diff --git a/src/elf/ELF.go b/src/elf/ELF.go index 4a36abe..b80978e 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -6,7 +6,7 @@ import ( "io" "git.urbach.dev/cli/q/src/config" - "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/exe" ) const HeaderEnd = HeaderSize + ProgramHeaderSize*2 @@ -21,19 +21,11 @@ type ELF struct { } // Write writes the ELF64 format to the given writer. -func Write(writer io.Writer, code []byte, data []byte) { - var ( - codeStart, codePadding = fs.Align(HeaderEnd, config.Align) - dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) - arch int16 - ) - - switch config.TargetArch { - case config.ARM: - arch = ArchitectureARM64 - case config.X86: - arch = ArchitectureAMD64 - } +func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { + sections := exe.MakeSections(HeaderEnd, codeBytes, dataBytes) + code := sections[0] + data := sections[1] + arch := cpu() elf := &ELF{ Header: Header{ @@ -46,7 +38,7 @@ func Write(writer io.Writer, code []byte, data []byte) { Type: TypeExecutable, Architecture: arch, FileVersion: 1, - EntryPointInMemory: int64(config.BaseAddress + codeStart), + EntryPointInMemory: int64(config.BaseAddress + code.MemoryOffset), ProgramHeaderOffset: HeaderSize, SectionHeaderOffset: 0, Flags: 0, @@ -58,24 +50,22 @@ func Write(writer io.Writer, code []byte, data []byte) { SectionNameStringTableIndex: 0, }, CodeHeader: ProgramHeader{ - Type: ProgramTypeLOAD, - Flags: ProgramFlagsExecutable | ProgramFlagsReadable, - Offset: int64(codeStart), - VirtualAddress: int64(config.BaseAddress + codeStart), - PhysicalAddress: int64(config.BaseAddress + codeStart), - SizeInFile: int64(len(code)), - SizeInMemory: int64(len(code)), - Align: config.Align, + Type: ProgramTypeLOAD, + Flags: ProgramFlagsExecutable | ProgramFlagsReadable, + Offset: int64(code.FileOffset), + VirtualAddress: int64(config.BaseAddress + code.MemoryOffset), + SizeInFile: int64(len(code.Bytes)), + SizeInMemory: int64(len(code.Bytes)), + Align: config.MemoryAlign, }, DataHeader: ProgramHeader{ - Type: ProgramTypeLOAD, - Flags: ProgramFlagsReadable, - Offset: int64(dataStart), - VirtualAddress: int64(config.BaseAddress + dataStart), - PhysicalAddress: int64(config.BaseAddress + dataStart), - SizeInFile: int64(len(data)), - SizeInMemory: int64(len(data)), - Align: config.Align, + Type: ProgramTypeLOAD, + Flags: ProgramFlagsReadable, + Offset: int64(data.FileOffset), + VirtualAddress: int64(config.BaseAddress + data.MemoryOffset), + SizeInFile: int64(len(data.Bytes)), + SizeInMemory: int64(len(data.Bytes)), + Align: config.MemoryAlign, }, } @@ -86,10 +76,10 @@ func Write(writer io.Writer, code []byte, data []byte) { binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.CodeHeader) binary.Write(writer, binary.LittleEndian, &elf.DataHeader) - writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) - writer.Write(code) - writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) - writer.Write(data) + writer.Write(bytes.Repeat([]byte{0x00}, code.Padding)) + writer.Write(code.Bytes) + writer.Write(bytes.Repeat([]byte{0x00}, data.Padding)) + writer.Write(data.Bytes) if config.Sections { writer.Write(elf.StringTable) diff --git a/src/elf/cpu.go b/src/elf/cpu.go new file mode 100644 index 0000000..53c1e85 --- /dev/null +++ b/src/elf/cpu.go @@ -0,0 +1,15 @@ +package elf + +import "git.urbach.dev/cli/q/src/config" + +// cpu returns the CPU architecture used in the ELF header. +func cpu() int16 { + switch config.TargetArch { + case config.ARM: + return ArchitectureARM64 + case config.X86: + return ArchitectureAMD64 + default: + panic("unknown architecture") + } +} diff --git a/src/exe/Align.go b/src/exe/Align.go new file mode 100644 index 0000000..f7843bd --- /dev/null +++ b/src/exe/Align.go @@ -0,0 +1,12 @@ +package exe + +// Align calculates the next aligned address. +func Align[T int | uint | int64 | uint64 | int32 | uint32](n T, alignment T) T { + return (n + (alignment - 1)) & ^(alignment - 1) +} + +// AlignPad calculates the next aligned address and the padding needed. +func AlignPad[T int | uint | int64 | uint64 | int32 | uint32](n T, alignment T) (T, T) { + aligned := Align(n, alignment) + return aligned, aligned - n +} diff --git a/src/exe/Executable.go b/src/exe/Executable.go new file mode 100644 index 0000000..80e173d --- /dev/null +++ b/src/exe/Executable.go @@ -0,0 +1,31 @@ +package exe + +import "git.urbach.dev/cli/q/src/config" + +// Executable is a generic definition of the binary that later gets translated to OS-specific formats. +type Executable []*Section + +// MakeSections creates new sections. +func MakeSections(headerEnd int, raw ...[]byte) Executable { + sections := make(Executable, len(raw)) + + for i, data := range raw { + sections[i] = &Section{Bytes: data} + } + + sections.Update(headerEnd) + return sections +} + +// Update recalculates all section offsets. +func (sections Executable) Update(headerEnd int) { + first := sections[0] + first.FileOffset, first.Padding = AlignPad(headerEnd, config.FileAlign) + first.MemoryOffset = Align(headerEnd, config.MemoryAlign) + + for i, section := range sections[1:] { + previous := sections[i] + section.FileOffset, section.Padding = AlignPad(previous.FileOffset+len(previous.Bytes), config.FileAlign) + section.MemoryOffset = Align(previous.MemoryOffset+len(previous.Bytes), config.MemoryAlign) + } +} diff --git a/src/exe/Section.go b/src/exe/Section.go new file mode 100644 index 0000000..ba3d384 --- /dev/null +++ b/src/exe/Section.go @@ -0,0 +1,9 @@ +package exe + +// Section represents some data within the executable that will also be loaded into memory. +type Section struct { + Bytes []byte + FileOffset int + Padding int + MemoryOffset int +} diff --git a/src/fs/Align.go b/src/fs/Align.go deleted file mode 100644 index 1ae4f0b..0000000 --- a/src/fs/Align.go +++ /dev/null @@ -1,7 +0,0 @@ -package fs - -// Align calculates the next aligned address and the padding needed. -func Align[T int | uint | int64 | uint64 | int32 | uint32](n T, alignment T) (T, T) { - aligned := (n + (alignment - 1)) & ^(alignment - 1) - return aligned, aligned - n -} diff --git a/src/macho/MachO.go b/src/macho/MachO.go index 4e6ecb7..b305b6a 100644 --- a/src/macho/MachO.go +++ b/src/macho/MachO.go @@ -6,7 +6,7 @@ import ( "io" "git.urbach.dev/cli/q/src/config" - "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/exe" ) const ( @@ -24,22 +24,11 @@ type MachO struct { } // Write writes the Mach-O format to the given writer. -func Write(writer io.Writer, code []byte, data []byte) { - var ( - codeStart, codePadding = fs.Align(HeaderEnd, config.Align) - dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) - arch CPU - microArch uint32 - ) - - switch config.TargetArch { - case config.ARM: - arch = CPU_ARM_64 - microArch = CPU_SUBTYPE_ARM64_ALL | 0x80000000 - case config.X86: - arch = CPU_X86_64 - microArch = CPU_SUBTYPE_X86_64_ALL | 0x80000000 - } +func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { + sections := exe.MakeSections(HeaderEnd, codeBytes, dataBytes) + code := sections[0] + data := sections[1] + arch, microArch := cpu() m := &MachO{ Header: Header{ @@ -70,9 +59,9 @@ func Write(writer io.Writer, code []byte, data []byte) { Length: Segment64Size, Name: [16]byte{'_', '_', 'T', 'E', 'X', 'T'}, Address: config.BaseAddress, - SizeInMemory: uint64(codeStart + len(code)), + SizeInMemory: uint64(code.MemoryOffset + len(code.Bytes)), Offset: 0, - SizeInFile: uint64(codeStart + len(code)), + SizeInFile: uint64(code.FileOffset + len(code.Bytes)), NumSections: 0, Flag: 0, MaxProt: ProtReadable | ProtExecutable, @@ -82,10 +71,10 @@ func Write(writer io.Writer, code []byte, data []byte) { LoadCommand: LcSegment64, Length: Segment64Size, Name: [16]byte{'_', '_', 'D', 'A', 'T', 'A'}, - Address: uint64(config.BaseAddress + dataStart), - SizeInMemory: uint64(len(data)), - Offset: uint64(dataStart), - SizeInFile: uint64(len(data)), + Address: uint64(config.BaseAddress + data.MemoryOffset), + SizeInMemory: uint64(len(data.Bytes)), + Offset: uint64(data.FileOffset), + SizeInFile: uint64(len(data.Bytes)), NumSections: 0, Flag: 0, MaxProt: ProtReadable, @@ -113,7 +102,7 @@ func Write(writer io.Writer, code []byte, data []byte) { 0, 0, 0, 0, 0, 0, - uint32(config.BaseAddress + codeStart), 0, + uint32(config.BaseAddress + code.MemoryOffset), 0, 0, 0, 0, 0, 0, 0, @@ -127,8 +116,8 @@ func Write(writer io.Writer, code []byte, data []byte) { binary.Write(writer, binary.LittleEndian, &m.CodeHeader) binary.Write(writer, binary.LittleEndian, &m.DataHeader) binary.Write(writer, binary.LittleEndian, &m.UnixThread) - writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) - writer.Write(code) - writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) - writer.Write(data) + writer.Write(bytes.Repeat([]byte{0x00}, code.Padding)) + writer.Write(code.Bytes) + writer.Write(bytes.Repeat([]byte{0x00}, data.Padding)) + writer.Write(data.Bytes) } diff --git a/src/macho/cpu.go b/src/macho/cpu.go new file mode 100644 index 0000000..35df5c2 --- /dev/null +++ b/src/macho/cpu.go @@ -0,0 +1,15 @@ +package macho + +import "git.urbach.dev/cli/q/src/config" + +// cpu returns the CPU architecture used in the Mach-O header. +func cpu() (CPU, uint32) { + switch config.TargetArch { + case config.ARM: + return CPU_ARM_64, CPU_SUBTYPE_ARM64_ALL | 0x80000000 + case config.X86: + return CPU_X86_64, CPU_SUBTYPE_X86_64_ALL | 0x80000000 + default: + panic("unknown architecture") + } +} diff --git a/src/pe/EXE.go b/src/pe/EXE.go index 579d6a0..8c9473c 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -7,7 +7,7 @@ import ( "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/dll" - "git.urbach.dev/cli/q/src/fs" + "git.urbach.dev/cli/q/src/exe" ) const ( @@ -24,31 +24,30 @@ type EXE struct { } // Write writes the EXE file to the given writer. -func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { +func Write(writer io.Writer, codeBytes []byte, dataBytes []byte, dlls dll.List) { var ( - codeStart, codePadding = fs.Align(HeaderEnd, config.Align) - dataStart, dataPadding = fs.Align(codeStart+len(code), config.Align) - importsStart, importsPadding = fs.Align(dataStart+len(data), config.Align) - subSystem = IMAGE_SUBSYSTEM_WINDOWS_CUI - imports, dllData, dllImports, dllDataStart = importLibraries(dlls, importsStart) - importDirectoryStart = dllDataStart + len(dllData) - importDirectorySize = DLLImportSize * len(dllImports) - importSectionSize = len(imports)*8 + len(dllData) + importDirectorySize - imageSize, _ = fs.Align(importsStart+importSectionSize, config.Align) - arch uint16 + sections = exe.MakeSections(HeaderEnd, codeBytes, dataBytes, nil) + code = sections[0] + data = sections[1] + imports = sections[2] + subSystem = IMAGE_SUBSYSTEM_WINDOWS_CUI + arch = cpu() ) + importsData, dllData, dllImports, dllDataStart := importLibraries(dlls, imports.FileOffset) + buffer := bytes.Buffer{} + binary.Write(&buffer, binary.LittleEndian, &importsData) + binary.Write(&buffer, binary.LittleEndian, &dllData) + binary.Write(&buffer, binary.LittleEndian, &dllImports) + imports.Bytes = buffer.Bytes() + importDirectoryStart := dllDataStart + len(dllData) + importDirectorySize := DLLImportSize * len(dllImports) + imageSize := exe.Align(imports.MemoryOffset+len(imports.Bytes), config.MemoryAlign) + if dlls.Contains("user32") { subSystem = IMAGE_SUBSYSTEM_WINDOWS_GUI } - switch config.TargetArch { - case config.ARM: - arch = IMAGE_FILE_MACHINE_ARM64 - case config.X86: - arch = IMAGE_FILE_MACHINE_AMD64 - } - pe := &EXE{ DOSHeader: DOSHeader{ Magic: [4]byte{'M', 'Z', 0, 0}, @@ -68,14 +67,14 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { Magic: 0x020B, // PE32+ / 64-bit executable MajorLinkerVersion: 0x0E, MinorLinkerVersion: 0x16, - SizeOfCode: uint32(len(code)), + SizeOfCode: uint32(len(code.Bytes)), SizeOfInitializedData: 0, SizeOfUninitializedData: 0, - AddressOfEntryPoint: uint32(codeStart), - BaseOfCode: uint32(codeStart), + AddressOfEntryPoint: uint32(code.MemoryOffset), + BaseOfCode: uint32(code.MemoryOffset), ImageBase: config.BaseAddress, - SectionAlignment: config.Align, // power of 2, must be greater than or equal to FileAlignment - FileAlignment: config.Align, // power of 2 + SectionAlignment: config.MemoryAlign, // power of 2, must be greater than or equal to FileAlignment + FileAlignment: config.FileAlign, // power of 2 MajorOperatingSystemVersion: 0x06, MinorOperatingSystemVersion: 0, MajorImageVersion: 0, @@ -83,8 +82,8 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { MajorSubsystemVersion: 0x06, MinorSubsystemVersion: 0, Win32VersionValue: 0, - SizeOfImage: uint32(imageSize), - SizeOfHeaders: uint32(codeStart), // section bodies begin here + SizeOfImage: uint32(imageSize), // a multiple of SectionAlignment + SizeOfHeaders: uint32(code.FileOffset), // section bodies begin here CheckSum: 0, Subsystem: uint16(subSystem), DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, // IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE @@ -107,7 +106,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, - {VirtualAddress: uint32(importsStart), Size: uint32(len(imports) * 8)}, // RVA of the import address table + {VirtualAddress: uint32(imports.MemoryOffset), Size: uint32(len(importsData) * 8)}, // RVA of the import address table {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, @@ -116,26 +115,26 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { Sections: []SectionHeader{ { Name: [8]byte{'.', 't', 'e', 'x', 't'}, - VirtualSize: uint32(len(code)), - VirtualAddress: uint32(codeStart), - RawSize: uint32(len(code)), // must be a multiple of FileAlignment - RawAddress: uint32(codeStart), // must be a multiple of FileAlignment + VirtualSize: uint32(len(code.Bytes)), + VirtualAddress: uint32(code.MemoryOffset), + RawSize: uint32(len(code.Bytes)), // must be a multiple of FileAlignment + RawAddress: uint32(code.FileOffset), // must be a multiple of FileAlignment Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ, }, { Name: [8]byte{'.', 'r', 'd', 'a', 't', 'a'}, - VirtualSize: uint32(len(data)), - VirtualAddress: uint32(dataStart), - RawSize: uint32(len(data)), - RawAddress: uint32(dataStart), + VirtualSize: uint32(len(data.Bytes)), + VirtualAddress: uint32(data.MemoryOffset), + RawSize: uint32(len(data.Bytes)), + RawAddress: uint32(data.FileOffset), Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, }, { Name: [8]byte{'.', 'i', 'd', 'a', 't', 'a'}, - VirtualSize: uint32(importSectionSize), - VirtualAddress: uint32(importsStart), - RawSize: uint32(importSectionSize), - RawAddress: uint32(importsStart), + VirtualSize: uint32(len(imports.Bytes)), + VirtualAddress: uint32(imports.MemoryOffset), + RawSize: uint32(len(imports.Bytes)), + RawAddress: uint32(imports.FileOffset), Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, }, }, @@ -145,12 +144,10 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { binary.Write(writer, binary.LittleEndian, &pe.NTHeader) binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) binary.Write(writer, binary.LittleEndian, &pe.Sections) - writer.Write(bytes.Repeat([]byte{0x00}, codePadding)) - writer.Write(code) - writer.Write(bytes.Repeat([]byte{0x00}, dataPadding)) - writer.Write(data) - writer.Write(bytes.Repeat([]byte{0x00}, importsPadding)) - binary.Write(writer, binary.LittleEndian, &imports) - binary.Write(writer, binary.LittleEndian, &dllData) - binary.Write(writer, binary.LittleEndian, &dllImports) + writer.Write(bytes.Repeat([]byte{0x00}, code.Padding)) + writer.Write(code.Bytes) + writer.Write(bytes.Repeat([]byte{0x00}, data.Padding)) + writer.Write(data.Bytes) + writer.Write(bytes.Repeat([]byte{0x00}, imports.Padding)) + writer.Write(imports.Bytes) } diff --git a/src/pe/cpu.go b/src/pe/cpu.go new file mode 100644 index 0000000..9f52560 --- /dev/null +++ b/src/pe/cpu.go @@ -0,0 +1,15 @@ +package pe + +import "git.urbach.dev/cli/q/src/config" + +// cpu returns the CPU architecture used in the PE header. +func cpu() uint16 { + switch config.TargetArch { + case config.ARM: + return IMAGE_FILE_MACHINE_ARM64 + case config.X86: + return IMAGE_FILE_MACHINE_AMD64 + default: + panic("unknown architecture") + } +} diff --git a/src/readme.md b/src/readme.md index 99ce70e..63ab66d 100644 --- a/src/readme.md +++ b/src/readme.md @@ -13,6 +13,7 @@ - [data](data) - Data container that can re-use existing data (e.g. the `Hello` in `Hello World`) - [dll](dll) - DLL support for Windows systems (w.i.p.) - [elf](elf) - ELF format for Linux executables +- [exe](exe) - Generic executable format to calculate section offsets - [errors](errors) - Error types - [eval](eval) - Evaluates expressions - [expression](expression) - Expression parser generating trees with the `Parse` function From c010a81ac6850f92bad0b2d17f09edfb9c1665cd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Mar 2025 00:23:23 +0100 Subject: [PATCH 0899/1012] Implemented dynamic section alignment --- src/asmc/Finalize.go | 2 +- src/asmc/codeOffset.go | 4 ++-- src/asmc/move.go | 7 +++---- src/asmc/resolvePointers.go | 2 +- src/cli/Build.go | 8 ++++++-- src/config/config.go | 21 ++++++++++++++++++--- src/config/const.go | 6 ------ src/elf/ELF.go | 4 ++-- src/pe/EXE.go | 4 ++-- 9 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index 708f207..3f35552 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -18,7 +18,7 @@ func Finalize(a asm.Assembler, dlls dll.List) ([]byte, []byte) { code: make([]byte, 0, len(a.Instructions)*8), codeLabels: make(map[string]Address, 32), codePointers: make([]*pointer, 0, len(a.Instructions)*8), - codeStart: codeOffset(), + codeStart: uint32(codeOffset()), data: data, dataLabels: dataLabels, dlls: dlls, diff --git a/src/asmc/codeOffset.go b/src/asmc/codeOffset.go index 95c03ae..3cf6e64 100644 --- a/src/asmc/codeOffset.go +++ b/src/asmc/codeOffset.go @@ -9,8 +9,8 @@ import ( ) // codeOffset returns the file offset of the code section. -func codeOffset() Address { - headerEnd := Address(0) +func codeOffset() int { + headerEnd := 0 switch config.TargetOS { case config.Linux: diff --git a/src/asmc/move.go b/src/asmc/move.go index c4f60d7..c3687fb 100644 --- a/src/asmc/move.go +++ b/src/asmc/move.go @@ -19,15 +19,14 @@ func (c *compiler) move(x asm.Instruction) { start := Address(len(c.code)) c.code = x86.LoadAddress(c.code, operands.Register, 0x00_00_00_00) end := Address(len(c.code)) - size := uint32(4) - position := end - size + position := end - 4 opSize := position - start if strings.HasPrefix(operands.Label, "data ") { c.dataPointers = append(c.dataPointers, &pointer{ Position: position, OpSize: uint8(opSize), - Size: uint8(size), + Size: uint8(4), Resolve: func() Address { destination, exists := c.dataLabels[operands.Label] @@ -44,7 +43,7 @@ func (c *compiler) move(x asm.Instruction) { c.codePointers = append(c.codePointers, &pointer{ Position: position, OpSize: uint8(opSize), - Size: uint8(size), + Size: uint8(4), Resolve: func() Address { destination, exists := c.codeLabels[operands.Label] diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index f42c805..7603a86 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -77,7 +77,7 @@ restart: } } - sections := exe.MakeSections(int(codeOffset()), c.code, c.data, nil) + sections := exe.MakeSections(codeOffset(), c.code, c.data, nil) data := sections[1] c.dataStart = Address(data.MemoryOffset) diff --git a/src/cli/Build.go b/src/cli/Build.go index 75f4377..087d6c1 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -53,9 +53,9 @@ func buildExecutable(args []string) (*build.Build, error) { switch args[i] { case "arm": - config.TargetArch = config.ARM + config.SetTargetArch(config.ARM) case "x86": - config.TargetArch = config.X86 + config.SetTargetArch(config.X86) default: return b, &InvalidValueError{Value: args[i], Parameter: "arch"} } @@ -91,6 +91,10 @@ func buildExecutable(args []string) (*build.Build, error) { return b, &InvalidValueError{Value: runtime.GOOS, Parameter: "os"} } + if config.TargetArch == config.UnknownArch { + return b, &InvalidValueError{Value: runtime.GOARCH, Parameter: "arch"} + } + if len(b.Files) == 0 { b.Files = append(b.Files, ".") } diff --git a/src/config/config.go b/src/config/config.go index b031375..74a3a5f 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -5,6 +5,8 @@ import "runtime" var ( ConstantFold bool // Calculates the result of operations on constants at compile time. Dry bool // Skips writing the executable to disk. + FileAlign int // FileAlign is the alignment of the sections in the file. + MemoryAlign int // MemoryAlign is the alignment of the sections in memory. ShowAssembly bool // Shows assembly instructions at the end. ShowStatistics bool // Shows statistics at the end. Sections bool // Adds section headers to the executable. @@ -21,11 +23,11 @@ func Reset() { switch runtime.GOARCH { case "amd64": - TargetArch = X86 + SetTargetArch(X86) case "arm64": - TargetArch = ARM + SetTargetArch(ARM) default: - TargetArch = UnknownArch + SetTargetArch(UnknownArch) } switch runtime.GOOS { @@ -46,3 +48,16 @@ func Reset() { func Optimize(enable bool) { ConstantFold = enable } + +// SetTargetArch changes the target architecture and updates the alignment. +func SetTargetArch(arch Arch) { + TargetArch = arch + + if TargetArch == ARM { + FileAlign = 0x4000 + MemoryAlign = 0x4000 + } else { + FileAlign = 0x1000 + MemoryAlign = 0x1000 + } +} diff --git a/src/config/const.go b/src/config/const.go index 2e683c2..2783f7f 100644 --- a/src/config/const.go +++ b/src/config/const.go @@ -6,10 +6,4 @@ const ( // The base address is the virtual address for our ELF file. BaseAddress = 0x40 * MinAddress - - // FileAlign is the alignment of the sections in the file. - FileAlign = 0x4000 - - // MemoryAlign is the alignment of the sections in memory and it must be a multiple of the page size. - MemoryAlign = 0x4000 ) diff --git a/src/elf/ELF.go b/src/elf/ELF.go index b80978e..29c828a 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -56,7 +56,7 @@ func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { VirtualAddress: int64(config.BaseAddress + code.MemoryOffset), SizeInFile: int64(len(code.Bytes)), SizeInMemory: int64(len(code.Bytes)), - Align: config.MemoryAlign, + Align: int64(config.MemoryAlign), }, DataHeader: ProgramHeader{ Type: ProgramTypeLOAD, @@ -65,7 +65,7 @@ func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { VirtualAddress: int64(config.BaseAddress + data.MemoryOffset), SizeInFile: int64(len(data.Bytes)), SizeInMemory: int64(len(data.Bytes)), - Align: config.MemoryAlign, + Align: int64(config.MemoryAlign), }, } diff --git a/src/pe/EXE.go b/src/pe/EXE.go index 8c9473c..740d0a5 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -73,8 +73,8 @@ func Write(writer io.Writer, codeBytes []byte, dataBytes []byte, dlls dll.List) AddressOfEntryPoint: uint32(code.MemoryOffset), BaseOfCode: uint32(code.MemoryOffset), ImageBase: config.BaseAddress, - SectionAlignment: config.MemoryAlign, // power of 2, must be greater than or equal to FileAlignment - FileAlignment: config.FileAlign, // power of 2 + SectionAlignment: uint32(config.MemoryAlign), // power of 2, must be greater than or equal to FileAlignment + FileAlignment: uint32(config.FileAlign), // power of 2 MajorOperatingSystemVersion: 0x06, MinorOperatingSystemVersion: 0, MajorImageVersion: 0, From 947a8db937504097a82a0e205712f04c6517b02f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Mar 2025 00:23:23 +0100 Subject: [PATCH 0900/1012] Implemented dynamic section alignment --- src/asmc/Finalize.go | 2 +- src/asmc/codeOffset.go | 4 ++-- src/asmc/move.go | 7 +++---- src/asmc/resolvePointers.go | 2 +- src/cli/Build.go | 8 ++++++-- src/config/config.go | 21 ++++++++++++++++++--- src/config/const.go | 6 ------ src/elf/ELF.go | 4 ++-- src/pe/EXE.go | 4 ++-- 9 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index 708f207..3f35552 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -18,7 +18,7 @@ func Finalize(a asm.Assembler, dlls dll.List) ([]byte, []byte) { code: make([]byte, 0, len(a.Instructions)*8), codeLabels: make(map[string]Address, 32), codePointers: make([]*pointer, 0, len(a.Instructions)*8), - codeStart: codeOffset(), + codeStart: uint32(codeOffset()), data: data, dataLabels: dataLabels, dlls: dlls, diff --git a/src/asmc/codeOffset.go b/src/asmc/codeOffset.go index 95c03ae..3cf6e64 100644 --- a/src/asmc/codeOffset.go +++ b/src/asmc/codeOffset.go @@ -9,8 +9,8 @@ import ( ) // codeOffset returns the file offset of the code section. -func codeOffset() Address { - headerEnd := Address(0) +func codeOffset() int { + headerEnd := 0 switch config.TargetOS { case config.Linux: diff --git a/src/asmc/move.go b/src/asmc/move.go index c4f60d7..c3687fb 100644 --- a/src/asmc/move.go +++ b/src/asmc/move.go @@ -19,15 +19,14 @@ func (c *compiler) move(x asm.Instruction) { start := Address(len(c.code)) c.code = x86.LoadAddress(c.code, operands.Register, 0x00_00_00_00) end := Address(len(c.code)) - size := uint32(4) - position := end - size + position := end - 4 opSize := position - start if strings.HasPrefix(operands.Label, "data ") { c.dataPointers = append(c.dataPointers, &pointer{ Position: position, OpSize: uint8(opSize), - Size: uint8(size), + Size: uint8(4), Resolve: func() Address { destination, exists := c.dataLabels[operands.Label] @@ -44,7 +43,7 @@ func (c *compiler) move(x asm.Instruction) { c.codePointers = append(c.codePointers, &pointer{ Position: position, OpSize: uint8(opSize), - Size: uint8(size), + Size: uint8(4), Resolve: func() Address { destination, exists := c.codeLabels[operands.Label] diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index f42c805..7603a86 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -77,7 +77,7 @@ restart: } } - sections := exe.MakeSections(int(codeOffset()), c.code, c.data, nil) + sections := exe.MakeSections(codeOffset(), c.code, c.data, nil) data := sections[1] c.dataStart = Address(data.MemoryOffset) diff --git a/src/cli/Build.go b/src/cli/Build.go index 75f4377..087d6c1 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -53,9 +53,9 @@ func buildExecutable(args []string) (*build.Build, error) { switch args[i] { case "arm": - config.TargetArch = config.ARM + config.SetTargetArch(config.ARM) case "x86": - config.TargetArch = config.X86 + config.SetTargetArch(config.X86) default: return b, &InvalidValueError{Value: args[i], Parameter: "arch"} } @@ -91,6 +91,10 @@ func buildExecutable(args []string) (*build.Build, error) { return b, &InvalidValueError{Value: runtime.GOOS, Parameter: "os"} } + if config.TargetArch == config.UnknownArch { + return b, &InvalidValueError{Value: runtime.GOARCH, Parameter: "arch"} + } + if len(b.Files) == 0 { b.Files = append(b.Files, ".") } diff --git a/src/config/config.go b/src/config/config.go index b031375..74a3a5f 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -5,6 +5,8 @@ import "runtime" var ( ConstantFold bool // Calculates the result of operations on constants at compile time. Dry bool // Skips writing the executable to disk. + FileAlign int // FileAlign is the alignment of the sections in the file. + MemoryAlign int // MemoryAlign is the alignment of the sections in memory. ShowAssembly bool // Shows assembly instructions at the end. ShowStatistics bool // Shows statistics at the end. Sections bool // Adds section headers to the executable. @@ -21,11 +23,11 @@ func Reset() { switch runtime.GOARCH { case "amd64": - TargetArch = X86 + SetTargetArch(X86) case "arm64": - TargetArch = ARM + SetTargetArch(ARM) default: - TargetArch = UnknownArch + SetTargetArch(UnknownArch) } switch runtime.GOOS { @@ -46,3 +48,16 @@ func Reset() { func Optimize(enable bool) { ConstantFold = enable } + +// SetTargetArch changes the target architecture and updates the alignment. +func SetTargetArch(arch Arch) { + TargetArch = arch + + if TargetArch == ARM { + FileAlign = 0x4000 + MemoryAlign = 0x4000 + } else { + FileAlign = 0x1000 + MemoryAlign = 0x1000 + } +} diff --git a/src/config/const.go b/src/config/const.go index 2e683c2..2783f7f 100644 --- a/src/config/const.go +++ b/src/config/const.go @@ -6,10 +6,4 @@ const ( // The base address is the virtual address for our ELF file. BaseAddress = 0x40 * MinAddress - - // FileAlign is the alignment of the sections in the file. - FileAlign = 0x4000 - - // MemoryAlign is the alignment of the sections in memory and it must be a multiple of the page size. - MemoryAlign = 0x4000 ) diff --git a/src/elf/ELF.go b/src/elf/ELF.go index b80978e..29c828a 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -56,7 +56,7 @@ func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { VirtualAddress: int64(config.BaseAddress + code.MemoryOffset), SizeInFile: int64(len(code.Bytes)), SizeInMemory: int64(len(code.Bytes)), - Align: config.MemoryAlign, + Align: int64(config.MemoryAlign), }, DataHeader: ProgramHeader{ Type: ProgramTypeLOAD, @@ -65,7 +65,7 @@ func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { VirtualAddress: int64(config.BaseAddress + data.MemoryOffset), SizeInFile: int64(len(data.Bytes)), SizeInMemory: int64(len(data.Bytes)), - Align: config.MemoryAlign, + Align: int64(config.MemoryAlign), }, } diff --git a/src/pe/EXE.go b/src/pe/EXE.go index 8c9473c..740d0a5 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -73,8 +73,8 @@ func Write(writer io.Writer, codeBytes []byte, dataBytes []byte, dlls dll.List) AddressOfEntryPoint: uint32(code.MemoryOffset), BaseOfCode: uint32(code.MemoryOffset), ImageBase: config.BaseAddress, - SectionAlignment: config.MemoryAlign, // power of 2, must be greater than or equal to FileAlignment - FileAlignment: config.FileAlign, // power of 2 + SectionAlignment: uint32(config.MemoryAlign), // power of 2, must be greater than or equal to FileAlignment + FileAlignment: uint32(config.FileAlign), // power of 2 MajorOperatingSystemVersion: 0x06, MinorOperatingSystemVersion: 0, MajorImageVersion: 0, From 86c795ecce4ef60e480e5a28638d0bd47d1e0f03 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Mar 2025 17:51:24 +0100 Subject: [PATCH 0901/1012] Fixed incorrect register lifetime in for loops --- src/asmc/compileX86.go | 3 ++- src/core/CompileFor.go | 4 +++- tests/programs/fibonacci.q | 26 ++++++++++++++++++++++++++ tests/programs_test.go | 1 + 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 tests/programs/fibonacci.q diff --git a/src/asmc/compileX86.go b/src/asmc/compileX86.go index b4279de..13ee381 100644 --- a/src/asmc/compileX86.go +++ b/src/asmc/compileX86.go @@ -90,7 +90,8 @@ func (c *compiler) compileX86(x asm.Instruction) { c.jump(x) case asm.LABEL: - c.codeLabels[x.Data.(*asm.Label).Name] = Address(len(c.code)) + label := x.Data.(*asm.Label) + c.codeLabels[label.Name] = Address(len(c.code)) case asm.LOAD: c.load(x) diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index 973c6f6..0d1530f 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -81,11 +81,13 @@ func (f *Function) CompileFor(loop *ast.For) error { case *eval.Register: if value.IsAlive() { tmp := f.NewRegister() - f.RegisterRegister(asm.MOVE, tmp, value.Register) defer f.FreeRegister(tmp) + f.RegisterRegister(asm.MOVE, tmp, value.Register) f.AddLabel(label) f.RegisterRegister(asm.COMPARE, counter, tmp) } else { + f.UseRegister(value.Register) + defer f.FreeRegister(value.Register) f.AddLabel(label) f.RegisterRegister(asm.COMPARE, counter, value.Register) } diff --git a/tests/programs/fibonacci.q b/tests/programs/fibonacci.q new file mode 100644 index 0000000..a930713 --- /dev/null +++ b/tests/programs/fibonacci.q @@ -0,0 +1,26 @@ +main() { + for i := 0..10 { + assert fib1(i) == fib2(i) + } +} + +fib1(n int) -> int { + b := 0 + c := 1 + + for 0..n { + a := b + b = c + c = a + b + } + + return b +} + +fib2(n int) -> int { + if n <= 1 { + return n + } + + return fib2(n - 1) + fib2(n - 2) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index a1e76ce..cfb6d2a 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -62,6 +62,7 @@ var programs = []struct { {"loop-lifetime", 0}, {"loop-in-loop", 0}, {"for", 0}, + {"fibonacci", 0}, {"memory-free", 0}, {"out-of-memory", 0}, {"index-static", 0}, From 016938932f3ea02cc2709b07144799bc0604d91a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Mar 2025 17:51:24 +0100 Subject: [PATCH 0902/1012] Fixed incorrect register lifetime in for loops --- src/asmc/compileX86.go | 3 ++- src/core/CompileFor.go | 4 +++- tests/programs/fibonacci.q | 26 ++++++++++++++++++++++++++ tests/programs_test.go | 1 + 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 tests/programs/fibonacci.q diff --git a/src/asmc/compileX86.go b/src/asmc/compileX86.go index b4279de..13ee381 100644 --- a/src/asmc/compileX86.go +++ b/src/asmc/compileX86.go @@ -90,7 +90,8 @@ func (c *compiler) compileX86(x asm.Instruction) { c.jump(x) case asm.LABEL: - c.codeLabels[x.Data.(*asm.Label).Name] = Address(len(c.code)) + label := x.Data.(*asm.Label) + c.codeLabels[label.Name] = Address(len(c.code)) case asm.LOAD: c.load(x) diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index 973c6f6..0d1530f 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -81,11 +81,13 @@ func (f *Function) CompileFor(loop *ast.For) error { case *eval.Register: if value.IsAlive() { tmp := f.NewRegister() - f.RegisterRegister(asm.MOVE, tmp, value.Register) defer f.FreeRegister(tmp) + f.RegisterRegister(asm.MOVE, tmp, value.Register) f.AddLabel(label) f.RegisterRegister(asm.COMPARE, counter, tmp) } else { + f.UseRegister(value.Register) + defer f.FreeRegister(value.Register) f.AddLabel(label) f.RegisterRegister(asm.COMPARE, counter, value.Register) } diff --git a/tests/programs/fibonacci.q b/tests/programs/fibonacci.q new file mode 100644 index 0000000..a930713 --- /dev/null +++ b/tests/programs/fibonacci.q @@ -0,0 +1,26 @@ +main() { + for i := 0..10 { + assert fib1(i) == fib2(i) + } +} + +fib1(n int) -> int { + b := 0 + c := 1 + + for 0..n { + a := b + b = c + c = a + b + } + + return b +} + +fib2(n int) -> int { + if n <= 1 { + return n + } + + return fib2(n - 1) + fib2(n - 2) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index a1e76ce..cfb6d2a 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -62,6 +62,7 @@ var programs = []struct { {"loop-lifetime", 0}, {"loop-in-loop", 0}, {"for", 0}, + {"fibonacci", 0}, {"memory-free", 0}, {"out-of-memory", 0}, {"index-static", 0}, From 792d0eb74d7c4e6eaf4925c94aa96d407a80bd37 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Mar 2025 19:24:15 +0100 Subject: [PATCH 0903/1012] Added more tests --- docs/readme.md | 2 +- src/core/CompileAssignDivision.go | 4 +-- src/core/CompileDefinition.go | 2 +- src/core/CompileFor.go | 43 ++++++++++++++++---------- src/core/MultiDefine.go | 2 +- src/core/{Define.go => NewVariable.go} | 4 +-- tests/programs/factorial.q | 23 ++++++++++++++ tests/programs_test.go | 1 + 8 files changed, 58 insertions(+), 23 deletions(-) rename src/core/{Define.go => NewVariable.go} (85%) create mode 100644 tests/programs/factorial.q diff --git a/docs/readme.md b/docs/readme.md index 1ef10e1..5873bd8 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -33,7 +33,7 @@ You can take a look at the [examples](../examples). go run gotest.tools/gotestsum@latest ``` -This will run over 350 [tests](../tests) in various categories. +This will run over 400 [tests](../tests) in various categories. ## Platforms diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index c75026a..3d4ab0e 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -24,13 +24,13 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { ) if expr.Token.Kind == token.Define { - quotientVariable, err = f.Define(variables.Children[0]) + quotientVariable, err = f.NewVariable(variables.Children[0]) if err != nil { return err } - remainderVariable, err = f.Define(variables.Children[1]) + remainderVariable, err = f.NewVariable(variables.Children[1]) if err != nil { return err diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index 1ea9564..ddab01b 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -13,7 +13,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { right := node.Expression.Children[1] if left.IsLeaf() { - variable, err := f.Define(left) + variable, err := f.NewVariable(left) if err != nil { return err diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index 0d1530f..a820b9d 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -9,6 +9,7 @@ import ( "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/scope" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" ) @@ -29,14 +30,15 @@ func (f *Function) CompileFor(loop *ast.For) error { to *expression.Expression ) + counter = f.NewRegister() + defer f.FreeRegister(counter) + switch loop.Head.Token.Kind { case token.Define: from = loop.Head.Children[1].Children[0] to = loop.Head.Children[1].Children[1] case token.Range: - counter = f.NewRegister() - defer f.FreeRegister(counter) from = loop.Head.Children[0] to = loop.Head.Children[1] @@ -60,20 +62,6 @@ func (f *Function) CompileFor(loop *ast.For) error { return errors.New(&errors.TypeMismatch{Encountered: value.Type().Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) } - scope := f.PushScope(loop.Body, f.File.Bytes) - scope.InLoop = true - - if loop.Head.Token.Kind == token.Define { - variable, err := f.Define(loop.Head.Children[0]) - - if err != nil { - return err - } - - counter = variable.Value.Register - f.AddVariable(variable) - } - switch value := value.(type) { case *eval.Number: f.AddLabel(label) @@ -95,6 +83,29 @@ func (f *Function) CompileFor(loop *ast.For) error { panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, value)) } + loopScope := f.PushScope(loop.Body, f.File.Bytes) + loopScope.InLoop = true + + if loop.Head.Token.Kind == token.Define { + leaf := loop.Head.Children[0] + name := leaf.Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + + if variable != nil { + return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, leaf.Token.Position) + } + + variable = &scope.Variable{ + Name: name, + Value: eval.Register{ + Register: counter, + Alive: ast.Count(loop.Body, f.File.Bytes, token.Identifier, name), + }, + } + + f.AddVariable(variable) + } + f.Jump(asm.JGE, labelEnd) err = f.CompileAST(loop.Body) f.RegisterNumber(asm.ADD, counter, 1) diff --git a/src/core/MultiDefine.go b/src/core/MultiDefine.go index 6240c69..c1ed73b 100644 --- a/src/core/MultiDefine.go +++ b/src/core/MultiDefine.go @@ -15,7 +15,7 @@ func (f *Function) MultiDefine(left *expression.Expression, right *expression.Ex } return left.EachLeaf(func(leaf *expression.Expression) error { - variable, err := f.Define(leaf) + variable, err := f.NewVariable(leaf) if err != nil { return err diff --git a/src/core/Define.go b/src/core/NewVariable.go similarity index 85% rename from src/core/Define.go rename to src/core/NewVariable.go index c5c6cef..ecf405a 100644 --- a/src/core/Define.go +++ b/src/core/NewVariable.go @@ -8,8 +8,8 @@ import ( "git.urbach.dev/cli/q/src/token" ) -// Define defines a new variable. -func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) { +// NewVariable creates a new variable. +func (f *Function) NewVariable(leaf *expression.Expression) (*scope.Variable, error) { name := leaf.Token.Text(f.File.Bytes) variable := f.VariableByName(name) diff --git a/tests/programs/factorial.q b/tests/programs/factorial.q new file mode 100644 index 0000000..3e52582 --- /dev/null +++ b/tests/programs/factorial.q @@ -0,0 +1,23 @@ +main() { + for i := 0..10 { + assert fac1(i) == fac2(i) + } +} + +fac1(n int) -> int { + x := 1 + + for i := 1..n+1 { + x = x * i + } + + return x +} + +fac2(n int) -> int { + if n <= 1 { + return 1 + } + + return n * fac2(n - 1) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index cfb6d2a..b4b18ed 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -62,6 +62,7 @@ var programs = []struct { {"loop-lifetime", 0}, {"loop-in-loop", 0}, {"for", 0}, + {"factorial", 0}, {"fibonacci", 0}, {"memory-free", 0}, {"out-of-memory", 0}, From 6c659a2b0c89b5ed7f1cb0ea8c60fc1568abf15c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Mar 2025 19:24:15 +0100 Subject: [PATCH 0904/1012] Added more tests --- docs/readme.md | 2 +- src/core/CompileAssignDivision.go | 4 +-- src/core/CompileDefinition.go | 2 +- src/core/CompileFor.go | 43 ++++++++++++++++---------- src/core/MultiDefine.go | 2 +- src/core/{Define.go => NewVariable.go} | 4 +-- tests/programs/factorial.q | 23 ++++++++++++++ tests/programs_test.go | 1 + 8 files changed, 58 insertions(+), 23 deletions(-) rename src/core/{Define.go => NewVariable.go} (85%) create mode 100644 tests/programs/factorial.q diff --git a/docs/readme.md b/docs/readme.md index 1ef10e1..5873bd8 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -33,7 +33,7 @@ You can take a look at the [examples](../examples). go run gotest.tools/gotestsum@latest ``` -This will run over 350 [tests](../tests) in various categories. +This will run over 400 [tests](../tests) in various categories. ## Platforms diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index c75026a..3d4ab0e 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -24,13 +24,13 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { ) if expr.Token.Kind == token.Define { - quotientVariable, err = f.Define(variables.Children[0]) + quotientVariable, err = f.NewVariable(variables.Children[0]) if err != nil { return err } - remainderVariable, err = f.Define(variables.Children[1]) + remainderVariable, err = f.NewVariable(variables.Children[1]) if err != nil { return err diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go index 1ea9564..ddab01b 100644 --- a/src/core/CompileDefinition.go +++ b/src/core/CompileDefinition.go @@ -13,7 +13,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error { right := node.Expression.Children[1] if left.IsLeaf() { - variable, err := f.Define(left) + variable, err := f.NewVariable(left) if err != nil { return err diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index 0d1530f..a820b9d 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -9,6 +9,7 @@ import ( "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/scope" "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" ) @@ -29,14 +30,15 @@ func (f *Function) CompileFor(loop *ast.For) error { to *expression.Expression ) + counter = f.NewRegister() + defer f.FreeRegister(counter) + switch loop.Head.Token.Kind { case token.Define: from = loop.Head.Children[1].Children[0] to = loop.Head.Children[1].Children[1] case token.Range: - counter = f.NewRegister() - defer f.FreeRegister(counter) from = loop.Head.Children[0] to = loop.Head.Children[1] @@ -60,20 +62,6 @@ func (f *Function) CompileFor(loop *ast.For) error { return errors.New(&errors.TypeMismatch{Encountered: value.Type().Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position) } - scope := f.PushScope(loop.Body, f.File.Bytes) - scope.InLoop = true - - if loop.Head.Token.Kind == token.Define { - variable, err := f.Define(loop.Head.Children[0]) - - if err != nil { - return err - } - - counter = variable.Value.Register - f.AddVariable(variable) - } - switch value := value.(type) { case *eval.Number: f.AddLabel(label) @@ -95,6 +83,29 @@ func (f *Function) CompileFor(loop *ast.For) error { panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, value)) } + loopScope := f.PushScope(loop.Body, f.File.Bytes) + loopScope.InLoop = true + + if loop.Head.Token.Kind == token.Define { + leaf := loop.Head.Children[0] + name := leaf.Token.Text(f.File.Bytes) + variable := f.VariableByName(name) + + if variable != nil { + return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, leaf.Token.Position) + } + + variable = &scope.Variable{ + Name: name, + Value: eval.Register{ + Register: counter, + Alive: ast.Count(loop.Body, f.File.Bytes, token.Identifier, name), + }, + } + + f.AddVariable(variable) + } + f.Jump(asm.JGE, labelEnd) err = f.CompileAST(loop.Body) f.RegisterNumber(asm.ADD, counter, 1) diff --git a/src/core/MultiDefine.go b/src/core/MultiDefine.go index 6240c69..c1ed73b 100644 --- a/src/core/MultiDefine.go +++ b/src/core/MultiDefine.go @@ -15,7 +15,7 @@ func (f *Function) MultiDefine(left *expression.Expression, right *expression.Ex } return left.EachLeaf(func(leaf *expression.Expression) error { - variable, err := f.Define(leaf) + variable, err := f.NewVariable(leaf) if err != nil { return err diff --git a/src/core/Define.go b/src/core/NewVariable.go similarity index 85% rename from src/core/Define.go rename to src/core/NewVariable.go index c5c6cef..ecf405a 100644 --- a/src/core/Define.go +++ b/src/core/NewVariable.go @@ -8,8 +8,8 @@ import ( "git.urbach.dev/cli/q/src/token" ) -// Define defines a new variable. -func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) { +// NewVariable creates a new variable. +func (f *Function) NewVariable(leaf *expression.Expression) (*scope.Variable, error) { name := leaf.Token.Text(f.File.Bytes) variable := f.VariableByName(name) diff --git a/tests/programs/factorial.q b/tests/programs/factorial.q new file mode 100644 index 0000000..3e52582 --- /dev/null +++ b/tests/programs/factorial.q @@ -0,0 +1,23 @@ +main() { + for i := 0..10 { + assert fac1(i) == fac2(i) + } +} + +fac1(n int) -> int { + x := 1 + + for i := 1..n+1 { + x = x * i + } + + return x +} + +fac2(n int) -> int { + if n <= 1 { + return 1 + } + + return n * fac2(n - 1) +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index cfb6d2a..b4b18ed 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -62,6 +62,7 @@ var programs = []struct { {"loop-lifetime", 0}, {"loop-in-loop", 0}, {"for", 0}, + {"factorial", 0}, {"fibonacci", 0}, {"memory-free", 0}, {"out-of-memory", 0}, From 377b66663f4c0a79642d7f5775ca3a6884460bed Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Mar 2025 23:27:40 +0100 Subject: [PATCH 0905/1012] Simplified x86 encoder --- src/x86/Add.go | 2 +- src/x86/Call.go | 36 ++++------------------------------ src/x86/Load.go | 2 +- src/x86/LoadDynamic.go | 2 +- src/x86/ModRM.go | 2 +- src/x86/Move.go | 11 +---------- src/x86/Store.go | 12 ++++++------ src/x86/StoreDynamic.go | 12 ++++++------ src/x86/memoryAccess.go | 12 ++++++------ src/x86/memoryAccessDynamic.go | 24 +++++++++++------------ 10 files changed, 39 insertions(+), 76 deletions(-) diff --git a/src/x86/Add.go b/src/x86/Add.go index e2a81ad..33d4419 100644 --- a/src/x86/Add.go +++ b/src/x86/Add.go @@ -6,7 +6,7 @@ import ( // AddRegisterNumber adds a number to the given register. func AddRegisterNumber(code []byte, register cpu.Register, number int) []byte { - return encodeNum(code, AddressDirect, 0, register, number, 0x83, 0x81) + return encodeNum(code, AddressDirect, 0b000, register, number, 0x83, 0x81) } // AddRegisterRegister adds a register value into another register. diff --git a/src/x86/Call.go b/src/x86/Call.go index 6815b24..4c87f6c 100644 --- a/src/x86/Call.go +++ b/src/x86/Call.go @@ -19,12 +19,8 @@ func Call(code []byte, offset uint32) []byte { // Calls a function whose address is stored in the given register. func CallRegister(code []byte, register cpu.Register) []byte { if register > 0b111 { - return append( - code, - 0x41, - 0xFF, - 0xD0+byte(register&0b111), - ) + code = append(code, 0x41) + register &= 0b111 } return append( @@ -50,30 +46,6 @@ func CallAt(code []byte, address uint32) []byte { // CallAtMemory calls a function at the address stored at the given memory address. // The memory address is relative to the next instruction. -func CallAtMemory(code []byte, register cpu.Register, offset int8) []byte { - mod := AddressMemory - - if offset != 0 || register == RBP || register == R13 { - mod = AddressMemoryOffset8 - } - - reg := byte(0b010) - rm := register - - if rm > 0b111 { - code = append(code, 0x41) - rm &= 0b111 - } - - code = append(code, 0xFF, ModRM(mod, reg, byte(rm))) - - if register == RSP || register == R12 { - code = append(code, SIB(Scale1, 0b100, 0b100)) - } - - if mod == AddressMemoryOffset8 { - code = append(code, byte(offset)) - } - - return code +func CallAtMemory(code []byte, base cpu.Register, offset int8) []byte { + return memoryAccess(code, 0xFF, 0xFF, 0b010, base, offset, 4) } diff --git a/src/x86/Load.go b/src/x86/Load.go index f29a08e..83bf558 100644 --- a/src/x86/Load.go +++ b/src/x86/Load.go @@ -4,5 +4,5 @@ import "git.urbach.dev/cli/q/src/cpu" // LoadRegister loads from memory into a register. func LoadRegister(code []byte, destination cpu.Register, base cpu.Register, offset int8, length byte) []byte { - return memoryAccess(code, 0x8A, 0x8B, base, offset, length, destination) + return memoryAccess(code, 0x8A, 0x8B, destination, base, offset, length) } diff --git a/src/x86/LoadDynamic.go b/src/x86/LoadDynamic.go index c06878e..4db16a2 100644 --- a/src/x86/LoadDynamic.go +++ b/src/x86/LoadDynamic.go @@ -4,5 +4,5 @@ import "git.urbach.dev/cli/q/src/cpu" // LoadDynamicRegister loads from memory with a register offset into a register. func LoadDynamicRegister(code []byte, destination cpu.Register, base cpu.Register, offset cpu.Register, length byte) []byte { - return memoryAccessDynamic(code, 0x8A, 0x8B, base, offset, length, destination) + return memoryAccessDynamic(code, 0x8A, 0x8B, destination, base, offset, length) } diff --git a/src/x86/ModRM.go b/src/x86/ModRM.go index 1ab4393..af17ad1 100644 --- a/src/x86/ModRM.go +++ b/src/x86/ModRM.go @@ -10,7 +10,7 @@ const ( AddressDirect = AddressMode(0b11) ) -// ModRM is used to generate a ModRM suffix. +// ModRM is used to generate a mode-register-memory suffix. // - mod: 2 bits. The addressing mode. // - reg: 3 bits. Register reference or opcode extension. // - rm: 3 bits. Register operand. diff --git a/src/x86/Move.go b/src/x86/Move.go index 7e3b103..3087723 100644 --- a/src/x86/Move.go +++ b/src/x86/Move.go @@ -41,16 +41,7 @@ func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byt // MoveRegisterNumber32 moves an integer into the given register and sign-extends the register. func MoveRegisterNumber32(code []byte, destination cpu.Register, number int) []byte { - b := byte(0) - - if destination > 0b111 { - b = 1 - destination &= 0b111 - } - - code = append(code, REX(1, 0, 0, b)) - code = append(code, 0xC7) - code = append(code, ModRM(AddressDirect, 0, byte(destination))) + code = encode(code, AddressDirect, 0, destination, 8, 0xC7) return binary.LittleEndian.AppendUint32(code, uint32(number)) } diff --git a/src/x86/Store.go b/src/x86/Store.go index 0f57b09..faabf79 100644 --- a/src/x86/Store.go +++ b/src/x86/Store.go @@ -6,9 +6,9 @@ import ( "git.urbach.dev/cli/q/src/cpu" ) -// StoreNumber stores a number into the memory address included in the given register. -func StoreNumber(code []byte, register cpu.Register, offset int8, length byte, number int) []byte { - code = memoryAccess(code, 0xC6, 0xC7, register, offset, length, 0b000) +// StoreNumber writes a number to a memory address. +func StoreNumber(code []byte, base cpu.Register, offset int8, length byte, number int) []byte { + code = memoryAccess(code, 0xC6, 0xC7, 0b000, base, offset, length) switch length { case 8, 4: @@ -21,7 +21,7 @@ func StoreNumber(code []byte, register cpu.Register, offset int8, length byte, n return append(code, byte(number)) } -// StoreRegister stores the contents of the `source` register into the memory address included in the given register. -func StoreRegister(code []byte, register cpu.Register, offset int8, length byte, source cpu.Register) []byte { - return memoryAccess(code, 0x88, 0x89, register, offset, length, source) +// StoreRegister writes the contents of the register to a memory address. +func StoreRegister(code []byte, base cpu.Register, offset int8, length byte, register cpu.Register) []byte { + return memoryAccess(code, 0x88, 0x89, register, base, offset, length) } diff --git a/src/x86/StoreDynamic.go b/src/x86/StoreDynamic.go index 6cb71e9..1b3a664 100644 --- a/src/x86/StoreDynamic.go +++ b/src/x86/StoreDynamic.go @@ -6,9 +6,9 @@ import ( "git.urbach.dev/cli/q/src/cpu" ) -// StoreDynamicNumber stores a number into the memory address at `destination` with a register offset. -func StoreDynamicNumber(code []byte, destination cpu.Register, offset cpu.Register, length byte, number int) []byte { - code = memoryAccessDynamic(code, 0xC6, 0xC7, destination, offset, length, 0b000) +// StoreDynamicNumber writes a number to a memory address with a register offset. +func StoreDynamicNumber(code []byte, base cpu.Register, offset cpu.Register, length byte, number int) []byte { + code = memoryAccessDynamic(code, 0xC6, 0xC7, 0b000, base, offset, length) switch length { case 8, 4: @@ -21,7 +21,7 @@ func StoreDynamicNumber(code []byte, destination cpu.Register, offset cpu.Regist return append(code, byte(number)) } -// StoreDynamicRegister stores the contents of the `source` register into the memory address at `destination` with a register offset. -func StoreDynamicRegister(code []byte, destination cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { - return memoryAccessDynamic(code, 0x88, 0x89, destination, offset, length, source) +// StoreDynamicRegister writes the contents of a register to a memory address with a register offset. +func StoreDynamicRegister(code []byte, base cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { + return memoryAccessDynamic(code, 0x88, 0x89, source, base, offset, length) } diff --git a/src/x86/memoryAccess.go b/src/x86/memoryAccess.go index 3757ec4..42e6a37 100644 --- a/src/x86/memoryAccess.go +++ b/src/x86/memoryAccess.go @@ -3,26 +3,26 @@ package x86 import "git.urbach.dev/cli/q/src/cpu" // memoryAccess encodes a memory access. -func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset int8, numBytes byte, source cpu.Register) []byte { +func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, base cpu.Register, offset int8, length byte) []byte { opCode := opCode32 - if numBytes == 1 { + if length == 1 { opCode = opCode8 } mod := AddressMemory - if offset != 0 || register == RBP || register == R13 { + if offset != 0 || base == RBP || base == R13 { mod = AddressMemoryOffset8 } - if numBytes == 2 { + if length == 2 { code = append(code, 0x66) } - code = encode(code, mod, source, register, numBytes, opCode) + code = encode(code, mod, register, base, length, opCode) - if register == RSP || register == R12 { + if base == RSP || base == R12 { code = append(code, SIB(Scale1, 0b100, 0b100)) } diff --git a/src/x86/memoryAccessDynamic.go b/src/x86/memoryAccessDynamic.go index 8282eaa..092fa64 100644 --- a/src/x86/memoryAccessDynamic.go +++ b/src/x86/memoryAccessDynamic.go @@ -3,7 +3,7 @@ package x86 import "git.urbach.dev/cli/q/src/cpu" // memoryAccessDynamic encodes a memory access using the value of a register as an offset. -func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, destination cpu.Register, offset cpu.Register, numBytes byte, source cpu.Register) []byte { +func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, base cpu.Register, offset cpu.Register, length byte) []byte { var ( w = byte(0) r = byte(0) @@ -13,21 +13,21 @@ func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, destination c mod = AddressMemory ) - if numBytes == 1 { + if length == 1 { opCode = opCode8 } if offset == RSP { - offset, destination = destination, offset + offset, base = base, offset } - if numBytes == 8 { + if length == 8 { w = 1 } - if source > 0b111 { + if register > 0b111 { r = 1 - source &= 0b111 + register &= 0b111 } if offset > 0b111 { @@ -35,23 +35,23 @@ func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, destination c offset &= 0b111 } - if destination > 0b111 { + if base > 0b111 { b = 1 - destination &= 0b111 + base &= 0b111 } - if destination == RBP || destination == R13 { + if base == RBP || base == R13 { mod = AddressMemoryOffset8 } - if numBytes == 2 { + if length == 2 { code = append(code, 0x66) } code = append(code, REX(w, r, x, b)) code = append(code, opCode) - code = append(code, ModRM(mod, byte(source), 0b100)) - code = append(code, SIB(Scale1, byte(offset), byte(destination))) + code = append(code, ModRM(mod, byte(register), 0b100)) + code = append(code, SIB(Scale1, byte(offset), byte(base))) if mod == AddressMemoryOffset8 { code = append(code, 0x00) From 0d562e8002548dd3171b92069084a731d83e548d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 9 Mar 2025 23:27:40 +0100 Subject: [PATCH 0906/1012] Simplified x86 encoder --- src/x86/Add.go | 2 +- src/x86/Call.go | 36 ++++------------------------------ src/x86/Load.go | 2 +- src/x86/LoadDynamic.go | 2 +- src/x86/ModRM.go | 2 +- src/x86/Move.go | 11 +---------- src/x86/Store.go | 12 ++++++------ src/x86/StoreDynamic.go | 12 ++++++------ src/x86/memoryAccess.go | 12 ++++++------ src/x86/memoryAccessDynamic.go | 24 +++++++++++------------ 10 files changed, 39 insertions(+), 76 deletions(-) diff --git a/src/x86/Add.go b/src/x86/Add.go index e2a81ad..33d4419 100644 --- a/src/x86/Add.go +++ b/src/x86/Add.go @@ -6,7 +6,7 @@ import ( // AddRegisterNumber adds a number to the given register. func AddRegisterNumber(code []byte, register cpu.Register, number int) []byte { - return encodeNum(code, AddressDirect, 0, register, number, 0x83, 0x81) + return encodeNum(code, AddressDirect, 0b000, register, number, 0x83, 0x81) } // AddRegisterRegister adds a register value into another register. diff --git a/src/x86/Call.go b/src/x86/Call.go index 6815b24..4c87f6c 100644 --- a/src/x86/Call.go +++ b/src/x86/Call.go @@ -19,12 +19,8 @@ func Call(code []byte, offset uint32) []byte { // Calls a function whose address is stored in the given register. func CallRegister(code []byte, register cpu.Register) []byte { if register > 0b111 { - return append( - code, - 0x41, - 0xFF, - 0xD0+byte(register&0b111), - ) + code = append(code, 0x41) + register &= 0b111 } return append( @@ -50,30 +46,6 @@ func CallAt(code []byte, address uint32) []byte { // CallAtMemory calls a function at the address stored at the given memory address. // The memory address is relative to the next instruction. -func CallAtMemory(code []byte, register cpu.Register, offset int8) []byte { - mod := AddressMemory - - if offset != 0 || register == RBP || register == R13 { - mod = AddressMemoryOffset8 - } - - reg := byte(0b010) - rm := register - - if rm > 0b111 { - code = append(code, 0x41) - rm &= 0b111 - } - - code = append(code, 0xFF, ModRM(mod, reg, byte(rm))) - - if register == RSP || register == R12 { - code = append(code, SIB(Scale1, 0b100, 0b100)) - } - - if mod == AddressMemoryOffset8 { - code = append(code, byte(offset)) - } - - return code +func CallAtMemory(code []byte, base cpu.Register, offset int8) []byte { + return memoryAccess(code, 0xFF, 0xFF, 0b010, base, offset, 4) } diff --git a/src/x86/Load.go b/src/x86/Load.go index f29a08e..83bf558 100644 --- a/src/x86/Load.go +++ b/src/x86/Load.go @@ -4,5 +4,5 @@ import "git.urbach.dev/cli/q/src/cpu" // LoadRegister loads from memory into a register. func LoadRegister(code []byte, destination cpu.Register, base cpu.Register, offset int8, length byte) []byte { - return memoryAccess(code, 0x8A, 0x8B, base, offset, length, destination) + return memoryAccess(code, 0x8A, 0x8B, destination, base, offset, length) } diff --git a/src/x86/LoadDynamic.go b/src/x86/LoadDynamic.go index c06878e..4db16a2 100644 --- a/src/x86/LoadDynamic.go +++ b/src/x86/LoadDynamic.go @@ -4,5 +4,5 @@ import "git.urbach.dev/cli/q/src/cpu" // LoadDynamicRegister loads from memory with a register offset into a register. func LoadDynamicRegister(code []byte, destination cpu.Register, base cpu.Register, offset cpu.Register, length byte) []byte { - return memoryAccessDynamic(code, 0x8A, 0x8B, base, offset, length, destination) + return memoryAccessDynamic(code, 0x8A, 0x8B, destination, base, offset, length) } diff --git a/src/x86/ModRM.go b/src/x86/ModRM.go index 1ab4393..af17ad1 100644 --- a/src/x86/ModRM.go +++ b/src/x86/ModRM.go @@ -10,7 +10,7 @@ const ( AddressDirect = AddressMode(0b11) ) -// ModRM is used to generate a ModRM suffix. +// ModRM is used to generate a mode-register-memory suffix. // - mod: 2 bits. The addressing mode. // - reg: 3 bits. Register reference or opcode extension. // - rm: 3 bits. Register operand. diff --git a/src/x86/Move.go b/src/x86/Move.go index 7e3b103..3087723 100644 --- a/src/x86/Move.go +++ b/src/x86/Move.go @@ -41,16 +41,7 @@ func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byt // MoveRegisterNumber32 moves an integer into the given register and sign-extends the register. func MoveRegisterNumber32(code []byte, destination cpu.Register, number int) []byte { - b := byte(0) - - if destination > 0b111 { - b = 1 - destination &= 0b111 - } - - code = append(code, REX(1, 0, 0, b)) - code = append(code, 0xC7) - code = append(code, ModRM(AddressDirect, 0, byte(destination))) + code = encode(code, AddressDirect, 0, destination, 8, 0xC7) return binary.LittleEndian.AppendUint32(code, uint32(number)) } diff --git a/src/x86/Store.go b/src/x86/Store.go index 0f57b09..faabf79 100644 --- a/src/x86/Store.go +++ b/src/x86/Store.go @@ -6,9 +6,9 @@ import ( "git.urbach.dev/cli/q/src/cpu" ) -// StoreNumber stores a number into the memory address included in the given register. -func StoreNumber(code []byte, register cpu.Register, offset int8, length byte, number int) []byte { - code = memoryAccess(code, 0xC6, 0xC7, register, offset, length, 0b000) +// StoreNumber writes a number to a memory address. +func StoreNumber(code []byte, base cpu.Register, offset int8, length byte, number int) []byte { + code = memoryAccess(code, 0xC6, 0xC7, 0b000, base, offset, length) switch length { case 8, 4: @@ -21,7 +21,7 @@ func StoreNumber(code []byte, register cpu.Register, offset int8, length byte, n return append(code, byte(number)) } -// StoreRegister stores the contents of the `source` register into the memory address included in the given register. -func StoreRegister(code []byte, register cpu.Register, offset int8, length byte, source cpu.Register) []byte { - return memoryAccess(code, 0x88, 0x89, register, offset, length, source) +// StoreRegister writes the contents of the register to a memory address. +func StoreRegister(code []byte, base cpu.Register, offset int8, length byte, register cpu.Register) []byte { + return memoryAccess(code, 0x88, 0x89, register, base, offset, length) } diff --git a/src/x86/StoreDynamic.go b/src/x86/StoreDynamic.go index 6cb71e9..1b3a664 100644 --- a/src/x86/StoreDynamic.go +++ b/src/x86/StoreDynamic.go @@ -6,9 +6,9 @@ import ( "git.urbach.dev/cli/q/src/cpu" ) -// StoreDynamicNumber stores a number into the memory address at `destination` with a register offset. -func StoreDynamicNumber(code []byte, destination cpu.Register, offset cpu.Register, length byte, number int) []byte { - code = memoryAccessDynamic(code, 0xC6, 0xC7, destination, offset, length, 0b000) +// StoreDynamicNumber writes a number to a memory address with a register offset. +func StoreDynamicNumber(code []byte, base cpu.Register, offset cpu.Register, length byte, number int) []byte { + code = memoryAccessDynamic(code, 0xC6, 0xC7, 0b000, base, offset, length) switch length { case 8, 4: @@ -21,7 +21,7 @@ func StoreDynamicNumber(code []byte, destination cpu.Register, offset cpu.Regist return append(code, byte(number)) } -// StoreDynamicRegister stores the contents of the `source` register into the memory address at `destination` with a register offset. -func StoreDynamicRegister(code []byte, destination cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { - return memoryAccessDynamic(code, 0x88, 0x89, destination, offset, length, source) +// StoreDynamicRegister writes the contents of a register to a memory address with a register offset. +func StoreDynamicRegister(code []byte, base cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { + return memoryAccessDynamic(code, 0x88, 0x89, source, base, offset, length) } diff --git a/src/x86/memoryAccess.go b/src/x86/memoryAccess.go index 3757ec4..42e6a37 100644 --- a/src/x86/memoryAccess.go +++ b/src/x86/memoryAccess.go @@ -3,26 +3,26 @@ package x86 import "git.urbach.dev/cli/q/src/cpu" // memoryAccess encodes a memory access. -func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset int8, numBytes byte, source cpu.Register) []byte { +func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, base cpu.Register, offset int8, length byte) []byte { opCode := opCode32 - if numBytes == 1 { + if length == 1 { opCode = opCode8 } mod := AddressMemory - if offset != 0 || register == RBP || register == R13 { + if offset != 0 || base == RBP || base == R13 { mod = AddressMemoryOffset8 } - if numBytes == 2 { + if length == 2 { code = append(code, 0x66) } - code = encode(code, mod, source, register, numBytes, opCode) + code = encode(code, mod, register, base, length, opCode) - if register == RSP || register == R12 { + if base == RSP || base == R12 { code = append(code, SIB(Scale1, 0b100, 0b100)) } diff --git a/src/x86/memoryAccessDynamic.go b/src/x86/memoryAccessDynamic.go index 8282eaa..092fa64 100644 --- a/src/x86/memoryAccessDynamic.go +++ b/src/x86/memoryAccessDynamic.go @@ -3,7 +3,7 @@ package x86 import "git.urbach.dev/cli/q/src/cpu" // memoryAccessDynamic encodes a memory access using the value of a register as an offset. -func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, destination cpu.Register, offset cpu.Register, numBytes byte, source cpu.Register) []byte { +func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, base cpu.Register, offset cpu.Register, length byte) []byte { var ( w = byte(0) r = byte(0) @@ -13,21 +13,21 @@ func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, destination c mod = AddressMemory ) - if numBytes == 1 { + if length == 1 { opCode = opCode8 } if offset == RSP { - offset, destination = destination, offset + offset, base = base, offset } - if numBytes == 8 { + if length == 8 { w = 1 } - if source > 0b111 { + if register > 0b111 { r = 1 - source &= 0b111 + register &= 0b111 } if offset > 0b111 { @@ -35,23 +35,23 @@ func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, destination c offset &= 0b111 } - if destination > 0b111 { + if base > 0b111 { b = 1 - destination &= 0b111 + base &= 0b111 } - if destination == RBP || destination == R13 { + if base == RBP || base == R13 { mod = AddressMemoryOffset8 } - if numBytes == 2 { + if length == 2 { code = append(code, 0x66) } code = append(code, REX(w, r, x, b)) code = append(code, opCode) - code = append(code, ModRM(mod, byte(source), 0b100)) - code = append(code, SIB(Scale1, byte(offset), byte(destination))) + code = append(code, ModRM(mod, byte(register), 0b100)) + code = append(code, SIB(Scale1, byte(offset), byte(base))) if mod == AddressMemoryOffset8 { code = append(code, 0x00) From f7a4f0cacc268b5f9dd035b953bc6d9b50c95f79 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 10 Mar 2025 15:58:31 +0100 Subject: [PATCH 0907/1012] Added struct lifetime test --- src/core/CompileAssign.go | 3 +++ tests/programs/struct-lifetime.q | 8 ++++++++ tests/programs_test.go | 1 + 3 files changed, 12 insertions(+) create mode 100644 tests/programs/struct-lifetime.q diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 25b3c0a..85eee9d 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -39,6 +39,9 @@ func (f *Function) CompileAssign(node *ast.Assign) error { case *eval.Register: f.Execute(operation, leftValue.Register, right) case *eval.Memory: + // TODO: Reservation needs to be canceled on defer + f.CurrentScope().Reserve(leftValue.Memory.Base) + if operation.Kind == token.Assign { rightValue, err := f.Evaluate(right) diff --git a/tests/programs/struct-lifetime.q b/tests/programs/struct-lifetime.q new file mode 100644 index 0000000..a32f469 --- /dev/null +++ b/tests/programs/struct-lifetime.q @@ -0,0 +1,8 @@ +main() { + c := new(Counter) + c.value += 16 +} + +struct Counter { + value int +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index b4b18ed..899cbb7 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -69,6 +69,7 @@ var programs = []struct { {"index-static", 0}, {"index-dynamic", 0}, {"struct", 0}, + {"struct-lifetime", 0}, {"len", 0}, {"cast", 0}, {"function-pointer", 0}, From 7807271f4353c13778256c879e70c7ab907c80df Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 10 Mar 2025 15:58:31 +0100 Subject: [PATCH 0908/1012] Added struct lifetime test --- src/core/CompileAssign.go | 3 +++ tests/programs/struct-lifetime.q | 8 ++++++++ tests/programs_test.go | 1 + 3 files changed, 12 insertions(+) create mode 100644 tests/programs/struct-lifetime.q diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 25b3c0a..85eee9d 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -39,6 +39,9 @@ func (f *Function) CompileAssign(node *ast.Assign) error { case *eval.Register: f.Execute(operation, leftValue.Register, right) case *eval.Memory: + // TODO: Reservation needs to be canceled on defer + f.CurrentScope().Reserve(leftValue.Memory.Base) + if operation.Kind == token.Assign { rightValue, err := f.Evaluate(right) diff --git a/tests/programs/struct-lifetime.q b/tests/programs/struct-lifetime.q new file mode 100644 index 0000000..a32f469 --- /dev/null +++ b/tests/programs/struct-lifetime.q @@ -0,0 +1,8 @@ +main() { + c := new(Counter) + c.value += 16 +} + +struct Counter { + value int +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index b4b18ed..899cbb7 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -69,6 +69,7 @@ var programs = []struct { {"index-static", 0}, {"index-dynamic", 0}, {"struct", 0}, + {"struct-lifetime", 0}, {"len", 0}, {"cast", 0}, {"function-pointer", 0}, From da3b83203c7040298cab762131fcda3f621fd32d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 10 Mar 2025 16:58:17 +0100 Subject: [PATCH 0909/1012] Added allocator test --- tests/programs/allocator.q | 35 +++++++++++++++++++++++++++++++++++ tests/programs_test.go | 1 + 2 files changed, 36 insertions(+) create mode 100644 tests/programs/allocator.q diff --git a/tests/programs/allocator.q b/tests/programs/allocator.q new file mode 100644 index 0000000..4b39208 --- /dev/null +++ b/tests/programs/allocator.q @@ -0,0 +1,35 @@ +import mem + +main() { + mgr := new(Allocator) + mgr.block = mem.alloc(2 * 1024 * 1024) + mgr.current = 0 + + a := new(Point) + a = mgr.block + mgr.current + mgr.current += 16 + + b := new(Point) + b = mgr.block + mgr.current + mgr.current += 16 + + a.x = 1 + a.y = 2 + b.x = 3 + b.y = 4 + + assert a.x == 1 + assert a.y == 2 + assert b.x == 3 + assert b.y == 4 +} + +struct Allocator { + block *any + current int +} + +struct Point { + x int + y int +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 899cbb7..953de1f 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -70,6 +70,7 @@ var programs = []struct { {"index-dynamic", 0}, {"struct", 0}, {"struct-lifetime", 0}, + {"allocator", 0}, {"len", 0}, {"cast", 0}, {"function-pointer", 0}, From 325dd126f9f677357c69062169d1b413e2350229 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 10 Mar 2025 16:58:17 +0100 Subject: [PATCH 0910/1012] Added allocator test --- tests/programs/allocator.q | 35 +++++++++++++++++++++++++++++++++++ tests/programs_test.go | 1 + 2 files changed, 36 insertions(+) create mode 100644 tests/programs/allocator.q diff --git a/tests/programs/allocator.q b/tests/programs/allocator.q new file mode 100644 index 0000000..4b39208 --- /dev/null +++ b/tests/programs/allocator.q @@ -0,0 +1,35 @@ +import mem + +main() { + mgr := new(Allocator) + mgr.block = mem.alloc(2 * 1024 * 1024) + mgr.current = 0 + + a := new(Point) + a = mgr.block + mgr.current + mgr.current += 16 + + b := new(Point) + b = mgr.block + mgr.current + mgr.current += 16 + + a.x = 1 + a.y = 2 + b.x = 3 + b.y = 4 + + assert a.x == 1 + assert a.y == 2 + assert b.x == 3 + assert b.y == 4 +} + +struct Allocator { + block *any + current int +} + +struct Point { + x int + y int +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 899cbb7..953de1f 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -70,6 +70,7 @@ var programs = []struct { {"index-dynamic", 0}, {"struct", 0}, {"struct-lifetime", 0}, + {"allocator", 0}, {"len", 0}, {"cast", 0}, {"function-pointer", 0}, From 35cbb936a9ae4dd76f20613dce0133bb35d7a666 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 10 Mar 2025 21:10:19 +0100 Subject: [PATCH 0911/1012] Updated dependencies --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c2ac09c..8245b16 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,4 @@ require ( git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d ) -require golang.org/x/sys v0.30.0 // indirect +require golang.org/x/sys v0.31.0 // indirect diff --git a/go.sum b/go.sum index 51f26c6..38c7851 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,5 @@ git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf h1:BQWa5GKNUsA5CSUa/ git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf/go.mod h1:y9jGII9JFiF1HNIju0u87OyPCt82xKCtqnAFyEreCDo= git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d h1:j1ARCrjUYE/c1STH/s6UYGQoOXAkxXll4AFhTb8P2GE= git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d/go.mod h1:UE8tQTrlWeVPKhfPZ9G5QrDFRhL9EoPazcDgfgd5Xsk= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= From d2ad8c83104651c24d2d7561dabd08b32f793ce0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 10 Mar 2025 21:10:19 +0100 Subject: [PATCH 0912/1012] Updated dependencies --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c2ac09c..8245b16 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,4 @@ require ( git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d ) -require golang.org/x/sys v0.30.0 // indirect +require golang.org/x/sys v0.31.0 // indirect diff --git a/go.sum b/go.sum index 51f26c6..38c7851 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,5 @@ git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf h1:BQWa5GKNUsA5CSUa/ git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf/go.mod h1:y9jGII9JFiF1HNIju0u87OyPCt82xKCtqnAFyEreCDo= git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d h1:j1ARCrjUYE/c1STH/s6UYGQoOXAkxXll4AFhTb8P2GE= git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d/go.mod h1:UE8tQTrlWeVPKhfPZ9G5QrDFRhL9EoPazcDgfgd5Xsk= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= From 8e36b7a7bcf2a72604246672072615b2b274bf35 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 11 Mar 2025 06:31:21 +0100 Subject: [PATCH 0913/1012] Improved assembler performance --- src/asm/Assembler.go | 22 +------- src/asm/CanSkip.go | 6 +-- src/asm/CanSkipReturn.go | 6 +-- src/asm/DataString.go | 28 ++++++++++ src/asm/Index.go | 3 ++ src/asm/Instruction.go | 5 +- src/asm/Instructions.go | 14 +---- src/asm/Label.go | 9 ++-- src/asm/Memory.go | 5 +- src/asm/MemoryLabel.go | 11 ++-- src/asm/MemoryNumber.go | 11 ++-- src/asm/MemoryRegister.go | 11 ++-- src/asm/Merge.go | 46 +++++++++++++++++ src/asm/Number.go | 9 ++-- src/asm/Param.go | 15 ++++++ src/asm/Register.go | 9 ++-- src/asm/RegisterLabel.go | 13 +++-- src/asm/RegisterNumber.go | 13 +++-- src/asm/RegisterRegister.go | 11 ++-- src/asm/SetData.go | 12 +++++ src/asm/Type.go | 17 +++++++ src/asm/bench_test.go | 36 +++++++++++++ src/asmc/Finalize.go | 3 +- src/asmc/call.go | 11 ++-- src/asmc/compileARM.go | 29 +++++++---- src/asmc/compileX86.go | 96 +++++++++++++++++++++-------------- src/asmc/compiler.go | 6 ++- src/asmc/dllCall.go | 2 +- src/asmc/jump.go | 2 +- src/asmc/load.go | 6 ++- src/asmc/move.go | 11 ++-- src/asmc/store.go | 16 +++--- src/compiler/Compile.go | 3 +- src/compiler/Result.go | 17 +++---- src/compiler/Statistics.go | 35 +++++++++++++ src/compiler/finalize.go | 16 +++++- src/core/PrintInstructions.go | 13 ++--- 37 files changed, 412 insertions(+), 166 deletions(-) create mode 100644 src/asm/DataString.go create mode 100644 src/asm/Index.go create mode 100644 src/asm/Merge.go create mode 100644 src/asm/Param.go create mode 100644 src/asm/SetData.go create mode 100644 src/asm/Type.go create mode 100644 src/asm/bench_test.go create mode 100644 src/compiler/Statistics.go diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index 8e32655..5605499 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -1,28 +1,10 @@ package asm -import ( - "maps" - - "git.urbach.dev/cli/q/src/data" -) +import "git.urbach.dev/cli/q/src/data" // Assembler contains a list of instructions. type Assembler struct { Data data.Data Instructions []Instruction -} - -// Merge combines the contents of this assembler with another one. -func (a *Assembler) Merge(b Assembler) { - maps.Copy(a.Data, b.Data) - a.Instructions = append(a.Instructions, b.Instructions...) -} - -// SetData sets the data for the given label. -func (a *Assembler) SetData(label string, bytes []byte) { - if a.Data == nil { - a.Data = data.Data{} - } - - a.Data.Insert(label, bytes) + Param Param } diff --git a/src/asm/CanSkip.go b/src/asm/CanSkip.go index ab58c65..f99b4a0 100644 --- a/src/asm/CanSkip.go +++ b/src/asm/CanSkip.go @@ -15,12 +15,12 @@ func (a *Assembler) CanSkip(mnemonic Mnemonic, left cpu.Register, right cpu.Regi last := a.Instructions[len(a.Instructions)-1] if mnemonic == MOVE && last.Mnemonic == MOVE { - lastData, isRegReg := last.Data.(*RegisterRegister) - - if !isRegReg { + if last.Type != TypeRegisterRegister { return false } + lastData := a.Param.RegisterRegister[last.Index] + if lastData.Destination == right && lastData.Source == left { return true } diff --git a/src/asm/CanSkipReturn.go b/src/asm/CanSkipReturn.go index 56e5096..0cf0bce 100644 --- a/src/asm/CanSkipReturn.go +++ b/src/asm/CanSkipReturn.go @@ -12,10 +12,10 @@ func (a *Assembler) CanSkipReturn() bool { return true } - if last.Mnemonic == CALL { - label, isLabel := last.Data.(*Label) + if last.Mnemonic == CALL && last.Type == TypeLabel { + label := a.Param.Label[last.Index] - if isLabel && label.String() == "core.exit" { + if label.String() == "core.exit" { return true } } diff --git a/src/asm/DataString.go b/src/asm/DataString.go new file mode 100644 index 0000000..144ca0e --- /dev/null +++ b/src/asm/DataString.go @@ -0,0 +1,28 @@ +package asm + +func (x Instruction) DataString(a *Assembler) string { + switch x.Type { + case TypeLabel: + return a.Param.Label[x.Index].String() + case TypeNumber: + return a.Param.Number[x.Index].String() + case TypeRegister: + return a.Param.Register[x.Index].String() + case TypeRegisterLabel: + return a.Param.RegisterLabel[x.Index].String() + case TypeRegisterNumber: + return a.Param.RegisterNumber[x.Index].String() + case TypeRegisterRegister: + return a.Param.RegisterRegister[x.Index].String() + case TypeMemory: + return a.Param.Memory[x.Index].String() + case TypeMemoryLabel: + return a.Param.MemoryLabel[x.Index].String() + case TypeMemoryNumber: + return a.Param.MemoryNumber[x.Index].String() + case TypeMemoryRegister: + return a.Param.MemoryRegister[x.Index].String() + default: + return "" + } +} diff --git a/src/asm/Index.go b/src/asm/Index.go new file mode 100644 index 0000000..ad942cc --- /dev/null +++ b/src/asm/Index.go @@ -0,0 +1,3 @@ +package asm + +type Index = uint16 diff --git a/src/asm/Instruction.go b/src/asm/Instruction.go index 2447bae..aace1ba 100644 --- a/src/asm/Instruction.go +++ b/src/asm/Instruction.go @@ -1,9 +1,8 @@ package asm -import "fmt" - // Instruction represents a single instruction which can be converted to machine code. type Instruction struct { - Data fmt.Stringer Mnemonic Mnemonic + Type Type + Index Index } diff --git a/src/asm/Instructions.go b/src/asm/Instructions.go index e203e3f..1066e10 100644 --- a/src/asm/Instructions.go +++ b/src/asm/Instructions.go @@ -2,22 +2,12 @@ package asm // Comment adds a comment at the current position. func (a *Assembler) Comment(text string) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: COMMENT, - Data: &Label{ - Name: text, - }, - }) + a.Label(COMMENT, text) } // DLLCall calls a function in a DLL file. func (a *Assembler) DLLCall(name string) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: DLLCALL, - Data: &Label{ - Name: name, - }, - }) + a.Label(DLLCALL, name) } // Return returns back to the caller. diff --git a/src/asm/Label.go b/src/asm/Label.go index 1b463bc..b55bd96 100644 --- a/src/asm/Label.go +++ b/src/asm/Label.go @@ -14,8 +14,11 @@ func (data *Label) String() string { func (a *Assembler) Label(mnemonic Mnemonic, name string) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &Label{ - Name: name, - }, + Type: TypeLabel, + Index: Index(len(a.Param.Label)), + }) + + a.Param.Label = append(a.Param.Label, Label{ + Name: name, }) } diff --git a/src/asm/Memory.go b/src/asm/Memory.go index 0af7c38..9ffd5d3 100644 --- a/src/asm/Memory.go +++ b/src/asm/Memory.go @@ -56,6 +56,9 @@ func (mem *Memory) String() string { func (a *Assembler) Memory(mnemonic Mnemonic, address Memory) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &address, + Type: TypeMemory, + Index: Index(len(a.Param.Memory)), }) + + a.Param.Memory = append(a.Param.Memory, address) } diff --git a/src/asm/MemoryLabel.go b/src/asm/MemoryLabel.go index 3ea1b9b..1402330 100644 --- a/src/asm/MemoryLabel.go +++ b/src/asm/MemoryLabel.go @@ -15,9 +15,12 @@ func (data *MemoryLabel) String() string { func (a *Assembler) MemoryLabel(mnemonic Mnemonic, address Memory, label string) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &MemoryLabel{ - Address: address, - Label: label, - }, + Type: TypeMemoryLabel, + Index: Index(len(a.Param.MemoryLabel)), + }) + + a.Param.MemoryLabel = append(a.Param.MemoryLabel, MemoryLabel{ + Address: address, + Label: label, }) } diff --git a/src/asm/MemoryNumber.go b/src/asm/MemoryNumber.go index 47c39d5..453f1de 100644 --- a/src/asm/MemoryNumber.go +++ b/src/asm/MemoryNumber.go @@ -17,9 +17,12 @@ func (data *MemoryNumber) String() string { func (a *Assembler) MemoryNumber(mnemonic Mnemonic, address Memory, number int) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &MemoryNumber{ - Address: address, - Number: number, - }, + Type: TypeMemoryNumber, + Index: Index(len(a.Param.MemoryNumber)), + }) + + a.Param.MemoryNumber = append(a.Param.MemoryNumber, MemoryNumber{ + Address: address, + Number: number, }) } diff --git a/src/asm/MemoryRegister.go b/src/asm/MemoryRegister.go index 5e76305..09d21b5 100644 --- a/src/asm/MemoryRegister.go +++ b/src/asm/MemoryRegister.go @@ -21,9 +21,12 @@ func (data *MemoryRegister) String() string { func (a *Assembler) MemoryRegister(mnemonic Mnemonic, address Memory, register cpu.Register) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &MemoryRegister{ - Address: address, - Register: register, - }, + Type: TypeMemoryRegister, + Index: Index(len(a.Param.MemoryRegister)), + }) + + a.Param.MemoryRegister = append(a.Param.MemoryRegister, MemoryRegister{ + Address: address, + Register: register, }) } diff --git a/src/asm/Merge.go b/src/asm/Merge.go new file mode 100644 index 0000000..68a5e1e --- /dev/null +++ b/src/asm/Merge.go @@ -0,0 +1,46 @@ +package asm + +import "maps" + +// Merge combines the contents of this assembler with another one. +func (a *Assembler) Merge(b *Assembler) { + maps.Copy(a.Data, b.Data) + from := len(a.Instructions) + a.Instructions = append(a.Instructions, b.Instructions...) + + for i := from; i < len(a.Instructions); i++ { + switch a.Instructions[i].Type { + case TypeLabel: + a.Instructions[i].Index += Index(len(a.Param.Label)) + case TypeNumber: + a.Instructions[i].Index += Index(len(a.Param.Number)) + case TypeRegister: + a.Instructions[i].Index += Index(len(a.Param.Register)) + case TypeRegisterLabel: + a.Instructions[i].Index += Index(len(a.Param.RegisterLabel)) + case TypeRegisterNumber: + a.Instructions[i].Index += Index(len(a.Param.RegisterNumber)) + case TypeRegisterRegister: + a.Instructions[i].Index += Index(len(a.Param.RegisterRegister)) + case TypeMemory: + a.Instructions[i].Index += Index(len(a.Param.Memory)) + case TypeMemoryLabel: + a.Instructions[i].Index += Index(len(a.Param.MemoryLabel)) + case TypeMemoryNumber: + a.Instructions[i].Index += Index(len(a.Param.MemoryNumber)) + case TypeMemoryRegister: + a.Instructions[i].Index += Index(len(a.Param.MemoryRegister)) + } + } + + a.Param.Label = append(a.Param.Label, b.Param.Label...) + a.Param.Number = append(a.Param.Number, b.Param.Number...) + a.Param.Register = append(a.Param.Register, b.Param.Register...) + a.Param.RegisterLabel = append(a.Param.RegisterLabel, b.Param.RegisterLabel...) + a.Param.RegisterNumber = append(a.Param.RegisterNumber, b.Param.RegisterNumber...) + a.Param.RegisterRegister = append(a.Param.RegisterRegister, b.Param.RegisterRegister...) + a.Param.Memory = append(a.Param.Memory, b.Param.Memory...) + a.Param.MemoryLabel = append(a.Param.MemoryLabel, b.Param.MemoryLabel...) + a.Param.MemoryNumber = append(a.Param.MemoryNumber, b.Param.MemoryNumber...) + a.Param.MemoryRegister = append(a.Param.MemoryRegister, b.Param.MemoryRegister...) +} diff --git a/src/asm/Number.go b/src/asm/Number.go index 3f0271c..4e58004 100644 --- a/src/asm/Number.go +++ b/src/asm/Number.go @@ -18,8 +18,11 @@ func (data *Number) String() string { func (a *Assembler) Number(mnemonic Mnemonic, number int) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &Number{ - Number: number, - }, + Type: TypeNumber, + Index: Index(len(a.Param.Number)), + }) + + a.Param.Number = append(a.Param.Number, Number{ + Number: number, }) } diff --git a/src/asm/Param.go b/src/asm/Param.go new file mode 100644 index 0000000..efb5f1b --- /dev/null +++ b/src/asm/Param.go @@ -0,0 +1,15 @@ +package asm + +// Param stores all the parameters for the instructions. +type Param struct { + Label []Label + Number []Number + Register []Register + RegisterLabel []RegisterLabel + RegisterNumber []RegisterNumber + RegisterRegister []RegisterRegister + Memory []Memory + MemoryLabel []MemoryLabel + MemoryNumber []MemoryNumber + MemoryRegister []MemoryRegister +} diff --git a/src/asm/Register.go b/src/asm/Register.go index 91be5e0..fee9080 100644 --- a/src/asm/Register.go +++ b/src/asm/Register.go @@ -18,8 +18,11 @@ func (data *Register) String() string { func (a *Assembler) Register(mnemonic Mnemonic, register cpu.Register) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &Register{ - Register: register, - }, + Type: TypeRegister, + Index: Index(len(a.Param.Register)), + }) + + a.Param.Register = append(a.Param.Register, Register{ + Register: register, }) } diff --git a/src/asm/RegisterLabel.go b/src/asm/RegisterLabel.go index 402954b..f5cb956 100644 --- a/src/asm/RegisterLabel.go +++ b/src/asm/RegisterLabel.go @@ -18,12 +18,15 @@ func (data *RegisterLabel) String() string { } // RegisterLabel adds an instruction with a register and a label. -func (a *Assembler) RegisterLabel(mnemonic Mnemonic, reg cpu.Register, label string) { +func (a *Assembler) RegisterLabel(mnemonic Mnemonic, register cpu.Register, label string) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &RegisterLabel{ - Register: reg, - Label: label, - }, + Type: TypeRegisterLabel, + Index: Index(len(a.Param.RegisterLabel)), + }) + + a.Param.RegisterLabel = append(a.Param.RegisterLabel, RegisterLabel{ + Register: register, + Label: label, }) } diff --git a/src/asm/RegisterNumber.go b/src/asm/RegisterNumber.go index f12bb81..2a5f711 100644 --- a/src/asm/RegisterNumber.go +++ b/src/asm/RegisterNumber.go @@ -18,12 +18,15 @@ func (data *RegisterNumber) String() string { } // RegisterNumber adds an instruction with a register and a number. -func (a *Assembler) RegisterNumber(mnemonic Mnemonic, reg cpu.Register, number int) { +func (a *Assembler) RegisterNumber(mnemonic Mnemonic, register cpu.Register, number int) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &RegisterNumber{ - Register: reg, - Number: number, - }, + Type: TypeRegisterNumber, + Index: Index(len(a.Param.RegisterNumber)), + }) + + a.Param.RegisterNumber = append(a.Param.RegisterNumber, RegisterNumber{ + Register: register, + Number: number, }) } diff --git a/src/asm/RegisterRegister.go b/src/asm/RegisterRegister.go index dadafd1..f6dd7e5 100644 --- a/src/asm/RegisterRegister.go +++ b/src/asm/RegisterRegister.go @@ -21,9 +21,12 @@ func (data *RegisterRegister) String() string { func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right cpu.Register) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &RegisterRegister{ - Destination: left, - Source: right, - }, + Type: TypeRegisterRegister, + Index: Index(len(a.Param.RegisterRegister)), + }) + + a.Param.RegisterRegister = append(a.Param.RegisterRegister, RegisterRegister{ + Destination: left, + Source: right, }) } diff --git a/src/asm/SetData.go b/src/asm/SetData.go new file mode 100644 index 0000000..257ded9 --- /dev/null +++ b/src/asm/SetData.go @@ -0,0 +1,12 @@ +package asm + +import "git.urbach.dev/cli/q/src/data" + +// SetData sets the data for the given label. +func (a *Assembler) SetData(label string, bytes []byte) { + if a.Data == nil { + a.Data = data.Data{} + } + + a.Data.Insert(label, bytes) +} diff --git a/src/asm/Type.go b/src/asm/Type.go new file mode 100644 index 0000000..25714f4 --- /dev/null +++ b/src/asm/Type.go @@ -0,0 +1,17 @@ +package asm + +type Type uint8 + +const ( + TypeNone Type = iota + TypeLabel + TypeNumber + TypeRegister + TypeRegisterLabel + TypeRegisterNumber + TypeRegisterRegister + TypeMemory + TypeMemoryLabel + TypeMemoryNumber + TypeMemoryRegister +) diff --git a/src/asm/bench_test.go b/src/asm/bench_test.go new file mode 100644 index 0000000..c060327 --- /dev/null +++ b/src/asm/bench_test.go @@ -0,0 +1,36 @@ +package asm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/asm" +) + +func BenchmarkMerge(b *testing.B) { + n := 100 + all := make([]*asm.Assembler, 0, n) + + for range n { + f := &asm.Assembler{} + f.Number(asm.PUSH, 1) + f.Number(asm.PUSH, 2) + f.Number(asm.PUSH, 3) + all = append(all, f) + } + + for b.Loop() { + final := asm.Assembler{} + + for _, f := range all { + final.Merge(f) + } + } +} + +func BenchmarkNumber(b *testing.B) { + a := asm.Assembler{} + + for b.Loop() { + a.Number(asm.PUSH, 42) + } +} diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index 3f35552..e2b935c 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -7,7 +7,7 @@ import ( ) // Finalize generates the final machine code. -func Finalize(a asm.Assembler, dlls dll.List) ([]byte, []byte) { +func Finalize(a *asm.Assembler, dlls dll.List) ([]byte, []byte) { data, dataLabels := a.Data.Finalize() if config.TargetOS == config.Windows && len(data) == 0 { @@ -15,6 +15,7 @@ func Finalize(a asm.Assembler, dlls dll.List) ([]byte, []byte) { } c := compiler{ + assembler: a, code: make([]byte, 0, len(a.Instructions)*8), codeLabels: make(map[string]Address, 32), codePointers: make([]*pointer, 0, len(a.Instructions)*8), diff --git a/src/asmc/call.go b/src/asmc/call.go index 3bdc411..c941bb6 100644 --- a/src/asmc/call.go +++ b/src/asmc/call.go @@ -8,8 +8,9 @@ import ( ) func (c *compiler) call(x asm.Instruction) { - switch data := x.Data.(type) { - case *asm.Label: + switch x.Type { + case asm.TypeLabel: + data := c.assembler.Param.Label[x.Index] c.code = x86.Call(c.code, 0x00_00_00_00) size := 4 @@ -32,10 +33,12 @@ func (c *compiler) call(x asm.Instruction) { c.codePointers = append(c.codePointers, pointer) - case *asm.Register: + case asm.TypeRegister: + data := c.assembler.Param.Register[x.Index] c.code = x86.CallRegister(c.code, data.Register) - case *asm.Memory: + case asm.TypeMemory: + data := c.assembler.Param.Memory[x.Index] c.code = x86.CallAtMemory(c.code, data.Base, data.Offset) } } diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 058f1f0..1c94725 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -13,8 +13,9 @@ import ( func (c *compiler) compileARM(x asm.Instruction) { switch x.Mnemonic { case asm.CALL: - switch data := x.Data.(type) { - case *asm.Label: + switch x.Type { + case asm.TypeLabel: + label := c.assembler.Param.Label[x.Index] position := Address(len(c.code)) c.append(arm.Call(0)) @@ -25,10 +26,10 @@ func (c *compiler) compileARM(x asm.Instruction) { } pointer.Resolve = func() Address { - destination, exists := c.codeLabels[data.Name] + destination, exists := c.codeLabels[label.Name] if !exists { - panic(fmt.Sprintf("unknown jump label %s", data.Name)) + panic(fmt.Sprintf("unknown jump label %s", label.Name)) } distance := (destination - position) / 4 @@ -39,13 +40,16 @@ func (c *compiler) compileARM(x asm.Instruction) { } case asm.LABEL: - c.codeLabels[x.Data.(*asm.Label).Name] = Address(len(c.code)) + label := c.assembler.Param.Label[x.Index] + c.codeLabels[label.Name] = Address(len(c.code)) c.append(0xa9be7bfd) c.append(0x910003fd) case asm.LOAD: - switch operands := x.Data.(type) { - case *asm.MemoryRegister: + switch x.Type { + case asm.TypeMemoryRegister: + operands := c.assembler.Param.MemoryRegister[x.Index] + if operands.Address.OffsetRegister == math.MaxUint8 { c.append(arm.LoadRegister(operands.Register, operands.Address.Base, int16(operands.Address.Offset), operands.Address.Length)) } else { @@ -55,14 +59,17 @@ func (c *compiler) compileARM(x asm.Instruction) { } case asm.MOVE: - switch operands := x.Data.(type) { - case *asm.RegisterRegister: + switch x.Type { + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.append(arm.MoveRegisterRegister(operands.Destination, operands.Source)) - case *asm.RegisterNumber: + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.append(arm.MoveRegisterNumber(operands.Register, operands.Number)) - case *asm.RegisterLabel: + case asm.TypeRegisterLabel: + operands := c.assembler.Param.RegisterLabel[x.Index] position := Address(len(c.code)) c.append(arm.LoadAddress(operands.Register, 0)) diff --git a/src/asmc/compileX86.go b/src/asmc/compileX86.go index 13ee381..f873cdd 100644 --- a/src/asmc/compileX86.go +++ b/src/asmc/compileX86.go @@ -8,40 +8,49 @@ import ( func (c *compiler) compileX86(x asm.Instruction) { switch x.Mnemonic { case asm.ADD: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.AddRegisterNumber(c.code, operands.Register, operands.Number) - case *asm.RegisterRegister: + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.code = x86.AddRegisterRegister(c.code, operands.Destination, operands.Source) } case asm.AND: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.AndRegisterNumber(c.code, operands.Register, operands.Number) - case *asm.RegisterRegister: + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.code = x86.AndRegisterRegister(c.code, operands.Destination, operands.Source) } case asm.SUB: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.SubRegisterNumber(c.code, operands.Register, operands.Number) - case *asm.RegisterRegister: + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.code = x86.SubRegisterRegister(c.code, operands.Destination, operands.Source) } case asm.MUL: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.MulRegisterNumber(c.code, operands.Register, operands.Number) - case *asm.RegisterRegister: + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.code = x86.MulRegisterRegister(c.code, operands.Destination, operands.Source) } case asm.DIV: - switch operands := x.Data.(type) { - case *asm.RegisterRegister: + switch x.Type { + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] if operands.Destination != x86.RAX { c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) } @@ -55,8 +64,9 @@ func (c *compiler) compileX86(x asm.Instruction) { } case asm.MODULO: - switch operands := x.Data.(type) { - case *asm.RegisterRegister: + switch x.Type { + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] if operands.Destination != x86.RAX { c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) } @@ -76,10 +86,12 @@ func (c *compiler) compileX86(x asm.Instruction) { return case asm.COMPARE: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.CompareRegisterNumber(c.code, operands.Register, operands.Number) - case *asm.RegisterRegister: + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.code = x86.CompareRegisterRegister(c.code, operands.Destination, operands.Source) } @@ -90,7 +102,7 @@ func (c *compiler) compileX86(x asm.Instruction) { c.jump(x) case asm.LABEL: - label := x.Data.(*asm.Label) + label := c.assembler.Param.Label[x.Index] c.codeLabels[label.Name] = Address(len(c.code)) case asm.LOAD: @@ -100,30 +112,36 @@ func (c *compiler) compileX86(x asm.Instruction) { c.move(x) case asm.NEGATE: - switch operands := x.Data.(type) { - case *asm.Register: + switch x.Type { + case asm.TypeRegister: + operands := c.assembler.Param.Register[x.Index] c.code = x86.NegateRegister(c.code, operands.Register) } case asm.OR: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.OrRegisterNumber(c.code, operands.Register, operands.Number) - case *asm.RegisterRegister: + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.code = x86.OrRegisterRegister(c.code, operands.Destination, operands.Source) } case asm.POP: - switch operands := x.Data.(type) { - case *asm.Register: + switch x.Type { + case asm.TypeRegister: + operands := c.assembler.Param.Register[x.Index] c.code = x86.PopRegister(c.code, operands.Register) } case asm.PUSH: - switch operands := x.Data.(type) { - case *asm.Number: + switch x.Type { + case asm.TypeNumber: + operands := c.assembler.Param.Number[x.Index] c.code = x86.PushNumber(c.code, int32(operands.Number)) - case *asm.Register: + case asm.TypeRegister: + operands := c.assembler.Param.Register[x.Index] c.code = x86.PushRegister(c.code, operands.Register) } @@ -131,14 +149,16 @@ func (c *compiler) compileX86(x asm.Instruction) { c.code = x86.Return(c.code) case asm.SHIFTL: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.ShiftLeftNumber(c.code, operands.Register, byte(operands.Number)&0b111111) } case asm.SHIFTRS: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.ShiftRightSignedNumber(c.code, operands.Register, byte(operands.Number)&0b111111) } @@ -149,10 +169,12 @@ func (c *compiler) compileX86(x asm.Instruction) { c.code = x86.Syscall(c.code) case asm.XOR: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.XorRegisterNumber(c.code, operands.Register, operands.Number) - case *asm.RegisterRegister: + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.code = x86.XorRegisterRegister(c.code, operands.Destination, operands.Source) } diff --git a/src/asmc/compiler.go b/src/asmc/compiler.go index 056704b..890b2d6 100644 --- a/src/asmc/compiler.go +++ b/src/asmc/compiler.go @@ -1,8 +1,12 @@ package asmc -import "git.urbach.dev/cli/q/src/dll" +import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/dll" +) type compiler struct { + assembler *asm.Assembler code []byte data []byte codeLabels map[string]Address diff --git a/src/asmc/dllCall.go b/src/asmc/dllCall.go index 22492e8..203fbc1 100644 --- a/src/asmc/dllCall.go +++ b/src/asmc/dllCall.go @@ -8,10 +8,10 @@ import ( ) func (c *compiler) dllCall(x asm.Instruction) { + label := c.assembler.Param.Label[x.Index] c.code = x86.CallAt(c.code, 0x00_00_00_00) next := Address(len(c.code)) position := next - 4 - label := x.Data.(*asm.Label) pointer := &pointer{ Position: Address(position), diff --git a/src/asmc/jump.go b/src/asmc/jump.go index 9964c8b..2b2f159 100644 --- a/src/asmc/jump.go +++ b/src/asmc/jump.go @@ -25,8 +25,8 @@ func (c *compiler) jump(x asm.Instruction) { c.code = x86.Jump8(c.code, 0x00) } + label := c.assembler.Param.Label[x.Index] size := 1 - label := x.Data.(*asm.Label) pointer := &pointer{ Position: Address(len(c.code) - size), diff --git a/src/asmc/load.go b/src/asmc/load.go index 8b28fa8..d6691f7 100644 --- a/src/asmc/load.go +++ b/src/asmc/load.go @@ -8,8 +8,10 @@ import ( ) func (c *compiler) load(x asm.Instruction) { - switch operands := x.Data.(type) { - case *asm.MemoryRegister: + switch x.Type { + case asm.TypeMemoryRegister: + operands := c.assembler.Param.MemoryRegister[x.Index] + if operands.Address.OffsetRegister == math.MaxUint8 { c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Base, operands.Address.Offset, operands.Address.Length) } else { diff --git a/src/asmc/move.go b/src/asmc/move.go index c3687fb..256ede6 100644 --- a/src/asmc/move.go +++ b/src/asmc/move.go @@ -8,14 +8,17 @@ import ( ) func (c *compiler) move(x asm.Instruction) { - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.MoveRegisterNumber(c.code, operands.Register, operands.Number) - case *asm.RegisterRegister: + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.code = x86.MoveRegisterRegister(c.code, operands.Destination, operands.Source) - case *asm.RegisterLabel: + case asm.TypeRegisterLabel: + operands := c.assembler.Param.RegisterLabel[x.Index] start := Address(len(c.code)) c.code = x86.LoadAddress(c.code, operands.Register, 0x00_00_00_00) end := Address(len(c.code)) diff --git a/src/asmc/store.go b/src/asmc/store.go index aa81960..f67b9b0 100644 --- a/src/asmc/store.go +++ b/src/asmc/store.go @@ -9,14 +9,17 @@ import ( ) func (c *compiler) store(x asm.Instruction) { - switch operands := x.Data.(type) { - case *asm.MemoryNumber: + switch x.Type { + case asm.TypeMemoryNumber: + operands := c.assembler.Param.MemoryNumber[x.Index] + if operands.Address.OffsetRegister == math.MaxUint8 { c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) } else { c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) } - case *asm.MemoryLabel: + case asm.TypeMemoryLabel: + operands := c.assembler.Param.MemoryLabel[x.Index] start := len(c.code) if operands.Address.OffsetRegister == math.MaxUint8 { @@ -27,14 +30,13 @@ func (c *compiler) store(x asm.Instruction) { size := 4 opSize := len(c.code) - size - start - memLabel := x.Data.(*asm.MemoryLabel) c.codePointers = append(c.codePointers, &pointer{ Position: Address(len(c.code) - size), OpSize: uint8(opSize), Size: uint8(size), Resolve: func() Address { - destination, exists := c.codeLabels[memLabel.Label] + destination, exists := c.codeLabels[operands.Label] if !exists { panic("unknown label") @@ -43,7 +45,9 @@ func (c *compiler) store(x asm.Instruction) { return config.BaseAddress + c.codeStart + destination }, }) - case *asm.MemoryRegister: + case asm.TypeMemoryRegister: + operands := c.assembler.Param.MemoryRegister[x.Index] + if operands.Address.OffsetRegister == math.MaxUint8 { c.code = x86.StoreRegister(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) } else { diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 6fd88c1..cffb1d6 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -105,8 +105,7 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < return result, function.Err } - result.InstructionCount += len(function.Assembler.Instructions) - result.DataCount += len(function.Assembler.Data) + result.Count(function) } // Check for unused imports in all files diff --git a/src/compiler/Result.go b/src/compiler/Result.go index b9bbb7b..04634ba 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -7,13 +7,12 @@ import ( // Result contains everything we need to write an executable file to disk. type Result struct { - Init *core.Function - Main *core.Function - Functions map[string]*core.Function - Traversed map[*core.Function]bool - Code []byte - Data []byte - DLLs dll.List - InstructionCount int - DataCount int + Init *core.Function + Main *core.Function + Functions map[string]*core.Function + Traversed map[*core.Function]bool + Code []byte + Data []byte + DLLs dll.List + Statistics } diff --git a/src/compiler/Statistics.go b/src/compiler/Statistics.go new file mode 100644 index 0000000..2c2cbbe --- /dev/null +++ b/src/compiler/Statistics.go @@ -0,0 +1,35 @@ +package compiler + +import "git.urbach.dev/cli/q/src/core" + +// Statistics contains counters for all functions in the build. +type Statistics struct { + InstructionCount int + DataCount int + LabelCount int + NumberCount int + RegisterCount int + RegisterLabelCount int + RegisterNumberCount int + RegisterRegisterCount int + MemoryCount int + MemoryLabelCount int + MemoryNumberCount int + MemoryRegisterCount int +} + +// Count adds statistics for this function. +func (s *Statistics) Count(function *core.Function) { + s.InstructionCount += len(function.Assembler.Instructions) + s.DataCount += len(function.Assembler.Data) + s.LabelCount += len(function.Assembler.Param.Label) + s.NumberCount += len(function.Assembler.Param.Number) + s.RegisterCount += len(function.Assembler.Param.Register) + s.RegisterLabelCount += len(function.Assembler.Param.RegisterLabel) + s.RegisterNumberCount += len(function.Assembler.Param.RegisterNumber) + s.RegisterRegisterCount += len(function.Assembler.Param.RegisterRegister) + s.MemoryCount += len(function.Assembler.Param.Memory) + s.MemoryLabelCount += len(function.Assembler.Param.MemoryLabel) + s.MemoryNumberCount += len(function.Assembler.Param.MemoryNumber) + s.MemoryRegisterCount += len(function.Assembler.Param.MemoryRegister) +} diff --git a/src/compiler/finalize.go b/src/compiler/finalize.go index 6437913..45d6c54 100644 --- a/src/compiler/finalize.go +++ b/src/compiler/finalize.go @@ -17,6 +17,18 @@ func (r *Result) finalize() { final := asm.Assembler{ Instructions: make([]asm.Instruction, 0, r.InstructionCount+8), Data: make(map[string][]byte, r.DataCount), + Param: asm.Param{ + Label: make([]asm.Label, 0, r.LabelCount), + Number: make([]asm.Number, 0, r.NumberCount), + Register: make([]asm.Register, 0, r.RegisterCount), + RegisterLabel: make([]asm.RegisterLabel, 0, r.RegisterLabelCount), + RegisterNumber: make([]asm.RegisterNumber, 0, r.RegisterNumberCount), + RegisterRegister: make([]asm.RegisterRegister, 0, r.RegisterRegisterCount), + Memory: make([]asm.Memory, 0, r.MemoryCount), + MemoryLabel: make([]asm.MemoryLabel, 0, r.MemoryLabelCount), + MemoryNumber: make([]asm.MemoryNumber, 0, r.MemoryNumberCount), + MemoryRegister: make([]asm.MemoryRegister, 0, r.MemoryRegisterCount), + }, } r.Traversed = make(map[*core.Function]bool, len(r.Functions)) @@ -24,7 +36,7 @@ func (r *Result) finalize() { // This will place the init function immediately after the entry point // and also add everything the init function calls recursively. r.eachFunction(r.Init, r.Traversed, func(f *core.Function) { - final.Merge(f.Assembler) + final.Merge(&f.Assembler) for _, library := range f.DLLs { for _, fn := range library.Functions { @@ -50,5 +62,5 @@ func (r *Result) finalize() { final.DLLCall("kernel32.ExitProcess") } - r.Code, r.Data = asmc.Finalize(final, r.DLLs) + r.Code, r.Data = asmc.Finalize(&final, r.DLLs) } diff --git a/src/core/PrintInstructions.go b/src/core/PrintInstructions.go index cc81953..a5d5f4c 100644 --- a/src/core/PrintInstructions.go +++ b/src/core/PrintInstructions.go @@ -26,19 +26,16 @@ func (f *Function) PrintInstructions() { switch x.Mnemonic { case asm.LABEL: - ansi.Yellow.Printf("%-44s", x.Data.String()+":") + label := f.Assembler.Param.Label[x.Index] + ansi.Yellow.Printf("%-44s", label.String()+":") case asm.COMMENT: - ansi.Dim.Printf("%-44s", x.Data.String()) + label := f.Assembler.Param.Label[x.Index] + ansi.Dim.Printf("%-44s", label.String()) default: ansi.Green.Printf("%-12s", x.Mnemonic.String()) - - if x.Data != nil { - fmt.Printf("%-32s", x.Data.String()) - } else { - fmt.Printf("%-32s", "") - } + fmt.Printf("%-32s", x.DataString(&f.Assembler)) } registers := bytes.Buffer{} From e7a06f5b266ec6652549254ac2b7c545f2c1b933 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 11 Mar 2025 06:31:21 +0100 Subject: [PATCH 0914/1012] Improved assembler performance --- src/asm/Assembler.go | 22 +------- src/asm/CanSkip.go | 6 +-- src/asm/CanSkipReturn.go | 6 +-- src/asm/DataString.go | 28 ++++++++++ src/asm/Index.go | 3 ++ src/asm/Instruction.go | 5 +- src/asm/Instructions.go | 14 +---- src/asm/Label.go | 9 ++-- src/asm/Memory.go | 5 +- src/asm/MemoryLabel.go | 11 ++-- src/asm/MemoryNumber.go | 11 ++-- src/asm/MemoryRegister.go | 11 ++-- src/asm/Merge.go | 46 +++++++++++++++++ src/asm/Number.go | 9 ++-- src/asm/Param.go | 15 ++++++ src/asm/Register.go | 9 ++-- src/asm/RegisterLabel.go | 13 +++-- src/asm/RegisterNumber.go | 13 +++-- src/asm/RegisterRegister.go | 11 ++-- src/asm/SetData.go | 12 +++++ src/asm/Type.go | 17 +++++++ src/asm/bench_test.go | 36 +++++++++++++ src/asmc/Finalize.go | 3 +- src/asmc/call.go | 11 ++-- src/asmc/compileARM.go | 29 +++++++---- src/asmc/compileX86.go | 96 +++++++++++++++++++++-------------- src/asmc/compiler.go | 6 ++- src/asmc/dllCall.go | 2 +- src/asmc/jump.go | 2 +- src/asmc/load.go | 6 ++- src/asmc/move.go | 11 ++-- src/asmc/store.go | 16 +++--- src/compiler/Compile.go | 3 +- src/compiler/Result.go | 17 +++---- src/compiler/Statistics.go | 35 +++++++++++++ src/compiler/finalize.go | 16 +++++- src/core/PrintInstructions.go | 13 ++--- 37 files changed, 412 insertions(+), 166 deletions(-) create mode 100644 src/asm/DataString.go create mode 100644 src/asm/Index.go create mode 100644 src/asm/Merge.go create mode 100644 src/asm/Param.go create mode 100644 src/asm/SetData.go create mode 100644 src/asm/Type.go create mode 100644 src/asm/bench_test.go create mode 100644 src/compiler/Statistics.go diff --git a/src/asm/Assembler.go b/src/asm/Assembler.go index 8e32655..5605499 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Assembler.go @@ -1,28 +1,10 @@ package asm -import ( - "maps" - - "git.urbach.dev/cli/q/src/data" -) +import "git.urbach.dev/cli/q/src/data" // Assembler contains a list of instructions. type Assembler struct { Data data.Data Instructions []Instruction -} - -// Merge combines the contents of this assembler with another one. -func (a *Assembler) Merge(b Assembler) { - maps.Copy(a.Data, b.Data) - a.Instructions = append(a.Instructions, b.Instructions...) -} - -// SetData sets the data for the given label. -func (a *Assembler) SetData(label string, bytes []byte) { - if a.Data == nil { - a.Data = data.Data{} - } - - a.Data.Insert(label, bytes) + Param Param } diff --git a/src/asm/CanSkip.go b/src/asm/CanSkip.go index ab58c65..f99b4a0 100644 --- a/src/asm/CanSkip.go +++ b/src/asm/CanSkip.go @@ -15,12 +15,12 @@ func (a *Assembler) CanSkip(mnemonic Mnemonic, left cpu.Register, right cpu.Regi last := a.Instructions[len(a.Instructions)-1] if mnemonic == MOVE && last.Mnemonic == MOVE { - lastData, isRegReg := last.Data.(*RegisterRegister) - - if !isRegReg { + if last.Type != TypeRegisterRegister { return false } + lastData := a.Param.RegisterRegister[last.Index] + if lastData.Destination == right && lastData.Source == left { return true } diff --git a/src/asm/CanSkipReturn.go b/src/asm/CanSkipReturn.go index 56e5096..0cf0bce 100644 --- a/src/asm/CanSkipReturn.go +++ b/src/asm/CanSkipReturn.go @@ -12,10 +12,10 @@ func (a *Assembler) CanSkipReturn() bool { return true } - if last.Mnemonic == CALL { - label, isLabel := last.Data.(*Label) + if last.Mnemonic == CALL && last.Type == TypeLabel { + label := a.Param.Label[last.Index] - if isLabel && label.String() == "core.exit" { + if label.String() == "core.exit" { return true } } diff --git a/src/asm/DataString.go b/src/asm/DataString.go new file mode 100644 index 0000000..144ca0e --- /dev/null +++ b/src/asm/DataString.go @@ -0,0 +1,28 @@ +package asm + +func (x Instruction) DataString(a *Assembler) string { + switch x.Type { + case TypeLabel: + return a.Param.Label[x.Index].String() + case TypeNumber: + return a.Param.Number[x.Index].String() + case TypeRegister: + return a.Param.Register[x.Index].String() + case TypeRegisterLabel: + return a.Param.RegisterLabel[x.Index].String() + case TypeRegisterNumber: + return a.Param.RegisterNumber[x.Index].String() + case TypeRegisterRegister: + return a.Param.RegisterRegister[x.Index].String() + case TypeMemory: + return a.Param.Memory[x.Index].String() + case TypeMemoryLabel: + return a.Param.MemoryLabel[x.Index].String() + case TypeMemoryNumber: + return a.Param.MemoryNumber[x.Index].String() + case TypeMemoryRegister: + return a.Param.MemoryRegister[x.Index].String() + default: + return "" + } +} diff --git a/src/asm/Index.go b/src/asm/Index.go new file mode 100644 index 0000000..ad942cc --- /dev/null +++ b/src/asm/Index.go @@ -0,0 +1,3 @@ +package asm + +type Index = uint16 diff --git a/src/asm/Instruction.go b/src/asm/Instruction.go index 2447bae..aace1ba 100644 --- a/src/asm/Instruction.go +++ b/src/asm/Instruction.go @@ -1,9 +1,8 @@ package asm -import "fmt" - // Instruction represents a single instruction which can be converted to machine code. type Instruction struct { - Data fmt.Stringer Mnemonic Mnemonic + Type Type + Index Index } diff --git a/src/asm/Instructions.go b/src/asm/Instructions.go index e203e3f..1066e10 100644 --- a/src/asm/Instructions.go +++ b/src/asm/Instructions.go @@ -2,22 +2,12 @@ package asm // Comment adds a comment at the current position. func (a *Assembler) Comment(text string) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: COMMENT, - Data: &Label{ - Name: text, - }, - }) + a.Label(COMMENT, text) } // DLLCall calls a function in a DLL file. func (a *Assembler) DLLCall(name string) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: DLLCALL, - Data: &Label{ - Name: name, - }, - }) + a.Label(DLLCALL, name) } // Return returns back to the caller. diff --git a/src/asm/Label.go b/src/asm/Label.go index 1b463bc..b55bd96 100644 --- a/src/asm/Label.go +++ b/src/asm/Label.go @@ -14,8 +14,11 @@ func (data *Label) String() string { func (a *Assembler) Label(mnemonic Mnemonic, name string) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &Label{ - Name: name, - }, + Type: TypeLabel, + Index: Index(len(a.Param.Label)), + }) + + a.Param.Label = append(a.Param.Label, Label{ + Name: name, }) } diff --git a/src/asm/Memory.go b/src/asm/Memory.go index 0af7c38..9ffd5d3 100644 --- a/src/asm/Memory.go +++ b/src/asm/Memory.go @@ -56,6 +56,9 @@ func (mem *Memory) String() string { func (a *Assembler) Memory(mnemonic Mnemonic, address Memory) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &address, + Type: TypeMemory, + Index: Index(len(a.Param.Memory)), }) + + a.Param.Memory = append(a.Param.Memory, address) } diff --git a/src/asm/MemoryLabel.go b/src/asm/MemoryLabel.go index 3ea1b9b..1402330 100644 --- a/src/asm/MemoryLabel.go +++ b/src/asm/MemoryLabel.go @@ -15,9 +15,12 @@ func (data *MemoryLabel) String() string { func (a *Assembler) MemoryLabel(mnemonic Mnemonic, address Memory, label string) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &MemoryLabel{ - Address: address, - Label: label, - }, + Type: TypeMemoryLabel, + Index: Index(len(a.Param.MemoryLabel)), + }) + + a.Param.MemoryLabel = append(a.Param.MemoryLabel, MemoryLabel{ + Address: address, + Label: label, }) } diff --git a/src/asm/MemoryNumber.go b/src/asm/MemoryNumber.go index 47c39d5..453f1de 100644 --- a/src/asm/MemoryNumber.go +++ b/src/asm/MemoryNumber.go @@ -17,9 +17,12 @@ func (data *MemoryNumber) String() string { func (a *Assembler) MemoryNumber(mnemonic Mnemonic, address Memory, number int) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &MemoryNumber{ - Address: address, - Number: number, - }, + Type: TypeMemoryNumber, + Index: Index(len(a.Param.MemoryNumber)), + }) + + a.Param.MemoryNumber = append(a.Param.MemoryNumber, MemoryNumber{ + Address: address, + Number: number, }) } diff --git a/src/asm/MemoryRegister.go b/src/asm/MemoryRegister.go index 5e76305..09d21b5 100644 --- a/src/asm/MemoryRegister.go +++ b/src/asm/MemoryRegister.go @@ -21,9 +21,12 @@ func (data *MemoryRegister) String() string { func (a *Assembler) MemoryRegister(mnemonic Mnemonic, address Memory, register cpu.Register) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &MemoryRegister{ - Address: address, - Register: register, - }, + Type: TypeMemoryRegister, + Index: Index(len(a.Param.MemoryRegister)), + }) + + a.Param.MemoryRegister = append(a.Param.MemoryRegister, MemoryRegister{ + Address: address, + Register: register, }) } diff --git a/src/asm/Merge.go b/src/asm/Merge.go new file mode 100644 index 0000000..68a5e1e --- /dev/null +++ b/src/asm/Merge.go @@ -0,0 +1,46 @@ +package asm + +import "maps" + +// Merge combines the contents of this assembler with another one. +func (a *Assembler) Merge(b *Assembler) { + maps.Copy(a.Data, b.Data) + from := len(a.Instructions) + a.Instructions = append(a.Instructions, b.Instructions...) + + for i := from; i < len(a.Instructions); i++ { + switch a.Instructions[i].Type { + case TypeLabel: + a.Instructions[i].Index += Index(len(a.Param.Label)) + case TypeNumber: + a.Instructions[i].Index += Index(len(a.Param.Number)) + case TypeRegister: + a.Instructions[i].Index += Index(len(a.Param.Register)) + case TypeRegisterLabel: + a.Instructions[i].Index += Index(len(a.Param.RegisterLabel)) + case TypeRegisterNumber: + a.Instructions[i].Index += Index(len(a.Param.RegisterNumber)) + case TypeRegisterRegister: + a.Instructions[i].Index += Index(len(a.Param.RegisterRegister)) + case TypeMemory: + a.Instructions[i].Index += Index(len(a.Param.Memory)) + case TypeMemoryLabel: + a.Instructions[i].Index += Index(len(a.Param.MemoryLabel)) + case TypeMemoryNumber: + a.Instructions[i].Index += Index(len(a.Param.MemoryNumber)) + case TypeMemoryRegister: + a.Instructions[i].Index += Index(len(a.Param.MemoryRegister)) + } + } + + a.Param.Label = append(a.Param.Label, b.Param.Label...) + a.Param.Number = append(a.Param.Number, b.Param.Number...) + a.Param.Register = append(a.Param.Register, b.Param.Register...) + a.Param.RegisterLabel = append(a.Param.RegisterLabel, b.Param.RegisterLabel...) + a.Param.RegisterNumber = append(a.Param.RegisterNumber, b.Param.RegisterNumber...) + a.Param.RegisterRegister = append(a.Param.RegisterRegister, b.Param.RegisterRegister...) + a.Param.Memory = append(a.Param.Memory, b.Param.Memory...) + a.Param.MemoryLabel = append(a.Param.MemoryLabel, b.Param.MemoryLabel...) + a.Param.MemoryNumber = append(a.Param.MemoryNumber, b.Param.MemoryNumber...) + a.Param.MemoryRegister = append(a.Param.MemoryRegister, b.Param.MemoryRegister...) +} diff --git a/src/asm/Number.go b/src/asm/Number.go index 3f0271c..4e58004 100644 --- a/src/asm/Number.go +++ b/src/asm/Number.go @@ -18,8 +18,11 @@ func (data *Number) String() string { func (a *Assembler) Number(mnemonic Mnemonic, number int) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &Number{ - Number: number, - }, + Type: TypeNumber, + Index: Index(len(a.Param.Number)), + }) + + a.Param.Number = append(a.Param.Number, Number{ + Number: number, }) } diff --git a/src/asm/Param.go b/src/asm/Param.go new file mode 100644 index 0000000..efb5f1b --- /dev/null +++ b/src/asm/Param.go @@ -0,0 +1,15 @@ +package asm + +// Param stores all the parameters for the instructions. +type Param struct { + Label []Label + Number []Number + Register []Register + RegisterLabel []RegisterLabel + RegisterNumber []RegisterNumber + RegisterRegister []RegisterRegister + Memory []Memory + MemoryLabel []MemoryLabel + MemoryNumber []MemoryNumber + MemoryRegister []MemoryRegister +} diff --git a/src/asm/Register.go b/src/asm/Register.go index 91be5e0..fee9080 100644 --- a/src/asm/Register.go +++ b/src/asm/Register.go @@ -18,8 +18,11 @@ func (data *Register) String() string { func (a *Assembler) Register(mnemonic Mnemonic, register cpu.Register) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &Register{ - Register: register, - }, + Type: TypeRegister, + Index: Index(len(a.Param.Register)), + }) + + a.Param.Register = append(a.Param.Register, Register{ + Register: register, }) } diff --git a/src/asm/RegisterLabel.go b/src/asm/RegisterLabel.go index 402954b..f5cb956 100644 --- a/src/asm/RegisterLabel.go +++ b/src/asm/RegisterLabel.go @@ -18,12 +18,15 @@ func (data *RegisterLabel) String() string { } // RegisterLabel adds an instruction with a register and a label. -func (a *Assembler) RegisterLabel(mnemonic Mnemonic, reg cpu.Register, label string) { +func (a *Assembler) RegisterLabel(mnemonic Mnemonic, register cpu.Register, label string) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &RegisterLabel{ - Register: reg, - Label: label, - }, + Type: TypeRegisterLabel, + Index: Index(len(a.Param.RegisterLabel)), + }) + + a.Param.RegisterLabel = append(a.Param.RegisterLabel, RegisterLabel{ + Register: register, + Label: label, }) } diff --git a/src/asm/RegisterNumber.go b/src/asm/RegisterNumber.go index f12bb81..2a5f711 100644 --- a/src/asm/RegisterNumber.go +++ b/src/asm/RegisterNumber.go @@ -18,12 +18,15 @@ func (data *RegisterNumber) String() string { } // RegisterNumber adds an instruction with a register and a number. -func (a *Assembler) RegisterNumber(mnemonic Mnemonic, reg cpu.Register, number int) { +func (a *Assembler) RegisterNumber(mnemonic Mnemonic, register cpu.Register, number int) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &RegisterNumber{ - Register: reg, - Number: number, - }, + Type: TypeRegisterNumber, + Index: Index(len(a.Param.RegisterNumber)), + }) + + a.Param.RegisterNumber = append(a.Param.RegisterNumber, RegisterNumber{ + Register: register, + Number: number, }) } diff --git a/src/asm/RegisterRegister.go b/src/asm/RegisterRegister.go index dadafd1..f6dd7e5 100644 --- a/src/asm/RegisterRegister.go +++ b/src/asm/RegisterRegister.go @@ -21,9 +21,12 @@ func (data *RegisterRegister) String() string { func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right cpu.Register) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, - Data: &RegisterRegister{ - Destination: left, - Source: right, - }, + Type: TypeRegisterRegister, + Index: Index(len(a.Param.RegisterRegister)), + }) + + a.Param.RegisterRegister = append(a.Param.RegisterRegister, RegisterRegister{ + Destination: left, + Source: right, }) } diff --git a/src/asm/SetData.go b/src/asm/SetData.go new file mode 100644 index 0000000..257ded9 --- /dev/null +++ b/src/asm/SetData.go @@ -0,0 +1,12 @@ +package asm + +import "git.urbach.dev/cli/q/src/data" + +// SetData sets the data for the given label. +func (a *Assembler) SetData(label string, bytes []byte) { + if a.Data == nil { + a.Data = data.Data{} + } + + a.Data.Insert(label, bytes) +} diff --git a/src/asm/Type.go b/src/asm/Type.go new file mode 100644 index 0000000..25714f4 --- /dev/null +++ b/src/asm/Type.go @@ -0,0 +1,17 @@ +package asm + +type Type uint8 + +const ( + TypeNone Type = iota + TypeLabel + TypeNumber + TypeRegister + TypeRegisterLabel + TypeRegisterNumber + TypeRegisterRegister + TypeMemory + TypeMemoryLabel + TypeMemoryNumber + TypeMemoryRegister +) diff --git a/src/asm/bench_test.go b/src/asm/bench_test.go new file mode 100644 index 0000000..c060327 --- /dev/null +++ b/src/asm/bench_test.go @@ -0,0 +1,36 @@ +package asm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/asm" +) + +func BenchmarkMerge(b *testing.B) { + n := 100 + all := make([]*asm.Assembler, 0, n) + + for range n { + f := &asm.Assembler{} + f.Number(asm.PUSH, 1) + f.Number(asm.PUSH, 2) + f.Number(asm.PUSH, 3) + all = append(all, f) + } + + for b.Loop() { + final := asm.Assembler{} + + for _, f := range all { + final.Merge(f) + } + } +} + +func BenchmarkNumber(b *testing.B) { + a := asm.Assembler{} + + for b.Loop() { + a.Number(asm.PUSH, 42) + } +} diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index 3f35552..e2b935c 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -7,7 +7,7 @@ import ( ) // Finalize generates the final machine code. -func Finalize(a asm.Assembler, dlls dll.List) ([]byte, []byte) { +func Finalize(a *asm.Assembler, dlls dll.List) ([]byte, []byte) { data, dataLabels := a.Data.Finalize() if config.TargetOS == config.Windows && len(data) == 0 { @@ -15,6 +15,7 @@ func Finalize(a asm.Assembler, dlls dll.List) ([]byte, []byte) { } c := compiler{ + assembler: a, code: make([]byte, 0, len(a.Instructions)*8), codeLabels: make(map[string]Address, 32), codePointers: make([]*pointer, 0, len(a.Instructions)*8), diff --git a/src/asmc/call.go b/src/asmc/call.go index 3bdc411..c941bb6 100644 --- a/src/asmc/call.go +++ b/src/asmc/call.go @@ -8,8 +8,9 @@ import ( ) func (c *compiler) call(x asm.Instruction) { - switch data := x.Data.(type) { - case *asm.Label: + switch x.Type { + case asm.TypeLabel: + data := c.assembler.Param.Label[x.Index] c.code = x86.Call(c.code, 0x00_00_00_00) size := 4 @@ -32,10 +33,12 @@ func (c *compiler) call(x asm.Instruction) { c.codePointers = append(c.codePointers, pointer) - case *asm.Register: + case asm.TypeRegister: + data := c.assembler.Param.Register[x.Index] c.code = x86.CallRegister(c.code, data.Register) - case *asm.Memory: + case asm.TypeMemory: + data := c.assembler.Param.Memory[x.Index] c.code = x86.CallAtMemory(c.code, data.Base, data.Offset) } } diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 058f1f0..1c94725 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -13,8 +13,9 @@ import ( func (c *compiler) compileARM(x asm.Instruction) { switch x.Mnemonic { case asm.CALL: - switch data := x.Data.(type) { - case *asm.Label: + switch x.Type { + case asm.TypeLabel: + label := c.assembler.Param.Label[x.Index] position := Address(len(c.code)) c.append(arm.Call(0)) @@ -25,10 +26,10 @@ func (c *compiler) compileARM(x asm.Instruction) { } pointer.Resolve = func() Address { - destination, exists := c.codeLabels[data.Name] + destination, exists := c.codeLabels[label.Name] if !exists { - panic(fmt.Sprintf("unknown jump label %s", data.Name)) + panic(fmt.Sprintf("unknown jump label %s", label.Name)) } distance := (destination - position) / 4 @@ -39,13 +40,16 @@ func (c *compiler) compileARM(x asm.Instruction) { } case asm.LABEL: - c.codeLabels[x.Data.(*asm.Label).Name] = Address(len(c.code)) + label := c.assembler.Param.Label[x.Index] + c.codeLabels[label.Name] = Address(len(c.code)) c.append(0xa9be7bfd) c.append(0x910003fd) case asm.LOAD: - switch operands := x.Data.(type) { - case *asm.MemoryRegister: + switch x.Type { + case asm.TypeMemoryRegister: + operands := c.assembler.Param.MemoryRegister[x.Index] + if operands.Address.OffsetRegister == math.MaxUint8 { c.append(arm.LoadRegister(operands.Register, operands.Address.Base, int16(operands.Address.Offset), operands.Address.Length)) } else { @@ -55,14 +59,17 @@ func (c *compiler) compileARM(x asm.Instruction) { } case asm.MOVE: - switch operands := x.Data.(type) { - case *asm.RegisterRegister: + switch x.Type { + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.append(arm.MoveRegisterRegister(operands.Destination, operands.Source)) - case *asm.RegisterNumber: + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.append(arm.MoveRegisterNumber(operands.Register, operands.Number)) - case *asm.RegisterLabel: + case asm.TypeRegisterLabel: + operands := c.assembler.Param.RegisterLabel[x.Index] position := Address(len(c.code)) c.append(arm.LoadAddress(operands.Register, 0)) diff --git a/src/asmc/compileX86.go b/src/asmc/compileX86.go index 13ee381..f873cdd 100644 --- a/src/asmc/compileX86.go +++ b/src/asmc/compileX86.go @@ -8,40 +8,49 @@ import ( func (c *compiler) compileX86(x asm.Instruction) { switch x.Mnemonic { case asm.ADD: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.AddRegisterNumber(c.code, operands.Register, operands.Number) - case *asm.RegisterRegister: + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.code = x86.AddRegisterRegister(c.code, operands.Destination, operands.Source) } case asm.AND: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.AndRegisterNumber(c.code, operands.Register, operands.Number) - case *asm.RegisterRegister: + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.code = x86.AndRegisterRegister(c.code, operands.Destination, operands.Source) } case asm.SUB: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.SubRegisterNumber(c.code, operands.Register, operands.Number) - case *asm.RegisterRegister: + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.code = x86.SubRegisterRegister(c.code, operands.Destination, operands.Source) } case asm.MUL: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.MulRegisterNumber(c.code, operands.Register, operands.Number) - case *asm.RegisterRegister: + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.code = x86.MulRegisterRegister(c.code, operands.Destination, operands.Source) } case asm.DIV: - switch operands := x.Data.(type) { - case *asm.RegisterRegister: + switch x.Type { + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] if operands.Destination != x86.RAX { c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) } @@ -55,8 +64,9 @@ func (c *compiler) compileX86(x asm.Instruction) { } case asm.MODULO: - switch operands := x.Data.(type) { - case *asm.RegisterRegister: + switch x.Type { + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] if operands.Destination != x86.RAX { c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) } @@ -76,10 +86,12 @@ func (c *compiler) compileX86(x asm.Instruction) { return case asm.COMPARE: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.CompareRegisterNumber(c.code, operands.Register, operands.Number) - case *asm.RegisterRegister: + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.code = x86.CompareRegisterRegister(c.code, operands.Destination, operands.Source) } @@ -90,7 +102,7 @@ func (c *compiler) compileX86(x asm.Instruction) { c.jump(x) case asm.LABEL: - label := x.Data.(*asm.Label) + label := c.assembler.Param.Label[x.Index] c.codeLabels[label.Name] = Address(len(c.code)) case asm.LOAD: @@ -100,30 +112,36 @@ func (c *compiler) compileX86(x asm.Instruction) { c.move(x) case asm.NEGATE: - switch operands := x.Data.(type) { - case *asm.Register: + switch x.Type { + case asm.TypeRegister: + operands := c.assembler.Param.Register[x.Index] c.code = x86.NegateRegister(c.code, operands.Register) } case asm.OR: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.OrRegisterNumber(c.code, operands.Register, operands.Number) - case *asm.RegisterRegister: + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.code = x86.OrRegisterRegister(c.code, operands.Destination, operands.Source) } case asm.POP: - switch operands := x.Data.(type) { - case *asm.Register: + switch x.Type { + case asm.TypeRegister: + operands := c.assembler.Param.Register[x.Index] c.code = x86.PopRegister(c.code, operands.Register) } case asm.PUSH: - switch operands := x.Data.(type) { - case *asm.Number: + switch x.Type { + case asm.TypeNumber: + operands := c.assembler.Param.Number[x.Index] c.code = x86.PushNumber(c.code, int32(operands.Number)) - case *asm.Register: + case asm.TypeRegister: + operands := c.assembler.Param.Register[x.Index] c.code = x86.PushRegister(c.code, operands.Register) } @@ -131,14 +149,16 @@ func (c *compiler) compileX86(x asm.Instruction) { c.code = x86.Return(c.code) case asm.SHIFTL: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.ShiftLeftNumber(c.code, operands.Register, byte(operands.Number)&0b111111) } case asm.SHIFTRS: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.ShiftRightSignedNumber(c.code, operands.Register, byte(operands.Number)&0b111111) } @@ -149,10 +169,12 @@ func (c *compiler) compileX86(x asm.Instruction) { c.code = x86.Syscall(c.code) case asm.XOR: - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.XorRegisterNumber(c.code, operands.Register, operands.Number) - case *asm.RegisterRegister: + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.code = x86.XorRegisterRegister(c.code, operands.Destination, operands.Source) } diff --git a/src/asmc/compiler.go b/src/asmc/compiler.go index 056704b..890b2d6 100644 --- a/src/asmc/compiler.go +++ b/src/asmc/compiler.go @@ -1,8 +1,12 @@ package asmc -import "git.urbach.dev/cli/q/src/dll" +import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/dll" +) type compiler struct { + assembler *asm.Assembler code []byte data []byte codeLabels map[string]Address diff --git a/src/asmc/dllCall.go b/src/asmc/dllCall.go index 22492e8..203fbc1 100644 --- a/src/asmc/dllCall.go +++ b/src/asmc/dllCall.go @@ -8,10 +8,10 @@ import ( ) func (c *compiler) dllCall(x asm.Instruction) { + label := c.assembler.Param.Label[x.Index] c.code = x86.CallAt(c.code, 0x00_00_00_00) next := Address(len(c.code)) position := next - 4 - label := x.Data.(*asm.Label) pointer := &pointer{ Position: Address(position), diff --git a/src/asmc/jump.go b/src/asmc/jump.go index 9964c8b..2b2f159 100644 --- a/src/asmc/jump.go +++ b/src/asmc/jump.go @@ -25,8 +25,8 @@ func (c *compiler) jump(x asm.Instruction) { c.code = x86.Jump8(c.code, 0x00) } + label := c.assembler.Param.Label[x.Index] size := 1 - label := x.Data.(*asm.Label) pointer := &pointer{ Position: Address(len(c.code) - size), diff --git a/src/asmc/load.go b/src/asmc/load.go index 8b28fa8..d6691f7 100644 --- a/src/asmc/load.go +++ b/src/asmc/load.go @@ -8,8 +8,10 @@ import ( ) func (c *compiler) load(x asm.Instruction) { - switch operands := x.Data.(type) { - case *asm.MemoryRegister: + switch x.Type { + case asm.TypeMemoryRegister: + operands := c.assembler.Param.MemoryRegister[x.Index] + if operands.Address.OffsetRegister == math.MaxUint8 { c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Base, operands.Address.Offset, operands.Address.Length) } else { diff --git a/src/asmc/move.go b/src/asmc/move.go index c3687fb..256ede6 100644 --- a/src/asmc/move.go +++ b/src/asmc/move.go @@ -8,14 +8,17 @@ import ( ) func (c *compiler) move(x asm.Instruction) { - switch operands := x.Data.(type) { - case *asm.RegisterNumber: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.MoveRegisterNumber(c.code, operands.Register, operands.Number) - case *asm.RegisterRegister: + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] c.code = x86.MoveRegisterRegister(c.code, operands.Destination, operands.Source) - case *asm.RegisterLabel: + case asm.TypeRegisterLabel: + operands := c.assembler.Param.RegisterLabel[x.Index] start := Address(len(c.code)) c.code = x86.LoadAddress(c.code, operands.Register, 0x00_00_00_00) end := Address(len(c.code)) diff --git a/src/asmc/store.go b/src/asmc/store.go index aa81960..f67b9b0 100644 --- a/src/asmc/store.go +++ b/src/asmc/store.go @@ -9,14 +9,17 @@ import ( ) func (c *compiler) store(x asm.Instruction) { - switch operands := x.Data.(type) { - case *asm.MemoryNumber: + switch x.Type { + case asm.TypeMemoryNumber: + operands := c.assembler.Param.MemoryNumber[x.Index] + if operands.Address.OffsetRegister == math.MaxUint8 { c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) } else { c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) } - case *asm.MemoryLabel: + case asm.TypeMemoryLabel: + operands := c.assembler.Param.MemoryLabel[x.Index] start := len(c.code) if operands.Address.OffsetRegister == math.MaxUint8 { @@ -27,14 +30,13 @@ func (c *compiler) store(x asm.Instruction) { size := 4 opSize := len(c.code) - size - start - memLabel := x.Data.(*asm.MemoryLabel) c.codePointers = append(c.codePointers, &pointer{ Position: Address(len(c.code) - size), OpSize: uint8(opSize), Size: uint8(size), Resolve: func() Address { - destination, exists := c.codeLabels[memLabel.Label] + destination, exists := c.codeLabels[operands.Label] if !exists { panic("unknown label") @@ -43,7 +45,9 @@ func (c *compiler) store(x asm.Instruction) { return config.BaseAddress + c.codeStart + destination }, }) - case *asm.MemoryRegister: + case asm.TypeMemoryRegister: + operands := c.assembler.Param.MemoryRegister[x.Index] + if operands.Address.OffsetRegister == math.MaxUint8 { c.code = x86.StoreRegister(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) } else { diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 6fd88c1..cffb1d6 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -105,8 +105,7 @@ func Compile(constants <-chan *core.Constant, files <-chan *fs.File, functions < return result, function.Err } - result.InstructionCount += len(function.Assembler.Instructions) - result.DataCount += len(function.Assembler.Data) + result.Count(function) } // Check for unused imports in all files diff --git a/src/compiler/Result.go b/src/compiler/Result.go index b9bbb7b..04634ba 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -7,13 +7,12 @@ import ( // Result contains everything we need to write an executable file to disk. type Result struct { - Init *core.Function - Main *core.Function - Functions map[string]*core.Function - Traversed map[*core.Function]bool - Code []byte - Data []byte - DLLs dll.List - InstructionCount int - DataCount int + Init *core.Function + Main *core.Function + Functions map[string]*core.Function + Traversed map[*core.Function]bool + Code []byte + Data []byte + DLLs dll.List + Statistics } diff --git a/src/compiler/Statistics.go b/src/compiler/Statistics.go new file mode 100644 index 0000000..2c2cbbe --- /dev/null +++ b/src/compiler/Statistics.go @@ -0,0 +1,35 @@ +package compiler + +import "git.urbach.dev/cli/q/src/core" + +// Statistics contains counters for all functions in the build. +type Statistics struct { + InstructionCount int + DataCount int + LabelCount int + NumberCount int + RegisterCount int + RegisterLabelCount int + RegisterNumberCount int + RegisterRegisterCount int + MemoryCount int + MemoryLabelCount int + MemoryNumberCount int + MemoryRegisterCount int +} + +// Count adds statistics for this function. +func (s *Statistics) Count(function *core.Function) { + s.InstructionCount += len(function.Assembler.Instructions) + s.DataCount += len(function.Assembler.Data) + s.LabelCount += len(function.Assembler.Param.Label) + s.NumberCount += len(function.Assembler.Param.Number) + s.RegisterCount += len(function.Assembler.Param.Register) + s.RegisterLabelCount += len(function.Assembler.Param.RegisterLabel) + s.RegisterNumberCount += len(function.Assembler.Param.RegisterNumber) + s.RegisterRegisterCount += len(function.Assembler.Param.RegisterRegister) + s.MemoryCount += len(function.Assembler.Param.Memory) + s.MemoryLabelCount += len(function.Assembler.Param.MemoryLabel) + s.MemoryNumberCount += len(function.Assembler.Param.MemoryNumber) + s.MemoryRegisterCount += len(function.Assembler.Param.MemoryRegister) +} diff --git a/src/compiler/finalize.go b/src/compiler/finalize.go index 6437913..45d6c54 100644 --- a/src/compiler/finalize.go +++ b/src/compiler/finalize.go @@ -17,6 +17,18 @@ func (r *Result) finalize() { final := asm.Assembler{ Instructions: make([]asm.Instruction, 0, r.InstructionCount+8), Data: make(map[string][]byte, r.DataCount), + Param: asm.Param{ + Label: make([]asm.Label, 0, r.LabelCount), + Number: make([]asm.Number, 0, r.NumberCount), + Register: make([]asm.Register, 0, r.RegisterCount), + RegisterLabel: make([]asm.RegisterLabel, 0, r.RegisterLabelCount), + RegisterNumber: make([]asm.RegisterNumber, 0, r.RegisterNumberCount), + RegisterRegister: make([]asm.RegisterRegister, 0, r.RegisterRegisterCount), + Memory: make([]asm.Memory, 0, r.MemoryCount), + MemoryLabel: make([]asm.MemoryLabel, 0, r.MemoryLabelCount), + MemoryNumber: make([]asm.MemoryNumber, 0, r.MemoryNumberCount), + MemoryRegister: make([]asm.MemoryRegister, 0, r.MemoryRegisterCount), + }, } r.Traversed = make(map[*core.Function]bool, len(r.Functions)) @@ -24,7 +36,7 @@ func (r *Result) finalize() { // This will place the init function immediately after the entry point // and also add everything the init function calls recursively. r.eachFunction(r.Init, r.Traversed, func(f *core.Function) { - final.Merge(f.Assembler) + final.Merge(&f.Assembler) for _, library := range f.DLLs { for _, fn := range library.Functions { @@ -50,5 +62,5 @@ func (r *Result) finalize() { final.DLLCall("kernel32.ExitProcess") } - r.Code, r.Data = asmc.Finalize(final, r.DLLs) + r.Code, r.Data = asmc.Finalize(&final, r.DLLs) } diff --git a/src/core/PrintInstructions.go b/src/core/PrintInstructions.go index cc81953..a5d5f4c 100644 --- a/src/core/PrintInstructions.go +++ b/src/core/PrintInstructions.go @@ -26,19 +26,16 @@ func (f *Function) PrintInstructions() { switch x.Mnemonic { case asm.LABEL: - ansi.Yellow.Printf("%-44s", x.Data.String()+":") + label := f.Assembler.Param.Label[x.Index] + ansi.Yellow.Printf("%-44s", label.String()+":") case asm.COMMENT: - ansi.Dim.Printf("%-44s", x.Data.String()) + label := f.Assembler.Param.Label[x.Index] + ansi.Dim.Printf("%-44s", label.String()) default: ansi.Green.Printf("%-12s", x.Mnemonic.String()) - - if x.Data != nil { - fmt.Printf("%-32s", x.Data.String()) - } else { - fmt.Printf("%-32s", "") - } + fmt.Printf("%-32s", x.DataString(&f.Assembler)) } registers := bytes.Buffer{} From aa40af44f6693314a453d72c2a140405afb8051b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 11 Mar 2025 14:31:06 +0100 Subject: [PATCH 0915/1012] Updated dependencies --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8245b16..ebfb164 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.24 require ( git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf - git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d + git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12 ) require golang.org/x/sys v0.31.0 // indirect diff --git a/go.sum b/go.sum index 38c7851..452b14f 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf h1:BQWa5GKNUsA5CSUa/+UlFWYCEVe3IDDKRbVqBLK0mAE= git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf/go.mod h1:y9jGII9JFiF1HNIju0u87OyPCt82xKCtqnAFyEreCDo= -git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d h1:j1ARCrjUYE/c1STH/s6UYGQoOXAkxXll4AFhTb8P2GE= -git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d/go.mod h1:UE8tQTrlWeVPKhfPZ9G5QrDFRhL9EoPazcDgfgd5Xsk= +git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12 h1:DML0/oUE0lzX8wq/b2+JizZs/iYxeqxPCwT97LFhW+A= +git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12/go.mod h1:s6wrC+nGE0YMz9K3BBjHoGCKkDZTUKnGbL0BqgzZkC4= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= From 03c8dfa34c59f4bfc15e937d1c8e34d574084bb0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 11 Mar 2025 14:31:06 +0100 Subject: [PATCH 0916/1012] Updated dependencies --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8245b16..ebfb164 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.24 require ( git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf - git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d + git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12 ) require golang.org/x/sys v0.31.0 // indirect diff --git a/go.sum b/go.sum index 38c7851..452b14f 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf h1:BQWa5GKNUsA5CSUa/+UlFWYCEVe3IDDKRbVqBLK0mAE= git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf/go.mod h1:y9jGII9JFiF1HNIju0u87OyPCt82xKCtqnAFyEreCDo= -git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d h1:j1ARCrjUYE/c1STH/s6UYGQoOXAkxXll4AFhTb8P2GE= -git.urbach.dev/go/color v0.0.0-20250225153715-1b0b4cb28f7d/go.mod h1:UE8tQTrlWeVPKhfPZ9G5QrDFRhL9EoPazcDgfgd5Xsk= +git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12 h1:DML0/oUE0lzX8wq/b2+JizZs/iYxeqxPCwT97LFhW+A= +git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12/go.mod h1:s6wrC+nGE0YMz9K3BBjHoGCKkDZTUKnGbL0BqgzZkC4= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= From 6e738d2c9f99e62fdcff2c45bd467189b43cb7d4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Mar 2025 12:29:55 +0100 Subject: [PATCH 0917/1012] Implemented arm64 instructions: ldp and stp --- src/arm/Add.go | 9 +++++++++ src/arm/LoadPair.go | 12 ++++++++++++ src/arm/LoadPair_test.go | 28 ++++++++++++++++++++++++++++ src/arm/Move.go | 4 ++++ src/arm/Move_test.go | 2 ++ src/arm/StorePair.go | 14 ++++++++++++++ src/arm/StorePair_test.go | 28 ++++++++++++++++++++++++++++ src/asmc/compileARM.go | 7 +++---- 8 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 src/arm/Add.go create mode 100644 src/arm/LoadPair.go create mode 100644 src/arm/LoadPair_test.go create mode 100644 src/arm/StorePair.go create mode 100644 src/arm/StorePair_test.go diff --git a/src/arm/Add.go b/src/arm/Add.go new file mode 100644 index 0000000..c97afa8 --- /dev/null +++ b/src/arm/Add.go @@ -0,0 +1,9 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// AddRegisterNumber adds a number to a register +func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { + number &= 0xFFF + return 0b100100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) +} diff --git a/src/arm/LoadPair.go b/src/arm/LoadPair.go new file mode 100644 index 0000000..12cc72c --- /dev/null +++ b/src/arm/LoadPair.go @@ -0,0 +1,12 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// LoadPair calculates an address from a base register value and an immediate offset, +// loads two 64-bit doublewords from memory, and writes them to two registers. +// This is the post-index version of the instruction so the offset is applied to the base register after the memory access. +func LoadPair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { + offset /= 8 + offset &= 0b1111111 + return 0b1010100011<<22 | (uint32(offset) << 15) | (uint32(reg2) << 10) | (uint32(base) << 5) | uint32(reg1) +} diff --git a/src/arm/LoadPair_test.go b/src/arm/LoadPair_test.go new file mode 100644 index 0000000..97edbdb --- /dev/null +++ b/src/arm/LoadPair_test.go @@ -0,0 +1,28 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestLoadPair(t *testing.T) { + usagePatterns := []struct { + Reg1 cpu.Register + Reg2 cpu.Register + Base cpu.Register + Offset int + Code uint32 + }{ + {arm.FP, arm.LR, arm.SP, 32, 0xA8C27BFD}, + {arm.FP, arm.LR, arm.SP, 16, 0xA8C17BFD}, + } + + for _, pattern := range usagePatterns { + t.Logf("ldp %s, %s, [%s], #%d", pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) + code := arm.LoadPair(pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Move.go b/src/arm/Move.go index e61ff8f..b48bd9d 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -6,6 +6,10 @@ import ( // MoveRegisterRegister copies a register to another register. func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 { + if source == SP || destination == SP { + return AddRegisterNumber(destination, source, 0) + } + return 0b10101010<<24 | uint32(source)<<16 | 0b11111<<5 | uint32(destination) } diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go index 9811e37..69f524e 100644 --- a/src/arm/Move_test.go +++ b/src/arm/Move_test.go @@ -16,6 +16,8 @@ func TestMoveRegisterRegister(t *testing.T) { }{ {arm.X0, arm.X1, 0xAA0103E0}, {arm.X1, arm.X0, 0xAA0003E1}, + {arm.FP, arm.SP, 0x910003FD}, + {arm.SP, arm.FP, 0x910003BF}, } for _, pattern := range usagePatterns { diff --git a/src/arm/StorePair.go b/src/arm/StorePair.go new file mode 100644 index 0000000..f31cce9 --- /dev/null +++ b/src/arm/StorePair.go @@ -0,0 +1,14 @@ +package arm + +import ( + "git.urbach.dev/cli/q/src/cpu" +) + +// StorePair calculates an address from a base register value and an immediate offset multiplied by 8, +// and stores the values of two registers to the calculated address. +// This is the pre-index version of the instruction so the offset is applied to the base register before the memory access. +func StorePair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { + offset /= 8 + offset &= 0b1111111 + return 0b1010100110<<22 | uint32(offset)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) +} diff --git a/src/arm/StorePair_test.go b/src/arm/StorePair_test.go new file mode 100644 index 0000000..eefdfbd --- /dev/null +++ b/src/arm/StorePair_test.go @@ -0,0 +1,28 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestStorePair(t *testing.T) { + usagePatterns := []struct { + Reg1 cpu.Register + Reg2 cpu.Register + Base cpu.Register + Offset int + Code uint32 + }{ + {arm.FP, arm.LR, arm.SP, -32, 0xA9BE7BFD}, + {arm.FP, arm.LR, arm.SP, -16, 0xA9BF7BFD}, + } + + for _, pattern := range usagePatterns { + t.Logf("stp %s, %s, [%s, #%d]!", pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) + code := arm.StorePair(pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 1c94725..fa4b99f 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -42,8 +42,8 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.LABEL: label := c.assembler.Param.Label[x.Index] c.codeLabels[label.Name] = Address(len(c.code)) - c.append(0xa9be7bfd) - c.append(0x910003fd) + c.append(arm.StorePair(arm.FP, arm.LR, arm.SP, -16)) + c.append(arm.MoveRegisterRegister(arm.FP, arm.SP)) case asm.LOAD: switch x.Type { @@ -96,8 +96,7 @@ func (c *compiler) compileARM(x asm.Instruction) { } case asm.RETURN: - c.append(0xa8c27bfd) - c.append(0xd65f03c0) + c.append(arm.LoadPair(arm.FP, arm.LR, arm.SP, 16)) c.append(arm.Return()) case asm.SYSCALL: From 450e634d79102f96a3e909b64e1fb18131c0a78b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Mar 2025 12:29:55 +0100 Subject: [PATCH 0918/1012] Implemented arm64 instructions: ldp and stp --- src/arm/Add.go | 9 +++++++++ src/arm/LoadPair.go | 12 ++++++++++++ src/arm/LoadPair_test.go | 28 ++++++++++++++++++++++++++++ src/arm/Move.go | 4 ++++ src/arm/Move_test.go | 2 ++ src/arm/StorePair.go | 14 ++++++++++++++ src/arm/StorePair_test.go | 28 ++++++++++++++++++++++++++++ src/asmc/compileARM.go | 7 +++---- 8 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 src/arm/Add.go create mode 100644 src/arm/LoadPair.go create mode 100644 src/arm/LoadPair_test.go create mode 100644 src/arm/StorePair.go create mode 100644 src/arm/StorePair_test.go diff --git a/src/arm/Add.go b/src/arm/Add.go new file mode 100644 index 0000000..c97afa8 --- /dev/null +++ b/src/arm/Add.go @@ -0,0 +1,9 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// AddRegisterNumber adds a number to a register +func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { + number &= 0xFFF + return 0b100100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) +} diff --git a/src/arm/LoadPair.go b/src/arm/LoadPair.go new file mode 100644 index 0000000..12cc72c --- /dev/null +++ b/src/arm/LoadPair.go @@ -0,0 +1,12 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// LoadPair calculates an address from a base register value and an immediate offset, +// loads two 64-bit doublewords from memory, and writes them to two registers. +// This is the post-index version of the instruction so the offset is applied to the base register after the memory access. +func LoadPair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { + offset /= 8 + offset &= 0b1111111 + return 0b1010100011<<22 | (uint32(offset) << 15) | (uint32(reg2) << 10) | (uint32(base) << 5) | uint32(reg1) +} diff --git a/src/arm/LoadPair_test.go b/src/arm/LoadPair_test.go new file mode 100644 index 0000000..97edbdb --- /dev/null +++ b/src/arm/LoadPair_test.go @@ -0,0 +1,28 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestLoadPair(t *testing.T) { + usagePatterns := []struct { + Reg1 cpu.Register + Reg2 cpu.Register + Base cpu.Register + Offset int + Code uint32 + }{ + {arm.FP, arm.LR, arm.SP, 32, 0xA8C27BFD}, + {arm.FP, arm.LR, arm.SP, 16, 0xA8C17BFD}, + } + + for _, pattern := range usagePatterns { + t.Logf("ldp %s, %s, [%s], #%d", pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) + code := arm.LoadPair(pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Move.go b/src/arm/Move.go index e61ff8f..b48bd9d 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -6,6 +6,10 @@ import ( // MoveRegisterRegister copies a register to another register. func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 { + if source == SP || destination == SP { + return AddRegisterNumber(destination, source, 0) + } + return 0b10101010<<24 | uint32(source)<<16 | 0b11111<<5 | uint32(destination) } diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go index 9811e37..69f524e 100644 --- a/src/arm/Move_test.go +++ b/src/arm/Move_test.go @@ -16,6 +16,8 @@ func TestMoveRegisterRegister(t *testing.T) { }{ {arm.X0, arm.X1, 0xAA0103E0}, {arm.X1, arm.X0, 0xAA0003E1}, + {arm.FP, arm.SP, 0x910003FD}, + {arm.SP, arm.FP, 0x910003BF}, } for _, pattern := range usagePatterns { diff --git a/src/arm/StorePair.go b/src/arm/StorePair.go new file mode 100644 index 0000000..f31cce9 --- /dev/null +++ b/src/arm/StorePair.go @@ -0,0 +1,14 @@ +package arm + +import ( + "git.urbach.dev/cli/q/src/cpu" +) + +// StorePair calculates an address from a base register value and an immediate offset multiplied by 8, +// and stores the values of two registers to the calculated address. +// This is the pre-index version of the instruction so the offset is applied to the base register before the memory access. +func StorePair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { + offset /= 8 + offset &= 0b1111111 + return 0b1010100110<<22 | uint32(offset)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) +} diff --git a/src/arm/StorePair_test.go b/src/arm/StorePair_test.go new file mode 100644 index 0000000..eefdfbd --- /dev/null +++ b/src/arm/StorePair_test.go @@ -0,0 +1,28 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestStorePair(t *testing.T) { + usagePatterns := []struct { + Reg1 cpu.Register + Reg2 cpu.Register + Base cpu.Register + Offset int + Code uint32 + }{ + {arm.FP, arm.LR, arm.SP, -32, 0xA9BE7BFD}, + {arm.FP, arm.LR, arm.SP, -16, 0xA9BF7BFD}, + } + + for _, pattern := range usagePatterns { + t.Logf("stp %s, %s, [%s, #%d]!", pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) + code := arm.StorePair(pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 1c94725..fa4b99f 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -42,8 +42,8 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.LABEL: label := c.assembler.Param.Label[x.Index] c.codeLabels[label.Name] = Address(len(c.code)) - c.append(0xa9be7bfd) - c.append(0x910003fd) + c.append(arm.StorePair(arm.FP, arm.LR, arm.SP, -16)) + c.append(arm.MoveRegisterRegister(arm.FP, arm.SP)) case asm.LOAD: switch x.Type { @@ -96,8 +96,7 @@ func (c *compiler) compileARM(x asm.Instruction) { } case asm.RETURN: - c.append(0xa8c27bfd) - c.append(0xd65f03c0) + c.append(arm.LoadPair(arm.FP, arm.LR, arm.SP, 16)) c.append(arm.Return()) case asm.SYSCALL: From 296fc37265ca8eeb552b6ca8537cec12a070406e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Mar 2025 23:15:43 +0100 Subject: [PATCH 0919/1012] Added wasm target --- src/cli/Build.go | 4 ++++ src/compiler/Write.go | 3 +++ src/config/arch.go | 1 + src/config/config.go | 4 ++++ src/config/os.go | 1 + src/core/NewFunction.go | 11 +++++++---- src/wasm/CPU.go | 11 +++++++++++ src/wasm/WASM.go | 13 +++++++++++++ 8 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 src/wasm/CPU.go create mode 100644 src/wasm/WASM.go diff --git a/src/cli/Build.go b/src/cli/Build.go index 087d6c1..2941400 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -56,6 +56,8 @@ func buildExecutable(args []string) (*build.Build, error) { config.SetTargetArch(config.ARM) case "x86": config.SetTargetArch(config.X86) + case "wasm": + config.SetTargetArch(config.WASM) default: return b, &InvalidValueError{Value: args[i], Parameter: "arch"} } @@ -72,6 +74,8 @@ func buildExecutable(args []string) (*build.Build, error) { config.TargetOS = config.Linux case "mac": config.TargetOS = config.Mac + case "web": + config.TargetOS = config.Web case "windows": config.TargetOS = config.Windows default: diff --git a/src/compiler/Write.go b/src/compiler/Write.go index bb14745..cc1981f 100644 --- a/src/compiler/Write.go +++ b/src/compiler/Write.go @@ -9,6 +9,7 @@ import ( "git.urbach.dev/cli/q/src/elf" "git.urbach.dev/cli/q/src/macho" "git.urbach.dev/cli/q/src/pe" + "git.urbach.dev/cli/q/src/wasm" ) // Write writes the executable to the given writer. @@ -25,6 +26,8 @@ func write(writer io.Writer, code []byte, data []byte, dlls dll.List) error { elf.Write(buffer, code, data) case config.Mac: macho.Write(buffer, code, data) + case config.Web: + wasm.Write(buffer, code, data) case config.Windows: pe.Write(buffer, code, data, dlls) } diff --git a/src/config/arch.go b/src/config/arch.go index 33eacf7..7f5064f 100644 --- a/src/config/arch.go +++ b/src/config/arch.go @@ -5,5 +5,6 @@ type Arch uint8 const ( UnknownArch Arch = iota ARM + WASM X86 ) diff --git a/src/config/config.go b/src/config/config.go index 74a3a5f..4f2b8f6 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -26,6 +26,8 @@ func Reset() { SetTargetArch(X86) case "arm64": SetTargetArch(ARM) + case "wasm": + SetTargetArch(WASM) default: SetTargetArch(UnknownArch) } @@ -35,6 +37,8 @@ func Reset() { TargetOS = Linux case "darwin": TargetOS = Mac + case "js": + TargetOS = Web case "windows": TargetOS = Windows default: diff --git a/src/config/os.go b/src/config/os.go index 48ed743..de8c011 100644 --- a/src/config/os.go +++ b/src/config/os.go @@ -6,5 +6,6 @@ const ( UnknownOS OS = iota Linux Mac + Web Windows ) diff --git a/src/core/NewFunction.go b/src/core/NewFunction.go index abb26ab..975ffa2 100644 --- a/src/core/NewFunction.go +++ b/src/core/NewFunction.go @@ -8,18 +8,21 @@ import ( "git.urbach.dev/cli/q/src/fs" "git.urbach.dev/cli/q/src/register" "git.urbach.dev/cli/q/src/scope" + "git.urbach.dev/cli/q/src/wasm" "git.urbach.dev/cli/q/src/x86" ) // NewFunction creates a new function. func NewFunction(pkg string, name string, file *fs.File) *Function { - var cpu *cpu.CPU + var c *cpu.CPU switch config.TargetArch { case config.ARM: - cpu = &arm.CPU + c = &arm.CPU + case config.WASM: + c = &wasm.CPU case config.X86: - cpu = &x86.CPU + c = &x86.CPU } return &Function{ @@ -34,7 +37,7 @@ func NewFunction(pkg string, name string, file *fs.File) *Function { Stack: scope.Stack{ Scopes: []*scope.Scope{{}}, }, - CPU: cpu, + CPU: c, }, } } diff --git a/src/wasm/CPU.go b/src/wasm/CPU.go new file mode 100644 index 0000000..24935c5 --- /dev/null +++ b/src/wasm/CPU.go @@ -0,0 +1,11 @@ +package wasm + +import "git.urbach.dev/cli/q/src/cpu" + +var ( + CPU = cpu.CPU{ + Input: []cpu.Register{0, 1, 2, 3, 4, 5}, + Output: []cpu.Register{0, 1, 2, 3, 4, 5}, + NumRegisters: 0, + } +) diff --git a/src/wasm/WASM.go b/src/wasm/WASM.go new file mode 100644 index 0000000..d388043 --- /dev/null +++ b/src/wasm/WASM.go @@ -0,0 +1,13 @@ +package wasm + +import ( + "io" +) + +const HeaderEnd = 8 + +// Write writes the WASM format to the given writer. +func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { + writer.Write([]byte{0x00, 0x61, 0x73, 0x6D}) + writer.Write([]byte{0x01, 0x00, 0x00, 0x00}) +} From dc664dbf5a453d5962e6ea988f05414d4fc570de Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Mar 2025 23:15:43 +0100 Subject: [PATCH 0920/1012] Added wasm target --- src/cli/Build.go | 4 ++++ src/compiler/Write.go | 3 +++ src/config/arch.go | 1 + src/config/config.go | 4 ++++ src/config/os.go | 1 + src/core/NewFunction.go | 11 +++++++---- src/wasm/CPU.go | 11 +++++++++++ src/wasm/WASM.go | 13 +++++++++++++ 8 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 src/wasm/CPU.go create mode 100644 src/wasm/WASM.go diff --git a/src/cli/Build.go b/src/cli/Build.go index 087d6c1..2941400 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -56,6 +56,8 @@ func buildExecutable(args []string) (*build.Build, error) { config.SetTargetArch(config.ARM) case "x86": config.SetTargetArch(config.X86) + case "wasm": + config.SetTargetArch(config.WASM) default: return b, &InvalidValueError{Value: args[i], Parameter: "arch"} } @@ -72,6 +74,8 @@ func buildExecutable(args []string) (*build.Build, error) { config.TargetOS = config.Linux case "mac": config.TargetOS = config.Mac + case "web": + config.TargetOS = config.Web case "windows": config.TargetOS = config.Windows default: diff --git a/src/compiler/Write.go b/src/compiler/Write.go index bb14745..cc1981f 100644 --- a/src/compiler/Write.go +++ b/src/compiler/Write.go @@ -9,6 +9,7 @@ import ( "git.urbach.dev/cli/q/src/elf" "git.urbach.dev/cli/q/src/macho" "git.urbach.dev/cli/q/src/pe" + "git.urbach.dev/cli/q/src/wasm" ) // Write writes the executable to the given writer. @@ -25,6 +26,8 @@ func write(writer io.Writer, code []byte, data []byte, dlls dll.List) error { elf.Write(buffer, code, data) case config.Mac: macho.Write(buffer, code, data) + case config.Web: + wasm.Write(buffer, code, data) case config.Windows: pe.Write(buffer, code, data, dlls) } diff --git a/src/config/arch.go b/src/config/arch.go index 33eacf7..7f5064f 100644 --- a/src/config/arch.go +++ b/src/config/arch.go @@ -5,5 +5,6 @@ type Arch uint8 const ( UnknownArch Arch = iota ARM + WASM X86 ) diff --git a/src/config/config.go b/src/config/config.go index 74a3a5f..4f2b8f6 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -26,6 +26,8 @@ func Reset() { SetTargetArch(X86) case "arm64": SetTargetArch(ARM) + case "wasm": + SetTargetArch(WASM) default: SetTargetArch(UnknownArch) } @@ -35,6 +37,8 @@ func Reset() { TargetOS = Linux case "darwin": TargetOS = Mac + case "js": + TargetOS = Web case "windows": TargetOS = Windows default: diff --git a/src/config/os.go b/src/config/os.go index 48ed743..de8c011 100644 --- a/src/config/os.go +++ b/src/config/os.go @@ -6,5 +6,6 @@ const ( UnknownOS OS = iota Linux Mac + Web Windows ) diff --git a/src/core/NewFunction.go b/src/core/NewFunction.go index abb26ab..975ffa2 100644 --- a/src/core/NewFunction.go +++ b/src/core/NewFunction.go @@ -8,18 +8,21 @@ import ( "git.urbach.dev/cli/q/src/fs" "git.urbach.dev/cli/q/src/register" "git.urbach.dev/cli/q/src/scope" + "git.urbach.dev/cli/q/src/wasm" "git.urbach.dev/cli/q/src/x86" ) // NewFunction creates a new function. func NewFunction(pkg string, name string, file *fs.File) *Function { - var cpu *cpu.CPU + var c *cpu.CPU switch config.TargetArch { case config.ARM: - cpu = &arm.CPU + c = &arm.CPU + case config.WASM: + c = &wasm.CPU case config.X86: - cpu = &x86.CPU + c = &x86.CPU } return &Function{ @@ -34,7 +37,7 @@ func NewFunction(pkg string, name string, file *fs.File) *Function { Stack: scope.Stack{ Scopes: []*scope.Scope{{}}, }, - CPU: cpu, + CPU: c, }, } } diff --git a/src/wasm/CPU.go b/src/wasm/CPU.go new file mode 100644 index 0000000..24935c5 --- /dev/null +++ b/src/wasm/CPU.go @@ -0,0 +1,11 @@ +package wasm + +import "git.urbach.dev/cli/q/src/cpu" + +var ( + CPU = cpu.CPU{ + Input: []cpu.Register{0, 1, 2, 3, 4, 5}, + Output: []cpu.Register{0, 1, 2, 3, 4, 5}, + NumRegisters: 0, + } +) diff --git a/src/wasm/WASM.go b/src/wasm/WASM.go new file mode 100644 index 0000000..d388043 --- /dev/null +++ b/src/wasm/WASM.go @@ -0,0 +1,13 @@ +package wasm + +import ( + "io" +) + +const HeaderEnd = 8 + +// Write writes the WASM format to the given writer. +func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { + writer.Write([]byte{0x00, 0x61, 0x73, 0x6D}) + writer.Write([]byte{0x01, 0x00, 0x00, 0x00}) +} From c4d763839a491f816e99cd459be9602ca356afe2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 13 Mar 2025 13:17:58 +0100 Subject: [PATCH 0921/1012] Implemented more arm64 instructions --- examples/array/array.q | 2 +- src/arm/Add.go | 4 +- src/arm/Jump.go | 43 ++++++++++++++++++ src/arm/Load.go | 19 +++++--- src/arm/LoadPair.go | 2 +- src/arm/Load_test.go | 10 ++++- src/arm/Store.go | 24 ++++++++++ src/arm/StorePair.go | 2 +- src/arm/Store_test.go | 34 +++++++++++++++ src/arm/Sub.go | 9 ++++ src/asm/Memory.go | 3 +- src/asmc/compileARM.go | 75 ++++++++++++++++++++++++++++++-- src/asmc/compileX86.go | 2 +- src/asmc/jumpARM.go | 53 ++++++++++++++++++++++ src/asmc/{jump.go => jumpX86.go} | 2 +- src/asmc/load.go | 4 +- src/asmc/store.go | 8 ++-- src/core/CompileLen.go | 3 +- src/core/CompileMemoryStore.go | 4 +- src/core/EvaluateArray.go | 3 +- src/core/EvaluateDot.go | 3 +- src/cpu/Register.go | 2 +- src/x86/Jump.go | 32 +++++++------- 23 files changed, 290 insertions(+), 53 deletions(-) create mode 100644 src/arm/Jump.go create mode 100644 src/arm/Store.go create mode 100644 src/arm/Store_test.go create mode 100644 src/arm/Sub.go create mode 100644 src/asmc/jumpARM.go rename src/asmc/{jump.go => jumpX86.go} (95%) diff --git a/examples/array/array.q b/examples/array/array.q index c81a7b0..69c3e45 100644 --- a/examples/array/array.q +++ b/examples/array/array.q @@ -8,6 +8,6 @@ main() { buffer[2] = 'l' buffer[3] = 'l' buffer[4] = 'o' - io.write(1, buffer) + io.out(buffer) mem.free(buffer) } \ No newline at end of file diff --git a/src/arm/Add.go b/src/arm/Add.go index c97afa8..c05e16e 100644 --- a/src/arm/Add.go +++ b/src/arm/Add.go @@ -2,8 +2,8 @@ package arm import "git.urbach.dev/cli/q/src/cpu" -// AddRegisterNumber adds a number to a register +// AddRegisterNumber adds a number to a register. func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { - number &= 0xFFF + number &= 0b1111_1111_1111 return 0b100100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) } diff --git a/src/arm/Jump.go b/src/arm/Jump.go new file mode 100644 index 0000000..78a75db --- /dev/null +++ b/src/arm/Jump.go @@ -0,0 +1,43 @@ +package arm + +// Jump continues program flow at the new offset. +func Jump(offset int) uint32 { + offset &= 0b11_1111_1111_1111_1111_1111_1111 + return 0b000101<<26 | uint32(offset) +} + +// JumpIfLess jumps if the result was less. +func JumpIfLess(offset int) uint32 { + return branchCond(0b1001, offset) +} + +// JumpIfLessOrEqual jumps if the result was less or equal. +func JumpIfLessOrEqual(offset int) uint32 { + return branchCond(0b1101, offset) +} + +// JumpIfGreater jumps if the result was greater. +func JumpIfGreater(offset int) uint32 { + return branchCond(0b1100, offset) +} + +// JumpIfGreaterOrEqual jumps if the result was greater or equal. +func JumpIfGreaterOrEqual(offset int) uint32 { + return branchCond(0b1010, offset) +} + +// JumpIfEqual jumps if the result was equal. +func JumpIfEqual(offset int) uint32 { + return branchCond(0b0000, offset) +} + +// JumpIfNotEqual jumps if the result was not equal. +func JumpIfNotEqual(offset int) uint32 { + return branchCond(0b0001, offset) +} + +// branchCond performs a conditional branch to a PC-relative offset. +func branchCond(cond uint32, offset int) uint32 { + offset &= 0b111_1111_1111_1111_1111 + return 0b01010100<<24 | uint32(offset)<<5 | cond +} diff --git a/src/arm/Load.go b/src/arm/Load.go index e1d09e1..8b82b98 100644 --- a/src/arm/Load.go +++ b/src/arm/Load.go @@ -3,11 +3,20 @@ package arm import "git.urbach.dev/cli/q/src/cpu" // LoadRegister loads from memory into a register. -func LoadRegister(destination cpu.Register, base cpu.Register, offset int16, length byte) uint32 { - if offset < 0 { - offset &= 0xFF - offset |= 1 << 8 +func LoadRegister(destination cpu.Register, base cpu.Register, offset int, length byte) uint32 { + offset &= 0b1_1111_1111 + common := 1<<22 | uint32(offset)<<12 | uint32(base)<<5 | uint32(destination) + + switch length { + case 1: + return 0b00111<<27 | common + case 2: + return 0b01111<<27 | common + case 4: + return 0b10111<<27 | common + case 8: + return 0b11111<<27 | common } - return 0b11111000010<<21 | uint32(offset)<<12 | uint32(base)<<5 | uint32(destination) + panic("invalid length") } diff --git a/src/arm/LoadPair.go b/src/arm/LoadPair.go index 12cc72c..8ca16d7 100644 --- a/src/arm/LoadPair.go +++ b/src/arm/LoadPair.go @@ -7,6 +7,6 @@ import "git.urbach.dev/cli/q/src/cpu" // This is the post-index version of the instruction so the offset is applied to the base register after the memory access. func LoadPair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { offset /= 8 - offset &= 0b1111111 + offset &= 0b111_1111 return 0b1010100011<<22 | (uint32(offset) << 15) | (uint32(reg2) << 10) | (uint32(base) << 5) | uint32(reg1) } diff --git a/src/arm/Load_test.go b/src/arm/Load_test.go index c04f60a..43eaf28 100644 --- a/src/arm/Load_test.go +++ b/src/arm/Load_test.go @@ -12,10 +12,18 @@ func TestLoadRegister(t *testing.T) { usagePatterns := []struct { Destination cpu.Register Base cpu.Register - Offset int16 + Offset int Length byte Code uint32 }{ + {arm.X0, arm.X1, -8, 1, 0x385F8020}, + {arm.X1, arm.X0, -8, 1, 0x385F8001}, + {arm.X0, arm.X1, -8, 2, 0x785F8020}, + {arm.X1, arm.X0, -8, 2, 0x785F8001}, + {arm.X0, arm.X1, -8, 4, 0xB85F8020}, + {arm.X1, arm.X0, -8, 4, 0xB85F8001}, + {arm.X0, arm.X1, -8, 8, 0xF85F8020}, + {arm.X1, arm.X0, -8, 8, 0xF85F8001}, {arm.X2, arm.X1, -8, 8, 0xF85F8022}, {arm.X2, arm.X1, 0, 8, 0xF8400022}, {arm.X2, arm.X1, 8, 8, 0xF8408022}, diff --git a/src/arm/Store.go b/src/arm/Store.go new file mode 100644 index 0000000..d81f6a1 --- /dev/null +++ b/src/arm/Store.go @@ -0,0 +1,24 @@ +package arm + +import ( + "git.urbach.dev/cli/q/src/cpu" +) + +// StoreRegister writes the contents of the register to a memory address. +func StoreRegister(source cpu.Register, base cpu.Register, offset int, length byte) uint32 { + offset &= 0b1_1111_1111 + common := uint32(offset)<<12 | uint32(base)<<5 | uint32(source) + + switch length { + case 1: + return 0b00111<<27 | common + case 2: + return 0b01111<<27 | common + case 4: + return 0b10111<<27 | common + case 8: + return 0b11111<<27 | common + } + + panic("invalid length") +} diff --git a/src/arm/StorePair.go b/src/arm/StorePair.go index f31cce9..006457d 100644 --- a/src/arm/StorePair.go +++ b/src/arm/StorePair.go @@ -9,6 +9,6 @@ import ( // This is the pre-index version of the instruction so the offset is applied to the base register before the memory access. func StorePair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { offset /= 8 - offset &= 0b1111111 + offset &= 0b111_1111 return 0b1010100110<<22 | uint32(offset)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) } diff --git a/src/arm/Store_test.go b/src/arm/Store_test.go new file mode 100644 index 0000000..9f4150d --- /dev/null +++ b/src/arm/Store_test.go @@ -0,0 +1,34 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestStoreRegister(t *testing.T) { + usagePatterns := []struct { + Source cpu.Register + Base cpu.Register + Offset int + Length byte + Code uint32 + }{ + {arm.X0, arm.X1, -8, 1, 0x381F8020}, + {arm.X1, arm.X0, -8, 1, 0x381F8001}, + {arm.X0, arm.X1, -8, 2, 0x781F8020}, + {arm.X1, arm.X0, -8, 2, 0x781F8001}, + {arm.X0, arm.X1, -8, 4, 0xB81F8020}, + {arm.X1, arm.X0, -8, 4, 0xB81F8001}, + {arm.X0, arm.X1, -8, 8, 0xF81F8020}, + {arm.X1, arm.X0, -8, 8, 0xF81F8001}, + } + + for _, pattern := range usagePatterns { + t.Logf("stur %s, [%s, #%d] %db", pattern.Source, pattern.Base, pattern.Offset, pattern.Length) + code := arm.StoreRegister(pattern.Source, pattern.Base, pattern.Offset, pattern.Length) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Sub.go b/src/arm/Sub.go new file mode 100644 index 0000000..6d222e7 --- /dev/null +++ b/src/arm/Sub.go @@ -0,0 +1,9 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// SubRegisterNumber subtracts a number from the given register. +func SubRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { + number &= 0b1111_1111_1111 + return 0b111100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) +} diff --git a/src/asm/Memory.go b/src/asm/Memory.go index 9ffd5d3..ddc5ddb 100644 --- a/src/asm/Memory.go +++ b/src/asm/Memory.go @@ -2,7 +2,6 @@ package asm import ( "fmt" - "math" "strconv" "strings" @@ -22,7 +21,7 @@ func (mem *Memory) Format(custom string) string { tmp.WriteString("[") tmp.WriteString(fmt.Sprint(mem.Base)) - if mem.OffsetRegister != math.MaxUint8 { + if mem.OffsetRegister >= 0 { tmp.WriteString("+") tmp.WriteString(fmt.Sprint(mem.OffsetRegister)) } diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index fa4b99f..9de618b 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -3,7 +3,6 @@ package asmc import ( "encoding/binary" "fmt" - "math" "strings" "git.urbach.dev/cli/q/src/arm" @@ -37,6 +36,9 @@ func (c *compiler) compileARM(x asm.Instruction) { } c.codePointers = append(c.codePointers, pointer) + + default: + panic("not implemented") } case asm.LABEL: @@ -50,14 +52,79 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.TypeMemoryRegister: operands := c.assembler.Param.MemoryRegister[x.Index] - if operands.Address.OffsetRegister == math.MaxUint8 { - c.append(arm.LoadRegister(operands.Register, operands.Address.Base, int16(operands.Address.Offset), operands.Address.Length)) + if operands.Address.OffsetRegister < 0 { + c.append(arm.LoadRegister(operands.Register, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) } else { - // TODO: LoadDynamicRegister panic("not implemented") } } + case asm.STORE: + switch x.Type { + case asm.TypeMemoryRegister: + operands := c.assembler.Param.MemoryRegister[x.Index] + + if operands.Address.OffsetRegister < 0 { + c.append(arm.StoreRegister(operands.Register, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) + } else { + panic("not implemented") + } + + case asm.TypeMemoryNumber: + operands := c.assembler.Param.MemoryNumber[x.Index] + + if operands.Address.OffsetRegister < 0 { + c.append(arm.MoveRegisterNumber(arm.X0, operands.Number)) + c.append(arm.StoreRegister(arm.X0, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) + } else { + panic("not implemented") + } + } + + case asm.PUSH: + switch x.Type { + case asm.TypeRegister: + operand := c.assembler.Param.Register[x.Index] + c.append(arm.StorePair(operand.Register, operand.Register, arm.SP, -16)) + } + + case asm.POP: + switch x.Type { + case asm.TypeRegister: + operand := c.assembler.Param.Register[x.Index] + c.append(arm.LoadPair(operand.Register, operand.Register, arm.SP, 16)) + } + + case asm.ADD: + switch x.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[x.Index] + c.append(arm.AddRegisterNumber(operand.Register, operand.Register, operand.Number)) + case asm.TypeRegisterRegister: + panic("not implemented") + } + + case asm.SUB: + switch x.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[x.Index] + c.append(arm.SubRegisterNumber(operand.Register, operand.Register, operand.Number)) + case asm.TypeRegisterRegister: + panic("not implemented") + } + + case asm.COMPARE: + switch x.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[x.Index] + c.append(arm.SubRegisterNumber(0b11111, operand.Register, operand.Number)) + case asm.TypeRegisterRegister: + panic("not implemented") + } + + case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: + c.jumpARM(x) + case asm.MOVE: switch x.Type { case asm.TypeRegisterRegister: diff --git a/src/asmc/compileX86.go b/src/asmc/compileX86.go index f873cdd..2b59340 100644 --- a/src/asmc/compileX86.go +++ b/src/asmc/compileX86.go @@ -99,7 +99,7 @@ func (c *compiler) compileX86(x asm.Instruction) { c.dllCall(x) case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: - c.jump(x) + c.jumpX86(x) case asm.LABEL: label := c.assembler.Param.Label[x.Index] diff --git a/src/asmc/jumpARM.go b/src/asmc/jumpARM.go new file mode 100644 index 0000000..6819262 --- /dev/null +++ b/src/asmc/jumpARM.go @@ -0,0 +1,53 @@ +package asmc + +import ( + "fmt" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/asm" +) + +func (c *compiler) jumpARM(x asm.Instruction) { + mnemonic := x.Mnemonic + position := Address(len(c.code)) + label := c.assembler.Param.Label[x.Index] + + pointer := &pointer{ + Position: position, + OpSize: 0, + Size: 4, + } + + c.append(arm.Nop()) + + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[label.Name] + + if !exists { + panic(fmt.Sprintf("unknown jump label %s", label.Name)) + } + + distance := (int(destination) - int(position)) / 4 + + switch mnemonic { + case asm.JE: + return arm.JumpIfEqual(distance) + case asm.JNE: + return arm.JumpIfNotEqual(distance) + case asm.JG: + return arm.JumpIfGreater(distance) + case asm.JGE: + return arm.JumpIfGreaterOrEqual(distance) + case asm.JL: + return arm.JumpIfLess(distance) + case asm.JLE: + return arm.JumpIfLessOrEqual(distance) + case asm.JUMP: + return arm.Jump(distance) + default: + panic("not implemented") + } + } + + c.codePointers = append(c.codePointers, pointer) +} diff --git a/src/asmc/jump.go b/src/asmc/jumpX86.go similarity index 95% rename from src/asmc/jump.go rename to src/asmc/jumpX86.go index 2b2f159..e9ace20 100644 --- a/src/asmc/jump.go +++ b/src/asmc/jumpX86.go @@ -7,7 +7,7 @@ import ( "git.urbach.dev/cli/q/src/x86" ) -func (c *compiler) jump(x asm.Instruction) { +func (c *compiler) jumpX86(x asm.Instruction) { switch x.Mnemonic { case asm.JE: c.code = x86.Jump8IfEqual(c.code, 0x00) diff --git a/src/asmc/load.go b/src/asmc/load.go index d6691f7..845092b 100644 --- a/src/asmc/load.go +++ b/src/asmc/load.go @@ -1,8 +1,6 @@ package asmc import ( - "math" - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/x86" ) @@ -12,7 +10,7 @@ func (c *compiler) load(x asm.Instruction) { case asm.TypeMemoryRegister: operands := c.assembler.Param.MemoryRegister[x.Index] - if operands.Address.OffsetRegister == math.MaxUint8 { + if operands.Address.OffsetRegister < 0 { c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Base, operands.Address.Offset, operands.Address.Length) } else { c.code = x86.LoadDynamicRegister(c.code, operands.Register, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length) diff --git a/src/asmc/store.go b/src/asmc/store.go index f67b9b0..b5d338e 100644 --- a/src/asmc/store.go +++ b/src/asmc/store.go @@ -1,8 +1,6 @@ package asmc import ( - "math" - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/x86" @@ -13,7 +11,7 @@ func (c *compiler) store(x asm.Instruction) { case asm.TypeMemoryNumber: operands := c.assembler.Param.MemoryNumber[x.Index] - if operands.Address.OffsetRegister == math.MaxUint8 { + if operands.Address.OffsetRegister < 0 { c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) } else { c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) @@ -22,7 +20,7 @@ func (c *compiler) store(x asm.Instruction) { operands := c.assembler.Param.MemoryLabel[x.Index] start := len(c.code) - if operands.Address.OffsetRegister == math.MaxUint8 { + if operands.Address.OffsetRegister < 0 { c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, 0b00_00_00_00) } else { c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, 0b00_00_00_00) @@ -48,7 +46,7 @@ func (c *compiler) store(x asm.Instruction) { case asm.TypeMemoryRegister: operands := c.assembler.Param.MemoryRegister[x.Index] - if operands.Address.OffsetRegister == math.MaxUint8 { + if operands.Address.OffsetRegister < 0 { c.code = x86.StoreRegister(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) } else { c.code = x86.StoreDynamicRegister(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index db20d71..8fe23ef 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -2,7 +2,6 @@ package core import ( "fmt" - "math" "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" @@ -25,7 +24,7 @@ func (f *Function) CompileLen(root *expression.Expression) error { memory := asm.Memory{ Offset: -8, - OffsetRegister: math.MaxUint8, + OffsetRegister: -1, Length: 8, } diff --git a/src/core/CompileMemoryStore.go b/src/core/CompileMemoryStore.go index ff6d59f..75e6da7 100644 --- a/src/core/CompileMemoryStore.go +++ b/src/core/CompileMemoryStore.go @@ -1,8 +1,6 @@ package core import ( - "math" - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" @@ -29,7 +27,7 @@ func (f *Function) CompileMemoryStore(root *expression.Expression) error { memory := asm.Memory{ Base: variable.Value.Register, - OffsetRegister: math.MaxUint8, + OffsetRegister: -1, Length: byte(numBytes), } diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go index 39cb01a..5e840a5 100644 --- a/src/core/EvaluateArray.go +++ b/src/core/EvaluateArray.go @@ -2,7 +2,6 @@ package core import ( "fmt" - "math" "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" @@ -25,7 +24,7 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (*eval.Memory, err memory := asm.Memory{ Base: base.Value.Register, Offset: 0, - OffsetRegister: math.MaxUint8, + OffsetRegister: -1, Length: byte(1), } diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go index 52738b3..f968839 100644 --- a/src/core/EvaluateDot.go +++ b/src/core/EvaluateDot.go @@ -2,7 +2,6 @@ package core import ( "fmt" - "math" "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" @@ -34,7 +33,7 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) Memory: asm.Memory{ Base: variable.Value.Register, Offset: int8(field.Offset), - OffsetRegister: math.MaxUint8, + OffsetRegister: -1, Length: byte(field.Type.Size()), }, } diff --git a/src/cpu/Register.go b/src/cpu/Register.go index 006fc68..8c75cb6 100644 --- a/src/cpu/Register.go +++ b/src/cpu/Register.go @@ -3,7 +3,7 @@ package cpu import "fmt" // Register represents the number of the register. -type Register uint8 +type Register int8 // String returns the human readable name of the register. func (r Register) String() string { diff --git a/src/x86/Jump.go b/src/x86/Jump.go index 27b1ef4..feed711 100644 --- a/src/x86/Jump.go +++ b/src/x86/Jump.go @@ -1,37 +1,37 @@ package x86 -// Jump continues program flow at the new address. -// The address is relative to the next instruction. -func Jump8(code []byte, address int8) []byte { - return append(code, 0xEB, byte(address)) +// Jump continues program flow at the new offset. +// The offset is relative to the next instruction. +func Jump8(code []byte, offset int8) []byte { + return append(code, 0xEB, byte(offset)) } // JumpIfLess jumps if the result was less. -func Jump8IfLess(code []byte, address int8) []byte { - return append(code, 0x7C, byte(address)) +func Jump8IfLess(code []byte, offset int8) []byte { + return append(code, 0x7C, byte(offset)) } // JumpIfLessOrEqual jumps if the result was less or equal. -func Jump8IfLessOrEqual(code []byte, address int8) []byte { - return append(code, 0x7E, byte(address)) +func Jump8IfLessOrEqual(code []byte, offset int8) []byte { + return append(code, 0x7E, byte(offset)) } // JumpIfGreater jumps if the result was greater. -func Jump8IfGreater(code []byte, address int8) []byte { - return append(code, 0x7F, byte(address)) +func Jump8IfGreater(code []byte, offset int8) []byte { + return append(code, 0x7F, byte(offset)) } // JumpIfGreaterOrEqual jumps if the result was greater or equal. -func Jump8IfGreaterOrEqual(code []byte, address int8) []byte { - return append(code, 0x7D, byte(address)) +func Jump8IfGreaterOrEqual(code []byte, offset int8) []byte { + return append(code, 0x7D, byte(offset)) } // JumpIfEqual jumps if the result was equal. -func Jump8IfEqual(code []byte, address int8) []byte { - return append(code, 0x74, byte(address)) +func Jump8IfEqual(code []byte, offset int8) []byte { + return append(code, 0x74, byte(offset)) } // JumpIfNotEqual jumps if the result was not equal. -func Jump8IfNotEqual(code []byte, address int8) []byte { - return append(code, 0x75, byte(address)) +func Jump8IfNotEqual(code []byte, offset int8) []byte { + return append(code, 0x75, byte(offset)) } From 5a5061c5d7603b30a65bb56214c26b3db9c4be93 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 13 Mar 2025 13:17:58 +0100 Subject: [PATCH 0922/1012] Implemented more arm64 instructions --- examples/array/array.q | 2 +- src/arm/Add.go | 4 +- src/arm/Jump.go | 43 ++++++++++++++++++ src/arm/Load.go | 19 +++++--- src/arm/LoadPair.go | 2 +- src/arm/Load_test.go | 10 ++++- src/arm/Store.go | 24 ++++++++++ src/arm/StorePair.go | 2 +- src/arm/Store_test.go | 34 +++++++++++++++ src/arm/Sub.go | 9 ++++ src/asm/Memory.go | 3 +- src/asmc/compileARM.go | 75 ++++++++++++++++++++++++++++++-- src/asmc/compileX86.go | 2 +- src/asmc/jumpARM.go | 53 ++++++++++++++++++++++ src/asmc/{jump.go => jumpX86.go} | 2 +- src/asmc/load.go | 4 +- src/asmc/store.go | 8 ++-- src/core/CompileLen.go | 3 +- src/core/CompileMemoryStore.go | 4 +- src/core/EvaluateArray.go | 3 +- src/core/EvaluateDot.go | 3 +- src/cpu/Register.go | 2 +- src/x86/Jump.go | 32 +++++++------- 23 files changed, 290 insertions(+), 53 deletions(-) create mode 100644 src/arm/Jump.go create mode 100644 src/arm/Store.go create mode 100644 src/arm/Store_test.go create mode 100644 src/arm/Sub.go create mode 100644 src/asmc/jumpARM.go rename src/asmc/{jump.go => jumpX86.go} (95%) diff --git a/examples/array/array.q b/examples/array/array.q index c81a7b0..69c3e45 100644 --- a/examples/array/array.q +++ b/examples/array/array.q @@ -8,6 +8,6 @@ main() { buffer[2] = 'l' buffer[3] = 'l' buffer[4] = 'o' - io.write(1, buffer) + io.out(buffer) mem.free(buffer) } \ No newline at end of file diff --git a/src/arm/Add.go b/src/arm/Add.go index c97afa8..c05e16e 100644 --- a/src/arm/Add.go +++ b/src/arm/Add.go @@ -2,8 +2,8 @@ package arm import "git.urbach.dev/cli/q/src/cpu" -// AddRegisterNumber adds a number to a register +// AddRegisterNumber adds a number to a register. func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { - number &= 0xFFF + number &= 0b1111_1111_1111 return 0b100100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) } diff --git a/src/arm/Jump.go b/src/arm/Jump.go new file mode 100644 index 0000000..78a75db --- /dev/null +++ b/src/arm/Jump.go @@ -0,0 +1,43 @@ +package arm + +// Jump continues program flow at the new offset. +func Jump(offset int) uint32 { + offset &= 0b11_1111_1111_1111_1111_1111_1111 + return 0b000101<<26 | uint32(offset) +} + +// JumpIfLess jumps if the result was less. +func JumpIfLess(offset int) uint32 { + return branchCond(0b1001, offset) +} + +// JumpIfLessOrEqual jumps if the result was less or equal. +func JumpIfLessOrEqual(offset int) uint32 { + return branchCond(0b1101, offset) +} + +// JumpIfGreater jumps if the result was greater. +func JumpIfGreater(offset int) uint32 { + return branchCond(0b1100, offset) +} + +// JumpIfGreaterOrEqual jumps if the result was greater or equal. +func JumpIfGreaterOrEqual(offset int) uint32 { + return branchCond(0b1010, offset) +} + +// JumpIfEqual jumps if the result was equal. +func JumpIfEqual(offset int) uint32 { + return branchCond(0b0000, offset) +} + +// JumpIfNotEqual jumps if the result was not equal. +func JumpIfNotEqual(offset int) uint32 { + return branchCond(0b0001, offset) +} + +// branchCond performs a conditional branch to a PC-relative offset. +func branchCond(cond uint32, offset int) uint32 { + offset &= 0b111_1111_1111_1111_1111 + return 0b01010100<<24 | uint32(offset)<<5 | cond +} diff --git a/src/arm/Load.go b/src/arm/Load.go index e1d09e1..8b82b98 100644 --- a/src/arm/Load.go +++ b/src/arm/Load.go @@ -3,11 +3,20 @@ package arm import "git.urbach.dev/cli/q/src/cpu" // LoadRegister loads from memory into a register. -func LoadRegister(destination cpu.Register, base cpu.Register, offset int16, length byte) uint32 { - if offset < 0 { - offset &= 0xFF - offset |= 1 << 8 +func LoadRegister(destination cpu.Register, base cpu.Register, offset int, length byte) uint32 { + offset &= 0b1_1111_1111 + common := 1<<22 | uint32(offset)<<12 | uint32(base)<<5 | uint32(destination) + + switch length { + case 1: + return 0b00111<<27 | common + case 2: + return 0b01111<<27 | common + case 4: + return 0b10111<<27 | common + case 8: + return 0b11111<<27 | common } - return 0b11111000010<<21 | uint32(offset)<<12 | uint32(base)<<5 | uint32(destination) + panic("invalid length") } diff --git a/src/arm/LoadPair.go b/src/arm/LoadPair.go index 12cc72c..8ca16d7 100644 --- a/src/arm/LoadPair.go +++ b/src/arm/LoadPair.go @@ -7,6 +7,6 @@ import "git.urbach.dev/cli/q/src/cpu" // This is the post-index version of the instruction so the offset is applied to the base register after the memory access. func LoadPair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { offset /= 8 - offset &= 0b1111111 + offset &= 0b111_1111 return 0b1010100011<<22 | (uint32(offset) << 15) | (uint32(reg2) << 10) | (uint32(base) << 5) | uint32(reg1) } diff --git a/src/arm/Load_test.go b/src/arm/Load_test.go index c04f60a..43eaf28 100644 --- a/src/arm/Load_test.go +++ b/src/arm/Load_test.go @@ -12,10 +12,18 @@ func TestLoadRegister(t *testing.T) { usagePatterns := []struct { Destination cpu.Register Base cpu.Register - Offset int16 + Offset int Length byte Code uint32 }{ + {arm.X0, arm.X1, -8, 1, 0x385F8020}, + {arm.X1, arm.X0, -8, 1, 0x385F8001}, + {arm.X0, arm.X1, -8, 2, 0x785F8020}, + {arm.X1, arm.X0, -8, 2, 0x785F8001}, + {arm.X0, arm.X1, -8, 4, 0xB85F8020}, + {arm.X1, arm.X0, -8, 4, 0xB85F8001}, + {arm.X0, arm.X1, -8, 8, 0xF85F8020}, + {arm.X1, arm.X0, -8, 8, 0xF85F8001}, {arm.X2, arm.X1, -8, 8, 0xF85F8022}, {arm.X2, arm.X1, 0, 8, 0xF8400022}, {arm.X2, arm.X1, 8, 8, 0xF8408022}, diff --git a/src/arm/Store.go b/src/arm/Store.go new file mode 100644 index 0000000..d81f6a1 --- /dev/null +++ b/src/arm/Store.go @@ -0,0 +1,24 @@ +package arm + +import ( + "git.urbach.dev/cli/q/src/cpu" +) + +// StoreRegister writes the contents of the register to a memory address. +func StoreRegister(source cpu.Register, base cpu.Register, offset int, length byte) uint32 { + offset &= 0b1_1111_1111 + common := uint32(offset)<<12 | uint32(base)<<5 | uint32(source) + + switch length { + case 1: + return 0b00111<<27 | common + case 2: + return 0b01111<<27 | common + case 4: + return 0b10111<<27 | common + case 8: + return 0b11111<<27 | common + } + + panic("invalid length") +} diff --git a/src/arm/StorePair.go b/src/arm/StorePair.go index f31cce9..006457d 100644 --- a/src/arm/StorePair.go +++ b/src/arm/StorePair.go @@ -9,6 +9,6 @@ import ( // This is the pre-index version of the instruction so the offset is applied to the base register before the memory access. func StorePair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { offset /= 8 - offset &= 0b1111111 + offset &= 0b111_1111 return 0b1010100110<<22 | uint32(offset)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) } diff --git a/src/arm/Store_test.go b/src/arm/Store_test.go new file mode 100644 index 0000000..9f4150d --- /dev/null +++ b/src/arm/Store_test.go @@ -0,0 +1,34 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestStoreRegister(t *testing.T) { + usagePatterns := []struct { + Source cpu.Register + Base cpu.Register + Offset int + Length byte + Code uint32 + }{ + {arm.X0, arm.X1, -8, 1, 0x381F8020}, + {arm.X1, arm.X0, -8, 1, 0x381F8001}, + {arm.X0, arm.X1, -8, 2, 0x781F8020}, + {arm.X1, arm.X0, -8, 2, 0x781F8001}, + {arm.X0, arm.X1, -8, 4, 0xB81F8020}, + {arm.X1, arm.X0, -8, 4, 0xB81F8001}, + {arm.X0, arm.X1, -8, 8, 0xF81F8020}, + {arm.X1, arm.X0, -8, 8, 0xF81F8001}, + } + + for _, pattern := range usagePatterns { + t.Logf("stur %s, [%s, #%d] %db", pattern.Source, pattern.Base, pattern.Offset, pattern.Length) + code := arm.StoreRegister(pattern.Source, pattern.Base, pattern.Offset, pattern.Length) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Sub.go b/src/arm/Sub.go new file mode 100644 index 0000000..6d222e7 --- /dev/null +++ b/src/arm/Sub.go @@ -0,0 +1,9 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// SubRegisterNumber subtracts a number from the given register. +func SubRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { + number &= 0b1111_1111_1111 + return 0b111100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) +} diff --git a/src/asm/Memory.go b/src/asm/Memory.go index 9ffd5d3..ddc5ddb 100644 --- a/src/asm/Memory.go +++ b/src/asm/Memory.go @@ -2,7 +2,6 @@ package asm import ( "fmt" - "math" "strconv" "strings" @@ -22,7 +21,7 @@ func (mem *Memory) Format(custom string) string { tmp.WriteString("[") tmp.WriteString(fmt.Sprint(mem.Base)) - if mem.OffsetRegister != math.MaxUint8 { + if mem.OffsetRegister >= 0 { tmp.WriteString("+") tmp.WriteString(fmt.Sprint(mem.OffsetRegister)) } diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index fa4b99f..9de618b 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -3,7 +3,6 @@ package asmc import ( "encoding/binary" "fmt" - "math" "strings" "git.urbach.dev/cli/q/src/arm" @@ -37,6 +36,9 @@ func (c *compiler) compileARM(x asm.Instruction) { } c.codePointers = append(c.codePointers, pointer) + + default: + panic("not implemented") } case asm.LABEL: @@ -50,14 +52,79 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.TypeMemoryRegister: operands := c.assembler.Param.MemoryRegister[x.Index] - if operands.Address.OffsetRegister == math.MaxUint8 { - c.append(arm.LoadRegister(operands.Register, operands.Address.Base, int16(operands.Address.Offset), operands.Address.Length)) + if operands.Address.OffsetRegister < 0 { + c.append(arm.LoadRegister(operands.Register, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) } else { - // TODO: LoadDynamicRegister panic("not implemented") } } + case asm.STORE: + switch x.Type { + case asm.TypeMemoryRegister: + operands := c.assembler.Param.MemoryRegister[x.Index] + + if operands.Address.OffsetRegister < 0 { + c.append(arm.StoreRegister(operands.Register, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) + } else { + panic("not implemented") + } + + case asm.TypeMemoryNumber: + operands := c.assembler.Param.MemoryNumber[x.Index] + + if operands.Address.OffsetRegister < 0 { + c.append(arm.MoveRegisterNumber(arm.X0, operands.Number)) + c.append(arm.StoreRegister(arm.X0, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) + } else { + panic("not implemented") + } + } + + case asm.PUSH: + switch x.Type { + case asm.TypeRegister: + operand := c.assembler.Param.Register[x.Index] + c.append(arm.StorePair(operand.Register, operand.Register, arm.SP, -16)) + } + + case asm.POP: + switch x.Type { + case asm.TypeRegister: + operand := c.assembler.Param.Register[x.Index] + c.append(arm.LoadPair(operand.Register, operand.Register, arm.SP, 16)) + } + + case asm.ADD: + switch x.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[x.Index] + c.append(arm.AddRegisterNumber(operand.Register, operand.Register, operand.Number)) + case asm.TypeRegisterRegister: + panic("not implemented") + } + + case asm.SUB: + switch x.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[x.Index] + c.append(arm.SubRegisterNumber(operand.Register, operand.Register, operand.Number)) + case asm.TypeRegisterRegister: + panic("not implemented") + } + + case asm.COMPARE: + switch x.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[x.Index] + c.append(arm.SubRegisterNumber(0b11111, operand.Register, operand.Number)) + case asm.TypeRegisterRegister: + panic("not implemented") + } + + case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: + c.jumpARM(x) + case asm.MOVE: switch x.Type { case asm.TypeRegisterRegister: diff --git a/src/asmc/compileX86.go b/src/asmc/compileX86.go index f873cdd..2b59340 100644 --- a/src/asmc/compileX86.go +++ b/src/asmc/compileX86.go @@ -99,7 +99,7 @@ func (c *compiler) compileX86(x asm.Instruction) { c.dllCall(x) case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: - c.jump(x) + c.jumpX86(x) case asm.LABEL: label := c.assembler.Param.Label[x.Index] diff --git a/src/asmc/jumpARM.go b/src/asmc/jumpARM.go new file mode 100644 index 0000000..6819262 --- /dev/null +++ b/src/asmc/jumpARM.go @@ -0,0 +1,53 @@ +package asmc + +import ( + "fmt" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/asm" +) + +func (c *compiler) jumpARM(x asm.Instruction) { + mnemonic := x.Mnemonic + position := Address(len(c.code)) + label := c.assembler.Param.Label[x.Index] + + pointer := &pointer{ + Position: position, + OpSize: 0, + Size: 4, + } + + c.append(arm.Nop()) + + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[label.Name] + + if !exists { + panic(fmt.Sprintf("unknown jump label %s", label.Name)) + } + + distance := (int(destination) - int(position)) / 4 + + switch mnemonic { + case asm.JE: + return arm.JumpIfEqual(distance) + case asm.JNE: + return arm.JumpIfNotEqual(distance) + case asm.JG: + return arm.JumpIfGreater(distance) + case asm.JGE: + return arm.JumpIfGreaterOrEqual(distance) + case asm.JL: + return arm.JumpIfLess(distance) + case asm.JLE: + return arm.JumpIfLessOrEqual(distance) + case asm.JUMP: + return arm.Jump(distance) + default: + panic("not implemented") + } + } + + c.codePointers = append(c.codePointers, pointer) +} diff --git a/src/asmc/jump.go b/src/asmc/jumpX86.go similarity index 95% rename from src/asmc/jump.go rename to src/asmc/jumpX86.go index 2b2f159..e9ace20 100644 --- a/src/asmc/jump.go +++ b/src/asmc/jumpX86.go @@ -7,7 +7,7 @@ import ( "git.urbach.dev/cli/q/src/x86" ) -func (c *compiler) jump(x asm.Instruction) { +func (c *compiler) jumpX86(x asm.Instruction) { switch x.Mnemonic { case asm.JE: c.code = x86.Jump8IfEqual(c.code, 0x00) diff --git a/src/asmc/load.go b/src/asmc/load.go index d6691f7..845092b 100644 --- a/src/asmc/load.go +++ b/src/asmc/load.go @@ -1,8 +1,6 @@ package asmc import ( - "math" - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/x86" ) @@ -12,7 +10,7 @@ func (c *compiler) load(x asm.Instruction) { case asm.TypeMemoryRegister: operands := c.assembler.Param.MemoryRegister[x.Index] - if operands.Address.OffsetRegister == math.MaxUint8 { + if operands.Address.OffsetRegister < 0 { c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Base, operands.Address.Offset, operands.Address.Length) } else { c.code = x86.LoadDynamicRegister(c.code, operands.Register, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length) diff --git a/src/asmc/store.go b/src/asmc/store.go index f67b9b0..b5d338e 100644 --- a/src/asmc/store.go +++ b/src/asmc/store.go @@ -1,8 +1,6 @@ package asmc import ( - "math" - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/x86" @@ -13,7 +11,7 @@ func (c *compiler) store(x asm.Instruction) { case asm.TypeMemoryNumber: operands := c.assembler.Param.MemoryNumber[x.Index] - if operands.Address.OffsetRegister == math.MaxUint8 { + if operands.Address.OffsetRegister < 0 { c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) } else { c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) @@ -22,7 +20,7 @@ func (c *compiler) store(x asm.Instruction) { operands := c.assembler.Param.MemoryLabel[x.Index] start := len(c.code) - if operands.Address.OffsetRegister == math.MaxUint8 { + if operands.Address.OffsetRegister < 0 { c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, 0b00_00_00_00) } else { c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, 0b00_00_00_00) @@ -48,7 +46,7 @@ func (c *compiler) store(x asm.Instruction) { case asm.TypeMemoryRegister: operands := c.assembler.Param.MemoryRegister[x.Index] - if operands.Address.OffsetRegister == math.MaxUint8 { + if operands.Address.OffsetRegister < 0 { c.code = x86.StoreRegister(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) } else { c.code = x86.StoreDynamicRegister(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) diff --git a/src/core/CompileLen.go b/src/core/CompileLen.go index db20d71..8fe23ef 100644 --- a/src/core/CompileLen.go +++ b/src/core/CompileLen.go @@ -2,7 +2,6 @@ package core import ( "fmt" - "math" "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" @@ -25,7 +24,7 @@ func (f *Function) CompileLen(root *expression.Expression) error { memory := asm.Memory{ Offset: -8, - OffsetRegister: math.MaxUint8, + OffsetRegister: -1, Length: 8, } diff --git a/src/core/CompileMemoryStore.go b/src/core/CompileMemoryStore.go index ff6d59f..75e6da7 100644 --- a/src/core/CompileMemoryStore.go +++ b/src/core/CompileMemoryStore.go @@ -1,8 +1,6 @@ package core import ( - "math" - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" @@ -29,7 +27,7 @@ func (f *Function) CompileMemoryStore(root *expression.Expression) error { memory := asm.Memory{ Base: variable.Value.Register, - OffsetRegister: math.MaxUint8, + OffsetRegister: -1, Length: byte(numBytes), } diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go index 39cb01a..5e840a5 100644 --- a/src/core/EvaluateArray.go +++ b/src/core/EvaluateArray.go @@ -2,7 +2,6 @@ package core import ( "fmt" - "math" "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" @@ -25,7 +24,7 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (*eval.Memory, err memory := asm.Memory{ Base: base.Value.Register, Offset: 0, - OffsetRegister: math.MaxUint8, + OffsetRegister: -1, Length: byte(1), } diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go index 52738b3..f968839 100644 --- a/src/core/EvaluateDot.go +++ b/src/core/EvaluateDot.go @@ -2,7 +2,6 @@ package core import ( "fmt" - "math" "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" @@ -34,7 +33,7 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) Memory: asm.Memory{ Base: variable.Value.Register, Offset: int8(field.Offset), - OffsetRegister: math.MaxUint8, + OffsetRegister: -1, Length: byte(field.Type.Size()), }, } diff --git a/src/cpu/Register.go b/src/cpu/Register.go index 006fc68..8c75cb6 100644 --- a/src/cpu/Register.go +++ b/src/cpu/Register.go @@ -3,7 +3,7 @@ package cpu import "fmt" // Register represents the number of the register. -type Register uint8 +type Register int8 // String returns the human readable name of the register. func (r Register) String() string { diff --git a/src/x86/Jump.go b/src/x86/Jump.go index 27b1ef4..feed711 100644 --- a/src/x86/Jump.go +++ b/src/x86/Jump.go @@ -1,37 +1,37 @@ package x86 -// Jump continues program flow at the new address. -// The address is relative to the next instruction. -func Jump8(code []byte, address int8) []byte { - return append(code, 0xEB, byte(address)) +// Jump continues program flow at the new offset. +// The offset is relative to the next instruction. +func Jump8(code []byte, offset int8) []byte { + return append(code, 0xEB, byte(offset)) } // JumpIfLess jumps if the result was less. -func Jump8IfLess(code []byte, address int8) []byte { - return append(code, 0x7C, byte(address)) +func Jump8IfLess(code []byte, offset int8) []byte { + return append(code, 0x7C, byte(offset)) } // JumpIfLessOrEqual jumps if the result was less or equal. -func Jump8IfLessOrEqual(code []byte, address int8) []byte { - return append(code, 0x7E, byte(address)) +func Jump8IfLessOrEqual(code []byte, offset int8) []byte { + return append(code, 0x7E, byte(offset)) } // JumpIfGreater jumps if the result was greater. -func Jump8IfGreater(code []byte, address int8) []byte { - return append(code, 0x7F, byte(address)) +func Jump8IfGreater(code []byte, offset int8) []byte { + return append(code, 0x7F, byte(offset)) } // JumpIfGreaterOrEqual jumps if the result was greater or equal. -func Jump8IfGreaterOrEqual(code []byte, address int8) []byte { - return append(code, 0x7D, byte(address)) +func Jump8IfGreaterOrEqual(code []byte, offset int8) []byte { + return append(code, 0x7D, byte(offset)) } // JumpIfEqual jumps if the result was equal. -func Jump8IfEqual(code []byte, address int8) []byte { - return append(code, 0x74, byte(address)) +func Jump8IfEqual(code []byte, offset int8) []byte { + return append(code, 0x74, byte(offset)) } // JumpIfNotEqual jumps if the result was not equal. -func Jump8IfNotEqual(code []byte, address int8) []byte { - return append(code, 0x75, byte(address)) +func Jump8IfNotEqual(code []byte, offset int8) []byte { + return append(code, 0x75, byte(offset)) } From f4b0020c493f3dd571e95cd5147b90348aa70d86 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 13 Mar 2025 16:02:09 +0100 Subject: [PATCH 0923/1012] Added more tests --- src/arm/Add.go | 3 +- src/arm/Add_test.go | 26 ++++++++++++++++ src/arm/Compare.go | 8 +++++ src/arm/Compare_test.go | 25 +++++++++++++++ src/arm/Jump.go | 40 ++++++++++++------------ src/arm/Jump_test.go | 68 +++++++++++++++++++++++++++++++++++++++++ src/arm/Sub.go | 3 +- src/arm/Sub_test.go | 26 ++++++++++++++++ src/arm/encode.go | 16 ++++++++++ src/asmc/compileARM.go | 2 +- src/asmc/jumpARM.go | 3 +- src/x86/Jump.go | 24 +++++++-------- 12 files changed, 205 insertions(+), 39 deletions(-) create mode 100644 src/arm/Add_test.go create mode 100644 src/arm/Compare.go create mode 100644 src/arm/Compare_test.go create mode 100644 src/arm/Jump_test.go create mode 100644 src/arm/Sub_test.go create mode 100644 src/arm/encode.go diff --git a/src/arm/Add.go b/src/arm/Add.go index c05e16e..dd868e9 100644 --- a/src/arm/Add.go +++ b/src/arm/Add.go @@ -4,6 +4,5 @@ import "git.urbach.dev/cli/q/src/cpu" // AddRegisterNumber adds a number to a register. func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { - number &= 0b1111_1111_1111 - return 0b100100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) + return encodeRegisterNumberFlags(0b10, destination, source, number, false) } diff --git a/src/arm/Add_test.go b/src/arm/Add_test.go new file mode 100644 index 0000000..9b403b3 --- /dev/null +++ b/src/arm/Add_test.go @@ -0,0 +1,26 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestAddRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, arm.X0, 1, 0x91000400}, + } + + for _, pattern := range usagePatterns { + t.Logf("add %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) + code := arm.AddRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Compare.go b/src/arm/Compare.go new file mode 100644 index 0000000..5fc07ec --- /dev/null +++ b/src/arm/Compare.go @@ -0,0 +1,8 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// CompareRegisterNumber is an alias for a subtraction that updates the conditional flags and discards the result. +func CompareRegisterNumber(register cpu.Register, number int) uint32 { + return encodeRegisterNumberFlags(0b11, 0b11111, register, number, true) +} diff --git a/src/arm/Compare_test.go b/src/arm/Compare_test.go new file mode 100644 index 0000000..7a3eca1 --- /dev/null +++ b/src/arm/Compare_test.go @@ -0,0 +1,25 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestCompareRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, 1, 0xF100041F}, + } + + for _, pattern := range usagePatterns { + t.Logf("cmp %s, %d", pattern.Source, pattern.Number) + code := arm.CompareRegisterNumber(pattern.Source, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Jump.go b/src/arm/Jump.go index 78a75db..8f3f807 100644 --- a/src/arm/Jump.go +++ b/src/arm/Jump.go @@ -6,26 +6,6 @@ func Jump(offset int) uint32 { return 0b000101<<26 | uint32(offset) } -// JumpIfLess jumps if the result was less. -func JumpIfLess(offset int) uint32 { - return branchCond(0b1001, offset) -} - -// JumpIfLessOrEqual jumps if the result was less or equal. -func JumpIfLessOrEqual(offset int) uint32 { - return branchCond(0b1101, offset) -} - -// JumpIfGreater jumps if the result was greater. -func JumpIfGreater(offset int) uint32 { - return branchCond(0b1100, offset) -} - -// JumpIfGreaterOrEqual jumps if the result was greater or equal. -func JumpIfGreaterOrEqual(offset int) uint32 { - return branchCond(0b1010, offset) -} - // JumpIfEqual jumps if the result was equal. func JumpIfEqual(offset int) uint32 { return branchCond(0b0000, offset) @@ -36,6 +16,26 @@ func JumpIfNotEqual(offset int) uint32 { return branchCond(0b0001, offset) } +// JumpIfGreater jumps if the result was greater. +func JumpIfGreater(offset int) uint32 { + return branchCond(0b1100, offset) +} + +// JumpIfGreaterOrEqual jumps if the result was greater or equal. +func JumpIfGreaterOrEqual(offset int) uint32 { + return branchCond(0b1010, offset) +} + +// JumpIfLess jumps if the result was less. +func JumpIfLess(offset int) uint32 { + return branchCond(0b1001, offset) +} + +// JumpIfLessOrEqual jumps if the result was less or equal. +func JumpIfLessOrEqual(offset int) uint32 { + return branchCond(0b1101, offset) +} + // branchCond performs a conditional branch to a PC-relative offset. func branchCond(cond uint32, offset int) uint32 { offset &= 0b111_1111_1111_1111_1111 diff --git a/src/arm/Jump_test.go b/src/arm/Jump_test.go new file mode 100644 index 0000000..423c568 --- /dev/null +++ b/src/arm/Jump_test.go @@ -0,0 +1,68 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/go/assert" +) + +func TestJump(t *testing.T) { + usagePatterns := []struct { + Type byte + Offset int + Code uint32 + }{ + {0, 0, 0x14000000}, + {0, 1, 0x14000001}, + {0, -1, 0x17FFFFFF}, + + {1, 0, 0x54000000}, + {1, 1, 0x54000020}, + {1, -1, 0x54FFFFE0}, + + {2, 0, 0x54000001}, + {2, 1, 0x54000021}, + {2, -1, 0x54FFFFE1}, + + {3, 0, 0x5400000C}, + {3, 1, 0x5400002C}, + {3, -1, 0x54FFFFEC}, + + {4, 0, 0x5400000A}, + {4, 1, 0x5400002A}, + {4, -1, 0x54FFFFEA}, + + {5, 0, 0x54000009}, + {5, 1, 0x54000029}, + {5, -1, 0x54FFFFE9}, + + {6, 0, 0x5400000D}, + {6, 1, 0x5400002D}, + {6, -1, 0x54FFFFED}, + } + + for _, pattern := range usagePatterns { + t.Logf("b %d", pattern.Offset) + var code uint32 + + switch pattern.Type { + case 0: + code = arm.Jump(pattern.Offset) + case 1: + code = arm.JumpIfEqual(pattern.Offset) + case 2: + code = arm.JumpIfNotEqual(pattern.Offset) + case 3: + code = arm.JumpIfGreater(pattern.Offset) + case 4: + code = arm.JumpIfGreaterOrEqual(pattern.Offset) + case 5: + code = arm.JumpIfLess(pattern.Offset) + case 6: + code = arm.JumpIfLessOrEqual(pattern.Offset) + } + + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Sub.go b/src/arm/Sub.go index 6d222e7..f468cea 100644 --- a/src/arm/Sub.go +++ b/src/arm/Sub.go @@ -4,6 +4,5 @@ import "git.urbach.dev/cli/q/src/cpu" // SubRegisterNumber subtracts a number from the given register. func SubRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { - number &= 0b1111_1111_1111 - return 0b111100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) + return encodeRegisterNumberFlags(0b11, destination, source, number, false) } diff --git a/src/arm/Sub_test.go b/src/arm/Sub_test.go new file mode 100644 index 0000000..d6cb4f6 --- /dev/null +++ b/src/arm/Sub_test.go @@ -0,0 +1,26 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestSubRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, arm.X0, 1, 0xD1000400}, + } + + for _, pattern := range usagePatterns { + t.Logf("sub %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) + code := arm.SubRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/encode.go b/src/arm/encode.go new file mode 100644 index 0000000..312cc37 --- /dev/null +++ b/src/arm/encode.go @@ -0,0 +1,16 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// encodeRegisterNumberFlags performs addition or subtraction on the given register +// and optionally updates the condition flags based on the result. +func encodeRegisterNumberFlags(op uint32, destination cpu.Register, source cpu.Register, number int, flags bool) uint32 { + number &= 0b1111_1111_1111 + common := op<<30 | 0b100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) + + if flags { + return 1<<29 | common + } + + return common +} diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 9de618b..77cfe02 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -117,7 +117,7 @@ func (c *compiler) compileARM(x asm.Instruction) { switch x.Type { case asm.TypeRegisterNumber: operand := c.assembler.Param.RegisterNumber[x.Index] - c.append(arm.SubRegisterNumber(0b11111, operand.Register, operand.Number)) + c.append(arm.CompareRegisterNumber(operand.Register, operand.Number)) case asm.TypeRegisterRegister: panic("not implemented") } diff --git a/src/asmc/jumpARM.go b/src/asmc/jumpARM.go index 6819262..81a96ee 100644 --- a/src/asmc/jumpARM.go +++ b/src/asmc/jumpARM.go @@ -8,13 +8,12 @@ import ( ) func (c *compiler) jumpARM(x asm.Instruction) { + label := c.assembler.Param.Label[x.Index] mnemonic := x.Mnemonic position := Address(len(c.code)) - label := c.assembler.Param.Label[x.Index] pointer := &pointer{ Position: position, - OpSize: 0, Size: 4, } diff --git a/src/x86/Jump.go b/src/x86/Jump.go index feed711..9baa597 100644 --- a/src/x86/Jump.go +++ b/src/x86/Jump.go @@ -6,14 +6,14 @@ func Jump8(code []byte, offset int8) []byte { return append(code, 0xEB, byte(offset)) } -// JumpIfLess jumps if the result was less. -func Jump8IfLess(code []byte, offset int8) []byte { - return append(code, 0x7C, byte(offset)) +// JumpIfEqual jumps if the result was equal. +func Jump8IfEqual(code []byte, offset int8) []byte { + return append(code, 0x74, byte(offset)) } -// JumpIfLessOrEqual jumps if the result was less or equal. -func Jump8IfLessOrEqual(code []byte, offset int8) []byte { - return append(code, 0x7E, byte(offset)) +// JumpIfNotEqual jumps if the result was not equal. +func Jump8IfNotEqual(code []byte, offset int8) []byte { + return append(code, 0x75, byte(offset)) } // JumpIfGreater jumps if the result was greater. @@ -26,12 +26,12 @@ func Jump8IfGreaterOrEqual(code []byte, offset int8) []byte { return append(code, 0x7D, byte(offset)) } -// JumpIfEqual jumps if the result was equal. -func Jump8IfEqual(code []byte, offset int8) []byte { - return append(code, 0x74, byte(offset)) +// JumpIfLess jumps if the result was less. +func Jump8IfLess(code []byte, offset int8) []byte { + return append(code, 0x7C, byte(offset)) } -// JumpIfNotEqual jumps if the result was not equal. -func Jump8IfNotEqual(code []byte, offset int8) []byte { - return append(code, 0x75, byte(offset)) +// JumpIfLessOrEqual jumps if the result was less or equal. +func Jump8IfLessOrEqual(code []byte, offset int8) []byte { + return append(code, 0x7E, byte(offset)) } From d96c351b4b81a09e68dd5a39e341bb48d12471d5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 13 Mar 2025 16:02:09 +0100 Subject: [PATCH 0924/1012] Added more tests --- src/arm/Add.go | 3 +- src/arm/Add_test.go | 26 ++++++++++++++++ src/arm/Compare.go | 8 +++++ src/arm/Compare_test.go | 25 +++++++++++++++ src/arm/Jump.go | 40 ++++++++++++------------ src/arm/Jump_test.go | 68 +++++++++++++++++++++++++++++++++++++++++ src/arm/Sub.go | 3 +- src/arm/Sub_test.go | 26 ++++++++++++++++ src/arm/encode.go | 16 ++++++++++ src/asmc/compileARM.go | 2 +- src/asmc/jumpARM.go | 3 +- src/x86/Jump.go | 24 +++++++-------- 12 files changed, 205 insertions(+), 39 deletions(-) create mode 100644 src/arm/Add_test.go create mode 100644 src/arm/Compare.go create mode 100644 src/arm/Compare_test.go create mode 100644 src/arm/Jump_test.go create mode 100644 src/arm/Sub_test.go create mode 100644 src/arm/encode.go diff --git a/src/arm/Add.go b/src/arm/Add.go index c05e16e..dd868e9 100644 --- a/src/arm/Add.go +++ b/src/arm/Add.go @@ -4,6 +4,5 @@ import "git.urbach.dev/cli/q/src/cpu" // AddRegisterNumber adds a number to a register. func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { - number &= 0b1111_1111_1111 - return 0b100100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) + return encodeRegisterNumberFlags(0b10, destination, source, number, false) } diff --git a/src/arm/Add_test.go b/src/arm/Add_test.go new file mode 100644 index 0000000..9b403b3 --- /dev/null +++ b/src/arm/Add_test.go @@ -0,0 +1,26 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestAddRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, arm.X0, 1, 0x91000400}, + } + + for _, pattern := range usagePatterns { + t.Logf("add %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) + code := arm.AddRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Compare.go b/src/arm/Compare.go new file mode 100644 index 0000000..5fc07ec --- /dev/null +++ b/src/arm/Compare.go @@ -0,0 +1,8 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// CompareRegisterNumber is an alias for a subtraction that updates the conditional flags and discards the result. +func CompareRegisterNumber(register cpu.Register, number int) uint32 { + return encodeRegisterNumberFlags(0b11, 0b11111, register, number, true) +} diff --git a/src/arm/Compare_test.go b/src/arm/Compare_test.go new file mode 100644 index 0000000..7a3eca1 --- /dev/null +++ b/src/arm/Compare_test.go @@ -0,0 +1,25 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestCompareRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, 1, 0xF100041F}, + } + + for _, pattern := range usagePatterns { + t.Logf("cmp %s, %d", pattern.Source, pattern.Number) + code := arm.CompareRegisterNumber(pattern.Source, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Jump.go b/src/arm/Jump.go index 78a75db..8f3f807 100644 --- a/src/arm/Jump.go +++ b/src/arm/Jump.go @@ -6,26 +6,6 @@ func Jump(offset int) uint32 { return 0b000101<<26 | uint32(offset) } -// JumpIfLess jumps if the result was less. -func JumpIfLess(offset int) uint32 { - return branchCond(0b1001, offset) -} - -// JumpIfLessOrEqual jumps if the result was less or equal. -func JumpIfLessOrEqual(offset int) uint32 { - return branchCond(0b1101, offset) -} - -// JumpIfGreater jumps if the result was greater. -func JumpIfGreater(offset int) uint32 { - return branchCond(0b1100, offset) -} - -// JumpIfGreaterOrEqual jumps if the result was greater or equal. -func JumpIfGreaterOrEqual(offset int) uint32 { - return branchCond(0b1010, offset) -} - // JumpIfEqual jumps if the result was equal. func JumpIfEqual(offset int) uint32 { return branchCond(0b0000, offset) @@ -36,6 +16,26 @@ func JumpIfNotEqual(offset int) uint32 { return branchCond(0b0001, offset) } +// JumpIfGreater jumps if the result was greater. +func JumpIfGreater(offset int) uint32 { + return branchCond(0b1100, offset) +} + +// JumpIfGreaterOrEqual jumps if the result was greater or equal. +func JumpIfGreaterOrEqual(offset int) uint32 { + return branchCond(0b1010, offset) +} + +// JumpIfLess jumps if the result was less. +func JumpIfLess(offset int) uint32 { + return branchCond(0b1001, offset) +} + +// JumpIfLessOrEqual jumps if the result was less or equal. +func JumpIfLessOrEqual(offset int) uint32 { + return branchCond(0b1101, offset) +} + // branchCond performs a conditional branch to a PC-relative offset. func branchCond(cond uint32, offset int) uint32 { offset &= 0b111_1111_1111_1111_1111 diff --git a/src/arm/Jump_test.go b/src/arm/Jump_test.go new file mode 100644 index 0000000..423c568 --- /dev/null +++ b/src/arm/Jump_test.go @@ -0,0 +1,68 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/go/assert" +) + +func TestJump(t *testing.T) { + usagePatterns := []struct { + Type byte + Offset int + Code uint32 + }{ + {0, 0, 0x14000000}, + {0, 1, 0x14000001}, + {0, -1, 0x17FFFFFF}, + + {1, 0, 0x54000000}, + {1, 1, 0x54000020}, + {1, -1, 0x54FFFFE0}, + + {2, 0, 0x54000001}, + {2, 1, 0x54000021}, + {2, -1, 0x54FFFFE1}, + + {3, 0, 0x5400000C}, + {3, 1, 0x5400002C}, + {3, -1, 0x54FFFFEC}, + + {4, 0, 0x5400000A}, + {4, 1, 0x5400002A}, + {4, -1, 0x54FFFFEA}, + + {5, 0, 0x54000009}, + {5, 1, 0x54000029}, + {5, -1, 0x54FFFFE9}, + + {6, 0, 0x5400000D}, + {6, 1, 0x5400002D}, + {6, -1, 0x54FFFFED}, + } + + for _, pattern := range usagePatterns { + t.Logf("b %d", pattern.Offset) + var code uint32 + + switch pattern.Type { + case 0: + code = arm.Jump(pattern.Offset) + case 1: + code = arm.JumpIfEqual(pattern.Offset) + case 2: + code = arm.JumpIfNotEqual(pattern.Offset) + case 3: + code = arm.JumpIfGreater(pattern.Offset) + case 4: + code = arm.JumpIfGreaterOrEqual(pattern.Offset) + case 5: + code = arm.JumpIfLess(pattern.Offset) + case 6: + code = arm.JumpIfLessOrEqual(pattern.Offset) + } + + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Sub.go b/src/arm/Sub.go index 6d222e7..f468cea 100644 --- a/src/arm/Sub.go +++ b/src/arm/Sub.go @@ -4,6 +4,5 @@ import "git.urbach.dev/cli/q/src/cpu" // SubRegisterNumber subtracts a number from the given register. func SubRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { - number &= 0b1111_1111_1111 - return 0b111100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) + return encodeRegisterNumberFlags(0b11, destination, source, number, false) } diff --git a/src/arm/Sub_test.go b/src/arm/Sub_test.go new file mode 100644 index 0000000..d6cb4f6 --- /dev/null +++ b/src/arm/Sub_test.go @@ -0,0 +1,26 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestSubRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, arm.X0, 1, 0xD1000400}, + } + + for _, pattern := range usagePatterns { + t.Logf("sub %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) + code := arm.SubRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/encode.go b/src/arm/encode.go new file mode 100644 index 0000000..312cc37 --- /dev/null +++ b/src/arm/encode.go @@ -0,0 +1,16 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// encodeRegisterNumberFlags performs addition or subtraction on the given register +// and optionally updates the condition flags based on the result. +func encodeRegisterNumberFlags(op uint32, destination cpu.Register, source cpu.Register, number int, flags bool) uint32 { + number &= 0b1111_1111_1111 + common := op<<30 | 0b100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) + + if flags { + return 1<<29 | common + } + + return common +} diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 9de618b..77cfe02 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -117,7 +117,7 @@ func (c *compiler) compileARM(x asm.Instruction) { switch x.Type { case asm.TypeRegisterNumber: operand := c.assembler.Param.RegisterNumber[x.Index] - c.append(arm.SubRegisterNumber(0b11111, operand.Register, operand.Number)) + c.append(arm.CompareRegisterNumber(operand.Register, operand.Number)) case asm.TypeRegisterRegister: panic("not implemented") } diff --git a/src/asmc/jumpARM.go b/src/asmc/jumpARM.go index 6819262..81a96ee 100644 --- a/src/asmc/jumpARM.go +++ b/src/asmc/jumpARM.go @@ -8,13 +8,12 @@ import ( ) func (c *compiler) jumpARM(x asm.Instruction) { + label := c.assembler.Param.Label[x.Index] mnemonic := x.Mnemonic position := Address(len(c.code)) - label := c.assembler.Param.Label[x.Index] pointer := &pointer{ Position: position, - OpSize: 0, Size: 4, } diff --git a/src/x86/Jump.go b/src/x86/Jump.go index feed711..9baa597 100644 --- a/src/x86/Jump.go +++ b/src/x86/Jump.go @@ -6,14 +6,14 @@ func Jump8(code []byte, offset int8) []byte { return append(code, 0xEB, byte(offset)) } -// JumpIfLess jumps if the result was less. -func Jump8IfLess(code []byte, offset int8) []byte { - return append(code, 0x7C, byte(offset)) +// JumpIfEqual jumps if the result was equal. +func Jump8IfEqual(code []byte, offset int8) []byte { + return append(code, 0x74, byte(offset)) } -// JumpIfLessOrEqual jumps if the result was less or equal. -func Jump8IfLessOrEqual(code []byte, offset int8) []byte { - return append(code, 0x7E, byte(offset)) +// JumpIfNotEqual jumps if the result was not equal. +func Jump8IfNotEqual(code []byte, offset int8) []byte { + return append(code, 0x75, byte(offset)) } // JumpIfGreater jumps if the result was greater. @@ -26,12 +26,12 @@ func Jump8IfGreaterOrEqual(code []byte, offset int8) []byte { return append(code, 0x7D, byte(offset)) } -// JumpIfEqual jumps if the result was equal. -func Jump8IfEqual(code []byte, offset int8) []byte { - return append(code, 0x74, byte(offset)) +// JumpIfLess jumps if the result was less. +func Jump8IfLess(code []byte, offset int8) []byte { + return append(code, 0x7C, byte(offset)) } -// JumpIfNotEqual jumps if the result was not equal. -func Jump8IfNotEqual(code []byte, offset int8) []byte { - return append(code, 0x75, byte(offset)) +// JumpIfLessOrEqual jumps if the result was less or equal. +func Jump8IfLessOrEqual(code []byte, offset int8) []byte { + return append(code, 0x7E, byte(offset)) } From 14a867bc8b49b8321469374cae55ef7d750f153a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 13 Mar 2025 16:57:13 +0100 Subject: [PATCH 0925/1012] Added label type --- src/asm/Instructions.go | 4 ++-- src/asm/Label.go | 7 +++---- src/asm/LabelType.go | 13 +++++++++++++ src/asm/MemoryLabel.go | 6 +++--- src/asm/RegisterLabel.go | 6 +++--- src/asmc/compileARM.go | 12 +++++++----- src/asmc/move.go | 8 +++----- src/asmc/store.go | 2 +- src/compiler/finalize.go | 5 ++++- src/core/AddBytes.go | 8 +++++--- src/core/Compile.go | 8 +++++++- src/core/CompileAssert.go | 6 +++--- src/core/CompileCall.go | 2 +- src/core/CompileCondition.go | 6 +++--- src/core/CompileDelete.go | 2 +- src/core/CompileFor.go | 4 ++-- src/core/CompileIf.go | 8 ++++---- src/core/CompileLoop.go | 2 +- src/core/CompileNew.go | 2 +- src/core/CompileSwitch.go | 6 +++--- src/core/CreateLabel.go | 10 ++++++++-- src/core/EvaluateDot.go | 7 +++++-- src/core/EvaluateToken.go | 8 ++++++-- src/core/JumpIfFalse.go | 2 +- src/core/JumpIfTrue.go | 2 +- src/eval/Label.go | 7 +++++-- src/register/AddLabel.go | 2 +- src/register/DLLCall.go | 2 +- src/register/Jump.go | 2 +- src/register/Label.go | 2 +- src/register/MemoryLabel.go | 2 +- src/register/RegisterLabel.go | 2 +- 32 files changed, 102 insertions(+), 63 deletions(-) create mode 100644 src/asm/LabelType.go diff --git a/src/asm/Instructions.go b/src/asm/Instructions.go index 1066e10..aebd962 100644 --- a/src/asm/Instructions.go +++ b/src/asm/Instructions.go @@ -2,12 +2,12 @@ package asm // Comment adds a comment at the current position. func (a *Assembler) Comment(text string) { - a.Label(COMMENT, text) + a.Label(COMMENT, Label{Name: text, Type: CommentLabel}) } // DLLCall calls a function in a DLL file. func (a *Assembler) DLLCall(name string) { - a.Label(DLLCALL, name) + a.Label(DLLCALL, Label{Name: name, Type: ExternLabel}) } // Return returns back to the caller. diff --git a/src/asm/Label.go b/src/asm/Label.go index b55bd96..0c097c9 100644 --- a/src/asm/Label.go +++ b/src/asm/Label.go @@ -3,6 +3,7 @@ package asm // Label represents a jump label. type Label struct { Name string + Type LabelType } // String returns a human readable version. @@ -11,14 +12,12 @@ func (data *Label) String() string { } // Label adds an instruction using a label. -func (a *Assembler) Label(mnemonic Mnemonic, name string) { +func (a *Assembler) Label(mnemonic Mnemonic, label Label) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, Type: TypeLabel, Index: Index(len(a.Param.Label)), }) - a.Param.Label = append(a.Param.Label, Label{ - Name: name, - }) + a.Param.Label = append(a.Param.Label, label) } diff --git a/src/asm/LabelType.go b/src/asm/LabelType.go new file mode 100644 index 0000000..db0875f --- /dev/null +++ b/src/asm/LabelType.go @@ -0,0 +1,13 @@ +package asm + +// LabelType shows what the label was used for. +type LabelType uint8 + +const ( + UnknownLabel LabelType = iota + CommentLabel + ControlLabel + DataLabel + FunctionLabel + ExternLabel +) diff --git a/src/asm/MemoryLabel.go b/src/asm/MemoryLabel.go index 1402330..e4038e8 100644 --- a/src/asm/MemoryLabel.go +++ b/src/asm/MemoryLabel.go @@ -2,17 +2,17 @@ package asm // MemoryLabel operates with a memory address and a number. type MemoryLabel struct { - Label string + Label Label Address Memory } // String returns a human readable version. func (data *MemoryLabel) String() string { - return data.Address.Format(data.Label) + return data.Address.Format(data.Label.Name) } // MemoryLabel adds an instruction with a memory address and a label. -func (a *Assembler) MemoryLabel(mnemonic Mnemonic, address Memory, label string) { +func (a *Assembler) MemoryLabel(mnemonic Mnemonic, address Memory, label Label) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, Type: TypeMemoryLabel, diff --git a/src/asm/RegisterLabel.go b/src/asm/RegisterLabel.go index f5cb956..dd0560b 100644 --- a/src/asm/RegisterLabel.go +++ b/src/asm/RegisterLabel.go @@ -8,17 +8,17 @@ import ( // RegisterLabel operates with a register and a label. type RegisterLabel struct { - Label string + Label Label Register cpu.Register } // String returns a human readable version. func (data *RegisterLabel) String() string { - return fmt.Sprintf("%s, %s", data.Register, data.Label) + return fmt.Sprintf("%s, %s", data.Register, data.Label.Name) } // RegisterLabel adds an instruction with a register and a label. -func (a *Assembler) RegisterLabel(mnemonic Mnemonic, register cpu.Register, label string) { +func (a *Assembler) RegisterLabel(mnemonic Mnemonic, register cpu.Register, label Label) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, Type: TypeRegisterLabel, diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 77cfe02..45a2d73 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -3,7 +3,6 @@ package asmc import ( "encoding/binary" "fmt" - "strings" "git.urbach.dev/cli/q/src/arm" "git.urbach.dev/cli/q/src/asm" @@ -44,8 +43,11 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.LABEL: label := c.assembler.Param.Label[x.Index] c.codeLabels[label.Name] = Address(len(c.code)) - c.append(arm.StorePair(arm.FP, arm.LR, arm.SP, -16)) - c.append(arm.MoveRegisterRegister(arm.FP, arm.SP)) + + if label.Type == asm.FunctionLabel { + c.append(arm.StorePair(arm.FP, arm.LR, arm.SP, -16)) + c.append(arm.MoveRegisterRegister(arm.FP, arm.SP)) + } case asm.LOAD: switch x.Type { @@ -140,13 +142,13 @@ func (c *compiler) compileARM(x asm.Instruction) { position := Address(len(c.code)) c.append(arm.LoadAddress(operands.Register, 0)) - if strings.HasPrefix(operands.Label, "data ") { + if operands.Label.Type == asm.DataLabel { c.dataPointers = append(c.dataPointers, &pointer{ Position: position, OpSize: 0, Size: 4, Resolve: func() Address { - destination, exists := c.dataLabels[operands.Label] + destination, exists := c.dataLabels[operands.Label.Name] if !exists { panic("unknown label") diff --git a/src/asmc/move.go b/src/asmc/move.go index 256ede6..3963ba5 100644 --- a/src/asmc/move.go +++ b/src/asmc/move.go @@ -1,8 +1,6 @@ package asmc import ( - "strings" - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/x86" ) @@ -25,13 +23,13 @@ func (c *compiler) move(x asm.Instruction) { position := end - 4 opSize := position - start - if strings.HasPrefix(operands.Label, "data ") { + if operands.Label.Type == asm.DataLabel { c.dataPointers = append(c.dataPointers, &pointer{ Position: position, OpSize: uint8(opSize), Size: uint8(4), Resolve: func() Address { - destination, exists := c.dataLabels[operands.Label] + destination, exists := c.dataLabels[operands.Label.Name] if !exists { panic("unknown label") @@ -48,7 +46,7 @@ func (c *compiler) move(x asm.Instruction) { OpSize: uint8(opSize), Size: uint8(4), Resolve: func() Address { - destination, exists := c.codeLabels[operands.Label] + destination, exists := c.codeLabels[operands.Label.Name] if !exists { panic("unknown label") diff --git a/src/asmc/store.go b/src/asmc/store.go index b5d338e..42352c4 100644 --- a/src/asmc/store.go +++ b/src/asmc/store.go @@ -34,7 +34,7 @@ func (c *compiler) store(x asm.Instruction) { OpSize: uint8(opSize), Size: uint8(size), Resolve: func() Address { - destination, exists := c.codeLabels[operands.Label] + destination, exists := c.codeLabels[operands.Label.Name] if !exists { panic("unknown label") diff --git a/src/compiler/finalize.go b/src/compiler/finalize.go index 45d6c54..16f24de 100644 --- a/src/compiler/finalize.go +++ b/src/compiler/finalize.go @@ -45,7 +45,10 @@ func (r *Result) finalize() { } }) - final.Label(asm.LABEL, "_crash") + final.Label(asm.LABEL, asm.Label{ + Name: "_crash", + Type: asm.ControlLabel, + }) switch config.TargetOS { case config.Linux: diff --git a/src/core/AddBytes.go b/src/core/AddBytes.go index a6c3955..93abb79 100644 --- a/src/core/AddBytes.go +++ b/src/core/AddBytes.go @@ -1,9 +1,11 @@ package core +import "git.urbach.dev/cli/q/src/asm" + // AddBytes adds a sequence of bytes and returns its address as a label. -func (f *Function) AddBytes(value []byte) string { +func (f *Function) AddBytes(value []byte) asm.Label { f.count.data++ - label := f.CreateLabel("data", f.count.data) - f.Assembler.SetData(label, value) + label := f.CreateLabel("data", f.count.data, asm.DataLabel) + f.Assembler.SetData(label.Name, value) return label } diff --git a/src/core/Compile.go b/src/core/Compile.go index 1a28253..6562c24 100644 --- a/src/core/Compile.go +++ b/src/core/Compile.go @@ -1,8 +1,14 @@ package core +import "git.urbach.dev/cli/q/src/asm" + // Compile turns a function into machine code. func (f *Function) Compile() { - f.AddLabel(f.UniqueName) + f.AddLabel(asm.Label{ + Name: f.UniqueName, + Type: asm.FunctionLabel, + }) + f.Err = f.CompileTokens(f.Body) f.Return() diff --git a/src/core/CompileAssert.go b/src/core/CompileAssert.go index a761982..7614bd9 100644 --- a/src/core/CompileAssert.go +++ b/src/core/CompileAssert.go @@ -8,8 +8,8 @@ import ( // CompileAssert compiles an assertion. func (f *Function) CompileAssert(assert *ast.Assert) error { f.count.assert++ - success := f.CreateLabel("assert true", f.count.assert) - fail := f.CreateLabel("assert false", f.count.assert) + success := f.CreateLabel("assert true", f.count.assert, asm.ControlLabel) + fail := f.CreateLabel("assert false", f.count.assert, asm.ControlLabel) err := f.CompileCondition(assert.Condition, success, fail) if err != nil { @@ -20,7 +20,7 @@ func (f *Function) CompileAssert(assert *ast.Assert) error { f.Defer(func() { f.AddLabel(fail) - f.Jump(asm.JUMP, "_crash") + f.Jump(asm.JUMP, asm.Label{Name: "_crash", Type: asm.ControlLabel}) }) return err diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 004bf72..fc5937b 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -58,7 +58,7 @@ func (f *Function) CompileCall(root *expression.Expression) ([]*Parameter, error switch value := value.(type) { case *eval.Label: - fn := f.All.Functions[value.Label] + fn := f.All.Functions[value.Label.Name] if len(parameters) != len(fn.Input) { return nil, errors.New(&errors.ParameterCountMismatch{Function: fn.Name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, root.Children[0].Token.End()) diff --git a/src/core/CompileCondition.go b/src/core/CompileCondition.go index 3e14d57..9b2f943 100644 --- a/src/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -12,11 +12,11 @@ import ( ) // CompileCondition inserts code to jump to the start label or end label depending on the truth of the condition. -func (f *Function) CompileCondition(condition *expression.Expression, successLabel string, failLabel string) error { +func (f *Function) CompileCondition(condition *expression.Expression, successLabel asm.Label, failLabel asm.Label) error { switch condition.Token.Kind { case token.LogicalOr: f.count.subBranch++ - leftFailLabel := f.CreateLabel("false", f.count.subBranch) + leftFailLabel := f.CreateLabel("false", f.count.subBranch, asm.ControlLabel) // Left left := condition.Children[0] @@ -43,7 +43,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab case token.LogicalAnd: f.count.subBranch++ - leftSuccessLabel := f.CreateLabel("true", f.count.subBranch) + leftSuccessLabel := f.CreateLabel("true", f.count.subBranch, asm.ControlLabel) // Left left := condition.Children[0] diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index 5036e38..b834d06 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -24,7 +24,7 @@ func (f *Function) CompileDelete(root *expression.Expression) error { f.RegisterNumber(asm.MOVE, f.CPU.Input[1], variable.Value.Typ.(*types.Pointer).To.Size()) free := f.All.Functions["mem.free"] f.BeforeCall() - f.Label(asm.CALL, "mem.free") + f.Label(asm.CALL, asm.Label{Name: "mem.free", Type: asm.FunctionLabel}) f.AfterCall(f.CPU.Input[:2]) f.Dependencies = append(f.Dependencies, free) return nil diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index a820b9d..2bdf9da 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -23,8 +23,8 @@ func (f *Function) CompileFor(loop *ast.For) error { f.count.loop++ var ( - label = f.CreateLabel("for", f.count.loop) - labelEnd = f.CreateLabel("for end", f.count.loop) + label = f.CreateLabel("for", f.count.loop, asm.ControlLabel) + labelEnd = f.CreateLabel("for end", f.count.loop, asm.ControlLabel) counter cpu.Register from *expression.Expression to *expression.Expression diff --git a/src/core/CompileIf.go b/src/core/CompileIf.go index 1474eff..393b782 100644 --- a/src/core/CompileIf.go +++ b/src/core/CompileIf.go @@ -14,9 +14,9 @@ func (f *Function) CompileIf(branch *ast.If) error { f.count.branch++ var ( - end string - success = f.CreateLabel("if true", f.count.branch) - fail = f.CreateLabel("if false", f.count.branch) + end asm.Label + success = f.CreateLabel("if true", f.count.branch, asm.ControlLabel) + fail = f.CreateLabel("if false", f.count.branch, asm.ControlLabel) err = f.CompileCondition(branch.Condition, success, fail) ) @@ -33,7 +33,7 @@ func (f *Function) CompileIf(branch *ast.If) error { } if branch.Else != nil { - end = f.CreateLabel("if end", f.count.branch) + end = f.CreateLabel("if end", f.count.branch, asm.ControlLabel) f.Jump(asm.JUMP, end) } diff --git a/src/core/CompileLoop.go b/src/core/CompileLoop.go index 923e690..a3a9c4d 100644 --- a/src/core/CompileLoop.go +++ b/src/core/CompileLoop.go @@ -12,7 +12,7 @@ func (f *Function) CompileLoop(loop *ast.Loop) error { } f.count.loop++ - label := f.CreateLabel("loop", f.count.loop) + label := f.CreateLabel("loop", f.count.loop, asm.ControlLabel) f.AddLabel(label) scope := f.PushScope(loop.Body, f.File.Bytes) scope.InLoop = true diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index 0a99a43..6d2ae30 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -47,7 +47,7 @@ func (f *Function) CompileNew(root *expression.Expression) (types.Type, error) { f.RegisterNumber(asm.MOVE, f.CPU.Input[0], typ.Size()) alloc := f.All.Functions["mem.alloc"] f.BeforeCall() - f.Label(asm.CALL, "mem.alloc") + f.Label(asm.CALL, asm.Label{Name: "mem.alloc", Type: asm.FunctionLabel}) f.AfterCall(f.CPU.Input[:1]) f.Dependencies = append(f.Dependencies, alloc) return &types.Pointer{To: typ}, nil diff --git a/src/core/CompileSwitch.go b/src/core/CompileSwitch.go index 7196f8a..b3bbee6 100644 --- a/src/core/CompileSwitch.go +++ b/src/core/CompileSwitch.go @@ -8,7 +8,7 @@ import ( // CompileSwitch compiles a multi-branch instruction. func (f *Function) CompileSwitch(s *ast.Switch) error { f.count.multiBranch++ - end := f.CreateLabel("switch end", f.count.multiBranch) + end := f.CreateLabel("switch end", f.count.multiBranch, asm.ControlLabel) for _, branch := range s.Cases { if branch.Condition == nil { @@ -26,8 +26,8 @@ func (f *Function) CompileSwitch(s *ast.Switch) error { f.count.branch++ var ( - success = f.CreateLabel("case true", f.count.branch) - fail = f.CreateLabel("case false", f.count.branch) + success = f.CreateLabel("case true", f.count.branch, asm.ControlLabel) + fail = f.CreateLabel("case false", f.count.branch, asm.ControlLabel) err = f.CompileCondition(branch.Condition, success, fail) ) diff --git a/src/core/CreateLabel.go b/src/core/CreateLabel.go index 5342638..2d1690e 100644 --- a/src/core/CreateLabel.go +++ b/src/core/CreateLabel.go @@ -3,10 +3,12 @@ package core import ( "strconv" "strings" + + "git.urbach.dev/cli/q/src/asm" ) // CreateLabel creates a label that is tied to this function by using a suffix. -func (f *Function) CreateLabel(prefix string, count counter) string { +func (f *Function) CreateLabel(prefix string, count counter, typ asm.LabelType) asm.Label { tmp := strings.Builder{} tmp.WriteString(prefix) tmp.WriteString(" ") @@ -14,5 +16,9 @@ func (f *Function) CreateLabel(prefix string, count counter) string { tmp.WriteString(" [") tmp.WriteString(f.UniqueName) tmp.WriteString("]") - return tmp.String() + + return asm.Label{ + Name: tmp.String(), + Type: typ, + } } diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go index f968839..3bc55c2 100644 --- a/src/core/EvaluateDot.go +++ b/src/core/EvaluateDot.go @@ -79,8 +79,11 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) f.Dependencies = append(f.Dependencies, function) value := &eval.Label{ - Typ: types.AnyPointer, - Label: label, + Typ: types.AnyPointer, + Label: asm.Label{ + Name: label, + Type: asm.FunctionLabel, + }, } return value, nil diff --git a/src/core/EvaluateToken.go b/src/core/EvaluateToken.go index f3125c8..0b1aa30 100644 --- a/src/core/EvaluateToken.go +++ b/src/core/EvaluateToken.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "fmt" + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/token" @@ -48,8 +49,11 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { f.Dependencies = append(f.Dependencies, function) value := &eval.Label{ - Typ: types.AnyPointer, - Label: function.UniqueName, + Typ: types.AnyPointer, + Label: asm.Label{ + Name: function.UniqueName, + Type: asm.FunctionLabel, + }, } return value, nil diff --git a/src/core/JumpIfFalse.go b/src/core/JumpIfFalse.go index 61c649d..5628b19 100644 --- a/src/core/JumpIfFalse.go +++ b/src/core/JumpIfFalse.go @@ -6,7 +6,7 @@ import ( ) // JumpIfFalse jumps to the label if the previous comparison was false. -func (f *Function) JumpIfFalse(operator token.Kind, label string) { +func (f *Function) JumpIfFalse(operator token.Kind, label asm.Label) { switch operator { case token.Equal: f.Jump(asm.JNE, label) diff --git a/src/core/JumpIfTrue.go b/src/core/JumpIfTrue.go index 5c8eddc..a9cc35d 100644 --- a/src/core/JumpIfTrue.go +++ b/src/core/JumpIfTrue.go @@ -6,7 +6,7 @@ import ( ) // JumpIfTrue jumps to the label if the previous comparison was true. -func (f *Function) JumpIfTrue(operator token.Kind, label string) { +func (f *Function) JumpIfTrue(operator token.Kind, label asm.Label) { switch operator { case token.Equal: f.Jump(asm.JE, label) diff --git a/src/eval/Label.go b/src/eval/Label.go index e88dbd3..c075ba9 100644 --- a/src/eval/Label.go +++ b/src/eval/Label.go @@ -1,11 +1,14 @@ package eval -import "git.urbach.dev/cli/q/src/types" +import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/types" +) // Label is a named pointer to a code or data section. type Label struct { Typ types.Type - Label string + Label asm.Label } func (v *Label) String() string { diff --git a/src/register/AddLabel.go b/src/register/AddLabel.go index 797bb30..1308bbb 100644 --- a/src/register/AddLabel.go +++ b/src/register/AddLabel.go @@ -2,7 +2,7 @@ package register import "git.urbach.dev/cli/q/src/asm" -func (f *Machine) AddLabel(label string) { +func (f *Machine) AddLabel(label asm.Label) { f.Assembler.Label(asm.LABEL, label) f.postInstruction() } diff --git a/src/register/DLLCall.go b/src/register/DLLCall.go index 4b80f04..80d7b12 100644 --- a/src/register/DLLCall.go +++ b/src/register/DLLCall.go @@ -3,7 +3,7 @@ package register import "git.urbach.dev/cli/q/src/asm" func (f *Machine) DLLCall(label string) { - f.Assembler.Label(asm.DLLCALL, label) + f.Assembler.Label(asm.DLLCALL, asm.Label{Name: label, Type: asm.UnknownLabel}) f.UseRegister(f.CPU.Output[0]) f.postInstruction() } diff --git a/src/register/Jump.go b/src/register/Jump.go index ccf1f7c..255ecd1 100644 --- a/src/register/Jump.go +++ b/src/register/Jump.go @@ -2,7 +2,7 @@ package register import "git.urbach.dev/cli/q/src/asm" -func (f *Machine) Jump(mnemonic asm.Mnemonic, label string) { +func (f *Machine) Jump(mnemonic asm.Mnemonic, label asm.Label) { f.Assembler.Label(mnemonic, label) f.postInstruction() } diff --git a/src/register/Label.go b/src/register/Label.go index 4dcea1a..1fdaf43 100644 --- a/src/register/Label.go +++ b/src/register/Label.go @@ -2,7 +2,7 @@ package register import "git.urbach.dev/cli/q/src/asm" -func (f *Machine) Label(mnemonic asm.Mnemonic, label string) { +func (f *Machine) Label(mnemonic asm.Mnemonic, label asm.Label) { f.Assembler.Label(mnemonic, label) f.postInstruction() } diff --git a/src/register/MemoryLabel.go b/src/register/MemoryLabel.go index 520ad5e..dc93c8d 100644 --- a/src/register/MemoryLabel.go +++ b/src/register/MemoryLabel.go @@ -2,7 +2,7 @@ package register import "git.urbach.dev/cli/q/src/asm" -func (f *Machine) MemoryLabel(mnemonic asm.Mnemonic, a asm.Memory, b string) { +func (f *Machine) MemoryLabel(mnemonic asm.Mnemonic, a asm.Memory, b asm.Label) { f.Assembler.MemoryLabel(mnemonic, a, b) f.postInstruction() } diff --git a/src/register/RegisterLabel.go b/src/register/RegisterLabel.go index 64a0b1a..cd5a020 100644 --- a/src/register/RegisterLabel.go +++ b/src/register/RegisterLabel.go @@ -5,7 +5,7 @@ import ( "git.urbach.dev/cli/q/src/cpu" ) -func (f *Machine) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { +func (f *Machine) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label asm.Label) { f.Assembler.RegisterLabel(mnemonic, register, label) if mnemonic == asm.MOVE { From c1913d99d01c3dedd9ab6a87bab098cab234ab93 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 13 Mar 2025 16:57:13 +0100 Subject: [PATCH 0926/1012] Added label type --- src/asm/Instructions.go | 4 ++-- src/asm/Label.go | 7 +++---- src/asm/LabelType.go | 13 +++++++++++++ src/asm/MemoryLabel.go | 6 +++--- src/asm/RegisterLabel.go | 6 +++--- src/asmc/compileARM.go | 12 +++++++----- src/asmc/move.go | 8 +++----- src/asmc/store.go | 2 +- src/compiler/finalize.go | 5 ++++- src/core/AddBytes.go | 8 +++++--- src/core/Compile.go | 8 +++++++- src/core/CompileAssert.go | 6 +++--- src/core/CompileCall.go | 2 +- src/core/CompileCondition.go | 6 +++--- src/core/CompileDelete.go | 2 +- src/core/CompileFor.go | 4 ++-- src/core/CompileIf.go | 8 ++++---- src/core/CompileLoop.go | 2 +- src/core/CompileNew.go | 2 +- src/core/CompileSwitch.go | 6 +++--- src/core/CreateLabel.go | 10 ++++++++-- src/core/EvaluateDot.go | 7 +++++-- src/core/EvaluateToken.go | 8 ++++++-- src/core/JumpIfFalse.go | 2 +- src/core/JumpIfTrue.go | 2 +- src/eval/Label.go | 7 +++++-- src/register/AddLabel.go | 2 +- src/register/DLLCall.go | 2 +- src/register/Jump.go | 2 +- src/register/Label.go | 2 +- src/register/MemoryLabel.go | 2 +- src/register/RegisterLabel.go | 2 +- 32 files changed, 102 insertions(+), 63 deletions(-) create mode 100644 src/asm/LabelType.go diff --git a/src/asm/Instructions.go b/src/asm/Instructions.go index 1066e10..aebd962 100644 --- a/src/asm/Instructions.go +++ b/src/asm/Instructions.go @@ -2,12 +2,12 @@ package asm // Comment adds a comment at the current position. func (a *Assembler) Comment(text string) { - a.Label(COMMENT, text) + a.Label(COMMENT, Label{Name: text, Type: CommentLabel}) } // DLLCall calls a function in a DLL file. func (a *Assembler) DLLCall(name string) { - a.Label(DLLCALL, name) + a.Label(DLLCALL, Label{Name: name, Type: ExternLabel}) } // Return returns back to the caller. diff --git a/src/asm/Label.go b/src/asm/Label.go index b55bd96..0c097c9 100644 --- a/src/asm/Label.go +++ b/src/asm/Label.go @@ -3,6 +3,7 @@ package asm // Label represents a jump label. type Label struct { Name string + Type LabelType } // String returns a human readable version. @@ -11,14 +12,12 @@ func (data *Label) String() string { } // Label adds an instruction using a label. -func (a *Assembler) Label(mnemonic Mnemonic, name string) { +func (a *Assembler) Label(mnemonic Mnemonic, label Label) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, Type: TypeLabel, Index: Index(len(a.Param.Label)), }) - a.Param.Label = append(a.Param.Label, Label{ - Name: name, - }) + a.Param.Label = append(a.Param.Label, label) } diff --git a/src/asm/LabelType.go b/src/asm/LabelType.go new file mode 100644 index 0000000..db0875f --- /dev/null +++ b/src/asm/LabelType.go @@ -0,0 +1,13 @@ +package asm + +// LabelType shows what the label was used for. +type LabelType uint8 + +const ( + UnknownLabel LabelType = iota + CommentLabel + ControlLabel + DataLabel + FunctionLabel + ExternLabel +) diff --git a/src/asm/MemoryLabel.go b/src/asm/MemoryLabel.go index 1402330..e4038e8 100644 --- a/src/asm/MemoryLabel.go +++ b/src/asm/MemoryLabel.go @@ -2,17 +2,17 @@ package asm // MemoryLabel operates with a memory address and a number. type MemoryLabel struct { - Label string + Label Label Address Memory } // String returns a human readable version. func (data *MemoryLabel) String() string { - return data.Address.Format(data.Label) + return data.Address.Format(data.Label.Name) } // MemoryLabel adds an instruction with a memory address and a label. -func (a *Assembler) MemoryLabel(mnemonic Mnemonic, address Memory, label string) { +func (a *Assembler) MemoryLabel(mnemonic Mnemonic, address Memory, label Label) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, Type: TypeMemoryLabel, diff --git a/src/asm/RegisterLabel.go b/src/asm/RegisterLabel.go index f5cb956..dd0560b 100644 --- a/src/asm/RegisterLabel.go +++ b/src/asm/RegisterLabel.go @@ -8,17 +8,17 @@ import ( // RegisterLabel operates with a register and a label. type RegisterLabel struct { - Label string + Label Label Register cpu.Register } // String returns a human readable version. func (data *RegisterLabel) String() string { - return fmt.Sprintf("%s, %s", data.Register, data.Label) + return fmt.Sprintf("%s, %s", data.Register, data.Label.Name) } // RegisterLabel adds an instruction with a register and a label. -func (a *Assembler) RegisterLabel(mnemonic Mnemonic, register cpu.Register, label string) { +func (a *Assembler) RegisterLabel(mnemonic Mnemonic, register cpu.Register, label Label) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: mnemonic, Type: TypeRegisterLabel, diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 77cfe02..45a2d73 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -3,7 +3,6 @@ package asmc import ( "encoding/binary" "fmt" - "strings" "git.urbach.dev/cli/q/src/arm" "git.urbach.dev/cli/q/src/asm" @@ -44,8 +43,11 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.LABEL: label := c.assembler.Param.Label[x.Index] c.codeLabels[label.Name] = Address(len(c.code)) - c.append(arm.StorePair(arm.FP, arm.LR, arm.SP, -16)) - c.append(arm.MoveRegisterRegister(arm.FP, arm.SP)) + + if label.Type == asm.FunctionLabel { + c.append(arm.StorePair(arm.FP, arm.LR, arm.SP, -16)) + c.append(arm.MoveRegisterRegister(arm.FP, arm.SP)) + } case asm.LOAD: switch x.Type { @@ -140,13 +142,13 @@ func (c *compiler) compileARM(x asm.Instruction) { position := Address(len(c.code)) c.append(arm.LoadAddress(operands.Register, 0)) - if strings.HasPrefix(operands.Label, "data ") { + if operands.Label.Type == asm.DataLabel { c.dataPointers = append(c.dataPointers, &pointer{ Position: position, OpSize: 0, Size: 4, Resolve: func() Address { - destination, exists := c.dataLabels[operands.Label] + destination, exists := c.dataLabels[operands.Label.Name] if !exists { panic("unknown label") diff --git a/src/asmc/move.go b/src/asmc/move.go index 256ede6..3963ba5 100644 --- a/src/asmc/move.go +++ b/src/asmc/move.go @@ -1,8 +1,6 @@ package asmc import ( - "strings" - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/x86" ) @@ -25,13 +23,13 @@ func (c *compiler) move(x asm.Instruction) { position := end - 4 opSize := position - start - if strings.HasPrefix(operands.Label, "data ") { + if operands.Label.Type == asm.DataLabel { c.dataPointers = append(c.dataPointers, &pointer{ Position: position, OpSize: uint8(opSize), Size: uint8(4), Resolve: func() Address { - destination, exists := c.dataLabels[operands.Label] + destination, exists := c.dataLabels[operands.Label.Name] if !exists { panic("unknown label") @@ -48,7 +46,7 @@ func (c *compiler) move(x asm.Instruction) { OpSize: uint8(opSize), Size: uint8(4), Resolve: func() Address { - destination, exists := c.codeLabels[operands.Label] + destination, exists := c.codeLabels[operands.Label.Name] if !exists { panic("unknown label") diff --git a/src/asmc/store.go b/src/asmc/store.go index b5d338e..42352c4 100644 --- a/src/asmc/store.go +++ b/src/asmc/store.go @@ -34,7 +34,7 @@ func (c *compiler) store(x asm.Instruction) { OpSize: uint8(opSize), Size: uint8(size), Resolve: func() Address { - destination, exists := c.codeLabels[operands.Label] + destination, exists := c.codeLabels[operands.Label.Name] if !exists { panic("unknown label") diff --git a/src/compiler/finalize.go b/src/compiler/finalize.go index 45d6c54..16f24de 100644 --- a/src/compiler/finalize.go +++ b/src/compiler/finalize.go @@ -45,7 +45,10 @@ func (r *Result) finalize() { } }) - final.Label(asm.LABEL, "_crash") + final.Label(asm.LABEL, asm.Label{ + Name: "_crash", + Type: asm.ControlLabel, + }) switch config.TargetOS { case config.Linux: diff --git a/src/core/AddBytes.go b/src/core/AddBytes.go index a6c3955..93abb79 100644 --- a/src/core/AddBytes.go +++ b/src/core/AddBytes.go @@ -1,9 +1,11 @@ package core +import "git.urbach.dev/cli/q/src/asm" + // AddBytes adds a sequence of bytes and returns its address as a label. -func (f *Function) AddBytes(value []byte) string { +func (f *Function) AddBytes(value []byte) asm.Label { f.count.data++ - label := f.CreateLabel("data", f.count.data) - f.Assembler.SetData(label, value) + label := f.CreateLabel("data", f.count.data, asm.DataLabel) + f.Assembler.SetData(label.Name, value) return label } diff --git a/src/core/Compile.go b/src/core/Compile.go index 1a28253..6562c24 100644 --- a/src/core/Compile.go +++ b/src/core/Compile.go @@ -1,8 +1,14 @@ package core +import "git.urbach.dev/cli/q/src/asm" + // Compile turns a function into machine code. func (f *Function) Compile() { - f.AddLabel(f.UniqueName) + f.AddLabel(asm.Label{ + Name: f.UniqueName, + Type: asm.FunctionLabel, + }) + f.Err = f.CompileTokens(f.Body) f.Return() diff --git a/src/core/CompileAssert.go b/src/core/CompileAssert.go index a761982..7614bd9 100644 --- a/src/core/CompileAssert.go +++ b/src/core/CompileAssert.go @@ -8,8 +8,8 @@ import ( // CompileAssert compiles an assertion. func (f *Function) CompileAssert(assert *ast.Assert) error { f.count.assert++ - success := f.CreateLabel("assert true", f.count.assert) - fail := f.CreateLabel("assert false", f.count.assert) + success := f.CreateLabel("assert true", f.count.assert, asm.ControlLabel) + fail := f.CreateLabel("assert false", f.count.assert, asm.ControlLabel) err := f.CompileCondition(assert.Condition, success, fail) if err != nil { @@ -20,7 +20,7 @@ func (f *Function) CompileAssert(assert *ast.Assert) error { f.Defer(func() { f.AddLabel(fail) - f.Jump(asm.JUMP, "_crash") + f.Jump(asm.JUMP, asm.Label{Name: "_crash", Type: asm.ControlLabel}) }) return err diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 004bf72..fc5937b 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -58,7 +58,7 @@ func (f *Function) CompileCall(root *expression.Expression) ([]*Parameter, error switch value := value.(type) { case *eval.Label: - fn := f.All.Functions[value.Label] + fn := f.All.Functions[value.Label.Name] if len(parameters) != len(fn.Input) { return nil, errors.New(&errors.ParameterCountMismatch{Function: fn.Name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, root.Children[0].Token.End()) diff --git a/src/core/CompileCondition.go b/src/core/CompileCondition.go index 3e14d57..9b2f943 100644 --- a/src/core/CompileCondition.go +++ b/src/core/CompileCondition.go @@ -12,11 +12,11 @@ import ( ) // CompileCondition inserts code to jump to the start label or end label depending on the truth of the condition. -func (f *Function) CompileCondition(condition *expression.Expression, successLabel string, failLabel string) error { +func (f *Function) CompileCondition(condition *expression.Expression, successLabel asm.Label, failLabel asm.Label) error { switch condition.Token.Kind { case token.LogicalOr: f.count.subBranch++ - leftFailLabel := f.CreateLabel("false", f.count.subBranch) + leftFailLabel := f.CreateLabel("false", f.count.subBranch, asm.ControlLabel) // Left left := condition.Children[0] @@ -43,7 +43,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab case token.LogicalAnd: f.count.subBranch++ - leftSuccessLabel := f.CreateLabel("true", f.count.subBranch) + leftSuccessLabel := f.CreateLabel("true", f.count.subBranch, asm.ControlLabel) // Left left := condition.Children[0] diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index 5036e38..b834d06 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -24,7 +24,7 @@ func (f *Function) CompileDelete(root *expression.Expression) error { f.RegisterNumber(asm.MOVE, f.CPU.Input[1], variable.Value.Typ.(*types.Pointer).To.Size()) free := f.All.Functions["mem.free"] f.BeforeCall() - f.Label(asm.CALL, "mem.free") + f.Label(asm.CALL, asm.Label{Name: "mem.free", Type: asm.FunctionLabel}) f.AfterCall(f.CPU.Input[:2]) f.Dependencies = append(f.Dependencies, free) return nil diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index a820b9d..2bdf9da 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -23,8 +23,8 @@ func (f *Function) CompileFor(loop *ast.For) error { f.count.loop++ var ( - label = f.CreateLabel("for", f.count.loop) - labelEnd = f.CreateLabel("for end", f.count.loop) + label = f.CreateLabel("for", f.count.loop, asm.ControlLabel) + labelEnd = f.CreateLabel("for end", f.count.loop, asm.ControlLabel) counter cpu.Register from *expression.Expression to *expression.Expression diff --git a/src/core/CompileIf.go b/src/core/CompileIf.go index 1474eff..393b782 100644 --- a/src/core/CompileIf.go +++ b/src/core/CompileIf.go @@ -14,9 +14,9 @@ func (f *Function) CompileIf(branch *ast.If) error { f.count.branch++ var ( - end string - success = f.CreateLabel("if true", f.count.branch) - fail = f.CreateLabel("if false", f.count.branch) + end asm.Label + success = f.CreateLabel("if true", f.count.branch, asm.ControlLabel) + fail = f.CreateLabel("if false", f.count.branch, asm.ControlLabel) err = f.CompileCondition(branch.Condition, success, fail) ) @@ -33,7 +33,7 @@ func (f *Function) CompileIf(branch *ast.If) error { } if branch.Else != nil { - end = f.CreateLabel("if end", f.count.branch) + end = f.CreateLabel("if end", f.count.branch, asm.ControlLabel) f.Jump(asm.JUMP, end) } diff --git a/src/core/CompileLoop.go b/src/core/CompileLoop.go index 923e690..a3a9c4d 100644 --- a/src/core/CompileLoop.go +++ b/src/core/CompileLoop.go @@ -12,7 +12,7 @@ func (f *Function) CompileLoop(loop *ast.Loop) error { } f.count.loop++ - label := f.CreateLabel("loop", f.count.loop) + label := f.CreateLabel("loop", f.count.loop, asm.ControlLabel) f.AddLabel(label) scope := f.PushScope(loop.Body, f.File.Bytes) scope.InLoop = true diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index 0a99a43..6d2ae30 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -47,7 +47,7 @@ func (f *Function) CompileNew(root *expression.Expression) (types.Type, error) { f.RegisterNumber(asm.MOVE, f.CPU.Input[0], typ.Size()) alloc := f.All.Functions["mem.alloc"] f.BeforeCall() - f.Label(asm.CALL, "mem.alloc") + f.Label(asm.CALL, asm.Label{Name: "mem.alloc", Type: asm.FunctionLabel}) f.AfterCall(f.CPU.Input[:1]) f.Dependencies = append(f.Dependencies, alloc) return &types.Pointer{To: typ}, nil diff --git a/src/core/CompileSwitch.go b/src/core/CompileSwitch.go index 7196f8a..b3bbee6 100644 --- a/src/core/CompileSwitch.go +++ b/src/core/CompileSwitch.go @@ -8,7 +8,7 @@ import ( // CompileSwitch compiles a multi-branch instruction. func (f *Function) CompileSwitch(s *ast.Switch) error { f.count.multiBranch++ - end := f.CreateLabel("switch end", f.count.multiBranch) + end := f.CreateLabel("switch end", f.count.multiBranch, asm.ControlLabel) for _, branch := range s.Cases { if branch.Condition == nil { @@ -26,8 +26,8 @@ func (f *Function) CompileSwitch(s *ast.Switch) error { f.count.branch++ var ( - success = f.CreateLabel("case true", f.count.branch) - fail = f.CreateLabel("case false", f.count.branch) + success = f.CreateLabel("case true", f.count.branch, asm.ControlLabel) + fail = f.CreateLabel("case false", f.count.branch, asm.ControlLabel) err = f.CompileCondition(branch.Condition, success, fail) ) diff --git a/src/core/CreateLabel.go b/src/core/CreateLabel.go index 5342638..2d1690e 100644 --- a/src/core/CreateLabel.go +++ b/src/core/CreateLabel.go @@ -3,10 +3,12 @@ package core import ( "strconv" "strings" + + "git.urbach.dev/cli/q/src/asm" ) // CreateLabel creates a label that is tied to this function by using a suffix. -func (f *Function) CreateLabel(prefix string, count counter) string { +func (f *Function) CreateLabel(prefix string, count counter, typ asm.LabelType) asm.Label { tmp := strings.Builder{} tmp.WriteString(prefix) tmp.WriteString(" ") @@ -14,5 +16,9 @@ func (f *Function) CreateLabel(prefix string, count counter) string { tmp.WriteString(" [") tmp.WriteString(f.UniqueName) tmp.WriteString("]") - return tmp.String() + + return asm.Label{ + Name: tmp.String(), + Type: typ, + } } diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go index f968839..3bc55c2 100644 --- a/src/core/EvaluateDot.go +++ b/src/core/EvaluateDot.go @@ -79,8 +79,11 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) f.Dependencies = append(f.Dependencies, function) value := &eval.Label{ - Typ: types.AnyPointer, - Label: label, + Typ: types.AnyPointer, + Label: asm.Label{ + Name: label, + Type: asm.FunctionLabel, + }, } return value, nil diff --git a/src/core/EvaluateToken.go b/src/core/EvaluateToken.go index f3125c8..0b1aa30 100644 --- a/src/core/EvaluateToken.go +++ b/src/core/EvaluateToken.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "fmt" + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/token" @@ -48,8 +49,11 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { f.Dependencies = append(f.Dependencies, function) value := &eval.Label{ - Typ: types.AnyPointer, - Label: function.UniqueName, + Typ: types.AnyPointer, + Label: asm.Label{ + Name: function.UniqueName, + Type: asm.FunctionLabel, + }, } return value, nil diff --git a/src/core/JumpIfFalse.go b/src/core/JumpIfFalse.go index 61c649d..5628b19 100644 --- a/src/core/JumpIfFalse.go +++ b/src/core/JumpIfFalse.go @@ -6,7 +6,7 @@ import ( ) // JumpIfFalse jumps to the label if the previous comparison was false. -func (f *Function) JumpIfFalse(operator token.Kind, label string) { +func (f *Function) JumpIfFalse(operator token.Kind, label asm.Label) { switch operator { case token.Equal: f.Jump(asm.JNE, label) diff --git a/src/core/JumpIfTrue.go b/src/core/JumpIfTrue.go index 5c8eddc..a9cc35d 100644 --- a/src/core/JumpIfTrue.go +++ b/src/core/JumpIfTrue.go @@ -6,7 +6,7 @@ import ( ) // JumpIfTrue jumps to the label if the previous comparison was true. -func (f *Function) JumpIfTrue(operator token.Kind, label string) { +func (f *Function) JumpIfTrue(operator token.Kind, label asm.Label) { switch operator { case token.Equal: f.Jump(asm.JE, label) diff --git a/src/eval/Label.go b/src/eval/Label.go index e88dbd3..c075ba9 100644 --- a/src/eval/Label.go +++ b/src/eval/Label.go @@ -1,11 +1,14 @@ package eval -import "git.urbach.dev/cli/q/src/types" +import ( + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/types" +) // Label is a named pointer to a code or data section. type Label struct { Typ types.Type - Label string + Label asm.Label } func (v *Label) String() string { diff --git a/src/register/AddLabel.go b/src/register/AddLabel.go index 797bb30..1308bbb 100644 --- a/src/register/AddLabel.go +++ b/src/register/AddLabel.go @@ -2,7 +2,7 @@ package register import "git.urbach.dev/cli/q/src/asm" -func (f *Machine) AddLabel(label string) { +func (f *Machine) AddLabel(label asm.Label) { f.Assembler.Label(asm.LABEL, label) f.postInstruction() } diff --git a/src/register/DLLCall.go b/src/register/DLLCall.go index 4b80f04..80d7b12 100644 --- a/src/register/DLLCall.go +++ b/src/register/DLLCall.go @@ -3,7 +3,7 @@ package register import "git.urbach.dev/cli/q/src/asm" func (f *Machine) DLLCall(label string) { - f.Assembler.Label(asm.DLLCALL, label) + f.Assembler.Label(asm.DLLCALL, asm.Label{Name: label, Type: asm.UnknownLabel}) f.UseRegister(f.CPU.Output[0]) f.postInstruction() } diff --git a/src/register/Jump.go b/src/register/Jump.go index ccf1f7c..255ecd1 100644 --- a/src/register/Jump.go +++ b/src/register/Jump.go @@ -2,7 +2,7 @@ package register import "git.urbach.dev/cli/q/src/asm" -func (f *Machine) Jump(mnemonic asm.Mnemonic, label string) { +func (f *Machine) Jump(mnemonic asm.Mnemonic, label asm.Label) { f.Assembler.Label(mnemonic, label) f.postInstruction() } diff --git a/src/register/Label.go b/src/register/Label.go index 4dcea1a..1fdaf43 100644 --- a/src/register/Label.go +++ b/src/register/Label.go @@ -2,7 +2,7 @@ package register import "git.urbach.dev/cli/q/src/asm" -func (f *Machine) Label(mnemonic asm.Mnemonic, label string) { +func (f *Machine) Label(mnemonic asm.Mnemonic, label asm.Label) { f.Assembler.Label(mnemonic, label) f.postInstruction() } diff --git a/src/register/MemoryLabel.go b/src/register/MemoryLabel.go index 520ad5e..dc93c8d 100644 --- a/src/register/MemoryLabel.go +++ b/src/register/MemoryLabel.go @@ -2,7 +2,7 @@ package register import "git.urbach.dev/cli/q/src/asm" -func (f *Machine) MemoryLabel(mnemonic asm.Mnemonic, a asm.Memory, b string) { +func (f *Machine) MemoryLabel(mnemonic asm.Mnemonic, a asm.Memory, b asm.Label) { f.Assembler.MemoryLabel(mnemonic, a, b) f.postInstruction() } diff --git a/src/register/RegisterLabel.go b/src/register/RegisterLabel.go index 64a0b1a..cd5a020 100644 --- a/src/register/RegisterLabel.go +++ b/src/register/RegisterLabel.go @@ -5,7 +5,7 @@ import ( "git.urbach.dev/cli/q/src/cpu" ) -func (f *Machine) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { +func (f *Machine) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label asm.Label) { f.Assembler.RegisterLabel(mnemonic, register, label) if mnemonic == asm.MOVE { From 29ee6730f2ee33a8af1b720cc576b873be56069d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 13 Mar 2025 17:20:11 +0100 Subject: [PATCH 0927/1012] Fixed push and pop on arm64 --- src/arm/Sub_test.go | 1 + src/asmc/compileARM.go | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/arm/Sub_test.go b/src/arm/Sub_test.go index d6cb4f6..42c4f4d 100644 --- a/src/arm/Sub_test.go +++ b/src/arm/Sub_test.go @@ -16,6 +16,7 @@ func TestSubRegisterNumber(t *testing.T) { Code uint32 }{ {arm.X0, arm.X0, 1, 0xD1000400}, + {arm.SP, arm.SP, 16, 0xD10043FF}, } for _, pattern := range usagePatterns { diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 45a2d73..5379846 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -87,14 +87,16 @@ func (c *compiler) compileARM(x asm.Instruction) { switch x.Type { case asm.TypeRegister: operand := c.assembler.Param.Register[x.Index] - c.append(arm.StorePair(operand.Register, operand.Register, arm.SP, -16)) + c.append(arm.SubRegisterNumber(arm.SP, arm.SP, 16)) + c.append(arm.StoreRegister(operand.Register, arm.SP, 0, 8)) } case asm.POP: switch x.Type { case asm.TypeRegister: operand := c.assembler.Param.Register[x.Index] - c.append(arm.LoadPair(operand.Register, operand.Register, arm.SP, 16)) + c.append(arm.LoadRegister(operand.Register, arm.SP, 0, 8)) + c.append(arm.AddRegisterNumber(arm.SP, arm.SP, 16)) } case asm.ADD: From 5b3769a0db8310f1e82e34099c95d06ebb499a66 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 13 Mar 2025 17:20:11 +0100 Subject: [PATCH 0928/1012] Fixed push and pop on arm64 --- src/arm/Sub_test.go | 1 + src/asmc/compileARM.go | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/arm/Sub_test.go b/src/arm/Sub_test.go index d6cb4f6..42c4f4d 100644 --- a/src/arm/Sub_test.go +++ b/src/arm/Sub_test.go @@ -16,6 +16,7 @@ func TestSubRegisterNumber(t *testing.T) { Code uint32 }{ {arm.X0, arm.X0, 1, 0xD1000400}, + {arm.SP, arm.SP, 16, 0xD10043FF}, } for _, pattern := range usagePatterns { diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 45a2d73..5379846 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -87,14 +87,16 @@ func (c *compiler) compileARM(x asm.Instruction) { switch x.Type { case asm.TypeRegister: operand := c.assembler.Param.Register[x.Index] - c.append(arm.StorePair(operand.Register, operand.Register, arm.SP, -16)) + c.append(arm.SubRegisterNumber(arm.SP, arm.SP, 16)) + c.append(arm.StoreRegister(operand.Register, arm.SP, 0, 8)) } case asm.POP: switch x.Type { case asm.TypeRegister: operand := c.assembler.Param.Register[x.Index] - c.append(arm.LoadPair(operand.Register, operand.Register, arm.SP, 16)) + c.append(arm.LoadRegister(operand.Register, arm.SP, 0, 8)) + c.append(arm.AddRegisterNumber(arm.SP, arm.SP, 16)) } case asm.ADD: From b7d76ff3e3ea7b9bb32a22af62067f41befb2f29 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 13 Mar 2025 23:12:15 +0100 Subject: [PATCH 0929/1012] Implemented more arm64 instructions --- src/arm/Add.go | 7 ++++++- src/arm/Add_test.go | 17 ++++++++++++++++ src/arm/Compare.go | 7 ++++++- src/arm/Div.go | 8 ++++++++ src/arm/Div_test.go | 26 +++++++++++++++++++++++++ src/arm/Load.go | 3 +-- src/arm/LoadPair.go | 4 +--- src/arm/Move.go | 4 ++-- src/arm/Mul.go | 13 +++++++++++++ src/arm/Mul_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++ src/arm/Registers.go | 7 ++++--- src/arm/Store.go | 3 +-- src/arm/StorePair.go | 4 +--- src/arm/Sub.go | 7 ++++++- src/arm/Sub_test.go | 17 ++++++++++++++++ src/arm/encode.go | 38 +++++++++++++++++++++++++++--------- src/asmc/compileARM.go | 36 ++++++++++++++++++++++++++++++++-- src/asmc/compileX86.go | 2 ++ 18 files changed, 218 insertions(+), 29 deletions(-) create mode 100644 src/arm/Div.go create mode 100644 src/arm/Div_test.go create mode 100644 src/arm/Mul.go create mode 100644 src/arm/Mul_test.go diff --git a/src/arm/Add.go b/src/arm/Add.go index dd868e9..be6d662 100644 --- a/src/arm/Add.go +++ b/src/arm/Add.go @@ -4,5 +4,10 @@ import "git.urbach.dev/cli/q/src/cpu" // AddRegisterNumber adds a number to a register. func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { - return encodeRegisterNumberFlags(0b10, destination, source, number, false) + return addRegisterNumber(0b10, 0, destination, source, number) +} + +// AddRegisterRegister adds a register to a register. +func AddRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return addRegisterRegister(0b10, 0, destination, source, operand) } diff --git a/src/arm/Add_test.go b/src/arm/Add_test.go index 9b403b3..1051b06 100644 --- a/src/arm/Add_test.go +++ b/src/arm/Add_test.go @@ -24,3 +24,20 @@ func TestAddRegisterNumber(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestAddRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0x8B020020}, + } + + for _, pattern := range usagePatterns { + t.Logf("add %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.AddRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Compare.go b/src/arm/Compare.go index 5fc07ec..5bf7d1a 100644 --- a/src/arm/Compare.go +++ b/src/arm/Compare.go @@ -4,5 +4,10 @@ import "git.urbach.dev/cli/q/src/cpu" // CompareRegisterNumber is an alias for a subtraction that updates the conditional flags and discards the result. func CompareRegisterNumber(register cpu.Register, number int) uint32 { - return encodeRegisterNumberFlags(0b11, 0b11111, register, number, true) + return addRegisterNumber(0b11, 1, ZR, register, number) +} + +// CompareRegisterRegister is an alias for a subtraction that updates the conditional flags and discards the result. +func CompareRegisterRegister(reg1 cpu.Register, reg2 cpu.Register) uint32 { + return addRegisterRegister(0b11, 1, ZR, reg1, reg2) } diff --git a/src/arm/Div.go b/src/arm/Div.go new file mode 100644 index 0000000..c885e00 --- /dev/null +++ b/src/arm/Div.go @@ -0,0 +1,8 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// DivSigned divides source by operand and stores the value in the destination. +func DivSigned(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return 0b10011010110<<21 | 0b000011<<10 | reg3(destination, source, operand) +} diff --git a/src/arm/Div_test.go b/src/arm/Div_test.go new file mode 100644 index 0000000..81c3f03 --- /dev/null +++ b/src/arm/Div_test.go @@ -0,0 +1,26 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestDivSigned(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0x9AC20C20}, + } + + for _, pattern := range usagePatterns { + t.Logf("sdiv %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.DivSigned(pattern.Destination, pattern.Source, pattern.Operand) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Load.go b/src/arm/Load.go index 8b82b98..afbd176 100644 --- a/src/arm/Load.go +++ b/src/arm/Load.go @@ -4,8 +4,7 @@ import "git.urbach.dev/cli/q/src/cpu" // LoadRegister loads from memory into a register. func LoadRegister(destination cpu.Register, base cpu.Register, offset int, length byte) uint32 { - offset &= 0b1_1111_1111 - common := 1<<22 | uint32(offset)<<12 | uint32(base)<<5 | uint32(destination) + common := 1<<22 | memory(destination, base, offset) switch length { case 1: diff --git a/src/arm/LoadPair.go b/src/arm/LoadPair.go index 8ca16d7..70f6b39 100644 --- a/src/arm/LoadPair.go +++ b/src/arm/LoadPair.go @@ -6,7 +6,5 @@ import "git.urbach.dev/cli/q/src/cpu" // loads two 64-bit doublewords from memory, and writes them to two registers. // This is the post-index version of the instruction so the offset is applied to the base register after the memory access. func LoadPair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { - offset /= 8 - offset &= 0b111_1111 - return 0b1010100011<<22 | (uint32(offset) << 15) | (uint32(reg2) << 10) | (uint32(base) << 5) | uint32(reg1) + return 0b1010100011<<22 | pair(reg1, reg2, base, offset/8) } diff --git a/src/arm/Move.go b/src/arm/Move.go index b48bd9d..2399d92 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -10,7 +10,7 @@ func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 return AddRegisterNumber(destination, source, 0) } - return 0b10101010<<24 | uint32(source)<<16 | 0b11111<<5 | uint32(destination) + return 0b10101010<<24 | reg3(destination, ZR, source) } // MoveRegisterNumber moves an integer into the given register. @@ -30,5 +30,5 @@ func MoveZero(destination cpu.Register, halfword int, number uint16) uint32 { // mov encodes a generic move instruction. func mov(opCode uint32, halfword int, number uint16, destination cpu.Register) uint32 { - return (1 << 31) | (opCode << 29) | (0b100101 << 23) | uint32(halfword<<21) | uint32(number<<5) | uint32(destination) + return 1<<31 | opCode<<29 | 0b100101<<23 | uint32(halfword<<21) | uint32(number<<5) | uint32(destination) } diff --git a/src/arm/Mul.go b/src/arm/Mul.go new file mode 100644 index 0000000..afad2cc --- /dev/null +++ b/src/arm/Mul.go @@ -0,0 +1,13 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// MulRegisterRegister multiplies `multiplicand` with `multiplier` and saves the result in `destination` +func MulRegisterRegister(destination cpu.Register, multiplicand cpu.Register, multiplier cpu.Register) uint32 { + return 0b10011011000<<21 | reg4(destination, multiplicand, multiplier, ZR) +} + +// MultiplySubtract multiplies `multiplicand` with `multiplier`, subtracts `minuend` and saves the result in `destination`. +func MultiplySubtract(destination cpu.Register, multiplicand cpu.Register, multiplier cpu.Register, minuend cpu.Register) uint32 { + return 0b10011011000<<21 | 1<<15 | reg4(destination, multiplicand, multiplier, minuend) +} diff --git a/src/arm/Mul_test.go b/src/arm/Mul_test.go new file mode 100644 index 0000000..d654208 --- /dev/null +++ b/src/arm/Mul_test.go @@ -0,0 +1,44 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestMulRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0x9B027C20}, + } + + for _, pattern := range usagePatterns { + t.Logf("mul %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.MulRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestMultiplySubtract(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Extra cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, arm.X3, 0x9B028C20}, + } + + for _, pattern := range usagePatterns { + t.Logf("msub %s, %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand, pattern.Extra) + code := arm.MultiplySubtract(pattern.Destination, pattern.Source, pattern.Operand, pattern.Extra) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Registers.go b/src/arm/Registers.go index 388dc0d..9204b69 100644 --- a/src/arm/Registers.go +++ b/src/arm/Registers.go @@ -32,9 +32,10 @@ const ( X26 X27 X28 - FP // Frame pointer - LR // Link register - SP // Stack pointer + FP // Frame pointer + LR // Link register + SP // Stack pointer + ZR = SP // Zero register uses the same numerical value as SP ) var ( diff --git a/src/arm/Store.go b/src/arm/Store.go index d81f6a1..ceeb839 100644 --- a/src/arm/Store.go +++ b/src/arm/Store.go @@ -6,8 +6,7 @@ import ( // StoreRegister writes the contents of the register to a memory address. func StoreRegister(source cpu.Register, base cpu.Register, offset int, length byte) uint32 { - offset &= 0b1_1111_1111 - common := uint32(offset)<<12 | uint32(base)<<5 | uint32(source) + common := memory(source, base, offset) switch length { case 1: diff --git a/src/arm/StorePair.go b/src/arm/StorePair.go index 006457d..3a2958d 100644 --- a/src/arm/StorePair.go +++ b/src/arm/StorePair.go @@ -8,7 +8,5 @@ import ( // and stores the values of two registers to the calculated address. // This is the pre-index version of the instruction so the offset is applied to the base register before the memory access. func StorePair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { - offset /= 8 - offset &= 0b111_1111 - return 0b1010100110<<22 | uint32(offset)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) + return 0b1010100110<<22 | pair(reg1, reg2, base, offset/8) } diff --git a/src/arm/Sub.go b/src/arm/Sub.go index f468cea..415db8d 100644 --- a/src/arm/Sub.go +++ b/src/arm/Sub.go @@ -4,5 +4,10 @@ import "git.urbach.dev/cli/q/src/cpu" // SubRegisterNumber subtracts a number from the given register. func SubRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { - return encodeRegisterNumberFlags(0b11, destination, source, number, false) + return addRegisterNumber(0b11, 0, destination, source, number) +} + +// SubRegisterRegister subtracts a register from a register. +func SubRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return addRegisterRegister(0b11, 0, destination, source, operand) } diff --git a/src/arm/Sub_test.go b/src/arm/Sub_test.go index 42c4f4d..dc42615 100644 --- a/src/arm/Sub_test.go +++ b/src/arm/Sub_test.go @@ -25,3 +25,20 @@ func TestSubRegisterNumber(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestSubRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0xCB020020}, + } + + for _, pattern := range usagePatterns { + t.Logf("sub %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.SubRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/encode.go b/src/arm/encode.go index 312cc37..767573d 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -2,15 +2,35 @@ package arm import "git.urbach.dev/cli/q/src/cpu" -// encodeRegisterNumberFlags performs addition or subtraction on the given register +// addRegisterNumber performs addition or subtraction on the given register // and optionally updates the condition flags based on the result. -func encodeRegisterNumberFlags(op uint32, destination cpu.Register, source cpu.Register, number int, flags bool) uint32 { +func addRegisterNumber(op uint32, flags uint32, destination cpu.Register, source cpu.Register, number int) uint32 { number &= 0b1111_1111_1111 - common := op<<30 | 0b100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) - - if flags { - return 1<<29 | common - } - - return common + return op<<30 | flags<<29 | 0b100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) +} + +// addRegisterRegister performs addition or subtraction on the given registers +// and optionally updates the condition flags based on the result. +func addRegisterRegister(op uint32, flags uint32, destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return op<<30 | flags<<29 | 0b01011000<<21 | reg3(destination, source, operand) +} + +// memory encodes an instruction with a register, a base register and an offset. +func memory(destination cpu.Register, base cpu.Register, offset int) uint32 { + return uint32(offset&0b1_1111_1111)<<12 | uint32(base)<<5 | uint32(destination) +} + +// pair encodes an instruction using a register pair with memory. +func pair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { + return uint32(offset&0b111_1111)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) +} + +// reg3 encodes an instruction with 3 registers. +func reg3(d cpu.Register, n cpu.Register, m cpu.Register) uint32 { + return uint32(m)<<16 | uint32(n)<<5 | uint32(d) +} + +// reg4 encodes an instruction with 4 registers. +func reg4(d cpu.Register, n cpu.Register, m cpu.Register, a cpu.Register) uint32 { + return uint32(m)<<16 | uint32(a)<<10 | uint32(n)<<5 | uint32(d) } diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 5379846..fbf01b5 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -105,7 +105,8 @@ func (c *compiler) compileARM(x asm.Instruction) { operand := c.assembler.Param.RegisterNumber[x.Index] c.append(arm.AddRegisterNumber(operand.Register, operand.Register, operand.Number)) case asm.TypeRegisterRegister: - panic("not implemented") + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.AddRegisterRegister(operand.Destination, operand.Destination, operand.Source)) } case asm.SUB: @@ -114,7 +115,8 @@ func (c *compiler) compileARM(x asm.Instruction) { operand := c.assembler.Param.RegisterNumber[x.Index] c.append(arm.SubRegisterNumber(operand.Register, operand.Register, operand.Number)) case asm.TypeRegisterRegister: - panic("not implemented") + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.SubRegisterRegister(operand.Destination, operand.Destination, operand.Source)) } case asm.COMPARE: @@ -123,6 +125,36 @@ func (c *compiler) compileARM(x asm.Instruction) { operand := c.assembler.Param.RegisterNumber[x.Index] c.append(arm.CompareRegisterNumber(operand.Register, operand.Number)) case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.CompareRegisterRegister(operand.Destination, operand.Source)) + } + + case asm.DIV: + switch x.Type { + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.DivSigned(operand.Destination, operand.Destination, operand.Source)) + case asm.TypeRegisterNumber: + panic("not implemented") + } + + case asm.MUL: + switch x.Type { + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.MulRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + case asm.TypeRegisterNumber: + panic("not implemented") + } + + case asm.MODULO: + switch x.Type { + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[x.Index] + quotient := arm.X28 + c.append(arm.DivSigned(quotient, operand.Destination, operand.Source)) + c.append(arm.MultiplySubtract(operand.Destination, quotient, operand.Source, operand.Destination)) + case asm.TypeRegisterNumber: panic("not implemented") } diff --git a/src/asmc/compileX86.go b/src/asmc/compileX86.go index 2b59340..122873d 100644 --- a/src/asmc/compileX86.go +++ b/src/asmc/compileX86.go @@ -51,6 +51,7 @@ func (c *compiler) compileX86(x asm.Instruction) { switch x.Type { case asm.TypeRegisterRegister: operands := c.assembler.Param.RegisterRegister[x.Index] + if operands.Destination != x86.RAX { c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) } @@ -67,6 +68,7 @@ func (c *compiler) compileX86(x asm.Instruction) { switch x.Type { case asm.TypeRegisterRegister: operands := c.assembler.Param.RegisterRegister[x.Index] + if operands.Destination != x86.RAX { c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) } From ac14ab4f7ae4281fbff063b0884033324dfdd46a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 13 Mar 2025 23:12:15 +0100 Subject: [PATCH 0930/1012] Implemented more arm64 instructions --- src/arm/Add.go | 7 ++++++- src/arm/Add_test.go | 17 ++++++++++++++++ src/arm/Compare.go | 7 ++++++- src/arm/Div.go | 8 ++++++++ src/arm/Div_test.go | 26 +++++++++++++++++++++++++ src/arm/Load.go | 3 +-- src/arm/LoadPair.go | 4 +--- src/arm/Move.go | 4 ++-- src/arm/Mul.go | 13 +++++++++++++ src/arm/Mul_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++ src/arm/Registers.go | 7 ++++--- src/arm/Store.go | 3 +-- src/arm/StorePair.go | 4 +--- src/arm/Sub.go | 7 ++++++- src/arm/Sub_test.go | 17 ++++++++++++++++ src/arm/encode.go | 38 +++++++++++++++++++++++++++--------- src/asmc/compileARM.go | 36 ++++++++++++++++++++++++++++++++-- src/asmc/compileX86.go | 2 ++ 18 files changed, 218 insertions(+), 29 deletions(-) create mode 100644 src/arm/Div.go create mode 100644 src/arm/Div_test.go create mode 100644 src/arm/Mul.go create mode 100644 src/arm/Mul_test.go diff --git a/src/arm/Add.go b/src/arm/Add.go index dd868e9..be6d662 100644 --- a/src/arm/Add.go +++ b/src/arm/Add.go @@ -4,5 +4,10 @@ import "git.urbach.dev/cli/q/src/cpu" // AddRegisterNumber adds a number to a register. func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { - return encodeRegisterNumberFlags(0b10, destination, source, number, false) + return addRegisterNumber(0b10, 0, destination, source, number) +} + +// AddRegisterRegister adds a register to a register. +func AddRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return addRegisterRegister(0b10, 0, destination, source, operand) } diff --git a/src/arm/Add_test.go b/src/arm/Add_test.go index 9b403b3..1051b06 100644 --- a/src/arm/Add_test.go +++ b/src/arm/Add_test.go @@ -24,3 +24,20 @@ func TestAddRegisterNumber(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestAddRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0x8B020020}, + } + + for _, pattern := range usagePatterns { + t.Logf("add %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.AddRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Compare.go b/src/arm/Compare.go index 5fc07ec..5bf7d1a 100644 --- a/src/arm/Compare.go +++ b/src/arm/Compare.go @@ -4,5 +4,10 @@ import "git.urbach.dev/cli/q/src/cpu" // CompareRegisterNumber is an alias for a subtraction that updates the conditional flags and discards the result. func CompareRegisterNumber(register cpu.Register, number int) uint32 { - return encodeRegisterNumberFlags(0b11, 0b11111, register, number, true) + return addRegisterNumber(0b11, 1, ZR, register, number) +} + +// CompareRegisterRegister is an alias for a subtraction that updates the conditional flags and discards the result. +func CompareRegisterRegister(reg1 cpu.Register, reg2 cpu.Register) uint32 { + return addRegisterRegister(0b11, 1, ZR, reg1, reg2) } diff --git a/src/arm/Div.go b/src/arm/Div.go new file mode 100644 index 0000000..c885e00 --- /dev/null +++ b/src/arm/Div.go @@ -0,0 +1,8 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// DivSigned divides source by operand and stores the value in the destination. +func DivSigned(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return 0b10011010110<<21 | 0b000011<<10 | reg3(destination, source, operand) +} diff --git a/src/arm/Div_test.go b/src/arm/Div_test.go new file mode 100644 index 0000000..81c3f03 --- /dev/null +++ b/src/arm/Div_test.go @@ -0,0 +1,26 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestDivSigned(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0x9AC20C20}, + } + + for _, pattern := range usagePatterns { + t.Logf("sdiv %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.DivSigned(pattern.Destination, pattern.Source, pattern.Operand) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Load.go b/src/arm/Load.go index 8b82b98..afbd176 100644 --- a/src/arm/Load.go +++ b/src/arm/Load.go @@ -4,8 +4,7 @@ import "git.urbach.dev/cli/q/src/cpu" // LoadRegister loads from memory into a register. func LoadRegister(destination cpu.Register, base cpu.Register, offset int, length byte) uint32 { - offset &= 0b1_1111_1111 - common := 1<<22 | uint32(offset)<<12 | uint32(base)<<5 | uint32(destination) + common := 1<<22 | memory(destination, base, offset) switch length { case 1: diff --git a/src/arm/LoadPair.go b/src/arm/LoadPair.go index 8ca16d7..70f6b39 100644 --- a/src/arm/LoadPair.go +++ b/src/arm/LoadPair.go @@ -6,7 +6,5 @@ import "git.urbach.dev/cli/q/src/cpu" // loads two 64-bit doublewords from memory, and writes them to two registers. // This is the post-index version of the instruction so the offset is applied to the base register after the memory access. func LoadPair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { - offset /= 8 - offset &= 0b111_1111 - return 0b1010100011<<22 | (uint32(offset) << 15) | (uint32(reg2) << 10) | (uint32(base) << 5) | uint32(reg1) + return 0b1010100011<<22 | pair(reg1, reg2, base, offset/8) } diff --git a/src/arm/Move.go b/src/arm/Move.go index b48bd9d..2399d92 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -10,7 +10,7 @@ func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 return AddRegisterNumber(destination, source, 0) } - return 0b10101010<<24 | uint32(source)<<16 | 0b11111<<5 | uint32(destination) + return 0b10101010<<24 | reg3(destination, ZR, source) } // MoveRegisterNumber moves an integer into the given register. @@ -30,5 +30,5 @@ func MoveZero(destination cpu.Register, halfword int, number uint16) uint32 { // mov encodes a generic move instruction. func mov(opCode uint32, halfword int, number uint16, destination cpu.Register) uint32 { - return (1 << 31) | (opCode << 29) | (0b100101 << 23) | uint32(halfword<<21) | uint32(number<<5) | uint32(destination) + return 1<<31 | opCode<<29 | 0b100101<<23 | uint32(halfword<<21) | uint32(number<<5) | uint32(destination) } diff --git a/src/arm/Mul.go b/src/arm/Mul.go new file mode 100644 index 0000000..afad2cc --- /dev/null +++ b/src/arm/Mul.go @@ -0,0 +1,13 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// MulRegisterRegister multiplies `multiplicand` with `multiplier` and saves the result in `destination` +func MulRegisterRegister(destination cpu.Register, multiplicand cpu.Register, multiplier cpu.Register) uint32 { + return 0b10011011000<<21 | reg4(destination, multiplicand, multiplier, ZR) +} + +// MultiplySubtract multiplies `multiplicand` with `multiplier`, subtracts `minuend` and saves the result in `destination`. +func MultiplySubtract(destination cpu.Register, multiplicand cpu.Register, multiplier cpu.Register, minuend cpu.Register) uint32 { + return 0b10011011000<<21 | 1<<15 | reg4(destination, multiplicand, multiplier, minuend) +} diff --git a/src/arm/Mul_test.go b/src/arm/Mul_test.go new file mode 100644 index 0000000..d654208 --- /dev/null +++ b/src/arm/Mul_test.go @@ -0,0 +1,44 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestMulRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0x9B027C20}, + } + + for _, pattern := range usagePatterns { + t.Logf("mul %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.MulRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestMultiplySubtract(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Extra cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, arm.X3, 0x9B028C20}, + } + + for _, pattern := range usagePatterns { + t.Logf("msub %s, %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand, pattern.Extra) + code := arm.MultiplySubtract(pattern.Destination, pattern.Source, pattern.Operand, pattern.Extra) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Registers.go b/src/arm/Registers.go index 388dc0d..9204b69 100644 --- a/src/arm/Registers.go +++ b/src/arm/Registers.go @@ -32,9 +32,10 @@ const ( X26 X27 X28 - FP // Frame pointer - LR // Link register - SP // Stack pointer + FP // Frame pointer + LR // Link register + SP // Stack pointer + ZR = SP // Zero register uses the same numerical value as SP ) var ( diff --git a/src/arm/Store.go b/src/arm/Store.go index d81f6a1..ceeb839 100644 --- a/src/arm/Store.go +++ b/src/arm/Store.go @@ -6,8 +6,7 @@ import ( // StoreRegister writes the contents of the register to a memory address. func StoreRegister(source cpu.Register, base cpu.Register, offset int, length byte) uint32 { - offset &= 0b1_1111_1111 - common := uint32(offset)<<12 | uint32(base)<<5 | uint32(source) + common := memory(source, base, offset) switch length { case 1: diff --git a/src/arm/StorePair.go b/src/arm/StorePair.go index 006457d..3a2958d 100644 --- a/src/arm/StorePair.go +++ b/src/arm/StorePair.go @@ -8,7 +8,5 @@ import ( // and stores the values of two registers to the calculated address. // This is the pre-index version of the instruction so the offset is applied to the base register before the memory access. func StorePair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { - offset /= 8 - offset &= 0b111_1111 - return 0b1010100110<<22 | uint32(offset)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) + return 0b1010100110<<22 | pair(reg1, reg2, base, offset/8) } diff --git a/src/arm/Sub.go b/src/arm/Sub.go index f468cea..415db8d 100644 --- a/src/arm/Sub.go +++ b/src/arm/Sub.go @@ -4,5 +4,10 @@ import "git.urbach.dev/cli/q/src/cpu" // SubRegisterNumber subtracts a number from the given register. func SubRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { - return encodeRegisterNumberFlags(0b11, destination, source, number, false) + return addRegisterNumber(0b11, 0, destination, source, number) +} + +// SubRegisterRegister subtracts a register from a register. +func SubRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return addRegisterRegister(0b11, 0, destination, source, operand) } diff --git a/src/arm/Sub_test.go b/src/arm/Sub_test.go index 42c4f4d..dc42615 100644 --- a/src/arm/Sub_test.go +++ b/src/arm/Sub_test.go @@ -25,3 +25,20 @@ func TestSubRegisterNumber(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestSubRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0xCB020020}, + } + + for _, pattern := range usagePatterns { + t.Logf("sub %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.SubRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/encode.go b/src/arm/encode.go index 312cc37..767573d 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -2,15 +2,35 @@ package arm import "git.urbach.dev/cli/q/src/cpu" -// encodeRegisterNumberFlags performs addition or subtraction on the given register +// addRegisterNumber performs addition or subtraction on the given register // and optionally updates the condition flags based on the result. -func encodeRegisterNumberFlags(op uint32, destination cpu.Register, source cpu.Register, number int, flags bool) uint32 { +func addRegisterNumber(op uint32, flags uint32, destination cpu.Register, source cpu.Register, number int) uint32 { number &= 0b1111_1111_1111 - common := op<<30 | 0b100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) - - if flags { - return 1<<29 | common - } - - return common + return op<<30 | flags<<29 | 0b100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) +} + +// addRegisterRegister performs addition or subtraction on the given registers +// and optionally updates the condition flags based on the result. +func addRegisterRegister(op uint32, flags uint32, destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return op<<30 | flags<<29 | 0b01011000<<21 | reg3(destination, source, operand) +} + +// memory encodes an instruction with a register, a base register and an offset. +func memory(destination cpu.Register, base cpu.Register, offset int) uint32 { + return uint32(offset&0b1_1111_1111)<<12 | uint32(base)<<5 | uint32(destination) +} + +// pair encodes an instruction using a register pair with memory. +func pair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { + return uint32(offset&0b111_1111)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) +} + +// reg3 encodes an instruction with 3 registers. +func reg3(d cpu.Register, n cpu.Register, m cpu.Register) uint32 { + return uint32(m)<<16 | uint32(n)<<5 | uint32(d) +} + +// reg4 encodes an instruction with 4 registers. +func reg4(d cpu.Register, n cpu.Register, m cpu.Register, a cpu.Register) uint32 { + return uint32(m)<<16 | uint32(a)<<10 | uint32(n)<<5 | uint32(d) } diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 5379846..fbf01b5 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -105,7 +105,8 @@ func (c *compiler) compileARM(x asm.Instruction) { operand := c.assembler.Param.RegisterNumber[x.Index] c.append(arm.AddRegisterNumber(operand.Register, operand.Register, operand.Number)) case asm.TypeRegisterRegister: - panic("not implemented") + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.AddRegisterRegister(operand.Destination, operand.Destination, operand.Source)) } case asm.SUB: @@ -114,7 +115,8 @@ func (c *compiler) compileARM(x asm.Instruction) { operand := c.assembler.Param.RegisterNumber[x.Index] c.append(arm.SubRegisterNumber(operand.Register, operand.Register, operand.Number)) case asm.TypeRegisterRegister: - panic("not implemented") + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.SubRegisterRegister(operand.Destination, operand.Destination, operand.Source)) } case asm.COMPARE: @@ -123,6 +125,36 @@ func (c *compiler) compileARM(x asm.Instruction) { operand := c.assembler.Param.RegisterNumber[x.Index] c.append(arm.CompareRegisterNumber(operand.Register, operand.Number)) case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.CompareRegisterRegister(operand.Destination, operand.Source)) + } + + case asm.DIV: + switch x.Type { + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.DivSigned(operand.Destination, operand.Destination, operand.Source)) + case asm.TypeRegisterNumber: + panic("not implemented") + } + + case asm.MUL: + switch x.Type { + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.MulRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + case asm.TypeRegisterNumber: + panic("not implemented") + } + + case asm.MODULO: + switch x.Type { + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[x.Index] + quotient := arm.X28 + c.append(arm.DivSigned(quotient, operand.Destination, operand.Source)) + c.append(arm.MultiplySubtract(operand.Destination, quotient, operand.Source, operand.Destination)) + case asm.TypeRegisterNumber: panic("not implemented") } diff --git a/src/asmc/compileX86.go b/src/asmc/compileX86.go index 2b59340..122873d 100644 --- a/src/asmc/compileX86.go +++ b/src/asmc/compileX86.go @@ -51,6 +51,7 @@ func (c *compiler) compileX86(x asm.Instruction) { switch x.Type { case asm.TypeRegisterRegister: operands := c.assembler.Param.RegisterRegister[x.Index] + if operands.Destination != x86.RAX { c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) } @@ -67,6 +68,7 @@ func (c *compiler) compileX86(x asm.Instruction) { switch x.Type { case asm.TypeRegisterRegister: operands := c.assembler.Param.RegisterRegister[x.Index] + if operands.Destination != x86.RAX { c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) } From e367b664bb0ba283d56678f54326a5dfc493bc4e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Mar 2025 00:08:58 +0100 Subject: [PATCH 0931/1012] Added more tests --- src/core/CompileAssign.go | 2 ++ tests/programs/add.q | 9 +++++++++ tests/programs/div.q | 9 +++++++++ tests/programs/mul.q | 9 +++++++++ tests/programs/sub.q | 9 +++++++++ tests/programs_test.go | 4 ++++ 6 files changed, 42 insertions(+) create mode 100644 tests/programs/add.q create mode 100644 tests/programs/div.q create mode 100644 tests/programs/mul.q create mode 100644 tests/programs/sub.q diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 85eee9d..1a2f2e6 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -37,6 +37,8 @@ func (f *Function) CompileAssign(node *ast.Assign) error { switch leftValue := leftValue.(type) { case *eval.Register: + // TODO: Reservation needs to be canceled on defer + f.CurrentScope().Reserve(leftValue.Register) f.Execute(operation, leftValue.Register, right) case *eval.Memory: // TODO: Reservation needs to be canceled on defer diff --git a/tests/programs/add.q b/tests/programs/add.q new file mode 100644 index 0000000..7d6e6cd --- /dev/null +++ b/tests/programs/add.q @@ -0,0 +1,9 @@ +main() { + assert 0 + 0 == 0 + assert 1 + 1 == 2 + assert 1 + 2 == 3 + assert 2 + 1 == 3 + assert 2 + 2 == 4 + assert 4 + 2 == 6 + assert 8 + 2 == 10 +} \ No newline at end of file diff --git a/tests/programs/div.q b/tests/programs/div.q new file mode 100644 index 0000000..4332cc7 --- /dev/null +++ b/tests/programs/div.q @@ -0,0 +1,9 @@ +main() { + assert 0 / 1 == 0 + assert 1 / 1 == 1 + assert 1 / 2 == 0 + assert 2 / 1 == 2 + assert 2 / 2 == 1 + assert 4 / 2 == 2 + assert 8 / 2 == 4 +} \ No newline at end of file diff --git a/tests/programs/mul.q b/tests/programs/mul.q new file mode 100644 index 0000000..e1b4661 --- /dev/null +++ b/tests/programs/mul.q @@ -0,0 +1,9 @@ +main() { + assert 0 * 0 == 0 + assert 1 * 1 == 1 + assert 1 * 2 == 2 + assert 2 * 1 == 2 + assert 2 * 2 == 4 + assert 4 * 2 == 8 + assert 8 * 2 == 16 +} \ No newline at end of file diff --git a/tests/programs/sub.q b/tests/programs/sub.q new file mode 100644 index 0000000..204b10d --- /dev/null +++ b/tests/programs/sub.q @@ -0,0 +1,9 @@ +main() { + assert 0 - 0 == 0 + assert 1 - 1 == 0 + assert 1 - 2 == -1 + assert 2 - 1 == 1 + assert 2 - 2 == 0 + assert 4 - 2 == 2 + assert 8 - 2 == 6 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 953de1f..480c089 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -26,6 +26,10 @@ var programs = []struct { {"reuse", 0}, {"return", 0}, {"return-multi", 0}, + {"add", 0}, + {"sub", 0}, + {"mul", 0}, + {"div", 0}, {"math", 0}, {"precedence", 0}, {"operator-number", 0}, From 06f3af256bf4408269190430fe6807e256af1293 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Mar 2025 00:08:58 +0100 Subject: [PATCH 0932/1012] Added more tests --- src/core/CompileAssign.go | 2 ++ tests/programs/add.q | 9 +++++++++ tests/programs/div.q | 9 +++++++++ tests/programs/mul.q | 9 +++++++++ tests/programs/sub.q | 9 +++++++++ tests/programs_test.go | 4 ++++ 6 files changed, 42 insertions(+) create mode 100644 tests/programs/add.q create mode 100644 tests/programs/div.q create mode 100644 tests/programs/mul.q create mode 100644 tests/programs/sub.q diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 85eee9d..1a2f2e6 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -37,6 +37,8 @@ func (f *Function) CompileAssign(node *ast.Assign) error { switch leftValue := leftValue.(type) { case *eval.Register: + // TODO: Reservation needs to be canceled on defer + f.CurrentScope().Reserve(leftValue.Register) f.Execute(operation, leftValue.Register, right) case *eval.Memory: // TODO: Reservation needs to be canceled on defer diff --git a/tests/programs/add.q b/tests/programs/add.q new file mode 100644 index 0000000..7d6e6cd --- /dev/null +++ b/tests/programs/add.q @@ -0,0 +1,9 @@ +main() { + assert 0 + 0 == 0 + assert 1 + 1 == 2 + assert 1 + 2 == 3 + assert 2 + 1 == 3 + assert 2 + 2 == 4 + assert 4 + 2 == 6 + assert 8 + 2 == 10 +} \ No newline at end of file diff --git a/tests/programs/div.q b/tests/programs/div.q new file mode 100644 index 0000000..4332cc7 --- /dev/null +++ b/tests/programs/div.q @@ -0,0 +1,9 @@ +main() { + assert 0 / 1 == 0 + assert 1 / 1 == 1 + assert 1 / 2 == 0 + assert 2 / 1 == 2 + assert 2 / 2 == 1 + assert 4 / 2 == 2 + assert 8 / 2 == 4 +} \ No newline at end of file diff --git a/tests/programs/mul.q b/tests/programs/mul.q new file mode 100644 index 0000000..e1b4661 --- /dev/null +++ b/tests/programs/mul.q @@ -0,0 +1,9 @@ +main() { + assert 0 * 0 == 0 + assert 1 * 1 == 1 + assert 1 * 2 == 2 + assert 2 * 1 == 2 + assert 2 * 2 == 4 + assert 4 * 2 == 8 + assert 8 * 2 == 16 +} \ No newline at end of file diff --git a/tests/programs/sub.q b/tests/programs/sub.q new file mode 100644 index 0000000..204b10d --- /dev/null +++ b/tests/programs/sub.q @@ -0,0 +1,9 @@ +main() { + assert 0 - 0 == 0 + assert 1 - 1 == 0 + assert 1 - 2 == -1 + assert 2 - 1 == 1 + assert 2 - 2 == 0 + assert 4 - 2 == 2 + assert 8 - 2 == 6 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 953de1f..480c089 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -26,6 +26,10 @@ var programs = []struct { {"reuse", 0}, {"return", 0}, {"return-multi", 0}, + {"add", 0}, + {"sub", 0}, + {"mul", 0}, + {"div", 0}, {"math", 0}, {"precedence", 0}, {"operator-number", 0}, From 716cc6dddd9b482483236985275688195d3850e0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Mar 2025 09:52:00 +0100 Subject: [PATCH 0933/1012] Added crash function --- lib/core/core_linux_arm.q | 4 ++++ lib/core/core_linux_x86.q | 4 ++++ lib/core/core_mac.q | 4 ++++ lib/core/core_windows.q | 4 ++++ src/asmc/resolvePointers.go | 2 +- src/compiler/finalize.go | 23 +---------------------- src/core/CompileAssert.go | 2 +- tests/programs/assert.q | 3 --- 8 files changed, 19 insertions(+), 27 deletions(-) diff --git a/lib/core/core_linux_arm.q b/lib/core/core_linux_arm.q index 939ba41..f20e626 100644 --- a/lib/core/core_linux_arm.q +++ b/lib/core/core_linux_arm.q @@ -5,4 +5,8 @@ init() { exit() { syscall(93, 0) +} + +crash() { + syscall(93, 1) } \ No newline at end of file diff --git a/lib/core/core_linux_x86.q b/lib/core/core_linux_x86.q index 41e7100..9c8f519 100644 --- a/lib/core/core_linux_x86.q +++ b/lib/core/core_linux_x86.q @@ -5,4 +5,8 @@ init() { exit() { syscall(60, 0) +} + +crash() { + syscall(60, 1) } \ No newline at end of file diff --git a/lib/core/core_mac.q b/lib/core/core_mac.q index 875a04a..91618b4 100644 --- a/lib/core/core_mac.q +++ b/lib/core/core_mac.q @@ -5,4 +5,8 @@ init() { exit() { syscall(0x2000001, 0) +} + +crash() { + syscall(0x2000001, 1) } \ No newline at end of file diff --git a/lib/core/core_windows.q b/lib/core/core_windows.q index 575b41e..516794a 100644 --- a/lib/core/core_windows.q +++ b/lib/core/core_windows.q @@ -9,6 +9,10 @@ exit() { kernel32.ExitProcess(0) } +crash() { + kernel32.ExitProcess(1) +} + const cp { utf8 65001 } diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index 7603a86..2cf0e52 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -16,7 +16,7 @@ restart: for i, pointer := range c.codePointers { address := pointer.Resolve() - if sizeof.Signed(int32(address)) > int(pointer.Size) { + if config.TargetArch == config.X86 && sizeof.Signed(int32(address)) > int(pointer.Size) { left := c.code[:pointer.Position-Address(pointer.OpSize)] right := c.code[pointer.Position+Address(pointer.Size):] size := pointer.Size + pointer.OpSize diff --git a/src/compiler/finalize.go b/src/compiler/finalize.go index 16f24de..85c0c7d 100644 --- a/src/compiler/finalize.go +++ b/src/compiler/finalize.go @@ -3,9 +3,7 @@ package compiler import ( "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/asmc" - "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/core" - "git.urbach.dev/cli/q/src/x86" ) // finalize generates the final machine code. @@ -45,25 +43,6 @@ func (r *Result) finalize() { } }) - final.Label(asm.LABEL, asm.Label{ - Name: "_crash", - Type: asm.ControlLabel, - }) - - switch config.TargetOS { - case config.Linux: - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], LinuxExit) - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 1) - final.Syscall() - case config.Mac: - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], MacExit) - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 1) - final.Syscall() - case config.Windows: - final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 1) - final.RegisterNumber(asm.AND, x86.RSP, -16) - final.DLLCall("kernel32.ExitProcess") - } - + final.Merge(&r.Functions["core.crash"].Assembler) r.Code, r.Data = asmc.Finalize(&final, r.DLLs) } diff --git a/src/core/CompileAssert.go b/src/core/CompileAssert.go index 7614bd9..07af8ff 100644 --- a/src/core/CompileAssert.go +++ b/src/core/CompileAssert.go @@ -20,7 +20,7 @@ func (f *Function) CompileAssert(assert *ast.Assert) error { f.Defer(func() { f.AddLabel(fail) - f.Jump(asm.JUMP, asm.Label{Name: "_crash", Type: asm.ControlLabel}) + f.Jump(asm.JUMP, asm.Label{Name: "core.crash", Type: asm.ControlLabel}) }) return err diff --git a/tests/programs/assert.q b/tests/programs/assert.q index 9c3123e..c54983d 100644 --- a/tests/programs/assert.q +++ b/tests/programs/assert.q @@ -1,6 +1,3 @@ main() { - assert 1 != 0 - assert 1 == 0 || 1 != 0 - assert 1 != 0 && 2 != 0 assert 1 == 0 } \ No newline at end of file From 9726f1d832353bf13b093435d8fa80719f5e1516 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Mar 2025 09:52:00 +0100 Subject: [PATCH 0934/1012] Added crash function --- lib/core/core_linux_arm.q | 4 ++++ lib/core/core_linux_x86.q | 4 ++++ lib/core/core_mac.q | 4 ++++ lib/core/core_windows.q | 4 ++++ src/asmc/resolvePointers.go | 2 +- src/compiler/finalize.go | 23 +---------------------- src/core/CompileAssert.go | 2 +- tests/programs/assert.q | 3 --- 8 files changed, 19 insertions(+), 27 deletions(-) diff --git a/lib/core/core_linux_arm.q b/lib/core/core_linux_arm.q index 939ba41..f20e626 100644 --- a/lib/core/core_linux_arm.q +++ b/lib/core/core_linux_arm.q @@ -5,4 +5,8 @@ init() { exit() { syscall(93, 0) +} + +crash() { + syscall(93, 1) } \ No newline at end of file diff --git a/lib/core/core_linux_x86.q b/lib/core/core_linux_x86.q index 41e7100..9c8f519 100644 --- a/lib/core/core_linux_x86.q +++ b/lib/core/core_linux_x86.q @@ -5,4 +5,8 @@ init() { exit() { syscall(60, 0) +} + +crash() { + syscall(60, 1) } \ No newline at end of file diff --git a/lib/core/core_mac.q b/lib/core/core_mac.q index 875a04a..91618b4 100644 --- a/lib/core/core_mac.q +++ b/lib/core/core_mac.q @@ -5,4 +5,8 @@ init() { exit() { syscall(0x2000001, 0) +} + +crash() { + syscall(0x2000001, 1) } \ No newline at end of file diff --git a/lib/core/core_windows.q b/lib/core/core_windows.q index 575b41e..516794a 100644 --- a/lib/core/core_windows.q +++ b/lib/core/core_windows.q @@ -9,6 +9,10 @@ exit() { kernel32.ExitProcess(0) } +crash() { + kernel32.ExitProcess(1) +} + const cp { utf8 65001 } diff --git a/src/asmc/resolvePointers.go b/src/asmc/resolvePointers.go index 7603a86..2cf0e52 100644 --- a/src/asmc/resolvePointers.go +++ b/src/asmc/resolvePointers.go @@ -16,7 +16,7 @@ restart: for i, pointer := range c.codePointers { address := pointer.Resolve() - if sizeof.Signed(int32(address)) > int(pointer.Size) { + if config.TargetArch == config.X86 && sizeof.Signed(int32(address)) > int(pointer.Size) { left := c.code[:pointer.Position-Address(pointer.OpSize)] right := c.code[pointer.Position+Address(pointer.Size):] size := pointer.Size + pointer.OpSize diff --git a/src/compiler/finalize.go b/src/compiler/finalize.go index 16f24de..85c0c7d 100644 --- a/src/compiler/finalize.go +++ b/src/compiler/finalize.go @@ -3,9 +3,7 @@ package compiler import ( "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/asmc" - "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/core" - "git.urbach.dev/cli/q/src/x86" ) // finalize generates the final machine code. @@ -45,25 +43,6 @@ func (r *Result) finalize() { } }) - final.Label(asm.LABEL, asm.Label{ - Name: "_crash", - Type: asm.ControlLabel, - }) - - switch config.TargetOS { - case config.Linux: - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], LinuxExit) - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 1) - final.Syscall() - case config.Mac: - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[0], MacExit) - final.RegisterNumber(asm.MOVE, x86.SyscallInputRegisters[1], 1) - final.Syscall() - case config.Windows: - final.RegisterNumber(asm.MOVE, x86.WindowsInputRegisters[0], 1) - final.RegisterNumber(asm.AND, x86.RSP, -16) - final.DLLCall("kernel32.ExitProcess") - } - + final.Merge(&r.Functions["core.crash"].Assembler) r.Code, r.Data = asmc.Finalize(&final, r.DLLs) } diff --git a/src/core/CompileAssert.go b/src/core/CompileAssert.go index 7614bd9..07af8ff 100644 --- a/src/core/CompileAssert.go +++ b/src/core/CompileAssert.go @@ -20,7 +20,7 @@ func (f *Function) CompileAssert(assert *ast.Assert) error { f.Defer(func() { f.AddLabel(fail) - f.Jump(asm.JUMP, asm.Label{Name: "_crash", Type: asm.ControlLabel}) + f.Jump(asm.JUMP, asm.Label{Name: "core.crash", Type: asm.ControlLabel}) }) return err diff --git a/tests/programs/assert.q b/tests/programs/assert.q index 9c3123e..c54983d 100644 --- a/tests/programs/assert.q +++ b/tests/programs/assert.q @@ -1,6 +1,3 @@ main() { - assert 1 != 0 - assert 1 == 0 || 1 != 0 - assert 1 != 0 && 2 != 0 assert 1 == 0 } \ No newline at end of file From f2f38f2ee9db6528f4b273ab61afb43e7a6c0d79 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Mar 2025 11:14:42 +0100 Subject: [PATCH 0935/1012] Fixed comparison with negative numbers on arm64 --- src/arm/Add.go | 14 ++++++++++++-- src/arm/Compare.go | 8 ++++++-- src/arm/Compare_test.go | 1 + src/arm/Sub.go | 14 ++++++++++++-- src/arm/encode.go | 26 +++++++++----------------- 5 files changed, 40 insertions(+), 23 deletions(-) diff --git a/src/arm/Add.go b/src/arm/Add.go index be6d662..4cacfc0 100644 --- a/src/arm/Add.go +++ b/src/arm/Add.go @@ -4,10 +4,20 @@ import "git.urbach.dev/cli/q/src/cpu" // AddRegisterNumber adds a number to a register. func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { - return addRegisterNumber(0b10, 0, destination, source, number) + return addRegisterNumber(destination, source, number, 0) } // AddRegisterRegister adds a register to a register. func AddRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { - return addRegisterRegister(0b10, 0, destination, source, operand) + return addRegisterRegister(destination, source, operand, 0) +} + +// addRegisterNumber adds the register and optionally updates the condition flags based on the result. +func addRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { + return flags<<29 | 0b100100010<<23 | reg2imm(destination, source, number) +} + +// addRegisterRegister adds the registers and optionally updates the condition flags based on the result. +func addRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register, flags uint32) uint32 { + return flags<<29 | 0b10001011000<<21 | reg3(destination, source, operand) } diff --git a/src/arm/Compare.go b/src/arm/Compare.go index 5bf7d1a..eb5a97e 100644 --- a/src/arm/Compare.go +++ b/src/arm/Compare.go @@ -4,10 +4,14 @@ import "git.urbach.dev/cli/q/src/cpu" // CompareRegisterNumber is an alias for a subtraction that updates the conditional flags and discards the result. func CompareRegisterNumber(register cpu.Register, number int) uint32 { - return addRegisterNumber(0b11, 1, ZR, register, number) + if number < 0 { + return addRegisterNumber(ZR, register, -number, 1) + } + + return subRegisterNumber(ZR, register, number, 1) } // CompareRegisterRegister is an alias for a subtraction that updates the conditional flags and discards the result. func CompareRegisterRegister(reg1 cpu.Register, reg2 cpu.Register) uint32 { - return addRegisterRegister(0b11, 1, ZR, reg1, reg2) + return subRegisterRegister(ZR, reg1, reg2, 1) } diff --git a/src/arm/Compare_test.go b/src/arm/Compare_test.go index 7a3eca1..8224614 100644 --- a/src/arm/Compare_test.go +++ b/src/arm/Compare_test.go @@ -15,6 +15,7 @@ func TestCompareRegisterNumber(t *testing.T) { Code uint32 }{ {arm.X0, 1, 0xF100041F}, + {arm.X0, -1, 0xB100041F}, } for _, pattern := range usagePatterns { diff --git a/src/arm/Sub.go b/src/arm/Sub.go index 415db8d..bf84f50 100644 --- a/src/arm/Sub.go +++ b/src/arm/Sub.go @@ -4,10 +4,20 @@ import "git.urbach.dev/cli/q/src/cpu" // SubRegisterNumber subtracts a number from the given register. func SubRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { - return addRegisterNumber(0b11, 0, destination, source, number) + return subRegisterNumber(destination, source, number, 0) } // SubRegisterRegister subtracts a register from a register. func SubRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { - return addRegisterRegister(0b11, 0, destination, source, operand) + return subRegisterRegister(destination, source, operand, 0) +} + +// subRegisterNumber subtracts the register and optionally updates the condition flags based on the result. +func subRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { + return flags<<29 | 0b110100010<<23 | reg2imm(destination, source, number) +} + +// subRegisterRegister subtracts the registers and optionally updates the condition flags based on the result. +func subRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register, flags uint32) uint32 { + return flags<<29 | 0b11001011000<<21 | reg3(destination, source, operand) } diff --git a/src/arm/encode.go b/src/arm/encode.go index 767573d..38f968d 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -2,27 +2,19 @@ package arm import "git.urbach.dev/cli/q/src/cpu" -// addRegisterNumber performs addition or subtraction on the given register -// and optionally updates the condition flags based on the result. -func addRegisterNumber(op uint32, flags uint32, destination cpu.Register, source cpu.Register, number int) uint32 { - number &= 0b1111_1111_1111 - return op<<30 | flags<<29 | 0b100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) -} - -// addRegisterRegister performs addition or subtraction on the given registers -// and optionally updates the condition flags based on the result. -func addRegisterRegister(op uint32, flags uint32, destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { - return op<<30 | flags<<29 | 0b01011000<<21 | reg3(destination, source, operand) -} - // memory encodes an instruction with a register, a base register and an offset. -func memory(destination cpu.Register, base cpu.Register, offset int) uint32 { - return uint32(offset&0b1_1111_1111)<<12 | uint32(base)<<5 | uint32(destination) +func memory(destination cpu.Register, base cpu.Register, imm9 int) uint32 { + return uint32(imm9&0b1_1111_1111)<<12 | uint32(base)<<5 | uint32(destination) } // pair encodes an instruction using a register pair with memory. -func pair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { - return uint32(offset&0b111_1111)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) +func pair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, imm7 int) uint32 { + return uint32(imm7&0b111_1111)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) +} + +// reg2imm encodes an instruction with 2 registers and an immediate. +func reg2imm(d cpu.Register, n cpu.Register, imm12 int) uint32 { + return uint32(imm12&0b1111_1111_1111)<<10 | uint32(n)<<5 | uint32(d) } // reg3 encodes an instruction with 3 registers. From 9410287605e757027edb9b5a0695d3b3dfbdd03f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Mar 2025 11:14:42 +0100 Subject: [PATCH 0936/1012] Fixed comparison with negative numbers on arm64 --- src/arm/Add.go | 14 ++++++++++++-- src/arm/Compare.go | 8 ++++++-- src/arm/Compare_test.go | 1 + src/arm/Sub.go | 14 ++++++++++++-- src/arm/encode.go | 26 +++++++++----------------- 5 files changed, 40 insertions(+), 23 deletions(-) diff --git a/src/arm/Add.go b/src/arm/Add.go index be6d662..4cacfc0 100644 --- a/src/arm/Add.go +++ b/src/arm/Add.go @@ -4,10 +4,20 @@ import "git.urbach.dev/cli/q/src/cpu" // AddRegisterNumber adds a number to a register. func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { - return addRegisterNumber(0b10, 0, destination, source, number) + return addRegisterNumber(destination, source, number, 0) } // AddRegisterRegister adds a register to a register. func AddRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { - return addRegisterRegister(0b10, 0, destination, source, operand) + return addRegisterRegister(destination, source, operand, 0) +} + +// addRegisterNumber adds the register and optionally updates the condition flags based on the result. +func addRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { + return flags<<29 | 0b100100010<<23 | reg2imm(destination, source, number) +} + +// addRegisterRegister adds the registers and optionally updates the condition flags based on the result. +func addRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register, flags uint32) uint32 { + return flags<<29 | 0b10001011000<<21 | reg3(destination, source, operand) } diff --git a/src/arm/Compare.go b/src/arm/Compare.go index 5bf7d1a..eb5a97e 100644 --- a/src/arm/Compare.go +++ b/src/arm/Compare.go @@ -4,10 +4,14 @@ import "git.urbach.dev/cli/q/src/cpu" // CompareRegisterNumber is an alias for a subtraction that updates the conditional flags and discards the result. func CompareRegisterNumber(register cpu.Register, number int) uint32 { - return addRegisterNumber(0b11, 1, ZR, register, number) + if number < 0 { + return addRegisterNumber(ZR, register, -number, 1) + } + + return subRegisterNumber(ZR, register, number, 1) } // CompareRegisterRegister is an alias for a subtraction that updates the conditional flags and discards the result. func CompareRegisterRegister(reg1 cpu.Register, reg2 cpu.Register) uint32 { - return addRegisterRegister(0b11, 1, ZR, reg1, reg2) + return subRegisterRegister(ZR, reg1, reg2, 1) } diff --git a/src/arm/Compare_test.go b/src/arm/Compare_test.go index 7a3eca1..8224614 100644 --- a/src/arm/Compare_test.go +++ b/src/arm/Compare_test.go @@ -15,6 +15,7 @@ func TestCompareRegisterNumber(t *testing.T) { Code uint32 }{ {arm.X0, 1, 0xF100041F}, + {arm.X0, -1, 0xB100041F}, } for _, pattern := range usagePatterns { diff --git a/src/arm/Sub.go b/src/arm/Sub.go index 415db8d..bf84f50 100644 --- a/src/arm/Sub.go +++ b/src/arm/Sub.go @@ -4,10 +4,20 @@ import "git.urbach.dev/cli/q/src/cpu" // SubRegisterNumber subtracts a number from the given register. func SubRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { - return addRegisterNumber(0b11, 0, destination, source, number) + return subRegisterNumber(destination, source, number, 0) } // SubRegisterRegister subtracts a register from a register. func SubRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { - return addRegisterRegister(0b11, 0, destination, source, operand) + return subRegisterRegister(destination, source, operand, 0) +} + +// subRegisterNumber subtracts the register and optionally updates the condition flags based on the result. +func subRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { + return flags<<29 | 0b110100010<<23 | reg2imm(destination, source, number) +} + +// subRegisterRegister subtracts the registers and optionally updates the condition flags based on the result. +func subRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register, flags uint32) uint32 { + return flags<<29 | 0b11001011000<<21 | reg3(destination, source, operand) } diff --git a/src/arm/encode.go b/src/arm/encode.go index 767573d..38f968d 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -2,27 +2,19 @@ package arm import "git.urbach.dev/cli/q/src/cpu" -// addRegisterNumber performs addition or subtraction on the given register -// and optionally updates the condition flags based on the result. -func addRegisterNumber(op uint32, flags uint32, destination cpu.Register, source cpu.Register, number int) uint32 { - number &= 0b1111_1111_1111 - return op<<30 | flags<<29 | 0b100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) -} - -// addRegisterRegister performs addition or subtraction on the given registers -// and optionally updates the condition flags based on the result. -func addRegisterRegister(op uint32, flags uint32, destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { - return op<<30 | flags<<29 | 0b01011000<<21 | reg3(destination, source, operand) -} - // memory encodes an instruction with a register, a base register and an offset. -func memory(destination cpu.Register, base cpu.Register, offset int) uint32 { - return uint32(offset&0b1_1111_1111)<<12 | uint32(base)<<5 | uint32(destination) +func memory(destination cpu.Register, base cpu.Register, imm9 int) uint32 { + return uint32(imm9&0b1_1111_1111)<<12 | uint32(base)<<5 | uint32(destination) } // pair encodes an instruction using a register pair with memory. -func pair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { - return uint32(offset&0b111_1111)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) +func pair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, imm7 int) uint32 { + return uint32(imm7&0b111_1111)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) +} + +// reg2imm encodes an instruction with 2 registers and an immediate. +func reg2imm(d cpu.Register, n cpu.Register, imm12 int) uint32 { + return uint32(imm12&0b1111_1111_1111)<<10 | uint32(n)<<5 | uint32(d) } // reg3 encodes an instruction with 3 registers. From 500a7201d43501547ff4da8f6e8b7c8dab3b4c85 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Mar 2025 11:58:36 +0100 Subject: [PATCH 0937/1012] Fixed move with negative numbers on arm64 --- src/arm/Move.go | 9 +++++++++ src/arm/Move_test.go | 22 ++++++++++++++++++++-- src/arm/encode.go | 19 ++++++++++++++++--- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/arm/Move.go b/src/arm/Move.go index 2399d92..5cbc4c9 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -15,9 +15,18 @@ func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 // MoveRegisterNumber moves an integer into the given register. func MoveRegisterNumber(destination cpu.Register, number int) uint32 { + if number < 0 { + return MoveInvertedWideImmediate(destination, ^number) + } + return MoveZero(destination, 0, uint16(number)) } +// MoveInvertedWideImmediate moves an inverted 16-bit immediate value to a register. +func MoveInvertedWideImmediate(destination cpu.Register, number int) uint32 { + return 0b100100101<<23 | regImm(destination, number) +} + // MoveKeep moves a 16-bit integer into the given register and keeps all other bits. func MoveKeep(destination cpu.Register, halfword int, number uint16) uint32 { return mov(0b11, halfword, number, destination) diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go index 69f524e..ba8e5cc 100644 --- a/src/arm/Move_test.go +++ b/src/arm/Move_test.go @@ -27,6 +27,24 @@ func TestMoveRegisterRegister(t *testing.T) { } } +func TestMoveRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code uint32 + }{ + {arm.X0, 0, 0xD2800000}, + {arm.X0, 1, 0xD2800020}, + {arm.X0, -1, 0x92800000}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, %d", pattern.Register, pattern.Number) + code := arm.MoveRegisterNumber(pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + func TestMoveKeep(t *testing.T) { usagePatterns := []struct { Register cpu.Register @@ -38,7 +56,7 @@ func TestMoveKeep(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("movk %s, %x", pattern.Register, pattern.Number) + t.Logf("movk %s, %d", pattern.Register, pattern.Number) code := arm.MoveKeep(pattern.Register, 0, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } @@ -55,7 +73,7 @@ func TestMoveZero(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("movz %s, %x", pattern.Register, pattern.Number) + t.Logf("movz %s, %d", pattern.Register, pattern.Number) code := arm.MoveZero(pattern.Register, 0, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } diff --git a/src/arm/encode.go b/src/arm/encode.go index 38f968d..bab6330 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -2,19 +2,32 @@ package arm import "git.urbach.dev/cli/q/src/cpu" +const ( + mask6 = 0b111111 + mask7 = 0b1111111 + mask9 = 0b1_11111111 + mask12 = 0b1111_11111111 + mask16 = 0b11111111_11111111 +) + // memory encodes an instruction with a register, a base register and an offset. func memory(destination cpu.Register, base cpu.Register, imm9 int) uint32 { - return uint32(imm9&0b1_1111_1111)<<12 | uint32(base)<<5 | uint32(destination) + return uint32(imm9&mask9)<<12 | uint32(base)<<5 | uint32(destination) } // pair encodes an instruction using a register pair with memory. func pair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, imm7 int) uint32 { - return uint32(imm7&0b111_1111)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) + return uint32(imm7&mask7)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) +} + +// regImm encodes an instruction with a register and an immediate. +func regImm(d cpu.Register, imm16 int) uint32 { + return uint32(imm16&mask16)<<5 | uint32(d) } // reg2imm encodes an instruction with 2 registers and an immediate. func reg2imm(d cpu.Register, n cpu.Register, imm12 int) uint32 { - return uint32(imm12&0b1111_1111_1111)<<10 | uint32(n)<<5 | uint32(d) + return uint32(imm12&mask12)<<10 | uint32(n)<<5 | uint32(d) } // reg3 encodes an instruction with 3 registers. From 1d0e49f0e3b7d1ced7d468ad1e546c838d1edee8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Mar 2025 11:58:36 +0100 Subject: [PATCH 0938/1012] Fixed move with negative numbers on arm64 --- src/arm/Move.go | 9 +++++++++ src/arm/Move_test.go | 22 ++++++++++++++++++++-- src/arm/encode.go | 19 ++++++++++++++++--- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/arm/Move.go b/src/arm/Move.go index 2399d92..5cbc4c9 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -15,9 +15,18 @@ func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 // MoveRegisterNumber moves an integer into the given register. func MoveRegisterNumber(destination cpu.Register, number int) uint32 { + if number < 0 { + return MoveInvertedWideImmediate(destination, ^number) + } + return MoveZero(destination, 0, uint16(number)) } +// MoveInvertedWideImmediate moves an inverted 16-bit immediate value to a register. +func MoveInvertedWideImmediate(destination cpu.Register, number int) uint32 { + return 0b100100101<<23 | regImm(destination, number) +} + // MoveKeep moves a 16-bit integer into the given register and keeps all other bits. func MoveKeep(destination cpu.Register, halfword int, number uint16) uint32 { return mov(0b11, halfword, number, destination) diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go index 69f524e..ba8e5cc 100644 --- a/src/arm/Move_test.go +++ b/src/arm/Move_test.go @@ -27,6 +27,24 @@ func TestMoveRegisterRegister(t *testing.T) { } } +func TestMoveRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code uint32 + }{ + {arm.X0, 0, 0xD2800000}, + {arm.X0, 1, 0xD2800020}, + {arm.X0, -1, 0x92800000}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, %d", pattern.Register, pattern.Number) + code := arm.MoveRegisterNumber(pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + func TestMoveKeep(t *testing.T) { usagePatterns := []struct { Register cpu.Register @@ -38,7 +56,7 @@ func TestMoveKeep(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("movk %s, %x", pattern.Register, pattern.Number) + t.Logf("movk %s, %d", pattern.Register, pattern.Number) code := arm.MoveKeep(pattern.Register, 0, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } @@ -55,7 +73,7 @@ func TestMoveZero(t *testing.T) { } for _, pattern := range usagePatterns { - t.Logf("movz %s, %x", pattern.Register, pattern.Number) + t.Logf("movz %s, %d", pattern.Register, pattern.Number) code := arm.MoveZero(pattern.Register, 0, pattern.Number) assert.DeepEqual(t, code, pattern.Code) } diff --git a/src/arm/encode.go b/src/arm/encode.go index 38f968d..bab6330 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -2,19 +2,32 @@ package arm import "git.urbach.dev/cli/q/src/cpu" +const ( + mask6 = 0b111111 + mask7 = 0b1111111 + mask9 = 0b1_11111111 + mask12 = 0b1111_11111111 + mask16 = 0b11111111_11111111 +) + // memory encodes an instruction with a register, a base register and an offset. func memory(destination cpu.Register, base cpu.Register, imm9 int) uint32 { - return uint32(imm9&0b1_1111_1111)<<12 | uint32(base)<<5 | uint32(destination) + return uint32(imm9&mask9)<<12 | uint32(base)<<5 | uint32(destination) } // pair encodes an instruction using a register pair with memory. func pair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, imm7 int) uint32 { - return uint32(imm7&0b111_1111)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) + return uint32(imm7&mask7)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) +} + +// regImm encodes an instruction with a register and an immediate. +func regImm(d cpu.Register, imm16 int) uint32 { + return uint32(imm16&mask16)<<5 | uint32(d) } // reg2imm encodes an instruction with 2 registers and an immediate. func reg2imm(d cpu.Register, n cpu.Register, imm12 int) uint32 { - return uint32(imm12&0b1111_1111_1111)<<10 | uint32(n)<<5 | uint32(d) + return uint32(imm12&mask12)<<10 | uint32(n)<<5 | uint32(d) } // reg3 encodes an instruction with 3 registers. From 8fcc57cee617847a21445964f60e34d4d401627e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Mar 2025 12:08:14 +0100 Subject: [PATCH 0939/1012] Implemented multiplication with a number on arm64 --- src/asmc/compileARM.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index fbf01b5..5658a91 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -144,16 +144,19 @@ func (c *compiler) compileARM(x asm.Instruction) { operand := c.assembler.Param.RegisterRegister[x.Index] c.append(arm.MulRegisterRegister(operand.Destination, operand.Destination, operand.Source)) case asm.TypeRegisterNumber: - panic("not implemented") + operand := c.assembler.Param.RegisterNumber[x.Index] + tmp := arm.X28 + c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.append(arm.MulRegisterRegister(operand.Register, operand.Register, tmp)) } case asm.MODULO: switch x.Type { case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] - quotient := arm.X28 - c.append(arm.DivSigned(quotient, operand.Destination, operand.Source)) - c.append(arm.MultiplySubtract(operand.Destination, quotient, operand.Source, operand.Destination)) + tmp := arm.X28 + c.append(arm.DivSigned(tmp, operand.Destination, operand.Source)) + c.append(arm.MultiplySubtract(operand.Destination, tmp, operand.Source, operand.Destination)) case asm.TypeRegisterNumber: panic("not implemented") } From 72272ed9af7a018650a13984966a519f29da48ea Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Mar 2025 12:08:14 +0100 Subject: [PATCH 0940/1012] Implemented multiplication with a number on arm64 --- src/asmc/compileARM.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index fbf01b5..5658a91 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -144,16 +144,19 @@ func (c *compiler) compileARM(x asm.Instruction) { operand := c.assembler.Param.RegisterRegister[x.Index] c.append(arm.MulRegisterRegister(operand.Destination, operand.Destination, operand.Source)) case asm.TypeRegisterNumber: - panic("not implemented") + operand := c.assembler.Param.RegisterNumber[x.Index] + tmp := arm.X28 + c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.append(arm.MulRegisterRegister(operand.Register, operand.Register, tmp)) } case asm.MODULO: switch x.Type { case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] - quotient := arm.X28 - c.append(arm.DivSigned(quotient, operand.Destination, operand.Source)) - c.append(arm.MultiplySubtract(operand.Destination, quotient, operand.Source, operand.Destination)) + tmp := arm.X28 + c.append(arm.DivSigned(tmp, operand.Destination, operand.Source)) + c.append(arm.MultiplySubtract(operand.Destination, tmp, operand.Source, operand.Destination)) case asm.TypeRegisterNumber: panic("not implemented") } From c5cc88c2b0242ae8a48ff90b44db3206adb232a5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Mar 2025 17:45:39 +0100 Subject: [PATCH 0941/1012] Implemented bitwise AND on arm64 --- src/arm/Add.go | 2 +- src/arm/And.go | 21 ++++++++++++++++++++ src/arm/And_test.go | 48 +++++++++++++++++++++++++++++++++++++++++++++ src/arm/Sub.go | 2 +- src/arm/bitmask.go | 33 +++++++++++++++++++++++++++++++ src/arm/encode.go | 18 ++++++++++++++--- 6 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 src/arm/And.go create mode 100644 src/arm/And_test.go create mode 100644 src/arm/bitmask.go diff --git a/src/arm/Add.go b/src/arm/Add.go index 4cacfc0..3ae49e3 100644 --- a/src/arm/Add.go +++ b/src/arm/Add.go @@ -14,7 +14,7 @@ func AddRegisterRegister(destination cpu.Register, source cpu.Register, operand // addRegisterNumber adds the register and optionally updates the condition flags based on the result. func addRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { - return flags<<29 | 0b100100010<<23 | reg2imm(destination, source, number) + return flags<<29 | 0b100100010<<23 | reg2Imm(destination, source, number) } // addRegisterRegister adds the registers and optionally updates the condition flags based on the result. diff --git a/src/arm/And.go b/src/arm/And.go new file mode 100644 index 0000000..add44af --- /dev/null +++ b/src/arm/And.go @@ -0,0 +1,21 @@ +package arm + +import ( + "git.urbach.dev/cli/q/src/cpu" +) + +// AndRegisterNumber performs a bitwise AND using a register and a number. +func AndRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { + imm13, encodable := encodeLogicalImmediate(uint(number)) + + if !encodable { + panic("bitwise and operand can't be encoded as a bitmask immediate") + } + + return 0b100100100<<23 | uint32(imm13)<<10 | reg2(destination, source) +} + +// AndRegisterRegister performs a bitwise AND using two registers. +func AndRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return 0b10001010<<24 | reg3Imm(destination, source, operand, 0) +} diff --git a/src/arm/And_test.go b/src/arm/And_test.go new file mode 100644 index 0000000..6e684c4 --- /dev/null +++ b/src/arm/And_test.go @@ -0,0 +1,48 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestAndRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, arm.X1, 1, 0x92400020}, + {arm.X0, arm.X1, 2, 0x927F0020}, + {arm.X0, arm.X1, 3, 0x92400420}, + {arm.X0, arm.X1, 7, 0x92400820}, + {arm.X0, arm.X1, 16, 0x927C0020}, + {arm.X0, arm.X1, 255, 0x92401C20}, + } + + for _, pattern := range usagePatterns { + t.Logf("and %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) + code := arm.AndRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestAndRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0x8A020020}, + } + + for _, pattern := range usagePatterns { + t.Logf("and %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.AndRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Sub.go b/src/arm/Sub.go index bf84f50..ef2377f 100644 --- a/src/arm/Sub.go +++ b/src/arm/Sub.go @@ -14,7 +14,7 @@ func SubRegisterRegister(destination cpu.Register, source cpu.Register, operand // subRegisterNumber subtracts the register and optionally updates the condition flags based on the result. func subRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { - return flags<<29 | 0b110100010<<23 | reg2imm(destination, source, number) + return flags<<29 | 0b110100010<<23 | reg2Imm(destination, source, number) } // subRegisterRegister subtracts the registers and optionally updates the condition flags based on the result. diff --git a/src/arm/bitmask.go b/src/arm/bitmask.go new file mode 100644 index 0000000..76668a2 --- /dev/null +++ b/src/arm/bitmask.go @@ -0,0 +1,33 @@ +package arm + +import "math/bits" + +// encodeLogicalImmediate encodes a bitmask immediate. +// The algorithm used here was made by Dougall Johnson. +func encodeLogicalImmediate(val uint) (int, bool) { + if val == 0 || ^val == 0 { + return 0, false + } + + rotation := bits.TrailingZeros(clearTrailingOnes(val)) + normalized := bits.RotateLeft(val, -(rotation & 63)) + + zeroes := bits.LeadingZeros(normalized) + ones := bits.TrailingZeros(^normalized) + size := zeroes + ones + + immr := -rotation & (size - 1) + imms := -(size << 1) | (ones - 1) + N := (size >> 6) + + if bits.RotateLeft(val, -(size&63)) != val { + return 0, false + } + + return N<<12 | immr<<6 | (imms & 0x3f), true +} + +// clearTrailingOnes clears trailing one bits. +func clearTrailingOnes(x uint) uint { + return x & (x + 1) +} diff --git a/src/arm/encode.go b/src/arm/encode.go index bab6330..5f3e56b 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -1,6 +1,8 @@ package arm -import "git.urbach.dev/cli/q/src/cpu" +import ( + "git.urbach.dev/cli/q/src/cpu" +) const ( mask6 = 0b111111 @@ -25,8 +27,13 @@ func regImm(d cpu.Register, imm16 int) uint32 { return uint32(imm16&mask16)<<5 | uint32(d) } -// reg2imm encodes an instruction with 2 registers and an immediate. -func reg2imm(d cpu.Register, n cpu.Register, imm12 int) uint32 { +// reg2 encodes an instruction with 2 registers. +func reg2(d cpu.Register, n cpu.Register) uint32 { + return uint32(n)<<5 | uint32(d) +} + +// reg2Imm encodes an instruction with 2 registers and an immediate. +func reg2Imm(d cpu.Register, n cpu.Register, imm12 int) uint32 { return uint32(imm12&mask12)<<10 | uint32(n)<<5 | uint32(d) } @@ -35,6 +42,11 @@ func reg3(d cpu.Register, n cpu.Register, m cpu.Register) uint32 { return uint32(m)<<16 | uint32(n)<<5 | uint32(d) } +// reg3Imm encodes an instruction with 3 registers. +func reg3Imm(d cpu.Register, n cpu.Register, m cpu.Register, imm6 int) uint32 { + return uint32(m)<<16 | uint32(imm6&mask6)<<10 | uint32(n)<<5 | uint32(d) +} + // reg4 encodes an instruction with 4 registers. func reg4(d cpu.Register, n cpu.Register, m cpu.Register, a cpu.Register) uint32 { return uint32(m)<<16 | uint32(a)<<10 | uint32(n)<<5 | uint32(d) From e123a26a1dff842afb993bf1b217c4487cc84713 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Mar 2025 17:45:39 +0100 Subject: [PATCH 0942/1012] Implemented bitwise AND on arm64 --- src/arm/Add.go | 2 +- src/arm/And.go | 21 ++++++++++++++++++++ src/arm/And_test.go | 48 +++++++++++++++++++++++++++++++++++++++++++++ src/arm/Sub.go | 2 +- src/arm/bitmask.go | 33 +++++++++++++++++++++++++++++++ src/arm/encode.go | 18 ++++++++++++++--- 6 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 src/arm/And.go create mode 100644 src/arm/And_test.go create mode 100644 src/arm/bitmask.go diff --git a/src/arm/Add.go b/src/arm/Add.go index 4cacfc0..3ae49e3 100644 --- a/src/arm/Add.go +++ b/src/arm/Add.go @@ -14,7 +14,7 @@ func AddRegisterRegister(destination cpu.Register, source cpu.Register, operand // addRegisterNumber adds the register and optionally updates the condition flags based on the result. func addRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { - return flags<<29 | 0b100100010<<23 | reg2imm(destination, source, number) + return flags<<29 | 0b100100010<<23 | reg2Imm(destination, source, number) } // addRegisterRegister adds the registers and optionally updates the condition flags based on the result. diff --git a/src/arm/And.go b/src/arm/And.go new file mode 100644 index 0000000..add44af --- /dev/null +++ b/src/arm/And.go @@ -0,0 +1,21 @@ +package arm + +import ( + "git.urbach.dev/cli/q/src/cpu" +) + +// AndRegisterNumber performs a bitwise AND using a register and a number. +func AndRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { + imm13, encodable := encodeLogicalImmediate(uint(number)) + + if !encodable { + panic("bitwise and operand can't be encoded as a bitmask immediate") + } + + return 0b100100100<<23 | uint32(imm13)<<10 | reg2(destination, source) +} + +// AndRegisterRegister performs a bitwise AND using two registers. +func AndRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return 0b10001010<<24 | reg3Imm(destination, source, operand, 0) +} diff --git a/src/arm/And_test.go b/src/arm/And_test.go new file mode 100644 index 0000000..6e684c4 --- /dev/null +++ b/src/arm/And_test.go @@ -0,0 +1,48 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestAndRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, arm.X1, 1, 0x92400020}, + {arm.X0, arm.X1, 2, 0x927F0020}, + {arm.X0, arm.X1, 3, 0x92400420}, + {arm.X0, arm.X1, 7, 0x92400820}, + {arm.X0, arm.X1, 16, 0x927C0020}, + {arm.X0, arm.X1, 255, 0x92401C20}, + } + + for _, pattern := range usagePatterns { + t.Logf("and %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) + code := arm.AndRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestAndRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0x8A020020}, + } + + for _, pattern := range usagePatterns { + t.Logf("and %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.AndRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Sub.go b/src/arm/Sub.go index bf84f50..ef2377f 100644 --- a/src/arm/Sub.go +++ b/src/arm/Sub.go @@ -14,7 +14,7 @@ func SubRegisterRegister(destination cpu.Register, source cpu.Register, operand // subRegisterNumber subtracts the register and optionally updates the condition flags based on the result. func subRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { - return flags<<29 | 0b110100010<<23 | reg2imm(destination, source, number) + return flags<<29 | 0b110100010<<23 | reg2Imm(destination, source, number) } // subRegisterRegister subtracts the registers and optionally updates the condition flags based on the result. diff --git a/src/arm/bitmask.go b/src/arm/bitmask.go new file mode 100644 index 0000000..76668a2 --- /dev/null +++ b/src/arm/bitmask.go @@ -0,0 +1,33 @@ +package arm + +import "math/bits" + +// encodeLogicalImmediate encodes a bitmask immediate. +// The algorithm used here was made by Dougall Johnson. +func encodeLogicalImmediate(val uint) (int, bool) { + if val == 0 || ^val == 0 { + return 0, false + } + + rotation := bits.TrailingZeros(clearTrailingOnes(val)) + normalized := bits.RotateLeft(val, -(rotation & 63)) + + zeroes := bits.LeadingZeros(normalized) + ones := bits.TrailingZeros(^normalized) + size := zeroes + ones + + immr := -rotation & (size - 1) + imms := -(size << 1) | (ones - 1) + N := (size >> 6) + + if bits.RotateLeft(val, -(size&63)) != val { + return 0, false + } + + return N<<12 | immr<<6 | (imms & 0x3f), true +} + +// clearTrailingOnes clears trailing one bits. +func clearTrailingOnes(x uint) uint { + return x & (x + 1) +} diff --git a/src/arm/encode.go b/src/arm/encode.go index bab6330..5f3e56b 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -1,6 +1,8 @@ package arm -import "git.urbach.dev/cli/q/src/cpu" +import ( + "git.urbach.dev/cli/q/src/cpu" +) const ( mask6 = 0b111111 @@ -25,8 +27,13 @@ func regImm(d cpu.Register, imm16 int) uint32 { return uint32(imm16&mask16)<<5 | uint32(d) } -// reg2imm encodes an instruction with 2 registers and an immediate. -func reg2imm(d cpu.Register, n cpu.Register, imm12 int) uint32 { +// reg2 encodes an instruction with 2 registers. +func reg2(d cpu.Register, n cpu.Register) uint32 { + return uint32(n)<<5 | uint32(d) +} + +// reg2Imm encodes an instruction with 2 registers and an immediate. +func reg2Imm(d cpu.Register, n cpu.Register, imm12 int) uint32 { return uint32(imm12&mask12)<<10 | uint32(n)<<5 | uint32(d) } @@ -35,6 +42,11 @@ func reg3(d cpu.Register, n cpu.Register, m cpu.Register) uint32 { return uint32(m)<<16 | uint32(n)<<5 | uint32(d) } +// reg3Imm encodes an instruction with 3 registers. +func reg3Imm(d cpu.Register, n cpu.Register, m cpu.Register, imm6 int) uint32 { + return uint32(m)<<16 | uint32(imm6&mask6)<<10 | uint32(n)<<5 | uint32(d) +} + // reg4 encodes an instruction with 4 registers. func reg4(d cpu.Register, n cpu.Register, m cpu.Register, a cpu.Register) uint32 { return uint32(m)<<16 | uint32(a)<<10 | uint32(n)<<5 | uint32(d) From ea575e32566032800f08f682c2d21c439e9b2e16 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Mar 2025 18:37:39 +0100 Subject: [PATCH 0943/1012] Implemented bitwise operations on arm64 --- src/arm/And.go | 9 ++----- src/arm/And_test.go | 3 ++- src/arm/Move.go | 2 +- src/arm/Or.go | 14 +++++++++++ src/arm/Or_test.go | 49 ++++++++++++++++++++++++++++++++++++++ src/arm/Xor.go | 14 +++++++++++ src/arm/Xor_test.go | 49 ++++++++++++++++++++++++++++++++++++++ src/arm/encode.go | 5 ++++ src/asmc/compileARM.go | 54 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 190 insertions(+), 9 deletions(-) create mode 100644 src/arm/Or.go create mode 100644 src/arm/Or_test.go create mode 100644 src/arm/Xor.go create mode 100644 src/arm/Xor_test.go diff --git a/src/arm/And.go b/src/arm/And.go index add44af..9c23ce0 100644 --- a/src/arm/And.go +++ b/src/arm/And.go @@ -5,14 +5,9 @@ import ( ) // AndRegisterNumber performs a bitwise AND using a register and a number. -func AndRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { +func AndRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) { imm13, encodable := encodeLogicalImmediate(uint(number)) - - if !encodable { - panic("bitwise and operand can't be encoded as a bitmask immediate") - } - - return 0b100100100<<23 | uint32(imm13)<<10 | reg2(destination, source) + return 0b100100100<<23 | reg2BitmaskImm(destination, source, imm13), encodable } // AndRegisterRegister performs a bitwise AND using two registers. diff --git a/src/arm/And_test.go b/src/arm/And_test.go index 6e684c4..c8e6317 100644 --- a/src/arm/And_test.go +++ b/src/arm/And_test.go @@ -25,7 +25,8 @@ func TestAndRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("and %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) - code := arm.AndRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + code, encodable := arm.AndRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.True(t, encodable) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/arm/Move.go b/src/arm/Move.go index 5cbc4c9..e18e81d 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -10,7 +10,7 @@ func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 return AddRegisterNumber(destination, source, 0) } - return 0b10101010<<24 | reg3(destination, ZR, source) + return OrRegisterRegister(destination, ZR, source) } // MoveRegisterNumber moves an integer into the given register. diff --git a/src/arm/Or.go b/src/arm/Or.go new file mode 100644 index 0000000..610328b --- /dev/null +++ b/src/arm/Or.go @@ -0,0 +1,14 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// OrRegisterNumber performs a bitwise OR using a register and a number. +func OrRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) { + imm13, encodable := encodeLogicalImmediate(uint(number)) + return 0b101100100<<23 | reg2BitmaskImm(destination, source, imm13), encodable +} + +// OrRegisterRegister performs a bitwise OR using two registers. +func OrRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return 0b10101010<<24 | reg3(destination, source, operand) +} diff --git a/src/arm/Or_test.go b/src/arm/Or_test.go new file mode 100644 index 0000000..0d7aee9 --- /dev/null +++ b/src/arm/Or_test.go @@ -0,0 +1,49 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestOrRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, arm.X1, 1, 0xB2400020}, + {arm.X0, arm.X1, 2, 0xB27F0020}, + {arm.X0, arm.X1, 3, 0xB2400420}, + {arm.X0, arm.X1, 7, 0xB2400820}, + {arm.X0, arm.X1, 16, 0xB27C0020}, + {arm.X0, arm.X1, 255, 0xB2401C20}, + } + + for _, pattern := range usagePatterns { + t.Logf("orr %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) + code, encodable := arm.OrRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.True(t, encodable) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestOrRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0xAA020020}, + } + + for _, pattern := range usagePatterns { + t.Logf("orr %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.OrRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Xor.go b/src/arm/Xor.go new file mode 100644 index 0000000..3194543 --- /dev/null +++ b/src/arm/Xor.go @@ -0,0 +1,14 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// XorRegisterNumber performs a bitwise XOR using a register and a number. +func XorRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) { + imm13, encodable := encodeLogicalImmediate(uint(number)) + return 0b110100100<<23 | reg2BitmaskImm(destination, source, imm13), encodable +} + +// XorRegisterRegister performs a bitwise XOR using two registers. +func XorRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return 0b11001010<<24 | reg3(destination, source, operand) +} diff --git a/src/arm/Xor_test.go b/src/arm/Xor_test.go new file mode 100644 index 0000000..c8f3dc5 --- /dev/null +++ b/src/arm/Xor_test.go @@ -0,0 +1,49 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestXorRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, arm.X1, 1, 0xD2400020}, + {arm.X0, arm.X1, 2, 0xD27F0020}, + {arm.X0, arm.X1, 3, 0xD2400420}, + {arm.X0, arm.X1, 7, 0xD2400820}, + {arm.X0, arm.X1, 16, 0xD27C0020}, + {arm.X0, arm.X1, 255, 0xD2401C20}, + } + + for _, pattern := range usagePatterns { + t.Logf("eor %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) + code, encodable := arm.XorRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.True(t, encodable) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestXorRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0xCA020020}, + } + + for _, pattern := range usagePatterns { + t.Logf("eor %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.XorRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/encode.go b/src/arm/encode.go index 5f3e56b..69724a7 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -37,6 +37,11 @@ func reg2Imm(d cpu.Register, n cpu.Register, imm12 int) uint32 { return uint32(imm12&mask12)<<10 | uint32(n)<<5 | uint32(d) } +// reg2BitmaskImm encodes an instruction with 2 registers and a bitmask immediate. +func reg2BitmaskImm(d cpu.Register, n cpu.Register, imm13 int) uint32 { + return uint32(imm13)<<10 | uint32(n)<<5 | uint32(d) +} + // reg3 encodes an instruction with 3 registers. func reg3(d cpu.Register, n cpu.Register, m cpu.Register) uint32 { return uint32(m)<<16 | uint32(n)<<5 | uint32(d) diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 5658a91..111c708 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -99,6 +99,60 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(arm.AddRegisterNumber(arm.SP, arm.SP, 16)) } + case asm.AND: + switch x.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[x.Index] + code, encodable := arm.AndRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + tmp := arm.X28 + c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.append(arm.AndRegisterRegister(operand.Register, operand.Register, tmp)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.AndRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } + + case asm.OR: + switch x.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[x.Index] + code, encodable := arm.OrRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + tmp := arm.X28 + c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.append(arm.OrRegisterRegister(operand.Register, operand.Register, tmp)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.OrRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } + + case asm.XOR: + switch x.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[x.Index] + code, encodable := arm.XorRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + tmp := arm.X28 + c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.append(arm.XorRegisterRegister(operand.Register, operand.Register, tmp)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.XorRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } + case asm.ADD: switch x.Type { case asm.TypeRegisterNumber: From 5f339187e69bbe607cfca5fd87e920fba5dfb126 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Mar 2025 18:37:39 +0100 Subject: [PATCH 0944/1012] Implemented bitwise operations on arm64 --- src/arm/And.go | 9 ++----- src/arm/And_test.go | 3 ++- src/arm/Move.go | 2 +- src/arm/Or.go | 14 +++++++++++ src/arm/Or_test.go | 49 ++++++++++++++++++++++++++++++++++++++ src/arm/Xor.go | 14 +++++++++++ src/arm/Xor_test.go | 49 ++++++++++++++++++++++++++++++++++++++ src/arm/encode.go | 5 ++++ src/asmc/compileARM.go | 54 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 190 insertions(+), 9 deletions(-) create mode 100644 src/arm/Or.go create mode 100644 src/arm/Or_test.go create mode 100644 src/arm/Xor.go create mode 100644 src/arm/Xor_test.go diff --git a/src/arm/And.go b/src/arm/And.go index add44af..9c23ce0 100644 --- a/src/arm/And.go +++ b/src/arm/And.go @@ -5,14 +5,9 @@ import ( ) // AndRegisterNumber performs a bitwise AND using a register and a number. -func AndRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { +func AndRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) { imm13, encodable := encodeLogicalImmediate(uint(number)) - - if !encodable { - panic("bitwise and operand can't be encoded as a bitmask immediate") - } - - return 0b100100100<<23 | uint32(imm13)<<10 | reg2(destination, source) + return 0b100100100<<23 | reg2BitmaskImm(destination, source, imm13), encodable } // AndRegisterRegister performs a bitwise AND using two registers. diff --git a/src/arm/And_test.go b/src/arm/And_test.go index 6e684c4..c8e6317 100644 --- a/src/arm/And_test.go +++ b/src/arm/And_test.go @@ -25,7 +25,8 @@ func TestAndRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("and %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) - code := arm.AndRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + code, encodable := arm.AndRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.True(t, encodable) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/arm/Move.go b/src/arm/Move.go index 5cbc4c9..e18e81d 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -10,7 +10,7 @@ func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 return AddRegisterNumber(destination, source, 0) } - return 0b10101010<<24 | reg3(destination, ZR, source) + return OrRegisterRegister(destination, ZR, source) } // MoveRegisterNumber moves an integer into the given register. diff --git a/src/arm/Or.go b/src/arm/Or.go new file mode 100644 index 0000000..610328b --- /dev/null +++ b/src/arm/Or.go @@ -0,0 +1,14 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// OrRegisterNumber performs a bitwise OR using a register and a number. +func OrRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) { + imm13, encodable := encodeLogicalImmediate(uint(number)) + return 0b101100100<<23 | reg2BitmaskImm(destination, source, imm13), encodable +} + +// OrRegisterRegister performs a bitwise OR using two registers. +func OrRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return 0b10101010<<24 | reg3(destination, source, operand) +} diff --git a/src/arm/Or_test.go b/src/arm/Or_test.go new file mode 100644 index 0000000..0d7aee9 --- /dev/null +++ b/src/arm/Or_test.go @@ -0,0 +1,49 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestOrRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, arm.X1, 1, 0xB2400020}, + {arm.X0, arm.X1, 2, 0xB27F0020}, + {arm.X0, arm.X1, 3, 0xB2400420}, + {arm.X0, arm.X1, 7, 0xB2400820}, + {arm.X0, arm.X1, 16, 0xB27C0020}, + {arm.X0, arm.X1, 255, 0xB2401C20}, + } + + for _, pattern := range usagePatterns { + t.Logf("orr %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) + code, encodable := arm.OrRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.True(t, encodable) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestOrRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0xAA020020}, + } + + for _, pattern := range usagePatterns { + t.Logf("orr %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.OrRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Xor.go b/src/arm/Xor.go new file mode 100644 index 0000000..3194543 --- /dev/null +++ b/src/arm/Xor.go @@ -0,0 +1,14 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// XorRegisterNumber performs a bitwise XOR using a register and a number. +func XorRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) { + imm13, encodable := encodeLogicalImmediate(uint(number)) + return 0b110100100<<23 | reg2BitmaskImm(destination, source, imm13), encodable +} + +// XorRegisterRegister performs a bitwise XOR using two registers. +func XorRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return 0b11001010<<24 | reg3(destination, source, operand) +} diff --git a/src/arm/Xor_test.go b/src/arm/Xor_test.go new file mode 100644 index 0000000..c8f3dc5 --- /dev/null +++ b/src/arm/Xor_test.go @@ -0,0 +1,49 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestXorRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, arm.X1, 1, 0xD2400020}, + {arm.X0, arm.X1, 2, 0xD27F0020}, + {arm.X0, arm.X1, 3, 0xD2400420}, + {arm.X0, arm.X1, 7, 0xD2400820}, + {arm.X0, arm.X1, 16, 0xD27C0020}, + {arm.X0, arm.X1, 255, 0xD2401C20}, + } + + for _, pattern := range usagePatterns { + t.Logf("eor %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) + code, encodable := arm.XorRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.True(t, encodable) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestXorRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0xCA020020}, + } + + for _, pattern := range usagePatterns { + t.Logf("eor %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.XorRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/encode.go b/src/arm/encode.go index 5f3e56b..69724a7 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -37,6 +37,11 @@ func reg2Imm(d cpu.Register, n cpu.Register, imm12 int) uint32 { return uint32(imm12&mask12)<<10 | uint32(n)<<5 | uint32(d) } +// reg2BitmaskImm encodes an instruction with 2 registers and a bitmask immediate. +func reg2BitmaskImm(d cpu.Register, n cpu.Register, imm13 int) uint32 { + return uint32(imm13)<<10 | uint32(n)<<5 | uint32(d) +} + // reg3 encodes an instruction with 3 registers. func reg3(d cpu.Register, n cpu.Register, m cpu.Register) uint32 { return uint32(m)<<16 | uint32(n)<<5 | uint32(d) diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 5658a91..111c708 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -99,6 +99,60 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(arm.AddRegisterNumber(arm.SP, arm.SP, 16)) } + case asm.AND: + switch x.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[x.Index] + code, encodable := arm.AndRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + tmp := arm.X28 + c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.append(arm.AndRegisterRegister(operand.Register, operand.Register, tmp)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.AndRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } + + case asm.OR: + switch x.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[x.Index] + code, encodable := arm.OrRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + tmp := arm.X28 + c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.append(arm.OrRegisterRegister(operand.Register, operand.Register, tmp)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.OrRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } + + case asm.XOR: + switch x.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[x.Index] + code, encodable := arm.XorRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + tmp := arm.X28 + c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.append(arm.XorRegisterRegister(operand.Register, operand.Register, tmp)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.XorRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } + case asm.ADD: switch x.Type { case asm.TypeRegisterNumber: From 22cbc15cdd16db81e8accf2f3ff4194da2bab6ca Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Mar 2025 21:16:35 +0100 Subject: [PATCH 0945/1012] Implemented more move instructions on arm64 --- src/arm/Move.go | 40 ++++++++++++++++++++++++++++++++++------ src/arm/Move_test.go | 25 +++++++++++++++++++++++-- src/arm/arm_test.go | 1 - src/asmc/compileARM.go | 15 ++++++++------- src/asmc/movARM.go | 16 ++++++++++++++++ 5 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 src/asmc/movARM.go diff --git a/src/arm/Move.go b/src/arm/Move.go index e18e81d..c687c2a 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -2,6 +2,7 @@ package arm import ( "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/sizeof" ) // MoveRegisterRegister copies a register to another register. @@ -13,18 +14,45 @@ func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 return OrRegisterRegister(destination, ZR, source) } -// MoveRegisterNumber moves an integer into the given register. -func MoveRegisterNumber(destination cpu.Register, number int) uint32 { +// MoveRegisterNumber moves a number into the given register. +func MoveRegisterNumber(destination cpu.Register, number int) (uint32, bool) { if number < 0 { - return MoveInvertedWideImmediate(destination, ^number) + return MoveInvertedWideImmediate(destination, ^number, 0), true } - return MoveZero(destination, 0, uint16(number)) + if sizeof.Signed(number) <= 2 { + return MoveZero(destination, 0, uint16(number)), true + } + + if (number&0xFFFFFFFFFFFF == 0xFFFFFFFFFFFF) && sizeof.Signed(number>>48) <= 2 { + return MoveInvertedWideImmediate(destination, (^number)>>48, 3), true + } + + code, encodable := MoveBitmaskImmediate(destination, number) + + if encodable { + return code, true + } + + if (number&0xFFFFFFFF == 0xFFFFFFFF) && sizeof.Signed(number>>32) <= 2 { + return MoveInvertedWideImmediate(destination, (^number)>>32, 2), true + } + + if (number&0xFFFF == 0xFFFF) && sizeof.Signed(number>>16) <= 2 { + return MoveInvertedWideImmediate(destination, (^number)>>16, 1), true + } + + return 0, false +} + +// MoveBitmaskImmediate moves a bitmask immediate value to a register. +func MoveBitmaskImmediate(destination cpu.Register, number int) (uint32, bool) { + return OrRegisterNumber(destination, ZR, number) } // MoveInvertedWideImmediate moves an inverted 16-bit immediate value to a register. -func MoveInvertedWideImmediate(destination cpu.Register, number int) uint32 { - return 0b100100101<<23 | regImm(destination, number) +func MoveInvertedWideImmediate(destination cpu.Register, number int, shift uint32) uint32 { + return 0b100100101<<23 | shift<<21 | regImm(destination, number) } // MoveKeep moves a 16-bit integer into the given register and keeps all other bits. diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go index ba8e5cc..6e02a4e 100644 --- a/src/arm/Move_test.go +++ b/src/arm/Move_test.go @@ -33,15 +33,36 @@ func TestMoveRegisterNumber(t *testing.T) { Number int Code uint32 }{ + // MOVZ {arm.X0, 0, 0xD2800000}, {arm.X0, 1, 0xD2800020}, + + // MOV (bitmask immediate) + {arm.X0, 0x1FFFF, 0xB24043E0}, + {arm.X0, 0x7FFFFFFF, 0xB2407BE0}, + {arm.X0, 0xFFFFFFFF, 0xB2407FE0}, + + // MOV (inverted wide immediate) {arm.X0, -1, 0x92800000}, + {arm.X0, 0x7FFFFFFFFFFFFFFF, 0x92F00000}, + {arm.X0, 0x2FFFFFFFF, 0x92DFFFA0}, // not encodable in the GNU assembler + {arm.X0, 0x2FFFF, 0x92BFFFA0}, // not encodable in the GNU assembler + + // Not encodable + {arm.X0, 0xCAFEBABE, 0}, + {arm.X0, 0xDEADC0DE, 0}, } for _, pattern := range usagePatterns { t.Logf("mov %s, %d", pattern.Register, pattern.Number) - code := arm.MoveRegisterNumber(pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) + code, encodable := arm.MoveRegisterNumber(pattern.Register, pattern.Number) + + if pattern.Code != 0 { + assert.True(t, encodable) + assert.DeepEqual(t, code, pattern.Code) + } else { + assert.False(t, encodable) + } } } diff --git a/src/arm/arm_test.go b/src/arm/arm_test.go index 948f869..43ef207 100644 --- a/src/arm/arm_test.go +++ b/src/arm/arm_test.go @@ -9,7 +9,6 @@ import ( func TestARM(t *testing.T) { assert.DeepEqual(t, arm.Call(0), 0x94000000) - assert.DeepEqual(t, arm.MoveRegisterNumber(arm.X0, 42), arm.MoveZero(arm.X0, 0, 42)) assert.DeepEqual(t, arm.Nop(), 0xD503201F) assert.DeepEqual(t, arm.Return(), 0xD65F03C0) assert.DeepEqual(t, arm.Syscall(), 0xD4000001) diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 111c708..a5b8e05 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -76,8 +76,9 @@ func (c *compiler) compileARM(x asm.Instruction) { operands := c.assembler.Param.MemoryNumber[x.Index] if operands.Address.OffsetRegister < 0 { - c.append(arm.MoveRegisterNumber(arm.X0, operands.Number)) - c.append(arm.StoreRegister(arm.X0, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) + tmp := arm.X28 + c.moveRegisterNumberARM(tmp, operands.Number) + c.append(arm.StoreRegister(tmp, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) } else { panic("not implemented") } @@ -109,7 +110,7 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(code) } else { tmp := arm.X28 - c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.moveRegisterNumberARM(tmp, operand.Number) c.append(arm.AndRegisterRegister(operand.Register, operand.Register, tmp)) } case asm.TypeRegisterRegister: @@ -127,7 +128,7 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(code) } else { tmp := arm.X28 - c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.moveRegisterNumberARM(tmp, operand.Number) c.append(arm.OrRegisterRegister(operand.Register, operand.Register, tmp)) } case asm.TypeRegisterRegister: @@ -145,7 +146,7 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(code) } else { tmp := arm.X28 - c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.moveRegisterNumberARM(tmp, operand.Number) c.append(arm.XorRegisterRegister(operand.Register, operand.Register, tmp)) } case asm.TypeRegisterRegister: @@ -200,7 +201,7 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.TypeRegisterNumber: operand := c.assembler.Param.RegisterNumber[x.Index] tmp := arm.X28 - c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.moveRegisterNumberARM(tmp, operand.Number) c.append(arm.MulRegisterRegister(operand.Register, operand.Register, tmp)) } @@ -226,7 +227,7 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.TypeRegisterNumber: operands := c.assembler.Param.RegisterNumber[x.Index] - c.append(arm.MoveRegisterNumber(operands.Register, operands.Number)) + c.moveRegisterNumberARM(operands.Register, operands.Number) case asm.TypeRegisterLabel: operands := c.assembler.Param.RegisterLabel[x.Index] diff --git a/src/asmc/movARM.go b/src/asmc/movARM.go new file mode 100644 index 0000000..e34fb0e --- /dev/null +++ b/src/asmc/movARM.go @@ -0,0 +1,16 @@ +package asmc + +import ( + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" +) + +func (c *compiler) moveRegisterNumberARM(register cpu.Register, number int) { + code, encodable := arm.MoveRegisterNumber(register, number) + + if encodable { + c.append(code) + } else { + panic("not implemented") // movz movk + } +} From 99ef02bbd678f34380d192b49710a04b946b6fae Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Mar 2025 21:16:35 +0100 Subject: [PATCH 0946/1012] Implemented more move instructions on arm64 --- src/arm/Move.go | 40 ++++++++++++++++++++++++++++++++++------ src/arm/Move_test.go | 25 +++++++++++++++++++++++-- src/arm/arm_test.go | 1 - src/asmc/compileARM.go | 15 ++++++++------- src/asmc/movARM.go | 16 ++++++++++++++++ 5 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 src/asmc/movARM.go diff --git a/src/arm/Move.go b/src/arm/Move.go index e18e81d..c687c2a 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -2,6 +2,7 @@ package arm import ( "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/sizeof" ) // MoveRegisterRegister copies a register to another register. @@ -13,18 +14,45 @@ func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 return OrRegisterRegister(destination, ZR, source) } -// MoveRegisterNumber moves an integer into the given register. -func MoveRegisterNumber(destination cpu.Register, number int) uint32 { +// MoveRegisterNumber moves a number into the given register. +func MoveRegisterNumber(destination cpu.Register, number int) (uint32, bool) { if number < 0 { - return MoveInvertedWideImmediate(destination, ^number) + return MoveInvertedWideImmediate(destination, ^number, 0), true } - return MoveZero(destination, 0, uint16(number)) + if sizeof.Signed(number) <= 2 { + return MoveZero(destination, 0, uint16(number)), true + } + + if (number&0xFFFFFFFFFFFF == 0xFFFFFFFFFFFF) && sizeof.Signed(number>>48) <= 2 { + return MoveInvertedWideImmediate(destination, (^number)>>48, 3), true + } + + code, encodable := MoveBitmaskImmediate(destination, number) + + if encodable { + return code, true + } + + if (number&0xFFFFFFFF == 0xFFFFFFFF) && sizeof.Signed(number>>32) <= 2 { + return MoveInvertedWideImmediate(destination, (^number)>>32, 2), true + } + + if (number&0xFFFF == 0xFFFF) && sizeof.Signed(number>>16) <= 2 { + return MoveInvertedWideImmediate(destination, (^number)>>16, 1), true + } + + return 0, false +} + +// MoveBitmaskImmediate moves a bitmask immediate value to a register. +func MoveBitmaskImmediate(destination cpu.Register, number int) (uint32, bool) { + return OrRegisterNumber(destination, ZR, number) } // MoveInvertedWideImmediate moves an inverted 16-bit immediate value to a register. -func MoveInvertedWideImmediate(destination cpu.Register, number int) uint32 { - return 0b100100101<<23 | regImm(destination, number) +func MoveInvertedWideImmediate(destination cpu.Register, number int, shift uint32) uint32 { + return 0b100100101<<23 | shift<<21 | regImm(destination, number) } // MoveKeep moves a 16-bit integer into the given register and keeps all other bits. diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go index ba8e5cc..6e02a4e 100644 --- a/src/arm/Move_test.go +++ b/src/arm/Move_test.go @@ -33,15 +33,36 @@ func TestMoveRegisterNumber(t *testing.T) { Number int Code uint32 }{ + // MOVZ {arm.X0, 0, 0xD2800000}, {arm.X0, 1, 0xD2800020}, + + // MOV (bitmask immediate) + {arm.X0, 0x1FFFF, 0xB24043E0}, + {arm.X0, 0x7FFFFFFF, 0xB2407BE0}, + {arm.X0, 0xFFFFFFFF, 0xB2407FE0}, + + // MOV (inverted wide immediate) {arm.X0, -1, 0x92800000}, + {arm.X0, 0x7FFFFFFFFFFFFFFF, 0x92F00000}, + {arm.X0, 0x2FFFFFFFF, 0x92DFFFA0}, // not encodable in the GNU assembler + {arm.X0, 0x2FFFF, 0x92BFFFA0}, // not encodable in the GNU assembler + + // Not encodable + {arm.X0, 0xCAFEBABE, 0}, + {arm.X0, 0xDEADC0DE, 0}, } for _, pattern := range usagePatterns { t.Logf("mov %s, %d", pattern.Register, pattern.Number) - code := arm.MoveRegisterNumber(pattern.Register, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) + code, encodable := arm.MoveRegisterNumber(pattern.Register, pattern.Number) + + if pattern.Code != 0 { + assert.True(t, encodable) + assert.DeepEqual(t, code, pattern.Code) + } else { + assert.False(t, encodable) + } } } diff --git a/src/arm/arm_test.go b/src/arm/arm_test.go index 948f869..43ef207 100644 --- a/src/arm/arm_test.go +++ b/src/arm/arm_test.go @@ -9,7 +9,6 @@ import ( func TestARM(t *testing.T) { assert.DeepEqual(t, arm.Call(0), 0x94000000) - assert.DeepEqual(t, arm.MoveRegisterNumber(arm.X0, 42), arm.MoveZero(arm.X0, 0, 42)) assert.DeepEqual(t, arm.Nop(), 0xD503201F) assert.DeepEqual(t, arm.Return(), 0xD65F03C0) assert.DeepEqual(t, arm.Syscall(), 0xD4000001) diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 111c708..a5b8e05 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -76,8 +76,9 @@ func (c *compiler) compileARM(x asm.Instruction) { operands := c.assembler.Param.MemoryNumber[x.Index] if operands.Address.OffsetRegister < 0 { - c.append(arm.MoveRegisterNumber(arm.X0, operands.Number)) - c.append(arm.StoreRegister(arm.X0, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) + tmp := arm.X28 + c.moveRegisterNumberARM(tmp, operands.Number) + c.append(arm.StoreRegister(tmp, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) } else { panic("not implemented") } @@ -109,7 +110,7 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(code) } else { tmp := arm.X28 - c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.moveRegisterNumberARM(tmp, operand.Number) c.append(arm.AndRegisterRegister(operand.Register, operand.Register, tmp)) } case asm.TypeRegisterRegister: @@ -127,7 +128,7 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(code) } else { tmp := arm.X28 - c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.moveRegisterNumberARM(tmp, operand.Number) c.append(arm.OrRegisterRegister(operand.Register, operand.Register, tmp)) } case asm.TypeRegisterRegister: @@ -145,7 +146,7 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(code) } else { tmp := arm.X28 - c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.moveRegisterNumberARM(tmp, operand.Number) c.append(arm.XorRegisterRegister(operand.Register, operand.Register, tmp)) } case asm.TypeRegisterRegister: @@ -200,7 +201,7 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.TypeRegisterNumber: operand := c.assembler.Param.RegisterNumber[x.Index] tmp := arm.X28 - c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.moveRegisterNumberARM(tmp, operand.Number) c.append(arm.MulRegisterRegister(operand.Register, operand.Register, tmp)) } @@ -226,7 +227,7 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.TypeRegisterNumber: operands := c.assembler.Param.RegisterNumber[x.Index] - c.append(arm.MoveRegisterNumber(operands.Register, operands.Number)) + c.moveRegisterNumberARM(operands.Register, operands.Number) case asm.TypeRegisterLabel: operands := c.assembler.Param.RegisterLabel[x.Index] diff --git a/src/asmc/movARM.go b/src/asmc/movARM.go new file mode 100644 index 0000000..e34fb0e --- /dev/null +++ b/src/asmc/movARM.go @@ -0,0 +1,16 @@ +package asmc + +import ( + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" +) + +func (c *compiler) moveRegisterNumberARM(register cpu.Register, number int) { + code, encodable := arm.MoveRegisterNumber(register, number) + + if encodable { + c.append(code) + } else { + panic("not implemented") // movz movk + } +} From bdf41ead5cf721b366037d2fe77b857d8e5a0689 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Mar 2025 13:49:30 +0100 Subject: [PATCH 0947/1012] Fixed move instruction encoding on arm64 --- src/arm/Move.go | 105 ++++++++++++++++++++++-------------- src/arm/Move_test.go | 24 +++++++-- src/arm/encode.go | 11 ++-- src/asmc/compileARM.go | 12 ++--- src/asmc/movARM.go | 16 ------ src/sizeof/Unsigned.go | 10 ++-- src/sizeof/Unsigned_test.go | 4 +- 7 files changed, 108 insertions(+), 74 deletions(-) delete mode 100644 src/asmc/movARM.go diff --git a/src/arm/Move.go b/src/arm/Move.go index c687c2a..a0fc070 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -1,10 +1,73 @@ package arm import ( + "encoding/binary" + "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/sizeof" ) +// MoveRegisterNumber moves a number into the given register. +func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + instruction, encodable := MoveRegisterNumberSI(destination, number) + + if encodable { + return binary.LittleEndian.AppendUint32(code, instruction) + } + + return MoveRegisterNumberMI(code, destination, number) +} + +// MoveRegisterNumberMI moves a number into the given register using movz and a series of movk instructions. +func MoveRegisterNumberMI(code []byte, destination cpu.Register, number int) []byte { + movz := MoveZero(destination, 0, uint16(number)) + code = binary.LittleEndian.AppendUint32(code, movz) + halfword := 1 + + for { + number >>= 16 + + if number == 0 { + return code + } + + movk := MoveKeep(destination, halfword, uint16(number)) + code = binary.LittleEndian.AppendUint32(code, movk) + halfword++ + } +} + +// MoveRegisterNumberSI moves a number into the given register using a single instruction. +func MoveRegisterNumberSI(destination cpu.Register, number int) (uint32, bool) { + if sizeof.Signed(number) <= 2 { + if number < 0 { + return MoveInvertedWideImmediate(destination, uint16(^number), 0), true + } + + return MoveZero(destination, 0, uint16(number)), true + } + + if (number&0xFFFFFFFFFFFF == 0xFFFFFFFFFFFF) && sizeof.Signed(number>>48) <= 2 { + return MoveInvertedWideImmediate(destination, uint16((^number)>>48), 3), true + } + + code, encodable := MoveBitmaskImmediate(destination, number) + + if encodable { + return code, true + } + + if (number&0xFFFFFFFF == 0xFFFFFFFF) && sizeof.Signed(number>>32) <= 2 { + return MoveInvertedWideImmediate(destination, uint16((^number)>>32), 2), true + } + + if (number&0xFFFF == 0xFFFF) && sizeof.Signed(number>>16) <= 2 { + return MoveInvertedWideImmediate(destination, uint16((^number)>>16), 1), true + } + + return 0, false +} + // MoveRegisterRegister copies a register to another register. func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 { if source == SP || destination == SP { @@ -14,58 +77,22 @@ func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 return OrRegisterRegister(destination, ZR, source) } -// MoveRegisterNumber moves a number into the given register. -func MoveRegisterNumber(destination cpu.Register, number int) (uint32, bool) { - if number < 0 { - return MoveInvertedWideImmediate(destination, ^number, 0), true - } - - if sizeof.Signed(number) <= 2 { - return MoveZero(destination, 0, uint16(number)), true - } - - if (number&0xFFFFFFFFFFFF == 0xFFFFFFFFFFFF) && sizeof.Signed(number>>48) <= 2 { - return MoveInvertedWideImmediate(destination, (^number)>>48, 3), true - } - - code, encodable := MoveBitmaskImmediate(destination, number) - - if encodable { - return code, true - } - - if (number&0xFFFFFFFF == 0xFFFFFFFF) && sizeof.Signed(number>>32) <= 2 { - return MoveInvertedWideImmediate(destination, (^number)>>32, 2), true - } - - if (number&0xFFFF == 0xFFFF) && sizeof.Signed(number>>16) <= 2 { - return MoveInvertedWideImmediate(destination, (^number)>>16, 1), true - } - - return 0, false -} - // MoveBitmaskImmediate moves a bitmask immediate value to a register. func MoveBitmaskImmediate(destination cpu.Register, number int) (uint32, bool) { return OrRegisterNumber(destination, ZR, number) } // MoveInvertedWideImmediate moves an inverted 16-bit immediate value to a register. -func MoveInvertedWideImmediate(destination cpu.Register, number int, shift uint32) uint32 { +func MoveInvertedWideImmediate(destination cpu.Register, number uint16, shift uint32) uint32 { return 0b100100101<<23 | shift<<21 | regImm(destination, number) } // MoveKeep moves a 16-bit integer into the given register and keeps all other bits. func MoveKeep(destination cpu.Register, halfword int, number uint16) uint32 { - return mov(0b11, halfword, number, destination) + return 0b111100101<<23 | regImmHw(destination, halfword, number) } // MoveZero moves a 16-bit integer into the given register and clears all other bits to zero. func MoveZero(destination cpu.Register, halfword int, number uint16) uint32 { - return mov(0b10, halfword, number, destination) -} - -// mov encodes a generic move instruction. -func mov(opCode uint32, halfword int, number uint16, destination cpu.Register) uint32 { - return 1<<31 | opCode<<29 | 0b100101<<23 | uint32(halfword<<21) | uint32(number<<5) | uint32(destination) + return 0b110100101<<23 | regImmHw(destination, halfword, number) } diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go index 6e02a4e..af4c58e 100644 --- a/src/arm/Move_test.go +++ b/src/arm/Move_test.go @@ -30,7 +30,24 @@ func TestMoveRegisterRegister(t *testing.T) { func TestMoveRegisterNumber(t *testing.T) { usagePatterns := []struct { Register cpu.Register - Number int + Number uint64 + Code []byte + }{ + {arm.X0, 0xCAFEBABE, []byte{0xC0, 0x57, 0x97, 0xD2, 0xC0, 0x5F, 0xB9, 0xF2}}, + {arm.X0, 0xDEADC0DE, []byte{0xC0, 0x1B, 0x98, 0xD2, 0xA0, 0xD5, 0xBB, 0xF2}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, 0x%X", pattern.Register, pattern.Number) + code := arm.MoveRegisterNumber(nil, pattern.Register, int(pattern.Number)) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestMoveRegisterNumberSI(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number uint64 Code uint32 }{ // MOVZ @@ -41,9 +58,10 @@ func TestMoveRegisterNumber(t *testing.T) { {arm.X0, 0x1FFFF, 0xB24043E0}, {arm.X0, 0x7FFFFFFF, 0xB2407BE0}, {arm.X0, 0xFFFFFFFF, 0xB2407FE0}, + {arm.X0, 0xC3FFFFFFC3FFFFFF, 0xB2026FE0}, // MOV (inverted wide immediate) - {arm.X0, -1, 0x92800000}, + {arm.X0, 0xFFFFFFFFFFFFFFFF, 0x92800000}, {arm.X0, 0x7FFFFFFFFFFFFFFF, 0x92F00000}, {arm.X0, 0x2FFFFFFFF, 0x92DFFFA0}, // not encodable in the GNU assembler {arm.X0, 0x2FFFF, 0x92BFFFA0}, // not encodable in the GNU assembler @@ -55,7 +73,7 @@ func TestMoveRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("mov %s, %d", pattern.Register, pattern.Number) - code, encodable := arm.MoveRegisterNumber(pattern.Register, pattern.Number) + code, encodable := arm.MoveRegisterNumberSI(pattern.Register, int(pattern.Number)) if pattern.Code != 0 { assert.True(t, encodable) diff --git a/src/arm/encode.go b/src/arm/encode.go index 69724a7..3e9aa4b 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -23,13 +23,14 @@ func pair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, imm7 int) uin } // regImm encodes an instruction with a register and an immediate. -func regImm(d cpu.Register, imm16 int) uint32 { - return uint32(imm16&mask16)<<5 | uint32(d) +func regImm(d cpu.Register, imm16 uint16) uint32 { + return uint32(imm16)<<5 | uint32(d) } -// reg2 encodes an instruction with 2 registers. -func reg2(d cpu.Register, n cpu.Register) uint32 { - return uint32(n)<<5 | uint32(d) +// regImmHw encodes an instruction with a register, an immediate and +// the 2-bit halfword specifying which 16-bit region of the register is addressed. +func regImmHw(d cpu.Register, hw int, imm16 uint16) uint32 { + return uint32(hw)<<21 | uint32(imm16)<<5 | uint32(d) } // reg2Imm encodes an instruction with 2 registers and an immediate. diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index a5b8e05..972d1e3 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -77,7 +77,7 @@ func (c *compiler) compileARM(x asm.Instruction) { if operands.Address.OffsetRegister < 0 { tmp := arm.X28 - c.moveRegisterNumberARM(tmp, operands.Number) + c.code = arm.MoveRegisterNumber(c.code, tmp, operands.Number) c.append(arm.StoreRegister(tmp, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) } else { panic("not implemented") @@ -110,7 +110,7 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(code) } else { tmp := arm.X28 - c.moveRegisterNumberARM(tmp, operand.Number) + c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number) c.append(arm.AndRegisterRegister(operand.Register, operand.Register, tmp)) } case asm.TypeRegisterRegister: @@ -128,7 +128,7 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(code) } else { tmp := arm.X28 - c.moveRegisterNumberARM(tmp, operand.Number) + c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number) c.append(arm.OrRegisterRegister(operand.Register, operand.Register, tmp)) } case asm.TypeRegisterRegister: @@ -146,7 +146,7 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(code) } else { tmp := arm.X28 - c.moveRegisterNumberARM(tmp, operand.Number) + c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number) c.append(arm.XorRegisterRegister(operand.Register, operand.Register, tmp)) } case asm.TypeRegisterRegister: @@ -201,7 +201,7 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.TypeRegisterNumber: operand := c.assembler.Param.RegisterNumber[x.Index] tmp := arm.X28 - c.moveRegisterNumberARM(tmp, operand.Number) + c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number) c.append(arm.MulRegisterRegister(operand.Register, operand.Register, tmp)) } @@ -227,7 +227,7 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.TypeRegisterNumber: operands := c.assembler.Param.RegisterNumber[x.Index] - c.moveRegisterNumberARM(operands.Register, operands.Number) + c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) case asm.TypeRegisterLabel: operands := c.assembler.Param.RegisterLabel[x.Index] diff --git a/src/asmc/movARM.go b/src/asmc/movARM.go deleted file mode 100644 index e34fb0e..0000000 --- a/src/asmc/movARM.go +++ /dev/null @@ -1,16 +0,0 @@ -package asmc - -import ( - "git.urbach.dev/cli/q/src/arm" - "git.urbach.dev/cli/q/src/cpu" -) - -func (c *compiler) moveRegisterNumberARM(register cpu.Register, number int) { - code, encodable := arm.MoveRegisterNumber(register, number) - - if encodable { - c.append(code) - } else { - panic("not implemented") // movz movk - } -} diff --git a/src/sizeof/Unsigned.go b/src/sizeof/Unsigned.go index 949b77a..b2df90a 100644 --- a/src/sizeof/Unsigned.go +++ b/src/sizeof/Unsigned.go @@ -3,15 +3,17 @@ package sizeof import "math" // Unsigned tells you how many bytes are needed to encode this unsigned number. -func Unsigned(number uint64) int { +func Unsigned[T uint | uint8 | uint16 | uint32 | uint64 | int | int8 | int16 | int32 | int64](number T) int { + x := uint64(number) + switch { - case number <= math.MaxUint8: + case x <= math.MaxUint8: return 1 - case number <= math.MaxUint16: + case x <= math.MaxUint16: return 2 - case number <= math.MaxUint32: + case x <= math.MaxUint32: return 4 default: diff --git a/src/sizeof/Unsigned_test.go b/src/sizeof/Unsigned_test.go index 94cdd10..b8c20f4 100644 --- a/src/sizeof/Unsigned_test.go +++ b/src/sizeof/Unsigned_test.go @@ -11,7 +11,9 @@ import ( func TestUnsigned(t *testing.T) { assert.Equal(t, sizeof.Unsigned(0), 1) assert.Equal(t, sizeof.Unsigned(math.MaxUint8), 1) + assert.Equal(t, sizeof.Unsigned(math.MaxUint8+1), 2) assert.Equal(t, sizeof.Unsigned(math.MaxUint16), 2) + assert.Equal(t, sizeof.Unsigned(math.MaxUint16+1), 4) assert.Equal(t, sizeof.Unsigned(math.MaxUint32), 4) - assert.Equal(t, sizeof.Unsigned(math.MaxUint64), 8) + assert.Equal(t, sizeof.Unsigned(math.MaxUint32+1), 8) } From fea4e4cbe7875039b361c51d6d98e51f97c750eb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Mar 2025 13:49:30 +0100 Subject: [PATCH 0948/1012] Fixed move instruction encoding on arm64 --- src/arm/Move.go | 105 ++++++++++++++++++++++-------------- src/arm/Move_test.go | 24 +++++++-- src/arm/encode.go | 11 ++-- src/asmc/compileARM.go | 12 ++--- src/asmc/movARM.go | 16 ------ src/sizeof/Unsigned.go | 10 ++-- src/sizeof/Unsigned_test.go | 4 +- 7 files changed, 108 insertions(+), 74 deletions(-) delete mode 100644 src/asmc/movARM.go diff --git a/src/arm/Move.go b/src/arm/Move.go index c687c2a..a0fc070 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -1,10 +1,73 @@ package arm import ( + "encoding/binary" + "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/sizeof" ) +// MoveRegisterNumber moves a number into the given register. +func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + instruction, encodable := MoveRegisterNumberSI(destination, number) + + if encodable { + return binary.LittleEndian.AppendUint32(code, instruction) + } + + return MoveRegisterNumberMI(code, destination, number) +} + +// MoveRegisterNumberMI moves a number into the given register using movz and a series of movk instructions. +func MoveRegisterNumberMI(code []byte, destination cpu.Register, number int) []byte { + movz := MoveZero(destination, 0, uint16(number)) + code = binary.LittleEndian.AppendUint32(code, movz) + halfword := 1 + + for { + number >>= 16 + + if number == 0 { + return code + } + + movk := MoveKeep(destination, halfword, uint16(number)) + code = binary.LittleEndian.AppendUint32(code, movk) + halfword++ + } +} + +// MoveRegisterNumberSI moves a number into the given register using a single instruction. +func MoveRegisterNumberSI(destination cpu.Register, number int) (uint32, bool) { + if sizeof.Signed(number) <= 2 { + if number < 0 { + return MoveInvertedWideImmediate(destination, uint16(^number), 0), true + } + + return MoveZero(destination, 0, uint16(number)), true + } + + if (number&0xFFFFFFFFFFFF == 0xFFFFFFFFFFFF) && sizeof.Signed(number>>48) <= 2 { + return MoveInvertedWideImmediate(destination, uint16((^number)>>48), 3), true + } + + code, encodable := MoveBitmaskImmediate(destination, number) + + if encodable { + return code, true + } + + if (number&0xFFFFFFFF == 0xFFFFFFFF) && sizeof.Signed(number>>32) <= 2 { + return MoveInvertedWideImmediate(destination, uint16((^number)>>32), 2), true + } + + if (number&0xFFFF == 0xFFFF) && sizeof.Signed(number>>16) <= 2 { + return MoveInvertedWideImmediate(destination, uint16((^number)>>16), 1), true + } + + return 0, false +} + // MoveRegisterRegister copies a register to another register. func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 { if source == SP || destination == SP { @@ -14,58 +77,22 @@ func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 return OrRegisterRegister(destination, ZR, source) } -// MoveRegisterNumber moves a number into the given register. -func MoveRegisterNumber(destination cpu.Register, number int) (uint32, bool) { - if number < 0 { - return MoveInvertedWideImmediate(destination, ^number, 0), true - } - - if sizeof.Signed(number) <= 2 { - return MoveZero(destination, 0, uint16(number)), true - } - - if (number&0xFFFFFFFFFFFF == 0xFFFFFFFFFFFF) && sizeof.Signed(number>>48) <= 2 { - return MoveInvertedWideImmediate(destination, (^number)>>48, 3), true - } - - code, encodable := MoveBitmaskImmediate(destination, number) - - if encodable { - return code, true - } - - if (number&0xFFFFFFFF == 0xFFFFFFFF) && sizeof.Signed(number>>32) <= 2 { - return MoveInvertedWideImmediate(destination, (^number)>>32, 2), true - } - - if (number&0xFFFF == 0xFFFF) && sizeof.Signed(number>>16) <= 2 { - return MoveInvertedWideImmediate(destination, (^number)>>16, 1), true - } - - return 0, false -} - // MoveBitmaskImmediate moves a bitmask immediate value to a register. func MoveBitmaskImmediate(destination cpu.Register, number int) (uint32, bool) { return OrRegisterNumber(destination, ZR, number) } // MoveInvertedWideImmediate moves an inverted 16-bit immediate value to a register. -func MoveInvertedWideImmediate(destination cpu.Register, number int, shift uint32) uint32 { +func MoveInvertedWideImmediate(destination cpu.Register, number uint16, shift uint32) uint32 { return 0b100100101<<23 | shift<<21 | regImm(destination, number) } // MoveKeep moves a 16-bit integer into the given register and keeps all other bits. func MoveKeep(destination cpu.Register, halfword int, number uint16) uint32 { - return mov(0b11, halfword, number, destination) + return 0b111100101<<23 | regImmHw(destination, halfword, number) } // MoveZero moves a 16-bit integer into the given register and clears all other bits to zero. func MoveZero(destination cpu.Register, halfword int, number uint16) uint32 { - return mov(0b10, halfword, number, destination) -} - -// mov encodes a generic move instruction. -func mov(opCode uint32, halfword int, number uint16, destination cpu.Register) uint32 { - return 1<<31 | opCode<<29 | 0b100101<<23 | uint32(halfword<<21) | uint32(number<<5) | uint32(destination) + return 0b110100101<<23 | regImmHw(destination, halfword, number) } diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go index 6e02a4e..af4c58e 100644 --- a/src/arm/Move_test.go +++ b/src/arm/Move_test.go @@ -30,7 +30,24 @@ func TestMoveRegisterRegister(t *testing.T) { func TestMoveRegisterNumber(t *testing.T) { usagePatterns := []struct { Register cpu.Register - Number int + Number uint64 + Code []byte + }{ + {arm.X0, 0xCAFEBABE, []byte{0xC0, 0x57, 0x97, 0xD2, 0xC0, 0x5F, 0xB9, 0xF2}}, + {arm.X0, 0xDEADC0DE, []byte{0xC0, 0x1B, 0x98, 0xD2, 0xA0, 0xD5, 0xBB, 0xF2}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, 0x%X", pattern.Register, pattern.Number) + code := arm.MoveRegisterNumber(nil, pattern.Register, int(pattern.Number)) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestMoveRegisterNumberSI(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number uint64 Code uint32 }{ // MOVZ @@ -41,9 +58,10 @@ func TestMoveRegisterNumber(t *testing.T) { {arm.X0, 0x1FFFF, 0xB24043E0}, {arm.X0, 0x7FFFFFFF, 0xB2407BE0}, {arm.X0, 0xFFFFFFFF, 0xB2407FE0}, + {arm.X0, 0xC3FFFFFFC3FFFFFF, 0xB2026FE0}, // MOV (inverted wide immediate) - {arm.X0, -1, 0x92800000}, + {arm.X0, 0xFFFFFFFFFFFFFFFF, 0x92800000}, {arm.X0, 0x7FFFFFFFFFFFFFFF, 0x92F00000}, {arm.X0, 0x2FFFFFFFF, 0x92DFFFA0}, // not encodable in the GNU assembler {arm.X0, 0x2FFFF, 0x92BFFFA0}, // not encodable in the GNU assembler @@ -55,7 +73,7 @@ func TestMoveRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("mov %s, %d", pattern.Register, pattern.Number) - code, encodable := arm.MoveRegisterNumber(pattern.Register, pattern.Number) + code, encodable := arm.MoveRegisterNumberSI(pattern.Register, int(pattern.Number)) if pattern.Code != 0 { assert.True(t, encodable) diff --git a/src/arm/encode.go b/src/arm/encode.go index 69724a7..3e9aa4b 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -23,13 +23,14 @@ func pair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, imm7 int) uin } // regImm encodes an instruction with a register and an immediate. -func regImm(d cpu.Register, imm16 int) uint32 { - return uint32(imm16&mask16)<<5 | uint32(d) +func regImm(d cpu.Register, imm16 uint16) uint32 { + return uint32(imm16)<<5 | uint32(d) } -// reg2 encodes an instruction with 2 registers. -func reg2(d cpu.Register, n cpu.Register) uint32 { - return uint32(n)<<5 | uint32(d) +// regImmHw encodes an instruction with a register, an immediate and +// the 2-bit halfword specifying which 16-bit region of the register is addressed. +func regImmHw(d cpu.Register, hw int, imm16 uint16) uint32 { + return uint32(hw)<<21 | uint32(imm16)<<5 | uint32(d) } // reg2Imm encodes an instruction with 2 registers and an immediate. diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index a5b8e05..972d1e3 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -77,7 +77,7 @@ func (c *compiler) compileARM(x asm.Instruction) { if operands.Address.OffsetRegister < 0 { tmp := arm.X28 - c.moveRegisterNumberARM(tmp, operands.Number) + c.code = arm.MoveRegisterNumber(c.code, tmp, operands.Number) c.append(arm.StoreRegister(tmp, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) } else { panic("not implemented") @@ -110,7 +110,7 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(code) } else { tmp := arm.X28 - c.moveRegisterNumberARM(tmp, operand.Number) + c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number) c.append(arm.AndRegisterRegister(operand.Register, operand.Register, tmp)) } case asm.TypeRegisterRegister: @@ -128,7 +128,7 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(code) } else { tmp := arm.X28 - c.moveRegisterNumberARM(tmp, operand.Number) + c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number) c.append(arm.OrRegisterRegister(operand.Register, operand.Register, tmp)) } case asm.TypeRegisterRegister: @@ -146,7 +146,7 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(code) } else { tmp := arm.X28 - c.moveRegisterNumberARM(tmp, operand.Number) + c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number) c.append(arm.XorRegisterRegister(operand.Register, operand.Register, tmp)) } case asm.TypeRegisterRegister: @@ -201,7 +201,7 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.TypeRegisterNumber: operand := c.assembler.Param.RegisterNumber[x.Index] tmp := arm.X28 - c.moveRegisterNumberARM(tmp, operand.Number) + c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number) c.append(arm.MulRegisterRegister(operand.Register, operand.Register, tmp)) } @@ -227,7 +227,7 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.TypeRegisterNumber: operands := c.assembler.Param.RegisterNumber[x.Index] - c.moveRegisterNumberARM(operands.Register, operands.Number) + c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) case asm.TypeRegisterLabel: operands := c.assembler.Param.RegisterLabel[x.Index] diff --git a/src/asmc/movARM.go b/src/asmc/movARM.go deleted file mode 100644 index e34fb0e..0000000 --- a/src/asmc/movARM.go +++ /dev/null @@ -1,16 +0,0 @@ -package asmc - -import ( - "git.urbach.dev/cli/q/src/arm" - "git.urbach.dev/cli/q/src/cpu" -) - -func (c *compiler) moveRegisterNumberARM(register cpu.Register, number int) { - code, encodable := arm.MoveRegisterNumber(register, number) - - if encodable { - c.append(code) - } else { - panic("not implemented") // movz movk - } -} diff --git a/src/sizeof/Unsigned.go b/src/sizeof/Unsigned.go index 949b77a..b2df90a 100644 --- a/src/sizeof/Unsigned.go +++ b/src/sizeof/Unsigned.go @@ -3,15 +3,17 @@ package sizeof import "math" // Unsigned tells you how many bytes are needed to encode this unsigned number. -func Unsigned(number uint64) int { +func Unsigned[T uint | uint8 | uint16 | uint32 | uint64 | int | int8 | int16 | int32 | int64](number T) int { + x := uint64(number) + switch { - case number <= math.MaxUint8: + case x <= math.MaxUint8: return 1 - case number <= math.MaxUint16: + case x <= math.MaxUint16: return 2 - case number <= math.MaxUint32: + case x <= math.MaxUint32: return 4 default: diff --git a/src/sizeof/Unsigned_test.go b/src/sizeof/Unsigned_test.go index 94cdd10..b8c20f4 100644 --- a/src/sizeof/Unsigned_test.go +++ b/src/sizeof/Unsigned_test.go @@ -11,7 +11,9 @@ import ( func TestUnsigned(t *testing.T) { assert.Equal(t, sizeof.Unsigned(0), 1) assert.Equal(t, sizeof.Unsigned(math.MaxUint8), 1) + assert.Equal(t, sizeof.Unsigned(math.MaxUint8+1), 2) assert.Equal(t, sizeof.Unsigned(math.MaxUint16), 2) + assert.Equal(t, sizeof.Unsigned(math.MaxUint16+1), 4) assert.Equal(t, sizeof.Unsigned(math.MaxUint32), 4) - assert.Equal(t, sizeof.Unsigned(math.MaxUint64), 8) + assert.Equal(t, sizeof.Unsigned(math.MaxUint32+1), 8) } From 1bb1b940fdf60811d8abc3e41d5d853a827d5599 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Mar 2025 15:51:08 +0100 Subject: [PATCH 0949/1012] Fixed add and sub instruction encoding on arm64 --- src/arm/Add.go | 17 ++++++++++++++++- src/arm/Add_test.go | 1 + src/arm/Compare_test.go | 2 ++ src/arm/Move.go | 18 +++++++++--------- src/arm/Move_test.go | 6 ++++-- src/arm/Sub.go | 17 ++++++++++++++++- src/arm/Sub_test.go | 1 + 7 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/arm/Add.go b/src/arm/Add.go index 3ae49e3..ba641a0 100644 --- a/src/arm/Add.go +++ b/src/arm/Add.go @@ -14,7 +14,22 @@ func AddRegisterRegister(destination cpu.Register, source cpu.Register, operand // addRegisterNumber adds the register and optionally updates the condition flags based on the result. func addRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { - return flags<<29 | 0b100100010<<23 | reg2Imm(destination, source, number) + shift := uint32(0) + + if number > mask12 { + if number&mask12 != 0 { + panic("number can't be encoded") + } + + shift = 1 + number >>= 12 + + if number > mask12 { + panic("number can't be encoded") + } + } + + return flags<<29 | 0b100100010<<23 | shift<<22 | reg2Imm(destination, source, number) } // addRegisterRegister adds the registers and optionally updates the condition flags based on the result. diff --git a/src/arm/Add_test.go b/src/arm/Add_test.go index 1051b06..3c75ecc 100644 --- a/src/arm/Add_test.go +++ b/src/arm/Add_test.go @@ -16,6 +16,7 @@ func TestAddRegisterNumber(t *testing.T) { Code uint32 }{ {arm.X0, arm.X0, 1, 0x91000400}, + {arm.X0, arm.X0, 0x1000, 0x91400400}, } for _, pattern := range usagePatterns { diff --git a/src/arm/Compare_test.go b/src/arm/Compare_test.go index 8224614..4580aea 100644 --- a/src/arm/Compare_test.go +++ b/src/arm/Compare_test.go @@ -14,8 +14,10 @@ func TestCompareRegisterNumber(t *testing.T) { Number int Code uint32 }{ + {arm.X0, 0, 0xF100001F}, {arm.X0, 1, 0xF100041F}, {arm.X0, -1, 0xB100041F}, + {arm.X0, 0x1000, 0xF140041F}, } for _, pattern := range usagePatterns { diff --git a/src/arm/Move.go b/src/arm/Move.go index a0fc070..d5851ec 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -41,28 +41,28 @@ func MoveRegisterNumberMI(code []byte, destination cpu.Register, number int) []b func MoveRegisterNumberSI(destination cpu.Register, number int) (uint32, bool) { if sizeof.Signed(number) <= 2 { if number < 0 { - return MoveInvertedWideImmediate(destination, uint16(^number), 0), true + return MoveInvertedNumber(destination, uint16(^number), 0), true } return MoveZero(destination, 0, uint16(number)), true } if (number&0xFFFFFFFFFFFF == 0xFFFFFFFFFFFF) && sizeof.Signed(number>>48) <= 2 { - return MoveInvertedWideImmediate(destination, uint16((^number)>>48), 3), true + return MoveInvertedNumber(destination, uint16((^number)>>48), 3), true } - code, encodable := MoveBitmaskImmediate(destination, number) + code, encodable := MoveBitmaskNumber(destination, number) if encodable { return code, true } if (number&0xFFFFFFFF == 0xFFFFFFFF) && sizeof.Signed(number>>32) <= 2 { - return MoveInvertedWideImmediate(destination, uint16((^number)>>32), 2), true + return MoveInvertedNumber(destination, uint16((^number)>>32), 2), true } if (number&0xFFFF == 0xFFFF) && sizeof.Signed(number>>16) <= 2 { - return MoveInvertedWideImmediate(destination, uint16((^number)>>16), 1), true + return MoveInvertedNumber(destination, uint16((^number)>>16), 1), true } return 0, false @@ -77,13 +77,13 @@ func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 return OrRegisterRegister(destination, ZR, source) } -// MoveBitmaskImmediate moves a bitmask immediate value to a register. -func MoveBitmaskImmediate(destination cpu.Register, number int) (uint32, bool) { +// MoveBitmaskNumber moves a bitmask immediate value to a register. +func MoveBitmaskNumber(destination cpu.Register, number int) (uint32, bool) { return OrRegisterNumber(destination, ZR, number) } -// MoveInvertedWideImmediate moves an inverted 16-bit immediate value to a register. -func MoveInvertedWideImmediate(destination cpu.Register, number uint16, shift uint32) uint32 { +// MoveInvertedNumber moves an inverted 16-bit immediate value to a register. +func MoveInvertedNumber(destination cpu.Register, number uint16, shift uint32) uint32 { return 0b100100101<<23 | shift<<21 | regImm(destination, number) } diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go index af4c58e..acfb1a3 100644 --- a/src/arm/Move_test.go +++ b/src/arm/Move_test.go @@ -33,6 +33,7 @@ func TestMoveRegisterNumber(t *testing.T) { Number uint64 Code []byte }{ + {arm.X0, 0, []byte{0x00, 0x00, 0x80, 0xD2}}, {arm.X0, 0xCAFEBABE, []byte{0xC0, 0x57, 0x97, 0xD2, 0xC0, 0x5F, 0xB9, 0xF2}}, {arm.X0, 0xDEADC0DE, []byte{0xC0, 0x1B, 0x98, 0xD2, 0xA0, 0xD5, 0xBB, 0xF2}}, } @@ -51,8 +52,9 @@ func TestMoveRegisterNumberSI(t *testing.T) { Code uint32 }{ // MOVZ - {arm.X0, 0, 0xD2800000}, - {arm.X0, 1, 0xD2800020}, + {arm.X0, 0x0, 0xD2800000}, + {arm.X0, 0x1, 0xD2800020}, + {arm.X0, 0x1000, 0xD2820000}, // MOV (bitmask immediate) {arm.X0, 0x1FFFF, 0xB24043E0}, diff --git a/src/arm/Sub.go b/src/arm/Sub.go index ef2377f..37d0a9e 100644 --- a/src/arm/Sub.go +++ b/src/arm/Sub.go @@ -14,7 +14,22 @@ func SubRegisterRegister(destination cpu.Register, source cpu.Register, operand // subRegisterNumber subtracts the register and optionally updates the condition flags based on the result. func subRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { - return flags<<29 | 0b110100010<<23 | reg2Imm(destination, source, number) + shift := uint32(0) + + if number > mask12 { + if number&mask12 != 0 { + panic("number can't be encoded") + } + + shift = 1 + number >>= 12 + + if number > mask12 { + panic("number can't be encoded") + } + } + + return flags<<29 | 0b110100010<<23 | shift<<22 | reg2Imm(destination, source, number) } // subRegisterRegister subtracts the registers and optionally updates the condition flags based on the result. diff --git a/src/arm/Sub_test.go b/src/arm/Sub_test.go index dc42615..e508c75 100644 --- a/src/arm/Sub_test.go +++ b/src/arm/Sub_test.go @@ -16,6 +16,7 @@ func TestSubRegisterNumber(t *testing.T) { Code uint32 }{ {arm.X0, arm.X0, 1, 0xD1000400}, + {arm.X0, arm.X0, 0x1000, 0xD1400400}, {arm.SP, arm.SP, 16, 0xD10043FF}, } From 93042f81e736e0e16ce5973c3dcf22b9644c7d5f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Mar 2025 15:51:08 +0100 Subject: [PATCH 0950/1012] Fixed add and sub instruction encoding on arm64 --- src/arm/Add.go | 17 ++++++++++++++++- src/arm/Add_test.go | 1 + src/arm/Compare_test.go | 2 ++ src/arm/Move.go | 18 +++++++++--------- src/arm/Move_test.go | 6 ++++-- src/arm/Sub.go | 17 ++++++++++++++++- src/arm/Sub_test.go | 1 + 7 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/arm/Add.go b/src/arm/Add.go index 3ae49e3..ba641a0 100644 --- a/src/arm/Add.go +++ b/src/arm/Add.go @@ -14,7 +14,22 @@ func AddRegisterRegister(destination cpu.Register, source cpu.Register, operand // addRegisterNumber adds the register and optionally updates the condition flags based on the result. func addRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { - return flags<<29 | 0b100100010<<23 | reg2Imm(destination, source, number) + shift := uint32(0) + + if number > mask12 { + if number&mask12 != 0 { + panic("number can't be encoded") + } + + shift = 1 + number >>= 12 + + if number > mask12 { + panic("number can't be encoded") + } + } + + return flags<<29 | 0b100100010<<23 | shift<<22 | reg2Imm(destination, source, number) } // addRegisterRegister adds the registers and optionally updates the condition flags based on the result. diff --git a/src/arm/Add_test.go b/src/arm/Add_test.go index 1051b06..3c75ecc 100644 --- a/src/arm/Add_test.go +++ b/src/arm/Add_test.go @@ -16,6 +16,7 @@ func TestAddRegisterNumber(t *testing.T) { Code uint32 }{ {arm.X0, arm.X0, 1, 0x91000400}, + {arm.X0, arm.X0, 0x1000, 0x91400400}, } for _, pattern := range usagePatterns { diff --git a/src/arm/Compare_test.go b/src/arm/Compare_test.go index 8224614..4580aea 100644 --- a/src/arm/Compare_test.go +++ b/src/arm/Compare_test.go @@ -14,8 +14,10 @@ func TestCompareRegisterNumber(t *testing.T) { Number int Code uint32 }{ + {arm.X0, 0, 0xF100001F}, {arm.X0, 1, 0xF100041F}, {arm.X0, -1, 0xB100041F}, + {arm.X0, 0x1000, 0xF140041F}, } for _, pattern := range usagePatterns { diff --git a/src/arm/Move.go b/src/arm/Move.go index a0fc070..d5851ec 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -41,28 +41,28 @@ func MoveRegisterNumberMI(code []byte, destination cpu.Register, number int) []b func MoveRegisterNumberSI(destination cpu.Register, number int) (uint32, bool) { if sizeof.Signed(number) <= 2 { if number < 0 { - return MoveInvertedWideImmediate(destination, uint16(^number), 0), true + return MoveInvertedNumber(destination, uint16(^number), 0), true } return MoveZero(destination, 0, uint16(number)), true } if (number&0xFFFFFFFFFFFF == 0xFFFFFFFFFFFF) && sizeof.Signed(number>>48) <= 2 { - return MoveInvertedWideImmediate(destination, uint16((^number)>>48), 3), true + return MoveInvertedNumber(destination, uint16((^number)>>48), 3), true } - code, encodable := MoveBitmaskImmediate(destination, number) + code, encodable := MoveBitmaskNumber(destination, number) if encodable { return code, true } if (number&0xFFFFFFFF == 0xFFFFFFFF) && sizeof.Signed(number>>32) <= 2 { - return MoveInvertedWideImmediate(destination, uint16((^number)>>32), 2), true + return MoveInvertedNumber(destination, uint16((^number)>>32), 2), true } if (number&0xFFFF == 0xFFFF) && sizeof.Signed(number>>16) <= 2 { - return MoveInvertedWideImmediate(destination, uint16((^number)>>16), 1), true + return MoveInvertedNumber(destination, uint16((^number)>>16), 1), true } return 0, false @@ -77,13 +77,13 @@ func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 return OrRegisterRegister(destination, ZR, source) } -// MoveBitmaskImmediate moves a bitmask immediate value to a register. -func MoveBitmaskImmediate(destination cpu.Register, number int) (uint32, bool) { +// MoveBitmaskNumber moves a bitmask immediate value to a register. +func MoveBitmaskNumber(destination cpu.Register, number int) (uint32, bool) { return OrRegisterNumber(destination, ZR, number) } -// MoveInvertedWideImmediate moves an inverted 16-bit immediate value to a register. -func MoveInvertedWideImmediate(destination cpu.Register, number uint16, shift uint32) uint32 { +// MoveInvertedNumber moves an inverted 16-bit immediate value to a register. +func MoveInvertedNumber(destination cpu.Register, number uint16, shift uint32) uint32 { return 0b100100101<<23 | shift<<21 | regImm(destination, number) } diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go index af4c58e..acfb1a3 100644 --- a/src/arm/Move_test.go +++ b/src/arm/Move_test.go @@ -33,6 +33,7 @@ func TestMoveRegisterNumber(t *testing.T) { Number uint64 Code []byte }{ + {arm.X0, 0, []byte{0x00, 0x00, 0x80, 0xD2}}, {arm.X0, 0xCAFEBABE, []byte{0xC0, 0x57, 0x97, 0xD2, 0xC0, 0x5F, 0xB9, 0xF2}}, {arm.X0, 0xDEADC0DE, []byte{0xC0, 0x1B, 0x98, 0xD2, 0xA0, 0xD5, 0xBB, 0xF2}}, } @@ -51,8 +52,9 @@ func TestMoveRegisterNumberSI(t *testing.T) { Code uint32 }{ // MOVZ - {arm.X0, 0, 0xD2800000}, - {arm.X0, 1, 0xD2800020}, + {arm.X0, 0x0, 0xD2800000}, + {arm.X0, 0x1, 0xD2800020}, + {arm.X0, 0x1000, 0xD2820000}, // MOV (bitmask immediate) {arm.X0, 0x1FFFF, 0xB24043E0}, diff --git a/src/arm/Sub.go b/src/arm/Sub.go index ef2377f..37d0a9e 100644 --- a/src/arm/Sub.go +++ b/src/arm/Sub.go @@ -14,7 +14,22 @@ func SubRegisterRegister(destination cpu.Register, source cpu.Register, operand // subRegisterNumber subtracts the register and optionally updates the condition flags based on the result. func subRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { - return flags<<29 | 0b110100010<<23 | reg2Imm(destination, source, number) + shift := uint32(0) + + if number > mask12 { + if number&mask12 != 0 { + panic("number can't be encoded") + } + + shift = 1 + number >>= 12 + + if number > mask12 { + panic("number can't be encoded") + } + } + + return flags<<29 | 0b110100010<<23 | shift<<22 | reg2Imm(destination, source, number) } // subRegisterRegister subtracts the registers and optionally updates the condition flags based on the result. diff --git a/src/arm/Sub_test.go b/src/arm/Sub_test.go index dc42615..e508c75 100644 --- a/src/arm/Sub_test.go +++ b/src/arm/Sub_test.go @@ -16,6 +16,7 @@ func TestSubRegisterNumber(t *testing.T) { Code uint32 }{ {arm.X0, arm.X0, 1, 0xD1000400}, + {arm.X0, arm.X0, 0x1000, 0xD1400400}, {arm.SP, arm.SP, 16, 0xD10043FF}, } From b3b233c6c9c4801179de5cd100e0933f7954da13 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Mar 2025 19:45:14 +0100 Subject: [PATCH 0951/1012] Implemented bit shifting on arm64 --- src/arm/And.go | 4 ++-- src/arm/Or.go | 4 ++-- src/arm/Shift.go | 15 ++++++++++++ src/arm/Shift_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++ src/arm/Xor.go | 4 ++-- src/arm/arm_test.go | 17 +++++++++++++- src/arm/bitmask.go | 14 ++++++------ src/arm/encode.go | 4 ++-- src/asmc/compileARM.go | 18 +++++++++++++++ src/asmc/compileX86.go | 4 ++++ 10 files changed, 120 insertions(+), 16 deletions(-) create mode 100644 src/arm/Shift.go create mode 100644 src/arm/Shift_test.go diff --git a/src/arm/And.go b/src/arm/And.go index 9c23ce0..8850855 100644 --- a/src/arm/And.go +++ b/src/arm/And.go @@ -6,8 +6,8 @@ import ( // AndRegisterNumber performs a bitwise AND using a register and a number. func AndRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) { - imm13, encodable := encodeLogicalImmediate(uint(number)) - return 0b100100100<<23 | reg2BitmaskImm(destination, source, imm13), encodable + n, immr, imms, encodable := encodeLogicalImmediate(uint(number)) + return 0b100100100<<23 | reg2BitmaskImm(destination, source, n, immr, imms), encodable } // AndRegisterRegister performs a bitwise AND using two registers. diff --git a/src/arm/Or.go b/src/arm/Or.go index 610328b..1d137a8 100644 --- a/src/arm/Or.go +++ b/src/arm/Or.go @@ -4,8 +4,8 @@ import "git.urbach.dev/cli/q/src/cpu" // OrRegisterNumber performs a bitwise OR using a register and a number. func OrRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) { - imm13, encodable := encodeLogicalImmediate(uint(number)) - return 0b101100100<<23 | reg2BitmaskImm(destination, source, imm13), encodable + n, immr, imms, encodable := encodeLogicalImmediate(uint(number)) + return 0b101100100<<23 | reg2BitmaskImm(destination, source, n, immr, imms), encodable } // OrRegisterRegister performs a bitwise OR using two registers. diff --git a/src/arm/Shift.go b/src/arm/Shift.go new file mode 100644 index 0000000..99ad4d2 --- /dev/null +++ b/src/arm/Shift.go @@ -0,0 +1,15 @@ +package arm + +import ( + "git.urbach.dev/cli/q/src/cpu" +) + +// ShiftLeftNumber shifts the register value a specified amount of bits to the left. +func ShiftLeftNumber(destination cpu.Register, source cpu.Register, bits int) uint32 { + return 0b110100110<<23 | reg2BitmaskImm(destination, source, 1, 64-bits, (^bits)&mask6) +} + +// ShiftRightSignedNumber shifts the signed register value a specified amount of bits to the right. +func ShiftRightSignedNumber(destination cpu.Register, source cpu.Register, bits int) uint32 { + return 0b100100110<<23 | reg2BitmaskImm(destination, source, 1, bits&mask6, 0b111111) +} diff --git a/src/arm/Shift_test.go b/src/arm/Shift_test.go new file mode 100644 index 0000000..8aa96d2 --- /dev/null +++ b/src/arm/Shift_test.go @@ -0,0 +1,52 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestShiftLeftNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Bits int + Code uint32 + }{ + {arm.X0, arm.X0, 0, 0xD340FC00}, + {arm.X0, arm.X0, 1, 0xD37FF800}, + {arm.X0, arm.X0, 8, 0xD378DC00}, + {arm.X0, arm.X0, 16, 0xD370BC00}, + {arm.X0, arm.X0, 63, 0xD3410000}, + } + + for _, pattern := range usagePatterns { + t.Logf("%b", pattern.Code) + t.Logf("lsl %s, %s, %x", pattern.Destination, pattern.Source, pattern.Bits) + code := arm.ShiftLeftNumber(pattern.Destination, pattern.Source, pattern.Bits) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestShiftRightSignedNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Bits int + Code uint32 + }{ + {arm.X0, arm.X0, 0, 0x9340FC00}, + {arm.X0, arm.X0, 1, 0x9341FC00}, + {arm.X0, arm.X0, 8, 0x9348FC00}, + {arm.X0, arm.X0, 16, 0x9350FC00}, + {arm.X0, arm.X0, 63, 0x937FFC00}, + } + + for _, pattern := range usagePatterns { + t.Logf("asr %s, %s, %x", pattern.Destination, pattern.Source, pattern.Bits) + code := arm.ShiftRightSignedNumber(pattern.Destination, pattern.Source, pattern.Bits) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Xor.go b/src/arm/Xor.go index 3194543..3580136 100644 --- a/src/arm/Xor.go +++ b/src/arm/Xor.go @@ -4,8 +4,8 @@ import "git.urbach.dev/cli/q/src/cpu" // XorRegisterNumber performs a bitwise XOR using a register and a number. func XorRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) { - imm13, encodable := encodeLogicalImmediate(uint(number)) - return 0b110100100<<23 | reg2BitmaskImm(destination, source, imm13), encodable + n, immr, imms, encodable := encodeLogicalImmediate(uint(number)) + return 0b110100100<<23 | reg2BitmaskImm(destination, source, n, immr, imms), encodable } // XorRegisterRegister performs a bitwise XOR using two registers. diff --git a/src/arm/arm_test.go b/src/arm/arm_test.go index 43ef207..b81e610 100644 --- a/src/arm/arm_test.go +++ b/src/arm/arm_test.go @@ -7,9 +7,24 @@ import ( "git.urbach.dev/go/assert" ) -func TestARM(t *testing.T) { +func TestGeneral(t *testing.T) { assert.DeepEqual(t, arm.Call(0), 0x94000000) assert.DeepEqual(t, arm.Nop(), 0xD503201F) assert.DeepEqual(t, arm.Return(), 0xD65F03C0) assert.DeepEqual(t, arm.Syscall(), 0xD4000001) } + +func TestNotEncodable(t *testing.T) { + _, encodable := arm.AndRegisterNumber(arm.X0, arm.X0, 0) + assert.False(t, encodable) + _, encodable = arm.OrRegisterNumber(arm.X0, arm.X0, 0) + assert.False(t, encodable) + _, encodable = arm.XorRegisterNumber(arm.X0, arm.X0, 0) + assert.False(t, encodable) + _, encodable = arm.AndRegisterNumber(arm.X0, arm.X0, -1) + assert.False(t, encodable) + _, encodable = arm.OrRegisterNumber(arm.X0, arm.X0, -1) + assert.False(t, encodable) + _, encodable = arm.XorRegisterNumber(arm.X0, arm.X0, -1) + assert.False(t, encodable) +} diff --git a/src/arm/bitmask.go b/src/arm/bitmask.go index 76668a2..19a65f8 100644 --- a/src/arm/bitmask.go +++ b/src/arm/bitmask.go @@ -4,9 +4,9 @@ import "math/bits" // encodeLogicalImmediate encodes a bitmask immediate. // The algorithm used here was made by Dougall Johnson. -func encodeLogicalImmediate(val uint) (int, bool) { +func encodeLogicalImmediate(val uint) (N int, immr int, imms int, encodable bool) { if val == 0 || ^val == 0 { - return 0, false + return 0, 0, 0, false } rotation := bits.TrailingZeros(clearTrailingOnes(val)) @@ -16,15 +16,15 @@ func encodeLogicalImmediate(val uint) (int, bool) { ones := bits.TrailingZeros(^normalized) size := zeroes + ones - immr := -rotation & (size - 1) - imms := -(size << 1) | (ones - 1) - N := (size >> 6) + immr = -rotation & (size - 1) + imms = -(size << 1) | (ones - 1) + N = (size >> 6) if bits.RotateLeft(val, -(size&63)) != val { - return 0, false + return 0, 0, 0, false } - return N<<12 | immr<<6 | (imms & 0x3f), true + return N, immr, (imms & 0x3f), true } // clearTrailingOnes clears trailing one bits. diff --git a/src/arm/encode.go b/src/arm/encode.go index 3e9aa4b..0b9d45c 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -39,8 +39,8 @@ func reg2Imm(d cpu.Register, n cpu.Register, imm12 int) uint32 { } // reg2BitmaskImm encodes an instruction with 2 registers and a bitmask immediate. -func reg2BitmaskImm(d cpu.Register, n cpu.Register, imm13 int) uint32 { - return uint32(imm13)<<10 | uint32(n)<<5 | uint32(d) +func reg2BitmaskImm(d cpu.Register, n cpu.Register, N int, immr int, imms int) uint32 { + return uint32(N)<<22 | uint32(immr)<<16 | uint32(imms)<<10 | uint32(n)<<5 | uint32(d) } // reg3 encodes an instruction with 3 registers. diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 972d1e3..6f0c122 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -256,6 +256,24 @@ func (c *compiler) compileARM(x asm.Instruction) { } } + case asm.SHIFTL: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] + c.append(arm.ShiftLeftNumber(operands.Register, operands.Register, operands.Number&0b111111)) + case asm.TypeRegisterRegister: + panic("not implemented") + } + + case asm.SHIFTRS: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] + c.append(arm.ShiftRightSignedNumber(operands.Register, operands.Register, operands.Number&0b111111)) + case asm.TypeRegisterRegister: + panic("not implemented") + } + case asm.RETURN: c.append(arm.LoadPair(arm.FP, arm.LR, arm.SP, 16)) c.append(arm.Return()) diff --git a/src/asmc/compileX86.go b/src/asmc/compileX86.go index 122873d..e54435a 100644 --- a/src/asmc/compileX86.go +++ b/src/asmc/compileX86.go @@ -155,6 +155,8 @@ func (c *compiler) compileX86(x asm.Instruction) { case asm.TypeRegisterNumber: operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.ShiftLeftNumber(c.code, operands.Register, byte(operands.Number)&0b111111) + case asm.TypeRegisterRegister: + panic("not implemented") } case asm.SHIFTRS: @@ -162,6 +164,8 @@ func (c *compiler) compileX86(x asm.Instruction) { case asm.TypeRegisterNumber: operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.ShiftRightSignedNumber(c.code, operands.Register, byte(operands.Number)&0b111111) + case asm.TypeRegisterRegister: + panic("not implemented") } case asm.STORE: From b8f05c8994ee663b1e3cabd236abf7e7010aabe7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Mar 2025 19:45:14 +0100 Subject: [PATCH 0952/1012] Implemented bit shifting on arm64 --- src/arm/And.go | 4 ++-- src/arm/Or.go | 4 ++-- src/arm/Shift.go | 15 ++++++++++++ src/arm/Shift_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++ src/arm/Xor.go | 4 ++-- src/arm/arm_test.go | 17 +++++++++++++- src/arm/bitmask.go | 14 ++++++------ src/arm/encode.go | 4 ++-- src/asmc/compileARM.go | 18 +++++++++++++++ src/asmc/compileX86.go | 4 ++++ 10 files changed, 120 insertions(+), 16 deletions(-) create mode 100644 src/arm/Shift.go create mode 100644 src/arm/Shift_test.go diff --git a/src/arm/And.go b/src/arm/And.go index 9c23ce0..8850855 100644 --- a/src/arm/And.go +++ b/src/arm/And.go @@ -6,8 +6,8 @@ import ( // AndRegisterNumber performs a bitwise AND using a register and a number. func AndRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) { - imm13, encodable := encodeLogicalImmediate(uint(number)) - return 0b100100100<<23 | reg2BitmaskImm(destination, source, imm13), encodable + n, immr, imms, encodable := encodeLogicalImmediate(uint(number)) + return 0b100100100<<23 | reg2BitmaskImm(destination, source, n, immr, imms), encodable } // AndRegisterRegister performs a bitwise AND using two registers. diff --git a/src/arm/Or.go b/src/arm/Or.go index 610328b..1d137a8 100644 --- a/src/arm/Or.go +++ b/src/arm/Or.go @@ -4,8 +4,8 @@ import "git.urbach.dev/cli/q/src/cpu" // OrRegisterNumber performs a bitwise OR using a register and a number. func OrRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) { - imm13, encodable := encodeLogicalImmediate(uint(number)) - return 0b101100100<<23 | reg2BitmaskImm(destination, source, imm13), encodable + n, immr, imms, encodable := encodeLogicalImmediate(uint(number)) + return 0b101100100<<23 | reg2BitmaskImm(destination, source, n, immr, imms), encodable } // OrRegisterRegister performs a bitwise OR using two registers. diff --git a/src/arm/Shift.go b/src/arm/Shift.go new file mode 100644 index 0000000..99ad4d2 --- /dev/null +++ b/src/arm/Shift.go @@ -0,0 +1,15 @@ +package arm + +import ( + "git.urbach.dev/cli/q/src/cpu" +) + +// ShiftLeftNumber shifts the register value a specified amount of bits to the left. +func ShiftLeftNumber(destination cpu.Register, source cpu.Register, bits int) uint32 { + return 0b110100110<<23 | reg2BitmaskImm(destination, source, 1, 64-bits, (^bits)&mask6) +} + +// ShiftRightSignedNumber shifts the signed register value a specified amount of bits to the right. +func ShiftRightSignedNumber(destination cpu.Register, source cpu.Register, bits int) uint32 { + return 0b100100110<<23 | reg2BitmaskImm(destination, source, 1, bits&mask6, 0b111111) +} diff --git a/src/arm/Shift_test.go b/src/arm/Shift_test.go new file mode 100644 index 0000000..8aa96d2 --- /dev/null +++ b/src/arm/Shift_test.go @@ -0,0 +1,52 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestShiftLeftNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Bits int + Code uint32 + }{ + {arm.X0, arm.X0, 0, 0xD340FC00}, + {arm.X0, arm.X0, 1, 0xD37FF800}, + {arm.X0, arm.X0, 8, 0xD378DC00}, + {arm.X0, arm.X0, 16, 0xD370BC00}, + {arm.X0, arm.X0, 63, 0xD3410000}, + } + + for _, pattern := range usagePatterns { + t.Logf("%b", pattern.Code) + t.Logf("lsl %s, %s, %x", pattern.Destination, pattern.Source, pattern.Bits) + code := arm.ShiftLeftNumber(pattern.Destination, pattern.Source, pattern.Bits) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestShiftRightSignedNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Bits int + Code uint32 + }{ + {arm.X0, arm.X0, 0, 0x9340FC00}, + {arm.X0, arm.X0, 1, 0x9341FC00}, + {arm.X0, arm.X0, 8, 0x9348FC00}, + {arm.X0, arm.X0, 16, 0x9350FC00}, + {arm.X0, arm.X0, 63, 0x937FFC00}, + } + + for _, pattern := range usagePatterns { + t.Logf("asr %s, %s, %x", pattern.Destination, pattern.Source, pattern.Bits) + code := arm.ShiftRightSignedNumber(pattern.Destination, pattern.Source, pattern.Bits) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Xor.go b/src/arm/Xor.go index 3194543..3580136 100644 --- a/src/arm/Xor.go +++ b/src/arm/Xor.go @@ -4,8 +4,8 @@ import "git.urbach.dev/cli/q/src/cpu" // XorRegisterNumber performs a bitwise XOR using a register and a number. func XorRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) { - imm13, encodable := encodeLogicalImmediate(uint(number)) - return 0b110100100<<23 | reg2BitmaskImm(destination, source, imm13), encodable + n, immr, imms, encodable := encodeLogicalImmediate(uint(number)) + return 0b110100100<<23 | reg2BitmaskImm(destination, source, n, immr, imms), encodable } // XorRegisterRegister performs a bitwise XOR using two registers. diff --git a/src/arm/arm_test.go b/src/arm/arm_test.go index 43ef207..b81e610 100644 --- a/src/arm/arm_test.go +++ b/src/arm/arm_test.go @@ -7,9 +7,24 @@ import ( "git.urbach.dev/go/assert" ) -func TestARM(t *testing.T) { +func TestGeneral(t *testing.T) { assert.DeepEqual(t, arm.Call(0), 0x94000000) assert.DeepEqual(t, arm.Nop(), 0xD503201F) assert.DeepEqual(t, arm.Return(), 0xD65F03C0) assert.DeepEqual(t, arm.Syscall(), 0xD4000001) } + +func TestNotEncodable(t *testing.T) { + _, encodable := arm.AndRegisterNumber(arm.X0, arm.X0, 0) + assert.False(t, encodable) + _, encodable = arm.OrRegisterNumber(arm.X0, arm.X0, 0) + assert.False(t, encodable) + _, encodable = arm.XorRegisterNumber(arm.X0, arm.X0, 0) + assert.False(t, encodable) + _, encodable = arm.AndRegisterNumber(arm.X0, arm.X0, -1) + assert.False(t, encodable) + _, encodable = arm.OrRegisterNumber(arm.X0, arm.X0, -1) + assert.False(t, encodable) + _, encodable = arm.XorRegisterNumber(arm.X0, arm.X0, -1) + assert.False(t, encodable) +} diff --git a/src/arm/bitmask.go b/src/arm/bitmask.go index 76668a2..19a65f8 100644 --- a/src/arm/bitmask.go +++ b/src/arm/bitmask.go @@ -4,9 +4,9 @@ import "math/bits" // encodeLogicalImmediate encodes a bitmask immediate. // The algorithm used here was made by Dougall Johnson. -func encodeLogicalImmediate(val uint) (int, bool) { +func encodeLogicalImmediate(val uint) (N int, immr int, imms int, encodable bool) { if val == 0 || ^val == 0 { - return 0, false + return 0, 0, 0, false } rotation := bits.TrailingZeros(clearTrailingOnes(val)) @@ -16,15 +16,15 @@ func encodeLogicalImmediate(val uint) (int, bool) { ones := bits.TrailingZeros(^normalized) size := zeroes + ones - immr := -rotation & (size - 1) - imms := -(size << 1) | (ones - 1) - N := (size >> 6) + immr = -rotation & (size - 1) + imms = -(size << 1) | (ones - 1) + N = (size >> 6) if bits.RotateLeft(val, -(size&63)) != val { - return 0, false + return 0, 0, 0, false } - return N<<12 | immr<<6 | (imms & 0x3f), true + return N, immr, (imms & 0x3f), true } // clearTrailingOnes clears trailing one bits. diff --git a/src/arm/encode.go b/src/arm/encode.go index 3e9aa4b..0b9d45c 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -39,8 +39,8 @@ func reg2Imm(d cpu.Register, n cpu.Register, imm12 int) uint32 { } // reg2BitmaskImm encodes an instruction with 2 registers and a bitmask immediate. -func reg2BitmaskImm(d cpu.Register, n cpu.Register, imm13 int) uint32 { - return uint32(imm13)<<10 | uint32(n)<<5 | uint32(d) +func reg2BitmaskImm(d cpu.Register, n cpu.Register, N int, immr int, imms int) uint32 { + return uint32(N)<<22 | uint32(immr)<<16 | uint32(imms)<<10 | uint32(n)<<5 | uint32(d) } // reg3 encodes an instruction with 3 registers. diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 972d1e3..6f0c122 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -256,6 +256,24 @@ func (c *compiler) compileARM(x asm.Instruction) { } } + case asm.SHIFTL: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] + c.append(arm.ShiftLeftNumber(operands.Register, operands.Register, operands.Number&0b111111)) + case asm.TypeRegisterRegister: + panic("not implemented") + } + + case asm.SHIFTRS: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] + c.append(arm.ShiftRightSignedNumber(operands.Register, operands.Register, operands.Number&0b111111)) + case asm.TypeRegisterRegister: + panic("not implemented") + } + case asm.RETURN: c.append(arm.LoadPair(arm.FP, arm.LR, arm.SP, 16)) c.append(arm.Return()) diff --git a/src/asmc/compileX86.go b/src/asmc/compileX86.go index 122873d..e54435a 100644 --- a/src/asmc/compileX86.go +++ b/src/asmc/compileX86.go @@ -155,6 +155,8 @@ func (c *compiler) compileX86(x asm.Instruction) { case asm.TypeRegisterNumber: operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.ShiftLeftNumber(c.code, operands.Register, byte(operands.Number)&0b111111) + case asm.TypeRegisterRegister: + panic("not implemented") } case asm.SHIFTRS: @@ -162,6 +164,8 @@ func (c *compiler) compileX86(x asm.Instruction) { case asm.TypeRegisterNumber: operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.ShiftRightSignedNumber(c.code, operands.Register, byte(operands.Number)&0b111111) + case asm.TypeRegisterRegister: + panic("not implemented") } case asm.STORE: From d5118148c9c0b922cce8bd79ce9055bf21026392 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Mar 2025 20:34:47 +0100 Subject: [PATCH 0953/1012] Fixed move with negative numbers on arm64 --- src/arm/Move.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/arm/Move.go b/src/arm/Move.go index d5851ec..e1bfcb3 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -22,16 +22,17 @@ func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byt func MoveRegisterNumberMI(code []byte, destination cpu.Register, number int) []byte { movz := MoveZero(destination, 0, uint16(number)) code = binary.LittleEndian.AppendUint32(code, movz) + num := uint64(number) halfword := 1 for { - number >>= 16 + num >>= 16 - if number == 0 { + if num == 0 { return code } - movk := MoveKeep(destination, halfword, uint16(number)) + movk := MoveKeep(destination, halfword, uint16(num)) code = binary.LittleEndian.AppendUint32(code, movk) halfword++ } From bcb04a4cece89a7d697f5f1cfb3e6f930f363fdf Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Mar 2025 20:34:47 +0100 Subject: [PATCH 0954/1012] Fixed move with negative numbers on arm64 --- src/arm/Move.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/arm/Move.go b/src/arm/Move.go index d5851ec..e1bfcb3 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -22,16 +22,17 @@ func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byt func MoveRegisterNumberMI(code []byte, destination cpu.Register, number int) []byte { movz := MoveZero(destination, 0, uint16(number)) code = binary.LittleEndian.AppendUint32(code, movz) + num := uint64(number) halfword := 1 for { - number >>= 16 + num >>= 16 - if number == 0 { + if num == 0 { return code } - movk := MoveKeep(destination, halfword, uint16(number)) + movk := MoveKeep(destination, halfword, uint16(num)) code = binary.LittleEndian.AppendUint32(code, movk) halfword++ } From 2a8d4fcc6d42d2f023f4940ecab6e32ffe157ee3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Mar 2025 13:36:42 +0100 Subject: [PATCH 0955/1012] Added fallback for numbers that can't be encoded on arm64 --- src/arm/Add.go | 10 +-- src/arm/Add_test.go | 3 +- src/arm/Compare.go | 2 +- src/arm/Compare_test.go | 3 +- src/arm/Move.go | 3 +- src/arm/Sub.go | 10 +-- src/arm/Sub_test.go | 3 +- src/asmc/{compileARM.go => ARM.go} | 126 +++++++++++++++++------------ src/asmc/Finalize.go | 4 +- src/asmc/{compileX86.go => X86.go} | 46 +++++------ src/asmc/bench_test.go | 18 +++++ 11 files changed, 138 insertions(+), 90 deletions(-) rename src/asmc/{compileARM.go => ARM.go} (88%) rename src/asmc/{compileX86.go => X86.go} (99%) create mode 100644 src/asmc/bench_test.go diff --git a/src/arm/Add.go b/src/arm/Add.go index ba641a0..6881689 100644 --- a/src/arm/Add.go +++ b/src/arm/Add.go @@ -3,7 +3,7 @@ package arm import "git.urbach.dev/cli/q/src/cpu" // AddRegisterNumber adds a number to a register. -func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { +func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) (code uint32, encodable bool) { return addRegisterNumber(destination, source, number, 0) } @@ -13,23 +13,23 @@ func AddRegisterRegister(destination cpu.Register, source cpu.Register, operand } // addRegisterNumber adds the register and optionally updates the condition flags based on the result. -func addRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { +func addRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) (code uint32, encodable bool) { shift := uint32(0) if number > mask12 { if number&mask12 != 0 { - panic("number can't be encoded") + return 0, false } shift = 1 number >>= 12 if number > mask12 { - panic("number can't be encoded") + return 0, false } } - return flags<<29 | 0b100100010<<23 | shift<<22 | reg2Imm(destination, source, number) + return flags<<29 | 0b100100010<<23 | shift<<22 | reg2Imm(destination, source, number), true } // addRegisterRegister adds the registers and optionally updates the condition flags based on the result. diff --git a/src/arm/Add_test.go b/src/arm/Add_test.go index 3c75ecc..d00d9f6 100644 --- a/src/arm/Add_test.go +++ b/src/arm/Add_test.go @@ -21,7 +21,8 @@ func TestAddRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("add %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) - code := arm.AddRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + code, encodable := arm.AddRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.True(t, encodable) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/arm/Compare.go b/src/arm/Compare.go index eb5a97e..5d4cf78 100644 --- a/src/arm/Compare.go +++ b/src/arm/Compare.go @@ -3,7 +3,7 @@ package arm import "git.urbach.dev/cli/q/src/cpu" // CompareRegisterNumber is an alias for a subtraction that updates the conditional flags and discards the result. -func CompareRegisterNumber(register cpu.Register, number int) uint32 { +func CompareRegisterNumber(register cpu.Register, number int) (code uint32, encodable bool) { if number < 0 { return addRegisterNumber(ZR, register, -number, 1) } diff --git a/src/arm/Compare_test.go b/src/arm/Compare_test.go index 4580aea..16cecdd 100644 --- a/src/arm/Compare_test.go +++ b/src/arm/Compare_test.go @@ -22,7 +22,8 @@ func TestCompareRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("cmp %s, %d", pattern.Source, pattern.Number) - code := arm.CompareRegisterNumber(pattern.Source, pattern.Number) + code, encodable := arm.CompareRegisterNumber(pattern.Source, pattern.Number) + assert.True(t, encodable) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/arm/Move.go b/src/arm/Move.go index e1bfcb3..3c52b9c 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -72,7 +72,8 @@ func MoveRegisterNumberSI(destination cpu.Register, number int) (uint32, bool) { // MoveRegisterRegister copies a register to another register. func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 { if source == SP || destination == SP { - return AddRegisterNumber(destination, source, 0) + code, _ := AddRegisterNumber(destination, source, 0) + return code } return OrRegisterRegister(destination, ZR, source) diff --git a/src/arm/Sub.go b/src/arm/Sub.go index 37d0a9e..39982a8 100644 --- a/src/arm/Sub.go +++ b/src/arm/Sub.go @@ -3,7 +3,7 @@ package arm import "git.urbach.dev/cli/q/src/cpu" // SubRegisterNumber subtracts a number from the given register. -func SubRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { +func SubRegisterNumber(destination cpu.Register, source cpu.Register, number int) (code uint32, encodable bool) { return subRegisterNumber(destination, source, number, 0) } @@ -13,23 +13,23 @@ func SubRegisterRegister(destination cpu.Register, source cpu.Register, operand } // subRegisterNumber subtracts the register and optionally updates the condition flags based on the result. -func subRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { +func subRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) (code uint32, encodable bool) { shift := uint32(0) if number > mask12 { if number&mask12 != 0 { - panic("number can't be encoded") + return 0, false } shift = 1 number >>= 12 if number > mask12 { - panic("number can't be encoded") + return 0, false } } - return flags<<29 | 0b110100010<<23 | shift<<22 | reg2Imm(destination, source, number) + return flags<<29 | 0b110100010<<23 | shift<<22 | reg2Imm(destination, source, number), true } // subRegisterRegister subtracts the registers and optionally updates the condition flags based on the result. diff --git a/src/arm/Sub_test.go b/src/arm/Sub_test.go index e508c75..2256704 100644 --- a/src/arm/Sub_test.go +++ b/src/arm/Sub_test.go @@ -22,7 +22,8 @@ func TestSubRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("sub %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) - code := arm.SubRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + code, encodable := arm.SubRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.True(t, encodable) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/asmc/compileARM.go b/src/asmc/ARM.go similarity index 88% rename from src/asmc/compileARM.go rename to src/asmc/ARM.go index 6f0c122..c6439b3 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/ARM.go @@ -8,8 +8,45 @@ import ( "git.urbach.dev/cli/q/src/asm" ) -func (c *compiler) compileARM(x asm.Instruction) { +func (c *compiler) ARM(x asm.Instruction) { switch x.Mnemonic { + case asm.MOVE: + switch x.Type { + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.MoveRegisterRegister(operands.Destination, operands.Source)) + + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] + c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) + + case asm.TypeRegisterLabel: + operands := c.assembler.Param.RegisterLabel[x.Index] + position := Address(len(c.code)) + c.append(arm.LoadAddress(operands.Register, 0)) + + if operands.Label.Type == asm.DataLabel { + c.dataPointers = append(c.dataPointers, &pointer{ + Position: position, + OpSize: 0, + Size: 4, + Resolve: func() Address { + destination, exists := c.dataLabels[operands.Label.Name] + + if !exists { + panic("unknown label") + } + + destination += c.dataStart - c.codeStart + distance := destination - position + 8 + return arm.LoadAddress(operands.Register, int(distance)) + }, + }) + } else { + panic("not implemented") + } + } + case asm.CALL: switch x.Type { case asm.TypeLabel: @@ -84,11 +121,19 @@ func (c *compiler) compileARM(x asm.Instruction) { } } + case asm.RETURN: + c.append(arm.LoadPair(arm.FP, arm.LR, arm.SP, 16)) + c.append(arm.Return()) + + case asm.SYSCALL: + c.append(arm.Syscall()) + case asm.PUSH: switch x.Type { case asm.TypeRegister: operand := c.assembler.Param.Register[x.Index] - c.append(arm.SubRegisterNumber(arm.SP, arm.SP, 16)) + code, _ := arm.SubRegisterNumber(arm.SP, arm.SP, 16) + c.append(code) c.append(arm.StoreRegister(operand.Register, arm.SP, 0, 8)) } @@ -97,7 +142,8 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.TypeRegister: operand := c.assembler.Param.Register[x.Index] c.append(arm.LoadRegister(operand.Register, arm.SP, 0, 8)) - c.append(arm.AddRegisterNumber(arm.SP, arm.SP, 16)) + code, _ := arm.AddRegisterNumber(arm.SP, arm.SP, 16) + c.append(code) } case asm.AND: @@ -158,7 +204,15 @@ func (c *compiler) compileARM(x asm.Instruction) { switch x.Type { case asm.TypeRegisterNumber: operand := c.assembler.Param.RegisterNumber[x.Index] - c.append(arm.AddRegisterNumber(operand.Register, operand.Register, operand.Number)) + code, encodable := arm.AddRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + tmp := arm.X28 + c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number) + c.append(arm.AddRegisterRegister(operand.Register, operand.Register, tmp)) + } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] c.append(arm.AddRegisterRegister(operand.Destination, operand.Destination, operand.Source)) @@ -168,7 +222,15 @@ func (c *compiler) compileARM(x asm.Instruction) { switch x.Type { case asm.TypeRegisterNumber: operand := c.assembler.Param.RegisterNumber[x.Index] - c.append(arm.SubRegisterNumber(operand.Register, operand.Register, operand.Number)) + code, encodable := arm.SubRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + tmp := arm.X28 + c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number) + c.append(arm.SubRegisterRegister(operand.Register, operand.Register, tmp)) + } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] c.append(arm.SubRegisterRegister(operand.Destination, operand.Destination, operand.Source)) @@ -178,7 +240,15 @@ func (c *compiler) compileARM(x asm.Instruction) { switch x.Type { case asm.TypeRegisterNumber: operand := c.assembler.Param.RegisterNumber[x.Index] - c.append(arm.CompareRegisterNumber(operand.Register, operand.Number)) + code, encodable := arm.CompareRegisterNumber(operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + tmp := arm.X28 + c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number) + c.append(arm.CompareRegisterRegister(operand.Register, tmp)) + } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] c.append(arm.CompareRegisterRegister(operand.Destination, operand.Source)) @@ -219,43 +289,6 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: c.jumpARM(x) - case asm.MOVE: - switch x.Type { - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.MoveRegisterRegister(operands.Destination, operands.Source)) - - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) - - case asm.TypeRegisterLabel: - operands := c.assembler.Param.RegisterLabel[x.Index] - position := Address(len(c.code)) - c.append(arm.LoadAddress(operands.Register, 0)) - - if operands.Label.Type == asm.DataLabel { - c.dataPointers = append(c.dataPointers, &pointer{ - Position: position, - OpSize: 0, - Size: 4, - Resolve: func() Address { - destination, exists := c.dataLabels[operands.Label.Name] - - if !exists { - panic("unknown label") - } - - destination += c.dataStart - c.codeStart - distance := destination - position + 8 - return arm.LoadAddress(operands.Register, int(distance)) - }, - }) - } else { - panic("not implemented") - } - } - case asm.SHIFTL: switch x.Type { case asm.TypeRegisterNumber: @@ -274,13 +307,6 @@ func (c *compiler) compileARM(x asm.Instruction) { panic("not implemented") } - case asm.RETURN: - c.append(arm.LoadPair(arm.FP, arm.LR, arm.SP, 16)) - c.append(arm.Return()) - - case asm.SYSCALL: - c.append(arm.Syscall()) - default: panic("unknown mnemonic: " + x.Mnemonic.String()) } diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index e2b935c..ec9e678 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -28,12 +28,12 @@ func Finalize(a *asm.Assembler, dlls dll.List) ([]byte, []byte) { switch config.TargetArch { case config.ARM: for _, x := range a.Instructions { - c.compileARM(x) + c.ARM(x) } case config.X86: for _, x := range a.Instructions { - c.compileX86(x) + c.X86(x) } } diff --git a/src/asmc/compileX86.go b/src/asmc/X86.go similarity index 99% rename from src/asmc/compileX86.go rename to src/asmc/X86.go index e54435a..448ec41 100644 --- a/src/asmc/compileX86.go +++ b/src/asmc/X86.go @@ -5,8 +5,30 @@ import ( "git.urbach.dev/cli/q/src/x86" ) -func (c *compiler) compileX86(x asm.Instruction) { +func (c *compiler) X86(x asm.Instruction) { switch x.Mnemonic { + case asm.MOVE: + c.move(x) + + case asm.CALL: + c.call(x) + + case asm.LABEL: + label := c.assembler.Param.Label[x.Index] + c.codeLabels[label.Name] = Address(len(c.code)) + + case asm.LOAD: + c.load(x) + + case asm.STORE: + c.store(x) + + case asm.RETURN: + c.code = x86.Return(c.code) + + case asm.SYSCALL: + c.code = x86.Syscall(c.code) + case asm.ADD: switch x.Type { case asm.TypeRegisterNumber: @@ -81,9 +103,6 @@ func (c *compiler) compileX86(x asm.Instruction) { } } - case asm.CALL: - c.call(x) - case asm.COMMENT: return @@ -103,16 +122,6 @@ func (c *compiler) compileX86(x asm.Instruction) { case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: c.jumpX86(x) - case asm.LABEL: - label := c.assembler.Param.Label[x.Index] - c.codeLabels[label.Name] = Address(len(c.code)) - - case asm.LOAD: - c.load(x) - - case asm.MOVE: - c.move(x) - case asm.NEGATE: switch x.Type { case asm.TypeRegister: @@ -147,9 +156,6 @@ func (c *compiler) compileX86(x asm.Instruction) { c.code = x86.PushRegister(c.code, operands.Register) } - case asm.RETURN: - c.code = x86.Return(c.code) - case asm.SHIFTL: switch x.Type { case asm.TypeRegisterNumber: @@ -168,12 +174,6 @@ func (c *compiler) compileX86(x asm.Instruction) { panic("not implemented") } - case asm.STORE: - c.store(x) - - case asm.SYSCALL: - c.code = x86.Syscall(c.code) - case asm.XOR: switch x.Type { case asm.TypeRegisterNumber: diff --git a/src/asmc/bench_test.go b/src/asmc/bench_test.go new file mode 100644 index 0000000..179c6c1 --- /dev/null +++ b/src/asmc/bench_test.go @@ -0,0 +1,18 @@ +package asmc_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/asmc" + "git.urbach.dev/cli/q/src/cpu" +) + +func BenchmarkFinalize(b *testing.B) { + a := asm.Assembler{} + a.RegisterNumber(asm.MOVE, cpu.Register(0), 0) + + for b.Loop() { + asmc.Finalize(&a, nil) + } +} From 76356b2d3855df41b920b4049deb8e1ba2cd23c5 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Mar 2025 13:36:42 +0100 Subject: [PATCH 0956/1012] Added fallback for numbers that can't be encoded on arm64 --- src/arm/Add.go | 10 +-- src/arm/Add_test.go | 3 +- src/arm/Compare.go | 2 +- src/arm/Compare_test.go | 3 +- src/arm/Move.go | 3 +- src/arm/Sub.go | 10 +-- src/arm/Sub_test.go | 3 +- src/asmc/{compileARM.go => ARM.go} | 126 +++++++++++++++++------------ src/asmc/Finalize.go | 4 +- src/asmc/{compileX86.go => X86.go} | 46 +++++------ src/asmc/bench_test.go | 18 +++++ 11 files changed, 138 insertions(+), 90 deletions(-) rename src/asmc/{compileARM.go => ARM.go} (88%) rename src/asmc/{compileX86.go => X86.go} (99%) create mode 100644 src/asmc/bench_test.go diff --git a/src/arm/Add.go b/src/arm/Add.go index ba641a0..6881689 100644 --- a/src/arm/Add.go +++ b/src/arm/Add.go @@ -3,7 +3,7 @@ package arm import "git.urbach.dev/cli/q/src/cpu" // AddRegisterNumber adds a number to a register. -func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { +func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) (code uint32, encodable bool) { return addRegisterNumber(destination, source, number, 0) } @@ -13,23 +13,23 @@ func AddRegisterRegister(destination cpu.Register, source cpu.Register, operand } // addRegisterNumber adds the register and optionally updates the condition flags based on the result. -func addRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { +func addRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) (code uint32, encodable bool) { shift := uint32(0) if number > mask12 { if number&mask12 != 0 { - panic("number can't be encoded") + return 0, false } shift = 1 number >>= 12 if number > mask12 { - panic("number can't be encoded") + return 0, false } } - return flags<<29 | 0b100100010<<23 | shift<<22 | reg2Imm(destination, source, number) + return flags<<29 | 0b100100010<<23 | shift<<22 | reg2Imm(destination, source, number), true } // addRegisterRegister adds the registers and optionally updates the condition flags based on the result. diff --git a/src/arm/Add_test.go b/src/arm/Add_test.go index 3c75ecc..d00d9f6 100644 --- a/src/arm/Add_test.go +++ b/src/arm/Add_test.go @@ -21,7 +21,8 @@ func TestAddRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("add %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) - code := arm.AddRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + code, encodable := arm.AddRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.True(t, encodable) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/arm/Compare.go b/src/arm/Compare.go index eb5a97e..5d4cf78 100644 --- a/src/arm/Compare.go +++ b/src/arm/Compare.go @@ -3,7 +3,7 @@ package arm import "git.urbach.dev/cli/q/src/cpu" // CompareRegisterNumber is an alias for a subtraction that updates the conditional flags and discards the result. -func CompareRegisterNumber(register cpu.Register, number int) uint32 { +func CompareRegisterNumber(register cpu.Register, number int) (code uint32, encodable bool) { if number < 0 { return addRegisterNumber(ZR, register, -number, 1) } diff --git a/src/arm/Compare_test.go b/src/arm/Compare_test.go index 4580aea..16cecdd 100644 --- a/src/arm/Compare_test.go +++ b/src/arm/Compare_test.go @@ -22,7 +22,8 @@ func TestCompareRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("cmp %s, %d", pattern.Source, pattern.Number) - code := arm.CompareRegisterNumber(pattern.Source, pattern.Number) + code, encodable := arm.CompareRegisterNumber(pattern.Source, pattern.Number) + assert.True(t, encodable) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/arm/Move.go b/src/arm/Move.go index e1bfcb3..3c52b9c 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -72,7 +72,8 @@ func MoveRegisterNumberSI(destination cpu.Register, number int) (uint32, bool) { // MoveRegisterRegister copies a register to another register. func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 { if source == SP || destination == SP { - return AddRegisterNumber(destination, source, 0) + code, _ := AddRegisterNumber(destination, source, 0) + return code } return OrRegisterRegister(destination, ZR, source) diff --git a/src/arm/Sub.go b/src/arm/Sub.go index 37d0a9e..39982a8 100644 --- a/src/arm/Sub.go +++ b/src/arm/Sub.go @@ -3,7 +3,7 @@ package arm import "git.urbach.dev/cli/q/src/cpu" // SubRegisterNumber subtracts a number from the given register. -func SubRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { +func SubRegisterNumber(destination cpu.Register, source cpu.Register, number int) (code uint32, encodable bool) { return subRegisterNumber(destination, source, number, 0) } @@ -13,23 +13,23 @@ func SubRegisterRegister(destination cpu.Register, source cpu.Register, operand } // subRegisterNumber subtracts the register and optionally updates the condition flags based on the result. -func subRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { +func subRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) (code uint32, encodable bool) { shift := uint32(0) if number > mask12 { if number&mask12 != 0 { - panic("number can't be encoded") + return 0, false } shift = 1 number >>= 12 if number > mask12 { - panic("number can't be encoded") + return 0, false } } - return flags<<29 | 0b110100010<<23 | shift<<22 | reg2Imm(destination, source, number) + return flags<<29 | 0b110100010<<23 | shift<<22 | reg2Imm(destination, source, number), true } // subRegisterRegister subtracts the registers and optionally updates the condition flags based on the result. diff --git a/src/arm/Sub_test.go b/src/arm/Sub_test.go index e508c75..2256704 100644 --- a/src/arm/Sub_test.go +++ b/src/arm/Sub_test.go @@ -22,7 +22,8 @@ func TestSubRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("sub %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) - code := arm.SubRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + code, encodable := arm.SubRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.True(t, encodable) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/asmc/compileARM.go b/src/asmc/ARM.go similarity index 88% rename from src/asmc/compileARM.go rename to src/asmc/ARM.go index 6f0c122..c6439b3 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/ARM.go @@ -8,8 +8,45 @@ import ( "git.urbach.dev/cli/q/src/asm" ) -func (c *compiler) compileARM(x asm.Instruction) { +func (c *compiler) ARM(x asm.Instruction) { switch x.Mnemonic { + case asm.MOVE: + switch x.Type { + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.MoveRegisterRegister(operands.Destination, operands.Source)) + + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] + c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) + + case asm.TypeRegisterLabel: + operands := c.assembler.Param.RegisterLabel[x.Index] + position := Address(len(c.code)) + c.append(arm.LoadAddress(operands.Register, 0)) + + if operands.Label.Type == asm.DataLabel { + c.dataPointers = append(c.dataPointers, &pointer{ + Position: position, + OpSize: 0, + Size: 4, + Resolve: func() Address { + destination, exists := c.dataLabels[operands.Label.Name] + + if !exists { + panic("unknown label") + } + + destination += c.dataStart - c.codeStart + distance := destination - position + 8 + return arm.LoadAddress(operands.Register, int(distance)) + }, + }) + } else { + panic("not implemented") + } + } + case asm.CALL: switch x.Type { case asm.TypeLabel: @@ -84,11 +121,19 @@ func (c *compiler) compileARM(x asm.Instruction) { } } + case asm.RETURN: + c.append(arm.LoadPair(arm.FP, arm.LR, arm.SP, 16)) + c.append(arm.Return()) + + case asm.SYSCALL: + c.append(arm.Syscall()) + case asm.PUSH: switch x.Type { case asm.TypeRegister: operand := c.assembler.Param.Register[x.Index] - c.append(arm.SubRegisterNumber(arm.SP, arm.SP, 16)) + code, _ := arm.SubRegisterNumber(arm.SP, arm.SP, 16) + c.append(code) c.append(arm.StoreRegister(operand.Register, arm.SP, 0, 8)) } @@ -97,7 +142,8 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.TypeRegister: operand := c.assembler.Param.Register[x.Index] c.append(arm.LoadRegister(operand.Register, arm.SP, 0, 8)) - c.append(arm.AddRegisterNumber(arm.SP, arm.SP, 16)) + code, _ := arm.AddRegisterNumber(arm.SP, arm.SP, 16) + c.append(code) } case asm.AND: @@ -158,7 +204,15 @@ func (c *compiler) compileARM(x asm.Instruction) { switch x.Type { case asm.TypeRegisterNumber: operand := c.assembler.Param.RegisterNumber[x.Index] - c.append(arm.AddRegisterNumber(operand.Register, operand.Register, operand.Number)) + code, encodable := arm.AddRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + tmp := arm.X28 + c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number) + c.append(arm.AddRegisterRegister(operand.Register, operand.Register, tmp)) + } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] c.append(arm.AddRegisterRegister(operand.Destination, operand.Destination, operand.Source)) @@ -168,7 +222,15 @@ func (c *compiler) compileARM(x asm.Instruction) { switch x.Type { case asm.TypeRegisterNumber: operand := c.assembler.Param.RegisterNumber[x.Index] - c.append(arm.SubRegisterNumber(operand.Register, operand.Register, operand.Number)) + code, encodable := arm.SubRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + tmp := arm.X28 + c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number) + c.append(arm.SubRegisterRegister(operand.Register, operand.Register, tmp)) + } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] c.append(arm.SubRegisterRegister(operand.Destination, operand.Destination, operand.Source)) @@ -178,7 +240,15 @@ func (c *compiler) compileARM(x asm.Instruction) { switch x.Type { case asm.TypeRegisterNumber: operand := c.assembler.Param.RegisterNumber[x.Index] - c.append(arm.CompareRegisterNumber(operand.Register, operand.Number)) + code, encodable := arm.CompareRegisterNumber(operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + tmp := arm.X28 + c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number) + c.append(arm.CompareRegisterRegister(operand.Register, tmp)) + } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] c.append(arm.CompareRegisterRegister(operand.Destination, operand.Source)) @@ -219,43 +289,6 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: c.jumpARM(x) - case asm.MOVE: - switch x.Type { - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.MoveRegisterRegister(operands.Destination, operands.Source)) - - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) - - case asm.TypeRegisterLabel: - operands := c.assembler.Param.RegisterLabel[x.Index] - position := Address(len(c.code)) - c.append(arm.LoadAddress(operands.Register, 0)) - - if operands.Label.Type == asm.DataLabel { - c.dataPointers = append(c.dataPointers, &pointer{ - Position: position, - OpSize: 0, - Size: 4, - Resolve: func() Address { - destination, exists := c.dataLabels[operands.Label.Name] - - if !exists { - panic("unknown label") - } - - destination += c.dataStart - c.codeStart - distance := destination - position + 8 - return arm.LoadAddress(operands.Register, int(distance)) - }, - }) - } else { - panic("not implemented") - } - } - case asm.SHIFTL: switch x.Type { case asm.TypeRegisterNumber: @@ -274,13 +307,6 @@ func (c *compiler) compileARM(x asm.Instruction) { panic("not implemented") } - case asm.RETURN: - c.append(arm.LoadPair(arm.FP, arm.LR, arm.SP, 16)) - c.append(arm.Return()) - - case asm.SYSCALL: - c.append(arm.Syscall()) - default: panic("unknown mnemonic: " + x.Mnemonic.String()) } diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index e2b935c..ec9e678 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -28,12 +28,12 @@ func Finalize(a *asm.Assembler, dlls dll.List) ([]byte, []byte) { switch config.TargetArch { case config.ARM: for _, x := range a.Instructions { - c.compileARM(x) + c.ARM(x) } case config.X86: for _, x := range a.Instructions { - c.compileX86(x) + c.X86(x) } } diff --git a/src/asmc/compileX86.go b/src/asmc/X86.go similarity index 99% rename from src/asmc/compileX86.go rename to src/asmc/X86.go index e54435a..448ec41 100644 --- a/src/asmc/compileX86.go +++ b/src/asmc/X86.go @@ -5,8 +5,30 @@ import ( "git.urbach.dev/cli/q/src/x86" ) -func (c *compiler) compileX86(x asm.Instruction) { +func (c *compiler) X86(x asm.Instruction) { switch x.Mnemonic { + case asm.MOVE: + c.move(x) + + case asm.CALL: + c.call(x) + + case asm.LABEL: + label := c.assembler.Param.Label[x.Index] + c.codeLabels[label.Name] = Address(len(c.code)) + + case asm.LOAD: + c.load(x) + + case asm.STORE: + c.store(x) + + case asm.RETURN: + c.code = x86.Return(c.code) + + case asm.SYSCALL: + c.code = x86.Syscall(c.code) + case asm.ADD: switch x.Type { case asm.TypeRegisterNumber: @@ -81,9 +103,6 @@ func (c *compiler) compileX86(x asm.Instruction) { } } - case asm.CALL: - c.call(x) - case asm.COMMENT: return @@ -103,16 +122,6 @@ func (c *compiler) compileX86(x asm.Instruction) { case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: c.jumpX86(x) - case asm.LABEL: - label := c.assembler.Param.Label[x.Index] - c.codeLabels[label.Name] = Address(len(c.code)) - - case asm.LOAD: - c.load(x) - - case asm.MOVE: - c.move(x) - case asm.NEGATE: switch x.Type { case asm.TypeRegister: @@ -147,9 +156,6 @@ func (c *compiler) compileX86(x asm.Instruction) { c.code = x86.PushRegister(c.code, operands.Register) } - case asm.RETURN: - c.code = x86.Return(c.code) - case asm.SHIFTL: switch x.Type { case asm.TypeRegisterNumber: @@ -168,12 +174,6 @@ func (c *compiler) compileX86(x asm.Instruction) { panic("not implemented") } - case asm.STORE: - c.store(x) - - case asm.SYSCALL: - c.code = x86.Syscall(c.code) - case asm.XOR: switch x.Type { case asm.TypeRegisterNumber: diff --git a/src/asmc/bench_test.go b/src/asmc/bench_test.go new file mode 100644 index 0000000..179c6c1 --- /dev/null +++ b/src/asmc/bench_test.go @@ -0,0 +1,18 @@ +package asmc_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/asmc" + "git.urbach.dev/cli/q/src/cpu" +) + +func BenchmarkFinalize(b *testing.B) { + a := asm.Assembler{} + a.RegisterNumber(asm.MOVE, cpu.Register(0), 0) + + for b.Loop() { + asmc.Finalize(&a, nil) + } +} From f5a6c9afbd874483ed057bdba1b769e7c4f7b223 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Mar 2025 14:35:12 +0100 Subject: [PATCH 0957/1012] Implemented register negation on arm64 --- src/arm/Negate.go | 8 ++++++++ src/arm/Negate_test.go | 26 ++++++++++++++++++++++++++ src/asmc/ARM.go | 7 +++++++ 3 files changed, 41 insertions(+) create mode 100644 src/arm/Negate.go create mode 100644 src/arm/Negate_test.go diff --git a/src/arm/Negate.go b/src/arm/Negate.go new file mode 100644 index 0000000..2e7a33c --- /dev/null +++ b/src/arm/Negate.go @@ -0,0 +1,8 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// NegateRegister negates the value in the source register and writes it to the destination register. +func NegateRegister(destination cpu.Register, source cpu.Register) uint32 { + return 0b11001011<<24 | reg3Imm(destination, ZR, source, 0) +} diff --git a/src/arm/Negate_test.go b/src/arm/Negate_test.go new file mode 100644 index 0000000..65f3809 --- /dev/null +++ b/src/arm/Negate_test.go @@ -0,0 +1,26 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestNegateRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Code uint32 + }{ + {arm.X0, arm.X0, 0xCB0003E0}, + {arm.X1, arm.X1, 0xCB0103E1}, + } + + for _, pattern := range usagePatterns { + t.Logf("neg %s, %s", pattern.Destination, pattern.Source) + code := arm.NegateRegister(pattern.Destination, pattern.Source) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/asmc/ARM.go b/src/asmc/ARM.go index c6439b3..1d98cb1 100644 --- a/src/asmc/ARM.go +++ b/src/asmc/ARM.go @@ -307,6 +307,13 @@ func (c *compiler) ARM(x asm.Instruction) { panic("not implemented") } + case asm.NEGATE: + switch x.Type { + case asm.TypeRegister: + operands := c.assembler.Param.Register[x.Index] + c.append(arm.NegateRegister(operands.Register, operands.Register)) + } + default: panic("unknown mnemonic: " + x.Mnemonic.String()) } From eb27595593a0bd6c3d430593e5da934eb17766cc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Mar 2025 14:35:12 +0100 Subject: [PATCH 0958/1012] Implemented register negation on arm64 --- src/arm/Negate.go | 8 ++++++++ src/arm/Negate_test.go | 26 ++++++++++++++++++++++++++ src/asmc/ARM.go | 7 +++++++ 3 files changed, 41 insertions(+) create mode 100644 src/arm/Negate.go create mode 100644 src/arm/Negate_test.go diff --git a/src/arm/Negate.go b/src/arm/Negate.go new file mode 100644 index 0000000..2e7a33c --- /dev/null +++ b/src/arm/Negate.go @@ -0,0 +1,8 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// NegateRegister negates the value in the source register and writes it to the destination register. +func NegateRegister(destination cpu.Register, source cpu.Register) uint32 { + return 0b11001011<<24 | reg3Imm(destination, ZR, source, 0) +} diff --git a/src/arm/Negate_test.go b/src/arm/Negate_test.go new file mode 100644 index 0000000..65f3809 --- /dev/null +++ b/src/arm/Negate_test.go @@ -0,0 +1,26 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestNegateRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Code uint32 + }{ + {arm.X0, arm.X0, 0xCB0003E0}, + {arm.X1, arm.X1, 0xCB0103E1}, + } + + for _, pattern := range usagePatterns { + t.Logf("neg %s, %s", pattern.Destination, pattern.Source) + code := arm.NegateRegister(pattern.Destination, pattern.Source) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/asmc/ARM.go b/src/asmc/ARM.go index c6439b3..1d98cb1 100644 --- a/src/asmc/ARM.go +++ b/src/asmc/ARM.go @@ -307,6 +307,13 @@ func (c *compiler) ARM(x asm.Instruction) { panic("not implemented") } + case asm.NEGATE: + switch x.Type { + case asm.TypeRegister: + operands := c.assembler.Param.Register[x.Index] + c.append(arm.NegateRegister(operands.Register, operands.Register)) + } + default: panic("unknown mnemonic: " + x.Mnemonic.String()) } From 3ffcfa008439d6210cfb2e726ac1cf936e531053 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 18 Mar 2025 18:24:30 +0100 Subject: [PATCH 0959/1012] Updated documentation --- docs/readme.md | 9 ++++++++- docs/todo.md | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/readme.md b/docs/readme.md index 5873bd8..c79dee4 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -1,6 +1,6 @@ # q -A programming language that quickly compiles to machine code without gigantic dependencies like LLVM. +A programming language that quickly compiles to machine code. ## Goals @@ -45,6 +45,13 @@ q build examples/hello --os mac q build examples/hello --os windows ``` +Or with different architectures: + +```shell +q build examples/hello --arch x86 +q build examples/hello --arch arm +``` + ## Status `q` is under heavy development and not ready for production yet. diff --git a/docs/todo.md b/docs/todo.md index e08b1d2..3e18782 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -49,6 +49,7 @@ - [x] Exclude unused functions - [x] Constant folding +- [ ] SSA form - [ ] Constant propagation - [ ] Function call inlining - [ ] Loop unrolls From 9a8bf8ff64a56526e5619a2d97a9bc70a889f374 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 18 Mar 2025 18:24:30 +0100 Subject: [PATCH 0960/1012] Updated documentation --- docs/readme.md | 9 ++++++++- docs/todo.md | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/readme.md b/docs/readme.md index 5873bd8..c79dee4 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -1,6 +1,6 @@ # q -A programming language that quickly compiles to machine code without gigantic dependencies like LLVM. +A programming language that quickly compiles to machine code. ## Goals @@ -45,6 +45,13 @@ q build examples/hello --os mac q build examples/hello --os windows ``` +Or with different architectures: + +```shell +q build examples/hello --arch x86 +q build examples/hello --arch arm +``` + ## Status `q` is under heavy development and not ready for production yet. diff --git a/docs/todo.md b/docs/todo.md index e08b1d2..3e18782 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -49,6 +49,7 @@ - [x] Exclude unused functions - [x] Constant folding +- [ ] SSA form - [ ] Constant propagation - [ ] Function call inlining - [ ] Loop unrolls From c2e489f9875ba66d289a8f311712d3b6bacf35ba Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 20 Mar 2025 00:26:42 +0100 Subject: [PATCH 0961/1012] Implemented token splitting as a generator --- src/expression/List.go | 7 ++-- src/scanner/scanFunctionSignature.go | 26 ++++++-------- src/token/List.go | 12 +++---- src/token/List_test.go | 54 +++++++++++----------------- 4 files changed, 39 insertions(+), 60 deletions(-) diff --git a/src/expression/List.go b/src/expression/List.go index b05058b..5f6a5a1 100644 --- a/src/expression/List.go +++ b/src/expression/List.go @@ -8,11 +8,10 @@ import ( func NewList(tokens token.List) []*Expression { var list []*Expression - tokens.Split(func(parameter token.List) error { - expression := Parse(parameter) + for param := range tokens.Split { + expression := Parse(param) list = append(list, expression) - return nil - }) + } return list } diff --git a/src/scanner/scanFunctionSignature.go b/src/scanner/scanFunctionSignature.go index d1f11ad..6cd5b17 100644 --- a/src/scanner/scanFunctionSignature.go +++ b/src/scanner/scanFunctionSignature.go @@ -96,30 +96,24 @@ func scanFunctionSignature(file *fs.File, tokens token.List, i int, delimiter to outputTokens := tokens[typeStart:typeEnd] - err := outputTokens.Split(func(tokens token.List) error { - function.Output = append(function.Output, core.NewParameter(tokens)) - return nil - }) - - if err != nil { - return nil, i, err + for param := range outputTokens.Split { + function.Output = append(function.Output, core.NewParameter(param)) } } parameters := tokens[paramsStart:paramsEnd] - err := parameters.Split(func(tokens token.List) error { - if len(tokens) == 0 { - return errors.New(errors.MissingParameter, file, parameters[0].Position) + for param := range parameters.Split { + if len(param) == 0 { + return nil, i, errors.New(errors.MissingParameter, file, parameters[0].Position) } - if len(tokens) == 1 { - return errors.New(errors.MissingType, file, tokens[0].End()) + if len(param) == 1 { + return nil, i, errors.New(errors.MissingType, file, param[0].End()) } - function.Input = append(function.Input, core.NewParameter(tokens)) - return nil - }) + function.Input = append(function.Input, core.NewParameter(param)) + } - return function, i, err + return function, i, nil } diff --git a/src/token/List.go b/src/token/List.go index 218dcec..7fd2824 100644 --- a/src/token/List.go +++ b/src/token/List.go @@ -30,9 +30,9 @@ func (list List) LastIndexKind(kind Kind) int { } // Split calls the callback function on each set of tokens in a comma separated list. -func (list List) Split(call func(List) error) error { +func (list List) Split(yield func(List) bool) { if len(list) == 0 { - return nil + return } start := 0 @@ -52,18 +52,16 @@ func (list List) Split(call func(List) error) error { } parameter := list[start:i] - err := call(parameter) - if err != nil { - return err + if !yield(parameter) { + return } start = i + 1 } } - parameter := list[start:] - return call(parameter) + yield(list[start:]) } // Text returns the concatenated token text. diff --git a/src/token/List_test.go b/src/token/List_test.go index 5ca101e..4aa9a71 100644 --- a/src/token/List_test.go +++ b/src/token/List_test.go @@ -1,7 +1,6 @@ package token_test import ( - "errors" "testing" "git.urbach.dev/cli/q/src/token" @@ -23,35 +22,28 @@ func TestSplit(t *testing.T) { tokens := token.Tokenize(src) parameters := []string{} - err := tokens.Split(func(parameter token.List) error { - parameters = append(parameters, parameter.Text(src)) - return nil - }) + for param := range tokens.Split { + parameters = append(parameters, param.Text(src)) + } - assert.Nil(t, err) assert.DeepEqual(t, parameters, []string{"1+2", "3*4", "5*6", "7+8"}) } +func TestSplitBreak(t *testing.T) { + src := []byte("1,2") + tokens := token.Tokenize(src) + + for range tokens.Split { + break + } +} + func TestSplitEmpty(t *testing.T) { tokens := token.List{} - err := tokens.Split(func(parameter token.List) error { - return errors.New("error") - }) - - assert.Nil(t, err) -} - -func TestSplitError(t *testing.T) { - src := []byte("1,2,3") - tokens := token.Tokenize(src) - - err := tokens.Split(func(parameter token.List) error { - return errors.New("error") - }) - - assert.NotNil(t, err) - assert.Equal(t, err.Error(), "error") + for range tokens.Split { + t.Fail() + } } func TestSplitGroups(t *testing.T) { @@ -59,12 +51,10 @@ func TestSplitGroups(t *testing.T) { tokens := token.Tokenize(src) parameters := []string{} - err := tokens.Split(func(parameter token.List) error { - parameters = append(parameters, parameter.Text(src)) - return nil - }) + for param := range tokens.Split { + parameters = append(parameters, param.Text(src)) + } - assert.Nil(t, err) assert.DeepEqual(t, parameters, []string{"f(1,2)", "g(3,4)"}) } @@ -73,11 +63,9 @@ func TestSplitSingle(t *testing.T) { tokens := token.Tokenize(src) parameters := []string{} - err := tokens.Split(func(parameter token.List) error { - parameters = append(parameters, parameter.Text(src)) - return nil - }) + for param := range tokens.Split { + parameters = append(parameters, param.Text(src)) + } - assert.Nil(t, err) assert.DeepEqual(t, parameters, []string{"123"}) } From f31ea5e825bb830567b5913168b3ae610666ad1b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 20 Mar 2025 00:26:42 +0100 Subject: [PATCH 0962/1012] Implemented token splitting as a generator --- src/expression/List.go | 7 ++-- src/scanner/scanFunctionSignature.go | 26 ++++++-------- src/token/List.go | 12 +++---- src/token/List_test.go | 54 +++++++++++----------------- 4 files changed, 39 insertions(+), 60 deletions(-) diff --git a/src/expression/List.go b/src/expression/List.go index b05058b..5f6a5a1 100644 --- a/src/expression/List.go +++ b/src/expression/List.go @@ -8,11 +8,10 @@ import ( func NewList(tokens token.List) []*Expression { var list []*Expression - tokens.Split(func(parameter token.List) error { - expression := Parse(parameter) + for param := range tokens.Split { + expression := Parse(param) list = append(list, expression) - return nil - }) + } return list } diff --git a/src/scanner/scanFunctionSignature.go b/src/scanner/scanFunctionSignature.go index d1f11ad..6cd5b17 100644 --- a/src/scanner/scanFunctionSignature.go +++ b/src/scanner/scanFunctionSignature.go @@ -96,30 +96,24 @@ func scanFunctionSignature(file *fs.File, tokens token.List, i int, delimiter to outputTokens := tokens[typeStart:typeEnd] - err := outputTokens.Split(func(tokens token.List) error { - function.Output = append(function.Output, core.NewParameter(tokens)) - return nil - }) - - if err != nil { - return nil, i, err + for param := range outputTokens.Split { + function.Output = append(function.Output, core.NewParameter(param)) } } parameters := tokens[paramsStart:paramsEnd] - err := parameters.Split(func(tokens token.List) error { - if len(tokens) == 0 { - return errors.New(errors.MissingParameter, file, parameters[0].Position) + for param := range parameters.Split { + if len(param) == 0 { + return nil, i, errors.New(errors.MissingParameter, file, parameters[0].Position) } - if len(tokens) == 1 { - return errors.New(errors.MissingType, file, tokens[0].End()) + if len(param) == 1 { + return nil, i, errors.New(errors.MissingType, file, param[0].End()) } - function.Input = append(function.Input, core.NewParameter(tokens)) - return nil - }) + function.Input = append(function.Input, core.NewParameter(param)) + } - return function, i, err + return function, i, nil } diff --git a/src/token/List.go b/src/token/List.go index 218dcec..7fd2824 100644 --- a/src/token/List.go +++ b/src/token/List.go @@ -30,9 +30,9 @@ func (list List) LastIndexKind(kind Kind) int { } // Split calls the callback function on each set of tokens in a comma separated list. -func (list List) Split(call func(List) error) error { +func (list List) Split(yield func(List) bool) { if len(list) == 0 { - return nil + return } start := 0 @@ -52,18 +52,16 @@ func (list List) Split(call func(List) error) error { } parameter := list[start:i] - err := call(parameter) - if err != nil { - return err + if !yield(parameter) { + return } start = i + 1 } } - parameter := list[start:] - return call(parameter) + yield(list[start:]) } // Text returns the concatenated token text. diff --git a/src/token/List_test.go b/src/token/List_test.go index 5ca101e..4aa9a71 100644 --- a/src/token/List_test.go +++ b/src/token/List_test.go @@ -1,7 +1,6 @@ package token_test import ( - "errors" "testing" "git.urbach.dev/cli/q/src/token" @@ -23,35 +22,28 @@ func TestSplit(t *testing.T) { tokens := token.Tokenize(src) parameters := []string{} - err := tokens.Split(func(parameter token.List) error { - parameters = append(parameters, parameter.Text(src)) - return nil - }) + for param := range tokens.Split { + parameters = append(parameters, param.Text(src)) + } - assert.Nil(t, err) assert.DeepEqual(t, parameters, []string{"1+2", "3*4", "5*6", "7+8"}) } +func TestSplitBreak(t *testing.T) { + src := []byte("1,2") + tokens := token.Tokenize(src) + + for range tokens.Split { + break + } +} + func TestSplitEmpty(t *testing.T) { tokens := token.List{} - err := tokens.Split(func(parameter token.List) error { - return errors.New("error") - }) - - assert.Nil(t, err) -} - -func TestSplitError(t *testing.T) { - src := []byte("1,2,3") - tokens := token.Tokenize(src) - - err := tokens.Split(func(parameter token.List) error { - return errors.New("error") - }) - - assert.NotNil(t, err) - assert.Equal(t, err.Error(), "error") + for range tokens.Split { + t.Fail() + } } func TestSplitGroups(t *testing.T) { @@ -59,12 +51,10 @@ func TestSplitGroups(t *testing.T) { tokens := token.Tokenize(src) parameters := []string{} - err := tokens.Split(func(parameter token.List) error { - parameters = append(parameters, parameter.Text(src)) - return nil - }) + for param := range tokens.Split { + parameters = append(parameters, param.Text(src)) + } - assert.Nil(t, err) assert.DeepEqual(t, parameters, []string{"f(1,2)", "g(3,4)"}) } @@ -73,11 +63,9 @@ func TestSplitSingle(t *testing.T) { tokens := token.Tokenize(src) parameters := []string{} - err := tokens.Split(func(parameter token.List) error { - parameters = append(parameters, parameter.Text(src)) - return nil - }) + for param := range tokens.Split { + parameters = append(parameters, param.Text(src)) + } - assert.Nil(t, err) assert.DeepEqual(t, parameters, []string{"123"}) } From ac8bd65054d9f983687ca2cf22b167042243a4ae Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 20 Mar 2025 13:53:23 +0100 Subject: [PATCH 0963/1012] Implemented instruction splitting as a generator --- src/ast/Parse.go | 14 ++++--- src/ast/eachInstruction.go | 69 ---------------------------------- src/ast/parseCases.go | 10 ++--- src/token/Instructions.go | 61 ++++++++++++++++++++++++++++++ src/token/Instructions_test.go | 20 ++++++++++ 5 files changed, 93 insertions(+), 81 deletions(-) delete mode 100644 src/ast/eachInstruction.go create mode 100644 src/token/Instructions.go create mode 100644 src/token/Instructions_test.go diff --git a/src/ast/Parse.go b/src/ast/Parse.go index 20634e4..ea7c758 100644 --- a/src/ast/Parse.go +++ b/src/ast/Parse.go @@ -6,18 +6,20 @@ import ( ) // Parse generates an AST from a list of tokens. -func Parse(tokens []token.Token, file *fs.File) (AST, error) { +func Parse(tokens token.List, file *fs.File) (AST, error) { nodes := make(AST, 0, len(tokens)/64) - err := eachInstruction(tokens, func(instruction token.List) error { - node, err := parseInstruction(instruction, file, nodes) + for tokens := range tokens.Instructions { + node, err := parseInstruction(tokens, file, nodes) if node != nil { nodes = append(nodes, node) } - return err - }) + if err != nil { + return nil, err + } + } - return nodes, err + return nodes, nil } diff --git a/src/ast/eachInstruction.go b/src/ast/eachInstruction.go deleted file mode 100644 index 0bed958..0000000 --- a/src/ast/eachInstruction.go +++ /dev/null @@ -1,69 +0,0 @@ -package ast - -import "git.urbach.dev/cli/q/src/token" - -// eachInstruction calls the function on each AST node. -func eachInstruction(tokens token.List, call func(token.List) error) error { - start := 0 - groupLevel := 0 - blockLevel := 0 - - for i, t := range tokens { - switch t.Kind { - case token.NewLine: - if start == i { - start = i + 1 - continue - } - - if groupLevel > 0 || blockLevel > 0 { - continue - } - - err := call(tokens[start:i]) - - if err != nil { - return err - } - - start = i + 1 - - case token.GroupStart: - groupLevel++ - - case token.GroupEnd: - groupLevel-- - - case token.BlockStart: - blockLevel++ - - case token.BlockEnd: - blockLevel-- - - if groupLevel > 0 || blockLevel > 0 { - continue - } - - err := call(tokens[start : i+1]) - - if err != nil { - return err - } - - start = i + 1 - - case token.EOF: - if start < i { - return call(tokens[start:i]) - } - - return nil - } - } - - if start < len(tokens) { - return call(tokens[start:]) - } - - return nil -} diff --git a/src/ast/parseCases.go b/src/ast/parseCases.go index dbb5412..2ea4cf1 100644 --- a/src/ast/parseCases.go +++ b/src/ast/parseCases.go @@ -10,11 +10,11 @@ import ( func parseCases(tokens token.List, file *fs.File) ([]Case, error) { var cases []Case - err := eachInstruction(tokens, func(caseTokens token.List) error { + for caseTokens := range tokens.Instructions { blockStart, _, body, err := block(caseTokens, file) if err != nil { - return err + return nil, err } conditionTokens := caseTokens[:blockStart] @@ -30,9 +30,7 @@ func parseCases(tokens token.List, file *fs.File) ([]Case, error) { Condition: condition, Body: body, }) + } - return nil - }) - - return cases, err + return cases, nil } diff --git a/src/token/Instructions.go b/src/token/Instructions.go new file mode 100644 index 0000000..183d6db --- /dev/null +++ b/src/token/Instructions.go @@ -0,0 +1,61 @@ +package token + +// Instructions yields on each AST node. +func (list List) Instructions(yield func(List) bool) { + start := 0 + groupLevel := 0 + blockLevel := 0 + + for i, t := range list { + switch t.Kind { + case NewLine: + if start == i { + start = i + 1 + continue + } + + if groupLevel > 0 || blockLevel > 0 { + continue + } + + if !yield(list[start:i]) { + return + } + + start = i + 1 + + case GroupStart: + groupLevel++ + + case GroupEnd: + groupLevel-- + + case BlockStart: + blockLevel++ + + case BlockEnd: + blockLevel-- + + if groupLevel > 0 || blockLevel > 0 { + continue + } + + if !yield(list[start : i+1]) { + return + } + + start = i + 1 + + case EOF: + if start < i { + yield(list[start:i]) + } + + return + } + } + + if start < len(list) { + yield(list[start:]) + } +} diff --git a/src/token/Instructions_test.go b/src/token/Instructions_test.go new file mode 100644 index 0000000..cb06501 --- /dev/null +++ b/src/token/Instructions_test.go @@ -0,0 +1,20 @@ +package token_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/go/assert" +) + +func TestInstructions(t *testing.T) { + src := []byte("a := 1\nb := 2\n") + tokens := token.Tokenize(src) + nodes := []string{} + + for param := range tokens.Instructions { + nodes = append(nodes, param.Text(src)) + } + + assert.DeepEqual(t, nodes, []string{"a:=1", "b:=2"}) +} From 56ce523642b0b228a64ea6e34274cb7c02e5b4d4 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 20 Mar 2025 13:53:23 +0100 Subject: [PATCH 0964/1012] Implemented instruction splitting as a generator --- src/ast/Parse.go | 14 ++++--- src/ast/eachInstruction.go | 69 ---------------------------------- src/ast/parseCases.go | 10 ++--- src/token/Instructions.go | 61 ++++++++++++++++++++++++++++++ src/token/Instructions_test.go | 20 ++++++++++ 5 files changed, 93 insertions(+), 81 deletions(-) delete mode 100644 src/ast/eachInstruction.go create mode 100644 src/token/Instructions.go create mode 100644 src/token/Instructions_test.go diff --git a/src/ast/Parse.go b/src/ast/Parse.go index 20634e4..ea7c758 100644 --- a/src/ast/Parse.go +++ b/src/ast/Parse.go @@ -6,18 +6,20 @@ import ( ) // Parse generates an AST from a list of tokens. -func Parse(tokens []token.Token, file *fs.File) (AST, error) { +func Parse(tokens token.List, file *fs.File) (AST, error) { nodes := make(AST, 0, len(tokens)/64) - err := eachInstruction(tokens, func(instruction token.List) error { - node, err := parseInstruction(instruction, file, nodes) + for tokens := range tokens.Instructions { + node, err := parseInstruction(tokens, file, nodes) if node != nil { nodes = append(nodes, node) } - return err - }) + if err != nil { + return nil, err + } + } - return nodes, err + return nodes, nil } diff --git a/src/ast/eachInstruction.go b/src/ast/eachInstruction.go deleted file mode 100644 index 0bed958..0000000 --- a/src/ast/eachInstruction.go +++ /dev/null @@ -1,69 +0,0 @@ -package ast - -import "git.urbach.dev/cli/q/src/token" - -// eachInstruction calls the function on each AST node. -func eachInstruction(tokens token.List, call func(token.List) error) error { - start := 0 - groupLevel := 0 - blockLevel := 0 - - for i, t := range tokens { - switch t.Kind { - case token.NewLine: - if start == i { - start = i + 1 - continue - } - - if groupLevel > 0 || blockLevel > 0 { - continue - } - - err := call(tokens[start:i]) - - if err != nil { - return err - } - - start = i + 1 - - case token.GroupStart: - groupLevel++ - - case token.GroupEnd: - groupLevel-- - - case token.BlockStart: - blockLevel++ - - case token.BlockEnd: - blockLevel-- - - if groupLevel > 0 || blockLevel > 0 { - continue - } - - err := call(tokens[start : i+1]) - - if err != nil { - return err - } - - start = i + 1 - - case token.EOF: - if start < i { - return call(tokens[start:i]) - } - - return nil - } - } - - if start < len(tokens) { - return call(tokens[start:]) - } - - return nil -} diff --git a/src/ast/parseCases.go b/src/ast/parseCases.go index dbb5412..2ea4cf1 100644 --- a/src/ast/parseCases.go +++ b/src/ast/parseCases.go @@ -10,11 +10,11 @@ import ( func parseCases(tokens token.List, file *fs.File) ([]Case, error) { var cases []Case - err := eachInstruction(tokens, func(caseTokens token.List) error { + for caseTokens := range tokens.Instructions { blockStart, _, body, err := block(caseTokens, file) if err != nil { - return err + return nil, err } conditionTokens := caseTokens[:blockStart] @@ -30,9 +30,7 @@ func parseCases(tokens token.List, file *fs.File) ([]Case, error) { Condition: condition, Body: body, }) + } - return nil - }) - - return cases, err + return cases, nil } diff --git a/src/token/Instructions.go b/src/token/Instructions.go new file mode 100644 index 0000000..183d6db --- /dev/null +++ b/src/token/Instructions.go @@ -0,0 +1,61 @@ +package token + +// Instructions yields on each AST node. +func (list List) Instructions(yield func(List) bool) { + start := 0 + groupLevel := 0 + blockLevel := 0 + + for i, t := range list { + switch t.Kind { + case NewLine: + if start == i { + start = i + 1 + continue + } + + if groupLevel > 0 || blockLevel > 0 { + continue + } + + if !yield(list[start:i]) { + return + } + + start = i + 1 + + case GroupStart: + groupLevel++ + + case GroupEnd: + groupLevel-- + + case BlockStart: + blockLevel++ + + case BlockEnd: + blockLevel-- + + if groupLevel > 0 || blockLevel > 0 { + continue + } + + if !yield(list[start : i+1]) { + return + } + + start = i + 1 + + case EOF: + if start < i { + yield(list[start:i]) + } + + return + } + } + + if start < len(list) { + yield(list[start:]) + } +} diff --git a/src/token/Instructions_test.go b/src/token/Instructions_test.go new file mode 100644 index 0000000..cb06501 --- /dev/null +++ b/src/token/Instructions_test.go @@ -0,0 +1,20 @@ +package token_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/go/assert" +) + +func TestInstructions(t *testing.T) { + src := []byte("a := 1\nb := 2\n") + tokens := token.Tokenize(src) + nodes := []string{} + + for param := range tokens.Instructions { + nodes = append(nodes, param.Text(src)) + } + + assert.DeepEqual(t, nodes, []string{"a:=1", "b:=2"}) +} From 597eb08ff6a839ec79b2132cf03fe26799e67937 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 22 Mar 2025 12:38:41 +0100 Subject: [PATCH 0965/1012] Implemented an ordered set for function dependencies --- src/compiler/eachFunction.go | 2 +- src/core/CompileDelete.go | 2 +- src/core/CompileNew.go | 2 +- src/core/EvaluateDot.go | 2 +- src/core/EvaluateToken.go | 2 +- src/core/Function.go | 3 ++- src/readme.md | 1 + src/set/Ordered.go | 33 +++++++++++++++++++++++++++++++++ 8 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 src/set/Ordered.go diff --git a/src/compiler/eachFunction.go b/src/compiler/eachFunction.go index 44bc00e..9ffa2e9 100644 --- a/src/compiler/eachFunction.go +++ b/src/compiler/eachFunction.go @@ -10,7 +10,7 @@ func (r *Result) eachFunction(caller *core.Function, traversed map[*core.Functio call(caller) traversed[caller] = true - for _, function := range caller.Dependencies { + for function := range caller.Dependencies.All() { if traversed[function] { continue } diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index b834d06..131a9d0 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -26,6 +26,6 @@ func (f *Function) CompileDelete(root *expression.Expression) error { f.BeforeCall() f.Label(asm.CALL, asm.Label{Name: "mem.free", Type: asm.FunctionLabel}) f.AfterCall(f.CPU.Input[:2]) - f.Dependencies = append(f.Dependencies, free) + f.Dependencies.Add(free) return nil } diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index 6d2ae30..c4b74bf 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -49,6 +49,6 @@ func (f *Function) CompileNew(root *expression.Expression) (types.Type, error) { f.BeforeCall() f.Label(asm.CALL, asm.Label{Name: "mem.alloc", Type: asm.FunctionLabel}) f.AfterCall(f.CPU.Input[:1]) - f.Dependencies = append(f.Dependencies, alloc) + f.Dependencies.Add(alloc) return &types.Pointer{To: typ}, nil } diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go index 3bc55c2..bed4420 100644 --- a/src/core/EvaluateDot.go +++ b/src/core/EvaluateDot.go @@ -76,7 +76,7 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) function, exists := f.All.Functions[label] if exists { - f.Dependencies = append(f.Dependencies, function) + f.Dependencies.Add(function) value := &eval.Label{ Typ: types.AnyPointer, diff --git a/src/core/EvaluateToken.go b/src/core/EvaluateToken.go index 0b1aa30..991089e 100644 --- a/src/core/EvaluateToken.go +++ b/src/core/EvaluateToken.go @@ -46,7 +46,7 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { function, exists := f.All.Functions[uniqueName] if exists { - f.Dependencies = append(f.Dependencies, function) + f.Dependencies.Add(function) value := &eval.Label{ Typ: types.AnyPointer, diff --git a/src/core/Function.go b/src/core/Function.go index 32de2cf..e38cbc7 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -4,6 +4,7 @@ import ( "git.urbach.dev/cli/q/src/dll" "git.urbach.dev/cli/q/src/fs" "git.urbach.dev/cli/q/src/register" + "git.urbach.dev/cli/q/src/set" "git.urbach.dev/cli/q/src/token" ) @@ -18,7 +19,7 @@ type Function struct { Body token.List Input []*Parameter Output []*Parameter - Dependencies []*Function + Dependencies set.Ordered[*Function] DLLs dll.List Err error deferred []func() diff --git a/src/readme.md b/src/readme.md index 63ab66d..83641ce 100644 --- a/src/readme.md +++ b/src/readme.md @@ -24,6 +24,7 @@ - [riscv](riscv) - RISCV implementation (w.i.p.) - [scanner](scanner) - Scanner frontend used by `build` - [scope](scope) - Defines a `Scope` used for code blocks +- [set](set) - Generic set implementation - [sizeof](sizeof) - Calculates the byte size of numbers - [token](token) - Converts a file to tokens with the `Tokenize` function - [types](types) - Type system diff --git a/src/set/Ordered.go b/src/set/Ordered.go new file mode 100644 index 0000000..bc7caa5 --- /dev/null +++ b/src/set/Ordered.go @@ -0,0 +1,33 @@ +package set + +import ( + "iter" + "slices" +) + +// Ordered is an ordered set. +type Ordered[T comparable] struct { + values []T +} + +// Add adds a value to the set if it doesn't exist yet. +// It returns `false` if it already exists, `true` if it was added. +func (set *Ordered[T]) Add(value T) bool { + if slices.Contains(set.values, value) { + return false + } + + set.values = append(set.values, value) + return true +} + +// All returns an iterator over all the values in the set. +func (set *Ordered[T]) All() iter.Seq[T] { + return func(yield func(T) bool) { + for _, value := range set.values { + if !yield(value) { + return + } + } + } +} From f231d92de5f9f6e753673310fe8143a2ed44fb18 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 22 Mar 2025 12:38:41 +0100 Subject: [PATCH 0966/1012] Implemented an ordered set for function dependencies --- src/compiler/eachFunction.go | 2 +- src/core/CompileDelete.go | 2 +- src/core/CompileNew.go | 2 +- src/core/EvaluateDot.go | 2 +- src/core/EvaluateToken.go | 2 +- src/core/Function.go | 3 ++- src/readme.md | 1 + src/set/Ordered.go | 33 +++++++++++++++++++++++++++++++++ 8 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 src/set/Ordered.go diff --git a/src/compiler/eachFunction.go b/src/compiler/eachFunction.go index 44bc00e..9ffa2e9 100644 --- a/src/compiler/eachFunction.go +++ b/src/compiler/eachFunction.go @@ -10,7 +10,7 @@ func (r *Result) eachFunction(caller *core.Function, traversed map[*core.Functio call(caller) traversed[caller] = true - for _, function := range caller.Dependencies { + for function := range caller.Dependencies.All() { if traversed[function] { continue } diff --git a/src/core/CompileDelete.go b/src/core/CompileDelete.go index b834d06..131a9d0 100644 --- a/src/core/CompileDelete.go +++ b/src/core/CompileDelete.go @@ -26,6 +26,6 @@ func (f *Function) CompileDelete(root *expression.Expression) error { f.BeforeCall() f.Label(asm.CALL, asm.Label{Name: "mem.free", Type: asm.FunctionLabel}) f.AfterCall(f.CPU.Input[:2]) - f.Dependencies = append(f.Dependencies, free) + f.Dependencies.Add(free) return nil } diff --git a/src/core/CompileNew.go b/src/core/CompileNew.go index 6d2ae30..c4b74bf 100644 --- a/src/core/CompileNew.go +++ b/src/core/CompileNew.go @@ -49,6 +49,6 @@ func (f *Function) CompileNew(root *expression.Expression) (types.Type, error) { f.BeforeCall() f.Label(asm.CALL, asm.Label{Name: "mem.alloc", Type: asm.FunctionLabel}) f.AfterCall(f.CPU.Input[:1]) - f.Dependencies = append(f.Dependencies, alloc) + f.Dependencies.Add(alloc) return &types.Pointer{To: typ}, nil } diff --git a/src/core/EvaluateDot.go b/src/core/EvaluateDot.go index 3bc55c2..bed4420 100644 --- a/src/core/EvaluateDot.go +++ b/src/core/EvaluateDot.go @@ -76,7 +76,7 @@ func (f *Function) EvaluateDot(expr *expression.Expression) (eval.Value, error) function, exists := f.All.Functions[label] if exists { - f.Dependencies = append(f.Dependencies, function) + f.Dependencies.Add(function) value := &eval.Label{ Typ: types.AnyPointer, diff --git a/src/core/EvaluateToken.go b/src/core/EvaluateToken.go index 0b1aa30..991089e 100644 --- a/src/core/EvaluateToken.go +++ b/src/core/EvaluateToken.go @@ -46,7 +46,7 @@ func (f *Function) EvaluateToken(t token.Token) (eval.Value, error) { function, exists := f.All.Functions[uniqueName] if exists { - f.Dependencies = append(f.Dependencies, function) + f.Dependencies.Add(function) value := &eval.Label{ Typ: types.AnyPointer, diff --git a/src/core/Function.go b/src/core/Function.go index 32de2cf..e38cbc7 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -4,6 +4,7 @@ import ( "git.urbach.dev/cli/q/src/dll" "git.urbach.dev/cli/q/src/fs" "git.urbach.dev/cli/q/src/register" + "git.urbach.dev/cli/q/src/set" "git.urbach.dev/cli/q/src/token" ) @@ -18,7 +19,7 @@ type Function struct { Body token.List Input []*Parameter Output []*Parameter - Dependencies []*Function + Dependencies set.Ordered[*Function] DLLs dll.List Err error deferred []func() diff --git a/src/readme.md b/src/readme.md index 63ab66d..83641ce 100644 --- a/src/readme.md +++ b/src/readme.md @@ -24,6 +24,7 @@ - [riscv](riscv) - RISCV implementation (w.i.p.) - [scanner](scanner) - Scanner frontend used by `build` - [scope](scope) - Defines a `Scope` used for code blocks +- [set](set) - Generic set implementation - [sizeof](sizeof) - Calculates the byte size of numbers - [token](token) - Converts a file to tokens with the `Tokenize` function - [types](types) - Type system diff --git a/src/set/Ordered.go b/src/set/Ordered.go new file mode 100644 index 0000000..bc7caa5 --- /dev/null +++ b/src/set/Ordered.go @@ -0,0 +1,33 @@ +package set + +import ( + "iter" + "slices" +) + +// Ordered is an ordered set. +type Ordered[T comparable] struct { + values []T +} + +// Add adds a value to the set if it doesn't exist yet. +// It returns `false` if it already exists, `true` if it was added. +func (set *Ordered[T]) Add(value T) bool { + if slices.Contains(set.values, value) { + return false + } + + set.values = append(set.values, value) + return true +} + +// All returns an iterator over all the values in the set. +func (set *Ordered[T]) All() iter.Seq[T] { + return func(yield func(T) bool) { + for _, value := range set.values { + if !yield(value) { + return + } + } + } +} From ae928156c5b893daee1edc2fdb4c667a96781177 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 23 Mar 2025 12:57:05 +0100 Subject: [PATCH 0967/1012] Added register documentation for arm64 --- src/arm/Registers.go | 26 ++++++++++++++----------- src/asmc/ARM.go | 45 ++++++++++++++++++-------------------------- 2 files changed, 33 insertions(+), 38 deletions(-) diff --git a/src/arm/Registers.go b/src/arm/Registers.go index 9204b69..6277134 100644 --- a/src/arm/Registers.go +++ b/src/arm/Registers.go @@ -3,7 +3,7 @@ package arm import "git.urbach.dev/cli/q/src/cpu" const ( - X0 cpu.Register = iota + X0 cpu.Register = iota // Function arguments and return values [0-7] X1 X2 X3 @@ -11,18 +11,18 @@ const ( X5 X6 X7 - X8 - X9 + X8 // Indirect result location register (used to pass a pointer to a structure return value) + X9 // Temporary registers (caller-saved, used for general computation) [9-15] X10 X11 X12 X13 X14 X15 - X16 + X16 // Intra-procedure call scratch registers [16-17] X17 - X18 - X19 + X18 // Platform register (reserved by the platform ABI for thread-local storage) + X19 // Callee-saved registers (must be preserved across function calls) [19-28] X20 X21 X22 @@ -32,14 +32,18 @@ const ( X26 X27 X28 - FP // Frame pointer - LR // Link register - SP // Stack pointer - ZR = SP // Zero register uses the same numerical value as SP + FP // Frame pointer + LR // Link register + SP // Stack pointer +) + +const ( + ZR = SP // Zero register uses the same numerical value as SP + TMP = X28 // Temporary register for the assembler ) var ( - GeneralRegisters = []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28} + GeneralRegisters = []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28} InputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5} OutputRegisters = InputRegisters SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} diff --git a/src/asmc/ARM.go b/src/asmc/ARM.go index 1d98cb1..a4d2347 100644 --- a/src/asmc/ARM.go +++ b/src/asmc/ARM.go @@ -113,9 +113,8 @@ func (c *compiler) ARM(x asm.Instruction) { operands := c.assembler.Param.MemoryNumber[x.Index] if operands.Address.OffsetRegister < 0 { - tmp := arm.X28 - c.code = arm.MoveRegisterNumber(c.code, tmp, operands.Number) - c.append(arm.StoreRegister(tmp, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operands.Number) + c.append(arm.StoreRegister(arm.TMP, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) } else { panic("not implemented") } @@ -155,9 +154,8 @@ func (c *compiler) ARM(x asm.Instruction) { if encodable { c.append(code) } else { - tmp := arm.X28 - c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number) - c.append(arm.AndRegisterRegister(operand.Register, operand.Register, tmp)) + c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) + c.append(arm.AndRegisterRegister(operand.Register, operand.Register, arm.TMP)) } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] @@ -173,9 +171,8 @@ func (c *compiler) ARM(x asm.Instruction) { if encodable { c.append(code) } else { - tmp := arm.X28 - c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number) - c.append(arm.OrRegisterRegister(operand.Register, operand.Register, tmp)) + c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) + c.append(arm.OrRegisterRegister(operand.Register, operand.Register, arm.TMP)) } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] @@ -191,9 +188,8 @@ func (c *compiler) ARM(x asm.Instruction) { if encodable { c.append(code) } else { - tmp := arm.X28 - c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number) - c.append(arm.XorRegisterRegister(operand.Register, operand.Register, tmp)) + c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) + c.append(arm.XorRegisterRegister(operand.Register, operand.Register, arm.TMP)) } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] @@ -209,9 +205,8 @@ func (c *compiler) ARM(x asm.Instruction) { if encodable { c.append(code) } else { - tmp := arm.X28 - c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number) - c.append(arm.AddRegisterRegister(operand.Register, operand.Register, tmp)) + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.AddRegisterRegister(operand.Register, operand.Register, arm.TMP)) } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] @@ -227,9 +222,8 @@ func (c *compiler) ARM(x asm.Instruction) { if encodable { c.append(code) } else { - tmp := arm.X28 - c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number) - c.append(arm.SubRegisterRegister(operand.Register, operand.Register, tmp)) + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.SubRegisterRegister(operand.Register, operand.Register, arm.TMP)) } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] @@ -245,9 +239,8 @@ func (c *compiler) ARM(x asm.Instruction) { if encodable { c.append(code) } else { - tmp := arm.X28 - c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number) - c.append(arm.CompareRegisterRegister(operand.Register, tmp)) + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.CompareRegisterRegister(operand.Register, arm.TMP)) } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] @@ -270,18 +263,16 @@ func (c *compiler) ARM(x asm.Instruction) { c.append(arm.MulRegisterRegister(operand.Destination, operand.Destination, operand.Source)) case asm.TypeRegisterNumber: operand := c.assembler.Param.RegisterNumber[x.Index] - tmp := arm.X28 - c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number) - c.append(arm.MulRegisterRegister(operand.Register, operand.Register, tmp)) + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.MulRegisterRegister(operand.Register, operand.Register, arm.TMP)) } case asm.MODULO: switch x.Type { case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] - tmp := arm.X28 - c.append(arm.DivSigned(tmp, operand.Destination, operand.Source)) - c.append(arm.MultiplySubtract(operand.Destination, tmp, operand.Source, operand.Destination)) + c.append(arm.DivSigned(arm.TMP, operand.Destination, operand.Source)) + c.append(arm.MultiplySubtract(operand.Destination, arm.TMP, operand.Source, operand.Destination)) case asm.TypeRegisterNumber: panic("not implemented") } From b96abd2fe42d8d50f573bf7dd5ce3e27a55f02dc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 23 Mar 2025 12:57:05 +0100 Subject: [PATCH 0968/1012] Added register documentation for arm64 --- src/arm/Registers.go | 26 ++++++++++++++----------- src/asmc/ARM.go | 45 ++++++++++++++++++-------------------------- 2 files changed, 33 insertions(+), 38 deletions(-) diff --git a/src/arm/Registers.go b/src/arm/Registers.go index 9204b69..6277134 100644 --- a/src/arm/Registers.go +++ b/src/arm/Registers.go @@ -3,7 +3,7 @@ package arm import "git.urbach.dev/cli/q/src/cpu" const ( - X0 cpu.Register = iota + X0 cpu.Register = iota // Function arguments and return values [0-7] X1 X2 X3 @@ -11,18 +11,18 @@ const ( X5 X6 X7 - X8 - X9 + X8 // Indirect result location register (used to pass a pointer to a structure return value) + X9 // Temporary registers (caller-saved, used for general computation) [9-15] X10 X11 X12 X13 X14 X15 - X16 + X16 // Intra-procedure call scratch registers [16-17] X17 - X18 - X19 + X18 // Platform register (reserved by the platform ABI for thread-local storage) + X19 // Callee-saved registers (must be preserved across function calls) [19-28] X20 X21 X22 @@ -32,14 +32,18 @@ const ( X26 X27 X28 - FP // Frame pointer - LR // Link register - SP // Stack pointer - ZR = SP // Zero register uses the same numerical value as SP + FP // Frame pointer + LR // Link register + SP // Stack pointer +) + +const ( + ZR = SP // Zero register uses the same numerical value as SP + TMP = X28 // Temporary register for the assembler ) var ( - GeneralRegisters = []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28} + GeneralRegisters = []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28} InputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5} OutputRegisters = InputRegisters SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} diff --git a/src/asmc/ARM.go b/src/asmc/ARM.go index 1d98cb1..a4d2347 100644 --- a/src/asmc/ARM.go +++ b/src/asmc/ARM.go @@ -113,9 +113,8 @@ func (c *compiler) ARM(x asm.Instruction) { operands := c.assembler.Param.MemoryNumber[x.Index] if operands.Address.OffsetRegister < 0 { - tmp := arm.X28 - c.code = arm.MoveRegisterNumber(c.code, tmp, operands.Number) - c.append(arm.StoreRegister(tmp, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operands.Number) + c.append(arm.StoreRegister(arm.TMP, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) } else { panic("not implemented") } @@ -155,9 +154,8 @@ func (c *compiler) ARM(x asm.Instruction) { if encodable { c.append(code) } else { - tmp := arm.X28 - c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number) - c.append(arm.AndRegisterRegister(operand.Register, operand.Register, tmp)) + c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) + c.append(arm.AndRegisterRegister(operand.Register, operand.Register, arm.TMP)) } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] @@ -173,9 +171,8 @@ func (c *compiler) ARM(x asm.Instruction) { if encodable { c.append(code) } else { - tmp := arm.X28 - c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number) - c.append(arm.OrRegisterRegister(operand.Register, operand.Register, tmp)) + c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) + c.append(arm.OrRegisterRegister(operand.Register, operand.Register, arm.TMP)) } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] @@ -191,9 +188,8 @@ func (c *compiler) ARM(x asm.Instruction) { if encodable { c.append(code) } else { - tmp := arm.X28 - c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number) - c.append(arm.XorRegisterRegister(operand.Register, operand.Register, tmp)) + c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) + c.append(arm.XorRegisterRegister(operand.Register, operand.Register, arm.TMP)) } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] @@ -209,9 +205,8 @@ func (c *compiler) ARM(x asm.Instruction) { if encodable { c.append(code) } else { - tmp := arm.X28 - c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number) - c.append(arm.AddRegisterRegister(operand.Register, operand.Register, tmp)) + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.AddRegisterRegister(operand.Register, operand.Register, arm.TMP)) } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] @@ -227,9 +222,8 @@ func (c *compiler) ARM(x asm.Instruction) { if encodable { c.append(code) } else { - tmp := arm.X28 - c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number) - c.append(arm.SubRegisterRegister(operand.Register, operand.Register, tmp)) + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.SubRegisterRegister(operand.Register, operand.Register, arm.TMP)) } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] @@ -245,9 +239,8 @@ func (c *compiler) ARM(x asm.Instruction) { if encodable { c.append(code) } else { - tmp := arm.X28 - c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number) - c.append(arm.CompareRegisterRegister(operand.Register, tmp)) + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.CompareRegisterRegister(operand.Register, arm.TMP)) } case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] @@ -270,18 +263,16 @@ func (c *compiler) ARM(x asm.Instruction) { c.append(arm.MulRegisterRegister(operand.Destination, operand.Destination, operand.Source)) case asm.TypeRegisterNumber: operand := c.assembler.Param.RegisterNumber[x.Index] - tmp := arm.X28 - c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number) - c.append(arm.MulRegisterRegister(operand.Register, operand.Register, tmp)) + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.MulRegisterRegister(operand.Register, operand.Register, arm.TMP)) } case asm.MODULO: switch x.Type { case asm.TypeRegisterRegister: operand := c.assembler.Param.RegisterRegister[x.Index] - tmp := arm.X28 - c.append(arm.DivSigned(tmp, operand.Destination, operand.Source)) - c.append(arm.MultiplySubtract(operand.Destination, tmp, operand.Source, operand.Destination)) + c.append(arm.DivSigned(arm.TMP, operand.Destination, operand.Source)) + c.append(arm.MultiplySubtract(operand.Destination, arm.TMP, operand.Source, operand.Destination)) case asm.TypeRegisterNumber: panic("not implemented") } From 33b91e7bf4ae900315e63059e3568c69fdf94903 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 24 Mar 2025 23:17:51 +0100 Subject: [PATCH 0969/1012] Added more tests --- src/arm/Compare_test.go | 16 ++++++++++++++++ src/arm/Load.go | 4 +--- src/arm/Store.go | 4 +--- src/arm/arm_test.go | 8 ++++++++ 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/arm/Compare_test.go b/src/arm/Compare_test.go index 16cecdd..4ed047c 100644 --- a/src/arm/Compare_test.go +++ b/src/arm/Compare_test.go @@ -27,3 +27,19 @@ func TestCompareRegisterNumber(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestCompareRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, 0xEB01001F}, + } + + for _, pattern := range usagePatterns { + t.Logf("cmp %s, %s", pattern.Left, pattern.Right) + code := arm.CompareRegisterRegister(pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Load.go b/src/arm/Load.go index afbd176..0b4f6de 100644 --- a/src/arm/Load.go +++ b/src/arm/Load.go @@ -13,9 +13,7 @@ func LoadRegister(destination cpu.Register, base cpu.Register, offset int, lengt return 0b01111<<27 | common case 4: return 0b10111<<27 | common - case 8: + default: return 0b11111<<27 | common } - - panic("invalid length") } diff --git a/src/arm/Store.go b/src/arm/Store.go index ceeb839..65c246b 100644 --- a/src/arm/Store.go +++ b/src/arm/Store.go @@ -15,9 +15,7 @@ func StoreRegister(source cpu.Register, base cpu.Register, offset int, length by return 0b01111<<27 | common case 4: return 0b10111<<27 | common - case 8: + default: return 0b11111<<27 | common } - - panic("invalid length") } diff --git a/src/arm/arm_test.go b/src/arm/arm_test.go index b81e610..9a6693f 100644 --- a/src/arm/arm_test.go +++ b/src/arm/arm_test.go @@ -27,4 +27,12 @@ func TestNotEncodable(t *testing.T) { assert.False(t, encodable) _, encodable = arm.XorRegisterNumber(arm.X0, arm.X0, -1) assert.False(t, encodable) + _, encodable = arm.AddRegisterNumber(arm.X0, arm.X0, 0xFFFF) + assert.False(t, encodable) + _, encodable = arm.AddRegisterNumber(arm.X0, arm.X0, 0xF0000000) + assert.False(t, encodable) + _, encodable = arm.SubRegisterNumber(arm.X0, arm.X0, 0xFFFF) + assert.False(t, encodable) + _, encodable = arm.SubRegisterNumber(arm.X0, arm.X0, 0xF0000000) + assert.False(t, encodable) } From 2324c860c961c6733e7431ee8d9f305916b1242a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 24 Mar 2025 23:17:51 +0100 Subject: [PATCH 0970/1012] Added more tests --- src/arm/Compare_test.go | 16 ++++++++++++++++ src/arm/Load.go | 4 +--- src/arm/Store.go | 4 +--- src/arm/arm_test.go | 8 ++++++++ 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/arm/Compare_test.go b/src/arm/Compare_test.go index 16cecdd..4ed047c 100644 --- a/src/arm/Compare_test.go +++ b/src/arm/Compare_test.go @@ -27,3 +27,19 @@ func TestCompareRegisterNumber(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestCompareRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, 0xEB01001F}, + } + + for _, pattern := range usagePatterns { + t.Logf("cmp %s, %s", pattern.Left, pattern.Right) + code := arm.CompareRegisterRegister(pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Load.go b/src/arm/Load.go index afbd176..0b4f6de 100644 --- a/src/arm/Load.go +++ b/src/arm/Load.go @@ -13,9 +13,7 @@ func LoadRegister(destination cpu.Register, base cpu.Register, offset int, lengt return 0b01111<<27 | common case 4: return 0b10111<<27 | common - case 8: + default: return 0b11111<<27 | common } - - panic("invalid length") } diff --git a/src/arm/Store.go b/src/arm/Store.go index ceeb839..65c246b 100644 --- a/src/arm/Store.go +++ b/src/arm/Store.go @@ -15,9 +15,7 @@ func StoreRegister(source cpu.Register, base cpu.Register, offset int, length by return 0b01111<<27 | common case 4: return 0b10111<<27 | common - case 8: + default: return 0b11111<<27 | common } - - panic("invalid length") } diff --git a/src/arm/arm_test.go b/src/arm/arm_test.go index b81e610..9a6693f 100644 --- a/src/arm/arm_test.go +++ b/src/arm/arm_test.go @@ -27,4 +27,12 @@ func TestNotEncodable(t *testing.T) { assert.False(t, encodable) _, encodable = arm.XorRegisterNumber(arm.X0, arm.X0, -1) assert.False(t, encodable) + _, encodable = arm.AddRegisterNumber(arm.X0, arm.X0, 0xFFFF) + assert.False(t, encodable) + _, encodable = arm.AddRegisterNumber(arm.X0, arm.X0, 0xF0000000) + assert.False(t, encodable) + _, encodable = arm.SubRegisterNumber(arm.X0, arm.X0, 0xFFFF) + assert.False(t, encodable) + _, encodable = arm.SubRegisterNumber(arm.X0, arm.X0, 0xF0000000) + assert.False(t, encodable) } From 0bc52fb6738d7eb723b5554e03b9f3adafb8b778 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Mar 2025 15:35:29 +0100 Subject: [PATCH 0971/1012] Improved documentation --- src/arm/Mul.go | 2 +- src/arm/Mul_test.go | 1 + src/riscv/Registers.go | 68 +++++++++++++++++++++--------------------- 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/arm/Mul.go b/src/arm/Mul.go index afad2cc..3df08e9 100644 --- a/src/arm/Mul.go +++ b/src/arm/Mul.go @@ -7,7 +7,7 @@ func MulRegisterRegister(destination cpu.Register, multiplicand cpu.Register, mu return 0b10011011000<<21 | reg4(destination, multiplicand, multiplier, ZR) } -// MultiplySubtract multiplies `multiplicand` with `multiplier`, subtracts `minuend` and saves the result in `destination`. +// MultiplySubtract multiplies `multiplicand` with `multiplier`, subtracts the product from `minuend` and saves the result in `destination`. func MultiplySubtract(destination cpu.Register, multiplicand cpu.Register, multiplier cpu.Register, minuend cpu.Register) uint32 { return 0b10011011000<<21 | 1<<15 | reg4(destination, multiplicand, multiplier, minuend) } diff --git a/src/arm/Mul_test.go b/src/arm/Mul_test.go index d654208..c59325e 100644 --- a/src/arm/Mul_test.go +++ b/src/arm/Mul_test.go @@ -34,6 +34,7 @@ func TestMultiplySubtract(t *testing.T) { Code uint32 }{ {arm.X0, arm.X1, arm.X2, arm.X3, 0x9B028C20}, + {arm.X3, arm.X0, arm.X2, arm.X1, 0x9B028403}, } for _, pattern := range usagePatterns { diff --git a/src/riscv/Registers.go b/src/riscv/Registers.go index 1313390..30ab2c8 100644 --- a/src/riscv/Registers.go +++ b/src/riscv/Registers.go @@ -3,41 +3,41 @@ package riscv import "git.urbach.dev/cli/q/src/cpu" const ( - X0 cpu.Register = iota - X1 - X2 - X3 - X4 - X5 - X6 - X7 - X8 - X9 - X10 - X11 - X12 - X13 - X14 - X15 - X16 - X17 - X18 - X19 - X20 - X21 - X22 - X23 - X24 - X25 - X26 - X27 - X28 - X29 - X30 - X31 + Zero cpu.Register = iota // hardwired zero + RA // return address + SP // stack pointer + GP // global pointer + TP // thread pointer + T0 // temporary register 0 + T1 // temporary register 1 + T2 // temporary register 2 + S0 // saved register 0 / frame pointer + S1 // saved register 1 + A0 // function argument 0 / return value 0 + A1 // function argument 1 / return value 1 + A2 // function argument 2 + A3 // function argument 3 + A4 // function argument 4 + A5 // function argument 5 + A6 // function argument 6 + A7 // function argument 7 + S2 // saved register 2 + S3 // saved register 3 + S4 // saved register 4 + S5 // saved register 5 + S6 // saved register 6 + S7 // saved register 7 + S8 // saved register 8 + S9 // saved register 9 + S10 // saved register 10 + S11 // saved register 11 + T3 // temporary register 3 + T4 // temporary register 4 + T5 // temporary register 5 + T6 // temporary register 6 ) var ( - SyscallInputRegisters = []cpu.Register{X17, X10, X11, X12, X13, X14, X15, X16} - SyscallOutputRegisters = []cpu.Register{X10, X11} + SyscallInputRegisters = []cpu.Register{A7, A0, A1, A2, A3, A4, A5, A6} + SyscallOutputRegisters = []cpu.Register{A0, A1} ) From 5bf2ce0bb51e65a14231276dd73354441ad6c078 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 28 Mar 2025 15:35:29 +0100 Subject: [PATCH 0972/1012] Improved documentation --- src/arm/Mul.go | 2 +- src/arm/Mul_test.go | 1 + src/riscv/Registers.go | 68 +++++++++++++++++++++--------------------- 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/arm/Mul.go b/src/arm/Mul.go index afad2cc..3df08e9 100644 --- a/src/arm/Mul.go +++ b/src/arm/Mul.go @@ -7,7 +7,7 @@ func MulRegisterRegister(destination cpu.Register, multiplicand cpu.Register, mu return 0b10011011000<<21 | reg4(destination, multiplicand, multiplier, ZR) } -// MultiplySubtract multiplies `multiplicand` with `multiplier`, subtracts `minuend` and saves the result in `destination`. +// MultiplySubtract multiplies `multiplicand` with `multiplier`, subtracts the product from `minuend` and saves the result in `destination`. func MultiplySubtract(destination cpu.Register, multiplicand cpu.Register, multiplier cpu.Register, minuend cpu.Register) uint32 { return 0b10011011000<<21 | 1<<15 | reg4(destination, multiplicand, multiplier, minuend) } diff --git a/src/arm/Mul_test.go b/src/arm/Mul_test.go index d654208..c59325e 100644 --- a/src/arm/Mul_test.go +++ b/src/arm/Mul_test.go @@ -34,6 +34,7 @@ func TestMultiplySubtract(t *testing.T) { Code uint32 }{ {arm.X0, arm.X1, arm.X2, arm.X3, 0x9B028C20}, + {arm.X3, arm.X0, arm.X2, arm.X1, 0x9B028403}, } for _, pattern := range usagePatterns { diff --git a/src/riscv/Registers.go b/src/riscv/Registers.go index 1313390..30ab2c8 100644 --- a/src/riscv/Registers.go +++ b/src/riscv/Registers.go @@ -3,41 +3,41 @@ package riscv import "git.urbach.dev/cli/q/src/cpu" const ( - X0 cpu.Register = iota - X1 - X2 - X3 - X4 - X5 - X6 - X7 - X8 - X9 - X10 - X11 - X12 - X13 - X14 - X15 - X16 - X17 - X18 - X19 - X20 - X21 - X22 - X23 - X24 - X25 - X26 - X27 - X28 - X29 - X30 - X31 + Zero cpu.Register = iota // hardwired zero + RA // return address + SP // stack pointer + GP // global pointer + TP // thread pointer + T0 // temporary register 0 + T1 // temporary register 1 + T2 // temporary register 2 + S0 // saved register 0 / frame pointer + S1 // saved register 1 + A0 // function argument 0 / return value 0 + A1 // function argument 1 / return value 1 + A2 // function argument 2 + A3 // function argument 3 + A4 // function argument 4 + A5 // function argument 5 + A6 // function argument 6 + A7 // function argument 7 + S2 // saved register 2 + S3 // saved register 3 + S4 // saved register 4 + S5 // saved register 5 + S6 // saved register 6 + S7 // saved register 7 + S8 // saved register 8 + S9 // saved register 9 + S10 // saved register 10 + S11 // saved register 11 + T3 // temporary register 3 + T4 // temporary register 4 + T5 // temporary register 5 + T6 // temporary register 6 ) var ( - SyscallInputRegisters = []cpu.Register{X17, X10, X11, X12, X13, X14, X15, X16} - SyscallOutputRegisters = []cpu.Register{X10, X11} + SyscallInputRegisters = []cpu.Register{A7, A0, A1, A2, A3, A4, A5, A6} + SyscallOutputRegisters = []cpu.Register{A0, A1} ) From 008f097186437b3ad3dda5083f5d3a9782c13a6d Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 31 Mar 2025 14:36:42 +0200 Subject: [PATCH 0973/1012] Simplified CPU state --- src/core/CompileAssign.go | 13 +++++++++---- src/cpu/State.go | 24 +++++------------------- src/cpu/State_test.go | 16 +++++----------- src/register/Machine.go | 2 +- src/register/NewRegister.go | 2 +- src/register/Return.go | 4 +--- src/register/postInstruction.go | 2 +- 7 files changed, 23 insertions(+), 40 deletions(-) diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 1a2f2e6..af628ef 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -37,12 +37,17 @@ func (f *Function) CompileAssign(node *ast.Assign) error { switch leftValue := leftValue.(type) { case *eval.Register: - // TODO: Reservation needs to be canceled on defer - f.CurrentScope().Reserve(leftValue.Register) + if !f.RegisterIsUsed(leftValue.Register) { + f.UseRegister(leftValue.Register) + defer f.FreeRegister(leftValue.Register) + } + f.Execute(operation, leftValue.Register, right) case *eval.Memory: - // TODO: Reservation needs to be canceled on defer - f.CurrentScope().Reserve(leftValue.Memory.Base) + if !f.RegisterIsUsed(leftValue.Memory.Base) { + f.UseRegister(leftValue.Memory.Base) + defer f.FreeRegister(leftValue.Memory.Base) + } if operation.Kind == token.Assign { rightValue, err := f.Evaluate(right) diff --git a/src/cpu/State.go b/src/cpu/State.go index fb8659b..481b585 100644 --- a/src/cpu/State.go +++ b/src/cpu/State.go @@ -1,41 +1,27 @@ package cpu // State contains information about which registers are currently in use. -type State struct { - Reserved uint64 - Used uint64 -} +type State uint64 // Free will reset the reserved and used status which means the register can be allocated again. func (s *State) Free(reg Register) { - s.Reserved &= ^(1 << reg) - s.Used &= ^(1 << reg) -} - -// IsReserved returns true if the register was marked for future use. -func (s *State) IsReserved(reg Register) bool { - return s.Reserved&(1< Date: Mon, 31 Mar 2025 14:36:42 +0200 Subject: [PATCH 0974/1012] Simplified CPU state --- src/core/CompileAssign.go | 13 +++++++++---- src/cpu/State.go | 24 +++++------------------- src/cpu/State_test.go | 16 +++++----------- src/register/Machine.go | 2 +- src/register/NewRegister.go | 2 +- src/register/Return.go | 4 +--- src/register/postInstruction.go | 2 +- 7 files changed, 23 insertions(+), 40 deletions(-) diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index 1a2f2e6..af628ef 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -37,12 +37,17 @@ func (f *Function) CompileAssign(node *ast.Assign) error { switch leftValue := leftValue.(type) { case *eval.Register: - // TODO: Reservation needs to be canceled on defer - f.CurrentScope().Reserve(leftValue.Register) + if !f.RegisterIsUsed(leftValue.Register) { + f.UseRegister(leftValue.Register) + defer f.FreeRegister(leftValue.Register) + } + f.Execute(operation, leftValue.Register, right) case *eval.Memory: - // TODO: Reservation needs to be canceled on defer - f.CurrentScope().Reserve(leftValue.Memory.Base) + if !f.RegisterIsUsed(leftValue.Memory.Base) { + f.UseRegister(leftValue.Memory.Base) + defer f.FreeRegister(leftValue.Memory.Base) + } if operation.Kind == token.Assign { rightValue, err := f.Evaluate(right) diff --git a/src/cpu/State.go b/src/cpu/State.go index fb8659b..481b585 100644 --- a/src/cpu/State.go +++ b/src/cpu/State.go @@ -1,41 +1,27 @@ package cpu // State contains information about which registers are currently in use. -type State struct { - Reserved uint64 - Used uint64 -} +type State uint64 // Free will reset the reserved and used status which means the register can be allocated again. func (s *State) Free(reg Register) { - s.Reserved &= ^(1 << reg) - s.Used &= ^(1 << reg) -} - -// IsReserved returns true if the register was marked for future use. -func (s *State) IsReserved(reg Register) bool { - return s.Reserved&(1< Date: Mon, 31 Mar 2025 15:51:04 +0200 Subject: [PATCH 0975/1012] Implemented division by immediates in the IR --- src/arm/Registers.go | 7 ++++--- src/asmc/ARM.go | 9 +++++++-- src/asmc/X86.go | 28 ++++++++++++++++++++++++++++ src/core/ExecuteRegisterNumber.go | 21 +++++++++++---------- src/core/ExecuteRegisterRegister.go | 11 +++++++++-- src/x86/Registers.go | 3 ++- tests/programs/variables.q | 2 -- 7 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/arm/Registers.go b/src/arm/Registers.go index 6277134..9e54156 100644 --- a/src/arm/Registers.go +++ b/src/arm/Registers.go @@ -38,12 +38,13 @@ const ( ) const ( - ZR = SP // Zero register uses the same numerical value as SP - TMP = X28 // Temporary register for the assembler + ZR = SP // Zero register uses the same numerical value as SP + TMP = X27 // Temporary register for the assembler + TMP2 = X28 // Temporary register for the assembler ) var ( - GeneralRegisters = []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28} + GeneralRegisters = []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X19, X20, X21, X22, X23, X24, X25, X26} InputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5} OutputRegisters = InputRegisters SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} diff --git a/src/asmc/ARM.go b/src/asmc/ARM.go index a4d2347..42ff3a6 100644 --- a/src/asmc/ARM.go +++ b/src/asmc/ARM.go @@ -253,7 +253,9 @@ func (c *compiler) ARM(x asm.Instruction) { operand := c.assembler.Param.RegisterRegister[x.Index] c.append(arm.DivSigned(operand.Destination, operand.Destination, operand.Source)) case asm.TypeRegisterNumber: - panic("not implemented") + operand := c.assembler.Param.RegisterNumber[x.Index] + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.DivSigned(operand.Register, operand.Register, arm.TMP)) } case asm.MUL: @@ -274,7 +276,10 @@ func (c *compiler) ARM(x asm.Instruction) { c.append(arm.DivSigned(arm.TMP, operand.Destination, operand.Source)) c.append(arm.MultiplySubtract(operand.Destination, arm.TMP, operand.Source, operand.Destination)) case asm.TypeRegisterNumber: - panic("not implemented") + operand := c.assembler.Param.RegisterNumber[x.Index] + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.DivSigned(arm.TMP2, operand.Register, arm.TMP)) + c.append(arm.MultiplySubtract(operand.Register, arm.TMP2, arm.TMP, operand.Register)) } case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: diff --git a/src/asmc/X86.go b/src/asmc/X86.go index 448ec41..5cdf60f 100644 --- a/src/asmc/X86.go +++ b/src/asmc/X86.go @@ -71,6 +71,20 @@ func (c *compiler) X86(x asm.Instruction) { case asm.DIV: switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] + + if operands.Register != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Register) + } + + c.code = x86.MoveRegisterNumber(c.code, x86.TMP, operands.Number) + c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.DivRegister(c.code, x86.TMP) + + if operands.Register != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.RAX) + } case asm.TypeRegisterRegister: operands := c.assembler.Param.RegisterRegister[x.Index] @@ -88,6 +102,20 @@ func (c *compiler) X86(x asm.Instruction) { case asm.MODULO: switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] + + if operands.Register != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Register) + } + + c.code = x86.MoveRegisterNumber(c.code, x86.TMP, operands.Number) + c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.DivRegister(c.code, x86.TMP) + + if operands.Register != x86.RDX { + c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.RDX) + } case asm.TypeRegisterRegister: operands := c.assembler.Param.RegisterRegister[x.Index] diff --git a/src/core/ExecuteRegisterNumber.go b/src/core/ExecuteRegisterNumber.go index a3acbb5..bd79684 100644 --- a/src/core/ExecuteRegisterNumber.go +++ b/src/core/ExecuteRegisterNumber.go @@ -2,6 +2,7 @@ package core import ( "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/token" @@ -25,18 +26,18 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg f.RegisterNumber(asm.MUL, register, number) case token.Div, token.DivAssign: - f.SaveRegister(x86.RDX) - tmp := f.NewRegister() - f.RegisterNumber(asm.MOVE, tmp, number) - f.RegisterRegister(asm.DIV, register, tmp) - f.FreeRegister(tmp) + if config.TargetArch == config.X86 { + f.SaveRegister(x86.RDX) + } + + f.RegisterNumber(asm.DIV, register, number) case token.Mod, token.ModAssign: - f.SaveRegister(x86.RDX) - tmp := f.NewRegister() - f.RegisterNumber(asm.MOVE, tmp, number) - f.RegisterRegister(asm.MODULO, register, tmp) - f.FreeRegister(tmp) + if config.TargetArch == config.X86 { + f.SaveRegister(x86.RDX) + } + + f.RegisterNumber(asm.MODULO, register, number) case token.And, token.AndAssign: f.RegisterNumber(asm.AND, register, number) diff --git a/src/core/ExecuteRegisterRegister.go b/src/core/ExecuteRegisterRegister.go index 5b59667..bcb19de 100644 --- a/src/core/ExecuteRegisterRegister.go +++ b/src/core/ExecuteRegisterRegister.go @@ -2,6 +2,7 @@ package core import ( "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/token" @@ -25,11 +26,17 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.R f.RegisterRegister(asm.MUL, register, operand) case token.Div, token.DivAssign: - f.SaveRegister(x86.RDX) + if config.TargetArch == config.X86 { + f.SaveRegister(x86.RDX) + } + f.RegisterRegister(asm.DIV, register, operand) case token.Mod, token.ModAssign: - f.SaveRegister(x86.RDX) + if config.TargetArch == config.X86 { + f.SaveRegister(x86.RDX) + } + f.RegisterRegister(asm.MODULO, register, operand) case token.And, token.AndAssign: diff --git a/src/x86/Registers.go b/src/x86/Registers.go index 793be1c..ab31583 100644 --- a/src/x86/Registers.go +++ b/src/x86/Registers.go @@ -19,12 +19,13 @@ const ( R13 R14 R15 + TMP = RCX ) var ( SyscallInputRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} SyscallOutputRegisters = []cpu.Register{RAX, RCX, R11} - GeneralRegisters = []cpu.Register{RBX, R12, R13, R14, R15, RCX, R11} + GeneralRegisters = []cpu.Register{RBX, R12, R13, R14, R15, R11} InputRegisters = SyscallInputRegisters OutputRegisters = SyscallInputRegisters WindowsInputRegisters = []cpu.Register{RCX, RDX, R8, R9} diff --git a/tests/programs/variables.q b/tests/programs/variables.q index 554f438..b82b974 100644 --- a/tests/programs/variables.q +++ b/tests/programs/variables.q @@ -5,7 +5,6 @@ main() { d := 4 e := 5 f := 6 - g := 7 assert a == 1 assert b == 2 @@ -13,5 +12,4 @@ main() { assert d == 4 assert e == 5 assert f == 6 - assert g == 7 } \ No newline at end of file From 42e7c8a30683ccaa86f31b78b9f329bdb8a8b19c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 31 Mar 2025 15:51:04 +0200 Subject: [PATCH 0976/1012] Implemented division by immediates in the IR --- src/arm/Registers.go | 7 ++++--- src/asmc/ARM.go | 9 +++++++-- src/asmc/X86.go | 28 ++++++++++++++++++++++++++++ src/core/ExecuteRegisterNumber.go | 21 +++++++++++---------- src/core/ExecuteRegisterRegister.go | 11 +++++++++-- src/x86/Registers.go | 3 ++- tests/programs/variables.q | 2 -- 7 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/arm/Registers.go b/src/arm/Registers.go index 6277134..9e54156 100644 --- a/src/arm/Registers.go +++ b/src/arm/Registers.go @@ -38,12 +38,13 @@ const ( ) const ( - ZR = SP // Zero register uses the same numerical value as SP - TMP = X28 // Temporary register for the assembler + ZR = SP // Zero register uses the same numerical value as SP + TMP = X27 // Temporary register for the assembler + TMP2 = X28 // Temporary register for the assembler ) var ( - GeneralRegisters = []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28} + GeneralRegisters = []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X19, X20, X21, X22, X23, X24, X25, X26} InputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5} OutputRegisters = InputRegisters SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} diff --git a/src/asmc/ARM.go b/src/asmc/ARM.go index a4d2347..42ff3a6 100644 --- a/src/asmc/ARM.go +++ b/src/asmc/ARM.go @@ -253,7 +253,9 @@ func (c *compiler) ARM(x asm.Instruction) { operand := c.assembler.Param.RegisterRegister[x.Index] c.append(arm.DivSigned(operand.Destination, operand.Destination, operand.Source)) case asm.TypeRegisterNumber: - panic("not implemented") + operand := c.assembler.Param.RegisterNumber[x.Index] + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.DivSigned(operand.Register, operand.Register, arm.TMP)) } case asm.MUL: @@ -274,7 +276,10 @@ func (c *compiler) ARM(x asm.Instruction) { c.append(arm.DivSigned(arm.TMP, operand.Destination, operand.Source)) c.append(arm.MultiplySubtract(operand.Destination, arm.TMP, operand.Source, operand.Destination)) case asm.TypeRegisterNumber: - panic("not implemented") + operand := c.assembler.Param.RegisterNumber[x.Index] + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.DivSigned(arm.TMP2, operand.Register, arm.TMP)) + c.append(arm.MultiplySubtract(operand.Register, arm.TMP2, arm.TMP, operand.Register)) } case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: diff --git a/src/asmc/X86.go b/src/asmc/X86.go index 448ec41..5cdf60f 100644 --- a/src/asmc/X86.go +++ b/src/asmc/X86.go @@ -71,6 +71,20 @@ func (c *compiler) X86(x asm.Instruction) { case asm.DIV: switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] + + if operands.Register != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Register) + } + + c.code = x86.MoveRegisterNumber(c.code, x86.TMP, operands.Number) + c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.DivRegister(c.code, x86.TMP) + + if operands.Register != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.RAX) + } case asm.TypeRegisterRegister: operands := c.assembler.Param.RegisterRegister[x.Index] @@ -88,6 +102,20 @@ func (c *compiler) X86(x asm.Instruction) { case asm.MODULO: switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] + + if operands.Register != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Register) + } + + c.code = x86.MoveRegisterNumber(c.code, x86.TMP, operands.Number) + c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.DivRegister(c.code, x86.TMP) + + if operands.Register != x86.RDX { + c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.RDX) + } case asm.TypeRegisterRegister: operands := c.assembler.Param.RegisterRegister[x.Index] diff --git a/src/core/ExecuteRegisterNumber.go b/src/core/ExecuteRegisterNumber.go index a3acbb5..bd79684 100644 --- a/src/core/ExecuteRegisterNumber.go +++ b/src/core/ExecuteRegisterNumber.go @@ -2,6 +2,7 @@ package core import ( "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/token" @@ -25,18 +26,18 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg f.RegisterNumber(asm.MUL, register, number) case token.Div, token.DivAssign: - f.SaveRegister(x86.RDX) - tmp := f.NewRegister() - f.RegisterNumber(asm.MOVE, tmp, number) - f.RegisterRegister(asm.DIV, register, tmp) - f.FreeRegister(tmp) + if config.TargetArch == config.X86 { + f.SaveRegister(x86.RDX) + } + + f.RegisterNumber(asm.DIV, register, number) case token.Mod, token.ModAssign: - f.SaveRegister(x86.RDX) - tmp := f.NewRegister() - f.RegisterNumber(asm.MOVE, tmp, number) - f.RegisterRegister(asm.MODULO, register, tmp) - f.FreeRegister(tmp) + if config.TargetArch == config.X86 { + f.SaveRegister(x86.RDX) + } + + f.RegisterNumber(asm.MODULO, register, number) case token.And, token.AndAssign: f.RegisterNumber(asm.AND, register, number) diff --git a/src/core/ExecuteRegisterRegister.go b/src/core/ExecuteRegisterRegister.go index 5b59667..bcb19de 100644 --- a/src/core/ExecuteRegisterRegister.go +++ b/src/core/ExecuteRegisterRegister.go @@ -2,6 +2,7 @@ package core import ( "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/config" "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/token" @@ -25,11 +26,17 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.R f.RegisterRegister(asm.MUL, register, operand) case token.Div, token.DivAssign: - f.SaveRegister(x86.RDX) + if config.TargetArch == config.X86 { + f.SaveRegister(x86.RDX) + } + f.RegisterRegister(asm.DIV, register, operand) case token.Mod, token.ModAssign: - f.SaveRegister(x86.RDX) + if config.TargetArch == config.X86 { + f.SaveRegister(x86.RDX) + } + f.RegisterRegister(asm.MODULO, register, operand) case token.And, token.AndAssign: diff --git a/src/x86/Registers.go b/src/x86/Registers.go index 793be1c..ab31583 100644 --- a/src/x86/Registers.go +++ b/src/x86/Registers.go @@ -19,12 +19,13 @@ const ( R13 R14 R15 + TMP = RCX ) var ( SyscallInputRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} SyscallOutputRegisters = []cpu.Register{RAX, RCX, R11} - GeneralRegisters = []cpu.Register{RBX, R12, R13, R14, R15, RCX, R11} + GeneralRegisters = []cpu.Register{RBX, R12, R13, R14, R15, R11} InputRegisters = SyscallInputRegisters OutputRegisters = SyscallInputRegisters WindowsInputRegisters = []cpu.Register{RCX, RDX, R8, R9} diff --git a/tests/programs/variables.q b/tests/programs/variables.q index 554f438..b82b974 100644 --- a/tests/programs/variables.q +++ b/tests/programs/variables.q @@ -5,7 +5,6 @@ main() { d := 4 e := 5 f := 6 - g := 7 assert a == 1 assert b == 2 @@ -13,5 +12,4 @@ main() { assert d == 4 assert e == 5 assert f == 6 - assert g == 7 } \ No newline at end of file From 863fb7de5aa679d0e8f42775739407b2ea0e1520 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 4 Apr 2025 13:32:55 +0200 Subject: [PATCH 0977/1012] Removed struct keyword --- examples/point/point.q | 2 +- lib/sys/struct_linux.q | 12 ++++----- lib/sys/struct_mac.q | 10 ++++---- src/errors/Common.go | 3 +-- src/scanner/scanFile.go | 25 ++++++++++++++++--- src/scanner/scanFunctionSignature.go | 4 +-- src/scanner/scanStruct.go | 6 ----- src/token/Kind.go | 1 - src/token/Tokenize_test.go | 3 +-- src/token/identifier.go | 2 -- tests/errors/ExpectedStructName.q | 1 - ...nctionParameters.q => InvalidDefinition.q} | 0 tests/errors/UnknownStructField.q | 2 +- tests/errors_test.go | 3 +-- tests/programs/allocator.q | 4 +-- tests/programs/function-pointer-field.q | 2 +- tests/programs/struct-lifetime.q | 2 +- tests/programs/struct.q | 2 +- 18 files changed, 44 insertions(+), 40 deletions(-) delete mode 100644 tests/errors/ExpectedStructName.q rename tests/errors/{ExpectedFunctionParameters.q => InvalidDefinition.q} (100%) diff --git a/examples/point/point.q b/examples/point/point.q index c2a7d5f..de2a763 100644 --- a/examples/point/point.q +++ b/examples/point/point.q @@ -1,7 +1,7 @@ import mem import sys -struct Point { +Point { x int y int } diff --git a/lib/sys/struct_linux.q b/lib/sys/struct_linux.q index 8a22345..adbea0d 100644 --- a/lib/sys/struct_linux.q +++ b/lib/sys/struct_linux.q @@ -1,11 +1,11 @@ -struct sockaddr_in { +sockaddr_in { sin_family int16 - sin_port int16 - sin_addr int64 - sin_zero int64 + sin_port int16 + sin_addr int64 + sin_zero int64 } -struct timespec { - seconds int64 +timespec { + seconds int64 nanoseconds int64 } \ No newline at end of file diff --git a/lib/sys/struct_mac.q b/lib/sys/struct_mac.q index 480199a..9172114 100644 --- a/lib/sys/struct_mac.q +++ b/lib/sys/struct_mac.q @@ -1,7 +1,7 @@ -struct sockaddr_in_bsd { - sin_len int8 +sockaddr_in_bsd { + sin_len int8 sin_family int8 - sin_port int16 - sin_addr int64 - sin_zero int64 + sin_port int16 + sin_addr int64 + sin_zero int64 } \ No newline at end of file diff --git a/src/errors/Common.go b/src/errors/Common.go index f5f4f50..206d4c5 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -3,11 +3,10 @@ package errors var ( EmptySwitch = &Base{"Empty switch"} ExpectedConstName = &Base{"Expected a name for the const group"} - ExpectedFunctionParameters = &Base{"Expected function parameters"} + InvalidDefinition = &Base{"Invalid definition"} ExpectedFunctionDefinition = &Base{"Expected function definition"} ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} ExpectedPackageName = &Base{"Expected package name"} - ExpectedStructName = &Base{"Expected struct name"} ExpectedDLLName = &Base{"Expected DLL name"} InvalidNumber = &Base{"Invalid number"} InvalidCondition = &Base{"Invalid condition"} diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 04bf2b5..0d9d7a4 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -31,12 +31,29 @@ func (s *Scanner) scanFile(path string, pkg string) error { switch tokens[i].Kind { case token.NewLine: case token.Comment: + case token.Identifier: + if i+1 >= len(tokens) { + return errors.New(errors.InvalidDefinition, file, tokens[i].End()) + } + + next := tokens[i+1] + + switch next.Kind { + case token.GroupStart: + i, err = s.scanFunction(file, tokens, i) + case token.BlockStart: + i, err = s.scanStruct(file, tokens, i) + case token.GroupEnd: + return errors.New(errors.MissingGroupStart, file, next.Position) + case token.BlockEnd: + return errors.New(errors.MissingBlockStart, file, next.Position) + case token.Invalid: + return errors.New(&errors.InvalidCharacter{Character: next.Text(file.Bytes)}, file, next.Position) + default: + return errors.New(errors.InvalidDefinition, file, next.Position) + } case token.Import: i, err = s.scanImport(file, tokens, i) - case token.Struct: - i, err = s.scanStruct(file, tokens, i) - case token.Identifier: - i, err = s.scanFunction(file, tokens, i) case token.Extern: i, err = s.scanExtern(file, tokens, i) case token.Const: diff --git a/src/scanner/scanFunctionSignature.go b/src/scanner/scanFunctionSignature.go index 6cd5b17..1833c61 100644 --- a/src/scanner/scanFunctionSignature.go +++ b/src/scanner/scanFunctionSignature.go @@ -60,7 +60,7 @@ func scanFunctionSignature(file *fs.File, tokens token.List, i int, delimiter to } if paramsStart == -1 { - return nil, i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) + return nil, i, errors.New(errors.InvalidDefinition, file, tokens[i].Position) } return nil, i, nil @@ -71,7 +71,7 @@ func scanFunctionSignature(file *fs.File, tokens token.List, i int, delimiter to continue } - return nil, i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) + return nil, i, errors.New(errors.InvalidDefinition, file, tokens[i].Position) } // Return type diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index 0de75cf..dc70838 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -9,12 +9,6 @@ import ( // scanStruct scans a struct. func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, error) { - i++ - - if tokens[i].Kind != token.Identifier { - return i, errors.New(errors.ExpectedStructName, file, tokens[i].Position) - } - structName := tokens[i].Text(file.Bytes) structure := types.NewStruct(file.Package, structName) diff --git a/src/token/Kind.go b/src/token/Kind.go index 881bd18..12d7362 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -74,7 +74,6 @@ const ( Import // import Loop // loop Return // return - Struct // struct Switch // switch ___END_KEYWORDS___ // ) diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index 19ebf16..ce78895 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -25,7 +25,7 @@ func TestFunction(t *testing.T) { } func TestKeyword(t *testing.T) { - tokens := token.Tokenize([]byte("assert const else extern if import for loop return struct switch")) + tokens := token.Tokenize([]byte("assert const else extern if import for loop return switch")) expected := []token.Kind{ token.Assert, @@ -37,7 +37,6 @@ func TestKeyword(t *testing.T) { token.For, token.Loop, token.Return, - token.Struct, token.Switch, token.EOF, } diff --git a/src/token/identifier.go b/src/token/identifier.go index 27849ed..db48049 100644 --- a/src/token/identifier.go +++ b/src/token/identifier.go @@ -31,8 +31,6 @@ func identifier(tokens List, buffer []byte, i Position) (List, Position) { kind = Loop case "return": kind = Return - case "struct": - kind = Struct case "switch": kind = Switch } diff --git a/tests/errors/ExpectedStructName.q b/tests/errors/ExpectedStructName.q deleted file mode 100644 index 6c8a4ee..0000000 --- a/tests/errors/ExpectedStructName.q +++ /dev/null @@ -1 +0,0 @@ -struct{} \ No newline at end of file diff --git a/tests/errors/ExpectedFunctionParameters.q b/tests/errors/InvalidDefinition.q similarity index 100% rename from tests/errors/ExpectedFunctionParameters.q rename to tests/errors/InvalidDefinition.q diff --git a/tests/errors/UnknownStructField.q b/tests/errors/UnknownStructField.q index bea43e5..aefe455 100644 --- a/tests/errors/UnknownStructField.q +++ b/tests/errors/UnknownStructField.q @@ -1,4 +1,4 @@ -struct A {} +A {} main() { a := new(A) diff --git a/tests/errors_test.go b/tests/errors_test.go index 9403bfc..8165e97 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -17,12 +17,11 @@ var errs = []struct { {"EmptySwitch.q", errors.EmptySwitch}, {"ExpectedDLLName.q", errors.ExpectedDLLName}, {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, - {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse}, {"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse}, - {"ExpectedStructName.q", errors.ExpectedStructName}, {"ExpectedPackageName.q", errors.ExpectedPackageName}, {"InvalidCondition.q", errors.InvalidCondition}, + {"InvalidDefinition.q", errors.InvalidDefinition}, {"InvalidInstructionCall.q", &errors.InvalidInstruction{Instruction: "sys.write"}}, {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "2+3"}}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, diff --git a/tests/programs/allocator.q b/tests/programs/allocator.q index 4b39208..ba9c959 100644 --- a/tests/programs/allocator.q +++ b/tests/programs/allocator.q @@ -24,12 +24,12 @@ main() { assert b.y == 4 } -struct Allocator { +Allocator { block *any current int } -struct Point { +Point { x int y int } \ No newline at end of file diff --git a/tests/programs/function-pointer-field.q b/tests/programs/function-pointer-field.q index 1484842..f37d9c9 100644 --- a/tests/programs/function-pointer-field.q +++ b/tests/programs/function-pointer-field.q @@ -1,6 +1,6 @@ import sys -struct Struct { +Struct { func *any } diff --git a/tests/programs/struct-lifetime.q b/tests/programs/struct-lifetime.q index a32f469..920ffc4 100644 --- a/tests/programs/struct-lifetime.q +++ b/tests/programs/struct-lifetime.q @@ -3,6 +3,6 @@ main() { c.value += 16 } -struct Counter { +Counter { value int } \ No newline at end of file diff --git a/tests/programs/struct.q b/tests/programs/struct.q index dd23388..15f096e 100644 --- a/tests/programs/struct.q +++ b/tests/programs/struct.q @@ -1,4 +1,4 @@ -struct Point { +Point { x int y int } From 1a2c21de15be4b00c5c6f453639e7c89d60af5dd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 4 Apr 2025 13:32:55 +0200 Subject: [PATCH 0978/1012] Removed struct keyword --- examples/point/point.q | 2 +- lib/sys/struct_linux.q | 12 ++++----- lib/sys/struct_mac.q | 10 ++++---- src/errors/Common.go | 3 +-- src/scanner/scanFile.go | 25 ++++++++++++++++--- src/scanner/scanFunctionSignature.go | 4 +-- src/scanner/scanStruct.go | 6 ----- src/token/Kind.go | 1 - src/token/Tokenize_test.go | 3 +-- src/token/identifier.go | 2 -- tests/errors/ExpectedStructName.q | 1 - ...nctionParameters.q => InvalidDefinition.q} | 0 tests/errors/UnknownStructField.q | 2 +- tests/errors_test.go | 3 +-- tests/programs/allocator.q | 4 +-- tests/programs/function-pointer-field.q | 2 +- tests/programs/struct-lifetime.q | 2 +- tests/programs/struct.q | 2 +- 18 files changed, 44 insertions(+), 40 deletions(-) delete mode 100644 tests/errors/ExpectedStructName.q rename tests/errors/{ExpectedFunctionParameters.q => InvalidDefinition.q} (100%) diff --git a/examples/point/point.q b/examples/point/point.q index c2a7d5f..de2a763 100644 --- a/examples/point/point.q +++ b/examples/point/point.q @@ -1,7 +1,7 @@ import mem import sys -struct Point { +Point { x int y int } diff --git a/lib/sys/struct_linux.q b/lib/sys/struct_linux.q index 8a22345..adbea0d 100644 --- a/lib/sys/struct_linux.q +++ b/lib/sys/struct_linux.q @@ -1,11 +1,11 @@ -struct sockaddr_in { +sockaddr_in { sin_family int16 - sin_port int16 - sin_addr int64 - sin_zero int64 + sin_port int16 + sin_addr int64 + sin_zero int64 } -struct timespec { - seconds int64 +timespec { + seconds int64 nanoseconds int64 } \ No newline at end of file diff --git a/lib/sys/struct_mac.q b/lib/sys/struct_mac.q index 480199a..9172114 100644 --- a/lib/sys/struct_mac.q +++ b/lib/sys/struct_mac.q @@ -1,7 +1,7 @@ -struct sockaddr_in_bsd { - sin_len int8 +sockaddr_in_bsd { + sin_len int8 sin_family int8 - sin_port int16 - sin_addr int64 - sin_zero int64 + sin_port int16 + sin_addr int64 + sin_zero int64 } \ No newline at end of file diff --git a/src/errors/Common.go b/src/errors/Common.go index f5f4f50..206d4c5 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -3,11 +3,10 @@ package errors var ( EmptySwitch = &Base{"Empty switch"} ExpectedConstName = &Base{"Expected a name for the const group"} - ExpectedFunctionParameters = &Base{"Expected function parameters"} + InvalidDefinition = &Base{"Invalid definition"} ExpectedFunctionDefinition = &Base{"Expected function definition"} ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} ExpectedPackageName = &Base{"Expected package name"} - ExpectedStructName = &Base{"Expected struct name"} ExpectedDLLName = &Base{"Expected DLL name"} InvalidNumber = &Base{"Invalid number"} InvalidCondition = &Base{"Invalid condition"} diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 04bf2b5..0d9d7a4 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -31,12 +31,29 @@ func (s *Scanner) scanFile(path string, pkg string) error { switch tokens[i].Kind { case token.NewLine: case token.Comment: + case token.Identifier: + if i+1 >= len(tokens) { + return errors.New(errors.InvalidDefinition, file, tokens[i].End()) + } + + next := tokens[i+1] + + switch next.Kind { + case token.GroupStart: + i, err = s.scanFunction(file, tokens, i) + case token.BlockStart: + i, err = s.scanStruct(file, tokens, i) + case token.GroupEnd: + return errors.New(errors.MissingGroupStart, file, next.Position) + case token.BlockEnd: + return errors.New(errors.MissingBlockStart, file, next.Position) + case token.Invalid: + return errors.New(&errors.InvalidCharacter{Character: next.Text(file.Bytes)}, file, next.Position) + default: + return errors.New(errors.InvalidDefinition, file, next.Position) + } case token.Import: i, err = s.scanImport(file, tokens, i) - case token.Struct: - i, err = s.scanStruct(file, tokens, i) - case token.Identifier: - i, err = s.scanFunction(file, tokens, i) case token.Extern: i, err = s.scanExtern(file, tokens, i) case token.Const: diff --git a/src/scanner/scanFunctionSignature.go b/src/scanner/scanFunctionSignature.go index 6cd5b17..1833c61 100644 --- a/src/scanner/scanFunctionSignature.go +++ b/src/scanner/scanFunctionSignature.go @@ -60,7 +60,7 @@ func scanFunctionSignature(file *fs.File, tokens token.List, i int, delimiter to } if paramsStart == -1 { - return nil, i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) + return nil, i, errors.New(errors.InvalidDefinition, file, tokens[i].Position) } return nil, i, nil @@ -71,7 +71,7 @@ func scanFunctionSignature(file *fs.File, tokens token.List, i int, delimiter to continue } - return nil, i, errors.New(errors.ExpectedFunctionParameters, file, tokens[i].Position) + return nil, i, errors.New(errors.InvalidDefinition, file, tokens[i].Position) } // Return type diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index 0de75cf..dc70838 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -9,12 +9,6 @@ import ( // scanStruct scans a struct. func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, error) { - i++ - - if tokens[i].Kind != token.Identifier { - return i, errors.New(errors.ExpectedStructName, file, tokens[i].Position) - } - structName := tokens[i].Text(file.Bytes) structure := types.NewStruct(file.Package, structName) diff --git a/src/token/Kind.go b/src/token/Kind.go index 881bd18..12d7362 100644 --- a/src/token/Kind.go +++ b/src/token/Kind.go @@ -74,7 +74,6 @@ const ( Import // import Loop // loop Return // return - Struct // struct Switch // switch ___END_KEYWORDS___ // ) diff --git a/src/token/Tokenize_test.go b/src/token/Tokenize_test.go index 19ebf16..ce78895 100644 --- a/src/token/Tokenize_test.go +++ b/src/token/Tokenize_test.go @@ -25,7 +25,7 @@ func TestFunction(t *testing.T) { } func TestKeyword(t *testing.T) { - tokens := token.Tokenize([]byte("assert const else extern if import for loop return struct switch")) + tokens := token.Tokenize([]byte("assert const else extern if import for loop return switch")) expected := []token.Kind{ token.Assert, @@ -37,7 +37,6 @@ func TestKeyword(t *testing.T) { token.For, token.Loop, token.Return, - token.Struct, token.Switch, token.EOF, } diff --git a/src/token/identifier.go b/src/token/identifier.go index 27849ed..db48049 100644 --- a/src/token/identifier.go +++ b/src/token/identifier.go @@ -31,8 +31,6 @@ func identifier(tokens List, buffer []byte, i Position) (List, Position) { kind = Loop case "return": kind = Return - case "struct": - kind = Struct case "switch": kind = Switch } diff --git a/tests/errors/ExpectedStructName.q b/tests/errors/ExpectedStructName.q deleted file mode 100644 index 6c8a4ee..0000000 --- a/tests/errors/ExpectedStructName.q +++ /dev/null @@ -1 +0,0 @@ -struct{} \ No newline at end of file diff --git a/tests/errors/ExpectedFunctionParameters.q b/tests/errors/InvalidDefinition.q similarity index 100% rename from tests/errors/ExpectedFunctionParameters.q rename to tests/errors/InvalidDefinition.q diff --git a/tests/errors/UnknownStructField.q b/tests/errors/UnknownStructField.q index bea43e5..aefe455 100644 --- a/tests/errors/UnknownStructField.q +++ b/tests/errors/UnknownStructField.q @@ -1,4 +1,4 @@ -struct A {} +A {} main() { a := new(A) diff --git a/tests/errors_test.go b/tests/errors_test.go index 9403bfc..8165e97 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -17,12 +17,11 @@ var errs = []struct { {"EmptySwitch.q", errors.EmptySwitch}, {"ExpectedDLLName.q", errors.ExpectedDLLName}, {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, - {"ExpectedFunctionParameters.q", errors.ExpectedFunctionParameters}, {"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse}, {"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse}, - {"ExpectedStructName.q", errors.ExpectedStructName}, {"ExpectedPackageName.q", errors.ExpectedPackageName}, {"InvalidCondition.q", errors.InvalidCondition}, + {"InvalidDefinition.q", errors.InvalidDefinition}, {"InvalidInstructionCall.q", &errors.InvalidInstruction{Instruction: "sys.write"}}, {"InvalidInstructionExpression.q", &errors.InvalidInstruction{Instruction: "2+3"}}, {"InvalidInstructionIdentifier.q", &errors.InvalidInstruction{Instruction: "abc"}}, diff --git a/tests/programs/allocator.q b/tests/programs/allocator.q index 4b39208..ba9c959 100644 --- a/tests/programs/allocator.q +++ b/tests/programs/allocator.q @@ -24,12 +24,12 @@ main() { assert b.y == 4 } -struct Allocator { +Allocator { block *any current int } -struct Point { +Point { x int y int } \ No newline at end of file diff --git a/tests/programs/function-pointer-field.q b/tests/programs/function-pointer-field.q index 1484842..f37d9c9 100644 --- a/tests/programs/function-pointer-field.q +++ b/tests/programs/function-pointer-field.q @@ -1,6 +1,6 @@ import sys -struct Struct { +Struct { func *any } diff --git a/tests/programs/struct-lifetime.q b/tests/programs/struct-lifetime.q index a32f469..920ffc4 100644 --- a/tests/programs/struct-lifetime.q +++ b/tests/programs/struct-lifetime.q @@ -3,6 +3,6 @@ main() { c.value += 16 } -struct Counter { +Counter { value int } \ No newline at end of file diff --git a/tests/programs/struct.q b/tests/programs/struct.q index dd23388..15f096e 100644 --- a/tests/programs/struct.q +++ b/tests/programs/struct.q @@ -1,4 +1,4 @@ -struct Point { +Point { x int y int } From ba34637dc108a34d85d33f60c59c3e3257013481 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 4 Apr 2025 20:15:31 +0200 Subject: [PATCH 0979/1012] Moved const and extern identifiers to the left --- examples/shell/const.q | 4 ++-- examples/winapi/winapi.q | 2 +- lib/core/core_windows.q | 20 ++++++++++---------- lib/io/io.q | 18 ++++++++++++++++++ lib/io/std.q | 19 ------------------- lib/mem/alloc_windows.q | 2 +- lib/mem/const_linux.q | 8 ++++---- lib/mem/const_mac.q | 8 ++++---- lib/mem/const_windows.q | 8 ++++---- lib/mem/free_windows.q | 2 +- lib/sys/sys_windows.q | 2 +- lib/thread/thread_linux.q | 20 ++++++++++---------- lib/thread/thread_windows.q | 8 ++++---- src/errors/Common.go | 2 -- src/scanner/scanConst.go | 8 +------- src/scanner/scanExtern.go | 8 +------- src/scanner/scanFile.go | 8 ++++---- tests/errors/ExpectedDLLName.q | 1 - tests/errors_test.go | 1 - tests/programs/const.q | 6 +++--- 20 files changed, 69 insertions(+), 86 deletions(-) delete mode 100644 lib/io/std.q delete mode 100644 tests/errors/ExpectedDLLName.q diff --git a/examples/shell/const.q b/examples/shell/const.q index 4f7eed7..9dadaf2 100644 --- a/examples/shell/const.q +++ b/examples/shell/const.q @@ -1,7 +1,7 @@ -const idtype { +idtype const { pid 1 } -const state { +state const { exited 0x4 } \ No newline at end of file diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q index 213f1ed..702e0cb 100644 --- a/examples/winapi/winapi.q +++ b/examples/winapi/winapi.q @@ -1,4 +1,4 @@ -extern user32 { +user32 extern { MessageBoxA(window *any, text *byte, title *byte, flags uint) -> int } diff --git a/lib/core/core_windows.q b/lib/core/core_windows.q index 516794a..abb6428 100644 --- a/lib/core/core_windows.q +++ b/lib/core/core_windows.q @@ -1,3 +1,13 @@ +cp const { + utf8 65001 +} + +kernel32 extern { + SetConsoleCP(cp uint) + SetConsoleOutputCP(cp uint) + ExitProcess(code uint) +} + init() { kernel32.SetConsoleCP(cp.utf8) kernel32.SetConsoleOutputCP(cp.utf8) @@ -11,14 +21,4 @@ exit() { crash() { kernel32.ExitProcess(1) -} - -const cp { - utf8 65001 -} - -extern kernel32 { - SetConsoleCP(cp uint) - SetConsoleOutputCP(cp uint) - ExitProcess(code uint) } \ No newline at end of file diff --git a/lib/io/io.q b/lib/io/io.q index 760c9db..e05f914 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -1,5 +1,23 @@ import sys +std const { + in 0 + out 1 + err 2 +} + +in(buffer []byte) -> int { + return sys.read(std.in, buffer, len(buffer)) +} + +out(buffer []byte) -> int { + return sys.write(std.out, buffer, len(buffer)) +} + +error(buffer []byte) -> int { + return sys.write(std.err, buffer, len(buffer)) +} + read(fd int, buffer []byte) -> int { return sys.read(fd, buffer, len(buffer)) } diff --git a/lib/io/std.q b/lib/io/std.q deleted file mode 100644 index c74cb52..0000000 --- a/lib/io/std.q +++ /dev/null @@ -1,19 +0,0 @@ -import sys - -in(buffer []byte) -> int { - return sys.read(std.in, buffer, len(buffer)) -} - -out(buffer []byte) -> int { - return sys.write(std.out, buffer, len(buffer)) -} - -error(buffer []byte) -> int { - return sys.write(std.err, buffer, len(buffer)) -} - -const std { - in 0 - out 1 - err 2 -} \ No newline at end of file diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index 3eda616..2b0c016 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,4 +1,4 @@ -extern kernel32 { +kernel32 extern { VirtualAlloc(address int, size uint, flags uint32, protection uint32) -> *any } diff --git a/lib/mem/const_linux.q b/lib/mem/const_linux.q index 9193a01..72dd4f4 100644 --- a/lib/mem/const_linux.q +++ b/lib/mem/const_linux.q @@ -1,9 +1,9 @@ -const prot { - read 0x1 +prot const { + read 0x1 write 0x2 } -const map { - private 0x02 +map const { + private 0x02 anonymous 0x20 } \ No newline at end of file diff --git a/lib/mem/const_mac.q b/lib/mem/const_mac.q index 7aac429..3f654cc 100644 --- a/lib/mem/const_mac.q +++ b/lib/mem/const_mac.q @@ -1,9 +1,9 @@ -const prot { - read 0x1 +prot const { + read 0x1 write 0x2 } -const map { - private 0x02 +map const { + private 0x02 anonymous 0x1000 } \ No newline at end of file diff --git a/lib/mem/const_windows.q b/lib/mem/const_windows.q index 7008e67..acc7f55 100644 --- a/lib/mem/const_windows.q +++ b/lib/mem/const_windows.q @@ -1,9 +1,9 @@ -const page { +page const { readwrite 0x0004 } -const mem { - commit 0x1000 - reserve 0x2000 +mem const { + commit 0x1000 + reserve 0x2000 decommit 0x4000 } \ No newline at end of file diff --git a/lib/mem/free_windows.q b/lib/mem/free_windows.q index 3cf8a91..d9bd818 100644 --- a/lib/mem/free_windows.q +++ b/lib/mem/free_windows.q @@ -1,4 +1,4 @@ -extern kernel32 { +kernel32 extern { VirtualFree(address *any, size uint, type uint32) -> bool } diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q index 59e6769..d3d3634 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/sys_windows.q @@ -10,7 +10,7 @@ write(fd int, buffer *byte, length int) -> int { return length } -extern kernel32 { +kernel32 extern { GetStdHandle(handle int64) -> int64 ReadConsole(fd int64, buffer *byte, length uint32, written *uint32) -> bool WriteConsoleA(fd int64, buffer *byte, length uint32, written *uint32) -> bool diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index 7d3429c..7652537 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -1,6 +1,16 @@ import core import sys +clone const { + vm 0x100 + fs 0x200 + files 0x400 + sighand 0x800 + parent 0x8000 + thread 0x10000 + io 0x80000000 +} + create(func *any) -> int { stack := sys.mmap(0, 4096, 0x1|0x2, 0x02|0x20|0x100) stack += 4096 - 8 @@ -8,14 +18,4 @@ create(func *any) -> int { stack -= 8 store(stack, 8, func) return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0) -} - -const clone { - vm 0x100 - fs 0x200 - files 0x400 - sighand 0x800 - parent 0x8000 - thread 0x10000 - io 0x80000000 } \ No newline at end of file diff --git a/lib/thread/thread_windows.q b/lib/thread/thread_windows.q index 7bb565f..dd5136b 100644 --- a/lib/thread/thread_windows.q +++ b/lib/thread/thread_windows.q @@ -1,7 +1,7 @@ -create(func *any) -> int { - return kernel32.CreateThread(0, 4096, func, 0) +kernel32 extern { + CreateThread(attributes int, stackSize int, address *any, parameter int) -> int } -extern kernel32 { - CreateThread(attributes int, stackSize int, address *any, parameter int) -> int +create(func *any) -> int { + return kernel32.CreateThread(0, 4096, func, 0) } \ No newline at end of file diff --git a/src/errors/Common.go b/src/errors/Common.go index 206d4c5..93097ee 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -2,12 +2,10 @@ package errors var ( EmptySwitch = &Base{"Empty switch"} - ExpectedConstName = &Base{"Expected a name for the const group"} InvalidDefinition = &Base{"Invalid definition"} ExpectedFunctionDefinition = &Base{"Expected function definition"} ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} ExpectedPackageName = &Base{"Expected package name"} - ExpectedDLLName = &Base{"Expected DLL name"} InvalidNumber = &Base{"Invalid number"} InvalidCondition = &Base{"Invalid condition"} InvalidExpression = &Base{"Invalid expression"} diff --git a/src/scanner/scanConst.go b/src/scanner/scanConst.go index f3223f1..5a83b0a 100644 --- a/src/scanner/scanConst.go +++ b/src/scanner/scanConst.go @@ -9,14 +9,8 @@ import ( // scanConst scans a block of constants. func (s *Scanner) scanConst(file *fs.File, tokens token.List, i int) (int, error) { - i++ - - if tokens[i].Kind != token.Identifier { - return i, errors.New(errors.ExpectedConstName, file, tokens[i].Position) - } - groupName := tokens[i].Text(file.Bytes) - i++ + i += 2 if tokens[i].Kind != token.BlockStart { return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) diff --git a/src/scanner/scanExtern.go b/src/scanner/scanExtern.go index 82f3b85..140c4ae 100644 --- a/src/scanner/scanExtern.go +++ b/src/scanner/scanExtern.go @@ -8,14 +8,8 @@ import ( // scanExtern scans a block of external function declarations. func (s *Scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, error) { - i++ - - if tokens[i].Kind != token.Identifier { - return i, errors.New(errors.ExpectedDLLName, file, tokens[i].Position) - } - dllName := tokens[i].Text(file.Bytes) - i++ + i += 2 if tokens[i].Kind != token.BlockStart { return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 0d9d7a4..234dd6e 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -43,6 +43,10 @@ func (s *Scanner) scanFile(path string, pkg string) error { i, err = s.scanFunction(file, tokens, i) case token.BlockStart: i, err = s.scanStruct(file, tokens, i) + case token.Extern: + i, err = s.scanExtern(file, tokens, i) + case token.Const: + i, err = s.scanConst(file, tokens, i) case token.GroupEnd: return errors.New(errors.MissingGroupStart, file, next.Position) case token.BlockEnd: @@ -54,10 +58,6 @@ func (s *Scanner) scanFile(path string, pkg string) error { } case token.Import: i, err = s.scanImport(file, tokens, i) - case token.Extern: - i, err = s.scanExtern(file, tokens, i) - case token.Const: - i, err = s.scanConst(file, tokens, i) case token.EOF: return nil case token.Invalid: diff --git a/tests/errors/ExpectedDLLName.q b/tests/errors/ExpectedDLLName.q deleted file mode 100644 index de11df4..0000000 --- a/tests/errors/ExpectedDLLName.q +++ /dev/null @@ -1 +0,0 @@ -extern {} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 8165e97..8880905 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -15,7 +15,6 @@ var errs = []struct { ExpectedError error }{ {"EmptySwitch.q", errors.EmptySwitch}, - {"ExpectedDLLName.q", errors.ExpectedDLLName}, {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, {"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse}, {"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse}, diff --git a/tests/programs/const.q b/tests/programs/const.q index c947f1a..5ce99b5 100644 --- a/tests/programs/const.q +++ b/tests/programs/const.q @@ -1,6 +1,6 @@ -const num { - one 1 - two 2 +num const { + one 1 + two 2 three 3 } From 4c90b6ea6af25596d421365d82ad7804f512426b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 4 Apr 2025 20:15:31 +0200 Subject: [PATCH 0980/1012] Moved const and extern identifiers to the left --- examples/shell/const.q | 4 ++-- examples/winapi/winapi.q | 2 +- lib/core/core_windows.q | 20 ++++++++++---------- lib/io/io.q | 18 ++++++++++++++++++ lib/io/std.q | 19 ------------------- lib/mem/alloc_windows.q | 2 +- lib/mem/const_linux.q | 8 ++++---- lib/mem/const_mac.q | 8 ++++---- lib/mem/const_windows.q | 8 ++++---- lib/mem/free_windows.q | 2 +- lib/sys/sys_windows.q | 2 +- lib/thread/thread_linux.q | 20 ++++++++++---------- lib/thread/thread_windows.q | 8 ++++---- src/errors/Common.go | 2 -- src/scanner/scanConst.go | 8 +------- src/scanner/scanExtern.go | 8 +------- src/scanner/scanFile.go | 8 ++++---- tests/errors/ExpectedDLLName.q | 1 - tests/errors_test.go | 1 - tests/programs/const.q | 6 +++--- 20 files changed, 69 insertions(+), 86 deletions(-) delete mode 100644 lib/io/std.q delete mode 100644 tests/errors/ExpectedDLLName.q diff --git a/examples/shell/const.q b/examples/shell/const.q index 4f7eed7..9dadaf2 100644 --- a/examples/shell/const.q +++ b/examples/shell/const.q @@ -1,7 +1,7 @@ -const idtype { +idtype const { pid 1 } -const state { +state const { exited 0x4 } \ No newline at end of file diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q index 213f1ed..702e0cb 100644 --- a/examples/winapi/winapi.q +++ b/examples/winapi/winapi.q @@ -1,4 +1,4 @@ -extern user32 { +user32 extern { MessageBoxA(window *any, text *byte, title *byte, flags uint) -> int } diff --git a/lib/core/core_windows.q b/lib/core/core_windows.q index 516794a..abb6428 100644 --- a/lib/core/core_windows.q +++ b/lib/core/core_windows.q @@ -1,3 +1,13 @@ +cp const { + utf8 65001 +} + +kernel32 extern { + SetConsoleCP(cp uint) + SetConsoleOutputCP(cp uint) + ExitProcess(code uint) +} + init() { kernel32.SetConsoleCP(cp.utf8) kernel32.SetConsoleOutputCP(cp.utf8) @@ -11,14 +21,4 @@ exit() { crash() { kernel32.ExitProcess(1) -} - -const cp { - utf8 65001 -} - -extern kernel32 { - SetConsoleCP(cp uint) - SetConsoleOutputCP(cp uint) - ExitProcess(code uint) } \ No newline at end of file diff --git a/lib/io/io.q b/lib/io/io.q index 760c9db..e05f914 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -1,5 +1,23 @@ import sys +std const { + in 0 + out 1 + err 2 +} + +in(buffer []byte) -> int { + return sys.read(std.in, buffer, len(buffer)) +} + +out(buffer []byte) -> int { + return sys.write(std.out, buffer, len(buffer)) +} + +error(buffer []byte) -> int { + return sys.write(std.err, buffer, len(buffer)) +} + read(fd int, buffer []byte) -> int { return sys.read(fd, buffer, len(buffer)) } diff --git a/lib/io/std.q b/lib/io/std.q deleted file mode 100644 index c74cb52..0000000 --- a/lib/io/std.q +++ /dev/null @@ -1,19 +0,0 @@ -import sys - -in(buffer []byte) -> int { - return sys.read(std.in, buffer, len(buffer)) -} - -out(buffer []byte) -> int { - return sys.write(std.out, buffer, len(buffer)) -} - -error(buffer []byte) -> int { - return sys.write(std.err, buffer, len(buffer)) -} - -const std { - in 0 - out 1 - err 2 -} \ No newline at end of file diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index 3eda616..2b0c016 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,4 +1,4 @@ -extern kernel32 { +kernel32 extern { VirtualAlloc(address int, size uint, flags uint32, protection uint32) -> *any } diff --git a/lib/mem/const_linux.q b/lib/mem/const_linux.q index 9193a01..72dd4f4 100644 --- a/lib/mem/const_linux.q +++ b/lib/mem/const_linux.q @@ -1,9 +1,9 @@ -const prot { - read 0x1 +prot const { + read 0x1 write 0x2 } -const map { - private 0x02 +map const { + private 0x02 anonymous 0x20 } \ No newline at end of file diff --git a/lib/mem/const_mac.q b/lib/mem/const_mac.q index 7aac429..3f654cc 100644 --- a/lib/mem/const_mac.q +++ b/lib/mem/const_mac.q @@ -1,9 +1,9 @@ -const prot { - read 0x1 +prot const { + read 0x1 write 0x2 } -const map { - private 0x02 +map const { + private 0x02 anonymous 0x1000 } \ No newline at end of file diff --git a/lib/mem/const_windows.q b/lib/mem/const_windows.q index 7008e67..acc7f55 100644 --- a/lib/mem/const_windows.q +++ b/lib/mem/const_windows.q @@ -1,9 +1,9 @@ -const page { +page const { readwrite 0x0004 } -const mem { - commit 0x1000 - reserve 0x2000 +mem const { + commit 0x1000 + reserve 0x2000 decommit 0x4000 } \ No newline at end of file diff --git a/lib/mem/free_windows.q b/lib/mem/free_windows.q index 3cf8a91..d9bd818 100644 --- a/lib/mem/free_windows.q +++ b/lib/mem/free_windows.q @@ -1,4 +1,4 @@ -extern kernel32 { +kernel32 extern { VirtualFree(address *any, size uint, type uint32) -> bool } diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q index 59e6769..d3d3634 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/sys_windows.q @@ -10,7 +10,7 @@ write(fd int, buffer *byte, length int) -> int { return length } -extern kernel32 { +kernel32 extern { GetStdHandle(handle int64) -> int64 ReadConsole(fd int64, buffer *byte, length uint32, written *uint32) -> bool WriteConsoleA(fd int64, buffer *byte, length uint32, written *uint32) -> bool diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index 7d3429c..7652537 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -1,6 +1,16 @@ import core import sys +clone const { + vm 0x100 + fs 0x200 + files 0x400 + sighand 0x800 + parent 0x8000 + thread 0x10000 + io 0x80000000 +} + create(func *any) -> int { stack := sys.mmap(0, 4096, 0x1|0x2, 0x02|0x20|0x100) stack += 4096 - 8 @@ -8,14 +18,4 @@ create(func *any) -> int { stack -= 8 store(stack, 8, func) return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0) -} - -const clone { - vm 0x100 - fs 0x200 - files 0x400 - sighand 0x800 - parent 0x8000 - thread 0x10000 - io 0x80000000 } \ No newline at end of file diff --git a/lib/thread/thread_windows.q b/lib/thread/thread_windows.q index 7bb565f..dd5136b 100644 --- a/lib/thread/thread_windows.q +++ b/lib/thread/thread_windows.q @@ -1,7 +1,7 @@ -create(func *any) -> int { - return kernel32.CreateThread(0, 4096, func, 0) +kernel32 extern { + CreateThread(attributes int, stackSize int, address *any, parameter int) -> int } -extern kernel32 { - CreateThread(attributes int, stackSize int, address *any, parameter int) -> int +create(func *any) -> int { + return kernel32.CreateThread(0, 4096, func, 0) } \ No newline at end of file diff --git a/src/errors/Common.go b/src/errors/Common.go index 206d4c5..93097ee 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -2,12 +2,10 @@ package errors var ( EmptySwitch = &Base{"Empty switch"} - ExpectedConstName = &Base{"Expected a name for the const group"} InvalidDefinition = &Base{"Invalid definition"} ExpectedFunctionDefinition = &Base{"Expected function definition"} ExpectedIfBeforeElse = &Base{"Expected an 'if' block before 'else'"} ExpectedPackageName = &Base{"Expected package name"} - ExpectedDLLName = &Base{"Expected DLL name"} InvalidNumber = &Base{"Invalid number"} InvalidCondition = &Base{"Invalid condition"} InvalidExpression = &Base{"Invalid expression"} diff --git a/src/scanner/scanConst.go b/src/scanner/scanConst.go index f3223f1..5a83b0a 100644 --- a/src/scanner/scanConst.go +++ b/src/scanner/scanConst.go @@ -9,14 +9,8 @@ import ( // scanConst scans a block of constants. func (s *Scanner) scanConst(file *fs.File, tokens token.List, i int) (int, error) { - i++ - - if tokens[i].Kind != token.Identifier { - return i, errors.New(errors.ExpectedConstName, file, tokens[i].Position) - } - groupName := tokens[i].Text(file.Bytes) - i++ + i += 2 if tokens[i].Kind != token.BlockStart { return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) diff --git a/src/scanner/scanExtern.go b/src/scanner/scanExtern.go index 82f3b85..140c4ae 100644 --- a/src/scanner/scanExtern.go +++ b/src/scanner/scanExtern.go @@ -8,14 +8,8 @@ import ( // scanExtern scans a block of external function declarations. func (s *Scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, error) { - i++ - - if tokens[i].Kind != token.Identifier { - return i, errors.New(errors.ExpectedDLLName, file, tokens[i].Position) - } - dllName := tokens[i].Text(file.Bytes) - i++ + i += 2 if tokens[i].Kind != token.BlockStart { return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 0d9d7a4..234dd6e 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -43,6 +43,10 @@ func (s *Scanner) scanFile(path string, pkg string) error { i, err = s.scanFunction(file, tokens, i) case token.BlockStart: i, err = s.scanStruct(file, tokens, i) + case token.Extern: + i, err = s.scanExtern(file, tokens, i) + case token.Const: + i, err = s.scanConst(file, tokens, i) case token.GroupEnd: return errors.New(errors.MissingGroupStart, file, next.Position) case token.BlockEnd: @@ -54,10 +58,6 @@ func (s *Scanner) scanFile(path string, pkg string) error { } case token.Import: i, err = s.scanImport(file, tokens, i) - case token.Extern: - i, err = s.scanExtern(file, tokens, i) - case token.Const: - i, err = s.scanConst(file, tokens, i) case token.EOF: return nil case token.Invalid: diff --git a/tests/errors/ExpectedDLLName.q b/tests/errors/ExpectedDLLName.q deleted file mode 100644 index de11df4..0000000 --- a/tests/errors/ExpectedDLLName.q +++ /dev/null @@ -1 +0,0 @@ -extern {} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 8165e97..8880905 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -15,7 +15,6 @@ var errs = []struct { ExpectedError error }{ {"EmptySwitch.q", errors.EmptySwitch}, - {"ExpectedDLLName.q", errors.ExpectedDLLName}, {"ExpectedFunctionDefinition.q", errors.ExpectedFunctionDefinition}, {"ExpectedIfBeforeElse.q", errors.ExpectedIfBeforeElse}, {"ExpectedIfBeforeElse2.q", errors.ExpectedIfBeforeElse}, diff --git a/tests/programs/const.q b/tests/programs/const.q index c947f1a..5ce99b5 100644 --- a/tests/programs/const.q +++ b/tests/programs/const.q @@ -1,6 +1,6 @@ -const num { - one 1 - two 2 +num const { + one 1 + two 2 three 3 } From 543558a02b52d84a40906777f3776123f237a7e3 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 5 Apr 2025 19:34:50 +0200 Subject: [PATCH 0981/1012] Modified const and extern to always use blocks --- examples/shell/const.q | 12 +++++++----- examples/winapi/winapi.q | 10 ++++++---- lib/core/core_windows.q | 24 ++++++++++++++---------- lib/io/io.q | 14 ++++++++------ lib/mem/alloc_windows.q | 10 ++++++---- lib/mem/const_linux.q | 16 +++++++++------- lib/mem/const_mac.q | 16 +++++++++------- lib/mem/const_windows.q | 16 +++++++++------- lib/mem/free_windows.q | 10 ++++++---- lib/sys/sys_windows.q | 10 ++++++---- lib/thread/thread_linux.q | 22 ++++++++++++---------- lib/thread/thread_windows.q | 10 ++++++---- src/scanner/scanConst.go | 30 ++++++++++++++++++++++++------ src/scanner/scanExtern.go | 32 ++++++++++++++++++++++++++++++-- src/scanner/scanFile.go | 8 ++++---- tests/programs/const.q | 14 ++++++++------ 16 files changed, 164 insertions(+), 90 deletions(-) diff --git a/examples/shell/const.q b/examples/shell/const.q index 9dadaf2..20c1bfa 100644 --- a/examples/shell/const.q +++ b/examples/shell/const.q @@ -1,7 +1,9 @@ -idtype const { - pid 1 -} +const { + idtype { + pid 1 + } -state const { - exited 0x4 + state { + exited 0x4 + } } \ No newline at end of file diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q index 702e0cb..5100506 100644 --- a/examples/winapi/winapi.q +++ b/examples/winapi/winapi.q @@ -1,9 +1,11 @@ -user32 extern { - MessageBoxA(window *any, text *byte, title *byte, flags uint) -> int -} - main() { title := "Title." text := "Hi!" user32.MessageBoxA(0, text, title, 0x240040) +} + +extern { + user32 { + MessageBoxA(window *any, text *byte, title *byte, flags uint) -> int + } } \ No newline at end of file diff --git a/lib/core/core_windows.q b/lib/core/core_windows.q index abb6428..088a104 100644 --- a/lib/core/core_windows.q +++ b/lib/core/core_windows.q @@ -1,13 +1,3 @@ -cp const { - utf8 65001 -} - -kernel32 extern { - SetConsoleCP(cp uint) - SetConsoleOutputCP(cp uint) - ExitProcess(code uint) -} - init() { kernel32.SetConsoleCP(cp.utf8) kernel32.SetConsoleOutputCP(cp.utf8) @@ -21,4 +11,18 @@ exit() { crash() { kernel32.ExitProcess(1) +} + +const { + cp { + utf8 65001 + } +} + +extern { + kernel32 { + SetConsoleCP(cp uint) + SetConsoleOutputCP(cp uint) + ExitProcess(code uint) + } } \ No newline at end of file diff --git a/lib/io/io.q b/lib/io/io.q index e05f914..6c83d05 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -1,11 +1,5 @@ import sys -std const { - in 0 - out 1 - err 2 -} - in(buffer []byte) -> int { return sys.read(std.in, buffer, len(buffer)) } @@ -24,4 +18,12 @@ read(fd int, buffer []byte) -> int { write(fd int, buffer []byte) -> int { return sys.write(fd, buffer, len(buffer)) +} + +const { + std { + in 0 + out 1 + err 2 + } } \ No newline at end of file diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index 2b0c016..c0df727 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,7 +1,3 @@ -kernel32 extern { - VirtualAlloc(address int, size uint, flags uint32, protection uint32) -> *any -} - alloc(length int) -> []byte { x := kernel32.VirtualAlloc(0, length+8, mem.commit|mem.reserve, page.readwrite) @@ -11,4 +7,10 @@ alloc(length int) -> []byte { store(x, 8, length) return x + 8 +} + +extern { + kernel32 { + VirtualAlloc(address int, size uint, flags uint32, protection uint32) -> *any + } } \ No newline at end of file diff --git a/lib/mem/const_linux.q b/lib/mem/const_linux.q index 72dd4f4..ca679c9 100644 --- a/lib/mem/const_linux.q +++ b/lib/mem/const_linux.q @@ -1,9 +1,11 @@ -prot const { - read 0x1 - write 0x2 -} +const { + prot { + read 0x1 + write 0x2 + } -map const { - private 0x02 - anonymous 0x20 + map { + private 0x02 + anonymous 0x20 + } } \ No newline at end of file diff --git a/lib/mem/const_mac.q b/lib/mem/const_mac.q index 3f654cc..e7b4123 100644 --- a/lib/mem/const_mac.q +++ b/lib/mem/const_mac.q @@ -1,9 +1,11 @@ -prot const { - read 0x1 - write 0x2 -} +const { + prot { + read 0x1 + write 0x2 + } -map const { - private 0x02 - anonymous 0x1000 + map { + private 0x02 + anonymous 0x1000 + } } \ No newline at end of file diff --git a/lib/mem/const_windows.q b/lib/mem/const_windows.q index acc7f55..45e532f 100644 --- a/lib/mem/const_windows.q +++ b/lib/mem/const_windows.q @@ -1,9 +1,11 @@ -page const { - readwrite 0x0004 -} +const { + page { + readwrite 0x0004 + } -mem const { - commit 0x1000 - reserve 0x2000 - decommit 0x4000 + mem { + commit 0x1000 + reserve 0x2000 + decommit 0x4000 + } } \ No newline at end of file diff --git a/lib/mem/free_windows.q b/lib/mem/free_windows.q index d9bd818..e9f3103 100644 --- a/lib/mem/free_windows.q +++ b/lib/mem/free_windows.q @@ -1,7 +1,9 @@ -kernel32 extern { - VirtualFree(address *any, size uint, type uint32) -> bool -} - free(address []any) { kernel32.VirtualFree(address-8, len(address)+8, mem.decommit) +} + +extern { + kernel32 { + VirtualFree(address *any, size uint, type uint32) -> bool + } } \ No newline at end of file diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q index d3d3634..5f63de0 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/sys_windows.q @@ -10,8 +10,10 @@ write(fd int, buffer *byte, length int) -> int { return length } -kernel32 extern { - GetStdHandle(handle int64) -> int64 - ReadConsole(fd int64, buffer *byte, length uint32, written *uint32) -> bool - WriteConsoleA(fd int64, buffer *byte, length uint32, written *uint32) -> bool +extern { + kernel32 { + GetStdHandle(handle int64) -> int64 + ReadConsole(fd int64, buffer *byte, length uint32, written *uint32) -> bool + WriteConsoleA(fd int64, buffer *byte, length uint32, written *uint32) -> bool + } } \ No newline at end of file diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index 7652537..2e3db31 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -1,16 +1,6 @@ import core import sys -clone const { - vm 0x100 - fs 0x200 - files 0x400 - sighand 0x800 - parent 0x8000 - thread 0x10000 - io 0x80000000 -} - create(func *any) -> int { stack := sys.mmap(0, 4096, 0x1|0x2, 0x02|0x20|0x100) stack += 4096 - 8 @@ -18,4 +8,16 @@ create(func *any) -> int { stack -= 8 store(stack, 8, func) return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0) +} + +const { + clone { + vm 0x100 + fs 0x200 + files 0x400 + sighand 0x800 + parent 0x8000 + thread 0x10000 + io 0x80000000 + } } \ No newline at end of file diff --git a/lib/thread/thread_windows.q b/lib/thread/thread_windows.q index dd5136b..f04db03 100644 --- a/lib/thread/thread_windows.q +++ b/lib/thread/thread_windows.q @@ -1,7 +1,9 @@ -kernel32 extern { - CreateThread(attributes int, stackSize int, address *any, parameter int) -> int -} - create(func *any) -> int { return kernel32.CreateThread(0, 4096, func, 0) +} + +extern { + kernel32 { + CreateThread(attributes int, stackSize int, address *any, parameter int) -> int + } } \ No newline at end of file diff --git a/src/scanner/scanConst.go b/src/scanner/scanConst.go index 5a83b0a..d65789d 100644 --- a/src/scanner/scanConst.go +++ b/src/scanner/scanConst.go @@ -9,14 +9,15 @@ import ( // scanConst scans a block of constants. func (s *Scanner) scanConst(file *fs.File, tokens token.List, i int) (int, error) { - groupName := tokens[i].Text(file.Bytes) - i += 2 + i++ if tokens[i].Kind != token.BlockStart { return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) } i++ + prefix := file.Package + "." + var stack []string for i < len(tokens) { switch tokens[i].Kind { @@ -24,13 +25,30 @@ func (s *Scanner) scanConst(file *fs.File, tokens token.List, i int) (int, error name := tokens[i].Text(file.Bytes) i++ - s.constants <- &core.Constant{ - Name: file.Package + "." + groupName + "." + name, - Token: tokens[i], - File: file, + switch tokens[i].Kind { + case token.Number: + s.constants <- &core.Constant{ + Name: prefix + name, + Token: tokens[i], + File: file, + } + + case token.BlockStart: + stack = append(stack, prefix) + prefix += name + "." + + default: + return i, errors.New(errors.NotImplemented, file, tokens[i].Position) } case token.BlockEnd: + if len(stack) > 0 { + prefix = stack[len(stack)-1] + stack = stack[:len(stack)-1] + i++ + continue + } + return i, nil } diff --git a/src/scanner/scanExtern.go b/src/scanner/scanExtern.go index 140c4ae..ee476b3 100644 --- a/src/scanner/scanExtern.go +++ b/src/scanner/scanExtern.go @@ -6,10 +6,38 @@ import ( "git.urbach.dev/cli/q/src/token" ) -// scanExtern scans a block of external function declarations. +// scanExtern scans a block of external libraries. func (s *Scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, error) { + i++ + + if tokens[i].Kind != token.BlockStart { + return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) + } + + for i < len(tokens) { + switch tokens[i].Kind { + case token.Identifier: + var err error + i, err = s.scanExternLibrary(file, tokens, i) + + if err != nil { + return i, err + } + + case token.BlockEnd: + return i, nil + } + + i++ + } + + return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) +} + +// scanExternLibrary scans a block of external function declarations. +func (s *Scanner) scanExternLibrary(file *fs.File, tokens token.List, i int) (int, error) { dllName := tokens[i].Text(file.Bytes) - i += 2 + i++ if tokens[i].Kind != token.BlockStart { return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 234dd6e..2266633 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -43,10 +43,6 @@ func (s *Scanner) scanFile(path string, pkg string) error { i, err = s.scanFunction(file, tokens, i) case token.BlockStart: i, err = s.scanStruct(file, tokens, i) - case token.Extern: - i, err = s.scanExtern(file, tokens, i) - case token.Const: - i, err = s.scanConst(file, tokens, i) case token.GroupEnd: return errors.New(errors.MissingGroupStart, file, next.Position) case token.BlockEnd: @@ -56,6 +52,10 @@ func (s *Scanner) scanFile(path string, pkg string) error { default: return errors.New(errors.InvalidDefinition, file, next.Position) } + case token.Const: + i, err = s.scanConst(file, tokens, i) + case token.Extern: + i, err = s.scanExtern(file, tokens, i) case token.Import: i, err = s.scanImport(file, tokens, i) case token.EOF: diff --git a/tests/programs/const.q b/tests/programs/const.q index 5ce99b5..cd40d04 100644 --- a/tests/programs/const.q +++ b/tests/programs/const.q @@ -1,12 +1,14 @@ -num const { - one 1 - two 2 - three 3 -} - main() { assert num.one == 1 assert num.two == 2 assert num.three == 3 assert num.one + num.two == num.three +} + +const { + num { + one 1 + two 2 + three 3 + } } \ No newline at end of file From 51fc0a1272152fccddd437a58b9421ed6a96c288 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 5 Apr 2025 19:34:50 +0200 Subject: [PATCH 0982/1012] Modified const and extern to always use blocks --- examples/shell/const.q | 12 +++++++----- examples/winapi/winapi.q | 10 ++++++---- lib/core/core_windows.q | 24 ++++++++++++++---------- lib/io/io.q | 14 ++++++++------ lib/mem/alloc_windows.q | 10 ++++++---- lib/mem/const_linux.q | 16 +++++++++------- lib/mem/const_mac.q | 16 +++++++++------- lib/mem/const_windows.q | 16 +++++++++------- lib/mem/free_windows.q | 10 ++++++---- lib/sys/sys_windows.q | 10 ++++++---- lib/thread/thread_linux.q | 22 ++++++++++++---------- lib/thread/thread_windows.q | 10 ++++++---- src/scanner/scanConst.go | 30 ++++++++++++++++++++++++------ src/scanner/scanExtern.go | 32 ++++++++++++++++++++++++++++++-- src/scanner/scanFile.go | 8 ++++---- tests/programs/const.q | 14 ++++++++------ 16 files changed, 164 insertions(+), 90 deletions(-) diff --git a/examples/shell/const.q b/examples/shell/const.q index 9dadaf2..20c1bfa 100644 --- a/examples/shell/const.q +++ b/examples/shell/const.q @@ -1,7 +1,9 @@ -idtype const { - pid 1 -} +const { + idtype { + pid 1 + } -state const { - exited 0x4 + state { + exited 0x4 + } } \ No newline at end of file diff --git a/examples/winapi/winapi.q b/examples/winapi/winapi.q index 702e0cb..5100506 100644 --- a/examples/winapi/winapi.q +++ b/examples/winapi/winapi.q @@ -1,9 +1,11 @@ -user32 extern { - MessageBoxA(window *any, text *byte, title *byte, flags uint) -> int -} - main() { title := "Title." text := "Hi!" user32.MessageBoxA(0, text, title, 0x240040) +} + +extern { + user32 { + MessageBoxA(window *any, text *byte, title *byte, flags uint) -> int + } } \ No newline at end of file diff --git a/lib/core/core_windows.q b/lib/core/core_windows.q index abb6428..088a104 100644 --- a/lib/core/core_windows.q +++ b/lib/core/core_windows.q @@ -1,13 +1,3 @@ -cp const { - utf8 65001 -} - -kernel32 extern { - SetConsoleCP(cp uint) - SetConsoleOutputCP(cp uint) - ExitProcess(code uint) -} - init() { kernel32.SetConsoleCP(cp.utf8) kernel32.SetConsoleOutputCP(cp.utf8) @@ -21,4 +11,18 @@ exit() { crash() { kernel32.ExitProcess(1) +} + +const { + cp { + utf8 65001 + } +} + +extern { + kernel32 { + SetConsoleCP(cp uint) + SetConsoleOutputCP(cp uint) + ExitProcess(code uint) + } } \ No newline at end of file diff --git a/lib/io/io.q b/lib/io/io.q index e05f914..6c83d05 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -1,11 +1,5 @@ import sys -std const { - in 0 - out 1 - err 2 -} - in(buffer []byte) -> int { return sys.read(std.in, buffer, len(buffer)) } @@ -24,4 +18,12 @@ read(fd int, buffer []byte) -> int { write(fd int, buffer []byte) -> int { return sys.write(fd, buffer, len(buffer)) +} + +const { + std { + in 0 + out 1 + err 2 + } } \ No newline at end of file diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index 2b0c016..c0df727 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -1,7 +1,3 @@ -kernel32 extern { - VirtualAlloc(address int, size uint, flags uint32, protection uint32) -> *any -} - alloc(length int) -> []byte { x := kernel32.VirtualAlloc(0, length+8, mem.commit|mem.reserve, page.readwrite) @@ -11,4 +7,10 @@ alloc(length int) -> []byte { store(x, 8, length) return x + 8 +} + +extern { + kernel32 { + VirtualAlloc(address int, size uint, flags uint32, protection uint32) -> *any + } } \ No newline at end of file diff --git a/lib/mem/const_linux.q b/lib/mem/const_linux.q index 72dd4f4..ca679c9 100644 --- a/lib/mem/const_linux.q +++ b/lib/mem/const_linux.q @@ -1,9 +1,11 @@ -prot const { - read 0x1 - write 0x2 -} +const { + prot { + read 0x1 + write 0x2 + } -map const { - private 0x02 - anonymous 0x20 + map { + private 0x02 + anonymous 0x20 + } } \ No newline at end of file diff --git a/lib/mem/const_mac.q b/lib/mem/const_mac.q index 3f654cc..e7b4123 100644 --- a/lib/mem/const_mac.q +++ b/lib/mem/const_mac.q @@ -1,9 +1,11 @@ -prot const { - read 0x1 - write 0x2 -} +const { + prot { + read 0x1 + write 0x2 + } -map const { - private 0x02 - anonymous 0x1000 + map { + private 0x02 + anonymous 0x1000 + } } \ No newline at end of file diff --git a/lib/mem/const_windows.q b/lib/mem/const_windows.q index acc7f55..45e532f 100644 --- a/lib/mem/const_windows.q +++ b/lib/mem/const_windows.q @@ -1,9 +1,11 @@ -page const { - readwrite 0x0004 -} +const { + page { + readwrite 0x0004 + } -mem const { - commit 0x1000 - reserve 0x2000 - decommit 0x4000 + mem { + commit 0x1000 + reserve 0x2000 + decommit 0x4000 + } } \ No newline at end of file diff --git a/lib/mem/free_windows.q b/lib/mem/free_windows.q index d9bd818..e9f3103 100644 --- a/lib/mem/free_windows.q +++ b/lib/mem/free_windows.q @@ -1,7 +1,9 @@ -kernel32 extern { - VirtualFree(address *any, size uint, type uint32) -> bool -} - free(address []any) { kernel32.VirtualFree(address-8, len(address)+8, mem.decommit) +} + +extern { + kernel32 { + VirtualFree(address *any, size uint, type uint32) -> bool + } } \ No newline at end of file diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q index d3d3634..5f63de0 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/sys_windows.q @@ -10,8 +10,10 @@ write(fd int, buffer *byte, length int) -> int { return length } -kernel32 extern { - GetStdHandle(handle int64) -> int64 - ReadConsole(fd int64, buffer *byte, length uint32, written *uint32) -> bool - WriteConsoleA(fd int64, buffer *byte, length uint32, written *uint32) -> bool +extern { + kernel32 { + GetStdHandle(handle int64) -> int64 + ReadConsole(fd int64, buffer *byte, length uint32, written *uint32) -> bool + WriteConsoleA(fd int64, buffer *byte, length uint32, written *uint32) -> bool + } } \ No newline at end of file diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index 7652537..2e3db31 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -1,16 +1,6 @@ import core import sys -clone const { - vm 0x100 - fs 0x200 - files 0x400 - sighand 0x800 - parent 0x8000 - thread 0x10000 - io 0x80000000 -} - create(func *any) -> int { stack := sys.mmap(0, 4096, 0x1|0x2, 0x02|0x20|0x100) stack += 4096 - 8 @@ -18,4 +8,16 @@ create(func *any) -> int { stack -= 8 store(stack, 8, func) return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0) +} + +const { + clone { + vm 0x100 + fs 0x200 + files 0x400 + sighand 0x800 + parent 0x8000 + thread 0x10000 + io 0x80000000 + } } \ No newline at end of file diff --git a/lib/thread/thread_windows.q b/lib/thread/thread_windows.q index dd5136b..f04db03 100644 --- a/lib/thread/thread_windows.q +++ b/lib/thread/thread_windows.q @@ -1,7 +1,9 @@ -kernel32 extern { - CreateThread(attributes int, stackSize int, address *any, parameter int) -> int -} - create(func *any) -> int { return kernel32.CreateThread(0, 4096, func, 0) +} + +extern { + kernel32 { + CreateThread(attributes int, stackSize int, address *any, parameter int) -> int + } } \ No newline at end of file diff --git a/src/scanner/scanConst.go b/src/scanner/scanConst.go index 5a83b0a..d65789d 100644 --- a/src/scanner/scanConst.go +++ b/src/scanner/scanConst.go @@ -9,14 +9,15 @@ import ( // scanConst scans a block of constants. func (s *Scanner) scanConst(file *fs.File, tokens token.List, i int) (int, error) { - groupName := tokens[i].Text(file.Bytes) - i += 2 + i++ if tokens[i].Kind != token.BlockStart { return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) } i++ + prefix := file.Package + "." + var stack []string for i < len(tokens) { switch tokens[i].Kind { @@ -24,13 +25,30 @@ func (s *Scanner) scanConst(file *fs.File, tokens token.List, i int) (int, error name := tokens[i].Text(file.Bytes) i++ - s.constants <- &core.Constant{ - Name: file.Package + "." + groupName + "." + name, - Token: tokens[i], - File: file, + switch tokens[i].Kind { + case token.Number: + s.constants <- &core.Constant{ + Name: prefix + name, + Token: tokens[i], + File: file, + } + + case token.BlockStart: + stack = append(stack, prefix) + prefix += name + "." + + default: + return i, errors.New(errors.NotImplemented, file, tokens[i].Position) } case token.BlockEnd: + if len(stack) > 0 { + prefix = stack[len(stack)-1] + stack = stack[:len(stack)-1] + i++ + continue + } + return i, nil } diff --git a/src/scanner/scanExtern.go b/src/scanner/scanExtern.go index 140c4ae..ee476b3 100644 --- a/src/scanner/scanExtern.go +++ b/src/scanner/scanExtern.go @@ -6,10 +6,38 @@ import ( "git.urbach.dev/cli/q/src/token" ) -// scanExtern scans a block of external function declarations. +// scanExtern scans a block of external libraries. func (s *Scanner) scanExtern(file *fs.File, tokens token.List, i int) (int, error) { + i++ + + if tokens[i].Kind != token.BlockStart { + return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) + } + + for i < len(tokens) { + switch tokens[i].Kind { + case token.Identifier: + var err error + i, err = s.scanExternLibrary(file, tokens, i) + + if err != nil { + return i, err + } + + case token.BlockEnd: + return i, nil + } + + i++ + } + + return i, errors.New(errors.MissingBlockEnd, file, tokens[i].Position) +} + +// scanExternLibrary scans a block of external function declarations. +func (s *Scanner) scanExternLibrary(file *fs.File, tokens token.List, i int) (int, error) { dllName := tokens[i].Text(file.Bytes) - i += 2 + i++ if tokens[i].Kind != token.BlockStart { return i, errors.New(errors.MissingBlockStart, file, tokens[i].Position) diff --git a/src/scanner/scanFile.go b/src/scanner/scanFile.go index 234dd6e..2266633 100644 --- a/src/scanner/scanFile.go +++ b/src/scanner/scanFile.go @@ -43,10 +43,6 @@ func (s *Scanner) scanFile(path string, pkg string) error { i, err = s.scanFunction(file, tokens, i) case token.BlockStart: i, err = s.scanStruct(file, tokens, i) - case token.Extern: - i, err = s.scanExtern(file, tokens, i) - case token.Const: - i, err = s.scanConst(file, tokens, i) case token.GroupEnd: return errors.New(errors.MissingGroupStart, file, next.Position) case token.BlockEnd: @@ -56,6 +52,10 @@ func (s *Scanner) scanFile(path string, pkg string) error { default: return errors.New(errors.InvalidDefinition, file, next.Position) } + case token.Const: + i, err = s.scanConst(file, tokens, i) + case token.Extern: + i, err = s.scanExtern(file, tokens, i) case token.Import: i, err = s.scanImport(file, tokens, i) case token.EOF: diff --git a/tests/programs/const.q b/tests/programs/const.q index 5ce99b5..cd40d04 100644 --- a/tests/programs/const.q +++ b/tests/programs/const.q @@ -1,12 +1,14 @@ -num const { - one 1 - two 2 - three 3 -} - main() { assert num.one == 1 assert num.two == 2 assert num.three == 3 assert num.one + num.two == num.three +} + +const { + num { + one 1 + two 2 + three 3 + } } \ No newline at end of file From cac2bad2fe5d7f303b2bb45730a436ec5d8ff0aa Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 6 Apr 2025 20:37:26 +0200 Subject: [PATCH 0983/1012] Refactored assembly compilation --- src/asmc/ARM.go | 320 --------------------------- src/asmc/Finalize.go | 8 +- src/asmc/X86.go | 218 ------------------ src/asmc/armCompiler.go | 420 +++++++++++++++++++++++++++++++++++ src/asmc/call.go | 44 ---- src/asmc/dllCall.go | 38 ---- src/asmc/jumpARM.go | 52 ----- src/asmc/jumpX86.go | 49 ----- src/asmc/load.go | 19 -- src/asmc/move.go | 60 ----- src/asmc/store.go | 55 ----- src/asmc/x86Compiler.go | 474 ++++++++++++++++++++++++++++++++++++++++ 12 files changed, 900 insertions(+), 857 deletions(-) delete mode 100644 src/asmc/ARM.go delete mode 100644 src/asmc/X86.go create mode 100644 src/asmc/armCompiler.go delete mode 100644 src/asmc/call.go delete mode 100644 src/asmc/dllCall.go delete mode 100644 src/asmc/jumpARM.go delete mode 100644 src/asmc/jumpX86.go delete mode 100644 src/asmc/load.go delete mode 100644 src/asmc/move.go delete mode 100644 src/asmc/store.go create mode 100644 src/asmc/x86Compiler.go diff --git a/src/asmc/ARM.go b/src/asmc/ARM.go deleted file mode 100644 index 42ff3a6..0000000 --- a/src/asmc/ARM.go +++ /dev/null @@ -1,320 +0,0 @@ -package asmc - -import ( - "encoding/binary" - "fmt" - - "git.urbach.dev/cli/q/src/arm" - "git.urbach.dev/cli/q/src/asm" -) - -func (c *compiler) ARM(x asm.Instruction) { - switch x.Mnemonic { - case asm.MOVE: - switch x.Type { - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.MoveRegisterRegister(operands.Destination, operands.Source)) - - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) - - case asm.TypeRegisterLabel: - operands := c.assembler.Param.RegisterLabel[x.Index] - position := Address(len(c.code)) - c.append(arm.LoadAddress(operands.Register, 0)) - - if operands.Label.Type == asm.DataLabel { - c.dataPointers = append(c.dataPointers, &pointer{ - Position: position, - OpSize: 0, - Size: 4, - Resolve: func() Address { - destination, exists := c.dataLabels[operands.Label.Name] - - if !exists { - panic("unknown label") - } - - destination += c.dataStart - c.codeStart - distance := destination - position + 8 - return arm.LoadAddress(operands.Register, int(distance)) - }, - }) - } else { - panic("not implemented") - } - } - - case asm.CALL: - switch x.Type { - case asm.TypeLabel: - label := c.assembler.Param.Label[x.Index] - position := Address(len(c.code)) - c.append(arm.Call(0)) - - pointer := &pointer{ - Position: position, - OpSize: 0, - Size: 4, - } - - pointer.Resolve = func() Address { - destination, exists := c.codeLabels[label.Name] - - if !exists { - panic(fmt.Sprintf("unknown jump label %s", label.Name)) - } - - distance := (destination - position) / 4 - return arm.Call(distance) - } - - c.codePointers = append(c.codePointers, pointer) - - default: - panic("not implemented") - } - - case asm.LABEL: - label := c.assembler.Param.Label[x.Index] - c.codeLabels[label.Name] = Address(len(c.code)) - - if label.Type == asm.FunctionLabel { - c.append(arm.StorePair(arm.FP, arm.LR, arm.SP, -16)) - c.append(arm.MoveRegisterRegister(arm.FP, arm.SP)) - } - - case asm.LOAD: - switch x.Type { - case asm.TypeMemoryRegister: - operands := c.assembler.Param.MemoryRegister[x.Index] - - if operands.Address.OffsetRegister < 0 { - c.append(arm.LoadRegister(operands.Register, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) - } else { - panic("not implemented") - } - } - - case asm.STORE: - switch x.Type { - case asm.TypeMemoryRegister: - operands := c.assembler.Param.MemoryRegister[x.Index] - - if operands.Address.OffsetRegister < 0 { - c.append(arm.StoreRegister(operands.Register, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) - } else { - panic("not implemented") - } - - case asm.TypeMemoryNumber: - operands := c.assembler.Param.MemoryNumber[x.Index] - - if operands.Address.OffsetRegister < 0 { - c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operands.Number) - c.append(arm.StoreRegister(arm.TMP, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) - } else { - panic("not implemented") - } - } - - case asm.RETURN: - c.append(arm.LoadPair(arm.FP, arm.LR, arm.SP, 16)) - c.append(arm.Return()) - - case asm.SYSCALL: - c.append(arm.Syscall()) - - case asm.PUSH: - switch x.Type { - case asm.TypeRegister: - operand := c.assembler.Param.Register[x.Index] - code, _ := arm.SubRegisterNumber(arm.SP, arm.SP, 16) - c.append(code) - c.append(arm.StoreRegister(operand.Register, arm.SP, 0, 8)) - } - - case asm.POP: - switch x.Type { - case asm.TypeRegister: - operand := c.assembler.Param.Register[x.Index] - c.append(arm.LoadRegister(operand.Register, arm.SP, 0, 8)) - code, _ := arm.AddRegisterNumber(arm.SP, arm.SP, 16) - c.append(code) - } - - case asm.AND: - switch x.Type { - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - code, encodable := arm.AndRegisterNumber(operand.Register, operand.Register, operand.Number) - - if encodable { - c.append(code) - } else { - c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) - c.append(arm.AndRegisterRegister(operand.Register, operand.Register, arm.TMP)) - } - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.AndRegisterRegister(operand.Destination, operand.Destination, operand.Source)) - } - - case asm.OR: - switch x.Type { - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - code, encodable := arm.OrRegisterNumber(operand.Register, operand.Register, operand.Number) - - if encodable { - c.append(code) - } else { - c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) - c.append(arm.OrRegisterRegister(operand.Register, operand.Register, arm.TMP)) - } - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.OrRegisterRegister(operand.Destination, operand.Destination, operand.Source)) - } - - case asm.XOR: - switch x.Type { - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - code, encodable := arm.XorRegisterNumber(operand.Register, operand.Register, operand.Number) - - if encodable { - c.append(code) - } else { - c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) - c.append(arm.XorRegisterRegister(operand.Register, operand.Register, arm.TMP)) - } - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.XorRegisterRegister(operand.Destination, operand.Destination, operand.Source)) - } - - case asm.ADD: - switch x.Type { - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - code, encodable := arm.AddRegisterNumber(operand.Register, operand.Register, operand.Number) - - if encodable { - c.append(code) - } else { - c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) - c.append(arm.AddRegisterRegister(operand.Register, operand.Register, arm.TMP)) - } - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.AddRegisterRegister(operand.Destination, operand.Destination, operand.Source)) - } - - case asm.SUB: - switch x.Type { - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - code, encodable := arm.SubRegisterNumber(operand.Register, operand.Register, operand.Number) - - if encodable { - c.append(code) - } else { - c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) - c.append(arm.SubRegisterRegister(operand.Register, operand.Register, arm.TMP)) - } - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.SubRegisterRegister(operand.Destination, operand.Destination, operand.Source)) - } - - case asm.COMPARE: - switch x.Type { - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - code, encodable := arm.CompareRegisterNumber(operand.Register, operand.Number) - - if encodable { - c.append(code) - } else { - c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) - c.append(arm.CompareRegisterRegister(operand.Register, arm.TMP)) - } - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.CompareRegisterRegister(operand.Destination, operand.Source)) - } - - case asm.DIV: - switch x.Type { - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.DivSigned(operand.Destination, operand.Destination, operand.Source)) - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) - c.append(arm.DivSigned(operand.Register, operand.Register, arm.TMP)) - } - - case asm.MUL: - switch x.Type { - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.MulRegisterRegister(operand.Destination, operand.Destination, operand.Source)) - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) - c.append(arm.MulRegisterRegister(operand.Register, operand.Register, arm.TMP)) - } - - case asm.MODULO: - switch x.Type { - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.DivSigned(arm.TMP, operand.Destination, operand.Source)) - c.append(arm.MultiplySubtract(operand.Destination, arm.TMP, operand.Source, operand.Destination)) - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) - c.append(arm.DivSigned(arm.TMP2, operand.Register, arm.TMP)) - c.append(arm.MultiplySubtract(operand.Register, arm.TMP2, arm.TMP, operand.Register)) - } - - case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: - c.jumpARM(x) - - case asm.SHIFTL: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.append(arm.ShiftLeftNumber(operands.Register, operands.Register, operands.Number&0b111111)) - case asm.TypeRegisterRegister: - panic("not implemented") - } - - case asm.SHIFTRS: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.append(arm.ShiftRightSignedNumber(operands.Register, operands.Register, operands.Number&0b111111)) - case asm.TypeRegisterRegister: - panic("not implemented") - } - - case asm.NEGATE: - switch x.Type { - case asm.TypeRegister: - operands := c.assembler.Param.Register[x.Index] - c.append(arm.NegateRegister(operands.Register, operands.Register)) - } - - default: - panic("unknown mnemonic: " + x.Mnemonic.String()) - } -} - -func (c *compiler) append(code uint32) { - c.code = binary.LittleEndian.AppendUint32(c.code, code) -} diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index ec9e678..93e04ab 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -27,13 +27,17 @@ func Finalize(a *asm.Assembler, dlls dll.List) ([]byte, []byte) { switch config.TargetArch { case config.ARM: + armc := armCompiler{compiler: &c} + for _, x := range a.Instructions { - c.ARM(x) + armc.Compile(x) } case config.X86: + x86c := x86Compiler{compiler: &c} + for _, x := range a.Instructions { - c.X86(x) + x86c.Compile(x) } } diff --git a/src/asmc/X86.go b/src/asmc/X86.go deleted file mode 100644 index 5cdf60f..0000000 --- a/src/asmc/X86.go +++ /dev/null @@ -1,218 +0,0 @@ -package asmc - -import ( - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/x86" -) - -func (c *compiler) X86(x asm.Instruction) { - switch x.Mnemonic { - case asm.MOVE: - c.move(x) - - case asm.CALL: - c.call(x) - - case asm.LABEL: - label := c.assembler.Param.Label[x.Index] - c.codeLabels[label.Name] = Address(len(c.code)) - - case asm.LOAD: - c.load(x) - - case asm.STORE: - c.store(x) - - case asm.RETURN: - c.code = x86.Return(c.code) - - case asm.SYSCALL: - c.code = x86.Syscall(c.code) - - case asm.ADD: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.AddRegisterNumber(c.code, operands.Register, operands.Number) - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.code = x86.AddRegisterRegister(c.code, operands.Destination, operands.Source) - } - - case asm.AND: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.AndRegisterNumber(c.code, operands.Register, operands.Number) - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.code = x86.AndRegisterRegister(c.code, operands.Destination, operands.Source) - } - - case asm.SUB: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.SubRegisterNumber(c.code, operands.Register, operands.Number) - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.code = x86.SubRegisterRegister(c.code, operands.Destination, operands.Source) - } - - case asm.MUL: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.MulRegisterNumber(c.code, operands.Register, operands.Number) - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.code = x86.MulRegisterRegister(c.code, operands.Destination, operands.Source) - } - - case asm.DIV: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - - if operands.Register != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Register) - } - - c.code = x86.MoveRegisterNumber(c.code, x86.TMP, operands.Number) - c.code = x86.ExtendRAXToRDX(c.code) - c.code = x86.DivRegister(c.code, x86.TMP) - - if operands.Register != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.RAX) - } - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - - if operands.Destination != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) - } - - c.code = x86.ExtendRAXToRDX(c.code) - c.code = x86.DivRegister(c.code, operands.Source) - - if operands.Destination != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.RAX) - } - } - - case asm.MODULO: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - - if operands.Register != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Register) - } - - c.code = x86.MoveRegisterNumber(c.code, x86.TMP, operands.Number) - c.code = x86.ExtendRAXToRDX(c.code) - c.code = x86.DivRegister(c.code, x86.TMP) - - if operands.Register != x86.RDX { - c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.RDX) - } - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - - if operands.Destination != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) - } - - c.code = x86.ExtendRAXToRDX(c.code) - c.code = x86.DivRegister(c.code, operands.Source) - - if operands.Destination != x86.RDX { - c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.RDX) - } - } - - case asm.COMMENT: - return - - case asm.COMPARE: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.CompareRegisterNumber(c.code, operands.Register, operands.Number) - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.code = x86.CompareRegisterRegister(c.code, operands.Destination, operands.Source) - } - - case asm.DLLCALL: - c.dllCall(x) - - case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: - c.jumpX86(x) - - case asm.NEGATE: - switch x.Type { - case asm.TypeRegister: - operands := c.assembler.Param.Register[x.Index] - c.code = x86.NegateRegister(c.code, operands.Register) - } - - case asm.OR: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.OrRegisterNumber(c.code, operands.Register, operands.Number) - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.code = x86.OrRegisterRegister(c.code, operands.Destination, operands.Source) - } - - case asm.POP: - switch x.Type { - case asm.TypeRegister: - operands := c.assembler.Param.Register[x.Index] - c.code = x86.PopRegister(c.code, operands.Register) - } - - case asm.PUSH: - switch x.Type { - case asm.TypeNumber: - operands := c.assembler.Param.Number[x.Index] - c.code = x86.PushNumber(c.code, int32(operands.Number)) - case asm.TypeRegister: - operands := c.assembler.Param.Register[x.Index] - c.code = x86.PushRegister(c.code, operands.Register) - } - - case asm.SHIFTL: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.ShiftLeftNumber(c.code, operands.Register, byte(operands.Number)&0b111111) - case asm.TypeRegisterRegister: - panic("not implemented") - } - - case asm.SHIFTRS: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.ShiftRightSignedNumber(c.code, operands.Register, byte(operands.Number)&0b111111) - case asm.TypeRegisterRegister: - panic("not implemented") - } - - case asm.XOR: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.XorRegisterNumber(c.code, operands.Register, operands.Number) - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.code = x86.XorRegisterRegister(c.code, operands.Destination, operands.Source) - } - - default: - panic("unknown mnemonic: " + x.Mnemonic.String()) - } -} diff --git a/src/asmc/armCompiler.go b/src/asmc/armCompiler.go new file mode 100644 index 0000000..6c11240 --- /dev/null +++ b/src/asmc/armCompiler.go @@ -0,0 +1,420 @@ +package asmc + +import ( + "encoding/binary" + "fmt" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/asm" +) + +type armCompiler struct { + *compiler +} + +func (c *armCompiler) Compile(instruction asm.Instruction) { + switch instruction.Mnemonic { + case asm.MOVE: + c.handleMoveInstruction(instruction) + case asm.CALL: + c.handleCallInstruction(instruction) + case asm.LABEL: + c.handleLabelInstruction(instruction) + case asm.LOAD: + c.handleLoadInstruction(instruction) + case asm.STORE: + c.handleStoreInstruction(instruction) + case asm.RETURN: + c.append(arm.LoadPair(arm.FP, arm.LR, arm.SP, 16)) + c.append(arm.Return()) + case asm.SYSCALL: + c.append(arm.Syscall()) + case asm.PUSH: + c.handlePushInstruction(instruction) + case asm.POP: + c.handlePopInstruction(instruction) + case asm.AND: + c.handleAndInstruction(instruction) + case asm.OR: + c.handleOrInstruction(instruction) + case asm.XOR: + c.handleXorInstruction(instruction) + case asm.ADD: + c.handleAddInstruction(instruction) + case asm.SUB: + c.handleSubInstruction(instruction) + case asm.COMPARE: + c.handleCompareInstruction(instruction) + case asm.DIV: + c.handleDivInstruction(instruction) + case asm.MUL: + c.handleMulInstruction(instruction) + case asm.MODULO: + c.handleModuloInstruction(instruction) + case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: + c.handleJumpInstruction(instruction) + case asm.SHIFTL: + c.handleShiftLeftInstruction(instruction) + case asm.SHIFTRS: + c.handleShiftRightSignedInstruction(instruction) + case asm.NEGATE: + c.handleNegateInstruction(instruction) + default: + panic("unknown mnemonic: " + instruction.Mnemonic.String()) + } +} + +func (c *armCompiler) handleMoveInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.MoveRegisterRegister(operands.Destination, operands.Source)) + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterLabel: + operands := c.assembler.Param.RegisterLabel[instruction.Index] + position := Address(len(c.code)) + c.append(arm.LoadAddress(operands.Register, 0)) + + if operands.Label.Type == asm.DataLabel { + c.dataPointers = append(c.dataPointers, c.createDataPointer(operands, position)) + } else { + panic("not implemented") + } + } +} + +func (c *armCompiler) handleCallInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeLabel: + label := c.assembler.Param.Label[instruction.Index] + position := Address(len(c.code)) + c.append(arm.Call(0)) + + pointer := &pointer{ + Position: position, + OpSize: 0, + Size: 4, + } + + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[label.Name] + if !exists { + panic(fmt.Sprintf("unknown jump label %s", label.Name)) + } + distance := (destination - position) / 4 + return arm.Call(distance) + } + + c.codePointers = append(c.codePointers, pointer) + default: + panic("not implemented") + } +} + +func (c *armCompiler) handleLabelInstruction(instruction asm.Instruction) { + label := c.assembler.Param.Label[instruction.Index] + c.codeLabels[label.Name] = Address(len(c.code)) + + if label.Type == asm.FunctionLabel { + c.append(arm.StorePair(arm.FP, arm.LR, arm.SP, -16)) + c.append(arm.MoveRegisterRegister(arm.FP, arm.SP)) + } +} + +func (c *armCompiler) handleLoadInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeMemoryRegister: + operands := c.assembler.Param.MemoryRegister[instruction.Index] + + if operands.Address.OffsetRegister < 0 { + c.append(arm.LoadRegister(operands.Register, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) + } else { + panic("not implemented") + } + } +} + +func (c *armCompiler) handleStoreInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeMemoryRegister: + operands := c.assembler.Param.MemoryRegister[instruction.Index] + + if operands.Address.OffsetRegister < 0 { + c.append(arm.StoreRegister(operands.Register, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) + } else { + panic("not implemented") + } + case asm.TypeMemoryNumber: + operands := c.assembler.Param.MemoryNumber[instruction.Index] + + if operands.Address.OffsetRegister < 0 { + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operands.Number) + c.append(arm.StoreRegister(arm.TMP, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) + } else { + panic("not implemented") + } + } +} + +func (c *armCompiler) handlePushInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegister: + operand := c.assembler.Param.Register[instruction.Index] + code, _ := arm.SubRegisterNumber(arm.SP, arm.SP, 16) + c.append(code) + c.append(arm.StoreRegister(operand.Register, arm.SP, 0, 8)) + } +} + +func (c *armCompiler) handlePopInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegister: + operand := c.assembler.Param.Register[instruction.Index] + c.append(arm.LoadRegister(operand.Register, arm.SP, 0, 8)) + code, _ := arm.AddRegisterNumber(arm.SP, arm.SP, 16) + c.append(code) + } +} + +func (c *armCompiler) handleAndInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + code, encodable := arm.AndRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) + c.append(arm.AndRegisterRegister(operand.Register, operand.Register, arm.TMP)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.AndRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } +} + +func (c *armCompiler) handleOrInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + code, encodable := arm.OrRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) + c.append(arm.OrRegisterRegister(operand.Register, operand.Register, arm.TMP)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.OrRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } +} + +func (c *armCompiler) handleXorInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + code, encodable := arm.XorRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) + c.append(arm.XorRegisterRegister(operand.Register, operand.Register, arm.TMP)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.XorRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } +} + +func (c *armCompiler) handleAddInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + code, encodable := arm.AddRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.AddRegisterRegister(operand.Register, operand.Register, arm.TMP)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.AddRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } +} + +func (c *armCompiler) handleSubInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + code, encodable := arm.SubRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.SubRegisterRegister(operand.Register, operand.Register, arm.TMP)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.SubRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } +} + +func (c *armCompiler) handleCompareInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + code, encodable := arm.CompareRegisterNumber(operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.CompareRegisterRegister(operand.Register, arm.TMP)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.CompareRegisterRegister(operand.Destination, operand.Source)) + } +} + +func (c *armCompiler) handleDivInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.DivSigned(operand.Destination, operand.Destination, operand.Source)) + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.DivSigned(operand.Register, operand.Register, arm.TMP)) + } +} + +func (c *armCompiler) handleMulInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.MulRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.MulRegisterRegister(operand.Register, operand.Register, arm.TMP)) + } +} + +func (c *armCompiler) handleModuloInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.DivSigned(arm.TMP, operand.Destination, operand.Source)) + c.append(arm.MultiplySubtract(operand.Destination, arm.TMP, operand.Source, operand.Destination)) + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.DivSigned(arm.TMP2, operand.Register, arm.TMP)) + c.append(arm.MultiplySubtract(operand.Register, arm.TMP2, arm.TMP, operand.Register)) + } +} + +func (c *armCompiler) handleJumpInstruction(x asm.Instruction) { + label := c.assembler.Param.Label[x.Index] + mnemonic := x.Mnemonic + position := Address(len(c.code)) + + pointer := &pointer{ + Position: position, + Size: 4, + } + + c.append(arm.Nop()) + + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[label.Name] + + if !exists { + panic(fmt.Sprintf("unknown jump label %s", label.Name)) + } + + distance := (int(destination) - int(position)) / 4 + + switch mnemonic { + case asm.JE: + return arm.JumpIfEqual(distance) + case asm.JNE: + return arm.JumpIfNotEqual(distance) + case asm.JG: + return arm.JumpIfGreater(distance) + case asm.JGE: + return arm.JumpIfGreaterOrEqual(distance) + case asm.JL: + return arm.JumpIfLess(distance) + case asm.JLE: + return arm.JumpIfLessOrEqual(distance) + case asm.JUMP: + return arm.Jump(distance) + default: + panic("not implemented") + } + } + + c.codePointers = append(c.codePointers, pointer) +} + +func (c *armCompiler) handleShiftLeftInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.append(arm.ShiftLeftNumber(operands.Register, operands.Register, operands.Number&0b111111)) + case asm.TypeRegisterRegister: + panic("not implemented") + } +} + +func (c *armCompiler) handleShiftRightSignedInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.append(arm.ShiftRightSignedNumber(operands.Register, operands.Register, operands.Number&0b111111)) + case asm.TypeRegisterRegister: + panic("not implemented") + } +} + +func (c *armCompiler) handleNegateInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegister: + operands := c.assembler.Param.Register[instruction.Index] + c.append(arm.NegateRegister(operands.Register, operands.Register)) + } +} + +func (c *armCompiler) createDataPointer(operands asm.RegisterLabel, position Address) *pointer { + return &pointer{ + Position: position, + OpSize: 0, + Size: 4, + Resolve: func() Address { + destination, exists := c.dataLabels[operands.Label.Name] + + if !exists { + panic("unknown label") + } + + destination += c.dataStart - c.codeStart + distance := destination - position + 8 + return arm.LoadAddress(operands.Register, int(distance)) + }, + } +} + +func (c *compiler) append(code uint32) { + c.code = binary.LittleEndian.AppendUint32(c.code, code) +} diff --git a/src/asmc/call.go b/src/asmc/call.go deleted file mode 100644 index c941bb6..0000000 --- a/src/asmc/call.go +++ /dev/null @@ -1,44 +0,0 @@ -package asmc - -import ( - "fmt" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/x86" -) - -func (c *compiler) call(x asm.Instruction) { - switch x.Type { - case asm.TypeLabel: - data := c.assembler.Param.Label[x.Index] - c.code = x86.Call(c.code, 0x00_00_00_00) - size := 4 - - pointer := &pointer{ - Position: Address(len(c.code) - size), - OpSize: 1, - Size: uint8(size), - } - - pointer.Resolve = func() Address { - destination, exists := c.codeLabels[data.Name] - - if !exists { - panic(fmt.Sprintf("unknown jump label %s", data.Name)) - } - - distance := destination - (pointer.Position + Address(pointer.Size)) - return distance - } - - c.codePointers = append(c.codePointers, pointer) - - case asm.TypeRegister: - data := c.assembler.Param.Register[x.Index] - c.code = x86.CallRegister(c.code, data.Register) - - case asm.TypeMemory: - data := c.assembler.Param.Memory[x.Index] - c.code = x86.CallAtMemory(c.code, data.Base, data.Offset) - } -} diff --git a/src/asmc/dllCall.go b/src/asmc/dllCall.go deleted file mode 100644 index 203fbc1..0000000 --- a/src/asmc/dllCall.go +++ /dev/null @@ -1,38 +0,0 @@ -package asmc - -import ( - "strings" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/x86" -) - -func (c *compiler) dllCall(x asm.Instruction) { - label := c.assembler.Param.Label[x.Index] - c.code = x86.CallAt(c.code, 0x00_00_00_00) - next := Address(len(c.code)) - position := next - 4 - - pointer := &pointer{ - Position: Address(position), - OpSize: 2, - Size: 4, - } - - pointer.Resolve = func() Address { - dot := strings.Index(label.Name, ".") - library := label.Name[:dot] - funcName := label.Name[dot+1:] - index := c.dlls.Index(library, funcName) - - if index == -1 { - panic("unknown DLL function " + label.Name) - } - - destination := c.importsStart + Address(index*8) - from := c.codeStart + next - return destination - from - } - - c.dllPointers = append(c.dllPointers, pointer) -} diff --git a/src/asmc/jumpARM.go b/src/asmc/jumpARM.go deleted file mode 100644 index 81a96ee..0000000 --- a/src/asmc/jumpARM.go +++ /dev/null @@ -1,52 +0,0 @@ -package asmc - -import ( - "fmt" - - "git.urbach.dev/cli/q/src/arm" - "git.urbach.dev/cli/q/src/asm" -) - -func (c *compiler) jumpARM(x asm.Instruction) { - label := c.assembler.Param.Label[x.Index] - mnemonic := x.Mnemonic - position := Address(len(c.code)) - - pointer := &pointer{ - Position: position, - Size: 4, - } - - c.append(arm.Nop()) - - pointer.Resolve = func() Address { - destination, exists := c.codeLabels[label.Name] - - if !exists { - panic(fmt.Sprintf("unknown jump label %s", label.Name)) - } - - distance := (int(destination) - int(position)) / 4 - - switch mnemonic { - case asm.JE: - return arm.JumpIfEqual(distance) - case asm.JNE: - return arm.JumpIfNotEqual(distance) - case asm.JG: - return arm.JumpIfGreater(distance) - case asm.JGE: - return arm.JumpIfGreaterOrEqual(distance) - case asm.JL: - return arm.JumpIfLess(distance) - case asm.JLE: - return arm.JumpIfLessOrEqual(distance) - case asm.JUMP: - return arm.Jump(distance) - default: - panic("not implemented") - } - } - - c.codePointers = append(c.codePointers, pointer) -} diff --git a/src/asmc/jumpX86.go b/src/asmc/jumpX86.go deleted file mode 100644 index e9ace20..0000000 --- a/src/asmc/jumpX86.go +++ /dev/null @@ -1,49 +0,0 @@ -package asmc - -import ( - "fmt" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/x86" -) - -func (c *compiler) jumpX86(x asm.Instruction) { - switch x.Mnemonic { - case asm.JE: - c.code = x86.Jump8IfEqual(c.code, 0x00) - case asm.JNE: - c.code = x86.Jump8IfNotEqual(c.code, 0x00) - case asm.JG: - c.code = x86.Jump8IfGreater(c.code, 0x00) - case asm.JGE: - c.code = x86.Jump8IfGreaterOrEqual(c.code, 0x00) - case asm.JL: - c.code = x86.Jump8IfLess(c.code, 0x00) - case asm.JLE: - c.code = x86.Jump8IfLessOrEqual(c.code, 0x00) - case asm.JUMP: - c.code = x86.Jump8(c.code, 0x00) - } - - label := c.assembler.Param.Label[x.Index] - size := 1 - - pointer := &pointer{ - Position: Address(len(c.code) - size), - OpSize: 1, - Size: uint8(size), - } - - pointer.Resolve = func() Address { - destination, exists := c.codeLabels[label.Name] - - if !exists { - panic(fmt.Sprintf("unknown jump label %s", label.Name)) - } - - distance := destination - (pointer.Position + Address(pointer.Size)) - return distance - } - - c.codePointers = append(c.codePointers, pointer) -} diff --git a/src/asmc/load.go b/src/asmc/load.go deleted file mode 100644 index 845092b..0000000 --- a/src/asmc/load.go +++ /dev/null @@ -1,19 +0,0 @@ -package asmc - -import ( - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/x86" -) - -func (c *compiler) load(x asm.Instruction) { - switch x.Type { - case asm.TypeMemoryRegister: - operands := c.assembler.Param.MemoryRegister[x.Index] - - if operands.Address.OffsetRegister < 0 { - c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Base, operands.Address.Offset, operands.Address.Length) - } else { - c.code = x86.LoadDynamicRegister(c.code, operands.Register, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length) - } - } -} diff --git a/src/asmc/move.go b/src/asmc/move.go deleted file mode 100644 index 3963ba5..0000000 --- a/src/asmc/move.go +++ /dev/null @@ -1,60 +0,0 @@ -package asmc - -import ( - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/x86" -) - -func (c *compiler) move(x asm.Instruction) { - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.MoveRegisterNumber(c.code, operands.Register, operands.Number) - - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.code = x86.MoveRegisterRegister(c.code, operands.Destination, operands.Source) - - case asm.TypeRegisterLabel: - operands := c.assembler.Param.RegisterLabel[x.Index] - start := Address(len(c.code)) - c.code = x86.LoadAddress(c.code, operands.Register, 0x00_00_00_00) - end := Address(len(c.code)) - position := end - 4 - opSize := position - start - - if operands.Label.Type == asm.DataLabel { - c.dataPointers = append(c.dataPointers, &pointer{ - Position: position, - OpSize: uint8(opSize), - Size: uint8(4), - Resolve: func() Address { - destination, exists := c.dataLabels[operands.Label.Name] - - if !exists { - panic("unknown label") - } - - destination += c.dataStart - c.codeStart - distance := destination - end - return distance + 8 - }, - }) - } else { - c.codePointers = append(c.codePointers, &pointer{ - Position: position, - OpSize: uint8(opSize), - Size: uint8(4), - Resolve: func() Address { - destination, exists := c.codeLabels[operands.Label.Name] - - if !exists { - panic("unknown label") - } - - return destination - end - }, - }) - } - } -} diff --git a/src/asmc/store.go b/src/asmc/store.go deleted file mode 100644 index 42352c4..0000000 --- a/src/asmc/store.go +++ /dev/null @@ -1,55 +0,0 @@ -package asmc - -import ( - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/config" - "git.urbach.dev/cli/q/src/x86" -) - -func (c *compiler) store(x asm.Instruction) { - switch x.Type { - case asm.TypeMemoryNumber: - operands := c.assembler.Param.MemoryNumber[x.Index] - - if operands.Address.OffsetRegister < 0 { - c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) - } else { - c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) - } - case asm.TypeMemoryLabel: - operands := c.assembler.Param.MemoryLabel[x.Index] - start := len(c.code) - - if operands.Address.OffsetRegister < 0 { - c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, 0b00_00_00_00) - } else { - c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, 0b00_00_00_00) - } - - size := 4 - opSize := len(c.code) - size - start - - c.codePointers = append(c.codePointers, &pointer{ - Position: Address(len(c.code) - size), - OpSize: uint8(opSize), - Size: uint8(size), - Resolve: func() Address { - destination, exists := c.codeLabels[operands.Label.Name] - - if !exists { - panic("unknown label") - } - - return config.BaseAddress + c.codeStart + destination - }, - }) - case asm.TypeMemoryRegister: - operands := c.assembler.Param.MemoryRegister[x.Index] - - if operands.Address.OffsetRegister < 0 { - c.code = x86.StoreRegister(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) - } else { - c.code = x86.StoreDynamicRegister(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) - } - } -} diff --git a/src/asmc/x86Compiler.go b/src/asmc/x86Compiler.go new file mode 100644 index 0000000..b721039 --- /dev/null +++ b/src/asmc/x86Compiler.go @@ -0,0 +1,474 @@ +package asmc + +import ( + "fmt" + "strings" + + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/x86" +) + +type x86Compiler struct { + *compiler +} + +func (c *x86Compiler) Compile(instruction asm.Instruction) { + switch instruction.Mnemonic { + case asm.MOVE: + c.handleMoveInstruction(instruction) + case asm.CALL: + c.handleCallInstruction(instruction) + case asm.LABEL: + label := c.assembler.Param.Label[instruction.Index] + c.codeLabels[label.Name] = Address(len(c.code)) + case asm.LOAD: + c.handleLoadInstruction(instruction) + case asm.STORE: + c.handleStoreInstruction(instruction) + case asm.RETURN: + c.code = x86.Return(c.code) + case asm.SYSCALL: + c.code = x86.Syscall(c.code) + case asm.ADD: + c.handleAddInstruction(instruction) + case asm.AND: + c.handleAndInstruction(instruction) + case asm.DIV: + c.handleDivInstruction(instruction) + case asm.MODULO: + c.handleModuloInstruction(instruction) + case asm.MUL: + c.handleMulInstruction(instruction) + case asm.OR: + c.handleOrInstruction(instruction) + case asm.SUB: + c.handleSubInstruction(instruction) + case asm.XOR: + c.handleXorInstruction(instruction) + case asm.NEGATE: + c.handleNegateInstruction(instruction) + case asm.POP: + c.handlePopInstruction(instruction) + case asm.PUSH: + c.handlePushInstruction(instruction) + case asm.SHIFTL: + c.handleShiftLeftInstruction(instruction) + case asm.SHIFTRS: + c.handleShiftRightSignedInstruction(instruction) + case asm.COMPARE: + c.handleCompareInstruction(instruction) + case asm.DLLCALL: + c.handleDllCallInstruction(instruction) + case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: + c.handleJumpInstruction(instruction) + default: + panic("unknown mnemonic: " + instruction.Mnemonic.String()) + } +} + +func (c *x86Compiler) handleMoveInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.MoveRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.code = x86.MoveRegisterRegister(c.code, operands.Destination, operands.Source) + case asm.TypeRegisterLabel: + operands := c.assembler.Param.RegisterLabel[instruction.Index] + start := Address(len(c.code)) + c.code = x86.LoadAddress(c.code, operands.Register, 0x00_00_00_00) + end := Address(len(c.code)) + position := end - 4 + opSize := position - start + + if operands.Label.Type == asm.DataLabel { + c.dataPointers = append(c.dataPointers, &pointer{ + Position: position, + OpSize: uint8(opSize), + Size: uint8(4), + Resolve: func() Address { + destination, exists := c.dataLabels[operands.Label.Name] + + if !exists { + panic("unknown label") + } + + destination += c.dataStart - c.codeStart + distance := destination - end + return distance + 8 + }, + }) + } else { + c.codePointers = append(c.codePointers, &pointer{ + Position: position, + OpSize: uint8(opSize), + Size: uint8(4), + Resolve: func() Address { + destination, exists := c.codeLabels[operands.Label.Name] + + if !exists { + panic("unknown label") + } + + return destination - end + }, + }) + } + } +} + +func (c *x86Compiler) handleCallInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeLabel: + data := c.assembler.Param.Label[instruction.Index] + c.code = x86.Call(c.code, 0x00_00_00_00) + size := 4 + + pointer := &pointer{ + Position: Address(len(c.code) - size), + OpSize: 1, + Size: uint8(size), + } + + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[data.Name] + + if !exists { + panic(fmt.Sprintf("unknown jump label %s", data.Name)) + } + + distance := destination - (pointer.Position + Address(pointer.Size)) + return distance + } + + c.codePointers = append(c.codePointers, pointer) + case asm.TypeRegister: + data := c.assembler.Param.Register[instruction.Index] + c.code = x86.CallRegister(c.code, data.Register) + case asm.TypeMemory: + data := c.assembler.Param.Memory[instruction.Index] + c.code = x86.CallAtMemory(c.code, data.Base, data.Offset) + } +} + +func (c *x86Compiler) handleLoadInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeMemoryRegister: + operands := c.assembler.Param.MemoryRegister[instruction.Index] + + if operands.Address.OffsetRegister < 0 { + c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Base, operands.Address.Offset, operands.Address.Length) + } else { + c.code = x86.LoadDynamicRegister(c.code, operands.Register, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length) + } + } +} + +func (c *x86Compiler) handleStoreInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeMemoryNumber: + operands := c.assembler.Param.MemoryNumber[instruction.Index] + + if operands.Address.OffsetRegister < 0 { + c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) + } else { + c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) + } + case asm.TypeMemoryLabel: + operands := c.assembler.Param.MemoryLabel[instruction.Index] + start := len(c.code) + + if operands.Address.OffsetRegister < 0 { + c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, 0b00_00_00_00) + } else { + c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, 0b00_00_00_00) + } + + size := 4 + opSize := len(c.code) - size - start + + c.codePointers = append(c.codePointers, &pointer{ + Position: Address(len(c.code) - size), + OpSize: uint8(opSize), + Size: uint8(size), + Resolve: func() Address { + destination, exists := c.codeLabels[operands.Label.Name] + + if !exists { + panic("unknown label") + } + + return config.BaseAddress + c.codeStart + destination + }, + }) + case asm.TypeMemoryRegister: + operands := c.assembler.Param.MemoryRegister[instruction.Index] + + if operands.Address.OffsetRegister < 0 { + c.code = x86.StoreRegister(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) + } else { + c.code = x86.StoreDynamicRegister(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) + } + } +} + +func (c *x86Compiler) handleAddInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.AddRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.code = x86.AddRegisterRegister(c.code, operands.Destination, operands.Source) + } +} + +func (c *x86Compiler) handleAndInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.AndRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.code = x86.AndRegisterRegister(c.code, operands.Destination, operands.Source) + } +} + +func (c *x86Compiler) handleDivInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + + if operands.Register != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Register) + } + + c.code = x86.MoveRegisterNumber(c.code, x86.TMP, operands.Number) + c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.DivRegister(c.code, x86.TMP) + + if operands.Register != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.RAX) + } + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + + if operands.Destination != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) + } + + c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.DivRegister(c.code, operands.Source) + + if operands.Destination != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.RAX) + } + } +} + +func (c *x86Compiler) handleModuloInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + + if operands.Register != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Register) + } + + c.code = x86.MoveRegisterNumber(c.code, x86.TMP, operands.Number) + c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.DivRegister(c.code, x86.TMP) + + if operands.Register != x86.RDX { + c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.RDX) + } + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + + if operands.Destination != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) + } + + c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.DivRegister(c.code, operands.Source) + + if operands.Destination != x86.RDX { + c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.RDX) + } + } +} + +func (c *x86Compiler) handleMulInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.MulRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.code = x86.MulRegisterRegister(c.code, operands.Destination, operands.Source) + } +} + +func (c *x86Compiler) handleOrInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.OrRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.code = x86.OrRegisterRegister(c.code, operands.Destination, operands.Source) + } +} + +func (c *x86Compiler) handleSubInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.SubRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.code = x86.SubRegisterRegister(c.code, operands.Destination, operands.Source) + } +} + +func (c *x86Compiler) handleXorInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.XorRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.code = x86.XorRegisterRegister(c.code, operands.Destination, operands.Source) + } +} + +func (c *x86Compiler) handleNegateInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegister: + operands := c.assembler.Param.Register[instruction.Index] + c.code = x86.NegateRegister(c.code, operands.Register) + } +} + +func (c *x86Compiler) handlePopInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegister: + operands := c.assembler.Param.Register[instruction.Index] + c.code = x86.PopRegister(c.code, operands.Register) + } +} + +func (c *x86Compiler) handlePushInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeNumber: + operands := c.assembler.Param.Number[instruction.Index] + c.code = x86.PushNumber(c.code, int32(operands.Number)) + case asm.TypeRegister: + operands := c.assembler.Param.Register[instruction.Index] + c.code = x86.PushRegister(c.code, operands.Register) + } +} + +func (c *x86Compiler) handleShiftLeftInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.ShiftLeftNumber(c.code, operands.Register, byte(operands.Number)&0b111111) + case asm.TypeRegisterRegister: + panic("not implemented") + } +} + +func (c *x86Compiler) handleShiftRightSignedInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.ShiftRightSignedNumber(c.code, operands.Register, byte(operands.Number)&0b111111) + case asm.TypeRegisterRegister: + panic("not implemented") + } +} + +func (c *x86Compiler) handleCompareInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.CompareRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.code = x86.CompareRegisterRegister(c.code, operands.Destination, operands.Source) + } +} + +func (c *x86Compiler) handleDllCallInstruction(instruction asm.Instruction) { + label := c.assembler.Param.Label[instruction.Index] + c.code = x86.CallAt(c.code, 0x00_00_00_00) + next := Address(len(c.code)) + position := next - 4 + + pointer := &pointer{ + Position: Address(position), + OpSize: 2, + Size: 4, + } + + pointer.Resolve = func() Address { + dot := strings.Index(label.Name, ".") + library := label.Name[:dot] + funcName := label.Name[dot+1:] + index := c.dlls.Index(library, funcName) + + if index == -1 { + panic("unknown DLL function " + label.Name) + } + + destination := c.importsStart + Address(index*8) + from := c.codeStart + next + return destination - from + } + + c.dllPointers = append(c.dllPointers, pointer) +} + +func (c *x86Compiler) handleJumpInstruction(instruction asm.Instruction) { + switch instruction.Mnemonic { + case asm.JE: + c.code = x86.Jump8IfEqual(c.code, 0x00) + case asm.JNE: + c.code = x86.Jump8IfNotEqual(c.code, 0x00) + case asm.JG: + c.code = x86.Jump8IfGreater(c.code, 0x00) + case asm.JGE: + c.code = x86.Jump8IfGreaterOrEqual(c.code, 0x00) + case asm.JL: + c.code = x86.Jump8IfLess(c.code, 0x00) + case asm.JLE: + c.code = x86.Jump8IfLessOrEqual(c.code, 0x00) + case asm.JUMP: + c.code = x86.Jump8(c.code, 0x00) + } + + label := c.assembler.Param.Label[instruction.Index] + size := 1 + + pointer := &pointer{ + Position: Address(len(c.code) - size), + OpSize: 1, + Size: uint8(size), + } + + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[label.Name] + + if !exists { + panic(fmt.Sprintf("unknown jump label %s", label.Name)) + } + + distance := destination - (pointer.Position + Address(pointer.Size)) + return distance + } + + c.codePointers = append(c.codePointers, pointer) +} From 1269a09049bf62aeebe9793747c18cac256b13c8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 6 Apr 2025 20:37:26 +0200 Subject: [PATCH 0984/1012] Refactored assembly compilation --- src/asmc/ARM.go | 320 --------------------------- src/asmc/Finalize.go | 8 +- src/asmc/X86.go | 218 ------------------ src/asmc/armCompiler.go | 420 +++++++++++++++++++++++++++++++++++ src/asmc/call.go | 44 ---- src/asmc/dllCall.go | 38 ---- src/asmc/jumpARM.go | 52 ----- src/asmc/jumpX86.go | 49 ----- src/asmc/load.go | 19 -- src/asmc/move.go | 60 ----- src/asmc/store.go | 55 ----- src/asmc/x86Compiler.go | 474 ++++++++++++++++++++++++++++++++++++++++ 12 files changed, 900 insertions(+), 857 deletions(-) delete mode 100644 src/asmc/ARM.go delete mode 100644 src/asmc/X86.go create mode 100644 src/asmc/armCompiler.go delete mode 100644 src/asmc/call.go delete mode 100644 src/asmc/dllCall.go delete mode 100644 src/asmc/jumpARM.go delete mode 100644 src/asmc/jumpX86.go delete mode 100644 src/asmc/load.go delete mode 100644 src/asmc/move.go delete mode 100644 src/asmc/store.go create mode 100644 src/asmc/x86Compiler.go diff --git a/src/asmc/ARM.go b/src/asmc/ARM.go deleted file mode 100644 index 42ff3a6..0000000 --- a/src/asmc/ARM.go +++ /dev/null @@ -1,320 +0,0 @@ -package asmc - -import ( - "encoding/binary" - "fmt" - - "git.urbach.dev/cli/q/src/arm" - "git.urbach.dev/cli/q/src/asm" -) - -func (c *compiler) ARM(x asm.Instruction) { - switch x.Mnemonic { - case asm.MOVE: - switch x.Type { - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.MoveRegisterRegister(operands.Destination, operands.Source)) - - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) - - case asm.TypeRegisterLabel: - operands := c.assembler.Param.RegisterLabel[x.Index] - position := Address(len(c.code)) - c.append(arm.LoadAddress(operands.Register, 0)) - - if operands.Label.Type == asm.DataLabel { - c.dataPointers = append(c.dataPointers, &pointer{ - Position: position, - OpSize: 0, - Size: 4, - Resolve: func() Address { - destination, exists := c.dataLabels[operands.Label.Name] - - if !exists { - panic("unknown label") - } - - destination += c.dataStart - c.codeStart - distance := destination - position + 8 - return arm.LoadAddress(operands.Register, int(distance)) - }, - }) - } else { - panic("not implemented") - } - } - - case asm.CALL: - switch x.Type { - case asm.TypeLabel: - label := c.assembler.Param.Label[x.Index] - position := Address(len(c.code)) - c.append(arm.Call(0)) - - pointer := &pointer{ - Position: position, - OpSize: 0, - Size: 4, - } - - pointer.Resolve = func() Address { - destination, exists := c.codeLabels[label.Name] - - if !exists { - panic(fmt.Sprintf("unknown jump label %s", label.Name)) - } - - distance := (destination - position) / 4 - return arm.Call(distance) - } - - c.codePointers = append(c.codePointers, pointer) - - default: - panic("not implemented") - } - - case asm.LABEL: - label := c.assembler.Param.Label[x.Index] - c.codeLabels[label.Name] = Address(len(c.code)) - - if label.Type == asm.FunctionLabel { - c.append(arm.StorePair(arm.FP, arm.LR, arm.SP, -16)) - c.append(arm.MoveRegisterRegister(arm.FP, arm.SP)) - } - - case asm.LOAD: - switch x.Type { - case asm.TypeMemoryRegister: - operands := c.assembler.Param.MemoryRegister[x.Index] - - if operands.Address.OffsetRegister < 0 { - c.append(arm.LoadRegister(operands.Register, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) - } else { - panic("not implemented") - } - } - - case asm.STORE: - switch x.Type { - case asm.TypeMemoryRegister: - operands := c.assembler.Param.MemoryRegister[x.Index] - - if operands.Address.OffsetRegister < 0 { - c.append(arm.StoreRegister(operands.Register, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) - } else { - panic("not implemented") - } - - case asm.TypeMemoryNumber: - operands := c.assembler.Param.MemoryNumber[x.Index] - - if operands.Address.OffsetRegister < 0 { - c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operands.Number) - c.append(arm.StoreRegister(arm.TMP, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) - } else { - panic("not implemented") - } - } - - case asm.RETURN: - c.append(arm.LoadPair(arm.FP, arm.LR, arm.SP, 16)) - c.append(arm.Return()) - - case asm.SYSCALL: - c.append(arm.Syscall()) - - case asm.PUSH: - switch x.Type { - case asm.TypeRegister: - operand := c.assembler.Param.Register[x.Index] - code, _ := arm.SubRegisterNumber(arm.SP, arm.SP, 16) - c.append(code) - c.append(arm.StoreRegister(operand.Register, arm.SP, 0, 8)) - } - - case asm.POP: - switch x.Type { - case asm.TypeRegister: - operand := c.assembler.Param.Register[x.Index] - c.append(arm.LoadRegister(operand.Register, arm.SP, 0, 8)) - code, _ := arm.AddRegisterNumber(arm.SP, arm.SP, 16) - c.append(code) - } - - case asm.AND: - switch x.Type { - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - code, encodable := arm.AndRegisterNumber(operand.Register, operand.Register, operand.Number) - - if encodable { - c.append(code) - } else { - c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) - c.append(arm.AndRegisterRegister(operand.Register, operand.Register, arm.TMP)) - } - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.AndRegisterRegister(operand.Destination, operand.Destination, operand.Source)) - } - - case asm.OR: - switch x.Type { - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - code, encodable := arm.OrRegisterNumber(operand.Register, operand.Register, operand.Number) - - if encodable { - c.append(code) - } else { - c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) - c.append(arm.OrRegisterRegister(operand.Register, operand.Register, arm.TMP)) - } - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.OrRegisterRegister(operand.Destination, operand.Destination, operand.Source)) - } - - case asm.XOR: - switch x.Type { - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - code, encodable := arm.XorRegisterNumber(operand.Register, operand.Register, operand.Number) - - if encodable { - c.append(code) - } else { - c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) - c.append(arm.XorRegisterRegister(operand.Register, operand.Register, arm.TMP)) - } - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.XorRegisterRegister(operand.Destination, operand.Destination, operand.Source)) - } - - case asm.ADD: - switch x.Type { - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - code, encodable := arm.AddRegisterNumber(operand.Register, operand.Register, operand.Number) - - if encodable { - c.append(code) - } else { - c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) - c.append(arm.AddRegisterRegister(operand.Register, operand.Register, arm.TMP)) - } - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.AddRegisterRegister(operand.Destination, operand.Destination, operand.Source)) - } - - case asm.SUB: - switch x.Type { - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - code, encodable := arm.SubRegisterNumber(operand.Register, operand.Register, operand.Number) - - if encodable { - c.append(code) - } else { - c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) - c.append(arm.SubRegisterRegister(operand.Register, operand.Register, arm.TMP)) - } - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.SubRegisterRegister(operand.Destination, operand.Destination, operand.Source)) - } - - case asm.COMPARE: - switch x.Type { - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - code, encodable := arm.CompareRegisterNumber(operand.Register, operand.Number) - - if encodable { - c.append(code) - } else { - c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) - c.append(arm.CompareRegisterRegister(operand.Register, arm.TMP)) - } - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.CompareRegisterRegister(operand.Destination, operand.Source)) - } - - case asm.DIV: - switch x.Type { - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.DivSigned(operand.Destination, operand.Destination, operand.Source)) - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) - c.append(arm.DivSigned(operand.Register, operand.Register, arm.TMP)) - } - - case asm.MUL: - switch x.Type { - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.MulRegisterRegister(operand.Destination, operand.Destination, operand.Source)) - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) - c.append(arm.MulRegisterRegister(operand.Register, operand.Register, arm.TMP)) - } - - case asm.MODULO: - switch x.Type { - case asm.TypeRegisterRegister: - operand := c.assembler.Param.RegisterRegister[x.Index] - c.append(arm.DivSigned(arm.TMP, operand.Destination, operand.Source)) - c.append(arm.MultiplySubtract(operand.Destination, arm.TMP, operand.Source, operand.Destination)) - case asm.TypeRegisterNumber: - operand := c.assembler.Param.RegisterNumber[x.Index] - c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) - c.append(arm.DivSigned(arm.TMP2, operand.Register, arm.TMP)) - c.append(arm.MultiplySubtract(operand.Register, arm.TMP2, arm.TMP, operand.Register)) - } - - case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: - c.jumpARM(x) - - case asm.SHIFTL: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.append(arm.ShiftLeftNumber(operands.Register, operands.Register, operands.Number&0b111111)) - case asm.TypeRegisterRegister: - panic("not implemented") - } - - case asm.SHIFTRS: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.append(arm.ShiftRightSignedNumber(operands.Register, operands.Register, operands.Number&0b111111)) - case asm.TypeRegisterRegister: - panic("not implemented") - } - - case asm.NEGATE: - switch x.Type { - case asm.TypeRegister: - operands := c.assembler.Param.Register[x.Index] - c.append(arm.NegateRegister(operands.Register, operands.Register)) - } - - default: - panic("unknown mnemonic: " + x.Mnemonic.String()) - } -} - -func (c *compiler) append(code uint32) { - c.code = binary.LittleEndian.AppendUint32(c.code, code) -} diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go index ec9e678..93e04ab 100644 --- a/src/asmc/Finalize.go +++ b/src/asmc/Finalize.go @@ -27,13 +27,17 @@ func Finalize(a *asm.Assembler, dlls dll.List) ([]byte, []byte) { switch config.TargetArch { case config.ARM: + armc := armCompiler{compiler: &c} + for _, x := range a.Instructions { - c.ARM(x) + armc.Compile(x) } case config.X86: + x86c := x86Compiler{compiler: &c} + for _, x := range a.Instructions { - c.X86(x) + x86c.Compile(x) } } diff --git a/src/asmc/X86.go b/src/asmc/X86.go deleted file mode 100644 index 5cdf60f..0000000 --- a/src/asmc/X86.go +++ /dev/null @@ -1,218 +0,0 @@ -package asmc - -import ( - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/x86" -) - -func (c *compiler) X86(x asm.Instruction) { - switch x.Mnemonic { - case asm.MOVE: - c.move(x) - - case asm.CALL: - c.call(x) - - case asm.LABEL: - label := c.assembler.Param.Label[x.Index] - c.codeLabels[label.Name] = Address(len(c.code)) - - case asm.LOAD: - c.load(x) - - case asm.STORE: - c.store(x) - - case asm.RETURN: - c.code = x86.Return(c.code) - - case asm.SYSCALL: - c.code = x86.Syscall(c.code) - - case asm.ADD: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.AddRegisterNumber(c.code, operands.Register, operands.Number) - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.code = x86.AddRegisterRegister(c.code, operands.Destination, operands.Source) - } - - case asm.AND: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.AndRegisterNumber(c.code, operands.Register, operands.Number) - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.code = x86.AndRegisterRegister(c.code, operands.Destination, operands.Source) - } - - case asm.SUB: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.SubRegisterNumber(c.code, operands.Register, operands.Number) - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.code = x86.SubRegisterRegister(c.code, operands.Destination, operands.Source) - } - - case asm.MUL: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.MulRegisterNumber(c.code, operands.Register, operands.Number) - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.code = x86.MulRegisterRegister(c.code, operands.Destination, operands.Source) - } - - case asm.DIV: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - - if operands.Register != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Register) - } - - c.code = x86.MoveRegisterNumber(c.code, x86.TMP, operands.Number) - c.code = x86.ExtendRAXToRDX(c.code) - c.code = x86.DivRegister(c.code, x86.TMP) - - if operands.Register != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.RAX) - } - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - - if operands.Destination != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) - } - - c.code = x86.ExtendRAXToRDX(c.code) - c.code = x86.DivRegister(c.code, operands.Source) - - if operands.Destination != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.RAX) - } - } - - case asm.MODULO: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - - if operands.Register != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Register) - } - - c.code = x86.MoveRegisterNumber(c.code, x86.TMP, operands.Number) - c.code = x86.ExtendRAXToRDX(c.code) - c.code = x86.DivRegister(c.code, x86.TMP) - - if operands.Register != x86.RDX { - c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.RDX) - } - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - - if operands.Destination != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) - } - - c.code = x86.ExtendRAXToRDX(c.code) - c.code = x86.DivRegister(c.code, operands.Source) - - if operands.Destination != x86.RDX { - c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.RDX) - } - } - - case asm.COMMENT: - return - - case asm.COMPARE: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.CompareRegisterNumber(c.code, operands.Register, operands.Number) - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.code = x86.CompareRegisterRegister(c.code, operands.Destination, operands.Source) - } - - case asm.DLLCALL: - c.dllCall(x) - - case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: - c.jumpX86(x) - - case asm.NEGATE: - switch x.Type { - case asm.TypeRegister: - operands := c.assembler.Param.Register[x.Index] - c.code = x86.NegateRegister(c.code, operands.Register) - } - - case asm.OR: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.OrRegisterNumber(c.code, operands.Register, operands.Number) - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.code = x86.OrRegisterRegister(c.code, operands.Destination, operands.Source) - } - - case asm.POP: - switch x.Type { - case asm.TypeRegister: - operands := c.assembler.Param.Register[x.Index] - c.code = x86.PopRegister(c.code, operands.Register) - } - - case asm.PUSH: - switch x.Type { - case asm.TypeNumber: - operands := c.assembler.Param.Number[x.Index] - c.code = x86.PushNumber(c.code, int32(operands.Number)) - case asm.TypeRegister: - operands := c.assembler.Param.Register[x.Index] - c.code = x86.PushRegister(c.code, operands.Register) - } - - case asm.SHIFTL: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.ShiftLeftNumber(c.code, operands.Register, byte(operands.Number)&0b111111) - case asm.TypeRegisterRegister: - panic("not implemented") - } - - case asm.SHIFTRS: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.ShiftRightSignedNumber(c.code, operands.Register, byte(operands.Number)&0b111111) - case asm.TypeRegisterRegister: - panic("not implemented") - } - - case asm.XOR: - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.XorRegisterNumber(c.code, operands.Register, operands.Number) - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.code = x86.XorRegisterRegister(c.code, operands.Destination, operands.Source) - } - - default: - panic("unknown mnemonic: " + x.Mnemonic.String()) - } -} diff --git a/src/asmc/armCompiler.go b/src/asmc/armCompiler.go new file mode 100644 index 0000000..6c11240 --- /dev/null +++ b/src/asmc/armCompiler.go @@ -0,0 +1,420 @@ +package asmc + +import ( + "encoding/binary" + "fmt" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/asm" +) + +type armCompiler struct { + *compiler +} + +func (c *armCompiler) Compile(instruction asm.Instruction) { + switch instruction.Mnemonic { + case asm.MOVE: + c.handleMoveInstruction(instruction) + case asm.CALL: + c.handleCallInstruction(instruction) + case asm.LABEL: + c.handleLabelInstruction(instruction) + case asm.LOAD: + c.handleLoadInstruction(instruction) + case asm.STORE: + c.handleStoreInstruction(instruction) + case asm.RETURN: + c.append(arm.LoadPair(arm.FP, arm.LR, arm.SP, 16)) + c.append(arm.Return()) + case asm.SYSCALL: + c.append(arm.Syscall()) + case asm.PUSH: + c.handlePushInstruction(instruction) + case asm.POP: + c.handlePopInstruction(instruction) + case asm.AND: + c.handleAndInstruction(instruction) + case asm.OR: + c.handleOrInstruction(instruction) + case asm.XOR: + c.handleXorInstruction(instruction) + case asm.ADD: + c.handleAddInstruction(instruction) + case asm.SUB: + c.handleSubInstruction(instruction) + case asm.COMPARE: + c.handleCompareInstruction(instruction) + case asm.DIV: + c.handleDivInstruction(instruction) + case asm.MUL: + c.handleMulInstruction(instruction) + case asm.MODULO: + c.handleModuloInstruction(instruction) + case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: + c.handleJumpInstruction(instruction) + case asm.SHIFTL: + c.handleShiftLeftInstruction(instruction) + case asm.SHIFTRS: + c.handleShiftRightSignedInstruction(instruction) + case asm.NEGATE: + c.handleNegateInstruction(instruction) + default: + panic("unknown mnemonic: " + instruction.Mnemonic.String()) + } +} + +func (c *armCompiler) handleMoveInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.MoveRegisterRegister(operands.Destination, operands.Source)) + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterLabel: + operands := c.assembler.Param.RegisterLabel[instruction.Index] + position := Address(len(c.code)) + c.append(arm.LoadAddress(operands.Register, 0)) + + if operands.Label.Type == asm.DataLabel { + c.dataPointers = append(c.dataPointers, c.createDataPointer(operands, position)) + } else { + panic("not implemented") + } + } +} + +func (c *armCompiler) handleCallInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeLabel: + label := c.assembler.Param.Label[instruction.Index] + position := Address(len(c.code)) + c.append(arm.Call(0)) + + pointer := &pointer{ + Position: position, + OpSize: 0, + Size: 4, + } + + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[label.Name] + if !exists { + panic(fmt.Sprintf("unknown jump label %s", label.Name)) + } + distance := (destination - position) / 4 + return arm.Call(distance) + } + + c.codePointers = append(c.codePointers, pointer) + default: + panic("not implemented") + } +} + +func (c *armCompiler) handleLabelInstruction(instruction asm.Instruction) { + label := c.assembler.Param.Label[instruction.Index] + c.codeLabels[label.Name] = Address(len(c.code)) + + if label.Type == asm.FunctionLabel { + c.append(arm.StorePair(arm.FP, arm.LR, arm.SP, -16)) + c.append(arm.MoveRegisterRegister(arm.FP, arm.SP)) + } +} + +func (c *armCompiler) handleLoadInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeMemoryRegister: + operands := c.assembler.Param.MemoryRegister[instruction.Index] + + if operands.Address.OffsetRegister < 0 { + c.append(arm.LoadRegister(operands.Register, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) + } else { + panic("not implemented") + } + } +} + +func (c *armCompiler) handleStoreInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeMemoryRegister: + operands := c.assembler.Param.MemoryRegister[instruction.Index] + + if operands.Address.OffsetRegister < 0 { + c.append(arm.StoreRegister(operands.Register, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) + } else { + panic("not implemented") + } + case asm.TypeMemoryNumber: + operands := c.assembler.Param.MemoryNumber[instruction.Index] + + if operands.Address.OffsetRegister < 0 { + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operands.Number) + c.append(arm.StoreRegister(arm.TMP, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) + } else { + panic("not implemented") + } + } +} + +func (c *armCompiler) handlePushInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegister: + operand := c.assembler.Param.Register[instruction.Index] + code, _ := arm.SubRegisterNumber(arm.SP, arm.SP, 16) + c.append(code) + c.append(arm.StoreRegister(operand.Register, arm.SP, 0, 8)) + } +} + +func (c *armCompiler) handlePopInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegister: + operand := c.assembler.Param.Register[instruction.Index] + c.append(arm.LoadRegister(operand.Register, arm.SP, 0, 8)) + code, _ := arm.AddRegisterNumber(arm.SP, arm.SP, 16) + c.append(code) + } +} + +func (c *armCompiler) handleAndInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + code, encodable := arm.AndRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) + c.append(arm.AndRegisterRegister(operand.Register, operand.Register, arm.TMP)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.AndRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } +} + +func (c *armCompiler) handleOrInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + code, encodable := arm.OrRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) + c.append(arm.OrRegisterRegister(operand.Register, operand.Register, arm.TMP)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.OrRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } +} + +func (c *armCompiler) handleXorInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + code, encodable := arm.XorRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + c.code = arm.MoveRegisterNumberMI(c.code, arm.TMP, operand.Number) + c.append(arm.XorRegisterRegister(operand.Register, operand.Register, arm.TMP)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.XorRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } +} + +func (c *armCompiler) handleAddInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + code, encodable := arm.AddRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.AddRegisterRegister(operand.Register, operand.Register, arm.TMP)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.AddRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } +} + +func (c *armCompiler) handleSubInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + code, encodable := arm.SubRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.SubRegisterRegister(operand.Register, operand.Register, arm.TMP)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.SubRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } +} + +func (c *armCompiler) handleCompareInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + code, encodable := arm.CompareRegisterNumber(operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.CompareRegisterRegister(operand.Register, arm.TMP)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.CompareRegisterRegister(operand.Destination, operand.Source)) + } +} + +func (c *armCompiler) handleDivInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.DivSigned(operand.Destination, operand.Destination, operand.Source)) + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.DivSigned(operand.Register, operand.Register, arm.TMP)) + } +} + +func (c *armCompiler) handleMulInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.MulRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.MulRegisterRegister(operand.Register, operand.Register, arm.TMP)) + } +} + +func (c *armCompiler) handleModuloInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[instruction.Index] + c.append(arm.DivSigned(arm.TMP, operand.Destination, operand.Source)) + c.append(arm.MultiplySubtract(operand.Destination, arm.TMP, operand.Source, operand.Destination)) + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = arm.MoveRegisterNumber(c.code, arm.TMP, operand.Number) + c.append(arm.DivSigned(arm.TMP2, operand.Register, arm.TMP)) + c.append(arm.MultiplySubtract(operand.Register, arm.TMP2, arm.TMP, operand.Register)) + } +} + +func (c *armCompiler) handleJumpInstruction(x asm.Instruction) { + label := c.assembler.Param.Label[x.Index] + mnemonic := x.Mnemonic + position := Address(len(c.code)) + + pointer := &pointer{ + Position: position, + Size: 4, + } + + c.append(arm.Nop()) + + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[label.Name] + + if !exists { + panic(fmt.Sprintf("unknown jump label %s", label.Name)) + } + + distance := (int(destination) - int(position)) / 4 + + switch mnemonic { + case asm.JE: + return arm.JumpIfEqual(distance) + case asm.JNE: + return arm.JumpIfNotEqual(distance) + case asm.JG: + return arm.JumpIfGreater(distance) + case asm.JGE: + return arm.JumpIfGreaterOrEqual(distance) + case asm.JL: + return arm.JumpIfLess(distance) + case asm.JLE: + return arm.JumpIfLessOrEqual(distance) + case asm.JUMP: + return arm.Jump(distance) + default: + panic("not implemented") + } + } + + c.codePointers = append(c.codePointers, pointer) +} + +func (c *armCompiler) handleShiftLeftInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.append(arm.ShiftLeftNumber(operands.Register, operands.Register, operands.Number&0b111111)) + case asm.TypeRegisterRegister: + panic("not implemented") + } +} + +func (c *armCompiler) handleShiftRightSignedInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.append(arm.ShiftRightSignedNumber(operands.Register, operands.Register, operands.Number&0b111111)) + case asm.TypeRegisterRegister: + panic("not implemented") + } +} + +func (c *armCompiler) handleNegateInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegister: + operands := c.assembler.Param.Register[instruction.Index] + c.append(arm.NegateRegister(operands.Register, operands.Register)) + } +} + +func (c *armCompiler) createDataPointer(operands asm.RegisterLabel, position Address) *pointer { + return &pointer{ + Position: position, + OpSize: 0, + Size: 4, + Resolve: func() Address { + destination, exists := c.dataLabels[operands.Label.Name] + + if !exists { + panic("unknown label") + } + + destination += c.dataStart - c.codeStart + distance := destination - position + 8 + return arm.LoadAddress(operands.Register, int(distance)) + }, + } +} + +func (c *compiler) append(code uint32) { + c.code = binary.LittleEndian.AppendUint32(c.code, code) +} diff --git a/src/asmc/call.go b/src/asmc/call.go deleted file mode 100644 index c941bb6..0000000 --- a/src/asmc/call.go +++ /dev/null @@ -1,44 +0,0 @@ -package asmc - -import ( - "fmt" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/x86" -) - -func (c *compiler) call(x asm.Instruction) { - switch x.Type { - case asm.TypeLabel: - data := c.assembler.Param.Label[x.Index] - c.code = x86.Call(c.code, 0x00_00_00_00) - size := 4 - - pointer := &pointer{ - Position: Address(len(c.code) - size), - OpSize: 1, - Size: uint8(size), - } - - pointer.Resolve = func() Address { - destination, exists := c.codeLabels[data.Name] - - if !exists { - panic(fmt.Sprintf("unknown jump label %s", data.Name)) - } - - distance := destination - (pointer.Position + Address(pointer.Size)) - return distance - } - - c.codePointers = append(c.codePointers, pointer) - - case asm.TypeRegister: - data := c.assembler.Param.Register[x.Index] - c.code = x86.CallRegister(c.code, data.Register) - - case asm.TypeMemory: - data := c.assembler.Param.Memory[x.Index] - c.code = x86.CallAtMemory(c.code, data.Base, data.Offset) - } -} diff --git a/src/asmc/dllCall.go b/src/asmc/dllCall.go deleted file mode 100644 index 203fbc1..0000000 --- a/src/asmc/dllCall.go +++ /dev/null @@ -1,38 +0,0 @@ -package asmc - -import ( - "strings" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/x86" -) - -func (c *compiler) dllCall(x asm.Instruction) { - label := c.assembler.Param.Label[x.Index] - c.code = x86.CallAt(c.code, 0x00_00_00_00) - next := Address(len(c.code)) - position := next - 4 - - pointer := &pointer{ - Position: Address(position), - OpSize: 2, - Size: 4, - } - - pointer.Resolve = func() Address { - dot := strings.Index(label.Name, ".") - library := label.Name[:dot] - funcName := label.Name[dot+1:] - index := c.dlls.Index(library, funcName) - - if index == -1 { - panic("unknown DLL function " + label.Name) - } - - destination := c.importsStart + Address(index*8) - from := c.codeStart + next - return destination - from - } - - c.dllPointers = append(c.dllPointers, pointer) -} diff --git a/src/asmc/jumpARM.go b/src/asmc/jumpARM.go deleted file mode 100644 index 81a96ee..0000000 --- a/src/asmc/jumpARM.go +++ /dev/null @@ -1,52 +0,0 @@ -package asmc - -import ( - "fmt" - - "git.urbach.dev/cli/q/src/arm" - "git.urbach.dev/cli/q/src/asm" -) - -func (c *compiler) jumpARM(x asm.Instruction) { - label := c.assembler.Param.Label[x.Index] - mnemonic := x.Mnemonic - position := Address(len(c.code)) - - pointer := &pointer{ - Position: position, - Size: 4, - } - - c.append(arm.Nop()) - - pointer.Resolve = func() Address { - destination, exists := c.codeLabels[label.Name] - - if !exists { - panic(fmt.Sprintf("unknown jump label %s", label.Name)) - } - - distance := (int(destination) - int(position)) / 4 - - switch mnemonic { - case asm.JE: - return arm.JumpIfEqual(distance) - case asm.JNE: - return arm.JumpIfNotEqual(distance) - case asm.JG: - return arm.JumpIfGreater(distance) - case asm.JGE: - return arm.JumpIfGreaterOrEqual(distance) - case asm.JL: - return arm.JumpIfLess(distance) - case asm.JLE: - return arm.JumpIfLessOrEqual(distance) - case asm.JUMP: - return arm.Jump(distance) - default: - panic("not implemented") - } - } - - c.codePointers = append(c.codePointers, pointer) -} diff --git a/src/asmc/jumpX86.go b/src/asmc/jumpX86.go deleted file mode 100644 index e9ace20..0000000 --- a/src/asmc/jumpX86.go +++ /dev/null @@ -1,49 +0,0 @@ -package asmc - -import ( - "fmt" - - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/x86" -) - -func (c *compiler) jumpX86(x asm.Instruction) { - switch x.Mnemonic { - case asm.JE: - c.code = x86.Jump8IfEqual(c.code, 0x00) - case asm.JNE: - c.code = x86.Jump8IfNotEqual(c.code, 0x00) - case asm.JG: - c.code = x86.Jump8IfGreater(c.code, 0x00) - case asm.JGE: - c.code = x86.Jump8IfGreaterOrEqual(c.code, 0x00) - case asm.JL: - c.code = x86.Jump8IfLess(c.code, 0x00) - case asm.JLE: - c.code = x86.Jump8IfLessOrEqual(c.code, 0x00) - case asm.JUMP: - c.code = x86.Jump8(c.code, 0x00) - } - - label := c.assembler.Param.Label[x.Index] - size := 1 - - pointer := &pointer{ - Position: Address(len(c.code) - size), - OpSize: 1, - Size: uint8(size), - } - - pointer.Resolve = func() Address { - destination, exists := c.codeLabels[label.Name] - - if !exists { - panic(fmt.Sprintf("unknown jump label %s", label.Name)) - } - - distance := destination - (pointer.Position + Address(pointer.Size)) - return distance - } - - c.codePointers = append(c.codePointers, pointer) -} diff --git a/src/asmc/load.go b/src/asmc/load.go deleted file mode 100644 index 845092b..0000000 --- a/src/asmc/load.go +++ /dev/null @@ -1,19 +0,0 @@ -package asmc - -import ( - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/x86" -) - -func (c *compiler) load(x asm.Instruction) { - switch x.Type { - case asm.TypeMemoryRegister: - operands := c.assembler.Param.MemoryRegister[x.Index] - - if operands.Address.OffsetRegister < 0 { - c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Base, operands.Address.Offset, operands.Address.Length) - } else { - c.code = x86.LoadDynamicRegister(c.code, operands.Register, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length) - } - } -} diff --git a/src/asmc/move.go b/src/asmc/move.go deleted file mode 100644 index 3963ba5..0000000 --- a/src/asmc/move.go +++ /dev/null @@ -1,60 +0,0 @@ -package asmc - -import ( - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/x86" -) - -func (c *compiler) move(x asm.Instruction) { - switch x.Type { - case asm.TypeRegisterNumber: - operands := c.assembler.Param.RegisterNumber[x.Index] - c.code = x86.MoveRegisterNumber(c.code, operands.Register, operands.Number) - - case asm.TypeRegisterRegister: - operands := c.assembler.Param.RegisterRegister[x.Index] - c.code = x86.MoveRegisterRegister(c.code, operands.Destination, operands.Source) - - case asm.TypeRegisterLabel: - operands := c.assembler.Param.RegisterLabel[x.Index] - start := Address(len(c.code)) - c.code = x86.LoadAddress(c.code, operands.Register, 0x00_00_00_00) - end := Address(len(c.code)) - position := end - 4 - opSize := position - start - - if operands.Label.Type == asm.DataLabel { - c.dataPointers = append(c.dataPointers, &pointer{ - Position: position, - OpSize: uint8(opSize), - Size: uint8(4), - Resolve: func() Address { - destination, exists := c.dataLabels[operands.Label.Name] - - if !exists { - panic("unknown label") - } - - destination += c.dataStart - c.codeStart - distance := destination - end - return distance + 8 - }, - }) - } else { - c.codePointers = append(c.codePointers, &pointer{ - Position: position, - OpSize: uint8(opSize), - Size: uint8(4), - Resolve: func() Address { - destination, exists := c.codeLabels[operands.Label.Name] - - if !exists { - panic("unknown label") - } - - return destination - end - }, - }) - } - } -} diff --git a/src/asmc/store.go b/src/asmc/store.go deleted file mode 100644 index 42352c4..0000000 --- a/src/asmc/store.go +++ /dev/null @@ -1,55 +0,0 @@ -package asmc - -import ( - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/config" - "git.urbach.dev/cli/q/src/x86" -) - -func (c *compiler) store(x asm.Instruction) { - switch x.Type { - case asm.TypeMemoryNumber: - operands := c.assembler.Param.MemoryNumber[x.Index] - - if operands.Address.OffsetRegister < 0 { - c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) - } else { - c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) - } - case asm.TypeMemoryLabel: - operands := c.assembler.Param.MemoryLabel[x.Index] - start := len(c.code) - - if operands.Address.OffsetRegister < 0 { - c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, 0b00_00_00_00) - } else { - c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, 0b00_00_00_00) - } - - size := 4 - opSize := len(c.code) - size - start - - c.codePointers = append(c.codePointers, &pointer{ - Position: Address(len(c.code) - size), - OpSize: uint8(opSize), - Size: uint8(size), - Resolve: func() Address { - destination, exists := c.codeLabels[operands.Label.Name] - - if !exists { - panic("unknown label") - } - - return config.BaseAddress + c.codeStart + destination - }, - }) - case asm.TypeMemoryRegister: - operands := c.assembler.Param.MemoryRegister[x.Index] - - if operands.Address.OffsetRegister < 0 { - c.code = x86.StoreRegister(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) - } else { - c.code = x86.StoreDynamicRegister(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) - } - } -} diff --git a/src/asmc/x86Compiler.go b/src/asmc/x86Compiler.go new file mode 100644 index 0000000..b721039 --- /dev/null +++ b/src/asmc/x86Compiler.go @@ -0,0 +1,474 @@ +package asmc + +import ( + "fmt" + "strings" + + "git.urbach.dev/cli/q/src/asm" + "git.urbach.dev/cli/q/src/config" + "git.urbach.dev/cli/q/src/x86" +) + +type x86Compiler struct { + *compiler +} + +func (c *x86Compiler) Compile(instruction asm.Instruction) { + switch instruction.Mnemonic { + case asm.MOVE: + c.handleMoveInstruction(instruction) + case asm.CALL: + c.handleCallInstruction(instruction) + case asm.LABEL: + label := c.assembler.Param.Label[instruction.Index] + c.codeLabels[label.Name] = Address(len(c.code)) + case asm.LOAD: + c.handleLoadInstruction(instruction) + case asm.STORE: + c.handleStoreInstruction(instruction) + case asm.RETURN: + c.code = x86.Return(c.code) + case asm.SYSCALL: + c.code = x86.Syscall(c.code) + case asm.ADD: + c.handleAddInstruction(instruction) + case asm.AND: + c.handleAndInstruction(instruction) + case asm.DIV: + c.handleDivInstruction(instruction) + case asm.MODULO: + c.handleModuloInstruction(instruction) + case asm.MUL: + c.handleMulInstruction(instruction) + case asm.OR: + c.handleOrInstruction(instruction) + case asm.SUB: + c.handleSubInstruction(instruction) + case asm.XOR: + c.handleXorInstruction(instruction) + case asm.NEGATE: + c.handleNegateInstruction(instruction) + case asm.POP: + c.handlePopInstruction(instruction) + case asm.PUSH: + c.handlePushInstruction(instruction) + case asm.SHIFTL: + c.handleShiftLeftInstruction(instruction) + case asm.SHIFTRS: + c.handleShiftRightSignedInstruction(instruction) + case asm.COMPARE: + c.handleCompareInstruction(instruction) + case asm.DLLCALL: + c.handleDllCallInstruction(instruction) + case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP: + c.handleJumpInstruction(instruction) + default: + panic("unknown mnemonic: " + instruction.Mnemonic.String()) + } +} + +func (c *x86Compiler) handleMoveInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.MoveRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.code = x86.MoveRegisterRegister(c.code, operands.Destination, operands.Source) + case asm.TypeRegisterLabel: + operands := c.assembler.Param.RegisterLabel[instruction.Index] + start := Address(len(c.code)) + c.code = x86.LoadAddress(c.code, operands.Register, 0x00_00_00_00) + end := Address(len(c.code)) + position := end - 4 + opSize := position - start + + if operands.Label.Type == asm.DataLabel { + c.dataPointers = append(c.dataPointers, &pointer{ + Position: position, + OpSize: uint8(opSize), + Size: uint8(4), + Resolve: func() Address { + destination, exists := c.dataLabels[operands.Label.Name] + + if !exists { + panic("unknown label") + } + + destination += c.dataStart - c.codeStart + distance := destination - end + return distance + 8 + }, + }) + } else { + c.codePointers = append(c.codePointers, &pointer{ + Position: position, + OpSize: uint8(opSize), + Size: uint8(4), + Resolve: func() Address { + destination, exists := c.codeLabels[operands.Label.Name] + + if !exists { + panic("unknown label") + } + + return destination - end + }, + }) + } + } +} + +func (c *x86Compiler) handleCallInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeLabel: + data := c.assembler.Param.Label[instruction.Index] + c.code = x86.Call(c.code, 0x00_00_00_00) + size := 4 + + pointer := &pointer{ + Position: Address(len(c.code) - size), + OpSize: 1, + Size: uint8(size), + } + + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[data.Name] + + if !exists { + panic(fmt.Sprintf("unknown jump label %s", data.Name)) + } + + distance := destination - (pointer.Position + Address(pointer.Size)) + return distance + } + + c.codePointers = append(c.codePointers, pointer) + case asm.TypeRegister: + data := c.assembler.Param.Register[instruction.Index] + c.code = x86.CallRegister(c.code, data.Register) + case asm.TypeMemory: + data := c.assembler.Param.Memory[instruction.Index] + c.code = x86.CallAtMemory(c.code, data.Base, data.Offset) + } +} + +func (c *x86Compiler) handleLoadInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeMemoryRegister: + operands := c.assembler.Param.MemoryRegister[instruction.Index] + + if operands.Address.OffsetRegister < 0 { + c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Base, operands.Address.Offset, operands.Address.Length) + } else { + c.code = x86.LoadDynamicRegister(c.code, operands.Register, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length) + } + } +} + +func (c *x86Compiler) handleStoreInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeMemoryNumber: + operands := c.assembler.Param.MemoryNumber[instruction.Index] + + if operands.Address.OffsetRegister < 0 { + c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number) + } else { + c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Number) + } + case asm.TypeMemoryLabel: + operands := c.assembler.Param.MemoryLabel[instruction.Index] + start := len(c.code) + + if operands.Address.OffsetRegister < 0 { + c.code = x86.StoreNumber(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, 0b00_00_00_00) + } else { + c.code = x86.StoreDynamicNumber(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, 0b00_00_00_00) + } + + size := 4 + opSize := len(c.code) - size - start + + c.codePointers = append(c.codePointers, &pointer{ + Position: Address(len(c.code) - size), + OpSize: uint8(opSize), + Size: uint8(size), + Resolve: func() Address { + destination, exists := c.codeLabels[operands.Label.Name] + + if !exists { + panic("unknown label") + } + + return config.BaseAddress + c.codeStart + destination + }, + }) + case asm.TypeMemoryRegister: + operands := c.assembler.Param.MemoryRegister[instruction.Index] + + if operands.Address.OffsetRegister < 0 { + c.code = x86.StoreRegister(c.code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register) + } else { + c.code = x86.StoreDynamicRegister(c.code, operands.Address.Base, operands.Address.OffsetRegister, operands.Address.Length, operands.Register) + } + } +} + +func (c *x86Compiler) handleAddInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.AddRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.code = x86.AddRegisterRegister(c.code, operands.Destination, operands.Source) + } +} + +func (c *x86Compiler) handleAndInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.AndRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.code = x86.AndRegisterRegister(c.code, operands.Destination, operands.Source) + } +} + +func (c *x86Compiler) handleDivInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + + if operands.Register != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Register) + } + + c.code = x86.MoveRegisterNumber(c.code, x86.TMP, operands.Number) + c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.DivRegister(c.code, x86.TMP) + + if operands.Register != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.RAX) + } + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + + if operands.Destination != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) + } + + c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.DivRegister(c.code, operands.Source) + + if operands.Destination != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.RAX) + } + } +} + +func (c *x86Compiler) handleModuloInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + + if operands.Register != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Register) + } + + c.code = x86.MoveRegisterNumber(c.code, x86.TMP, operands.Number) + c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.DivRegister(c.code, x86.TMP) + + if operands.Register != x86.RDX { + c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.RDX) + } + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + + if operands.Destination != x86.RAX { + c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) + } + + c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.DivRegister(c.code, operands.Source) + + if operands.Destination != x86.RDX { + c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.RDX) + } + } +} + +func (c *x86Compiler) handleMulInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.MulRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.code = x86.MulRegisterRegister(c.code, operands.Destination, operands.Source) + } +} + +func (c *x86Compiler) handleOrInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.OrRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.code = x86.OrRegisterRegister(c.code, operands.Destination, operands.Source) + } +} + +func (c *x86Compiler) handleSubInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.SubRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.code = x86.SubRegisterRegister(c.code, operands.Destination, operands.Source) + } +} + +func (c *x86Compiler) handleXorInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.XorRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.code = x86.XorRegisterRegister(c.code, operands.Destination, operands.Source) + } +} + +func (c *x86Compiler) handleNegateInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegister: + operands := c.assembler.Param.Register[instruction.Index] + c.code = x86.NegateRegister(c.code, operands.Register) + } +} + +func (c *x86Compiler) handlePopInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegister: + operands := c.assembler.Param.Register[instruction.Index] + c.code = x86.PopRegister(c.code, operands.Register) + } +} + +func (c *x86Compiler) handlePushInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeNumber: + operands := c.assembler.Param.Number[instruction.Index] + c.code = x86.PushNumber(c.code, int32(operands.Number)) + case asm.TypeRegister: + operands := c.assembler.Param.Register[instruction.Index] + c.code = x86.PushRegister(c.code, operands.Register) + } +} + +func (c *x86Compiler) handleShiftLeftInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.ShiftLeftNumber(c.code, operands.Register, byte(operands.Number)&0b111111) + case asm.TypeRegisterRegister: + panic("not implemented") + } +} + +func (c *x86Compiler) handleShiftRightSignedInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.ShiftRightSignedNumber(c.code, operands.Register, byte(operands.Number)&0b111111) + case asm.TypeRegisterRegister: + panic("not implemented") + } +} + +func (c *x86Compiler) handleCompareInstruction(instruction asm.Instruction) { + switch instruction.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[instruction.Index] + c.code = x86.CompareRegisterNumber(c.code, operands.Register, operands.Number) + case asm.TypeRegisterRegister: + operands := c.assembler.Param.RegisterRegister[instruction.Index] + c.code = x86.CompareRegisterRegister(c.code, operands.Destination, operands.Source) + } +} + +func (c *x86Compiler) handleDllCallInstruction(instruction asm.Instruction) { + label := c.assembler.Param.Label[instruction.Index] + c.code = x86.CallAt(c.code, 0x00_00_00_00) + next := Address(len(c.code)) + position := next - 4 + + pointer := &pointer{ + Position: Address(position), + OpSize: 2, + Size: 4, + } + + pointer.Resolve = func() Address { + dot := strings.Index(label.Name, ".") + library := label.Name[:dot] + funcName := label.Name[dot+1:] + index := c.dlls.Index(library, funcName) + + if index == -1 { + panic("unknown DLL function " + label.Name) + } + + destination := c.importsStart + Address(index*8) + from := c.codeStart + next + return destination - from + } + + c.dllPointers = append(c.dllPointers, pointer) +} + +func (c *x86Compiler) handleJumpInstruction(instruction asm.Instruction) { + switch instruction.Mnemonic { + case asm.JE: + c.code = x86.Jump8IfEqual(c.code, 0x00) + case asm.JNE: + c.code = x86.Jump8IfNotEqual(c.code, 0x00) + case asm.JG: + c.code = x86.Jump8IfGreater(c.code, 0x00) + case asm.JGE: + c.code = x86.Jump8IfGreaterOrEqual(c.code, 0x00) + case asm.JL: + c.code = x86.Jump8IfLess(c.code, 0x00) + case asm.JLE: + c.code = x86.Jump8IfLessOrEqual(c.code, 0x00) + case asm.JUMP: + c.code = x86.Jump8(c.code, 0x00) + } + + label := c.assembler.Param.Label[instruction.Index] + size := 1 + + pointer := &pointer{ + Position: Address(len(c.code) - size), + OpSize: 1, + Size: uint8(size), + } + + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[label.Name] + + if !exists { + panic(fmt.Sprintf("unknown jump label %s", label.Name)) + } + + distance := destination - (pointer.Position + Address(pointer.Size)) + return distance + } + + c.codePointers = append(c.codePointers, pointer) +} From 08ea91f46c47eedb27c4ff79e9e85a33b005600e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 7 Apr 2025 15:13:57 +0200 Subject: [PATCH 0985/1012] Added fmt package --- examples/collatz/collatz.q | 3 ++- examples/factorial/factorial.q | 4 ++-- examples/fibonacci/fibonacci.q | 4 ++-- examples/fizzbuzz/fizzbuzz.q | 3 ++- examples/gcd/gcd.q | 4 ++-- examples/itoa/itoa.q | 4 ++-- examples/prime/prime.q | 3 ++- lib/{io => fmt}/fmt.q | 2 +- lib/io/io.q | 8 -------- lib/io/read.q | 5 +++++ lib/io/write.q | 5 +++++ 11 files changed, 25 insertions(+), 20 deletions(-) rename lib/{io => fmt}/fmt.q (95%) create mode 100644 lib/io/read.q create mode 100644 lib/io/write.q diff --git a/examples/collatz/collatz.q b/examples/collatz/collatz.q index 00048d6..0b92c3a 100644 --- a/examples/collatz/collatz.q +++ b/examples/collatz/collatz.q @@ -1,3 +1,4 @@ +import fmt import io main() { @@ -12,7 +13,7 @@ collatz(x int) { x = 3 * x + 1 } - io.number(x) + fmt.decimal(x) if x == 1 { return diff --git a/examples/factorial/factorial.q b/examples/factorial/factorial.q index 07390b5..222153f 100644 --- a/examples/factorial/factorial.q +++ b/examples/factorial/factorial.q @@ -1,7 +1,7 @@ -import io +import fmt main() { - io.number(factorial(5)) + fmt.decimal(factorial(5)) } factorial(x int) -> int { diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q index d189253..db7b565 100644 --- a/examples/fibonacci/fibonacci.q +++ b/examples/fibonacci/fibonacci.q @@ -1,7 +1,7 @@ -import io +import fmt main() { - io.number(fibonacci(10)) + fmt.decimal(fibonacci(10)) } fibonacci(x int) -> int { diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q index e4986d1..bcdc027 100644 --- a/examples/fizzbuzz/fizzbuzz.q +++ b/examples/fizzbuzz/fizzbuzz.q @@ -1,3 +1,4 @@ +import fmt import io main() { @@ -12,7 +13,7 @@ fizzbuzz(n int) { x % 15 == 0 { io.out("FizzBuzz") } x % 5 == 0 { io.out("Buzz") } x % 3 == 0 { io.out("Fizz") } - _ { io.number(x) } + _ { fmt.decimal(x) } } x += 1 diff --git a/examples/gcd/gcd.q b/examples/gcd/gcd.q index 9b6ece1..7deb773 100644 --- a/examples/gcd/gcd.q +++ b/examples/gcd/gcd.q @@ -1,7 +1,7 @@ -import io +import fmt main() { - io.number(gcd(1071, 462)) + fmt.decimal(gcd(1071, 462)) } gcd(a int, b int) -> int { diff --git a/examples/itoa/itoa.q b/examples/itoa/itoa.q index 72d3599..68916fc 100644 --- a/examples/itoa/itoa.q +++ b/examples/itoa/itoa.q @@ -1,5 +1,5 @@ -import io +import fmt main() { - io.number(9223372036854775807) + fmt.decimal(9223372036854775807) } \ No newline at end of file diff --git a/examples/prime/prime.q b/examples/prime/prime.q index f29c701..fae869c 100644 --- a/examples/prime/prime.q +++ b/examples/prime/prime.q @@ -1,3 +1,4 @@ +import fmt import io main() { @@ -14,7 +15,7 @@ main() { io.out(" ") } - io.number(i) + fmt.decimal(i) } i += 1 diff --git a/lib/io/fmt.q b/lib/fmt/fmt.q similarity index 95% rename from lib/io/fmt.q rename to lib/fmt/fmt.q index 230571c..311eb44 100644 --- a/lib/io/fmt.q +++ b/lib/fmt/fmt.q @@ -1,7 +1,7 @@ import mem import sys -number(x int) { +decimal(x int) { buffer := mem.alloc(20) address, count := itoa(x, buffer) sys.write(1, address, count) diff --git a/lib/io/io.q b/lib/io/io.q index 6c83d05..98ee9fe 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -12,14 +12,6 @@ error(buffer []byte) -> int { return sys.write(std.err, buffer, len(buffer)) } -read(fd int, buffer []byte) -> int { - return sys.read(fd, buffer, len(buffer)) -} - -write(fd int, buffer []byte) -> int { - return sys.write(fd, buffer, len(buffer)) -} - const { std { in 0 diff --git a/lib/io/read.q b/lib/io/read.q new file mode 100644 index 0000000..7756c17 --- /dev/null +++ b/lib/io/read.q @@ -0,0 +1,5 @@ +import sys + +read(fd int, buffer []byte) -> int { + return sys.read(fd, buffer, len(buffer)) +} \ No newline at end of file diff --git a/lib/io/write.q b/lib/io/write.q new file mode 100644 index 0000000..a46a22d --- /dev/null +++ b/lib/io/write.q @@ -0,0 +1,5 @@ +import sys + +write(fd int, buffer []byte) -> int { + return sys.write(fd, buffer, len(buffer)) +} \ No newline at end of file From 127b87f4eab5614cd7041ff3d4923b630bbe47ba Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 7 Apr 2025 15:13:57 +0200 Subject: [PATCH 0986/1012] Added fmt package --- examples/collatz/collatz.q | 3 ++- examples/factorial/factorial.q | 4 ++-- examples/fibonacci/fibonacci.q | 4 ++-- examples/fizzbuzz/fizzbuzz.q | 3 ++- examples/gcd/gcd.q | 4 ++-- examples/itoa/itoa.q | 4 ++-- examples/prime/prime.q | 3 ++- lib/{io => fmt}/fmt.q | 2 +- lib/io/io.q | 8 -------- lib/io/read.q | 5 +++++ lib/io/write.q | 5 +++++ 11 files changed, 25 insertions(+), 20 deletions(-) rename lib/{io => fmt}/fmt.q (95%) create mode 100644 lib/io/read.q create mode 100644 lib/io/write.q diff --git a/examples/collatz/collatz.q b/examples/collatz/collatz.q index 00048d6..0b92c3a 100644 --- a/examples/collatz/collatz.q +++ b/examples/collatz/collatz.q @@ -1,3 +1,4 @@ +import fmt import io main() { @@ -12,7 +13,7 @@ collatz(x int) { x = 3 * x + 1 } - io.number(x) + fmt.decimal(x) if x == 1 { return diff --git a/examples/factorial/factorial.q b/examples/factorial/factorial.q index 07390b5..222153f 100644 --- a/examples/factorial/factorial.q +++ b/examples/factorial/factorial.q @@ -1,7 +1,7 @@ -import io +import fmt main() { - io.number(factorial(5)) + fmt.decimal(factorial(5)) } factorial(x int) -> int { diff --git a/examples/fibonacci/fibonacci.q b/examples/fibonacci/fibonacci.q index d189253..db7b565 100644 --- a/examples/fibonacci/fibonacci.q +++ b/examples/fibonacci/fibonacci.q @@ -1,7 +1,7 @@ -import io +import fmt main() { - io.number(fibonacci(10)) + fmt.decimal(fibonacci(10)) } fibonacci(x int) -> int { diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q index e4986d1..bcdc027 100644 --- a/examples/fizzbuzz/fizzbuzz.q +++ b/examples/fizzbuzz/fizzbuzz.q @@ -1,3 +1,4 @@ +import fmt import io main() { @@ -12,7 +13,7 @@ fizzbuzz(n int) { x % 15 == 0 { io.out("FizzBuzz") } x % 5 == 0 { io.out("Buzz") } x % 3 == 0 { io.out("Fizz") } - _ { io.number(x) } + _ { fmt.decimal(x) } } x += 1 diff --git a/examples/gcd/gcd.q b/examples/gcd/gcd.q index 9b6ece1..7deb773 100644 --- a/examples/gcd/gcd.q +++ b/examples/gcd/gcd.q @@ -1,7 +1,7 @@ -import io +import fmt main() { - io.number(gcd(1071, 462)) + fmt.decimal(gcd(1071, 462)) } gcd(a int, b int) -> int { diff --git a/examples/itoa/itoa.q b/examples/itoa/itoa.q index 72d3599..68916fc 100644 --- a/examples/itoa/itoa.q +++ b/examples/itoa/itoa.q @@ -1,5 +1,5 @@ -import io +import fmt main() { - io.number(9223372036854775807) + fmt.decimal(9223372036854775807) } \ No newline at end of file diff --git a/examples/prime/prime.q b/examples/prime/prime.q index f29c701..fae869c 100644 --- a/examples/prime/prime.q +++ b/examples/prime/prime.q @@ -1,3 +1,4 @@ +import fmt import io main() { @@ -14,7 +15,7 @@ main() { io.out(" ") } - io.number(i) + fmt.decimal(i) } i += 1 diff --git a/lib/io/fmt.q b/lib/fmt/fmt.q similarity index 95% rename from lib/io/fmt.q rename to lib/fmt/fmt.q index 230571c..311eb44 100644 --- a/lib/io/fmt.q +++ b/lib/fmt/fmt.q @@ -1,7 +1,7 @@ import mem import sys -number(x int) { +decimal(x int) { buffer := mem.alloc(20) address, count := itoa(x, buffer) sys.write(1, address, count) diff --git a/lib/io/io.q b/lib/io/io.q index 6c83d05..98ee9fe 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -12,14 +12,6 @@ error(buffer []byte) -> int { return sys.write(std.err, buffer, len(buffer)) } -read(fd int, buffer []byte) -> int { - return sys.read(fd, buffer, len(buffer)) -} - -write(fd int, buffer []byte) -> int { - return sys.write(fd, buffer, len(buffer)) -} - const { std { in 0 diff --git a/lib/io/read.q b/lib/io/read.q new file mode 100644 index 0000000..7756c17 --- /dev/null +++ b/lib/io/read.q @@ -0,0 +1,5 @@ +import sys + +read(fd int, buffer []byte) -> int { + return sys.read(fd, buffer, len(buffer)) +} \ No newline at end of file diff --git a/lib/io/write.q b/lib/io/write.q new file mode 100644 index 0000000..a46a22d --- /dev/null +++ b/lib/io/write.q @@ -0,0 +1,5 @@ +import sys + +write(fd int, buffer []byte) -> int { + return sys.write(fd, buffer, len(buffer)) +} \ No newline at end of file From a9d783c6753d1590c67e985dca9c764f39f2e453 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 7 Apr 2025 18:02:36 +0200 Subject: [PATCH 0987/1012] Refactored the interface of the io package --- examples/array/array.q | 2 +- examples/collatz/collatz.q | 2 +- examples/fizzbuzz/fizzbuzz.q | 8 ++++---- examples/hello/hello.q | 2 +- examples/point/point.q | 4 ++-- examples/prime/prime.q | 2 +- examples/server/server.q | 12 ++++++------ examples/shell/shell.q | 2 +- examples/thread/thread.q | 4 ++-- lib/io/io.q | 14 -------------- lib/io/read.q | 6 +++++- lib/io/write.q | 6 +++++- 12 files changed, 29 insertions(+), 35 deletions(-) diff --git a/examples/array/array.q b/examples/array/array.q index 69c3e45..3d8571f 100644 --- a/examples/array/array.q +++ b/examples/array/array.q @@ -8,6 +8,6 @@ main() { buffer[2] = 'l' buffer[3] = 'l' buffer[4] = 'o' - io.out(buffer) + io.write(buffer) mem.free(buffer) } \ No newline at end of file diff --git a/examples/collatz/collatz.q b/examples/collatz/collatz.q index 0b92c3a..5df31c3 100644 --- a/examples/collatz/collatz.q +++ b/examples/collatz/collatz.q @@ -19,6 +19,6 @@ collatz(x int) { return } - io.out(" ") + io.write(" ") } } \ No newline at end of file diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q index bcdc027..3862f0f 100644 --- a/examples/fizzbuzz/fizzbuzz.q +++ b/examples/fizzbuzz/fizzbuzz.q @@ -10,9 +10,9 @@ fizzbuzz(n int) { loop { switch { - x % 15 == 0 { io.out("FizzBuzz") } - x % 5 == 0 { io.out("Buzz") } - x % 3 == 0 { io.out("Fizz") } + x % 15 == 0 { io.write("FizzBuzz") } + x % 5 == 0 { io.write("Buzz") } + x % 3 == 0 { io.write("Fizz") } _ { fmt.decimal(x) } } @@ -22,6 +22,6 @@ fizzbuzz(n int) { return } - io.out(" ") + io.write(" ") } } \ No newline at end of file diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 0475212..05029db 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,5 +1,5 @@ import io main() { - io.out("Hello\n") + io.write("Hello\n") } \ No newline at end of file diff --git a/examples/point/point.q b/examples/point/point.q index de2a763..b34c1cc 100644 --- a/examples/point/point.q +++ b/examples/point/point.q @@ -1,5 +1,5 @@ +import io import mem -import sys Point { x int @@ -29,6 +29,6 @@ print(p *Point) { out[5] = ' ' out[6] = '0' + p.y out[7] = '\n' - sys.write(1, out, 8) + io.write(out) mem.free(out) } \ No newline at end of file diff --git a/examples/prime/prime.q b/examples/prime/prime.q index fae869c..33fcbf5 100644 --- a/examples/prime/prime.q +++ b/examples/prime/prime.q @@ -12,7 +12,7 @@ main() { if isPrime(i) { if i != 2 { - io.out(" ") + io.write(" ") } fmt.decimal(i) diff --git a/examples/server/server.q b/examples/server/server.q index f90c1c7..edb8699 100644 --- a/examples/server/server.q +++ b/examples/server/server.q @@ -6,30 +6,30 @@ main() { socket := sys.socket(2, 1, 0) if socket < 0 { - io.error("socket error\n") + io.write("socket error\n") sys.exit(1) } if net.bind(socket, 8080) != 0 { - io.error("bind error\n") + io.write("bind error\n") sys.exit(1) } if sys.listen(socket, 128) != 0 { - io.error("listen error\n") + io.write("listen error\n") sys.exit(1) } - io.out("listening...\n") + io.write("listening...\n") loop { conn := sys.accept(socket, 0, 0) if conn >= 0 { - io.write(conn, "HTTP/1.0 200 OK\r\nContent-Length: 6\r\n\r\nHello\n") + io.writeTo(conn, "HTTP/1.0 200 OK\r\nContent-Length: 6\r\n\r\nHello\n") sys.close(conn) } else { - io.error("accept error\n") + io.write("accept error\n") } } } \ No newline at end of file diff --git a/examples/shell/shell.q b/examples/shell/shell.q index f5680d6..c866aa4 100644 --- a/examples/shell/shell.q +++ b/examples/shell/shell.q @@ -7,7 +7,7 @@ main() { command := mem.alloc(length) loop { - io.out("λ ") + io.write("λ ") n := io.in(command) if n <= 0 { diff --git a/examples/thread/thread.q b/examples/thread/thread.q index b878ce8..d8d8960 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -10,6 +10,6 @@ main() { } work() { - io.out("[ ] start\n") - io.out("[x] end\n") + io.write("[ ] start\n") + io.write("[x] end\n") } \ No newline at end of file diff --git a/lib/io/io.q b/lib/io/io.q index 98ee9fe..730ddf1 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -1,17 +1,3 @@ -import sys - -in(buffer []byte) -> int { - return sys.read(std.in, buffer, len(buffer)) -} - -out(buffer []byte) -> int { - return sys.write(std.out, buffer, len(buffer)) -} - -error(buffer []byte) -> int { - return sys.write(std.err, buffer, len(buffer)) -} - const { std { in 0 diff --git a/lib/io/read.q b/lib/io/read.q index 7756c17..deb777b 100644 --- a/lib/io/read.q +++ b/lib/io/read.q @@ -1,5 +1,9 @@ import sys -read(fd int, buffer []byte) -> int { +read(buffer []byte) -> int { + return sys.read(std.in, buffer, len(buffer)) +} + +readFrom(fd int, buffer []byte) -> int { return sys.read(fd, buffer, len(buffer)) } \ No newline at end of file diff --git a/lib/io/write.q b/lib/io/write.q index a46a22d..9d107dc 100644 --- a/lib/io/write.q +++ b/lib/io/write.q @@ -1,5 +1,9 @@ import sys -write(fd int, buffer []byte) -> int { +write(buffer []byte) -> int { + return sys.write(std.out, buffer, len(buffer)) +} + +writeTo(fd int, buffer []byte) -> int { return sys.write(fd, buffer, len(buffer)) } \ No newline at end of file From 013d3dd8cd28805835173850cde5ebf9ddda05a2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 7 Apr 2025 18:02:36 +0200 Subject: [PATCH 0988/1012] Refactored the interface of the io package --- examples/array/array.q | 2 +- examples/collatz/collatz.q | 2 +- examples/fizzbuzz/fizzbuzz.q | 8 ++++---- examples/hello/hello.q | 2 +- examples/point/point.q | 4 ++-- examples/prime/prime.q | 2 +- examples/server/server.q | 12 ++++++------ examples/shell/shell.q | 2 +- examples/thread/thread.q | 4 ++-- lib/io/io.q | 14 -------------- lib/io/read.q | 6 +++++- lib/io/write.q | 6 +++++- 12 files changed, 29 insertions(+), 35 deletions(-) diff --git a/examples/array/array.q b/examples/array/array.q index 69c3e45..3d8571f 100644 --- a/examples/array/array.q +++ b/examples/array/array.q @@ -8,6 +8,6 @@ main() { buffer[2] = 'l' buffer[3] = 'l' buffer[4] = 'o' - io.out(buffer) + io.write(buffer) mem.free(buffer) } \ No newline at end of file diff --git a/examples/collatz/collatz.q b/examples/collatz/collatz.q index 0b92c3a..5df31c3 100644 --- a/examples/collatz/collatz.q +++ b/examples/collatz/collatz.q @@ -19,6 +19,6 @@ collatz(x int) { return } - io.out(" ") + io.write(" ") } } \ No newline at end of file diff --git a/examples/fizzbuzz/fizzbuzz.q b/examples/fizzbuzz/fizzbuzz.q index bcdc027..3862f0f 100644 --- a/examples/fizzbuzz/fizzbuzz.q +++ b/examples/fizzbuzz/fizzbuzz.q @@ -10,9 +10,9 @@ fizzbuzz(n int) { loop { switch { - x % 15 == 0 { io.out("FizzBuzz") } - x % 5 == 0 { io.out("Buzz") } - x % 3 == 0 { io.out("Fizz") } + x % 15 == 0 { io.write("FizzBuzz") } + x % 5 == 0 { io.write("Buzz") } + x % 3 == 0 { io.write("Fizz") } _ { fmt.decimal(x) } } @@ -22,6 +22,6 @@ fizzbuzz(n int) { return } - io.out(" ") + io.write(" ") } } \ No newline at end of file diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 0475212..05029db 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,5 +1,5 @@ import io main() { - io.out("Hello\n") + io.write("Hello\n") } \ No newline at end of file diff --git a/examples/point/point.q b/examples/point/point.q index de2a763..b34c1cc 100644 --- a/examples/point/point.q +++ b/examples/point/point.q @@ -1,5 +1,5 @@ +import io import mem -import sys Point { x int @@ -29,6 +29,6 @@ print(p *Point) { out[5] = ' ' out[6] = '0' + p.y out[7] = '\n' - sys.write(1, out, 8) + io.write(out) mem.free(out) } \ No newline at end of file diff --git a/examples/prime/prime.q b/examples/prime/prime.q index fae869c..33fcbf5 100644 --- a/examples/prime/prime.q +++ b/examples/prime/prime.q @@ -12,7 +12,7 @@ main() { if isPrime(i) { if i != 2 { - io.out(" ") + io.write(" ") } fmt.decimal(i) diff --git a/examples/server/server.q b/examples/server/server.q index f90c1c7..edb8699 100644 --- a/examples/server/server.q +++ b/examples/server/server.q @@ -6,30 +6,30 @@ main() { socket := sys.socket(2, 1, 0) if socket < 0 { - io.error("socket error\n") + io.write("socket error\n") sys.exit(1) } if net.bind(socket, 8080) != 0 { - io.error("bind error\n") + io.write("bind error\n") sys.exit(1) } if sys.listen(socket, 128) != 0 { - io.error("listen error\n") + io.write("listen error\n") sys.exit(1) } - io.out("listening...\n") + io.write("listening...\n") loop { conn := sys.accept(socket, 0, 0) if conn >= 0 { - io.write(conn, "HTTP/1.0 200 OK\r\nContent-Length: 6\r\n\r\nHello\n") + io.writeTo(conn, "HTTP/1.0 200 OK\r\nContent-Length: 6\r\n\r\nHello\n") sys.close(conn) } else { - io.error("accept error\n") + io.write("accept error\n") } } } \ No newline at end of file diff --git a/examples/shell/shell.q b/examples/shell/shell.q index f5680d6..c866aa4 100644 --- a/examples/shell/shell.q +++ b/examples/shell/shell.q @@ -7,7 +7,7 @@ main() { command := mem.alloc(length) loop { - io.out("λ ") + io.write("λ ") n := io.in(command) if n <= 0 { diff --git a/examples/thread/thread.q b/examples/thread/thread.q index b878ce8..d8d8960 100644 --- a/examples/thread/thread.q +++ b/examples/thread/thread.q @@ -10,6 +10,6 @@ main() { } work() { - io.out("[ ] start\n") - io.out("[x] end\n") + io.write("[ ] start\n") + io.write("[x] end\n") } \ No newline at end of file diff --git a/lib/io/io.q b/lib/io/io.q index 98ee9fe..730ddf1 100644 --- a/lib/io/io.q +++ b/lib/io/io.q @@ -1,17 +1,3 @@ -import sys - -in(buffer []byte) -> int { - return sys.read(std.in, buffer, len(buffer)) -} - -out(buffer []byte) -> int { - return sys.write(std.out, buffer, len(buffer)) -} - -error(buffer []byte) -> int { - return sys.write(std.err, buffer, len(buffer)) -} - const { std { in 0 diff --git a/lib/io/read.q b/lib/io/read.q index 7756c17..deb777b 100644 --- a/lib/io/read.q +++ b/lib/io/read.q @@ -1,5 +1,9 @@ import sys -read(fd int, buffer []byte) -> int { +read(buffer []byte) -> int { + return sys.read(std.in, buffer, len(buffer)) +} + +readFrom(fd int, buffer []byte) -> int { return sys.read(fd, buffer, len(buffer)) } \ No newline at end of file diff --git a/lib/io/write.q b/lib/io/write.q index a46a22d..9d107dc 100644 --- a/lib/io/write.q +++ b/lib/io/write.q @@ -1,5 +1,9 @@ import sys -write(fd int, buffer []byte) -> int { +write(buffer []byte) -> int { + return sys.write(std.out, buffer, len(buffer)) +} + +writeTo(fd int, buffer []byte) -> int { return sys.write(fd, buffer, len(buffer)) } \ No newline at end of file From 43a006e4af242cdd0b2ad2a81002d23289a07038 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 11 Apr 2025 14:21:51 +0200 Subject: [PATCH 0989/1012] Replaced builtin store function with a new syntax --- lib/mem/alloc_unix.q | 2 +- lib/mem/alloc_windows.q | 2 +- lib/thread/thread_linux.q | 4 ++-- src/core/CompileAssign.go | 7 ++++++ src/core/CompileCall.go | 3 --- src/core/CompileFor.go | 3 ++- src/core/CompileMemoryStore.go | 36 ------------------------------- src/core/EvaluateArray.go | 18 ++++++++++++++++ src/expression/Expression_test.go | 5 +++++ src/expression/Parse.go | 13 +++++++++-- tests/errors/TypeMismatch5.q | 4 ++++ tests/errors_test.go | 3 +++ 12 files changed, 54 insertions(+), 46 deletions(-) delete mode 100644 src/core/CompileMemoryStore.go create mode 100644 tests/errors/TypeMismatch5.q diff --git a/lib/mem/alloc_unix.q b/lib/mem/alloc_unix.q index e12a331..0720da0 100644 --- a/lib/mem/alloc_unix.q +++ b/lib/mem/alloc_unix.q @@ -7,6 +7,6 @@ alloc(length int) -> []byte { return x } - store(x, 8, length) + [x] = length return x + 8 } \ No newline at end of file diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index c0df727..1ee9838 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -5,7 +5,7 @@ alloc(length int) -> []byte { return x } - store(x, 8, length) + [x] = length return x + 8 } diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index 2e3db31..e452309 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -4,9 +4,9 @@ import sys create(func *any) -> int { stack := sys.mmap(0, 4096, 0x1|0x2, 0x02|0x20|0x100) stack += 4096 - 8 - store(stack, 8, core.exit) + [stack] = core.exit stack -= 8 - store(stack, 8, func) + [stack] = func return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0) } diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index af628ef..ee9d022 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -56,6 +56,13 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return err } + leftSize := leftValue.Memory.Length + rightSize := uint8(rightValue.Type().Size()) + + if rightSize != 0 && leftSize != rightSize { + panic("memory store length mismatch") + } + f.ValueToMemory(rightValue, leftValue.Memory) return nil } diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index fc5937b..c1cc23f 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -41,9 +41,6 @@ func (f *Function) CompileCall(root *expression.Expression) ([]*Parameter, error case "delete": return nil, f.CompileDelete(root) - - case "store": - return nil, f.CompileMemoryStore(root) } } diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index 2bdf9da..45ad2c3 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -46,7 +46,7 @@ func (f *Function) CompileFor(loop *ast.For) error { panic("could not recognize loop header") } - _, err := f.ExpressionToRegister(from, counter) + typ, err := f.ExpressionToRegister(from, counter) if err != nil { return err @@ -98,6 +98,7 @@ func (f *Function) CompileFor(loop *ast.For) error { variable = &scope.Variable{ Name: name, Value: eval.Register{ + Typ: typ, Register: counter, Alive: ast.Count(loop.Body, f.File.Bytes, token.Identifier, name), }, diff --git a/src/core/CompileMemoryStore.go b/src/core/CompileMemoryStore.go deleted file mode 100644 index 75e6da7..0000000 --- a/src/core/CompileMemoryStore.go +++ /dev/null @@ -1,36 +0,0 @@ -package core - -import ( - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/errors" - "git.urbach.dev/cli/q/src/expression" -) - -// CompileMemoryStore compiles a variable-width store to memory. -func (f *Function) CompileMemoryStore(root *expression.Expression) error { - parameters := root.Children[1:] - name := parameters[0].Token.Text(f.File.Bytes) - numBytes, err := f.ToNumber(parameters[1].Token) - - if err != nil { - return err - } - - value := parameters[2] - variable := f.VariableByName(name) - - if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, parameters[0].Token.Position) - } - - defer f.UseVariable(variable) - - memory := asm.Memory{ - Base: variable.Value.Register, - OffsetRegister: -1, - Length: byte(numBytes), - } - - _, err = f.ExpressionToMemory(value, memory) - return err -} diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go index 5e840a5..a114bf4 100644 --- a/src/core/EvaluateArray.go +++ b/src/core/EvaluateArray.go @@ -28,6 +28,24 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (*eval.Memory, err Length: byte(1), } + if len(expr.Children) == 1 { + pointer, isPointer := base.Value.Typ.(*types.Pointer) + + if !isPointer { + return nil, errors.New(&errors.TypeMismatch{Encountered: base.Value.Typ.Name(), Expected: types.AnyPointer.Name()}, f.File, expr.Token.Position) + } + + // TODO: This is a hack that needs to be removed + memory.Length = 8 + + value := &eval.Memory{ + Typ: pointer.To, + Memory: memory, + } + + return value, nil + } + indexExpr := expr.Children[1] index, err := f.Evaluate(indexExpr) diff --git a/src/expression/Expression_test.go b/src/expression/Expression_test.go index cfd2ea9..15c461c 100644 --- a/src/expression/Expression_test.go +++ b/src/expression/Expression_test.go @@ -112,6 +112,11 @@ func TestParse(t *testing.T) { {"Array access 6", "a.b()[c]", "(@ (λ (. a b)) c)"}, {"Array access 7", "a.b(c)[d]", "(@ (λ (. a b) c) d)"}, {"Array access 8", "a.b(c)[d][e]", "(@ (@ (λ (. a b) c) d) e)"}, + + {"Dereferencing", "[a]", "(@ a)"}, + {"Dereferencing 2", "[a+b]", "(@ (+ a b))"}, + {"Dereferencing 3", "[a+b]=c", "(= (@ (+ a b)) c)"}, + {"Dereferencing 3", "[a+b]=c+d", "(= (@ (+ a b)) (+ c d))"}, } for _, test := range tests { diff --git a/src/expression/Parse.go b/src/expression/Parse.go index 750fd29..cc61d6c 100644 --- a/src/expression/Parse.go +++ b/src/expression/Parse.go @@ -75,8 +75,17 @@ func Parse(tokens []token.Token) *Expression { group.Precedence = math.MaxInt8 if cursor == nil { - cursor = group - root = group + if t.Kind == token.ArrayEnd { + cursor = New() + cursor.Token.Position = tokens[groupPosition].Position + cursor.Token.Kind = token.Array + cursor.Precedence = precedence(token.Array) + cursor.AddChild(group) + root = cursor + } else { + cursor = group + root = group + } } else { cursor.AddChild(group) } diff --git a/tests/errors/TypeMismatch5.q b/tests/errors/TypeMismatch5.q new file mode 100644 index 0000000..9c01cd6 --- /dev/null +++ b/tests/errors/TypeMismatch5.q @@ -0,0 +1,4 @@ +main() { + a := 1 + [a] = 2 +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 8880905..5fa7c11 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -51,6 +51,9 @@ var errs = []struct { {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*any", Encountered: "int", ParameterName: "p"}}, {"TypeMismatch2.q", &errors.TypeMismatch{Expected: "[]any", Encountered: "int", ParameterName: "array"}}, + {"TypeMismatch3.q", &errors.TypeMismatch{Expected: "int", Encountered: "[]uint8"}}, + {"TypeMismatch4.q", &errors.TypeMismatch{Expected: "int", Encountered: "[]uint8"}}, + {"TypeMismatch5.q", &errors.TypeMismatch{Expected: "*any", Encountered: "int64"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}}, From 3da61f5c6942068de8231f516ca0dfdcc731c219 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 11 Apr 2025 14:21:51 +0200 Subject: [PATCH 0990/1012] Replaced builtin store function with a new syntax --- lib/mem/alloc_unix.q | 2 +- lib/mem/alloc_windows.q | 2 +- lib/thread/thread_linux.q | 4 ++-- src/core/CompileAssign.go | 7 ++++++ src/core/CompileCall.go | 3 --- src/core/CompileFor.go | 3 ++- src/core/CompileMemoryStore.go | 36 ------------------------------- src/core/EvaluateArray.go | 18 ++++++++++++++++ src/expression/Expression_test.go | 5 +++++ src/expression/Parse.go | 13 +++++++++-- tests/errors/TypeMismatch5.q | 4 ++++ tests/errors_test.go | 3 +++ 12 files changed, 54 insertions(+), 46 deletions(-) delete mode 100644 src/core/CompileMemoryStore.go create mode 100644 tests/errors/TypeMismatch5.q diff --git a/lib/mem/alloc_unix.q b/lib/mem/alloc_unix.q index e12a331..0720da0 100644 --- a/lib/mem/alloc_unix.q +++ b/lib/mem/alloc_unix.q @@ -7,6 +7,6 @@ alloc(length int) -> []byte { return x } - store(x, 8, length) + [x] = length return x + 8 } \ No newline at end of file diff --git a/lib/mem/alloc_windows.q b/lib/mem/alloc_windows.q index c0df727..1ee9838 100644 --- a/lib/mem/alloc_windows.q +++ b/lib/mem/alloc_windows.q @@ -5,7 +5,7 @@ alloc(length int) -> []byte { return x } - store(x, 8, length) + [x] = length return x + 8 } diff --git a/lib/thread/thread_linux.q b/lib/thread/thread_linux.q index 2e3db31..e452309 100644 --- a/lib/thread/thread_linux.q +++ b/lib/thread/thread_linux.q @@ -4,9 +4,9 @@ import sys create(func *any) -> int { stack := sys.mmap(0, 4096, 0x1|0x2, 0x02|0x20|0x100) stack += 4096 - 8 - store(stack, 8, core.exit) + [stack] = core.exit stack -= 8 - store(stack, 8, func) + [stack] = func return sys.clone(clone.vm|clone.fs|clone.files|clone.sighand|clone.parent|clone.thread|clone.io, stack, 0, 0, 0) } diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index af628ef..ee9d022 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -56,6 +56,13 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return err } + leftSize := leftValue.Memory.Length + rightSize := uint8(rightValue.Type().Size()) + + if rightSize != 0 && leftSize != rightSize { + panic("memory store length mismatch") + } + f.ValueToMemory(rightValue, leftValue.Memory) return nil } diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index fc5937b..c1cc23f 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -41,9 +41,6 @@ func (f *Function) CompileCall(root *expression.Expression) ([]*Parameter, error case "delete": return nil, f.CompileDelete(root) - - case "store": - return nil, f.CompileMemoryStore(root) } } diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go index 2bdf9da..45ad2c3 100644 --- a/src/core/CompileFor.go +++ b/src/core/CompileFor.go @@ -46,7 +46,7 @@ func (f *Function) CompileFor(loop *ast.For) error { panic("could not recognize loop header") } - _, err := f.ExpressionToRegister(from, counter) + typ, err := f.ExpressionToRegister(from, counter) if err != nil { return err @@ -98,6 +98,7 @@ func (f *Function) CompileFor(loop *ast.For) error { variable = &scope.Variable{ Name: name, Value: eval.Register{ + Typ: typ, Register: counter, Alive: ast.Count(loop.Body, f.File.Bytes, token.Identifier, name), }, diff --git a/src/core/CompileMemoryStore.go b/src/core/CompileMemoryStore.go deleted file mode 100644 index 75e6da7..0000000 --- a/src/core/CompileMemoryStore.go +++ /dev/null @@ -1,36 +0,0 @@ -package core - -import ( - "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/errors" - "git.urbach.dev/cli/q/src/expression" -) - -// CompileMemoryStore compiles a variable-width store to memory. -func (f *Function) CompileMemoryStore(root *expression.Expression) error { - parameters := root.Children[1:] - name := parameters[0].Token.Text(f.File.Bytes) - numBytes, err := f.ToNumber(parameters[1].Token) - - if err != nil { - return err - } - - value := parameters[2] - variable := f.VariableByName(name) - - if variable == nil { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, parameters[0].Token.Position) - } - - defer f.UseVariable(variable) - - memory := asm.Memory{ - Base: variable.Value.Register, - OffsetRegister: -1, - Length: byte(numBytes), - } - - _, err = f.ExpressionToMemory(value, memory) - return err -} diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go index 5e840a5..a114bf4 100644 --- a/src/core/EvaluateArray.go +++ b/src/core/EvaluateArray.go @@ -28,6 +28,24 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (*eval.Memory, err Length: byte(1), } + if len(expr.Children) == 1 { + pointer, isPointer := base.Value.Typ.(*types.Pointer) + + if !isPointer { + return nil, errors.New(&errors.TypeMismatch{Encountered: base.Value.Typ.Name(), Expected: types.AnyPointer.Name()}, f.File, expr.Token.Position) + } + + // TODO: This is a hack that needs to be removed + memory.Length = 8 + + value := &eval.Memory{ + Typ: pointer.To, + Memory: memory, + } + + return value, nil + } + indexExpr := expr.Children[1] index, err := f.Evaluate(indexExpr) diff --git a/src/expression/Expression_test.go b/src/expression/Expression_test.go index cfd2ea9..15c461c 100644 --- a/src/expression/Expression_test.go +++ b/src/expression/Expression_test.go @@ -112,6 +112,11 @@ func TestParse(t *testing.T) { {"Array access 6", "a.b()[c]", "(@ (λ (. a b)) c)"}, {"Array access 7", "a.b(c)[d]", "(@ (λ (. a b) c) d)"}, {"Array access 8", "a.b(c)[d][e]", "(@ (@ (λ (. a b) c) d) e)"}, + + {"Dereferencing", "[a]", "(@ a)"}, + {"Dereferencing 2", "[a+b]", "(@ (+ a b))"}, + {"Dereferencing 3", "[a+b]=c", "(= (@ (+ a b)) c)"}, + {"Dereferencing 3", "[a+b]=c+d", "(= (@ (+ a b)) (+ c d))"}, } for _, test := range tests { diff --git a/src/expression/Parse.go b/src/expression/Parse.go index 750fd29..cc61d6c 100644 --- a/src/expression/Parse.go +++ b/src/expression/Parse.go @@ -75,8 +75,17 @@ func Parse(tokens []token.Token) *Expression { group.Precedence = math.MaxInt8 if cursor == nil { - cursor = group - root = group + if t.Kind == token.ArrayEnd { + cursor = New() + cursor.Token.Position = tokens[groupPosition].Position + cursor.Token.Kind = token.Array + cursor.Precedence = precedence(token.Array) + cursor.AddChild(group) + root = cursor + } else { + cursor = group + root = group + } } else { cursor.AddChild(group) } diff --git a/tests/errors/TypeMismatch5.q b/tests/errors/TypeMismatch5.q new file mode 100644 index 0000000..9c01cd6 --- /dev/null +++ b/tests/errors/TypeMismatch5.q @@ -0,0 +1,4 @@ +main() { + a := 1 + [a] = 2 +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 8880905..5fa7c11 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -51,6 +51,9 @@ var errs = []struct { {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*any", Encountered: "int", ParameterName: "p"}}, {"TypeMismatch2.q", &errors.TypeMismatch{Expected: "[]any", Encountered: "int", ParameterName: "array"}}, + {"TypeMismatch3.q", &errors.TypeMismatch{Expected: "int", Encountered: "[]uint8"}}, + {"TypeMismatch4.q", &errors.TypeMismatch{Expected: "int", Encountered: "[]uint8"}}, + {"TypeMismatch5.q", &errors.TypeMismatch{Expected: "*any", Encountered: "int64"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}}, From 6483573923ab690461fd786f8767ef6ba5a99864 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 14 Apr 2025 15:58:15 +0200 Subject: [PATCH 0991/1012] Improved type safety for memory writes --- src/core/CompileAssign.go | 17 ++++++++-- src/core/EvaluateArray.go | 65 ++++++++++++------------------------ src/errors/Common.go | 1 + src/scanner/scanStruct.go | 9 +++-- tests/errors/TypeMismatch.q | 4 +-- tests/errors/TypeMismatch6.q | 6 ++++ tests/errors_test.go | 3 +- 7 files changed, 54 insertions(+), 51 deletions(-) create mode 100644 tests/errors/TypeMismatch6.q diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index ee9d022..56acd5c 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -8,6 +8,7 @@ import ( "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // CompileAssign compiles an assign statement. @@ -56,11 +57,23 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return err } + if !types.Is(rightValue.Type(), leftValue.Typ) { + return errors.New(&errors.TypeMismatch{Encountered: rightValue.Type().Name(), Expected: leftValue.Typ.Name()}, f.File, right.Token.Position) + } + leftSize := leftValue.Memory.Length rightSize := uint8(rightValue.Type().Size()) - if rightSize != 0 && leftSize != rightSize { - panic("memory store length mismatch") + switch { + case leftSize == 0: + leftSize = rightSize + leftValue.Memory.Length = rightSize + case rightSize == 0: + rightSize = leftSize + } + + if leftSize == 0 { + return errors.New(errors.UnknownMemorySize, f.File, operation.Position) } f.ValueToMemory(rightValue, leftValue.Memory) diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go index a114bf4..2e34b71 100644 --- a/src/core/EvaluateArray.go +++ b/src/core/EvaluateArray.go @@ -21,28 +21,27 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (*eval.Memory, err defer f.UseVariable(base) - memory := asm.Memory{ - Base: base.Value.Register, - Offset: 0, - OffsetRegister: -1, - Length: byte(1), + value := &eval.Memory{ + Memory: asm.Memory{ + Base: base.Value.Register, + Offset: 0, + OffsetRegister: -1, + Length: 0, + }, + } + + switch baseType := base.Value.Typ.(type) { + case *types.Array: + value.Typ = baseType.Of + value.Memory.Length = byte(baseType.Of.Size()) + case *types.Pointer: + value.Typ = baseType.To + value.Memory.Length = byte(baseType.To.Size()) + default: + return nil, errors.New(&errors.TypeMismatch{Encountered: base.Value.Typ.Name(), Expected: types.AnyPointer.Name()}, f.File, expr.Token.Position) } if len(expr.Children) == 1 { - pointer, isPointer := base.Value.Typ.(*types.Pointer) - - if !isPointer { - return nil, errors.New(&errors.TypeMismatch{Encountered: base.Value.Typ.Name(), Expected: types.AnyPointer.Name()}, f.File, expr.Token.Position) - } - - // TODO: This is a hack that needs to be removed - memory.Length = 8 - - value := &eval.Memory{ - Typ: pointer.To, - Memory: memory, - } - return value, nil } @@ -59,34 +58,12 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (*eval.Memory, err switch index := index.(type) { case *eval.Number: - memory.Offset = int8(index.Number) + value.Memory.Offset = int8(index.Number) case *eval.Register: - memory.OffsetRegister = index.Register + value.Memory.OffsetRegister = index.Register default: panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, index)) } - array, isArray := base.Value.Typ.(*types.Array) - - if isArray { - value := &eval.Memory{ - Typ: array.Of, - Memory: memory, - } - - return value, nil - } - - pointer, isPointer := base.Value.Typ.(*types.Pointer) - - if isPointer { - value := &eval.Memory{ - Typ: pointer.To, - Memory: memory, - } - - return value, nil - } - - panic("invalid type") + return value, nil } diff --git a/src/errors/Common.go b/src/errors/Common.go index 93097ee..c628d64 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -21,5 +21,6 @@ var ( MissingParameter = &Base{"Missing parameter"} MissingType = &Base{"Missing type"} NotImplemented = &Base{"Not implemented"} + UnknownMemorySize = &Base{"Unknown memory size"} UntypedExpression = &Base{"Untyped expression"} ) diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index dc70838..8b26f23 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -26,8 +26,13 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro fieldPosition := i fieldName := tokens[i].Text(file.Bytes) i++ - fieldTypeName := tokens[i].Text(file.Bytes) - i++ + typePosition := i + + for i < len(tokens) && tokens[i].Kind != token.NewLine && tokens[i].Kind != token.BlockEnd { + i++ + } + + fieldTypeName := tokens[typePosition:i].Text(file.Bytes) structure.AddField(&types.Field{ Name: fieldName, diff --git a/tests/errors/TypeMismatch.q b/tests/errors/TypeMismatch.q index 09d6221..2088006 100644 --- a/tests/errors/TypeMismatch.q +++ b/tests/errors/TypeMismatch.q @@ -2,6 +2,6 @@ main() { writeToMemory(42) } -writeToMemory(p *any) { - p[0] = 'A' +writeToMemory(p *byte) { + [p] = 'A' } \ No newline at end of file diff --git a/tests/errors/TypeMismatch6.q b/tests/errors/TypeMismatch6.q new file mode 100644 index 0000000..71784aa --- /dev/null +++ b/tests/errors/TypeMismatch6.q @@ -0,0 +1,6 @@ +import mem + +main() { + a := mem.alloc(1) + a[0] = int64(1) +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 5fa7c11..ddbf815 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -49,11 +49,12 @@ var errs = []struct { {"MissingParameter3.q", errors.MissingParameter}, {"MissingType.q", errors.MissingType}, {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, - {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*any", Encountered: "int", ParameterName: "p"}}, + {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*uint8", Encountered: "int", ParameterName: "p"}}, {"TypeMismatch2.q", &errors.TypeMismatch{Expected: "[]any", Encountered: "int", ParameterName: "array"}}, {"TypeMismatch3.q", &errors.TypeMismatch{Expected: "int", Encountered: "[]uint8"}}, {"TypeMismatch4.q", &errors.TypeMismatch{Expected: "int", Encountered: "[]uint8"}}, {"TypeMismatch5.q", &errors.TypeMismatch{Expected: "*any", Encountered: "int64"}}, + {"TypeMismatch6.q", &errors.TypeMismatch{Expected: "uint8", Encountered: "int64"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}}, From fb19ee2373b3f3b32d1e2cfa7f8ad25fe58861b2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 14 Apr 2025 15:58:15 +0200 Subject: [PATCH 0992/1012] Improved type safety for memory writes --- src/core/CompileAssign.go | 17 ++++++++-- src/core/EvaluateArray.go | 65 ++++++++++++------------------------ src/errors/Common.go | 1 + src/scanner/scanStruct.go | 9 +++-- tests/errors/TypeMismatch.q | 4 +-- tests/errors/TypeMismatch6.q | 6 ++++ tests/errors_test.go | 3 +- 7 files changed, 54 insertions(+), 51 deletions(-) create mode 100644 tests/errors/TypeMismatch6.q diff --git a/src/core/CompileAssign.go b/src/core/CompileAssign.go index ee9d022..56acd5c 100644 --- a/src/core/CompileAssign.go +++ b/src/core/CompileAssign.go @@ -8,6 +8,7 @@ import ( "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/eval" "git.urbach.dev/cli/q/src/token" + "git.urbach.dev/cli/q/src/types" ) // CompileAssign compiles an assign statement. @@ -56,11 +57,23 @@ func (f *Function) CompileAssign(node *ast.Assign) error { return err } + if !types.Is(rightValue.Type(), leftValue.Typ) { + return errors.New(&errors.TypeMismatch{Encountered: rightValue.Type().Name(), Expected: leftValue.Typ.Name()}, f.File, right.Token.Position) + } + leftSize := leftValue.Memory.Length rightSize := uint8(rightValue.Type().Size()) - if rightSize != 0 && leftSize != rightSize { - panic("memory store length mismatch") + switch { + case leftSize == 0: + leftSize = rightSize + leftValue.Memory.Length = rightSize + case rightSize == 0: + rightSize = leftSize + } + + if leftSize == 0 { + return errors.New(errors.UnknownMemorySize, f.File, operation.Position) } f.ValueToMemory(rightValue, leftValue.Memory) diff --git a/src/core/EvaluateArray.go b/src/core/EvaluateArray.go index a114bf4..2e34b71 100644 --- a/src/core/EvaluateArray.go +++ b/src/core/EvaluateArray.go @@ -21,28 +21,27 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (*eval.Memory, err defer f.UseVariable(base) - memory := asm.Memory{ - Base: base.Value.Register, - Offset: 0, - OffsetRegister: -1, - Length: byte(1), + value := &eval.Memory{ + Memory: asm.Memory{ + Base: base.Value.Register, + Offset: 0, + OffsetRegister: -1, + Length: 0, + }, + } + + switch baseType := base.Value.Typ.(type) { + case *types.Array: + value.Typ = baseType.Of + value.Memory.Length = byte(baseType.Of.Size()) + case *types.Pointer: + value.Typ = baseType.To + value.Memory.Length = byte(baseType.To.Size()) + default: + return nil, errors.New(&errors.TypeMismatch{Encountered: base.Value.Typ.Name(), Expected: types.AnyPointer.Name()}, f.File, expr.Token.Position) } if len(expr.Children) == 1 { - pointer, isPointer := base.Value.Typ.(*types.Pointer) - - if !isPointer { - return nil, errors.New(&errors.TypeMismatch{Encountered: base.Value.Typ.Name(), Expected: types.AnyPointer.Name()}, f.File, expr.Token.Position) - } - - // TODO: This is a hack that needs to be removed - memory.Length = 8 - - value := &eval.Memory{ - Typ: pointer.To, - Memory: memory, - } - return value, nil } @@ -59,34 +58,12 @@ func (f *Function) EvaluateArray(expr *expression.Expression) (*eval.Memory, err switch index := index.(type) { case *eval.Number: - memory.Offset = int8(index.Number) + value.Memory.Offset = int8(index.Number) case *eval.Register: - memory.OffsetRegister = index.Register + value.Memory.OffsetRegister = index.Register default: panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, index)) } - array, isArray := base.Value.Typ.(*types.Array) - - if isArray { - value := &eval.Memory{ - Typ: array.Of, - Memory: memory, - } - - return value, nil - } - - pointer, isPointer := base.Value.Typ.(*types.Pointer) - - if isPointer { - value := &eval.Memory{ - Typ: pointer.To, - Memory: memory, - } - - return value, nil - } - - panic("invalid type") + return value, nil } diff --git a/src/errors/Common.go b/src/errors/Common.go index 93097ee..c628d64 100644 --- a/src/errors/Common.go +++ b/src/errors/Common.go @@ -21,5 +21,6 @@ var ( MissingParameter = &Base{"Missing parameter"} MissingType = &Base{"Missing type"} NotImplemented = &Base{"Not implemented"} + UnknownMemorySize = &Base{"Unknown memory size"} UntypedExpression = &Base{"Untyped expression"} ) diff --git a/src/scanner/scanStruct.go b/src/scanner/scanStruct.go index dc70838..8b26f23 100644 --- a/src/scanner/scanStruct.go +++ b/src/scanner/scanStruct.go @@ -26,8 +26,13 @@ func (s *Scanner) scanStruct(file *fs.File, tokens token.List, i int) (int, erro fieldPosition := i fieldName := tokens[i].Text(file.Bytes) i++ - fieldTypeName := tokens[i].Text(file.Bytes) - i++ + typePosition := i + + for i < len(tokens) && tokens[i].Kind != token.NewLine && tokens[i].Kind != token.BlockEnd { + i++ + } + + fieldTypeName := tokens[typePosition:i].Text(file.Bytes) structure.AddField(&types.Field{ Name: fieldName, diff --git a/tests/errors/TypeMismatch.q b/tests/errors/TypeMismatch.q index 09d6221..2088006 100644 --- a/tests/errors/TypeMismatch.q +++ b/tests/errors/TypeMismatch.q @@ -2,6 +2,6 @@ main() { writeToMemory(42) } -writeToMemory(p *any) { - p[0] = 'A' +writeToMemory(p *byte) { + [p] = 'A' } \ No newline at end of file diff --git a/tests/errors/TypeMismatch6.q b/tests/errors/TypeMismatch6.q new file mode 100644 index 0000000..71784aa --- /dev/null +++ b/tests/errors/TypeMismatch6.q @@ -0,0 +1,6 @@ +import mem + +main() { + a := mem.alloc(1) + a[0] = int64(1) +} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index 5fa7c11..ddbf815 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -49,11 +49,12 @@ var errs = []struct { {"MissingParameter3.q", errors.MissingParameter}, {"MissingType.q", errors.MissingType}, {"ReturnCountMismatch.q", &errors.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, - {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*any", Encountered: "int", ParameterName: "p"}}, + {"TypeMismatch.q", &errors.TypeMismatch{Expected: "*uint8", Encountered: "int", ParameterName: "p"}}, {"TypeMismatch2.q", &errors.TypeMismatch{Expected: "[]any", Encountered: "int", ParameterName: "array"}}, {"TypeMismatch3.q", &errors.TypeMismatch{Expected: "int", Encountered: "[]uint8"}}, {"TypeMismatch4.q", &errors.TypeMismatch{Expected: "int", Encountered: "[]uint8"}}, {"TypeMismatch5.q", &errors.TypeMismatch{Expected: "*any", Encountered: "int64"}}, + {"TypeMismatch6.q", &errors.TypeMismatch{Expected: "uint8", Encountered: "int64"}}, {"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}}, From 19a8154e062467c86809363baf2258932e42f41e Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 15 Apr 2025 09:31:49 +0200 Subject: [PATCH 0993/1012] Removed old syscalls --- examples/shell/const.q | 4 ++ examples/shell/shell.q | 4 +- lib/sys/struct_linux.q | 2 +- lib/sys/struct_mac.q | 2 +- lib/sys/sys_linux.q | 87 +++++++++++++++++++++++++++++++ lib/sys/sys_linux_arm.q | 38 +++++++++----- lib/sys/sys_linux_x86.q | 113 +++++++++------------------------------- 7 files changed, 143 insertions(+), 107 deletions(-) create mode 100644 lib/sys/sys_linux.q diff --git a/examples/shell/const.q b/examples/shell/const.q index 20c1bfa..960cff6 100644 --- a/examples/shell/const.q +++ b/examples/shell/const.q @@ -3,6 +3,10 @@ const { pid 1 } + sig { + chld 17 + } + state { exited 0x4 } diff --git a/examples/shell/shell.q b/examples/shell/shell.q index c866aa4..5a2b017 100644 --- a/examples/shell/shell.q +++ b/examples/shell/shell.q @@ -8,14 +8,14 @@ main() { loop { io.write("λ ") - n := io.in(command) + n := io.read(command) if n <= 0 { return } command[n-1] = 0 - pid := sys.fork() + pid := sys.clone(sig.chld, 0, 0, 0, 0) if pid == 0 { sys.execve(command, 0, 0) diff --git a/lib/sys/struct_linux.q b/lib/sys/struct_linux.q index adbea0d..e07b9dd 100644 --- a/lib/sys/struct_linux.q +++ b/lib/sys/struct_linux.q @@ -1,6 +1,6 @@ sockaddr_in { sin_family int16 - sin_port int16 + sin_port uint16 sin_addr int64 sin_zero int64 } diff --git a/lib/sys/struct_mac.q b/lib/sys/struct_mac.q index 9172114..e64ddc7 100644 --- a/lib/sys/struct_mac.q +++ b/lib/sys/struct_mac.q @@ -1,7 +1,7 @@ sockaddr_in_bsd { sin_len int8 sin_family int8 - sin_port int16 + sin_port uint16 sin_addr int64 sin_zero int64 } \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q new file mode 100644 index 0000000..3c89ebf --- /dev/null +++ b/lib/sys/sys_linux.q @@ -0,0 +1,87 @@ +read(fd int, buffer *byte, length int) -> int { + return syscall(n.read, fd, buffer, length) +} + +write(fd int, buffer *byte, length int) -> int { + return syscall(n.write, fd, buffer, length) +} + +mmap(address int, length uint, protection int, flags int) -> *any { + return syscall(n.mmap, address, length, protection, flags) +} + +munmap(address *any, length uint) -> int { + return syscall(n.munmap, address, length) +} + +open(path *any, flags int, mode int) -> int { + return syscall(n.openat, -100, path, flags, mode) +} + +close(fd int) -> int { + return syscall(n.close, fd) +} + +clone(flags uint, stack *any, parent *int, child *int, tls uint) -> int { + return syscall(n.clone, flags, stack, parent, child, tls) +} + +execve(path *any, argv *any, envp *any) -> int { + return syscall(n.execve, path, argv, envp) +} + +exit(status int) { + syscall(n.exit, status) +} + +waitid(type int, id int, info *any, options int) -> int { + return syscall(n.waitid, type, id, info, options) +} + +socket(family int, type int, protocol int) -> int { + return syscall(n.socket, family, type, protocol) +} + +accept(fd int, address *any, length int) -> int { + return syscall(n.accept, fd, address, length) +} + +bind(fd int, address *sockaddr_in, length int) -> int { + return syscall(n.bind, fd, address, length) +} + +listen(fd int, backlog int) -> int { + return syscall(n.listen, fd, backlog) +} + +setsockopt(fd int, level int, optname int, optval *any, optlen int) -> int { + return syscall(n.setsockopt, fd, level, optname, optval, optlen) +} + +getcwd(buffer *any, length int) -> int { + return syscall(n.getcwd, buffer, length) +} + +chdir(path *any) -> int { + return syscall(n.chdir, path) +} + +rename(old *any, new *any) -> int { + return syscall(n.renameat, -100, old, -100, new) +} + +mkdir(path *any, mode int) -> int { + return syscall(n.mkdirat, -100, path, mode) +} + +rmdir(path *any) -> int { + return syscall(n.unlinkat, -100, path, 0x200) +} + +unlink(file *any) -> int { + return syscall(n.unlinkat, -100, file, 0) +} + +nanosleep(duration *timespec) -> int { + return syscall(n.nanosleep, duration, 0) +} \ No newline at end of file diff --git a/lib/sys/sys_linux_arm.q b/lib/sys/sys_linux_arm.q index cfc351e..b8c8e39 100644 --- a/lib/sys/sys_linux_arm.q +++ b/lib/sys/sys_linux_arm.q @@ -1,15 +1,25 @@ -read(fd int, buffer *byte, length int) -> int { - return syscall(63, fd, buffer, length) -} - -write(fd int, buffer *byte, length int) -> int { - return syscall(64, fd, buffer, length) -} - -mmap(address int, length uint, protection int, flags int) -> *any { - return syscall(222, address, length, protection, flags) -} - -munmap(address *any, length uint) -> int { - return syscall(215, address, length) +const { + n { + read 63 + write 64 + mmap 222 + munmap 215 + openat 56 + close 57 + clone 220 + execve 221 + exit 93 + waitid 95 + socket 198 + accept 202 + bind 200 + listen 201 + setsockopt 208 + getcwd 17 + chdir 49 + mkdirat 34 + unlinkat 35 + renameat 38 + nanosleep 101 + } } \ No newline at end of file diff --git a/lib/sys/sys_linux_x86.q b/lib/sys/sys_linux_x86.q index 4d82732..02266a7 100644 --- a/lib/sys/sys_linux_x86.q +++ b/lib/sys/sys_linux_x86.q @@ -1,91 +1,26 @@ -read(fd int, buffer *byte, length int) -> int { - return syscall(0, fd, buffer, length) +const { + n { + read 0 + write 1 + mmap 9 + munmap 11 + openat 257 + close 3 + clone 56 + execve 59 + exit 60 + waitid 247 + socket 41 + accept 43 + bind 49 + listen 50 + setsockopt 54 + getcwd 79 + chdir 80 + mkdirat 258 + unlinkat 263 + renameat 264 + nanosleep 35 + } } -write(fd int, buffer *byte, length int) -> int { - return syscall(1, fd, buffer, length) -} - -open(path *any, flags int, mode int) -> int { - return syscall(2, path, flags, mode) -} - -close(fd int) -> int { - return syscall(3, fd) -} - -mmap(address int, length uint, protection int, flags int) -> *any { - return syscall(9, address, length, protection, flags) -} - -munmap(address *any, length uint) -> int { - return syscall(11, address, length) -} - -clone(flags uint, stack *any, parent *int, child *int, tls uint) -> int { - return syscall(56, flags, stack, parent, child, tls) -} - -fork() -> int { - return syscall(57) -} - -execve(path *any, argv *any, envp *any) -> int { - return syscall(59, path, argv, envp) -} - -exit(status int) { - syscall(60, status) -} - -waitid(type int, id int, info *any, options int) -> int { - return syscall(247, type, id, info, options) -} - -socket(family int, type int, protocol int) -> int { - return syscall(41, family, type, protocol) -} - -accept(fd int, address *any, length int) -> int { - return syscall(43, fd, address, length) -} - -bind(fd int, address *sockaddr_in, length int) -> int { - return syscall(49, fd, address, length) -} - -listen(fd int, backlog int) -> int { - return syscall(50, fd, backlog) -} - -setsockopt(fd int, level int, optname int, optval *any, optlen int) -> int { - return syscall(54, fd, level, optname, optval, optlen) -} - -getcwd(buffer *any, length int) -> int { - return syscall(79, buffer, length) -} - -chdir(path *any) -> int { - return syscall(80, path) -} - -rename(old *any, new *any) -> int { - return syscall(82, old, new) -} - -mkdir(path *any, mode int) -> int { - return syscall(83, path, mode) -} - -rmdir(path *any) -> int { - return syscall(84, path) -} - -unlink(file *any) -> int { - return syscall(87, file) -} - -nanosleep(duration *timespec) -> int { - return syscall(35, duration, 0) -} \ No newline at end of file From 4550892f5869eead5ff4fe73ffd3a5a9ced1f02f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 15 Apr 2025 09:31:49 +0200 Subject: [PATCH 0994/1012] Removed old syscalls --- examples/shell/const.q | 4 ++ examples/shell/shell.q | 4 +- lib/sys/struct_linux.q | 2 +- lib/sys/struct_mac.q | 2 +- lib/sys/sys_linux.q | 87 +++++++++++++++++++++++++++++++ lib/sys/sys_linux_arm.q | 38 +++++++++----- lib/sys/sys_linux_x86.q | 113 +++++++++------------------------------- 7 files changed, 143 insertions(+), 107 deletions(-) create mode 100644 lib/sys/sys_linux.q diff --git a/examples/shell/const.q b/examples/shell/const.q index 20c1bfa..960cff6 100644 --- a/examples/shell/const.q +++ b/examples/shell/const.q @@ -3,6 +3,10 @@ const { pid 1 } + sig { + chld 17 + } + state { exited 0x4 } diff --git a/examples/shell/shell.q b/examples/shell/shell.q index c866aa4..5a2b017 100644 --- a/examples/shell/shell.q +++ b/examples/shell/shell.q @@ -8,14 +8,14 @@ main() { loop { io.write("λ ") - n := io.in(command) + n := io.read(command) if n <= 0 { return } command[n-1] = 0 - pid := sys.fork() + pid := sys.clone(sig.chld, 0, 0, 0, 0) if pid == 0 { sys.execve(command, 0, 0) diff --git a/lib/sys/struct_linux.q b/lib/sys/struct_linux.q index adbea0d..e07b9dd 100644 --- a/lib/sys/struct_linux.q +++ b/lib/sys/struct_linux.q @@ -1,6 +1,6 @@ sockaddr_in { sin_family int16 - sin_port int16 + sin_port uint16 sin_addr int64 sin_zero int64 } diff --git a/lib/sys/struct_mac.q b/lib/sys/struct_mac.q index 9172114..e64ddc7 100644 --- a/lib/sys/struct_mac.q +++ b/lib/sys/struct_mac.q @@ -1,7 +1,7 @@ sockaddr_in_bsd { sin_len int8 sin_family int8 - sin_port int16 + sin_port uint16 sin_addr int64 sin_zero int64 } \ No newline at end of file diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q new file mode 100644 index 0000000..3c89ebf --- /dev/null +++ b/lib/sys/sys_linux.q @@ -0,0 +1,87 @@ +read(fd int, buffer *byte, length int) -> int { + return syscall(n.read, fd, buffer, length) +} + +write(fd int, buffer *byte, length int) -> int { + return syscall(n.write, fd, buffer, length) +} + +mmap(address int, length uint, protection int, flags int) -> *any { + return syscall(n.mmap, address, length, protection, flags) +} + +munmap(address *any, length uint) -> int { + return syscall(n.munmap, address, length) +} + +open(path *any, flags int, mode int) -> int { + return syscall(n.openat, -100, path, flags, mode) +} + +close(fd int) -> int { + return syscall(n.close, fd) +} + +clone(flags uint, stack *any, parent *int, child *int, tls uint) -> int { + return syscall(n.clone, flags, stack, parent, child, tls) +} + +execve(path *any, argv *any, envp *any) -> int { + return syscall(n.execve, path, argv, envp) +} + +exit(status int) { + syscall(n.exit, status) +} + +waitid(type int, id int, info *any, options int) -> int { + return syscall(n.waitid, type, id, info, options) +} + +socket(family int, type int, protocol int) -> int { + return syscall(n.socket, family, type, protocol) +} + +accept(fd int, address *any, length int) -> int { + return syscall(n.accept, fd, address, length) +} + +bind(fd int, address *sockaddr_in, length int) -> int { + return syscall(n.bind, fd, address, length) +} + +listen(fd int, backlog int) -> int { + return syscall(n.listen, fd, backlog) +} + +setsockopt(fd int, level int, optname int, optval *any, optlen int) -> int { + return syscall(n.setsockopt, fd, level, optname, optval, optlen) +} + +getcwd(buffer *any, length int) -> int { + return syscall(n.getcwd, buffer, length) +} + +chdir(path *any) -> int { + return syscall(n.chdir, path) +} + +rename(old *any, new *any) -> int { + return syscall(n.renameat, -100, old, -100, new) +} + +mkdir(path *any, mode int) -> int { + return syscall(n.mkdirat, -100, path, mode) +} + +rmdir(path *any) -> int { + return syscall(n.unlinkat, -100, path, 0x200) +} + +unlink(file *any) -> int { + return syscall(n.unlinkat, -100, file, 0) +} + +nanosleep(duration *timespec) -> int { + return syscall(n.nanosleep, duration, 0) +} \ No newline at end of file diff --git a/lib/sys/sys_linux_arm.q b/lib/sys/sys_linux_arm.q index cfc351e..b8c8e39 100644 --- a/lib/sys/sys_linux_arm.q +++ b/lib/sys/sys_linux_arm.q @@ -1,15 +1,25 @@ -read(fd int, buffer *byte, length int) -> int { - return syscall(63, fd, buffer, length) -} - -write(fd int, buffer *byte, length int) -> int { - return syscall(64, fd, buffer, length) -} - -mmap(address int, length uint, protection int, flags int) -> *any { - return syscall(222, address, length, protection, flags) -} - -munmap(address *any, length uint) -> int { - return syscall(215, address, length) +const { + n { + read 63 + write 64 + mmap 222 + munmap 215 + openat 56 + close 57 + clone 220 + execve 221 + exit 93 + waitid 95 + socket 198 + accept 202 + bind 200 + listen 201 + setsockopt 208 + getcwd 17 + chdir 49 + mkdirat 34 + unlinkat 35 + renameat 38 + nanosleep 101 + } } \ No newline at end of file diff --git a/lib/sys/sys_linux_x86.q b/lib/sys/sys_linux_x86.q index 4d82732..02266a7 100644 --- a/lib/sys/sys_linux_x86.q +++ b/lib/sys/sys_linux_x86.q @@ -1,91 +1,26 @@ -read(fd int, buffer *byte, length int) -> int { - return syscall(0, fd, buffer, length) +const { + n { + read 0 + write 1 + mmap 9 + munmap 11 + openat 257 + close 3 + clone 56 + execve 59 + exit 60 + waitid 247 + socket 41 + accept 43 + bind 49 + listen 50 + setsockopt 54 + getcwd 79 + chdir 80 + mkdirat 258 + unlinkat 263 + renameat 264 + nanosleep 35 + } } -write(fd int, buffer *byte, length int) -> int { - return syscall(1, fd, buffer, length) -} - -open(path *any, flags int, mode int) -> int { - return syscall(2, path, flags, mode) -} - -close(fd int) -> int { - return syscall(3, fd) -} - -mmap(address int, length uint, protection int, flags int) -> *any { - return syscall(9, address, length, protection, flags) -} - -munmap(address *any, length uint) -> int { - return syscall(11, address, length) -} - -clone(flags uint, stack *any, parent *int, child *int, tls uint) -> int { - return syscall(56, flags, stack, parent, child, tls) -} - -fork() -> int { - return syscall(57) -} - -execve(path *any, argv *any, envp *any) -> int { - return syscall(59, path, argv, envp) -} - -exit(status int) { - syscall(60, status) -} - -waitid(type int, id int, info *any, options int) -> int { - return syscall(247, type, id, info, options) -} - -socket(family int, type int, protocol int) -> int { - return syscall(41, family, type, protocol) -} - -accept(fd int, address *any, length int) -> int { - return syscall(43, fd, address, length) -} - -bind(fd int, address *sockaddr_in, length int) -> int { - return syscall(49, fd, address, length) -} - -listen(fd int, backlog int) -> int { - return syscall(50, fd, backlog) -} - -setsockopt(fd int, level int, optname int, optval *any, optlen int) -> int { - return syscall(54, fd, level, optname, optval, optlen) -} - -getcwd(buffer *any, length int) -> int { - return syscall(79, buffer, length) -} - -chdir(path *any) -> int { - return syscall(80, path) -} - -rename(old *any, new *any) -> int { - return syscall(82, old, new) -} - -mkdir(path *any, mode int) -> int { - return syscall(83, path, mode) -} - -rmdir(path *any) -> int { - return syscall(84, path) -} - -unlink(file *any) -> int { - return syscall(87, file) -} - -nanosleep(duration *timespec) -> int { - return syscall(35, duration, 0) -} \ No newline at end of file From 80aa833a9b0a6415786f3f9d77139b9aea8f2ff9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 15 Apr 2025 11:08:33 +0200 Subject: [PATCH 0995/1012] Added a function for memory size alignment --- lib/math/math.q | 11 +++++++++++ src/expression/Expression_test.go | 3 +++ src/expression/Operator.go | 3 +++ tests/programs/align.q | 26 ++++++++++++++++++++++++++ tests/programs_test.go | 1 + 5 files changed, 44 insertions(+) create mode 100644 lib/math/math.q create mode 100644 tests/programs/align.q diff --git a/lib/math/math.q b/lib/math/math.q new file mode 100644 index 0000000..d71b952 --- /dev/null +++ b/lib/math/math.q @@ -0,0 +1,11 @@ +align2(x uint64) -> uint64 { + x -= 1 + x |= x >> 1 + x |= x >> 2 + x |= x >> 4 + x |= x >> 8 + x |= x >> 16 + x |= x >> 32 + x += 1 + return x +} \ No newline at end of file diff --git a/src/expression/Expression_test.go b/src/expression/Expression_test.go index 15c461c..1880aea 100644 --- a/src/expression/Expression_test.go +++ b/src/expression/Expression_test.go @@ -117,6 +117,9 @@ func TestParse(t *testing.T) { {"Dereferencing 2", "[a+b]", "(@ (+ a b))"}, {"Dereferencing 3", "[a+b]=c", "(= (@ (+ a b)) c)"}, {"Dereferencing 3", "[a+b]=c+d", "(= (@ (+ a b)) (+ c d))"}, + + {"Assign bitwise OR", "a|=b", "(|= a b)"}, + {"Assign bitwise OR 2", "a|=b<>=", math.MinInt8, 2}, token.ShlAssign: {"<<=", math.MinInt8, 2}, } diff --git a/tests/programs/align.q b/tests/programs/align.q new file mode 100644 index 0000000..264ddf9 --- /dev/null +++ b/tests/programs/align.q @@ -0,0 +1,26 @@ +import math + +main() { + assert math.align2(0) == 0 + assert math.align2(1) == 1 + assert math.align2(2) == 2 + assert math.align2(3) == 4 + assert math.align2(4) == 4 + assert math.align2(5) == 8 + assert math.align2(6) == 8 + assert math.align2(7) == 8 + assert math.align2(8) == 8 + assert math.align2(9) == 16 + assert math.align2(10) == 16 + assert math.align2(30) == 32 + assert math.align2(60) == 64 + assert math.align2(100) == 128 + assert math.align2(200) == 256 + assert math.align2(500) == 512 + assert math.align2(1000) == 1024 + assert math.align2(2000) == 2048 + assert math.align2(4000) == 4096 + assert math.align2(8000) == 8192 + assert math.align2(16000) == 16384 + assert math.align2(32000) == 32768 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 480c089..44047f2 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -40,6 +40,7 @@ var programs = []struct { {"bitwise-or", 0}, {"bitwise-xor", 0}, {"shift", 0}, + {"align", 0}, {"modulo", 0}, {"modulo-assign", 0}, {"div-split", 0}, From 54eb919e987848a6f6333dbc33c3f6816b25566f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 15 Apr 2025 11:08:33 +0200 Subject: [PATCH 0996/1012] Added a function for memory size alignment --- lib/math/math.q | 11 +++++++++++ src/expression/Expression_test.go | 3 +++ src/expression/Operator.go | 3 +++ tests/programs/align.q | 26 ++++++++++++++++++++++++++ tests/programs_test.go | 1 + 5 files changed, 44 insertions(+) create mode 100644 lib/math/math.q create mode 100644 tests/programs/align.q diff --git a/lib/math/math.q b/lib/math/math.q new file mode 100644 index 0000000..d71b952 --- /dev/null +++ b/lib/math/math.q @@ -0,0 +1,11 @@ +align2(x uint64) -> uint64 { + x -= 1 + x |= x >> 1 + x |= x >> 2 + x |= x >> 4 + x |= x >> 8 + x |= x >> 16 + x |= x >> 32 + x += 1 + return x +} \ No newline at end of file diff --git a/src/expression/Expression_test.go b/src/expression/Expression_test.go index 15c461c..1880aea 100644 --- a/src/expression/Expression_test.go +++ b/src/expression/Expression_test.go @@ -117,6 +117,9 @@ func TestParse(t *testing.T) { {"Dereferencing 2", "[a+b]", "(@ (+ a b))"}, {"Dereferencing 3", "[a+b]=c", "(= (@ (+ a b)) c)"}, {"Dereferencing 3", "[a+b]=c+d", "(= (@ (+ a b)) (+ c d))"}, + + {"Assign bitwise OR", "a|=b", "(|= a b)"}, + {"Assign bitwise OR 2", "a|=b<>=", math.MinInt8, 2}, token.ShlAssign: {"<<=", math.MinInt8, 2}, } diff --git a/tests/programs/align.q b/tests/programs/align.q new file mode 100644 index 0000000..264ddf9 --- /dev/null +++ b/tests/programs/align.q @@ -0,0 +1,26 @@ +import math + +main() { + assert math.align2(0) == 0 + assert math.align2(1) == 1 + assert math.align2(2) == 2 + assert math.align2(3) == 4 + assert math.align2(4) == 4 + assert math.align2(5) == 8 + assert math.align2(6) == 8 + assert math.align2(7) == 8 + assert math.align2(8) == 8 + assert math.align2(9) == 16 + assert math.align2(10) == 16 + assert math.align2(30) == 32 + assert math.align2(60) == 64 + assert math.align2(100) == 128 + assert math.align2(200) == 256 + assert math.align2(500) == 512 + assert math.align2(1000) == 1024 + assert math.align2(2000) == 2048 + assert math.align2(4000) == 4096 + assert math.align2(8000) == 8192 + assert math.align2(16000) == 16384 + assert math.align2(32000) == 32768 +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 480c089..44047f2 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -40,6 +40,7 @@ var programs = []struct { {"bitwise-or", 0}, {"bitwise-xor", 0}, {"shift", 0}, + {"align", 0}, {"modulo", 0}, {"modulo-assign", 0}, {"div-split", 0}, From 392b35423fd26a344eee141e9d06e9dfa7467477 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 15 Apr 2025 11:51:03 +0200 Subject: [PATCH 0997/1012] Improved test descriptions --- src/expression/Expression_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/expression/Expression_test.go b/src/expression/Expression_test.go index 1880aea..8ed0b79 100644 --- a/src/expression/Expression_test.go +++ b/src/expression/Expression_test.go @@ -72,6 +72,9 @@ func TestParse(t *testing.T) { {"Unary minus 6", "a + -b", "(+ a (- b))"}, {"Unary minus 7", "-a + -b", "(+ (- a) (- b))"}, + {"Assign bitwise operation", "a|=b", "(|= a b)"}, + {"Assign bitwise operation 2", "a|=b< Date: Tue, 15 Apr 2025 11:51:03 +0200 Subject: [PATCH 0998/1012] Improved test descriptions --- src/expression/Expression_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/expression/Expression_test.go b/src/expression/Expression_test.go index 1880aea..8ed0b79 100644 --- a/src/expression/Expression_test.go +++ b/src/expression/Expression_test.go @@ -72,6 +72,9 @@ func TestParse(t *testing.T) { {"Unary minus 6", "a + -b", "(+ a (- b))"}, {"Unary minus 7", "-a + -b", "(+ (- a) (- b))"}, + {"Assign bitwise operation", "a|=b", "(|= a b)"}, + {"Assign bitwise operation 2", "a|=b< Date: Tue, 15 Apr 2025 18:56:01 +0200 Subject: [PATCH 0999/1012] Implemented stat syscall --- examples/readfile/readfile.q | 13 +++++++++++++ lib/fs/fs.q | 27 +++++++++++++++++++++++++++ lib/sys/struct_linux.q | 23 +++++++++++++++++++++++ lib/sys/sys_linux.q | 4 ++++ lib/sys/sys_linux_arm.q | 1 + lib/sys/sys_linux_x86.q | 1 + 6 files changed, 69 insertions(+) create mode 100644 examples/readfile/readfile.q create mode 100644 lib/fs/fs.q diff --git a/examples/readfile/readfile.q b/examples/readfile/readfile.q new file mode 100644 index 0000000..8488cc1 --- /dev/null +++ b/examples/readfile/readfile.q @@ -0,0 +1,13 @@ +import fs +import sys + +main() { + show("/proc/sys/kernel/ostype") + show("/proc/sys/kernel/osrelease") + show("/proc/sys/kernel/arch") +} + +show(path []byte) { + contents, length := fs.readFile(path) + sys.write(1, contents, length) +} \ No newline at end of file diff --git a/lib/fs/fs.q b/lib/fs/fs.q new file mode 100644 index 0000000..c0e3c5c --- /dev/null +++ b/lib/fs/fs.q @@ -0,0 +1,27 @@ +import mem +import sys + +readFile(path []byte) -> (*byte, int) { + stat := new(sys.file_stat) + + if sys.stat(path, stat) < 0 { + return 0, 0 + } + + size := stat.st_size + + if size < 512 { + size = 512 + } + + file := sys.open(path, 0, 0) + + if file < 0 { + return 0, 0 + } + + contents := mem.alloc(size) + length := sys.read(file, contents, size) + sys.close(file) + return contents, length +} \ No newline at end of file diff --git a/lib/sys/struct_linux.q b/lib/sys/struct_linux.q index e07b9dd..ce76851 100644 --- a/lib/sys/struct_linux.q +++ b/lib/sys/struct_linux.q @@ -5,6 +5,29 @@ sockaddr_in { sin_zero int64 } +file_stat { + st_dev uint64 + st_ino uint64 + st_nlink uint64 + st_mode uint32 + st_uid uint32 + st_gid uint32 + _ uint32 + st_rdev uint64 + st_size int64 + st_blksize int64 + st_blocks int64 + st_atime int64 + st_atime_nsec int64 + st_mtime int64 + st_mtime_nsec int64 + st_ctime int64 + st_ctime_nsec int64 + _ int64 + _ int64 + _ int64 +} + timespec { seconds int64 nanoseconds int64 diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q index 3c89ebf..c536d7f 100644 --- a/lib/sys/sys_linux.q +++ b/lib/sys/sys_linux.q @@ -22,6 +22,10 @@ close(fd int) -> int { return syscall(n.close, fd) } +stat(path *any, stat *file_stat) -> int { + return syscall(n.newfstatat, -100, path, stat, 0) +} + clone(flags uint, stack *any, parent *int, child *int, tls uint) -> int { return syscall(n.clone, flags, stack, parent, child, tls) } diff --git a/lib/sys/sys_linux_arm.q b/lib/sys/sys_linux_arm.q index b8c8e39..df6c67a 100644 --- a/lib/sys/sys_linux_arm.q +++ b/lib/sys/sys_linux_arm.q @@ -6,6 +6,7 @@ const { munmap 215 openat 56 close 57 + newfstatat 79 clone 220 execve 221 exit 93 diff --git a/lib/sys/sys_linux_x86.q b/lib/sys/sys_linux_x86.q index 02266a7..6b5817c 100644 --- a/lib/sys/sys_linux_x86.q +++ b/lib/sys/sys_linux_x86.q @@ -6,6 +6,7 @@ const { munmap 11 openat 257 close 3 + newfstatat 262 clone 56 execve 59 exit 60 From c238e3a1907486a0a4f80ab2ce2f9152b5ec59c0 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 15 Apr 2025 18:56:01 +0200 Subject: [PATCH 1000/1012] Implemented stat syscall --- examples/readfile/readfile.q | 13 +++++++++++++ lib/fs/fs.q | 27 +++++++++++++++++++++++++++ lib/sys/struct_linux.q | 23 +++++++++++++++++++++++ lib/sys/sys_linux.q | 4 ++++ lib/sys/sys_linux_arm.q | 1 + lib/sys/sys_linux_x86.q | 1 + 6 files changed, 69 insertions(+) create mode 100644 examples/readfile/readfile.q create mode 100644 lib/fs/fs.q diff --git a/examples/readfile/readfile.q b/examples/readfile/readfile.q new file mode 100644 index 0000000..8488cc1 --- /dev/null +++ b/examples/readfile/readfile.q @@ -0,0 +1,13 @@ +import fs +import sys + +main() { + show("/proc/sys/kernel/ostype") + show("/proc/sys/kernel/osrelease") + show("/proc/sys/kernel/arch") +} + +show(path []byte) { + contents, length := fs.readFile(path) + sys.write(1, contents, length) +} \ No newline at end of file diff --git a/lib/fs/fs.q b/lib/fs/fs.q new file mode 100644 index 0000000..c0e3c5c --- /dev/null +++ b/lib/fs/fs.q @@ -0,0 +1,27 @@ +import mem +import sys + +readFile(path []byte) -> (*byte, int) { + stat := new(sys.file_stat) + + if sys.stat(path, stat) < 0 { + return 0, 0 + } + + size := stat.st_size + + if size < 512 { + size = 512 + } + + file := sys.open(path, 0, 0) + + if file < 0 { + return 0, 0 + } + + contents := mem.alloc(size) + length := sys.read(file, contents, size) + sys.close(file) + return contents, length +} \ No newline at end of file diff --git a/lib/sys/struct_linux.q b/lib/sys/struct_linux.q index e07b9dd..ce76851 100644 --- a/lib/sys/struct_linux.q +++ b/lib/sys/struct_linux.q @@ -5,6 +5,29 @@ sockaddr_in { sin_zero int64 } +file_stat { + st_dev uint64 + st_ino uint64 + st_nlink uint64 + st_mode uint32 + st_uid uint32 + st_gid uint32 + _ uint32 + st_rdev uint64 + st_size int64 + st_blksize int64 + st_blocks int64 + st_atime int64 + st_atime_nsec int64 + st_mtime int64 + st_mtime_nsec int64 + st_ctime int64 + st_ctime_nsec int64 + _ int64 + _ int64 + _ int64 +} + timespec { seconds int64 nanoseconds int64 diff --git a/lib/sys/sys_linux.q b/lib/sys/sys_linux.q index 3c89ebf..c536d7f 100644 --- a/lib/sys/sys_linux.q +++ b/lib/sys/sys_linux.q @@ -22,6 +22,10 @@ close(fd int) -> int { return syscall(n.close, fd) } +stat(path *any, stat *file_stat) -> int { + return syscall(n.newfstatat, -100, path, stat, 0) +} + clone(flags uint, stack *any, parent *int, child *int, tls uint) -> int { return syscall(n.clone, flags, stack, parent, child, tls) } diff --git a/lib/sys/sys_linux_arm.q b/lib/sys/sys_linux_arm.q index b8c8e39..df6c67a 100644 --- a/lib/sys/sys_linux_arm.q +++ b/lib/sys/sys_linux_arm.q @@ -6,6 +6,7 @@ const { munmap 215 openat 56 close 57 + newfstatat 79 clone 220 execve 221 exit 93 diff --git a/lib/sys/sys_linux_x86.q b/lib/sys/sys_linux_x86.q index 02266a7..6b5817c 100644 --- a/lib/sys/sys_linux_x86.q +++ b/lib/sys/sys_linux_x86.q @@ -6,6 +6,7 @@ const { munmap 11 openat 257 close 3 + newfstatat 262 clone 56 execve 59 exit 60 From df725a2b238a70b585884268a82d2c7ee93583e7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 16 Apr 2025 17:38:48 +0200 Subject: [PATCH 1001/1012] Refactored arm package --- src/arm/Add_test.go | 4 ++-- src/arm/And_test.go | 4 ++-- src/arm/Call.go | 4 ++-- src/arm/Call_test.go | 25 +++++++++++++++++++++++++ src/arm/Compare_test.go | 4 ++-- src/arm/Div_test.go | 2 +- src/arm/Jump.go | 20 +++++++++----------- src/arm/Jump_test.go | 2 +- src/arm/LoadAddress_test.go | 2 +- src/arm/LoadPair_test.go | 2 +- src/arm/Load_test.go | 2 +- src/arm/Move_test.go | 8 ++++---- src/arm/Mul_test.go | 4 ++-- src/arm/Negate_test.go | 2 +- src/arm/Or_test.go | 4 ++-- src/arm/Shift_test.go | 4 ++-- src/arm/StorePair_test.go | 2 +- src/arm/Store_test.go | 2 +- src/arm/Sub_test.go | 4 ++-- src/arm/Xor_test.go | 4 ++-- src/arm/arm_test.go | 3 +-- src/arm/bitmask.go | 2 +- src/arm/condition.go | 22 ++++++++++++++++++++++ src/arm/encode.go | 8 -------- src/arm/mask.go | 11 +++++++++++ src/asmc/armCompiler.go | 4 +++- 26 files changed, 102 insertions(+), 53 deletions(-) create mode 100644 src/arm/Call_test.go create mode 100644 src/arm/condition.go create mode 100644 src/arm/mask.go diff --git a/src/arm/Add_test.go b/src/arm/Add_test.go index d00d9f6..723580e 100644 --- a/src/arm/Add_test.go +++ b/src/arm/Add_test.go @@ -23,7 +23,7 @@ func TestAddRegisterNumber(t *testing.T) { t.Logf("add %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) code, encodable := arm.AddRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) assert.True(t, encodable) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -40,6 +40,6 @@ func TestAddRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("add %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) code := arm.AddRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/And_test.go b/src/arm/And_test.go index c8e6317..f5bb123 100644 --- a/src/arm/And_test.go +++ b/src/arm/And_test.go @@ -27,7 +27,7 @@ func TestAndRegisterNumber(t *testing.T) { t.Logf("and %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) code, encodable := arm.AndRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) assert.True(t, encodable) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -44,6 +44,6 @@ func TestAndRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("and %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) code := arm.AndRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Call.go b/src/arm/Call.go index 8c1901f..7e18522 100644 --- a/src/arm/Call.go +++ b/src/arm/Call.go @@ -3,6 +3,6 @@ package arm // Call branches to a PC-relative offset, setting the register X30 to PC+4. // The offset starts from the address of this instruction and is encoded as "imm26" times 4. // This instruction is also known as BL (branch with link). -func Call(offset uint32) uint32 { - return uint32(0b100101<<26) | offset +func Call(offset int) uint32 { + return uint32(0b100101<<26) | uint32(offset&mask26) } diff --git a/src/arm/Call_test.go b/src/arm/Call_test.go new file mode 100644 index 0000000..7000e8f --- /dev/null +++ b/src/arm/Call_test.go @@ -0,0 +1,25 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/go/assert" +) + +func TestCall(t *testing.T) { + usagePatterns := []struct { + Offset int + Code uint32 + }{ + {0, 0x94000000}, + {1, 0x94000001}, + {-1, 0x97FFFFFF}, + } + + for _, pattern := range usagePatterns { + t.Logf("bl %d", pattern.Offset) + code := arm.Call(pattern.Offset) + assert.Equal(t, code, pattern.Code) + } +} diff --git a/src/arm/Compare_test.go b/src/arm/Compare_test.go index 4ed047c..ab4abdb 100644 --- a/src/arm/Compare_test.go +++ b/src/arm/Compare_test.go @@ -24,7 +24,7 @@ func TestCompareRegisterNumber(t *testing.T) { t.Logf("cmp %s, %d", pattern.Source, pattern.Number) code, encodable := arm.CompareRegisterNumber(pattern.Source, pattern.Number) assert.True(t, encodable) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -40,6 +40,6 @@ func TestCompareRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("cmp %s, %s", pattern.Left, pattern.Right) code := arm.CompareRegisterRegister(pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Div_test.go b/src/arm/Div_test.go index 81c3f03..5d3f281 100644 --- a/src/arm/Div_test.go +++ b/src/arm/Div_test.go @@ -21,6 +21,6 @@ func TestDivSigned(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("sdiv %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) code := arm.DivSigned(pattern.Destination, pattern.Source, pattern.Operand) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Jump.go b/src/arm/Jump.go index 8f3f807..a51eea9 100644 --- a/src/arm/Jump.go +++ b/src/arm/Jump.go @@ -2,42 +2,40 @@ package arm // Jump continues program flow at the new offset. func Jump(offset int) uint32 { - offset &= 0b11_1111_1111_1111_1111_1111_1111 - return 0b000101<<26 | uint32(offset) + return 0b000101<<26 | uint32(offset&mask26) } // JumpIfEqual jumps if the result was equal. func JumpIfEqual(offset int) uint32 { - return branchCond(0b0000, offset) + return branchCond(EQ, offset) } // JumpIfNotEqual jumps if the result was not equal. func JumpIfNotEqual(offset int) uint32 { - return branchCond(0b0001, offset) + return branchCond(NE, offset) } // JumpIfGreater jumps if the result was greater. func JumpIfGreater(offset int) uint32 { - return branchCond(0b1100, offset) + return branchCond(GT, offset) } // JumpIfGreaterOrEqual jumps if the result was greater or equal. func JumpIfGreaterOrEqual(offset int) uint32 { - return branchCond(0b1010, offset) + return branchCond(GE, offset) } // JumpIfLess jumps if the result was less. func JumpIfLess(offset int) uint32 { - return branchCond(0b1001, offset) + return branchCond(LS, offset) } // JumpIfLessOrEqual jumps if the result was less or equal. func JumpIfLessOrEqual(offset int) uint32 { - return branchCond(0b1101, offset) + return branchCond(LE, offset) } // branchCond performs a conditional branch to a PC-relative offset. -func branchCond(cond uint32, offset int) uint32 { - offset &= 0b111_1111_1111_1111_1111 - return 0b01010100<<24 | uint32(offset)<<5 | cond +func branchCond(cond condition, imm19 int) uint32 { + return 0b01010100<<24 | uint32(imm19&mask19)<<5 | uint32(cond) } diff --git a/src/arm/Jump_test.go b/src/arm/Jump_test.go index 423c568..651cdb0 100644 --- a/src/arm/Jump_test.go +++ b/src/arm/Jump_test.go @@ -63,6 +63,6 @@ func TestJump(t *testing.T) { code = arm.JumpIfLessOrEqual(pattern.Offset) } - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/LoadAddress_test.go b/src/arm/LoadAddress_test.go index 3b3c05f..332b842 100644 --- a/src/arm/LoadAddress_test.go +++ b/src/arm/LoadAddress_test.go @@ -21,6 +21,6 @@ func TestLoadAddress(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("adr %s, %d", pattern.Destination, pattern.Number) code := arm.LoadAddress(pattern.Destination, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/LoadPair_test.go b/src/arm/LoadPair_test.go index 97edbdb..0cd0448 100644 --- a/src/arm/LoadPair_test.go +++ b/src/arm/LoadPair_test.go @@ -23,6 +23,6 @@ func TestLoadPair(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("ldp %s, %s, [%s], #%d", pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) code := arm.LoadPair(pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Load_test.go b/src/arm/Load_test.go index 43eaf28..b8294f2 100644 --- a/src/arm/Load_test.go +++ b/src/arm/Load_test.go @@ -34,6 +34,6 @@ func TestLoadRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("ldur %s, [%s, %d] %db", pattern.Destination, pattern.Base, pattern.Offset, pattern.Length) code := arm.LoadRegister(pattern.Destination, pattern.Base, pattern.Offset, pattern.Length) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go index acfb1a3..968cd1e 100644 --- a/src/arm/Move_test.go +++ b/src/arm/Move_test.go @@ -23,7 +23,7 @@ func TestMoveRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("mov %s, %s", pattern.Destination, pattern.Source) code := arm.MoveRegisterRegister(pattern.Destination, pattern.Source) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -79,7 +79,7 @@ func TestMoveRegisterNumberSI(t *testing.T) { if pattern.Code != 0 { assert.True(t, encodable) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } else { assert.False(t, encodable) } @@ -99,7 +99,7 @@ func TestMoveKeep(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("movk %s, %d", pattern.Register, pattern.Number) code := arm.MoveKeep(pattern.Register, 0, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -116,6 +116,6 @@ func TestMoveZero(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("movz %s, %d", pattern.Register, pattern.Number) code := arm.MoveZero(pattern.Register, 0, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Mul_test.go b/src/arm/Mul_test.go index c59325e..3d9f5ca 100644 --- a/src/arm/Mul_test.go +++ b/src/arm/Mul_test.go @@ -21,7 +21,7 @@ func TestMulRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("mul %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) code := arm.MulRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -40,6 +40,6 @@ func TestMultiplySubtract(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("msub %s, %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand, pattern.Extra) code := arm.MultiplySubtract(pattern.Destination, pattern.Source, pattern.Operand, pattern.Extra) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Negate_test.go b/src/arm/Negate_test.go index 65f3809..b5f385c 100644 --- a/src/arm/Negate_test.go +++ b/src/arm/Negate_test.go @@ -21,6 +21,6 @@ func TestNegateRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("neg %s, %s", pattern.Destination, pattern.Source) code := arm.NegateRegister(pattern.Destination, pattern.Source) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Or_test.go b/src/arm/Or_test.go index 0d7aee9..98e6e8a 100644 --- a/src/arm/Or_test.go +++ b/src/arm/Or_test.go @@ -27,7 +27,7 @@ func TestOrRegisterNumber(t *testing.T) { t.Logf("orr %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) code, encodable := arm.OrRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) assert.True(t, encodable) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -44,6 +44,6 @@ func TestOrRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("orr %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) code := arm.OrRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Shift_test.go b/src/arm/Shift_test.go index 8aa96d2..556e349 100644 --- a/src/arm/Shift_test.go +++ b/src/arm/Shift_test.go @@ -26,7 +26,7 @@ func TestShiftLeftNumber(t *testing.T) { t.Logf("%b", pattern.Code) t.Logf("lsl %s, %s, %x", pattern.Destination, pattern.Source, pattern.Bits) code := arm.ShiftLeftNumber(pattern.Destination, pattern.Source, pattern.Bits) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -47,6 +47,6 @@ func TestShiftRightSignedNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("asr %s, %s, %x", pattern.Destination, pattern.Source, pattern.Bits) code := arm.ShiftRightSignedNumber(pattern.Destination, pattern.Source, pattern.Bits) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/StorePair_test.go b/src/arm/StorePair_test.go index eefdfbd..beca1bb 100644 --- a/src/arm/StorePair_test.go +++ b/src/arm/StorePair_test.go @@ -23,6 +23,6 @@ func TestStorePair(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("stp %s, %s, [%s, #%d]!", pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) code := arm.StorePair(pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Store_test.go b/src/arm/Store_test.go index 9f4150d..b83ebc6 100644 --- a/src/arm/Store_test.go +++ b/src/arm/Store_test.go @@ -29,6 +29,6 @@ func TestStoreRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("stur %s, [%s, #%d] %db", pattern.Source, pattern.Base, pattern.Offset, pattern.Length) code := arm.StoreRegister(pattern.Source, pattern.Base, pattern.Offset, pattern.Length) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Sub_test.go b/src/arm/Sub_test.go index 2256704..0770b6a 100644 --- a/src/arm/Sub_test.go +++ b/src/arm/Sub_test.go @@ -24,7 +24,7 @@ func TestSubRegisterNumber(t *testing.T) { t.Logf("sub %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) code, encodable := arm.SubRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) assert.True(t, encodable) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -41,6 +41,6 @@ func TestSubRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("sub %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) code := arm.SubRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Xor_test.go b/src/arm/Xor_test.go index c8f3dc5..aec2b42 100644 --- a/src/arm/Xor_test.go +++ b/src/arm/Xor_test.go @@ -27,7 +27,7 @@ func TestXorRegisterNumber(t *testing.T) { t.Logf("eor %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) code, encodable := arm.XorRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) assert.True(t, encodable) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -44,6 +44,6 @@ func TestXorRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("eor %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) code := arm.XorRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/arm_test.go b/src/arm/arm_test.go index 9a6693f..12c6a4c 100644 --- a/src/arm/arm_test.go +++ b/src/arm/arm_test.go @@ -7,8 +7,7 @@ import ( "git.urbach.dev/go/assert" ) -func TestGeneral(t *testing.T) { - assert.DeepEqual(t, arm.Call(0), 0x94000000) +func TestConstants(t *testing.T) { assert.DeepEqual(t, arm.Nop(), 0xD503201F) assert.DeepEqual(t, arm.Return(), 0xD65F03C0) assert.DeepEqual(t, arm.Syscall(), 0xD4000001) diff --git a/src/arm/bitmask.go b/src/arm/bitmask.go index 19a65f8..ecf487d 100644 --- a/src/arm/bitmask.go +++ b/src/arm/bitmask.go @@ -24,7 +24,7 @@ func encodeLogicalImmediate(val uint) (N int, immr int, imms int, encodable bool return 0, 0, 0, false } - return N, immr, (imms & 0x3f), true + return N, immr, (imms & 0x3F), true } // clearTrailingOnes clears trailing one bits. diff --git a/src/arm/condition.go b/src/arm/condition.go new file mode 100644 index 0000000..a959c1a --- /dev/null +++ b/src/arm/condition.go @@ -0,0 +1,22 @@ +package arm + +type condition uint8 + +const ( + EQ condition = iota + NE + CS + CC + MI + PL + VS + VC + HI + LS + GE + LT + GT + LE + AL + NV +) diff --git a/src/arm/encode.go b/src/arm/encode.go index 0b9d45c..89ec099 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -4,14 +4,6 @@ import ( "git.urbach.dev/cli/q/src/cpu" ) -const ( - mask6 = 0b111111 - mask7 = 0b1111111 - mask9 = 0b1_11111111 - mask12 = 0b1111_11111111 - mask16 = 0b11111111_11111111 -) - // memory encodes an instruction with a register, a base register and an offset. func memory(destination cpu.Register, base cpu.Register, imm9 int) uint32 { return uint32(imm9&mask9)<<12 | uint32(base)<<5 | uint32(destination) diff --git a/src/arm/mask.go b/src/arm/mask.go new file mode 100644 index 0000000..b1bb519 --- /dev/null +++ b/src/arm/mask.go @@ -0,0 +1,11 @@ +package arm + +const ( + mask6 = 0b111111 + mask7 = 0b1111111 + mask9 = 0b1_11111111 + mask12 = 0b1111_11111111 + mask16 = 0b11111111_11111111 + mask19 = 0b111_11111111_11111111 + mask26 = 0b11_11111111_11111111_11111111 +) diff --git a/src/asmc/armCompiler.go b/src/asmc/armCompiler.go index 6c11240..0ce0434 100644 --- a/src/asmc/armCompiler.go +++ b/src/asmc/armCompiler.go @@ -100,10 +100,12 @@ func (c *armCompiler) handleCallInstruction(instruction asm.Instruction) { pointer.Resolve = func() Address { destination, exists := c.codeLabels[label.Name] + if !exists { panic(fmt.Sprintf("unknown jump label %s", label.Name)) } - distance := (destination - position) / 4 + + distance := int(destination-position) / 4 return arm.Call(distance) } From f3f3ccc95f9fb3017af6053907f87e402def682f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 16 Apr 2025 17:38:48 +0200 Subject: [PATCH 1002/1012] Refactored arm package --- src/arm/Add_test.go | 4 ++-- src/arm/And_test.go | 4 ++-- src/arm/Call.go | 4 ++-- src/arm/Call_test.go | 25 +++++++++++++++++++++++++ src/arm/Compare_test.go | 4 ++-- src/arm/Div_test.go | 2 +- src/arm/Jump.go | 20 +++++++++----------- src/arm/Jump_test.go | 2 +- src/arm/LoadAddress_test.go | 2 +- src/arm/LoadPair_test.go | 2 +- src/arm/Load_test.go | 2 +- src/arm/Move_test.go | 8 ++++---- src/arm/Mul_test.go | 4 ++-- src/arm/Negate_test.go | 2 +- src/arm/Or_test.go | 4 ++-- src/arm/Shift_test.go | 4 ++-- src/arm/StorePair_test.go | 2 +- src/arm/Store_test.go | 2 +- src/arm/Sub_test.go | 4 ++-- src/arm/Xor_test.go | 4 ++-- src/arm/arm_test.go | 3 +-- src/arm/bitmask.go | 2 +- src/arm/condition.go | 22 ++++++++++++++++++++++ src/arm/encode.go | 8 -------- src/arm/mask.go | 11 +++++++++++ src/asmc/armCompiler.go | 4 +++- 26 files changed, 102 insertions(+), 53 deletions(-) create mode 100644 src/arm/Call_test.go create mode 100644 src/arm/condition.go create mode 100644 src/arm/mask.go diff --git a/src/arm/Add_test.go b/src/arm/Add_test.go index d00d9f6..723580e 100644 --- a/src/arm/Add_test.go +++ b/src/arm/Add_test.go @@ -23,7 +23,7 @@ func TestAddRegisterNumber(t *testing.T) { t.Logf("add %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) code, encodable := arm.AddRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) assert.True(t, encodable) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -40,6 +40,6 @@ func TestAddRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("add %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) code := arm.AddRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/And_test.go b/src/arm/And_test.go index c8e6317..f5bb123 100644 --- a/src/arm/And_test.go +++ b/src/arm/And_test.go @@ -27,7 +27,7 @@ func TestAndRegisterNumber(t *testing.T) { t.Logf("and %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) code, encodable := arm.AndRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) assert.True(t, encodable) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -44,6 +44,6 @@ func TestAndRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("and %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) code := arm.AndRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Call.go b/src/arm/Call.go index 8c1901f..7e18522 100644 --- a/src/arm/Call.go +++ b/src/arm/Call.go @@ -3,6 +3,6 @@ package arm // Call branches to a PC-relative offset, setting the register X30 to PC+4. // The offset starts from the address of this instruction and is encoded as "imm26" times 4. // This instruction is also known as BL (branch with link). -func Call(offset uint32) uint32 { - return uint32(0b100101<<26) | offset +func Call(offset int) uint32 { + return uint32(0b100101<<26) | uint32(offset&mask26) } diff --git a/src/arm/Call_test.go b/src/arm/Call_test.go new file mode 100644 index 0000000..7000e8f --- /dev/null +++ b/src/arm/Call_test.go @@ -0,0 +1,25 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/go/assert" +) + +func TestCall(t *testing.T) { + usagePatterns := []struct { + Offset int + Code uint32 + }{ + {0, 0x94000000}, + {1, 0x94000001}, + {-1, 0x97FFFFFF}, + } + + for _, pattern := range usagePatterns { + t.Logf("bl %d", pattern.Offset) + code := arm.Call(pattern.Offset) + assert.Equal(t, code, pattern.Code) + } +} diff --git a/src/arm/Compare_test.go b/src/arm/Compare_test.go index 4ed047c..ab4abdb 100644 --- a/src/arm/Compare_test.go +++ b/src/arm/Compare_test.go @@ -24,7 +24,7 @@ func TestCompareRegisterNumber(t *testing.T) { t.Logf("cmp %s, %d", pattern.Source, pattern.Number) code, encodable := arm.CompareRegisterNumber(pattern.Source, pattern.Number) assert.True(t, encodable) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -40,6 +40,6 @@ func TestCompareRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("cmp %s, %s", pattern.Left, pattern.Right) code := arm.CompareRegisterRegister(pattern.Left, pattern.Right) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Div_test.go b/src/arm/Div_test.go index 81c3f03..5d3f281 100644 --- a/src/arm/Div_test.go +++ b/src/arm/Div_test.go @@ -21,6 +21,6 @@ func TestDivSigned(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("sdiv %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) code := arm.DivSigned(pattern.Destination, pattern.Source, pattern.Operand) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Jump.go b/src/arm/Jump.go index 8f3f807..a51eea9 100644 --- a/src/arm/Jump.go +++ b/src/arm/Jump.go @@ -2,42 +2,40 @@ package arm // Jump continues program flow at the new offset. func Jump(offset int) uint32 { - offset &= 0b11_1111_1111_1111_1111_1111_1111 - return 0b000101<<26 | uint32(offset) + return 0b000101<<26 | uint32(offset&mask26) } // JumpIfEqual jumps if the result was equal. func JumpIfEqual(offset int) uint32 { - return branchCond(0b0000, offset) + return branchCond(EQ, offset) } // JumpIfNotEqual jumps if the result was not equal. func JumpIfNotEqual(offset int) uint32 { - return branchCond(0b0001, offset) + return branchCond(NE, offset) } // JumpIfGreater jumps if the result was greater. func JumpIfGreater(offset int) uint32 { - return branchCond(0b1100, offset) + return branchCond(GT, offset) } // JumpIfGreaterOrEqual jumps if the result was greater or equal. func JumpIfGreaterOrEqual(offset int) uint32 { - return branchCond(0b1010, offset) + return branchCond(GE, offset) } // JumpIfLess jumps if the result was less. func JumpIfLess(offset int) uint32 { - return branchCond(0b1001, offset) + return branchCond(LS, offset) } // JumpIfLessOrEqual jumps if the result was less or equal. func JumpIfLessOrEqual(offset int) uint32 { - return branchCond(0b1101, offset) + return branchCond(LE, offset) } // branchCond performs a conditional branch to a PC-relative offset. -func branchCond(cond uint32, offset int) uint32 { - offset &= 0b111_1111_1111_1111_1111 - return 0b01010100<<24 | uint32(offset)<<5 | cond +func branchCond(cond condition, imm19 int) uint32 { + return 0b01010100<<24 | uint32(imm19&mask19)<<5 | uint32(cond) } diff --git a/src/arm/Jump_test.go b/src/arm/Jump_test.go index 423c568..651cdb0 100644 --- a/src/arm/Jump_test.go +++ b/src/arm/Jump_test.go @@ -63,6 +63,6 @@ func TestJump(t *testing.T) { code = arm.JumpIfLessOrEqual(pattern.Offset) } - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/LoadAddress_test.go b/src/arm/LoadAddress_test.go index 3b3c05f..332b842 100644 --- a/src/arm/LoadAddress_test.go +++ b/src/arm/LoadAddress_test.go @@ -21,6 +21,6 @@ func TestLoadAddress(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("adr %s, %d", pattern.Destination, pattern.Number) code := arm.LoadAddress(pattern.Destination, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/LoadPair_test.go b/src/arm/LoadPair_test.go index 97edbdb..0cd0448 100644 --- a/src/arm/LoadPair_test.go +++ b/src/arm/LoadPair_test.go @@ -23,6 +23,6 @@ func TestLoadPair(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("ldp %s, %s, [%s], #%d", pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) code := arm.LoadPair(pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Load_test.go b/src/arm/Load_test.go index 43eaf28..b8294f2 100644 --- a/src/arm/Load_test.go +++ b/src/arm/Load_test.go @@ -34,6 +34,6 @@ func TestLoadRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("ldur %s, [%s, %d] %db", pattern.Destination, pattern.Base, pattern.Offset, pattern.Length) code := arm.LoadRegister(pattern.Destination, pattern.Base, pattern.Offset, pattern.Length) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go index acfb1a3..968cd1e 100644 --- a/src/arm/Move_test.go +++ b/src/arm/Move_test.go @@ -23,7 +23,7 @@ func TestMoveRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("mov %s, %s", pattern.Destination, pattern.Source) code := arm.MoveRegisterRegister(pattern.Destination, pattern.Source) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -79,7 +79,7 @@ func TestMoveRegisterNumberSI(t *testing.T) { if pattern.Code != 0 { assert.True(t, encodable) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } else { assert.False(t, encodable) } @@ -99,7 +99,7 @@ func TestMoveKeep(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("movk %s, %d", pattern.Register, pattern.Number) code := arm.MoveKeep(pattern.Register, 0, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -116,6 +116,6 @@ func TestMoveZero(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("movz %s, %d", pattern.Register, pattern.Number) code := arm.MoveZero(pattern.Register, 0, pattern.Number) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Mul_test.go b/src/arm/Mul_test.go index c59325e..3d9f5ca 100644 --- a/src/arm/Mul_test.go +++ b/src/arm/Mul_test.go @@ -21,7 +21,7 @@ func TestMulRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("mul %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) code := arm.MulRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -40,6 +40,6 @@ func TestMultiplySubtract(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("msub %s, %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand, pattern.Extra) code := arm.MultiplySubtract(pattern.Destination, pattern.Source, pattern.Operand, pattern.Extra) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Negate_test.go b/src/arm/Negate_test.go index 65f3809..b5f385c 100644 --- a/src/arm/Negate_test.go +++ b/src/arm/Negate_test.go @@ -21,6 +21,6 @@ func TestNegateRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("neg %s, %s", pattern.Destination, pattern.Source) code := arm.NegateRegister(pattern.Destination, pattern.Source) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Or_test.go b/src/arm/Or_test.go index 0d7aee9..98e6e8a 100644 --- a/src/arm/Or_test.go +++ b/src/arm/Or_test.go @@ -27,7 +27,7 @@ func TestOrRegisterNumber(t *testing.T) { t.Logf("orr %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) code, encodable := arm.OrRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) assert.True(t, encodable) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -44,6 +44,6 @@ func TestOrRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("orr %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) code := arm.OrRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Shift_test.go b/src/arm/Shift_test.go index 8aa96d2..556e349 100644 --- a/src/arm/Shift_test.go +++ b/src/arm/Shift_test.go @@ -26,7 +26,7 @@ func TestShiftLeftNumber(t *testing.T) { t.Logf("%b", pattern.Code) t.Logf("lsl %s, %s, %x", pattern.Destination, pattern.Source, pattern.Bits) code := arm.ShiftLeftNumber(pattern.Destination, pattern.Source, pattern.Bits) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -47,6 +47,6 @@ func TestShiftRightSignedNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("asr %s, %s, %x", pattern.Destination, pattern.Source, pattern.Bits) code := arm.ShiftRightSignedNumber(pattern.Destination, pattern.Source, pattern.Bits) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/StorePair_test.go b/src/arm/StorePair_test.go index eefdfbd..beca1bb 100644 --- a/src/arm/StorePair_test.go +++ b/src/arm/StorePair_test.go @@ -23,6 +23,6 @@ func TestStorePair(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("stp %s, %s, [%s, #%d]!", pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) code := arm.StorePair(pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Store_test.go b/src/arm/Store_test.go index 9f4150d..b83ebc6 100644 --- a/src/arm/Store_test.go +++ b/src/arm/Store_test.go @@ -29,6 +29,6 @@ func TestStoreRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("stur %s, [%s, #%d] %db", pattern.Source, pattern.Base, pattern.Offset, pattern.Length) code := arm.StoreRegister(pattern.Source, pattern.Base, pattern.Offset, pattern.Length) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Sub_test.go b/src/arm/Sub_test.go index 2256704..0770b6a 100644 --- a/src/arm/Sub_test.go +++ b/src/arm/Sub_test.go @@ -24,7 +24,7 @@ func TestSubRegisterNumber(t *testing.T) { t.Logf("sub %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) code, encodable := arm.SubRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) assert.True(t, encodable) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -41,6 +41,6 @@ func TestSubRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("sub %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) code := arm.SubRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/Xor_test.go b/src/arm/Xor_test.go index c8f3dc5..aec2b42 100644 --- a/src/arm/Xor_test.go +++ b/src/arm/Xor_test.go @@ -27,7 +27,7 @@ func TestXorRegisterNumber(t *testing.T) { t.Logf("eor %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) code, encodable := arm.XorRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) assert.True(t, encodable) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } @@ -44,6 +44,6 @@ func TestXorRegisterRegister(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("eor %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) code := arm.XorRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) - assert.DeepEqual(t, code, pattern.Code) + assert.Equal(t, code, pattern.Code) } } diff --git a/src/arm/arm_test.go b/src/arm/arm_test.go index 9a6693f..12c6a4c 100644 --- a/src/arm/arm_test.go +++ b/src/arm/arm_test.go @@ -7,8 +7,7 @@ import ( "git.urbach.dev/go/assert" ) -func TestGeneral(t *testing.T) { - assert.DeepEqual(t, arm.Call(0), 0x94000000) +func TestConstants(t *testing.T) { assert.DeepEqual(t, arm.Nop(), 0xD503201F) assert.DeepEqual(t, arm.Return(), 0xD65F03C0) assert.DeepEqual(t, arm.Syscall(), 0xD4000001) diff --git a/src/arm/bitmask.go b/src/arm/bitmask.go index 19a65f8..ecf487d 100644 --- a/src/arm/bitmask.go +++ b/src/arm/bitmask.go @@ -24,7 +24,7 @@ func encodeLogicalImmediate(val uint) (N int, immr int, imms int, encodable bool return 0, 0, 0, false } - return N, immr, (imms & 0x3f), true + return N, immr, (imms & 0x3F), true } // clearTrailingOnes clears trailing one bits. diff --git a/src/arm/condition.go b/src/arm/condition.go new file mode 100644 index 0000000..a959c1a --- /dev/null +++ b/src/arm/condition.go @@ -0,0 +1,22 @@ +package arm + +type condition uint8 + +const ( + EQ condition = iota + NE + CS + CC + MI + PL + VS + VC + HI + LS + GE + LT + GT + LE + AL + NV +) diff --git a/src/arm/encode.go b/src/arm/encode.go index 0b9d45c..89ec099 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -4,14 +4,6 @@ import ( "git.urbach.dev/cli/q/src/cpu" ) -const ( - mask6 = 0b111111 - mask7 = 0b1111111 - mask9 = 0b1_11111111 - mask12 = 0b1111_11111111 - mask16 = 0b11111111_11111111 -) - // memory encodes an instruction with a register, a base register and an offset. func memory(destination cpu.Register, base cpu.Register, imm9 int) uint32 { return uint32(imm9&mask9)<<12 | uint32(base)<<5 | uint32(destination) diff --git a/src/arm/mask.go b/src/arm/mask.go new file mode 100644 index 0000000..b1bb519 --- /dev/null +++ b/src/arm/mask.go @@ -0,0 +1,11 @@ +package arm + +const ( + mask6 = 0b111111 + mask7 = 0b1111111 + mask9 = 0b1_11111111 + mask12 = 0b1111_11111111 + mask16 = 0b11111111_11111111 + mask19 = 0b111_11111111_11111111 + mask26 = 0b11_11111111_11111111_11111111 +) diff --git a/src/asmc/armCompiler.go b/src/asmc/armCompiler.go index 6c11240..0ce0434 100644 --- a/src/asmc/armCompiler.go +++ b/src/asmc/armCompiler.go @@ -100,10 +100,12 @@ func (c *armCompiler) handleCallInstruction(instruction asm.Instruction) { pointer.Resolve = func() Address { destination, exists := c.codeLabels[label.Name] + if !exists { panic(fmt.Sprintf("unknown jump label %s", label.Name)) } - distance := (destination - position) / 4 + + distance := int(destination-position) / 4 return arm.Call(distance) } From a023b058f86d9b8dc00ee9cd615f2274c4a4b302 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 17 Apr 2025 14:16:00 +0200 Subject: [PATCH 1003/1012] Simplified register names --- src/arm/Registers.go | 12 +- src/arm/Registers_test.go | 2 +- src/asmc/x86Compiler.go | 40 +-- src/core/CallExtern.go | 12 +- src/core/CompileAssignDivision.go | 10 +- src/core/ExecuteRegisterNumber.go | 4 +- src/core/ExecuteRegisterRegister.go | 4 +- src/x86/Add_test.go | 64 ++--- src/x86/And_test.go | 64 ++--- src/x86/Call_test.go | 48 ++-- src/x86/Compare_test.go | 64 ++--- src/x86/Div_test.go | 16 +- src/x86/ExtendRAXToRDX.go | 4 +- src/x86/LoadAddress_test.go | 16 +- src/x86/LoadDynamic_test.go | 128 +++++----- src/x86/Load_test.go | 256 +++++++++---------- src/x86/Move_test.go | 80 +++--- src/x86/Mul_test.go | 64 ++--- src/x86/Negate_test.go | 16 +- src/x86/Or_test.go | 64 ++--- src/x86/Pop_test.go | 16 +- src/x86/Push_test.go | 16 +- src/x86/Registers.go | 38 ++- src/x86/Registers_test.go | 2 +- src/x86/Shift_test.go | 32 +-- src/x86/StoreDynamic_test.go | 256 +++++++++---------- src/x86/Store_test.go | 384 ++++++++++++++-------------- src/x86/Sub_test.go | 64 ++--- src/x86/Xor_test.go | 64 ++--- src/x86/encode.go | 2 +- src/x86/memoryAccess.go | 4 +- src/x86/memoryAccessDynamic.go | 4 +- src/x86/x86_test.go | 2 +- 33 files changed, 922 insertions(+), 930 deletions(-) diff --git a/src/arm/Registers.go b/src/arm/Registers.go index 9e54156..884816a 100644 --- a/src/arm/Registers.go +++ b/src/arm/Registers.go @@ -44,20 +44,16 @@ const ( ) var ( - GeneralRegisters = []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X19, X20, X21, X22, X23, X24, X25, X26} InputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5} - OutputRegisters = InputRegisters - SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} - SyscallOutputRegisters = []cpu.Register{X0, X1} WindowsInputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5, X6, X7} WindowsOutputRegisters = []cpu.Register{X0, X1} CPU = cpu.CPU{ - General: GeneralRegisters, + General: []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X19, X20, X21, X22, X23, X24, X25, X26}, Input: InputRegisters, - Output: OutputRegisters, - SyscallInput: SyscallInputRegisters, - SyscallOutput: SyscallOutputRegisters, + Output: InputRegisters, + SyscallInput: []cpu.Register{X8, X0, X1, X2, X3, X4, X5}, + SyscallOutput: []cpu.Register{X0, X1}, NumRegisters: 32, } ) diff --git a/src/arm/Registers_test.go b/src/arm/Registers_test.go index 50cdae9..d5c9755 100644 --- a/src/arm/Registers_test.go +++ b/src/arm/Registers_test.go @@ -8,5 +8,5 @@ import ( ) func TestRegisters(t *testing.T) { - assert.NotNil(t, arm.SyscallInputRegisters) + assert.NotContains(t, arm.CPU.General, arm.SP) } diff --git a/src/asmc/x86Compiler.go b/src/asmc/x86Compiler.go index b721039..b09e1d2 100644 --- a/src/asmc/x86Compiler.go +++ b/src/asmc/x86Compiler.go @@ -241,29 +241,29 @@ func (c *x86Compiler) handleDivInstruction(instruction asm.Instruction) { case asm.TypeRegisterNumber: operands := c.assembler.Param.RegisterNumber[instruction.Index] - if operands.Register != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Register) + if operands.Register != x86.R0 { + c.code = x86.MoveRegisterRegister(c.code, x86.R0, operands.Register) } c.code = x86.MoveRegisterNumber(c.code, x86.TMP, operands.Number) - c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.ExtendR0ToR2(c.code) c.code = x86.DivRegister(c.code, x86.TMP) - if operands.Register != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.RAX) + if operands.Register != x86.R0 { + c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.R0) } case asm.TypeRegisterRegister: operands := c.assembler.Param.RegisterRegister[instruction.Index] - if operands.Destination != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) + if operands.Destination != x86.R0 { + c.code = x86.MoveRegisterRegister(c.code, x86.R0, operands.Destination) } - c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.ExtendR0ToR2(c.code) c.code = x86.DivRegister(c.code, operands.Source) - if operands.Destination != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.RAX) + if operands.Destination != x86.R0 { + c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.R0) } } } @@ -273,29 +273,29 @@ func (c *x86Compiler) handleModuloInstruction(instruction asm.Instruction) { case asm.TypeRegisterNumber: operands := c.assembler.Param.RegisterNumber[instruction.Index] - if operands.Register != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Register) + if operands.Register != x86.R0 { + c.code = x86.MoveRegisterRegister(c.code, x86.R0, operands.Register) } c.code = x86.MoveRegisterNumber(c.code, x86.TMP, operands.Number) - c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.ExtendR0ToR2(c.code) c.code = x86.DivRegister(c.code, x86.TMP) - if operands.Register != x86.RDX { - c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.RDX) + if operands.Register != x86.R2 { + c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.R2) } case asm.TypeRegisterRegister: operands := c.assembler.Param.RegisterRegister[instruction.Index] - if operands.Destination != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) + if operands.Destination != x86.R0 { + c.code = x86.MoveRegisterRegister(c.code, x86.R0, operands.Destination) } - c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.ExtendR0ToR2(c.code) c.code = x86.DivRegister(c.code, operands.Source) - if operands.Destination != x86.RDX { - c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.RDX) + if operands.Destination != x86.R2 { + c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.R2) } } } diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go index 241c15a..2adc153 100644 --- a/src/core/CallExtern.go +++ b/src/core/CallExtern.go @@ -30,15 +30,15 @@ func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) return nil, err } - f.Register(asm.PUSH, x86.RBP) - f.RegisterRegister(asm.MOVE, x86.RBP, x86.RSP) - f.RegisterNumber(asm.AND, x86.RSP, -16) + f.Register(asm.PUSH, x86.R5) + f.RegisterRegister(asm.MOVE, x86.R5, x86.SP) + f.RegisterNumber(asm.AND, x86.SP, -16) f.Number(asm.PUSH, 0) f.Number(asm.PUSH, 0) - f.RegisterNumber(asm.SUB, x86.RSP, 32) + f.RegisterNumber(asm.SUB, x86.SP, 32) f.DLLCall(fn.UniqueName) - f.RegisterRegister(asm.MOVE, x86.RSP, x86.RBP) - f.Register(asm.POP, x86.RBP) + f.RegisterRegister(asm.MOVE, x86.SP, x86.R5) + f.Register(asm.POP, x86.R5) for _, register := range registers { f.FreeRegister(register) diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index 3d4ab0e..59fa074 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -76,9 +76,9 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { switch dividend := dividend.(type) { case *eval.Number: - f.SaveRegister(x86.RAX) - f.RegisterNumber(asm.MOVE, x86.RAX, dividend.Number) - err = f.Execute(division.Token, x86.RAX, divisor) + f.SaveRegister(x86.R0) + f.RegisterNumber(asm.MOVE, x86.R0, dividend.Number) + err = f.Execute(division.Token, x86.R0, divisor) case *eval.Register: if dividend.Register != quotientVariable.Value.Register && dividend.IsAlive() { tmp := f.NewRegister() @@ -92,7 +92,7 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, dividend)) } - f.RegisterRegister(asm.MOVE, quotientVariable.Value.Register, x86.RAX) - f.RegisterRegister(asm.MOVE, remainderVariable.Value.Register, x86.RDX) + f.RegisterRegister(asm.MOVE, quotientVariable.Value.Register, x86.R0) + f.RegisterRegister(asm.MOVE, remainderVariable.Value.Register, x86.R2) return err } diff --git a/src/core/ExecuteRegisterNumber.go b/src/core/ExecuteRegisterNumber.go index bd79684..5294e82 100644 --- a/src/core/ExecuteRegisterNumber.go +++ b/src/core/ExecuteRegisterNumber.go @@ -27,14 +27,14 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg case token.Div, token.DivAssign: if config.TargetArch == config.X86 { - f.SaveRegister(x86.RDX) + f.SaveRegister(x86.R2) } f.RegisterNumber(asm.DIV, register, number) case token.Mod, token.ModAssign: if config.TargetArch == config.X86 { - f.SaveRegister(x86.RDX) + f.SaveRegister(x86.R2) } f.RegisterNumber(asm.MODULO, register, number) diff --git a/src/core/ExecuteRegisterRegister.go b/src/core/ExecuteRegisterRegister.go index bcb19de..166ec7f 100644 --- a/src/core/ExecuteRegisterRegister.go +++ b/src/core/ExecuteRegisterRegister.go @@ -27,14 +27,14 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.R case token.Div, token.DivAssign: if config.TargetArch == config.X86 { - f.SaveRegister(x86.RDX) + f.SaveRegister(x86.R2) } f.RegisterRegister(asm.DIV, register, operand) case token.Mod, token.ModAssign: if config.TargetArch == config.X86 { - f.SaveRegister(x86.RDX) + f.SaveRegister(x86.R2) } f.RegisterRegister(asm.MODULO, register, operand) diff --git a/src/x86/Add_test.go b/src/x86/Add_test.go index 71097d4..2291761 100644 --- a/src/x86/Add_test.go +++ b/src/x86/Add_test.go @@ -14,14 +14,14 @@ func TestAddRegisterNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0x83, 0xC0, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0x83, 0xC1, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0x83, 0xC2, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0x83, 0xC3, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0x83, 0xC4, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0x83, 0xC5, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0x83, 0xC6, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0x83, 0xC7, 0x01}}, + {x86.R0, 1, []byte{0x48, 0x83, 0xC0, 0x01}}, + {x86.R1, 1, []byte{0x48, 0x83, 0xC1, 0x01}}, + {x86.R2, 1, []byte{0x48, 0x83, 0xC2, 0x01}}, + {x86.R3, 1, []byte{0x48, 0x83, 0xC3, 0x01}}, + {x86.SP, 1, []byte{0x48, 0x83, 0xC4, 0x01}}, + {x86.R5, 1, []byte{0x48, 0x83, 0xC5, 0x01}}, + {x86.R6, 1, []byte{0x48, 0x83, 0xC6, 0x01}}, + {x86.R7, 1, []byte{0x48, 0x83, 0xC7, 0x01}}, {x86.R8, 1, []byte{0x49, 0x83, 0xC0, 0x01}}, {x86.R9, 1, []byte{0x49, 0x83, 0xC1, 0x01}}, {x86.R10, 1, []byte{0x49, 0x83, 0xC2, 0x01}}, @@ -31,14 +31,14 @@ func TestAddRegisterNumber(t *testing.T) { {x86.R14, 1, []byte{0x49, 0x83, 0xC6, 0x01}}, {x86.R15, 1, []byte{0x49, 0x83, 0xC7, 0x01}}, - {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -62,22 +62,22 @@ func TestAddRegisterRegister(t *testing.T) { Right cpu.Register Code []byte }{ - {x86.RAX, x86.R15, []byte{0x4C, 0x01, 0xF8}}, - {x86.RCX, x86.R14, []byte{0x4C, 0x01, 0xF1}}, - {x86.RDX, x86.R13, []byte{0x4C, 0x01, 0xEA}}, - {x86.RBX, x86.R12, []byte{0x4C, 0x01, 0xE3}}, - {x86.RSP, x86.R11, []byte{0x4C, 0x01, 0xDC}}, - {x86.RBP, x86.R10, []byte{0x4C, 0x01, 0xD5}}, - {x86.RSI, x86.R9, []byte{0x4C, 0x01, 0xCE}}, - {x86.RDI, x86.R8, []byte{0x4C, 0x01, 0xC7}}, - {x86.R8, x86.RDI, []byte{0x49, 0x01, 0xF8}}, - {x86.R9, x86.RSI, []byte{0x49, 0x01, 0xF1}}, - {x86.R10, x86.RBP, []byte{0x49, 0x01, 0xEA}}, - {x86.R11, x86.RSP, []byte{0x49, 0x01, 0xE3}}, - {x86.R12, x86.RBX, []byte{0x49, 0x01, 0xDC}}, - {x86.R13, x86.RDX, []byte{0x49, 0x01, 0xD5}}, - {x86.R14, x86.RCX, []byte{0x49, 0x01, 0xCE}}, - {x86.R15, x86.RAX, []byte{0x49, 0x01, 0xC7}}, + {x86.R0, x86.R15, []byte{0x4C, 0x01, 0xF8}}, + {x86.R1, x86.R14, []byte{0x4C, 0x01, 0xF1}}, + {x86.R2, x86.R13, []byte{0x4C, 0x01, 0xEA}}, + {x86.R3, x86.R12, []byte{0x4C, 0x01, 0xE3}}, + {x86.SP, x86.R11, []byte{0x4C, 0x01, 0xDC}}, + {x86.R5, x86.R10, []byte{0x4C, 0x01, 0xD5}}, + {x86.R6, x86.R9, []byte{0x4C, 0x01, 0xCE}}, + {x86.R7, x86.R8, []byte{0x4C, 0x01, 0xC7}}, + {x86.R8, x86.R7, []byte{0x49, 0x01, 0xF8}}, + {x86.R9, x86.R6, []byte{0x49, 0x01, 0xF1}}, + {x86.R10, x86.R5, []byte{0x49, 0x01, 0xEA}}, + {x86.R11, x86.SP, []byte{0x49, 0x01, 0xE3}}, + {x86.R12, x86.R3, []byte{0x49, 0x01, 0xDC}}, + {x86.R13, x86.R2, []byte{0x49, 0x01, 0xD5}}, + {x86.R14, x86.R1, []byte{0x49, 0x01, 0xCE}}, + {x86.R15, x86.R0, []byte{0x49, 0x01, 0xC7}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/And_test.go b/src/x86/And_test.go index 7fb6fd4..9474456 100644 --- a/src/x86/And_test.go +++ b/src/x86/And_test.go @@ -14,14 +14,14 @@ func TestAndRegisterNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0x83, 0xE0, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0x83, 0xE1, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0x83, 0xE2, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0x83, 0xE3, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0x83, 0xE4, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0x83, 0xE5, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0x83, 0xE6, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0x83, 0xE7, 0x01}}, + {x86.R0, 1, []byte{0x48, 0x83, 0xE0, 0x01}}, + {x86.R1, 1, []byte{0x48, 0x83, 0xE1, 0x01}}, + {x86.R2, 1, []byte{0x48, 0x83, 0xE2, 0x01}}, + {x86.R3, 1, []byte{0x48, 0x83, 0xE3, 0x01}}, + {x86.SP, 1, []byte{0x48, 0x83, 0xE4, 0x01}}, + {x86.R5, 1, []byte{0x48, 0x83, 0xE5, 0x01}}, + {x86.R6, 1, []byte{0x48, 0x83, 0xE6, 0x01}}, + {x86.R7, 1, []byte{0x48, 0x83, 0xE7, 0x01}}, {x86.R8, 1, []byte{0x49, 0x83, 0xE0, 0x01}}, {x86.R9, 1, []byte{0x49, 0x83, 0xE1, 0x01}}, {x86.R10, 1, []byte{0x49, 0x83, 0xE2, 0x01}}, @@ -31,14 +31,14 @@ func TestAndRegisterNumber(t *testing.T) { {x86.R14, 1, []byte{0x49, 0x83, 0xE6, 0x01}}, {x86.R15, 1, []byte{0x49, 0x83, 0xE7, 0x01}}, - {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE7, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -62,22 +62,22 @@ func TestAndRegisterRegister(t *testing.T) { Right cpu.Register Code []byte }{ - {x86.RAX, x86.R15, []byte{0x4C, 0x21, 0xF8}}, - {x86.RCX, x86.R14, []byte{0x4C, 0x21, 0xF1}}, - {x86.RDX, x86.R13, []byte{0x4C, 0x21, 0xEA}}, - {x86.RBX, x86.R12, []byte{0x4C, 0x21, 0xE3}}, - {x86.RSP, x86.R11, []byte{0x4C, 0x21, 0xDC}}, - {x86.RBP, x86.R10, []byte{0x4C, 0x21, 0xD5}}, - {x86.RSI, x86.R9, []byte{0x4C, 0x21, 0xCE}}, - {x86.RDI, x86.R8, []byte{0x4C, 0x21, 0xC7}}, - {x86.R8, x86.RDI, []byte{0x49, 0x21, 0xF8}}, - {x86.R9, x86.RSI, []byte{0x49, 0x21, 0xF1}}, - {x86.R10, x86.RBP, []byte{0x49, 0x21, 0xEA}}, - {x86.R11, x86.RSP, []byte{0x49, 0x21, 0xE3}}, - {x86.R12, x86.RBX, []byte{0x49, 0x21, 0xDC}}, - {x86.R13, x86.RDX, []byte{0x49, 0x21, 0xD5}}, - {x86.R14, x86.RCX, []byte{0x49, 0x21, 0xCE}}, - {x86.R15, x86.RAX, []byte{0x49, 0x21, 0xC7}}, + {x86.R0, x86.R15, []byte{0x4C, 0x21, 0xF8}}, + {x86.R1, x86.R14, []byte{0x4C, 0x21, 0xF1}}, + {x86.R2, x86.R13, []byte{0x4C, 0x21, 0xEA}}, + {x86.R3, x86.R12, []byte{0x4C, 0x21, 0xE3}}, + {x86.SP, x86.R11, []byte{0x4C, 0x21, 0xDC}}, + {x86.R5, x86.R10, []byte{0x4C, 0x21, 0xD5}}, + {x86.R6, x86.R9, []byte{0x4C, 0x21, 0xCE}}, + {x86.R7, x86.R8, []byte{0x4C, 0x21, 0xC7}}, + {x86.R8, x86.R7, []byte{0x49, 0x21, 0xF8}}, + {x86.R9, x86.R6, []byte{0x49, 0x21, 0xF1}}, + {x86.R10, x86.R5, []byte{0x49, 0x21, 0xEA}}, + {x86.R11, x86.SP, []byte{0x49, 0x21, 0xE3}}, + {x86.R12, x86.R3, []byte{0x49, 0x21, 0xDC}}, + {x86.R13, x86.R2, []byte{0x49, 0x21, 0xD5}}, + {x86.R14, x86.R1, []byte{0x49, 0x21, 0xCE}}, + {x86.R15, x86.R0, []byte{0x49, 0x21, 0xC7}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Call_test.go b/src/x86/Call_test.go index 033ee64..ec8006e 100644 --- a/src/x86/Call_test.go +++ b/src/x86/Call_test.go @@ -13,14 +13,14 @@ func TestCallRegister(t *testing.T) { Register cpu.Register Code []byte }{ - {x86.RAX, []byte{0xFF, 0xD0}}, - {x86.RCX, []byte{0xFF, 0xD1}}, - {x86.RDX, []byte{0xFF, 0xD2}}, - {x86.RBX, []byte{0xFF, 0xD3}}, - {x86.RSP, []byte{0xFF, 0xD4}}, - {x86.RBP, []byte{0xFF, 0xD5}}, - {x86.RSI, []byte{0xFF, 0xD6}}, - {x86.RDI, []byte{0xFF, 0xD7}}, + {x86.R0, []byte{0xFF, 0xD0}}, + {x86.R1, []byte{0xFF, 0xD1}}, + {x86.R2, []byte{0xFF, 0xD2}}, + {x86.R3, []byte{0xFF, 0xD3}}, + {x86.SP, []byte{0xFF, 0xD4}}, + {x86.R5, []byte{0xFF, 0xD5}}, + {x86.R6, []byte{0xFF, 0xD6}}, + {x86.R7, []byte{0xFF, 0xD7}}, {x86.R8, []byte{0x41, 0xFF, 0xD0}}, {x86.R9, []byte{0x41, 0xFF, 0xD1}}, {x86.R10, []byte{0x41, 0xFF, 0xD2}}, @@ -44,14 +44,14 @@ func TestCallAtMemory(t *testing.T) { Offset int8 Code []byte }{ - {x86.RAX, 0, []byte{0xFF, 0x10}}, - {x86.RCX, 0, []byte{0xFF, 0x11}}, - {x86.RDX, 0, []byte{0xFF, 0x12}}, - {x86.RBX, 0, []byte{0xFF, 0x13}}, - {x86.RSP, 0, []byte{0xFF, 0x14, 0x24}}, - {x86.RBP, 0, []byte{0xFF, 0x55, 0x00}}, - {x86.RSI, 0, []byte{0xFF, 0x16}}, - {x86.RDI, 0, []byte{0xFF, 0x17}}, + {x86.R0, 0, []byte{0xFF, 0x10}}, + {x86.R1, 0, []byte{0xFF, 0x11}}, + {x86.R2, 0, []byte{0xFF, 0x12}}, + {x86.R3, 0, []byte{0xFF, 0x13}}, + {x86.SP, 0, []byte{0xFF, 0x14, 0x24}}, + {x86.R5, 0, []byte{0xFF, 0x55, 0x00}}, + {x86.R6, 0, []byte{0xFF, 0x16}}, + {x86.R7, 0, []byte{0xFF, 0x17}}, {x86.R8, 0, []byte{0x41, 0xFF, 0x10}}, {x86.R9, 0, []byte{0x41, 0xFF, 0x11}}, {x86.R10, 0, []byte{0x41, 0xFF, 0x12}}, @@ -61,14 +61,14 @@ func TestCallAtMemory(t *testing.T) { {x86.R14, 0, []byte{0x41, 0xFF, 0x16}}, {x86.R15, 0, []byte{0x41, 0xFF, 0x17}}, - {x86.RAX, 1, []byte{0xFF, 0x50, 0x01}}, - {x86.RCX, 1, []byte{0xFF, 0x51, 0x01}}, - {x86.RDX, 1, []byte{0xFF, 0x52, 0x01}}, - {x86.RBX, 1, []byte{0xFF, 0x53, 0x01}}, - {x86.RSP, 1, []byte{0xFF, 0x54, 0x24, 0x01}}, - {x86.RBP, 1, []byte{0xFF, 0x55, 0x01}}, - {x86.RSI, 1, []byte{0xFF, 0x56, 0x01}}, - {x86.RDI, 1, []byte{0xFF, 0x57, 0x01}}, + {x86.R0, 1, []byte{0xFF, 0x50, 0x01}}, + {x86.R1, 1, []byte{0xFF, 0x51, 0x01}}, + {x86.R2, 1, []byte{0xFF, 0x52, 0x01}}, + {x86.R3, 1, []byte{0xFF, 0x53, 0x01}}, + {x86.SP, 1, []byte{0xFF, 0x54, 0x24, 0x01}}, + {x86.R5, 1, []byte{0xFF, 0x55, 0x01}}, + {x86.R6, 1, []byte{0xFF, 0x56, 0x01}}, + {x86.R7, 1, []byte{0xFF, 0x57, 0x01}}, {x86.R8, 1, []byte{0x41, 0xFF, 0x50, 0x01}}, {x86.R9, 1, []byte{0x41, 0xFF, 0x51, 0x01}}, {x86.R10, 1, []byte{0x41, 0xFF, 0x52, 0x01}}, diff --git a/src/x86/Compare_test.go b/src/x86/Compare_test.go index 141c863..953e4a0 100644 --- a/src/x86/Compare_test.go +++ b/src/x86/Compare_test.go @@ -14,14 +14,14 @@ func TestCompareRegisterNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0x83, 0xF8, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0x83, 0xF9, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0x83, 0xFA, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0x83, 0xFB, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0x83, 0xFC, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0x83, 0xFD, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0x83, 0xFE, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0x83, 0xFF, 0x01}}, + {x86.R0, 1, []byte{0x48, 0x83, 0xF8, 0x01}}, + {x86.R1, 1, []byte{0x48, 0x83, 0xF9, 0x01}}, + {x86.R2, 1, []byte{0x48, 0x83, 0xFA, 0x01}}, + {x86.R3, 1, []byte{0x48, 0x83, 0xFB, 0x01}}, + {x86.SP, 1, []byte{0x48, 0x83, 0xFC, 0x01}}, + {x86.R5, 1, []byte{0x48, 0x83, 0xFD, 0x01}}, + {x86.R6, 1, []byte{0x48, 0x83, 0xFE, 0x01}}, + {x86.R7, 1, []byte{0x48, 0x83, 0xFF, 0x01}}, {x86.R8, 1, []byte{0x49, 0x83, 0xF8, 0x01}}, {x86.R9, 1, []byte{0x49, 0x83, 0xF9, 0x01}}, {x86.R10, 1, []byte{0x49, 0x83, 0xFA, 0x01}}, @@ -31,14 +31,14 @@ func TestCompareRegisterNumber(t *testing.T) { {x86.R14, 1, []byte{0x49, 0x83, 0xFE, 0x01}}, {x86.R15, 1, []byte{0x49, 0x83, 0xFF, 0x01}}, - {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -62,22 +62,22 @@ func TestCompareRegisterRegister(t *testing.T) { Right cpu.Register Code []byte }{ - {x86.RAX, x86.R15, []byte{0x4C, 0x39, 0xF8}}, - {x86.RCX, x86.R14, []byte{0x4C, 0x39, 0xF1}}, - {x86.RDX, x86.R13, []byte{0x4C, 0x39, 0xEA}}, - {x86.RBX, x86.R12, []byte{0x4C, 0x39, 0xE3}}, - {x86.RSP, x86.R11, []byte{0x4C, 0x39, 0xDC}}, - {x86.RBP, x86.R10, []byte{0x4C, 0x39, 0xD5}}, - {x86.RSI, x86.R9, []byte{0x4C, 0x39, 0xCE}}, - {x86.RDI, x86.R8, []byte{0x4C, 0x39, 0xC7}}, - {x86.R8, x86.RDI, []byte{0x49, 0x39, 0xF8}}, - {x86.R9, x86.RSI, []byte{0x49, 0x39, 0xF1}}, - {x86.R10, x86.RBP, []byte{0x49, 0x39, 0xEA}}, - {x86.R11, x86.RSP, []byte{0x49, 0x39, 0xE3}}, - {x86.R12, x86.RBX, []byte{0x49, 0x39, 0xDC}}, - {x86.R13, x86.RDX, []byte{0x49, 0x39, 0xD5}}, - {x86.R14, x86.RCX, []byte{0x49, 0x39, 0xCE}}, - {x86.R15, x86.RAX, []byte{0x49, 0x39, 0xC7}}, + {x86.R0, x86.R15, []byte{0x4C, 0x39, 0xF8}}, + {x86.R1, x86.R14, []byte{0x4C, 0x39, 0xF1}}, + {x86.R2, x86.R13, []byte{0x4C, 0x39, 0xEA}}, + {x86.R3, x86.R12, []byte{0x4C, 0x39, 0xE3}}, + {x86.SP, x86.R11, []byte{0x4C, 0x39, 0xDC}}, + {x86.R5, x86.R10, []byte{0x4C, 0x39, 0xD5}}, + {x86.R6, x86.R9, []byte{0x4C, 0x39, 0xCE}}, + {x86.R7, x86.R8, []byte{0x4C, 0x39, 0xC7}}, + {x86.R8, x86.R7, []byte{0x49, 0x39, 0xF8}}, + {x86.R9, x86.R6, []byte{0x49, 0x39, 0xF1}}, + {x86.R10, x86.R5, []byte{0x49, 0x39, 0xEA}}, + {x86.R11, x86.SP, []byte{0x49, 0x39, 0xE3}}, + {x86.R12, x86.R3, []byte{0x49, 0x39, 0xDC}}, + {x86.R13, x86.R2, []byte{0x49, 0x39, 0xD5}}, + {x86.R14, x86.R1, []byte{0x49, 0x39, 0xCE}}, + {x86.R15, x86.R0, []byte{0x49, 0x39, 0xC7}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Div_test.go b/src/x86/Div_test.go index 5069f76..aa8ca0d 100644 --- a/src/x86/Div_test.go +++ b/src/x86/Div_test.go @@ -13,14 +13,14 @@ func TestDivRegister(t *testing.T) { Register cpu.Register Code []byte }{ - {x86.RAX, []byte{0x48, 0xF7, 0xF8}}, - {x86.RCX, []byte{0x48, 0xF7, 0xF9}}, - {x86.RDX, []byte{0x48, 0xF7, 0xFA}}, - {x86.RBX, []byte{0x48, 0xF7, 0xFB}}, - {x86.RSP, []byte{0x48, 0xF7, 0xFC}}, - {x86.RBP, []byte{0x48, 0xF7, 0xFD}}, - {x86.RSI, []byte{0x48, 0xF7, 0xFE}}, - {x86.RDI, []byte{0x48, 0xF7, 0xFF}}, + {x86.R0, []byte{0x48, 0xF7, 0xF8}}, + {x86.R1, []byte{0x48, 0xF7, 0xF9}}, + {x86.R2, []byte{0x48, 0xF7, 0xFA}}, + {x86.R3, []byte{0x48, 0xF7, 0xFB}}, + {x86.SP, []byte{0x48, 0xF7, 0xFC}}, + {x86.R5, []byte{0x48, 0xF7, 0xFD}}, + {x86.R6, []byte{0x48, 0xF7, 0xFE}}, + {x86.R7, []byte{0x48, 0xF7, 0xFF}}, {x86.R8, []byte{0x49, 0xF7, 0xF8}}, {x86.R9, []byte{0x49, 0xF7, 0xF9}}, {x86.R10, []byte{0x49, 0xF7, 0xFA}}, diff --git a/src/x86/ExtendRAXToRDX.go b/src/x86/ExtendRAXToRDX.go index 7d97a86..825a084 100644 --- a/src/x86/ExtendRAXToRDX.go +++ b/src/x86/ExtendRAXToRDX.go @@ -1,7 +1,7 @@ package x86 -// ExtendRAXToRDX doubles the size of RAX by sign-extending it to RDX. +// ExtendR0ToR2 doubles the size of R0 (RAX) by sign-extending it to R2 (RDX). // This is also known as CQO. -func ExtendRAXToRDX(code []byte) []byte { +func ExtendR0ToR2(code []byte) []byte { return append(code, 0x48, 0x99) } diff --git a/src/x86/LoadAddress_test.go b/src/x86/LoadAddress_test.go index cedf23f..a0570e3 100644 --- a/src/x86/LoadAddress_test.go +++ b/src/x86/LoadAddress_test.go @@ -14,14 +14,14 @@ func TestLoadAddress(t *testing.T) { Offset int Code []byte }{ - {x86.RAX, 0, []byte{0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00}}, - {x86.RCX, 0, []byte{0x48, 0x8D, 0x0D, 0x00, 0x00, 0x00, 0x00}}, - {x86.RDX, 0, []byte{0x48, 0x8D, 0x15, 0x00, 0x00, 0x00, 0x00}}, - {x86.RBX, 0, []byte{0x48, 0x8D, 0x1D, 0x00, 0x00, 0x00, 0x00}}, - {x86.RSP, 0, []byte{0x48, 0x8D, 0x25, 0x00, 0x00, 0x00, 0x00}}, - {x86.RBP, 0, []byte{0x48, 0x8D, 0x2D, 0x00, 0x00, 0x00, 0x00}}, - {x86.RSI, 0, []byte{0x48, 0x8D, 0x35, 0x00, 0x00, 0x00, 0x00}}, - {x86.RDI, 0, []byte{0x48, 0x8D, 0x3D, 0x00, 0x00, 0x00, 0x00}}, + {x86.R0, 0, []byte{0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00}}, + {x86.R1, 0, []byte{0x48, 0x8D, 0x0D, 0x00, 0x00, 0x00, 0x00}}, + {x86.R2, 0, []byte{0x48, 0x8D, 0x15, 0x00, 0x00, 0x00, 0x00}}, + {x86.R3, 0, []byte{0x48, 0x8D, 0x1D, 0x00, 0x00, 0x00, 0x00}}, + {x86.SP, 0, []byte{0x48, 0x8D, 0x25, 0x00, 0x00, 0x00, 0x00}}, + {x86.R5, 0, []byte{0x48, 0x8D, 0x2D, 0x00, 0x00, 0x00, 0x00}}, + {x86.R6, 0, []byte{0x48, 0x8D, 0x35, 0x00, 0x00, 0x00, 0x00}}, + {x86.R7, 0, []byte{0x48, 0x8D, 0x3D, 0x00, 0x00, 0x00, 0x00}}, {x86.R8, 0, []byte{0x4C, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00}}, {x86.R9, 0, []byte{0x4C, 0x8D, 0x0D, 0x00, 0x00, 0x00, 0x00}}, {x86.R10, 0, []byte{0x4C, 0x8D, 0x15, 0x00, 0x00, 0x00, 0x00}}, diff --git a/src/x86/LoadDynamic_test.go b/src/x86/LoadDynamic_test.go index 4cc3935..86cbf36 100644 --- a/src/x86/LoadDynamic_test.go +++ b/src/x86/LoadDynamic_test.go @@ -16,70 +16,70 @@ func TestLoadDynamicRegister(t *testing.T) { OffsetRegister cpu.Register Code []byte }{ - {x86.R15, 8, x86.RAX, x86.R15, []byte{0x4E, 0x8B, 0x3C, 0x38}}, - {x86.R15, 4, x86.RAX, x86.R15, []byte{0x46, 0x8B, 0x3C, 0x38}}, - {x86.R15, 2, x86.RAX, x86.R15, []byte{0x66, 0x46, 0x8B, 0x3C, 0x38}}, - {x86.R15, 1, x86.RAX, x86.R15, []byte{0x46, 0x8A, 0x3C, 0x38}}, - {x86.R14, 8, x86.RCX, x86.R14, []byte{0x4E, 0x8B, 0x34, 0x31}}, - {x86.R14, 4, x86.RCX, x86.R14, []byte{0x46, 0x8B, 0x34, 0x31}}, - {x86.R14, 2, x86.RCX, x86.R14, []byte{0x66, 0x46, 0x8B, 0x34, 0x31}}, - {x86.R14, 1, x86.RCX, x86.R14, []byte{0x46, 0x8A, 0x34, 0x31}}, - {x86.R13, 8, x86.RDX, x86.R13, []byte{0x4E, 0x8B, 0x2C, 0x2A}}, - {x86.R13, 4, x86.RDX, x86.R13, []byte{0x46, 0x8B, 0x2C, 0x2A}}, - {x86.R13, 2, x86.RDX, x86.R13, []byte{0x66, 0x46, 0x8B, 0x2C, 0x2A}}, - {x86.R13, 1, x86.RDX, x86.R13, []byte{0x46, 0x8A, 0x2C, 0x2A}}, - {x86.R12, 8, x86.RBX, x86.R12, []byte{0x4E, 0x8B, 0x24, 0x23}}, - {x86.R12, 4, x86.RBX, x86.R12, []byte{0x46, 0x8B, 0x24, 0x23}}, - {x86.R12, 2, x86.RBX, x86.R12, []byte{0x66, 0x46, 0x8B, 0x24, 0x23}}, - {x86.R12, 1, x86.RBX, x86.R12, []byte{0x46, 0x8A, 0x24, 0x23}}, - {x86.R11, 8, x86.RSP, x86.R11, []byte{0x4E, 0x8B, 0x1C, 0x1C}}, - {x86.R11, 4, x86.RSP, x86.R11, []byte{0x46, 0x8B, 0x1C, 0x1C}}, - {x86.R11, 2, x86.RSP, x86.R11, []byte{0x66, 0x46, 0x8B, 0x1C, 0x1C}}, - {x86.R11, 1, x86.RSP, x86.R11, []byte{0x46, 0x8A, 0x1C, 0x1C}}, - {x86.R10, 8, x86.RBP, x86.R10, []byte{0x4E, 0x8B, 0x54, 0x15, 0x00}}, - {x86.R10, 4, x86.RBP, x86.R10, []byte{0x46, 0x8B, 0x54, 0x15, 0x00}}, - {x86.R10, 2, x86.RBP, x86.R10, []byte{0x66, 0x46, 0x8B, 0x54, 0x15, 0x00}}, - {x86.R10, 1, x86.RBP, x86.R10, []byte{0x46, 0x8A, 0x54, 0x15, 0x00}}, - {x86.R9, 8, x86.RSI, x86.R9, []byte{0x4E, 0x8B, 0x0C, 0x0E}}, - {x86.R9, 4, x86.RSI, x86.R9, []byte{0x46, 0x8B, 0x0C, 0x0E}}, - {x86.R9, 2, x86.RSI, x86.R9, []byte{0x66, 0x46, 0x8B, 0x0C, 0x0E}}, - {x86.R9, 1, x86.RSI, x86.R9, []byte{0x46, 0x8A, 0x0C, 0x0E}}, - {x86.R8, 8, x86.RDI, x86.R8, []byte{0x4E, 0x8B, 0x04, 0x07}}, - {x86.R8, 4, x86.RDI, x86.R8, []byte{0x46, 0x8B, 0x04, 0x07}}, - {x86.R8, 2, x86.RDI, x86.R8, []byte{0x66, 0x46, 0x8B, 0x04, 0x07}}, - {x86.R8, 1, x86.RDI, x86.R8, []byte{0x46, 0x8A, 0x04, 0x07}}, - {x86.RDI, 8, x86.R8, x86.RDI, []byte{0x49, 0x8B, 0x3C, 0x38}}, - {x86.RDI, 4, x86.R8, x86.RDI, []byte{0x41, 0x8B, 0x3C, 0x38}}, - {x86.RDI, 2, x86.R8, x86.RDI, []byte{0x66, 0x41, 0x8B, 0x3C, 0x38}}, - {x86.RDI, 1, x86.R8, x86.RDI, []byte{0x41, 0x8A, 0x3C, 0x38}}, - {x86.RSI, 8, x86.R9, x86.RSI, []byte{0x49, 0x8B, 0x34, 0x31}}, - {x86.RSI, 4, x86.R9, x86.RSI, []byte{0x41, 0x8B, 0x34, 0x31}}, - {x86.RSI, 2, x86.R9, x86.RSI, []byte{0x66, 0x41, 0x8B, 0x34, 0x31}}, - {x86.RSI, 1, x86.R9, x86.RSI, []byte{0x41, 0x8A, 0x34, 0x31}}, - {x86.RBP, 8, x86.R10, x86.RBP, []byte{0x49, 0x8B, 0x2C, 0x2A}}, - {x86.RBP, 4, x86.R10, x86.RBP, []byte{0x41, 0x8B, 0x2C, 0x2A}}, - {x86.RBP, 2, x86.R10, x86.RBP, []byte{0x66, 0x41, 0x8B, 0x2C, 0x2A}}, - {x86.RBP, 1, x86.R10, x86.RBP, []byte{0x41, 0x8A, 0x2C, 0x2A}}, - {x86.RSP, 8, x86.R11, x86.RSP, []byte{0x4A, 0x8B, 0x24, 0x1C}}, - {x86.RSP, 4, x86.R11, x86.RSP, []byte{0x42, 0x8B, 0x24, 0x1C}}, - {x86.RSP, 2, x86.R11, x86.RSP, []byte{0x66, 0x42, 0x8B, 0x24, 0x1C}}, - {x86.RSP, 1, x86.R11, x86.RSP, []byte{0x42, 0x8A, 0x24, 0x1C}}, - {x86.RBX, 8, x86.R12, x86.RBX, []byte{0x49, 0x8B, 0x1C, 0x1C}}, - {x86.RBX, 4, x86.R12, x86.RBX, []byte{0x41, 0x8B, 0x1C, 0x1C}}, - {x86.RBX, 2, x86.R12, x86.RBX, []byte{0x66, 0x41, 0x8B, 0x1C, 0x1C}}, - {x86.RBX, 1, x86.R12, x86.RBX, []byte{0x41, 0x8A, 0x1C, 0x1C}}, - {x86.RDX, 8, x86.R13, x86.RDX, []byte{0x49, 0x8B, 0x54, 0x15, 0x00}}, - {x86.RDX, 4, x86.R13, x86.RDX, []byte{0x41, 0x8B, 0x54, 0x15, 0x00}}, - {x86.RDX, 2, x86.R13, x86.RDX, []byte{0x66, 0x41, 0x8B, 0x54, 0x15, 0x00}}, - {x86.RDX, 1, x86.R13, x86.RDX, []byte{0x41, 0x8A, 0x54, 0x15, 0x00}}, - {x86.RCX, 8, x86.R14, x86.RCX, []byte{0x49, 0x8B, 0x0C, 0x0E}}, - {x86.RCX, 4, x86.R14, x86.RCX, []byte{0x41, 0x8B, 0x0C, 0x0E}}, - {x86.RCX, 2, x86.R14, x86.RCX, []byte{0x66, 0x41, 0x8B, 0x0C, 0x0E}}, - {x86.RCX, 1, x86.R14, x86.RCX, []byte{0x41, 0x8A, 0x0C, 0x0E}}, - {x86.RAX, 8, x86.R15, x86.RAX, []byte{0x49, 0x8B, 0x04, 0x07}}, - {x86.RAX, 4, x86.R15, x86.RAX, []byte{0x41, 0x8B, 0x04, 0x07}}, - {x86.RAX, 2, x86.R15, x86.RAX, []byte{0x66, 0x41, 0x8B, 0x04, 0x07}}, - {x86.RAX, 1, x86.R15, x86.RAX, []byte{0x41, 0x8A, 0x04, 0x07}}, + {x86.R15, 8, x86.R0, x86.R15, []byte{0x4E, 0x8B, 0x3C, 0x38}}, + {x86.R15, 4, x86.R0, x86.R15, []byte{0x46, 0x8B, 0x3C, 0x38}}, + {x86.R15, 2, x86.R0, x86.R15, []byte{0x66, 0x46, 0x8B, 0x3C, 0x38}}, + {x86.R15, 1, x86.R0, x86.R15, []byte{0x46, 0x8A, 0x3C, 0x38}}, + {x86.R14, 8, x86.R1, x86.R14, []byte{0x4E, 0x8B, 0x34, 0x31}}, + {x86.R14, 4, x86.R1, x86.R14, []byte{0x46, 0x8B, 0x34, 0x31}}, + {x86.R14, 2, x86.R1, x86.R14, []byte{0x66, 0x46, 0x8B, 0x34, 0x31}}, + {x86.R14, 1, x86.R1, x86.R14, []byte{0x46, 0x8A, 0x34, 0x31}}, + {x86.R13, 8, x86.R2, x86.R13, []byte{0x4E, 0x8B, 0x2C, 0x2A}}, + {x86.R13, 4, x86.R2, x86.R13, []byte{0x46, 0x8B, 0x2C, 0x2A}}, + {x86.R13, 2, x86.R2, x86.R13, []byte{0x66, 0x46, 0x8B, 0x2C, 0x2A}}, + {x86.R13, 1, x86.R2, x86.R13, []byte{0x46, 0x8A, 0x2C, 0x2A}}, + {x86.R12, 8, x86.R3, x86.R12, []byte{0x4E, 0x8B, 0x24, 0x23}}, + {x86.R12, 4, x86.R3, x86.R12, []byte{0x46, 0x8B, 0x24, 0x23}}, + {x86.R12, 2, x86.R3, x86.R12, []byte{0x66, 0x46, 0x8B, 0x24, 0x23}}, + {x86.R12, 1, x86.R3, x86.R12, []byte{0x46, 0x8A, 0x24, 0x23}}, + {x86.R11, 8, x86.SP, x86.R11, []byte{0x4E, 0x8B, 0x1C, 0x1C}}, + {x86.R11, 4, x86.SP, x86.R11, []byte{0x46, 0x8B, 0x1C, 0x1C}}, + {x86.R11, 2, x86.SP, x86.R11, []byte{0x66, 0x46, 0x8B, 0x1C, 0x1C}}, + {x86.R11, 1, x86.SP, x86.R11, []byte{0x46, 0x8A, 0x1C, 0x1C}}, + {x86.R10, 8, x86.R5, x86.R10, []byte{0x4E, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R10, 4, x86.R5, x86.R10, []byte{0x46, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R10, 2, x86.R5, x86.R10, []byte{0x66, 0x46, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R10, 1, x86.R5, x86.R10, []byte{0x46, 0x8A, 0x54, 0x15, 0x00}}, + {x86.R9, 8, x86.R6, x86.R9, []byte{0x4E, 0x8B, 0x0C, 0x0E}}, + {x86.R9, 4, x86.R6, x86.R9, []byte{0x46, 0x8B, 0x0C, 0x0E}}, + {x86.R9, 2, x86.R6, x86.R9, []byte{0x66, 0x46, 0x8B, 0x0C, 0x0E}}, + {x86.R9, 1, x86.R6, x86.R9, []byte{0x46, 0x8A, 0x0C, 0x0E}}, + {x86.R8, 8, x86.R7, x86.R8, []byte{0x4E, 0x8B, 0x04, 0x07}}, + {x86.R8, 4, x86.R7, x86.R8, []byte{0x46, 0x8B, 0x04, 0x07}}, + {x86.R8, 2, x86.R7, x86.R8, []byte{0x66, 0x46, 0x8B, 0x04, 0x07}}, + {x86.R8, 1, x86.R7, x86.R8, []byte{0x46, 0x8A, 0x04, 0x07}}, + {x86.R7, 8, x86.R8, x86.R7, []byte{0x49, 0x8B, 0x3C, 0x38}}, + {x86.R7, 4, x86.R8, x86.R7, []byte{0x41, 0x8B, 0x3C, 0x38}}, + {x86.R7, 2, x86.R8, x86.R7, []byte{0x66, 0x41, 0x8B, 0x3C, 0x38}}, + {x86.R7, 1, x86.R8, x86.R7, []byte{0x41, 0x8A, 0x3C, 0x38}}, + {x86.R6, 8, x86.R9, x86.R6, []byte{0x49, 0x8B, 0x34, 0x31}}, + {x86.R6, 4, x86.R9, x86.R6, []byte{0x41, 0x8B, 0x34, 0x31}}, + {x86.R6, 2, x86.R9, x86.R6, []byte{0x66, 0x41, 0x8B, 0x34, 0x31}}, + {x86.R6, 1, x86.R9, x86.R6, []byte{0x41, 0x8A, 0x34, 0x31}}, + {x86.R5, 8, x86.R10, x86.R5, []byte{0x49, 0x8B, 0x2C, 0x2A}}, + {x86.R5, 4, x86.R10, x86.R5, []byte{0x41, 0x8B, 0x2C, 0x2A}}, + {x86.R5, 2, x86.R10, x86.R5, []byte{0x66, 0x41, 0x8B, 0x2C, 0x2A}}, + {x86.R5, 1, x86.R10, x86.R5, []byte{0x41, 0x8A, 0x2C, 0x2A}}, + {x86.SP, 8, x86.R11, x86.SP, []byte{0x4A, 0x8B, 0x24, 0x1C}}, + {x86.SP, 4, x86.R11, x86.SP, []byte{0x42, 0x8B, 0x24, 0x1C}}, + {x86.SP, 2, x86.R11, x86.SP, []byte{0x66, 0x42, 0x8B, 0x24, 0x1C}}, + {x86.SP, 1, x86.R11, x86.SP, []byte{0x42, 0x8A, 0x24, 0x1C}}, + {x86.R3, 8, x86.R12, x86.R3, []byte{0x49, 0x8B, 0x1C, 0x1C}}, + {x86.R3, 4, x86.R12, x86.R3, []byte{0x41, 0x8B, 0x1C, 0x1C}}, + {x86.R3, 2, x86.R12, x86.R3, []byte{0x66, 0x41, 0x8B, 0x1C, 0x1C}}, + {x86.R3, 1, x86.R12, x86.R3, []byte{0x41, 0x8A, 0x1C, 0x1C}}, + {x86.R2, 8, x86.R13, x86.R2, []byte{0x49, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R2, 4, x86.R13, x86.R2, []byte{0x41, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R2, 2, x86.R13, x86.R2, []byte{0x66, 0x41, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R2, 1, x86.R13, x86.R2, []byte{0x41, 0x8A, 0x54, 0x15, 0x00}}, + {x86.R1, 8, x86.R14, x86.R1, []byte{0x49, 0x8B, 0x0C, 0x0E}}, + {x86.R1, 4, x86.R14, x86.R1, []byte{0x41, 0x8B, 0x0C, 0x0E}}, + {x86.R1, 2, x86.R14, x86.R1, []byte{0x66, 0x41, 0x8B, 0x0C, 0x0E}}, + {x86.R1, 1, x86.R14, x86.R1, []byte{0x41, 0x8A, 0x0C, 0x0E}}, + {x86.R0, 8, x86.R15, x86.R0, []byte{0x49, 0x8B, 0x04, 0x07}}, + {x86.R0, 4, x86.R15, x86.R0, []byte{0x41, 0x8B, 0x04, 0x07}}, + {x86.R0, 2, x86.R15, x86.R0, []byte{0x66, 0x41, 0x8B, 0x04, 0x07}}, + {x86.R0, 1, x86.R15, x86.R0, []byte{0x41, 0x8A, 0x04, 0x07}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Load_test.go b/src/x86/Load_test.go index 3d5240b..006abad 100644 --- a/src/x86/Load_test.go +++ b/src/x86/Load_test.go @@ -17,136 +17,136 @@ func TestLoadRegister(t *testing.T) { Code []byte }{ // No offset - {x86.RAX, x86.R15, 0, 8, []byte{0x49, 0x8B, 0x07}}, - {x86.RAX, x86.R15, 0, 4, []byte{0x41, 0x8B, 0x07}}, - {x86.RAX, x86.R15, 0, 2, []byte{0x66, 0x41, 0x8B, 0x07}}, - {x86.RAX, x86.R15, 0, 1, []byte{0x41, 0x8A, 0x07}}, - {x86.RCX, x86.R14, 0, 8, []byte{0x49, 0x8B, 0x0E}}, - {x86.RCX, x86.R14, 0, 4, []byte{0x41, 0x8B, 0x0E}}, - {x86.RCX, x86.R14, 0, 2, []byte{0x66, 0x41, 0x8B, 0x0E}}, - {x86.RCX, x86.R14, 0, 1, []byte{0x41, 0x8A, 0x0E}}, - {x86.RDX, x86.R13, 0, 8, []byte{0x49, 0x8B, 0x55, 0x00}}, - {x86.RDX, x86.R13, 0, 4, []byte{0x41, 0x8B, 0x55, 0x00}}, - {x86.RDX, x86.R13, 0, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x00}}, - {x86.RDX, x86.R13, 0, 1, []byte{0x41, 0x8A, 0x55, 0x00}}, - {x86.RBX, x86.R12, 0, 8, []byte{0x49, 0x8B, 0x1C, 0x24}}, - {x86.RBX, x86.R12, 0, 4, []byte{0x41, 0x8B, 0x1C, 0x24}}, - {x86.RBX, x86.R12, 0, 2, []byte{0x66, 0x41, 0x8B, 0x1C, 0x24}}, - {x86.RBX, x86.R12, 0, 1, []byte{0x41, 0x8A, 0x1C, 0x24}}, - {x86.RSP, x86.R11, 0, 8, []byte{0x49, 0x8B, 0x23}}, - {x86.RSP, x86.R11, 0, 4, []byte{0x41, 0x8B, 0x23}}, - {x86.RSP, x86.R11, 0, 2, []byte{0x66, 0x41, 0x8B, 0x23}}, - {x86.RSP, x86.R11, 0, 1, []byte{0x41, 0x8A, 0x23}}, - {x86.RBP, x86.R10, 0, 8, []byte{0x49, 0x8B, 0x2A}}, - {x86.RBP, x86.R10, 0, 4, []byte{0x41, 0x8B, 0x2A}}, - {x86.RBP, x86.R10, 0, 2, []byte{0x66, 0x41, 0x8B, 0x2A}}, - {x86.RBP, x86.R10, 0, 1, []byte{0x41, 0x8A, 0x2A}}, - {x86.RSI, x86.R9, 0, 8, []byte{0x49, 0x8B, 0x31}}, - {x86.RSI, x86.R9, 0, 4, []byte{0x41, 0x8B, 0x31}}, - {x86.RSI, x86.R9, 0, 2, []byte{0x66, 0x41, 0x8B, 0x31}}, - {x86.RSI, x86.R9, 0, 1, []byte{0x41, 0x8A, 0x31}}, - {x86.RDI, x86.R8, 0, 8, []byte{0x49, 0x8B, 0x38}}, - {x86.RDI, x86.R8, 0, 4, []byte{0x41, 0x8B, 0x38}}, - {x86.RDI, x86.R8, 0, 2, []byte{0x66, 0x41, 0x8B, 0x38}}, - {x86.RDI, x86.R8, 0, 1, []byte{0x41, 0x8A, 0x38}}, - {x86.R8, x86.RDI, 0, 8, []byte{0x4C, 0x8B, 0x07}}, - {x86.R8, x86.RDI, 0, 4, []byte{0x44, 0x8B, 0x07}}, - {x86.R8, x86.RDI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x07}}, - {x86.R8, x86.RDI, 0, 1, []byte{0x44, 0x8A, 0x07}}, - {x86.R9, x86.RSI, 0, 8, []byte{0x4C, 0x8B, 0x0E}}, - {x86.R9, x86.RSI, 0, 4, []byte{0x44, 0x8B, 0x0E}}, - {x86.R9, x86.RSI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x0E}}, - {x86.R9, x86.RSI, 0, 1, []byte{0x44, 0x8A, 0x0E}}, - {x86.R10, x86.RBP, 0, 8, []byte{0x4C, 0x8B, 0x55, 0x00}}, - {x86.R10, x86.RBP, 0, 4, []byte{0x44, 0x8B, 0x55, 0x00}}, - {x86.R10, x86.RBP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x00}}, - {x86.R10, x86.RBP, 0, 1, []byte{0x44, 0x8A, 0x55, 0x00}}, - {x86.R11, x86.RSP, 0, 8, []byte{0x4C, 0x8B, 0x1C, 0x24}}, - {x86.R11, x86.RSP, 0, 4, []byte{0x44, 0x8B, 0x1C, 0x24}}, - {x86.R11, x86.RSP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x1C, 0x24}}, - {x86.R11, x86.RSP, 0, 1, []byte{0x44, 0x8A, 0x1C, 0x24}}, - {x86.R12, x86.RBX, 0, 8, []byte{0x4C, 0x8B, 0x23}}, - {x86.R12, x86.RBX, 0, 4, []byte{0x44, 0x8B, 0x23}}, - {x86.R12, x86.RBX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x23}}, - {x86.R12, x86.RBX, 0, 1, []byte{0x44, 0x8A, 0x23}}, - {x86.R13, x86.RDX, 0, 8, []byte{0x4C, 0x8B, 0x2A}}, - {x86.R13, x86.RDX, 0, 4, []byte{0x44, 0x8B, 0x2A}}, - {x86.R13, x86.RDX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x2A}}, - {x86.R13, x86.RDX, 0, 1, []byte{0x44, 0x8A, 0x2A}}, - {x86.R14, x86.RCX, 0, 8, []byte{0x4C, 0x8B, 0x31}}, - {x86.R14, x86.RCX, 0, 4, []byte{0x44, 0x8B, 0x31}}, - {x86.R14, x86.RCX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x31}}, - {x86.R14, x86.RCX, 0, 1, []byte{0x44, 0x8A, 0x31}}, - {x86.R15, x86.RAX, 0, 8, []byte{0x4C, 0x8B, 0x38}}, - {x86.R15, x86.RAX, 0, 4, []byte{0x44, 0x8B, 0x38}}, - {x86.R15, x86.RAX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x38}}, - {x86.R15, x86.RAX, 0, 1, []byte{0x44, 0x8A, 0x38}}, + {x86.R0, x86.R15, 0, 8, []byte{0x49, 0x8B, 0x07}}, + {x86.R0, x86.R15, 0, 4, []byte{0x41, 0x8B, 0x07}}, + {x86.R0, x86.R15, 0, 2, []byte{0x66, 0x41, 0x8B, 0x07}}, + {x86.R0, x86.R15, 0, 1, []byte{0x41, 0x8A, 0x07}}, + {x86.R1, x86.R14, 0, 8, []byte{0x49, 0x8B, 0x0E}}, + {x86.R1, x86.R14, 0, 4, []byte{0x41, 0x8B, 0x0E}}, + {x86.R1, x86.R14, 0, 2, []byte{0x66, 0x41, 0x8B, 0x0E}}, + {x86.R1, x86.R14, 0, 1, []byte{0x41, 0x8A, 0x0E}}, + {x86.R2, x86.R13, 0, 8, []byte{0x49, 0x8B, 0x55, 0x00}}, + {x86.R2, x86.R13, 0, 4, []byte{0x41, 0x8B, 0x55, 0x00}}, + {x86.R2, x86.R13, 0, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x00}}, + {x86.R2, x86.R13, 0, 1, []byte{0x41, 0x8A, 0x55, 0x00}}, + {x86.R3, x86.R12, 0, 8, []byte{0x49, 0x8B, 0x1C, 0x24}}, + {x86.R3, x86.R12, 0, 4, []byte{0x41, 0x8B, 0x1C, 0x24}}, + {x86.R3, x86.R12, 0, 2, []byte{0x66, 0x41, 0x8B, 0x1C, 0x24}}, + {x86.R3, x86.R12, 0, 1, []byte{0x41, 0x8A, 0x1C, 0x24}}, + {x86.SP, x86.R11, 0, 8, []byte{0x49, 0x8B, 0x23}}, + {x86.SP, x86.R11, 0, 4, []byte{0x41, 0x8B, 0x23}}, + {x86.SP, x86.R11, 0, 2, []byte{0x66, 0x41, 0x8B, 0x23}}, + {x86.SP, x86.R11, 0, 1, []byte{0x41, 0x8A, 0x23}}, + {x86.R5, x86.R10, 0, 8, []byte{0x49, 0x8B, 0x2A}}, + {x86.R5, x86.R10, 0, 4, []byte{0x41, 0x8B, 0x2A}}, + {x86.R5, x86.R10, 0, 2, []byte{0x66, 0x41, 0x8B, 0x2A}}, + {x86.R5, x86.R10, 0, 1, []byte{0x41, 0x8A, 0x2A}}, + {x86.R6, x86.R9, 0, 8, []byte{0x49, 0x8B, 0x31}}, + {x86.R6, x86.R9, 0, 4, []byte{0x41, 0x8B, 0x31}}, + {x86.R6, x86.R9, 0, 2, []byte{0x66, 0x41, 0x8B, 0x31}}, + {x86.R6, x86.R9, 0, 1, []byte{0x41, 0x8A, 0x31}}, + {x86.R7, x86.R8, 0, 8, []byte{0x49, 0x8B, 0x38}}, + {x86.R7, x86.R8, 0, 4, []byte{0x41, 0x8B, 0x38}}, + {x86.R7, x86.R8, 0, 2, []byte{0x66, 0x41, 0x8B, 0x38}}, + {x86.R7, x86.R8, 0, 1, []byte{0x41, 0x8A, 0x38}}, + {x86.R8, x86.R7, 0, 8, []byte{0x4C, 0x8B, 0x07}}, + {x86.R8, x86.R7, 0, 4, []byte{0x44, 0x8B, 0x07}}, + {x86.R8, x86.R7, 0, 2, []byte{0x66, 0x44, 0x8B, 0x07}}, + {x86.R8, x86.R7, 0, 1, []byte{0x44, 0x8A, 0x07}}, + {x86.R9, x86.R6, 0, 8, []byte{0x4C, 0x8B, 0x0E}}, + {x86.R9, x86.R6, 0, 4, []byte{0x44, 0x8B, 0x0E}}, + {x86.R9, x86.R6, 0, 2, []byte{0x66, 0x44, 0x8B, 0x0E}}, + {x86.R9, x86.R6, 0, 1, []byte{0x44, 0x8A, 0x0E}}, + {x86.R10, x86.R5, 0, 8, []byte{0x4C, 0x8B, 0x55, 0x00}}, + {x86.R10, x86.R5, 0, 4, []byte{0x44, 0x8B, 0x55, 0x00}}, + {x86.R10, x86.R5, 0, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x00}}, + {x86.R10, x86.R5, 0, 1, []byte{0x44, 0x8A, 0x55, 0x00}}, + {x86.R11, x86.SP, 0, 8, []byte{0x4C, 0x8B, 0x1C, 0x24}}, + {x86.R11, x86.SP, 0, 4, []byte{0x44, 0x8B, 0x1C, 0x24}}, + {x86.R11, x86.SP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x1C, 0x24}}, + {x86.R11, x86.SP, 0, 1, []byte{0x44, 0x8A, 0x1C, 0x24}}, + {x86.R12, x86.R3, 0, 8, []byte{0x4C, 0x8B, 0x23}}, + {x86.R12, x86.R3, 0, 4, []byte{0x44, 0x8B, 0x23}}, + {x86.R12, x86.R3, 0, 2, []byte{0x66, 0x44, 0x8B, 0x23}}, + {x86.R12, x86.R3, 0, 1, []byte{0x44, 0x8A, 0x23}}, + {x86.R13, x86.R2, 0, 8, []byte{0x4C, 0x8B, 0x2A}}, + {x86.R13, x86.R2, 0, 4, []byte{0x44, 0x8B, 0x2A}}, + {x86.R13, x86.R2, 0, 2, []byte{0x66, 0x44, 0x8B, 0x2A}}, + {x86.R13, x86.R2, 0, 1, []byte{0x44, 0x8A, 0x2A}}, + {x86.R14, x86.R1, 0, 8, []byte{0x4C, 0x8B, 0x31}}, + {x86.R14, x86.R1, 0, 4, []byte{0x44, 0x8B, 0x31}}, + {x86.R14, x86.R1, 0, 2, []byte{0x66, 0x44, 0x8B, 0x31}}, + {x86.R14, x86.R1, 0, 1, []byte{0x44, 0x8A, 0x31}}, + {x86.R15, x86.R0, 0, 8, []byte{0x4C, 0x8B, 0x38}}, + {x86.R15, x86.R0, 0, 4, []byte{0x44, 0x8B, 0x38}}, + {x86.R15, x86.R0, 0, 2, []byte{0x66, 0x44, 0x8B, 0x38}}, + {x86.R15, x86.R0, 0, 1, []byte{0x44, 0x8A, 0x38}}, // Offset of 1 - {x86.RAX, x86.R15, 1, 8, []byte{0x49, 0x8B, 0x47, 0x01}}, - {x86.RAX, x86.R15, 1, 4, []byte{0x41, 0x8B, 0x47, 0x01}}, - {x86.RAX, x86.R15, 1, 2, []byte{0x66, 0x41, 0x8B, 0x47, 0x01}}, - {x86.RAX, x86.R15, 1, 1, []byte{0x41, 0x8A, 0x47, 0x01}}, - {x86.RCX, x86.R14, 1, 8, []byte{0x49, 0x8B, 0x4E, 0x01}}, - {x86.RCX, x86.R14, 1, 4, []byte{0x41, 0x8B, 0x4E, 0x01}}, - {x86.RCX, x86.R14, 1, 2, []byte{0x66, 0x41, 0x8B, 0x4E, 0x01}}, - {x86.RCX, x86.R14, 1, 1, []byte{0x41, 0x8A, 0x4E, 0x01}}, - {x86.RDX, x86.R13, 1, 8, []byte{0x49, 0x8B, 0x55, 0x01}}, - {x86.RDX, x86.R13, 1, 4, []byte{0x41, 0x8B, 0x55, 0x01}}, - {x86.RDX, x86.R13, 1, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x01}}, - {x86.RDX, x86.R13, 1, 1, []byte{0x41, 0x8A, 0x55, 0x01}}, - {x86.RBX, x86.R12, 1, 8, []byte{0x49, 0x8B, 0x5C, 0x24, 0x01}}, - {x86.RBX, x86.R12, 1, 4, []byte{0x41, 0x8B, 0x5C, 0x24, 0x01}}, - {x86.RBX, x86.R12, 1, 2, []byte{0x66, 0x41, 0x8B, 0x5C, 0x24, 0x01}}, - {x86.RBX, x86.R12, 1, 1, []byte{0x41, 0x8A, 0x5C, 0x24, 0x01}}, - {x86.RSP, x86.R11, 1, 8, []byte{0x49, 0x8B, 0x63, 0x01}}, - {x86.RSP, x86.R11, 1, 4, []byte{0x41, 0x8B, 0x63, 0x01}}, - {x86.RSP, x86.R11, 1, 2, []byte{0x66, 0x41, 0x8B, 0x63, 0x01}}, - {x86.RSP, x86.R11, 1, 1, []byte{0x41, 0x8A, 0x63, 0x01}}, - {x86.RBP, x86.R10, 1, 8, []byte{0x49, 0x8B, 0x6A, 0x01}}, - {x86.RBP, x86.R10, 1, 4, []byte{0x41, 0x8B, 0x6A, 0x01}}, - {x86.RBP, x86.R10, 1, 2, []byte{0x66, 0x41, 0x8B, 0x6A, 0x01}}, - {x86.RBP, x86.R10, 1, 1, []byte{0x41, 0x8A, 0x6A, 0x01}}, - {x86.RSI, x86.R9, 1, 8, []byte{0x49, 0x8B, 0x71, 0x01}}, - {x86.RSI, x86.R9, 1, 4, []byte{0x41, 0x8B, 0x71, 0x01}}, - {x86.RSI, x86.R9, 1, 2, []byte{0x66, 0x41, 0x8B, 0x71, 0x01}}, - {x86.RSI, x86.R9, 1, 1, []byte{0x41, 0x8A, 0x71, 0x01}}, - {x86.RDI, x86.R8, 1, 8, []byte{0x49, 0x8B, 0x78, 0x01}}, - {x86.RDI, x86.R8, 1, 4, []byte{0x41, 0x8B, 0x78, 0x01}}, - {x86.RDI, x86.R8, 1, 2, []byte{0x66, 0x41, 0x8B, 0x78, 0x01}}, - {x86.RDI, x86.R8, 1, 1, []byte{0x41, 0x8A, 0x78, 0x01}}, - {x86.R8, x86.RDI, 1, 8, []byte{0x4C, 0x8B, 0x47, 0x01}}, - {x86.R8, x86.RDI, 1, 4, []byte{0x44, 0x8B, 0x47, 0x01}}, - {x86.R8, x86.RDI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x47, 0x01}}, - {x86.R8, x86.RDI, 1, 1, []byte{0x44, 0x8A, 0x47, 0x01}}, - {x86.R9, x86.RSI, 1, 8, []byte{0x4C, 0x8B, 0x4E, 0x01}}, - {x86.R9, x86.RSI, 1, 4, []byte{0x44, 0x8B, 0x4E, 0x01}}, - {x86.R9, x86.RSI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x4E, 0x01}}, - {x86.R9, x86.RSI, 1, 1, []byte{0x44, 0x8A, 0x4E, 0x01}}, - {x86.R10, x86.RBP, 1, 8, []byte{0x4C, 0x8B, 0x55, 0x01}}, - {x86.R10, x86.RBP, 1, 4, []byte{0x44, 0x8B, 0x55, 0x01}}, - {x86.R10, x86.RBP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x01}}, - {x86.R10, x86.RBP, 1, 1, []byte{0x44, 0x8A, 0x55, 0x01}}, - {x86.R11, x86.RSP, 1, 8, []byte{0x4C, 0x8B, 0x5C, 0x24, 0x01}}, - {x86.R11, x86.RSP, 1, 4, []byte{0x44, 0x8B, 0x5C, 0x24, 0x01}}, - {x86.R11, x86.RSP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x5C, 0x24, 0x01}}, - {x86.R11, x86.RSP, 1, 1, []byte{0x44, 0x8A, 0x5C, 0x24, 0x01}}, - {x86.R12, x86.RBX, 1, 8, []byte{0x4C, 0x8B, 0x63, 0x01}}, - {x86.R12, x86.RBX, 1, 4, []byte{0x44, 0x8B, 0x63, 0x01}}, - {x86.R12, x86.RBX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x63, 0x01}}, - {x86.R12, x86.RBX, 1, 1, []byte{0x44, 0x8A, 0x63, 0x01}}, - {x86.R13, x86.RDX, 1, 8, []byte{0x4C, 0x8B, 0x6A, 0x01}}, - {x86.R13, x86.RDX, 1, 4, []byte{0x44, 0x8B, 0x6A, 0x01}}, - {x86.R13, x86.RDX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x6A, 0x01}}, - {x86.R13, x86.RDX, 1, 1, []byte{0x44, 0x8A, 0x6A, 0x01}}, - {x86.R14, x86.RCX, 1, 8, []byte{0x4C, 0x8B, 0x71, 0x01}}, - {x86.R14, x86.RCX, 1, 4, []byte{0x44, 0x8B, 0x71, 0x01}}, - {x86.R14, x86.RCX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x71, 0x01}}, - {x86.R14, x86.RCX, 1, 1, []byte{0x44, 0x8A, 0x71, 0x01}}, - {x86.R15, x86.RAX, 1, 8, []byte{0x4C, 0x8B, 0x78, 0x01}}, - {x86.R15, x86.RAX, 1, 4, []byte{0x44, 0x8B, 0x78, 0x01}}, - {x86.R15, x86.RAX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x78, 0x01}}, - {x86.R15, x86.RAX, 1, 1, []byte{0x44, 0x8A, 0x78, 0x01}}, + {x86.R0, x86.R15, 1, 8, []byte{0x49, 0x8B, 0x47, 0x01}}, + {x86.R0, x86.R15, 1, 4, []byte{0x41, 0x8B, 0x47, 0x01}}, + {x86.R0, x86.R15, 1, 2, []byte{0x66, 0x41, 0x8B, 0x47, 0x01}}, + {x86.R0, x86.R15, 1, 1, []byte{0x41, 0x8A, 0x47, 0x01}}, + {x86.R1, x86.R14, 1, 8, []byte{0x49, 0x8B, 0x4E, 0x01}}, + {x86.R1, x86.R14, 1, 4, []byte{0x41, 0x8B, 0x4E, 0x01}}, + {x86.R1, x86.R14, 1, 2, []byte{0x66, 0x41, 0x8B, 0x4E, 0x01}}, + {x86.R1, x86.R14, 1, 1, []byte{0x41, 0x8A, 0x4E, 0x01}}, + {x86.R2, x86.R13, 1, 8, []byte{0x49, 0x8B, 0x55, 0x01}}, + {x86.R2, x86.R13, 1, 4, []byte{0x41, 0x8B, 0x55, 0x01}}, + {x86.R2, x86.R13, 1, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x01}}, + {x86.R2, x86.R13, 1, 1, []byte{0x41, 0x8A, 0x55, 0x01}}, + {x86.R3, x86.R12, 1, 8, []byte{0x49, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R3, x86.R12, 1, 4, []byte{0x41, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R3, x86.R12, 1, 2, []byte{0x66, 0x41, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R3, x86.R12, 1, 1, []byte{0x41, 0x8A, 0x5C, 0x24, 0x01}}, + {x86.SP, x86.R11, 1, 8, []byte{0x49, 0x8B, 0x63, 0x01}}, + {x86.SP, x86.R11, 1, 4, []byte{0x41, 0x8B, 0x63, 0x01}}, + {x86.SP, x86.R11, 1, 2, []byte{0x66, 0x41, 0x8B, 0x63, 0x01}}, + {x86.SP, x86.R11, 1, 1, []byte{0x41, 0x8A, 0x63, 0x01}}, + {x86.R5, x86.R10, 1, 8, []byte{0x49, 0x8B, 0x6A, 0x01}}, + {x86.R5, x86.R10, 1, 4, []byte{0x41, 0x8B, 0x6A, 0x01}}, + {x86.R5, x86.R10, 1, 2, []byte{0x66, 0x41, 0x8B, 0x6A, 0x01}}, + {x86.R5, x86.R10, 1, 1, []byte{0x41, 0x8A, 0x6A, 0x01}}, + {x86.R6, x86.R9, 1, 8, []byte{0x49, 0x8B, 0x71, 0x01}}, + {x86.R6, x86.R9, 1, 4, []byte{0x41, 0x8B, 0x71, 0x01}}, + {x86.R6, x86.R9, 1, 2, []byte{0x66, 0x41, 0x8B, 0x71, 0x01}}, + {x86.R6, x86.R9, 1, 1, []byte{0x41, 0x8A, 0x71, 0x01}}, + {x86.R7, x86.R8, 1, 8, []byte{0x49, 0x8B, 0x78, 0x01}}, + {x86.R7, x86.R8, 1, 4, []byte{0x41, 0x8B, 0x78, 0x01}}, + {x86.R7, x86.R8, 1, 2, []byte{0x66, 0x41, 0x8B, 0x78, 0x01}}, + {x86.R7, x86.R8, 1, 1, []byte{0x41, 0x8A, 0x78, 0x01}}, + {x86.R8, x86.R7, 1, 8, []byte{0x4C, 0x8B, 0x47, 0x01}}, + {x86.R8, x86.R7, 1, 4, []byte{0x44, 0x8B, 0x47, 0x01}}, + {x86.R8, x86.R7, 1, 2, []byte{0x66, 0x44, 0x8B, 0x47, 0x01}}, + {x86.R8, x86.R7, 1, 1, []byte{0x44, 0x8A, 0x47, 0x01}}, + {x86.R9, x86.R6, 1, 8, []byte{0x4C, 0x8B, 0x4E, 0x01}}, + {x86.R9, x86.R6, 1, 4, []byte{0x44, 0x8B, 0x4E, 0x01}}, + {x86.R9, x86.R6, 1, 2, []byte{0x66, 0x44, 0x8B, 0x4E, 0x01}}, + {x86.R9, x86.R6, 1, 1, []byte{0x44, 0x8A, 0x4E, 0x01}}, + {x86.R10, x86.R5, 1, 8, []byte{0x4C, 0x8B, 0x55, 0x01}}, + {x86.R10, x86.R5, 1, 4, []byte{0x44, 0x8B, 0x55, 0x01}}, + {x86.R10, x86.R5, 1, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x01}}, + {x86.R10, x86.R5, 1, 1, []byte{0x44, 0x8A, 0x55, 0x01}}, + {x86.R11, x86.SP, 1, 8, []byte{0x4C, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R11, x86.SP, 1, 4, []byte{0x44, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R11, x86.SP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R11, x86.SP, 1, 1, []byte{0x44, 0x8A, 0x5C, 0x24, 0x01}}, + {x86.R12, x86.R3, 1, 8, []byte{0x4C, 0x8B, 0x63, 0x01}}, + {x86.R12, x86.R3, 1, 4, []byte{0x44, 0x8B, 0x63, 0x01}}, + {x86.R12, x86.R3, 1, 2, []byte{0x66, 0x44, 0x8B, 0x63, 0x01}}, + {x86.R12, x86.R3, 1, 1, []byte{0x44, 0x8A, 0x63, 0x01}}, + {x86.R13, x86.R2, 1, 8, []byte{0x4C, 0x8B, 0x6A, 0x01}}, + {x86.R13, x86.R2, 1, 4, []byte{0x44, 0x8B, 0x6A, 0x01}}, + {x86.R13, x86.R2, 1, 2, []byte{0x66, 0x44, 0x8B, 0x6A, 0x01}}, + {x86.R13, x86.R2, 1, 1, []byte{0x44, 0x8A, 0x6A, 0x01}}, + {x86.R14, x86.R1, 1, 8, []byte{0x4C, 0x8B, 0x71, 0x01}}, + {x86.R14, x86.R1, 1, 4, []byte{0x44, 0x8B, 0x71, 0x01}}, + {x86.R14, x86.R1, 1, 2, []byte{0x66, 0x44, 0x8B, 0x71, 0x01}}, + {x86.R14, x86.R1, 1, 1, []byte{0x44, 0x8A, 0x71, 0x01}}, + {x86.R15, x86.R0, 1, 8, []byte{0x4C, 0x8B, 0x78, 0x01}}, + {x86.R15, x86.R0, 1, 4, []byte{0x44, 0x8B, 0x78, 0x01}}, + {x86.R15, x86.R0, 1, 2, []byte{0x66, 0x44, 0x8B, 0x78, 0x01}}, + {x86.R15, x86.R0, 1, 1, []byte{0x44, 0x8A, 0x78, 0x01}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Move_test.go b/src/x86/Move_test.go index 9ac8318..803a418 100644 --- a/src/x86/Move_test.go +++ b/src/x86/Move_test.go @@ -15,14 +15,14 @@ func TestMoveRegisterNumber(t *testing.T) { Code []byte }{ // 32 bits - {x86.RAX, 0x7FFFFFFF, []byte{0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFF, []byte{0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFF, []byte{0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFF, []byte{0xBB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFF, []byte{0xBC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFF, []byte{0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFF, []byte{0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFF, []byte{0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFF, []byte{0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFF, []byte{0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFF, []byte{0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFF, []byte{0xBB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFF, []byte{0xBC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFF, []byte{0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFF, []byte{0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFF, []byte{0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFF, []byte{0x41, 0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFF, []byte{0x41, 0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFF, []byte{0x41, 0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -33,14 +33,14 @@ func TestMoveRegisterNumber(t *testing.T) { {x86.R15, 0x7FFFFFFF, []byte{0x41, 0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, // 64 bits - {x86.RAX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -51,14 +51,14 @@ func TestMoveRegisterNumber(t *testing.T) { {x86.R15, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, // Negative numbers - {x86.RAX, -1, []byte{0x48, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x86.RCX, -1, []byte{0x48, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x86.RDX, -1, []byte{0x48, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x86.RBX, -1, []byte{0x48, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x86.RSP, -1, []byte{0x48, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x86.RBP, -1, []byte{0x48, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x86.RSI, -1, []byte{0x48, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x86.RDI, -1, []byte{0x48, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R0, -1, []byte{0x48, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R1, -1, []byte{0x48, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R2, -1, []byte{0x48, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R3, -1, []byte{0x48, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.SP, -1, []byte{0x48, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R5, -1, []byte{0x48, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R6, -1, []byte{0x48, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R7, -1, []byte{0x48, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}}, {x86.R8, -1, []byte{0x49, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, {x86.R9, -1, []byte{0x49, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, {x86.R10, -1, []byte{0x49, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, @@ -82,22 +82,22 @@ func TestMoveRegisterRegister(t *testing.T) { Right cpu.Register Code []byte }{ - {x86.RAX, x86.R15, []byte{0x4C, 0x89, 0xF8}}, - {x86.RCX, x86.R14, []byte{0x4C, 0x89, 0xF1}}, - {x86.RDX, x86.R13, []byte{0x4C, 0x89, 0xEA}}, - {x86.RBX, x86.R12, []byte{0x4C, 0x89, 0xE3}}, - {x86.RSP, x86.R11, []byte{0x4C, 0x89, 0xDC}}, - {x86.RBP, x86.R10, []byte{0x4C, 0x89, 0xD5}}, - {x86.RSI, x86.R9, []byte{0x4C, 0x89, 0xCE}}, - {x86.RDI, x86.R8, []byte{0x4C, 0x89, 0xC7}}, - {x86.R8, x86.RDI, []byte{0x49, 0x89, 0xF8}}, - {x86.R9, x86.RSI, []byte{0x49, 0x89, 0xF1}}, - {x86.R10, x86.RBP, []byte{0x49, 0x89, 0xEA}}, - {x86.R11, x86.RSP, []byte{0x49, 0x89, 0xE3}}, - {x86.R12, x86.RBX, []byte{0x49, 0x89, 0xDC}}, - {x86.R13, x86.RDX, []byte{0x49, 0x89, 0xD5}}, - {x86.R14, x86.RCX, []byte{0x49, 0x89, 0xCE}}, - {x86.R15, x86.RAX, []byte{0x49, 0x89, 0xC7}}, + {x86.R0, x86.R15, []byte{0x4C, 0x89, 0xF8}}, + {x86.R1, x86.R14, []byte{0x4C, 0x89, 0xF1}}, + {x86.R2, x86.R13, []byte{0x4C, 0x89, 0xEA}}, + {x86.R3, x86.R12, []byte{0x4C, 0x89, 0xE3}}, + {x86.SP, x86.R11, []byte{0x4C, 0x89, 0xDC}}, + {x86.R5, x86.R10, []byte{0x4C, 0x89, 0xD5}}, + {x86.R6, x86.R9, []byte{0x4C, 0x89, 0xCE}}, + {x86.R7, x86.R8, []byte{0x4C, 0x89, 0xC7}}, + {x86.R8, x86.R7, []byte{0x49, 0x89, 0xF8}}, + {x86.R9, x86.R6, []byte{0x49, 0x89, 0xF1}}, + {x86.R10, x86.R5, []byte{0x49, 0x89, 0xEA}}, + {x86.R11, x86.SP, []byte{0x49, 0x89, 0xE3}}, + {x86.R12, x86.R3, []byte{0x49, 0x89, 0xDC}}, + {x86.R13, x86.R2, []byte{0x49, 0x89, 0xD5}}, + {x86.R14, x86.R1, []byte{0x49, 0x89, 0xCE}}, + {x86.R15, x86.R0, []byte{0x49, 0x89, 0xC7}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Mul_test.go b/src/x86/Mul_test.go index cfde232..5584ede 100644 --- a/src/x86/Mul_test.go +++ b/src/x86/Mul_test.go @@ -14,14 +14,14 @@ func TestMulRegisterNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0x6B, 0xC0, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0x6B, 0xC9, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0x6B, 0xD2, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0x6B, 0xDB, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0x6B, 0xE4, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0x6B, 0xED, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0x6B, 0xF6, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0x6B, 0xFF, 0x01}}, + {x86.R0, 1, []byte{0x48, 0x6B, 0xC0, 0x01}}, + {x86.R1, 1, []byte{0x48, 0x6B, 0xC9, 0x01}}, + {x86.R2, 1, []byte{0x48, 0x6B, 0xD2, 0x01}}, + {x86.R3, 1, []byte{0x48, 0x6B, 0xDB, 0x01}}, + {x86.SP, 1, []byte{0x48, 0x6B, 0xE4, 0x01}}, + {x86.R5, 1, []byte{0x48, 0x6B, 0xED, 0x01}}, + {x86.R6, 1, []byte{0x48, 0x6B, 0xF6, 0x01}}, + {x86.R7, 1, []byte{0x48, 0x6B, 0xFF, 0x01}}, {x86.R8, 1, []byte{0x4D, 0x6B, 0xC0, 0x01}}, {x86.R9, 1, []byte{0x4D, 0x6B, 0xC9, 0x01}}, {x86.R10, 1, []byte{0x4D, 0x6B, 0xD2, 0x01}}, @@ -31,14 +31,14 @@ func TestMulRegisterNumber(t *testing.T) { {x86.R14, 1, []byte{0x4D, 0x6B, 0xF6, 0x01}}, {x86.R15, 1, []byte{0x4D, 0x6B, 0xFF, 0x01}}, - {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFF, []byte{0x48, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFF, []byte{0x48, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFF, []byte{0x48, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFF, []byte{0x48, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFF, []byte{0x48, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -62,22 +62,22 @@ func TestMulRegisterRegister(t *testing.T) { Right cpu.Register Code []byte }{ - {x86.RAX, x86.R15, []byte{0x49, 0x0F, 0xAF, 0xC7}}, - {x86.RCX, x86.R14, []byte{0x49, 0x0F, 0xAF, 0xCE}}, - {x86.RDX, x86.R13, []byte{0x49, 0x0F, 0xAF, 0xD5}}, - {x86.RBX, x86.R12, []byte{0x49, 0x0F, 0xAF, 0xDC}}, - {x86.RSP, x86.R11, []byte{0x49, 0x0F, 0xAF, 0xE3}}, - {x86.RBP, x86.R10, []byte{0x49, 0x0F, 0xAF, 0xEA}}, - {x86.RSI, x86.R9, []byte{0x49, 0x0F, 0xAF, 0xF1}}, - {x86.RDI, x86.R8, []byte{0x49, 0x0F, 0xAF, 0xF8}}, - {x86.R8, x86.RDI, []byte{0x4C, 0x0F, 0xAF, 0xC7}}, - {x86.R9, x86.RSI, []byte{0x4C, 0x0F, 0xAF, 0xCE}}, - {x86.R10, x86.RBP, []byte{0x4C, 0x0F, 0xAF, 0xD5}}, - {x86.R11, x86.RSP, []byte{0x4C, 0x0F, 0xAF, 0xDC}}, - {x86.R12, x86.RBX, []byte{0x4C, 0x0F, 0xAF, 0xE3}}, - {x86.R13, x86.RDX, []byte{0x4C, 0x0F, 0xAF, 0xEA}}, - {x86.R14, x86.RCX, []byte{0x4C, 0x0F, 0xAF, 0xF1}}, - {x86.R15, x86.RAX, []byte{0x4C, 0x0F, 0xAF, 0xF8}}, + {x86.R0, x86.R15, []byte{0x49, 0x0F, 0xAF, 0xC7}}, + {x86.R1, x86.R14, []byte{0x49, 0x0F, 0xAF, 0xCE}}, + {x86.R2, x86.R13, []byte{0x49, 0x0F, 0xAF, 0xD5}}, + {x86.R3, x86.R12, []byte{0x49, 0x0F, 0xAF, 0xDC}}, + {x86.SP, x86.R11, []byte{0x49, 0x0F, 0xAF, 0xE3}}, + {x86.R5, x86.R10, []byte{0x49, 0x0F, 0xAF, 0xEA}}, + {x86.R6, x86.R9, []byte{0x49, 0x0F, 0xAF, 0xF1}}, + {x86.R7, x86.R8, []byte{0x49, 0x0F, 0xAF, 0xF8}}, + {x86.R8, x86.R7, []byte{0x4C, 0x0F, 0xAF, 0xC7}}, + {x86.R9, x86.R6, []byte{0x4C, 0x0F, 0xAF, 0xCE}}, + {x86.R10, x86.R5, []byte{0x4C, 0x0F, 0xAF, 0xD5}}, + {x86.R11, x86.SP, []byte{0x4C, 0x0F, 0xAF, 0xDC}}, + {x86.R12, x86.R3, []byte{0x4C, 0x0F, 0xAF, 0xE3}}, + {x86.R13, x86.R2, []byte{0x4C, 0x0F, 0xAF, 0xEA}}, + {x86.R14, x86.R1, []byte{0x4C, 0x0F, 0xAF, 0xF1}}, + {x86.R15, x86.R0, []byte{0x4C, 0x0F, 0xAF, 0xF8}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Negate_test.go b/src/x86/Negate_test.go index df02835..b982850 100644 --- a/src/x86/Negate_test.go +++ b/src/x86/Negate_test.go @@ -13,14 +13,14 @@ func TestNegateRegister(t *testing.T) { Register cpu.Register Code []byte }{ - {x86.RAX, []byte{0x48, 0xF7, 0xD8}}, - {x86.RCX, []byte{0x48, 0xF7, 0xD9}}, - {x86.RDX, []byte{0x48, 0xF7, 0xDA}}, - {x86.RBX, []byte{0x48, 0xF7, 0xDB}}, - {x86.RSP, []byte{0x48, 0xF7, 0xDC}}, - {x86.RBP, []byte{0x48, 0xF7, 0xDD}}, - {x86.RSI, []byte{0x48, 0xF7, 0xDE}}, - {x86.RDI, []byte{0x48, 0xF7, 0xDF}}, + {x86.R0, []byte{0x48, 0xF7, 0xD8}}, + {x86.R1, []byte{0x48, 0xF7, 0xD9}}, + {x86.R2, []byte{0x48, 0xF7, 0xDA}}, + {x86.R3, []byte{0x48, 0xF7, 0xDB}}, + {x86.SP, []byte{0x48, 0xF7, 0xDC}}, + {x86.R5, []byte{0x48, 0xF7, 0xDD}}, + {x86.R6, []byte{0x48, 0xF7, 0xDE}}, + {x86.R7, []byte{0x48, 0xF7, 0xDF}}, {x86.R8, []byte{0x49, 0xF7, 0xD8}}, {x86.R9, []byte{0x49, 0xF7, 0xD9}}, {x86.R10, []byte{0x49, 0xF7, 0xDA}}, diff --git a/src/x86/Or_test.go b/src/x86/Or_test.go index bac71e4..0c4431e 100644 --- a/src/x86/Or_test.go +++ b/src/x86/Or_test.go @@ -14,14 +14,14 @@ func TestOrRegisterNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0x83, 0xC8, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0x83, 0xC9, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0x83, 0xCA, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0x83, 0xCB, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0x83, 0xCC, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0x83, 0xCD, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0x83, 0xCE, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0x83, 0xCF, 0x01}}, + {x86.R0, 1, []byte{0x48, 0x83, 0xC8, 0x01}}, + {x86.R1, 1, []byte{0x48, 0x83, 0xC9, 0x01}}, + {x86.R2, 1, []byte{0x48, 0x83, 0xCA, 0x01}}, + {x86.R3, 1, []byte{0x48, 0x83, 0xCB, 0x01}}, + {x86.SP, 1, []byte{0x48, 0x83, 0xCC, 0x01}}, + {x86.R5, 1, []byte{0x48, 0x83, 0xCD, 0x01}}, + {x86.R6, 1, []byte{0x48, 0x83, 0xCE, 0x01}}, + {x86.R7, 1, []byte{0x48, 0x83, 0xCF, 0x01}}, {x86.R8, 1, []byte{0x49, 0x83, 0xC8, 0x01}}, {x86.R9, 1, []byte{0x49, 0x83, 0xC9, 0x01}}, {x86.R10, 1, []byte{0x49, 0x83, 0xCA, 0x01}}, @@ -31,14 +31,14 @@ func TestOrRegisterNumber(t *testing.T) { {x86.R14, 1, []byte{0x49, 0x83, 0xCE, 0x01}}, {x86.R15, 1, []byte{0x49, 0x83, 0xCF, 0x01}}, - {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -62,22 +62,22 @@ func TestOrRegisterRegister(t *testing.T) { Right cpu.Register Code []byte }{ - {x86.RAX, x86.R15, []byte{0x4C, 0x09, 0xF8}}, - {x86.RCX, x86.R14, []byte{0x4C, 0x09, 0xF1}}, - {x86.RDX, x86.R13, []byte{0x4C, 0x09, 0xEA}}, - {x86.RBX, x86.R12, []byte{0x4C, 0x09, 0xE3}}, - {x86.RSP, x86.R11, []byte{0x4C, 0x09, 0xDC}}, - {x86.RBP, x86.R10, []byte{0x4C, 0x09, 0xD5}}, - {x86.RSI, x86.R9, []byte{0x4C, 0x09, 0xCE}}, - {x86.RDI, x86.R8, []byte{0x4C, 0x09, 0xC7}}, - {x86.R8, x86.RDI, []byte{0x49, 0x09, 0xF8}}, - {x86.R9, x86.RSI, []byte{0x49, 0x09, 0xF1}}, - {x86.R10, x86.RBP, []byte{0x49, 0x09, 0xEA}}, - {x86.R11, x86.RSP, []byte{0x49, 0x09, 0xE3}}, - {x86.R12, x86.RBX, []byte{0x49, 0x09, 0xDC}}, - {x86.R13, x86.RDX, []byte{0x49, 0x09, 0xD5}}, - {x86.R14, x86.RCX, []byte{0x49, 0x09, 0xCE}}, - {x86.R15, x86.RAX, []byte{0x49, 0x09, 0xC7}}, + {x86.R0, x86.R15, []byte{0x4C, 0x09, 0xF8}}, + {x86.R1, x86.R14, []byte{0x4C, 0x09, 0xF1}}, + {x86.R2, x86.R13, []byte{0x4C, 0x09, 0xEA}}, + {x86.R3, x86.R12, []byte{0x4C, 0x09, 0xE3}}, + {x86.SP, x86.R11, []byte{0x4C, 0x09, 0xDC}}, + {x86.R5, x86.R10, []byte{0x4C, 0x09, 0xD5}}, + {x86.R6, x86.R9, []byte{0x4C, 0x09, 0xCE}}, + {x86.R7, x86.R8, []byte{0x4C, 0x09, 0xC7}}, + {x86.R8, x86.R7, []byte{0x49, 0x09, 0xF8}}, + {x86.R9, x86.R6, []byte{0x49, 0x09, 0xF1}}, + {x86.R10, x86.R5, []byte{0x49, 0x09, 0xEA}}, + {x86.R11, x86.SP, []byte{0x49, 0x09, 0xE3}}, + {x86.R12, x86.R3, []byte{0x49, 0x09, 0xDC}}, + {x86.R13, x86.R2, []byte{0x49, 0x09, 0xD5}}, + {x86.R14, x86.R1, []byte{0x49, 0x09, 0xCE}}, + {x86.R15, x86.R0, []byte{0x49, 0x09, 0xC7}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Pop_test.go b/src/x86/Pop_test.go index 65cb1aa..c01a1ee 100644 --- a/src/x86/Pop_test.go +++ b/src/x86/Pop_test.go @@ -13,14 +13,14 @@ func TestPopRegister(t *testing.T) { Register cpu.Register Code []byte }{ - {x86.RAX, []byte{0x58}}, - {x86.RCX, []byte{0x59}}, - {x86.RDX, []byte{0x5A}}, - {x86.RBX, []byte{0x5B}}, - {x86.RSP, []byte{0x5C}}, - {x86.RBP, []byte{0x5D}}, - {x86.RSI, []byte{0x5E}}, - {x86.RDI, []byte{0x5F}}, + {x86.R0, []byte{0x58}}, + {x86.R1, []byte{0x59}}, + {x86.R2, []byte{0x5A}}, + {x86.R3, []byte{0x5B}}, + {x86.SP, []byte{0x5C}}, + {x86.R5, []byte{0x5D}}, + {x86.R6, []byte{0x5E}}, + {x86.R7, []byte{0x5F}}, {x86.R8, []byte{0x41, 0x58}}, {x86.R9, []byte{0x41, 0x59}}, {x86.R10, []byte{0x41, 0x5A}}, diff --git a/src/x86/Push_test.go b/src/x86/Push_test.go index 138478d..c7f79dc 100644 --- a/src/x86/Push_test.go +++ b/src/x86/Push_test.go @@ -35,14 +35,14 @@ func TestPushRegister(t *testing.T) { Register cpu.Register Code []byte }{ - {x86.RAX, []byte{0x50}}, - {x86.RCX, []byte{0x51}}, - {x86.RDX, []byte{0x52}}, - {x86.RBX, []byte{0x53}}, - {x86.RSP, []byte{0x54}}, - {x86.RBP, []byte{0x55}}, - {x86.RSI, []byte{0x56}}, - {x86.RDI, []byte{0x57}}, + {x86.R0, []byte{0x50}}, + {x86.R1, []byte{0x51}}, + {x86.R2, []byte{0x52}}, + {x86.R3, []byte{0x53}}, + {x86.SP, []byte{0x54}}, + {x86.R5, []byte{0x55}}, + {x86.R6, []byte{0x56}}, + {x86.R7, []byte{0x57}}, {x86.R8, []byte{0x41, 0x50}}, {x86.R9, []byte{0x41, 0x51}}, {x86.R10, []byte{0x41, 0x52}}, diff --git a/src/x86/Registers.go b/src/x86/Registers.go index ab31583..3e2958d 100644 --- a/src/x86/Registers.go +++ b/src/x86/Registers.go @@ -3,14 +3,14 @@ package x86 import "git.urbach.dev/cli/q/src/cpu" const ( - RAX cpu.Register = iota - RCX - RDX - RBX - RSP - RBP - RSI - RDI + R0 cpu.Register = iota // RAX + R1 // RCX + R2 // RDX + R3 // RBX + SP // Stack pointer + R5 // RBP + R6 // RSI + R7 // RDI R8 R9 R10 @@ -19,25 +19,21 @@ const ( R13 R14 R15 - TMP = RCX + TMP = R1 ) var ( - SyscallInputRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} - SyscallOutputRegisters = []cpu.Register{RAX, RCX, R11} - GeneralRegisters = []cpu.Register{RBX, R12, R13, R14, R15, R11} - InputRegisters = SyscallInputRegisters - OutputRegisters = SyscallInputRegisters - WindowsInputRegisters = []cpu.Register{RCX, RDX, R8, R9} - WindowsOutputRegisters = []cpu.Register{RAX} - WindowsVolatileRegisters = []cpu.Register{RCX, RDX, R8, R9, R10, R11} + SyscallInputRegisters = []cpu.Register{R0, R7, R6, R2, R10, R8, R9} + WindowsInputRegisters = []cpu.Register{R1, R2, R8, R9} + WindowsOutputRegisters = []cpu.Register{R0} + WindowsVolatileRegisters = []cpu.Register{R1, R2, R8, R9, R10, R11} CPU = cpu.CPU{ - General: GeneralRegisters, - Input: InputRegisters, - Output: OutputRegisters, + General: []cpu.Register{R3, R12, R13, R14, R15, R11}, + Input: SyscallInputRegisters, + Output: SyscallInputRegisters, SyscallInput: SyscallInputRegisters, - SyscallOutput: SyscallOutputRegisters, + SyscallOutput: []cpu.Register{R0, R1, R11}, NumRegisters: 16, } ) diff --git a/src/x86/Registers_test.go b/src/x86/Registers_test.go index d1aadad..91eb96d 100644 --- a/src/x86/Registers_test.go +++ b/src/x86/Registers_test.go @@ -8,5 +8,5 @@ import ( ) func TestRegisters(t *testing.T) { - assert.NotContains(t, x86.GeneralRegisters, x86.RSP) + assert.NotContains(t, x86.CPU.General, x86.SP) } diff --git a/src/x86/Shift_test.go b/src/x86/Shift_test.go index 19fa94a..a65fb7b 100644 --- a/src/x86/Shift_test.go +++ b/src/x86/Shift_test.go @@ -14,14 +14,14 @@ func TestShiftLeftNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0xC1, 0xE0, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0xC1, 0xE1, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0xC1, 0xE2, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0xC1, 0xE3, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0xC1, 0xE4, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0xC1, 0xE5, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0xC1, 0xE6, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0xC1, 0xE7, 0x01}}, + {x86.R0, 1, []byte{0x48, 0xC1, 0xE0, 0x01}}, + {x86.R1, 1, []byte{0x48, 0xC1, 0xE1, 0x01}}, + {x86.R2, 1, []byte{0x48, 0xC1, 0xE2, 0x01}}, + {x86.R3, 1, []byte{0x48, 0xC1, 0xE3, 0x01}}, + {x86.SP, 1, []byte{0x48, 0xC1, 0xE4, 0x01}}, + {x86.R5, 1, []byte{0x48, 0xC1, 0xE5, 0x01}}, + {x86.R6, 1, []byte{0x48, 0xC1, 0xE6, 0x01}}, + {x86.R7, 1, []byte{0x48, 0xC1, 0xE7, 0x01}}, {x86.R8, 1, []byte{0x49, 0xC1, 0xE0, 0x01}}, {x86.R9, 1, []byte{0x49, 0xC1, 0xE1, 0x01}}, {x86.R10, 1, []byte{0x49, 0xC1, 0xE2, 0x01}}, @@ -45,14 +45,14 @@ func TestShiftRightSignedNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0xC1, 0xF8, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0xC1, 0xF9, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0xC1, 0xFA, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0xC1, 0xFB, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0xC1, 0xFC, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0xC1, 0xFD, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0xC1, 0xFE, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0xC1, 0xFF, 0x01}}, + {x86.R0, 1, []byte{0x48, 0xC1, 0xF8, 0x01}}, + {x86.R1, 1, []byte{0x48, 0xC1, 0xF9, 0x01}}, + {x86.R2, 1, []byte{0x48, 0xC1, 0xFA, 0x01}}, + {x86.R3, 1, []byte{0x48, 0xC1, 0xFB, 0x01}}, + {x86.SP, 1, []byte{0x48, 0xC1, 0xFC, 0x01}}, + {x86.R5, 1, []byte{0x48, 0xC1, 0xFD, 0x01}}, + {x86.R6, 1, []byte{0x48, 0xC1, 0xFE, 0x01}}, + {x86.R7, 1, []byte{0x48, 0xC1, 0xFF, 0x01}}, {x86.R8, 1, []byte{0x49, 0xC1, 0xF8, 0x01}}, {x86.R9, 1, []byte{0x49, 0xC1, 0xF9, 0x01}}, {x86.R10, 1, []byte{0x49, 0xC1, 0xFA, 0x01}}, diff --git a/src/x86/StoreDynamic_test.go b/src/x86/StoreDynamic_test.go index 2e55105..cc0756e 100644 --- a/src/x86/StoreDynamic_test.go +++ b/src/x86/StoreDynamic_test.go @@ -16,70 +16,70 @@ func TestStoreDynamicNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, x86.R15, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RAX, x86.R15, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RAX, x86.R15, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, - {x86.RAX, x86.R15, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x38, 0x7F}}, - {x86.RCX, x86.R14, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RCX, x86.R14, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RCX, x86.R14, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, - {x86.RCX, x86.R14, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x31, 0x7F}}, - {x86.RDX, x86.R13, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDX, x86.R13, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDX, x86.R13, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, - {x86.RDX, x86.R13, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x2A, 0x7F}}, - {x86.RBX, x86.R12, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBX, x86.R12, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBX, x86.R12, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00}}, - {x86.RBX, x86.R12, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x23, 0x7F}}, - {x86.RSP, x86.R11, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSP, x86.R11, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSP, x86.R11, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, - {x86.RSP, x86.R11, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, - {x86.RBP, x86.R10, 8, 0x7F, []byte{0x4A, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBP, x86.R10, 4, 0x7F, []byte{0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBP, x86.R10, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, - {x86.RBP, x86.R10, 1, 0x7F, []byte{0x42, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, - {x86.RSI, x86.R9, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSI, x86.R9, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSI, x86.R9, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, - {x86.RSI, x86.R9, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x0E, 0x7F}}, - {x86.RDI, x86.R8, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDI, x86.R8, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDI, x86.R8, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, - {x86.RDI, x86.R8, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x07, 0x7F}}, - {x86.R8, x86.RDI, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R8, x86.RDI, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R8, x86.RDI, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, - {x86.R8, x86.RDI, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x38, 0x7F}}, - {x86.R9, x86.RSI, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R9, x86.RSI, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R9, x86.RSI, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, - {x86.R9, x86.RSI, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x31, 0x7F}}, - {x86.R10, x86.RBP, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R10, x86.RBP, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R10, x86.RBP, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, - {x86.R10, x86.RBP, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x2A, 0x7F}}, - {x86.R11, x86.RSP, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R11, x86.RSP, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R11, x86.RSP, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, - {x86.R11, x86.RSP, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, - {x86.R12, x86.RBX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R12, x86.RBX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R12, x86.RBX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, - {x86.R12, x86.RBX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x1C, 0x7F}}, - {x86.R13, x86.RDX, 8, 0x7F, []byte{0x49, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R13, x86.RDX, 4, 0x7F, []byte{0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R13, x86.RDX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, - {x86.R13, x86.RDX, 1, 0x7F, []byte{0x41, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, - {x86.R14, x86.RCX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R14, x86.RCX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R14, x86.RCX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, - {x86.R14, x86.RCX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x0E, 0x7F}}, - {x86.R15, x86.RAX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R15, x86.RAX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R15, x86.RAX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, - {x86.R15, x86.RAX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x07, 0x7F}}, + {x86.R0, x86.R15, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R0, x86.R15, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R0, x86.R15, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, + {x86.R0, x86.R15, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x38, 0x7F}}, + {x86.R1, x86.R14, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R1, x86.R14, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R1, x86.R14, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, + {x86.R1, x86.R14, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x31, 0x7F}}, + {x86.R2, x86.R13, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R2, x86.R13, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R2, x86.R13, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, + {x86.R2, x86.R13, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x2A, 0x7F}}, + {x86.R3, x86.R12, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R3, x86.R12, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R3, x86.R12, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00}}, + {x86.R3, x86.R12, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x23, 0x7F}}, + {x86.SP, x86.R11, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.SP, x86.R11, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.SP, x86.R11, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x86.SP, x86.R11, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, + {x86.R5, x86.R10, 8, 0x7F, []byte{0x4A, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R5, x86.R10, 4, 0x7F, []byte{0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R5, x86.R10, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, + {x86.R5, x86.R10, 1, 0x7F, []byte{0x42, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, + {x86.R6, x86.R9, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R6, x86.R9, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R6, x86.R9, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, + {x86.R6, x86.R9, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x0E, 0x7F}}, + {x86.R7, x86.R8, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R7, x86.R8, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R7, x86.R8, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, + {x86.R7, x86.R8, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x07, 0x7F}}, + {x86.R8, x86.R7, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R8, x86.R7, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R8, x86.R7, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, + {x86.R8, x86.R7, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x38, 0x7F}}, + {x86.R9, x86.R6, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R9, x86.R6, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R9, x86.R6, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, + {x86.R9, x86.R6, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x31, 0x7F}}, + {x86.R10, x86.R5, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R10, x86.R5, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R10, x86.R5, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, + {x86.R10, x86.R5, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x2A, 0x7F}}, + {x86.R11, x86.SP, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R11, x86.SP, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R11, x86.SP, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x86.R11, x86.SP, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, + {x86.R12, x86.R3, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R12, x86.R3, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R12, x86.R3, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x86.R12, x86.R3, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x1C, 0x7F}}, + {x86.R13, x86.R2, 8, 0x7F, []byte{0x49, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R13, x86.R2, 4, 0x7F, []byte{0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R13, x86.R2, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, + {x86.R13, x86.R2, 1, 0x7F, []byte{0x41, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, + {x86.R14, x86.R1, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R14, x86.R1, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R14, x86.R1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, + {x86.R14, x86.R1, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x0E, 0x7F}}, + {x86.R15, x86.R0, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R15, x86.R0, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R15, x86.R0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, + {x86.R15, x86.R0, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x07, 0x7F}}, } for _, pattern := range usagePatterns { @@ -97,70 +97,70 @@ func TestStoreDynamicRegister(t *testing.T) { RegisterFrom cpu.Register Code []byte }{ - {x86.RAX, x86.R15, 8, x86.R15, []byte{0x4E, 0x89, 0x3C, 0x38}}, - {x86.RAX, x86.R15, 4, x86.R15, []byte{0x46, 0x89, 0x3C, 0x38}}, - {x86.RAX, x86.R15, 2, x86.R15, []byte{0x66, 0x46, 0x89, 0x3C, 0x38}}, - {x86.RAX, x86.R15, 1, x86.R15, []byte{0x46, 0x88, 0x3C, 0x38}}, - {x86.RCX, x86.R14, 8, x86.R14, []byte{0x4E, 0x89, 0x34, 0x31}}, - {x86.RCX, x86.R14, 4, x86.R14, []byte{0x46, 0x89, 0x34, 0x31}}, - {x86.RCX, x86.R14, 2, x86.R14, []byte{0x66, 0x46, 0x89, 0x34, 0x31}}, - {x86.RCX, x86.R14, 1, x86.R14, []byte{0x46, 0x88, 0x34, 0x31}}, - {x86.RDX, x86.R13, 8, x86.R13, []byte{0x4E, 0x89, 0x2C, 0x2A}}, - {x86.RDX, x86.R13, 4, x86.R13, []byte{0x46, 0x89, 0x2C, 0x2A}}, - {x86.RDX, x86.R13, 2, x86.R13, []byte{0x66, 0x46, 0x89, 0x2C, 0x2A}}, - {x86.RDX, x86.R13, 1, x86.R13, []byte{0x46, 0x88, 0x2C, 0x2A}}, - {x86.RBX, x86.R12, 8, x86.R12, []byte{0x4E, 0x89, 0x24, 0x23}}, - {x86.RBX, x86.R12, 4, x86.R12, []byte{0x46, 0x89, 0x24, 0x23}}, - {x86.RBX, x86.R12, 2, x86.R12, []byte{0x66, 0x46, 0x89, 0x24, 0x23}}, - {x86.RBX, x86.R12, 1, x86.R12, []byte{0x46, 0x88, 0x24, 0x23}}, - {x86.RSP, x86.R11, 8, x86.R11, []byte{0x4E, 0x89, 0x1C, 0x1C}}, - {x86.RSP, x86.R11, 4, x86.R11, []byte{0x46, 0x89, 0x1C, 0x1C}}, - {x86.RSP, x86.R11, 2, x86.R11, []byte{0x66, 0x46, 0x89, 0x1C, 0x1C}}, - {x86.RSP, x86.R11, 1, x86.R11, []byte{0x46, 0x88, 0x1C, 0x1C}}, - {x86.RBP, x86.R10, 8, x86.R10, []byte{0x4E, 0x89, 0x54, 0x15, 0x00}}, - {x86.RBP, x86.R10, 4, x86.R10, []byte{0x46, 0x89, 0x54, 0x15, 0x00}}, - {x86.RBP, x86.R10, 2, x86.R10, []byte{0x66, 0x46, 0x89, 0x54, 0x15, 0x00}}, - {x86.RBP, x86.R10, 1, x86.R10, []byte{0x46, 0x88, 0x54, 0x15, 0x00}}, - {x86.RSI, x86.R9, 8, x86.R9, []byte{0x4E, 0x89, 0x0C, 0x0E}}, - {x86.RSI, x86.R9, 4, x86.R9, []byte{0x46, 0x89, 0x0C, 0x0E}}, - {x86.RSI, x86.R9, 2, x86.R9, []byte{0x66, 0x46, 0x89, 0x0C, 0x0E}}, - {x86.RSI, x86.R9, 1, x86.R9, []byte{0x46, 0x88, 0x0C, 0x0E}}, - {x86.RDI, x86.R8, 8, x86.R8, []byte{0x4E, 0x89, 0x04, 0x07}}, - {x86.RDI, x86.R8, 4, x86.R8, []byte{0x46, 0x89, 0x04, 0x07}}, - {x86.RDI, x86.R8, 2, x86.R8, []byte{0x66, 0x46, 0x89, 0x04, 0x07}}, - {x86.RDI, x86.R8, 1, x86.R8, []byte{0x46, 0x88, 0x04, 0x07}}, - {x86.R8, x86.RDI, 8, x86.RDI, []byte{0x49, 0x89, 0x3C, 0x38}}, - {x86.R8, x86.RDI, 4, x86.RDI, []byte{0x41, 0x89, 0x3C, 0x38}}, - {x86.R8, x86.RDI, 2, x86.RDI, []byte{0x66, 0x41, 0x89, 0x3C, 0x38}}, - {x86.R8, x86.RDI, 1, x86.RDI, []byte{0x41, 0x88, 0x3C, 0x38}}, - {x86.R9, x86.RSI, 8, x86.RSI, []byte{0x49, 0x89, 0x34, 0x31}}, - {x86.R9, x86.RSI, 4, x86.RSI, []byte{0x41, 0x89, 0x34, 0x31}}, - {x86.R9, x86.RSI, 2, x86.RSI, []byte{0x66, 0x41, 0x89, 0x34, 0x31}}, - {x86.R9, x86.RSI, 1, x86.RSI, []byte{0x41, 0x88, 0x34, 0x31}}, - {x86.R10, x86.RBP, 8, x86.RBP, []byte{0x49, 0x89, 0x2C, 0x2A}}, - {x86.R10, x86.RBP, 4, x86.RBP, []byte{0x41, 0x89, 0x2C, 0x2A}}, - {x86.R10, x86.RBP, 2, x86.RBP, []byte{0x66, 0x41, 0x89, 0x2C, 0x2A}}, - {x86.R10, x86.RBP, 1, x86.RBP, []byte{0x41, 0x88, 0x2C, 0x2A}}, - {x86.R11, x86.RSP, 8, x86.RSP, []byte{0x4A, 0x89, 0x24, 0x1C}}, - {x86.R11, x86.RSP, 4, x86.RSP, []byte{0x42, 0x89, 0x24, 0x1C}}, - {x86.R11, x86.RSP, 2, x86.RSP, []byte{0x66, 0x42, 0x89, 0x24, 0x1C}}, - {x86.R11, x86.RSP, 1, x86.RSP, []byte{0x42, 0x88, 0x24, 0x1C}}, - {x86.R12, x86.RBX, 8, x86.RBX, []byte{0x49, 0x89, 0x1C, 0x1C}}, - {x86.R12, x86.RBX, 4, x86.RBX, []byte{0x41, 0x89, 0x1C, 0x1C}}, - {x86.R12, x86.RBX, 2, x86.RBX, []byte{0x66, 0x41, 0x89, 0x1C, 0x1C}}, - {x86.R12, x86.RBX, 1, x86.RBX, []byte{0x41, 0x88, 0x1C, 0x1C}}, - {x86.R13, x86.RDX, 8, x86.RDX, []byte{0x49, 0x89, 0x54, 0x15, 0x00}}, - {x86.R13, x86.RDX, 4, x86.RDX, []byte{0x41, 0x89, 0x54, 0x15, 0x00}}, - {x86.R13, x86.RDX, 2, x86.RDX, []byte{0x66, 0x41, 0x89, 0x54, 0x15, 0x00}}, - {x86.R13, x86.RDX, 1, x86.RDX, []byte{0x41, 0x88, 0x54, 0x15, 0x00}}, - {x86.R14, x86.RCX, 8, x86.RCX, []byte{0x49, 0x89, 0x0C, 0x0E}}, - {x86.R14, x86.RCX, 4, x86.RCX, []byte{0x41, 0x89, 0x0C, 0x0E}}, - {x86.R14, x86.RCX, 2, x86.RCX, []byte{0x66, 0x41, 0x89, 0x0C, 0x0E}}, - {x86.R14, x86.RCX, 1, x86.RCX, []byte{0x41, 0x88, 0x0C, 0x0E}}, - {x86.R15, x86.RAX, 8, x86.RAX, []byte{0x49, 0x89, 0x04, 0x07}}, - {x86.R15, x86.RAX, 4, x86.RAX, []byte{0x41, 0x89, 0x04, 0x07}}, - {x86.R15, x86.RAX, 2, x86.RAX, []byte{0x66, 0x41, 0x89, 0x04, 0x07}}, - {x86.R15, x86.RAX, 1, x86.RAX, []byte{0x41, 0x88, 0x04, 0x07}}, + {x86.R0, x86.R15, 8, x86.R15, []byte{0x4E, 0x89, 0x3C, 0x38}}, + {x86.R0, x86.R15, 4, x86.R15, []byte{0x46, 0x89, 0x3C, 0x38}}, + {x86.R0, x86.R15, 2, x86.R15, []byte{0x66, 0x46, 0x89, 0x3C, 0x38}}, + {x86.R0, x86.R15, 1, x86.R15, []byte{0x46, 0x88, 0x3C, 0x38}}, + {x86.R1, x86.R14, 8, x86.R14, []byte{0x4E, 0x89, 0x34, 0x31}}, + {x86.R1, x86.R14, 4, x86.R14, []byte{0x46, 0x89, 0x34, 0x31}}, + {x86.R1, x86.R14, 2, x86.R14, []byte{0x66, 0x46, 0x89, 0x34, 0x31}}, + {x86.R1, x86.R14, 1, x86.R14, []byte{0x46, 0x88, 0x34, 0x31}}, + {x86.R2, x86.R13, 8, x86.R13, []byte{0x4E, 0x89, 0x2C, 0x2A}}, + {x86.R2, x86.R13, 4, x86.R13, []byte{0x46, 0x89, 0x2C, 0x2A}}, + {x86.R2, x86.R13, 2, x86.R13, []byte{0x66, 0x46, 0x89, 0x2C, 0x2A}}, + {x86.R2, x86.R13, 1, x86.R13, []byte{0x46, 0x88, 0x2C, 0x2A}}, + {x86.R3, x86.R12, 8, x86.R12, []byte{0x4E, 0x89, 0x24, 0x23}}, + {x86.R3, x86.R12, 4, x86.R12, []byte{0x46, 0x89, 0x24, 0x23}}, + {x86.R3, x86.R12, 2, x86.R12, []byte{0x66, 0x46, 0x89, 0x24, 0x23}}, + {x86.R3, x86.R12, 1, x86.R12, []byte{0x46, 0x88, 0x24, 0x23}}, + {x86.SP, x86.R11, 8, x86.R11, []byte{0x4E, 0x89, 0x1C, 0x1C}}, + {x86.SP, x86.R11, 4, x86.R11, []byte{0x46, 0x89, 0x1C, 0x1C}}, + {x86.SP, x86.R11, 2, x86.R11, []byte{0x66, 0x46, 0x89, 0x1C, 0x1C}}, + {x86.SP, x86.R11, 1, x86.R11, []byte{0x46, 0x88, 0x1C, 0x1C}}, + {x86.R5, x86.R10, 8, x86.R10, []byte{0x4E, 0x89, 0x54, 0x15, 0x00}}, + {x86.R5, x86.R10, 4, x86.R10, []byte{0x46, 0x89, 0x54, 0x15, 0x00}}, + {x86.R5, x86.R10, 2, x86.R10, []byte{0x66, 0x46, 0x89, 0x54, 0x15, 0x00}}, + {x86.R5, x86.R10, 1, x86.R10, []byte{0x46, 0x88, 0x54, 0x15, 0x00}}, + {x86.R6, x86.R9, 8, x86.R9, []byte{0x4E, 0x89, 0x0C, 0x0E}}, + {x86.R6, x86.R9, 4, x86.R9, []byte{0x46, 0x89, 0x0C, 0x0E}}, + {x86.R6, x86.R9, 2, x86.R9, []byte{0x66, 0x46, 0x89, 0x0C, 0x0E}}, + {x86.R6, x86.R9, 1, x86.R9, []byte{0x46, 0x88, 0x0C, 0x0E}}, + {x86.R7, x86.R8, 8, x86.R8, []byte{0x4E, 0x89, 0x04, 0x07}}, + {x86.R7, x86.R8, 4, x86.R8, []byte{0x46, 0x89, 0x04, 0x07}}, + {x86.R7, x86.R8, 2, x86.R8, []byte{0x66, 0x46, 0x89, 0x04, 0x07}}, + {x86.R7, x86.R8, 1, x86.R8, []byte{0x46, 0x88, 0x04, 0x07}}, + {x86.R8, x86.R7, 8, x86.R7, []byte{0x49, 0x89, 0x3C, 0x38}}, + {x86.R8, x86.R7, 4, x86.R7, []byte{0x41, 0x89, 0x3C, 0x38}}, + {x86.R8, x86.R7, 2, x86.R7, []byte{0x66, 0x41, 0x89, 0x3C, 0x38}}, + {x86.R8, x86.R7, 1, x86.R7, []byte{0x41, 0x88, 0x3C, 0x38}}, + {x86.R9, x86.R6, 8, x86.R6, []byte{0x49, 0x89, 0x34, 0x31}}, + {x86.R9, x86.R6, 4, x86.R6, []byte{0x41, 0x89, 0x34, 0x31}}, + {x86.R9, x86.R6, 2, x86.R6, []byte{0x66, 0x41, 0x89, 0x34, 0x31}}, + {x86.R9, x86.R6, 1, x86.R6, []byte{0x41, 0x88, 0x34, 0x31}}, + {x86.R10, x86.R5, 8, x86.R5, []byte{0x49, 0x89, 0x2C, 0x2A}}, + {x86.R10, x86.R5, 4, x86.R5, []byte{0x41, 0x89, 0x2C, 0x2A}}, + {x86.R10, x86.R5, 2, x86.R5, []byte{0x66, 0x41, 0x89, 0x2C, 0x2A}}, + {x86.R10, x86.R5, 1, x86.R5, []byte{0x41, 0x88, 0x2C, 0x2A}}, + {x86.R11, x86.SP, 8, x86.SP, []byte{0x4A, 0x89, 0x24, 0x1C}}, + {x86.R11, x86.SP, 4, x86.SP, []byte{0x42, 0x89, 0x24, 0x1C}}, + {x86.R11, x86.SP, 2, x86.SP, []byte{0x66, 0x42, 0x89, 0x24, 0x1C}}, + {x86.R11, x86.SP, 1, x86.SP, []byte{0x42, 0x88, 0x24, 0x1C}}, + {x86.R12, x86.R3, 8, x86.R3, []byte{0x49, 0x89, 0x1C, 0x1C}}, + {x86.R12, x86.R3, 4, x86.R3, []byte{0x41, 0x89, 0x1C, 0x1C}}, + {x86.R12, x86.R3, 2, x86.R3, []byte{0x66, 0x41, 0x89, 0x1C, 0x1C}}, + {x86.R12, x86.R3, 1, x86.R3, []byte{0x41, 0x88, 0x1C, 0x1C}}, + {x86.R13, x86.R2, 8, x86.R2, []byte{0x49, 0x89, 0x54, 0x15, 0x00}}, + {x86.R13, x86.R2, 4, x86.R2, []byte{0x41, 0x89, 0x54, 0x15, 0x00}}, + {x86.R13, x86.R2, 2, x86.R2, []byte{0x66, 0x41, 0x89, 0x54, 0x15, 0x00}}, + {x86.R13, x86.R2, 1, x86.R2, []byte{0x41, 0x88, 0x54, 0x15, 0x00}}, + {x86.R14, x86.R1, 8, x86.R1, []byte{0x49, 0x89, 0x0C, 0x0E}}, + {x86.R14, x86.R1, 4, x86.R1, []byte{0x41, 0x89, 0x0C, 0x0E}}, + {x86.R14, x86.R1, 2, x86.R1, []byte{0x66, 0x41, 0x89, 0x0C, 0x0E}}, + {x86.R14, x86.R1, 1, x86.R1, []byte{0x41, 0x88, 0x0C, 0x0E}}, + {x86.R15, x86.R0, 8, x86.R0, []byte{0x49, 0x89, 0x04, 0x07}}, + {x86.R15, x86.R0, 4, x86.R0, []byte{0x41, 0x89, 0x04, 0x07}}, + {x86.R15, x86.R0, 2, x86.R0, []byte{0x66, 0x41, 0x89, 0x04, 0x07}}, + {x86.R15, x86.R0, 1, x86.R0, []byte{0x41, 0x88, 0x04, 0x07}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Store_test.go b/src/x86/Store_test.go index 1e912c5..457209a 100644 --- a/src/x86/Store_test.go +++ b/src/x86/Store_test.go @@ -17,38 +17,38 @@ func TestStoreNumber(t *testing.T) { Code []byte }{ // No offset - {x86.RAX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RAX, 0, 4, 0x7F, []byte{0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RAX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x00, 0x7F, 0x00}}, - {x86.RAX, 0, 1, 0x7F, []byte{0xC6, 0x00, 0x7F}}, - {x86.RCX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RCX, 0, 4, 0x7F, []byte{0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RCX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x01, 0x7F, 0x00}}, - {x86.RCX, 0, 1, 0x7F, []byte{0xC6, 0x01, 0x7F}}, - {x86.RDX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDX, 0, 4, 0x7F, []byte{0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x02, 0x7F, 0x00}}, - {x86.RDX, 0, 1, 0x7F, []byte{0xC6, 0x02, 0x7F}}, - {x86.RBX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBX, 0, 4, 0x7F, []byte{0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x03, 0x7F, 0x00}}, - {x86.RBX, 0, 1, 0x7F, []byte{0xC6, 0x03, 0x7F}}, - {x86.RSP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSP, 0, 4, 0x7F, []byte{0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x04, 0x24, 0x7F, 0x00}}, - {x86.RSP, 0, 1, 0x7F, []byte{0xC6, 0x04, 0x24, 0x7F}}, - {x86.RBP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBP, 0, 4, 0x7F, []byte{0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x00, 0x7F, 0x00}}, - {x86.RBP, 0, 1, 0x7F, []byte{0xC6, 0x45, 0x00, 0x7F}}, - {x86.RSI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSI, 0, 4, 0x7F, []byte{0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x06, 0x7F, 0x00}}, - {x86.RSI, 0, 1, 0x7F, []byte{0xC6, 0x06, 0x7F}}, - {x86.RDI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDI, 0, 4, 0x7F, []byte{0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x07, 0x7F, 0x00}}, - {x86.RDI, 0, 1, 0x7F, []byte{0xC6, 0x07, 0x7F}}, + {x86.R0, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R0, 0, 4, 0x7F, []byte{0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R0, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x00, 0x7F, 0x00}}, + {x86.R0, 0, 1, 0x7F, []byte{0xC6, 0x00, 0x7F}}, + {x86.R1, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R1, 0, 4, 0x7F, []byte{0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R1, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x01, 0x7F, 0x00}}, + {x86.R1, 0, 1, 0x7F, []byte{0xC6, 0x01, 0x7F}}, + {x86.R2, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R2, 0, 4, 0x7F, []byte{0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R2, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x02, 0x7F, 0x00}}, + {x86.R2, 0, 1, 0x7F, []byte{0xC6, 0x02, 0x7F}}, + {x86.R3, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R3, 0, 4, 0x7F, []byte{0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R3, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x03, 0x7F, 0x00}}, + {x86.R3, 0, 1, 0x7F, []byte{0xC6, 0x03, 0x7F}}, + {x86.SP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x86.SP, 0, 4, 0x7F, []byte{0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x86.SP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x04, 0x24, 0x7F, 0x00}}, + {x86.SP, 0, 1, 0x7F, []byte{0xC6, 0x04, 0x24, 0x7F}}, + {x86.R5, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R5, 0, 4, 0x7F, []byte{0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R5, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x00, 0x7F, 0x00}}, + {x86.R5, 0, 1, 0x7F, []byte{0xC6, 0x45, 0x00, 0x7F}}, + {x86.R6, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R6, 0, 4, 0x7F, []byte{0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R6, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x06, 0x7F, 0x00}}, + {x86.R6, 0, 1, 0x7F, []byte{0xC6, 0x06, 0x7F}}, + {x86.R7, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R7, 0, 4, 0x7F, []byte{0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R7, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x07, 0x7F, 0x00}}, + {x86.R7, 0, 1, 0x7F, []byte{0xC6, 0x07, 0x7F}}, {x86.R8, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, {x86.R8, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, {x86.R8, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x00, 0x7F, 0x00}}, @@ -83,38 +83,38 @@ func TestStoreNumber(t *testing.T) { {x86.R15, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x07, 0x7F}}, // Offset of 1 - {x86.RAX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RAX, 1, 4, 0x7F, []byte{0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RAX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, - {x86.RAX, 1, 1, 0x7F, []byte{0xC6, 0x40, 0x01, 0x7F}}, - {x86.RCX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RCX, 1, 4, 0x7F, []byte{0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RCX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x41, 0x01, 0x7F, 0x00}}, - {x86.RCX, 1, 1, 0x7F, []byte{0xC6, 0x41, 0x01, 0x7F}}, - {x86.RDX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDX, 1, 4, 0x7F, []byte{0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x42, 0x01, 0x7F, 0x00}}, - {x86.RDX, 1, 1, 0x7F, []byte{0xC6, 0x42, 0x01, 0x7F}}, - {x86.RBX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBX, 1, 4, 0x7F, []byte{0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, - {x86.RBX, 1, 1, 0x7F, []byte{0xC6, 0x43, 0x01, 0x7F}}, - {x86.RSP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSP, 1, 4, 0x7F, []byte{0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, - {x86.RSP, 1, 1, 0x7F, []byte{0xC6, 0x44, 0x24, 0x01, 0x7F}}, - {x86.RBP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBP, 1, 4, 0x7F, []byte{0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, - {x86.RBP, 1, 1, 0x7F, []byte{0xC6, 0x45, 0x01, 0x7F}}, - {x86.RSI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSI, 1, 4, 0x7F, []byte{0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, - {x86.RSI, 1, 1, 0x7F, []byte{0xC6, 0x46, 0x01, 0x7F}}, - {x86.RDI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDI, 1, 4, 0x7F, []byte{0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, - {x86.RDI, 1, 1, 0x7F, []byte{0xC6, 0x47, 0x01, 0x7F}}, + {x86.R0, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R0, 1, 4, 0x7F, []byte{0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R0, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, + {x86.R0, 1, 1, 0x7F, []byte{0xC6, 0x40, 0x01, 0x7F}}, + {x86.R1, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R1, 1, 4, 0x7F, []byte{0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R1, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x41, 0x01, 0x7F, 0x00}}, + {x86.R1, 1, 1, 0x7F, []byte{0xC6, 0x41, 0x01, 0x7F}}, + {x86.R2, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R2, 1, 4, 0x7F, []byte{0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R2, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x42, 0x01, 0x7F, 0x00}}, + {x86.R2, 1, 1, 0x7F, []byte{0xC6, 0x42, 0x01, 0x7F}}, + {x86.R3, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R3, 1, 4, 0x7F, []byte{0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R3, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, + {x86.R3, 1, 1, 0x7F, []byte{0xC6, 0x43, 0x01, 0x7F}}, + {x86.SP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.SP, 1, 4, 0x7F, []byte{0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.SP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, + {x86.SP, 1, 1, 0x7F, []byte{0xC6, 0x44, 0x24, 0x01, 0x7F}}, + {x86.R5, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R5, 1, 4, 0x7F, []byte{0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R5, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, + {x86.R5, 1, 1, 0x7F, []byte{0xC6, 0x45, 0x01, 0x7F}}, + {x86.R6, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R6, 1, 4, 0x7F, []byte{0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R6, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, + {x86.R6, 1, 1, 0x7F, []byte{0xC6, 0x46, 0x01, 0x7F}}, + {x86.R7, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R7, 1, 4, 0x7F, []byte{0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R7, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, + {x86.R7, 1, 1, 0x7F, []byte{0xC6, 0x47, 0x01, 0x7F}}, {x86.R8, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, {x86.R8, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, {x86.R8, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, @@ -165,136 +165,136 @@ func TestStoreRegister(t *testing.T) { Code []byte }{ // No offset - {x86.RAX, 0, 8, x86.R15, []byte{0x4C, 0x89, 0x38}}, - {x86.RAX, 0, 4, x86.R15, []byte{0x44, 0x89, 0x38}}, - {x86.RAX, 0, 2, x86.R15, []byte{0x66, 0x44, 0x89, 0x38}}, - {x86.RAX, 0, 1, x86.R15, []byte{0x44, 0x88, 0x38}}, - {x86.RCX, 0, 8, x86.R14, []byte{0x4C, 0x89, 0x31}}, - {x86.RCX, 0, 4, x86.R14, []byte{0x44, 0x89, 0x31}}, - {x86.RCX, 0, 2, x86.R14, []byte{0x66, 0x44, 0x89, 0x31}}, - {x86.RCX, 0, 1, x86.R14, []byte{0x44, 0x88, 0x31}}, - {x86.RDX, 0, 8, x86.R13, []byte{0x4C, 0x89, 0x2A}}, - {x86.RDX, 0, 4, x86.R13, []byte{0x44, 0x89, 0x2A}}, - {x86.RDX, 0, 2, x86.R13, []byte{0x66, 0x44, 0x89, 0x2A}}, - {x86.RDX, 0, 1, x86.R13, []byte{0x44, 0x88, 0x2A}}, - {x86.RBX, 0, 8, x86.R12, []byte{0x4C, 0x89, 0x23}}, - {x86.RBX, 0, 4, x86.R12, []byte{0x44, 0x89, 0x23}}, - {x86.RBX, 0, 2, x86.R12, []byte{0x66, 0x44, 0x89, 0x23}}, - {x86.RBX, 0, 1, x86.R12, []byte{0x44, 0x88, 0x23}}, - {x86.RSP, 0, 8, x86.R11, []byte{0x4C, 0x89, 0x1C, 0x24}}, - {x86.RSP, 0, 4, x86.R11, []byte{0x44, 0x89, 0x1C, 0x24}}, - {x86.RSP, 0, 2, x86.R11, []byte{0x66, 0x44, 0x89, 0x1C, 0x24}}, - {x86.RSP, 0, 1, x86.R11, []byte{0x44, 0x88, 0x1C, 0x24}}, - {x86.RBP, 0, 8, x86.R10, []byte{0x4C, 0x89, 0x55, 0x00}}, - {x86.RBP, 0, 4, x86.R10, []byte{0x44, 0x89, 0x55, 0x00}}, - {x86.RBP, 0, 2, x86.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x00}}, - {x86.RBP, 0, 1, x86.R10, []byte{0x44, 0x88, 0x55, 0x00}}, - {x86.RSI, 0, 8, x86.R9, []byte{0x4C, 0x89, 0x0E}}, - {x86.RSI, 0, 4, x86.R9, []byte{0x44, 0x89, 0x0E}}, - {x86.RSI, 0, 2, x86.R9, []byte{0x66, 0x44, 0x89, 0x0E}}, - {x86.RSI, 0, 1, x86.R9, []byte{0x44, 0x88, 0x0E}}, - {x86.RDI, 0, 8, x86.R8, []byte{0x4C, 0x89, 0x07}}, - {x86.RDI, 0, 4, x86.R8, []byte{0x44, 0x89, 0x07}}, - {x86.RDI, 0, 2, x86.R8, []byte{0x66, 0x44, 0x89, 0x07}}, - {x86.RDI, 0, 1, x86.R8, []byte{0x44, 0x88, 0x07}}, - {x86.R8, 0, 8, x86.RDI, []byte{0x49, 0x89, 0x38}}, - {x86.R8, 0, 4, x86.RDI, []byte{0x41, 0x89, 0x38}}, - {x86.R8, 0, 2, x86.RDI, []byte{0x66, 0x41, 0x89, 0x38}}, - {x86.R8, 0, 1, x86.RDI, []byte{0x41, 0x88, 0x38}}, - {x86.R9, 0, 8, x86.RSI, []byte{0x49, 0x89, 0x31}}, - {x86.R9, 0, 4, x86.RSI, []byte{0x41, 0x89, 0x31}}, - {x86.R9, 0, 2, x86.RSI, []byte{0x66, 0x41, 0x89, 0x31}}, - {x86.R9, 0, 1, x86.RSI, []byte{0x41, 0x88, 0x31}}, - {x86.R10, 0, 8, x86.RBP, []byte{0x49, 0x89, 0x2A}}, - {x86.R10, 0, 4, x86.RBP, []byte{0x41, 0x89, 0x2A}}, - {x86.R10, 0, 2, x86.RBP, []byte{0x66, 0x41, 0x89, 0x2A}}, - {x86.R10, 0, 1, x86.RBP, []byte{0x41, 0x88, 0x2A}}, - {x86.R11, 0, 8, x86.RSP, []byte{0x49, 0x89, 0x23}}, - {x86.R11, 0, 4, x86.RSP, []byte{0x41, 0x89, 0x23}}, - {x86.R11, 0, 2, x86.RSP, []byte{0x66, 0x41, 0x89, 0x23}}, - {x86.R11, 0, 1, x86.RSP, []byte{0x41, 0x88, 0x23}}, - {x86.R12, 0, 8, x86.RBX, []byte{0x49, 0x89, 0x1C, 0x24}}, - {x86.R12, 0, 4, x86.RBX, []byte{0x41, 0x89, 0x1C, 0x24}}, - {x86.R12, 0, 2, x86.RBX, []byte{0x66, 0x41, 0x89, 0x1C, 0x24}}, - {x86.R12, 0, 1, x86.RBX, []byte{0x41, 0x88, 0x1C, 0x24}}, - {x86.R13, 0, 8, x86.RDX, []byte{0x49, 0x89, 0x55, 0x00}}, - {x86.R13, 0, 4, x86.RDX, []byte{0x41, 0x89, 0x55, 0x00}}, - {x86.R13, 0, 2, x86.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x00}}, - {x86.R13, 0, 1, x86.RDX, []byte{0x41, 0x88, 0x55, 0x00}}, - {x86.R14, 0, 8, x86.RCX, []byte{0x49, 0x89, 0x0E}}, - {x86.R14, 0, 4, x86.RCX, []byte{0x41, 0x89, 0x0E}}, - {x86.R14, 0, 2, x86.RCX, []byte{0x66, 0x41, 0x89, 0x0E}}, - {x86.R14, 0, 1, x86.RCX, []byte{0x41, 0x88, 0x0E}}, - {x86.R15, 0, 8, x86.RAX, []byte{0x49, 0x89, 0x07}}, - {x86.R15, 0, 4, x86.RAX, []byte{0x41, 0x89, 0x07}}, - {x86.R15, 0, 2, x86.RAX, []byte{0x66, 0x41, 0x89, 0x07}}, - {x86.R15, 0, 1, x86.RAX, []byte{0x41, 0x88, 0x07}}, + {x86.R0, 0, 8, x86.R15, []byte{0x4C, 0x89, 0x38}}, + {x86.R0, 0, 4, x86.R15, []byte{0x44, 0x89, 0x38}}, + {x86.R0, 0, 2, x86.R15, []byte{0x66, 0x44, 0x89, 0x38}}, + {x86.R0, 0, 1, x86.R15, []byte{0x44, 0x88, 0x38}}, + {x86.R1, 0, 8, x86.R14, []byte{0x4C, 0x89, 0x31}}, + {x86.R1, 0, 4, x86.R14, []byte{0x44, 0x89, 0x31}}, + {x86.R1, 0, 2, x86.R14, []byte{0x66, 0x44, 0x89, 0x31}}, + {x86.R1, 0, 1, x86.R14, []byte{0x44, 0x88, 0x31}}, + {x86.R2, 0, 8, x86.R13, []byte{0x4C, 0x89, 0x2A}}, + {x86.R2, 0, 4, x86.R13, []byte{0x44, 0x89, 0x2A}}, + {x86.R2, 0, 2, x86.R13, []byte{0x66, 0x44, 0x89, 0x2A}}, + {x86.R2, 0, 1, x86.R13, []byte{0x44, 0x88, 0x2A}}, + {x86.R3, 0, 8, x86.R12, []byte{0x4C, 0x89, 0x23}}, + {x86.R3, 0, 4, x86.R12, []byte{0x44, 0x89, 0x23}}, + {x86.R3, 0, 2, x86.R12, []byte{0x66, 0x44, 0x89, 0x23}}, + {x86.R3, 0, 1, x86.R12, []byte{0x44, 0x88, 0x23}}, + {x86.SP, 0, 8, x86.R11, []byte{0x4C, 0x89, 0x1C, 0x24}}, + {x86.SP, 0, 4, x86.R11, []byte{0x44, 0x89, 0x1C, 0x24}}, + {x86.SP, 0, 2, x86.R11, []byte{0x66, 0x44, 0x89, 0x1C, 0x24}}, + {x86.SP, 0, 1, x86.R11, []byte{0x44, 0x88, 0x1C, 0x24}}, + {x86.R5, 0, 8, x86.R10, []byte{0x4C, 0x89, 0x55, 0x00}}, + {x86.R5, 0, 4, x86.R10, []byte{0x44, 0x89, 0x55, 0x00}}, + {x86.R5, 0, 2, x86.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x00}}, + {x86.R5, 0, 1, x86.R10, []byte{0x44, 0x88, 0x55, 0x00}}, + {x86.R6, 0, 8, x86.R9, []byte{0x4C, 0x89, 0x0E}}, + {x86.R6, 0, 4, x86.R9, []byte{0x44, 0x89, 0x0E}}, + {x86.R6, 0, 2, x86.R9, []byte{0x66, 0x44, 0x89, 0x0E}}, + {x86.R6, 0, 1, x86.R9, []byte{0x44, 0x88, 0x0E}}, + {x86.R7, 0, 8, x86.R8, []byte{0x4C, 0x89, 0x07}}, + {x86.R7, 0, 4, x86.R8, []byte{0x44, 0x89, 0x07}}, + {x86.R7, 0, 2, x86.R8, []byte{0x66, 0x44, 0x89, 0x07}}, + {x86.R7, 0, 1, x86.R8, []byte{0x44, 0x88, 0x07}}, + {x86.R8, 0, 8, x86.R7, []byte{0x49, 0x89, 0x38}}, + {x86.R8, 0, 4, x86.R7, []byte{0x41, 0x89, 0x38}}, + {x86.R8, 0, 2, x86.R7, []byte{0x66, 0x41, 0x89, 0x38}}, + {x86.R8, 0, 1, x86.R7, []byte{0x41, 0x88, 0x38}}, + {x86.R9, 0, 8, x86.R6, []byte{0x49, 0x89, 0x31}}, + {x86.R9, 0, 4, x86.R6, []byte{0x41, 0x89, 0x31}}, + {x86.R9, 0, 2, x86.R6, []byte{0x66, 0x41, 0x89, 0x31}}, + {x86.R9, 0, 1, x86.R6, []byte{0x41, 0x88, 0x31}}, + {x86.R10, 0, 8, x86.R5, []byte{0x49, 0x89, 0x2A}}, + {x86.R10, 0, 4, x86.R5, []byte{0x41, 0x89, 0x2A}}, + {x86.R10, 0, 2, x86.R5, []byte{0x66, 0x41, 0x89, 0x2A}}, + {x86.R10, 0, 1, x86.R5, []byte{0x41, 0x88, 0x2A}}, + {x86.R11, 0, 8, x86.SP, []byte{0x49, 0x89, 0x23}}, + {x86.R11, 0, 4, x86.SP, []byte{0x41, 0x89, 0x23}}, + {x86.R11, 0, 2, x86.SP, []byte{0x66, 0x41, 0x89, 0x23}}, + {x86.R11, 0, 1, x86.SP, []byte{0x41, 0x88, 0x23}}, + {x86.R12, 0, 8, x86.R3, []byte{0x49, 0x89, 0x1C, 0x24}}, + {x86.R12, 0, 4, x86.R3, []byte{0x41, 0x89, 0x1C, 0x24}}, + {x86.R12, 0, 2, x86.R3, []byte{0x66, 0x41, 0x89, 0x1C, 0x24}}, + {x86.R12, 0, 1, x86.R3, []byte{0x41, 0x88, 0x1C, 0x24}}, + {x86.R13, 0, 8, x86.R2, []byte{0x49, 0x89, 0x55, 0x00}}, + {x86.R13, 0, 4, x86.R2, []byte{0x41, 0x89, 0x55, 0x00}}, + {x86.R13, 0, 2, x86.R2, []byte{0x66, 0x41, 0x89, 0x55, 0x00}}, + {x86.R13, 0, 1, x86.R2, []byte{0x41, 0x88, 0x55, 0x00}}, + {x86.R14, 0, 8, x86.R1, []byte{0x49, 0x89, 0x0E}}, + {x86.R14, 0, 4, x86.R1, []byte{0x41, 0x89, 0x0E}}, + {x86.R14, 0, 2, x86.R1, []byte{0x66, 0x41, 0x89, 0x0E}}, + {x86.R14, 0, 1, x86.R1, []byte{0x41, 0x88, 0x0E}}, + {x86.R15, 0, 8, x86.R0, []byte{0x49, 0x89, 0x07}}, + {x86.R15, 0, 4, x86.R0, []byte{0x41, 0x89, 0x07}}, + {x86.R15, 0, 2, x86.R0, []byte{0x66, 0x41, 0x89, 0x07}}, + {x86.R15, 0, 1, x86.R0, []byte{0x41, 0x88, 0x07}}, // Offset of 1 - {x86.RAX, 1, 8, x86.R15, []byte{0x4C, 0x89, 0x78, 0x01}}, - {x86.RAX, 1, 4, x86.R15, []byte{0x44, 0x89, 0x78, 0x01}}, - {x86.RAX, 1, 2, x86.R15, []byte{0x66, 0x44, 0x89, 0x78, 0x01}}, - {x86.RAX, 1, 1, x86.R15, []byte{0x44, 0x88, 0x78, 0x01}}, - {x86.RCX, 1, 8, x86.R14, []byte{0x4C, 0x89, 0x71, 0x01}}, - {x86.RCX, 1, 4, x86.R14, []byte{0x44, 0x89, 0x71, 0x01}}, - {x86.RCX, 1, 2, x86.R14, []byte{0x66, 0x44, 0x89, 0x71, 0x01}}, - {x86.RCX, 1, 1, x86.R14, []byte{0x44, 0x88, 0x71, 0x01}}, - {x86.RDX, 1, 8, x86.R13, []byte{0x4C, 0x89, 0x6A, 0x01}}, - {x86.RDX, 1, 4, x86.R13, []byte{0x44, 0x89, 0x6A, 0x01}}, - {x86.RDX, 1, 2, x86.R13, []byte{0x66, 0x44, 0x89, 0x6A, 0x01}}, - {x86.RDX, 1, 1, x86.R13, []byte{0x44, 0x88, 0x6A, 0x01}}, - {x86.RBX, 1, 8, x86.R12, []byte{0x4C, 0x89, 0x63, 0x01}}, - {x86.RBX, 1, 4, x86.R12, []byte{0x44, 0x89, 0x63, 0x01}}, - {x86.RBX, 1, 2, x86.R12, []byte{0x66, 0x44, 0x89, 0x63, 0x01}}, - {x86.RBX, 1, 1, x86.R12, []byte{0x44, 0x88, 0x63, 0x01}}, - {x86.RSP, 1, 8, x86.R11, []byte{0x4C, 0x89, 0x5C, 0x24, 0x01}}, - {x86.RSP, 1, 4, x86.R11, []byte{0x44, 0x89, 0x5C, 0x24, 0x01}}, - {x86.RSP, 1, 2, x86.R11, []byte{0x66, 0x44, 0x89, 0x5C, 0x24, 0x01}}, - {x86.RSP, 1, 1, x86.R11, []byte{0x44, 0x88, 0x5C, 0x24, 0x01}}, - {x86.RBP, 1, 8, x86.R10, []byte{0x4C, 0x89, 0x55, 0x01}}, - {x86.RBP, 1, 4, x86.R10, []byte{0x44, 0x89, 0x55, 0x01}}, - {x86.RBP, 1, 2, x86.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x01}}, - {x86.RBP, 1, 1, x86.R10, []byte{0x44, 0x88, 0x55, 0x01}}, - {x86.RSI, 1, 8, x86.R9, []byte{0x4C, 0x89, 0x4E, 0x01}}, - {x86.RSI, 1, 4, x86.R9, []byte{0x44, 0x89, 0x4E, 0x01}}, - {x86.RSI, 1, 2, x86.R9, []byte{0x66, 0x44, 0x89, 0x4E, 0x01}}, - {x86.RSI, 1, 1, x86.R9, []byte{0x44, 0x88, 0x4E, 0x01}}, - {x86.RDI, 1, 8, x86.R8, []byte{0x4C, 0x89, 0x47, 0x01}}, - {x86.RDI, 1, 4, x86.R8, []byte{0x44, 0x89, 0x47, 0x01}}, - {x86.RDI, 1, 2, x86.R8, []byte{0x66, 0x44, 0x89, 0x47, 0x01}}, - {x86.RDI, 1, 1, x86.R8, []byte{0x44, 0x88, 0x47, 0x01}}, - {x86.R8, 1, 8, x86.RDI, []byte{0x49, 0x89, 0x78, 0x01}}, - {x86.R8, 1, 4, x86.RDI, []byte{0x41, 0x89, 0x78, 0x01}}, - {x86.R8, 1, 2, x86.RDI, []byte{0x66, 0x41, 0x89, 0x78, 0x01}}, - {x86.R8, 1, 1, x86.RDI, []byte{0x41, 0x88, 0x78, 0x01}}, - {x86.R9, 1, 8, x86.RSI, []byte{0x49, 0x89, 0x71, 0x01}}, - {x86.R9, 1, 4, x86.RSI, []byte{0x41, 0x89, 0x71, 0x01}}, - {x86.R9, 1, 2, x86.RSI, []byte{0x66, 0x41, 0x89, 0x71, 0x01}}, - {x86.R9, 1, 1, x86.RSI, []byte{0x41, 0x88, 0x71, 0x01}}, - {x86.R10, 1, 8, x86.RBP, []byte{0x49, 0x89, 0x6A, 0x01}}, - {x86.R10, 1, 4, x86.RBP, []byte{0x41, 0x89, 0x6A, 0x01}}, - {x86.R10, 1, 2, x86.RBP, []byte{0x66, 0x41, 0x89, 0x6A, 0x01}}, - {x86.R10, 1, 1, x86.RBP, []byte{0x41, 0x88, 0x6A, 0x01}}, - {x86.R11, 1, 8, x86.RSP, []byte{0x49, 0x89, 0x63, 0x01}}, - {x86.R11, 1, 4, x86.RSP, []byte{0x41, 0x89, 0x63, 0x01}}, - {x86.R11, 1, 2, x86.RSP, []byte{0x66, 0x41, 0x89, 0x63, 0x01}}, - {x86.R11, 1, 1, x86.RSP, []byte{0x41, 0x88, 0x63, 0x01}}, - {x86.R12, 1, 8, x86.RBX, []byte{0x49, 0x89, 0x5C, 0x24, 0x01}}, - {x86.R12, 1, 4, x86.RBX, []byte{0x41, 0x89, 0x5C, 0x24, 0x01}}, - {x86.R12, 1, 2, x86.RBX, []byte{0x66, 0x41, 0x89, 0x5C, 0x24, 0x01}}, - {x86.R12, 1, 1, x86.RBX, []byte{0x41, 0x88, 0x5C, 0x24, 01}}, - {x86.R13, 1, 8, x86.RDX, []byte{0x49, 0x89, 0x55, 0x01}}, - {x86.R13, 1, 4, x86.RDX, []byte{0x41, 0x89, 0x55, 0x01}}, - {x86.R13, 1, 2, x86.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x01}}, - {x86.R13, 1, 1, x86.RDX, []byte{0x41, 0x88, 0x55, 0x01}}, - {x86.R14, 1, 8, x86.RCX, []byte{0x49, 0x89, 0x4E, 0x01}}, - {x86.R14, 1, 4, x86.RCX, []byte{0x41, 0x89, 0x4E, 0x01}}, - {x86.R14, 1, 2, x86.RCX, []byte{0x66, 0x41, 0x89, 0x4E, 0x01}}, - {x86.R14, 1, 1, x86.RCX, []byte{0x41, 0x88, 0x4E, 0x01}}, - {x86.R15, 1, 8, x86.RAX, []byte{0x49, 0x89, 0x47, 0x01}}, - {x86.R15, 1, 4, x86.RAX, []byte{0x41, 0x89, 0x47, 0x01}}, - {x86.R15, 1, 2, x86.RAX, []byte{0x66, 0x41, 0x89, 0x47, 0x01}}, - {x86.R15, 1, 1, x86.RAX, []byte{0x41, 0x88, 0x47, 0x01}}, + {x86.R0, 1, 8, x86.R15, []byte{0x4C, 0x89, 0x78, 0x01}}, + {x86.R0, 1, 4, x86.R15, []byte{0x44, 0x89, 0x78, 0x01}}, + {x86.R0, 1, 2, x86.R15, []byte{0x66, 0x44, 0x89, 0x78, 0x01}}, + {x86.R0, 1, 1, x86.R15, []byte{0x44, 0x88, 0x78, 0x01}}, + {x86.R1, 1, 8, x86.R14, []byte{0x4C, 0x89, 0x71, 0x01}}, + {x86.R1, 1, 4, x86.R14, []byte{0x44, 0x89, 0x71, 0x01}}, + {x86.R1, 1, 2, x86.R14, []byte{0x66, 0x44, 0x89, 0x71, 0x01}}, + {x86.R1, 1, 1, x86.R14, []byte{0x44, 0x88, 0x71, 0x01}}, + {x86.R2, 1, 8, x86.R13, []byte{0x4C, 0x89, 0x6A, 0x01}}, + {x86.R2, 1, 4, x86.R13, []byte{0x44, 0x89, 0x6A, 0x01}}, + {x86.R2, 1, 2, x86.R13, []byte{0x66, 0x44, 0x89, 0x6A, 0x01}}, + {x86.R2, 1, 1, x86.R13, []byte{0x44, 0x88, 0x6A, 0x01}}, + {x86.R3, 1, 8, x86.R12, []byte{0x4C, 0x89, 0x63, 0x01}}, + {x86.R3, 1, 4, x86.R12, []byte{0x44, 0x89, 0x63, 0x01}}, + {x86.R3, 1, 2, x86.R12, []byte{0x66, 0x44, 0x89, 0x63, 0x01}}, + {x86.R3, 1, 1, x86.R12, []byte{0x44, 0x88, 0x63, 0x01}}, + {x86.SP, 1, 8, x86.R11, []byte{0x4C, 0x89, 0x5C, 0x24, 0x01}}, + {x86.SP, 1, 4, x86.R11, []byte{0x44, 0x89, 0x5C, 0x24, 0x01}}, + {x86.SP, 1, 2, x86.R11, []byte{0x66, 0x44, 0x89, 0x5C, 0x24, 0x01}}, + {x86.SP, 1, 1, x86.R11, []byte{0x44, 0x88, 0x5C, 0x24, 0x01}}, + {x86.R5, 1, 8, x86.R10, []byte{0x4C, 0x89, 0x55, 0x01}}, + {x86.R5, 1, 4, x86.R10, []byte{0x44, 0x89, 0x55, 0x01}}, + {x86.R5, 1, 2, x86.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x01}}, + {x86.R5, 1, 1, x86.R10, []byte{0x44, 0x88, 0x55, 0x01}}, + {x86.R6, 1, 8, x86.R9, []byte{0x4C, 0x89, 0x4E, 0x01}}, + {x86.R6, 1, 4, x86.R9, []byte{0x44, 0x89, 0x4E, 0x01}}, + {x86.R6, 1, 2, x86.R9, []byte{0x66, 0x44, 0x89, 0x4E, 0x01}}, + {x86.R6, 1, 1, x86.R9, []byte{0x44, 0x88, 0x4E, 0x01}}, + {x86.R7, 1, 8, x86.R8, []byte{0x4C, 0x89, 0x47, 0x01}}, + {x86.R7, 1, 4, x86.R8, []byte{0x44, 0x89, 0x47, 0x01}}, + {x86.R7, 1, 2, x86.R8, []byte{0x66, 0x44, 0x89, 0x47, 0x01}}, + {x86.R7, 1, 1, x86.R8, []byte{0x44, 0x88, 0x47, 0x01}}, + {x86.R8, 1, 8, x86.R7, []byte{0x49, 0x89, 0x78, 0x01}}, + {x86.R8, 1, 4, x86.R7, []byte{0x41, 0x89, 0x78, 0x01}}, + {x86.R8, 1, 2, x86.R7, []byte{0x66, 0x41, 0x89, 0x78, 0x01}}, + {x86.R8, 1, 1, x86.R7, []byte{0x41, 0x88, 0x78, 0x01}}, + {x86.R9, 1, 8, x86.R6, []byte{0x49, 0x89, 0x71, 0x01}}, + {x86.R9, 1, 4, x86.R6, []byte{0x41, 0x89, 0x71, 0x01}}, + {x86.R9, 1, 2, x86.R6, []byte{0x66, 0x41, 0x89, 0x71, 0x01}}, + {x86.R9, 1, 1, x86.R6, []byte{0x41, 0x88, 0x71, 0x01}}, + {x86.R10, 1, 8, x86.R5, []byte{0x49, 0x89, 0x6A, 0x01}}, + {x86.R10, 1, 4, x86.R5, []byte{0x41, 0x89, 0x6A, 0x01}}, + {x86.R10, 1, 2, x86.R5, []byte{0x66, 0x41, 0x89, 0x6A, 0x01}}, + {x86.R10, 1, 1, x86.R5, []byte{0x41, 0x88, 0x6A, 0x01}}, + {x86.R11, 1, 8, x86.SP, []byte{0x49, 0x89, 0x63, 0x01}}, + {x86.R11, 1, 4, x86.SP, []byte{0x41, 0x89, 0x63, 0x01}}, + {x86.R11, 1, 2, x86.SP, []byte{0x66, 0x41, 0x89, 0x63, 0x01}}, + {x86.R11, 1, 1, x86.SP, []byte{0x41, 0x88, 0x63, 0x01}}, + {x86.R12, 1, 8, x86.R3, []byte{0x49, 0x89, 0x5C, 0x24, 0x01}}, + {x86.R12, 1, 4, x86.R3, []byte{0x41, 0x89, 0x5C, 0x24, 0x01}}, + {x86.R12, 1, 2, x86.R3, []byte{0x66, 0x41, 0x89, 0x5C, 0x24, 0x01}}, + {x86.R12, 1, 1, x86.R3, []byte{0x41, 0x88, 0x5C, 0x24, 01}}, + {x86.R13, 1, 8, x86.R2, []byte{0x49, 0x89, 0x55, 0x01}}, + {x86.R13, 1, 4, x86.R2, []byte{0x41, 0x89, 0x55, 0x01}}, + {x86.R13, 1, 2, x86.R2, []byte{0x66, 0x41, 0x89, 0x55, 0x01}}, + {x86.R13, 1, 1, x86.R2, []byte{0x41, 0x88, 0x55, 0x01}}, + {x86.R14, 1, 8, x86.R1, []byte{0x49, 0x89, 0x4E, 0x01}}, + {x86.R14, 1, 4, x86.R1, []byte{0x41, 0x89, 0x4E, 0x01}}, + {x86.R14, 1, 2, x86.R1, []byte{0x66, 0x41, 0x89, 0x4E, 0x01}}, + {x86.R14, 1, 1, x86.R1, []byte{0x41, 0x88, 0x4E, 0x01}}, + {x86.R15, 1, 8, x86.R0, []byte{0x49, 0x89, 0x47, 0x01}}, + {x86.R15, 1, 4, x86.R0, []byte{0x41, 0x89, 0x47, 0x01}}, + {x86.R15, 1, 2, x86.R0, []byte{0x66, 0x41, 0x89, 0x47, 0x01}}, + {x86.R15, 1, 1, x86.R0, []byte{0x41, 0x88, 0x47, 0x01}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Sub_test.go b/src/x86/Sub_test.go index 5502edc..bd58796 100644 --- a/src/x86/Sub_test.go +++ b/src/x86/Sub_test.go @@ -14,14 +14,14 @@ func TestSubRegisterNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0x83, 0xE8, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0x83, 0xE9, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0x83, 0xEA, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0x83, 0xEB, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0x83, 0xEC, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0x83, 0xED, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0x83, 0xEE, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0x83, 0xEF, 0x01}}, + {x86.R0, 1, []byte{0x48, 0x83, 0xE8, 0x01}}, + {x86.R1, 1, []byte{0x48, 0x83, 0xE9, 0x01}}, + {x86.R2, 1, []byte{0x48, 0x83, 0xEA, 0x01}}, + {x86.R3, 1, []byte{0x48, 0x83, 0xEB, 0x01}}, + {x86.SP, 1, []byte{0x48, 0x83, 0xEC, 0x01}}, + {x86.R5, 1, []byte{0x48, 0x83, 0xED, 0x01}}, + {x86.R6, 1, []byte{0x48, 0x83, 0xEE, 0x01}}, + {x86.R7, 1, []byte{0x48, 0x83, 0xEF, 0x01}}, {x86.R8, 1, []byte{0x49, 0x83, 0xE8, 0x01}}, {x86.R9, 1, []byte{0x49, 0x83, 0xE9, 0x01}}, {x86.R10, 1, []byte{0x49, 0x83, 0xEA, 0x01}}, @@ -31,14 +31,14 @@ func TestSubRegisterNumber(t *testing.T) { {x86.R14, 1, []byte{0x49, 0x83, 0xEE, 0x01}}, {x86.R15, 1, []byte{0x49, 0x83, 0xEF, 0x01}}, - {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFF, []byte{0x48, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -62,22 +62,22 @@ func TestSubRegisterRegister(t *testing.T) { Right cpu.Register Code []byte }{ - {x86.RAX, x86.R15, []byte{0x4C, 0x29, 0xF8}}, - {x86.RCX, x86.R14, []byte{0x4C, 0x29, 0xF1}}, - {x86.RDX, x86.R13, []byte{0x4C, 0x29, 0xEA}}, - {x86.RBX, x86.R12, []byte{0x4C, 0x29, 0xE3}}, - {x86.RSP, x86.R11, []byte{0x4C, 0x29, 0xDC}}, - {x86.RBP, x86.R10, []byte{0x4C, 0x29, 0xD5}}, - {x86.RSI, x86.R9, []byte{0x4C, 0x29, 0xCE}}, - {x86.RDI, x86.R8, []byte{0x4C, 0x29, 0xC7}}, - {x86.R8, x86.RDI, []byte{0x49, 0x29, 0xF8}}, - {x86.R9, x86.RSI, []byte{0x49, 0x29, 0xF1}}, - {x86.R10, x86.RBP, []byte{0x49, 0x29, 0xEA}}, - {x86.R11, x86.RSP, []byte{0x49, 0x29, 0xE3}}, - {x86.R12, x86.RBX, []byte{0x49, 0x29, 0xDC}}, - {x86.R13, x86.RDX, []byte{0x49, 0x29, 0xD5}}, - {x86.R14, x86.RCX, []byte{0x49, 0x29, 0xCE}}, - {x86.R15, x86.RAX, []byte{0x49, 0x29, 0xC7}}, + {x86.R0, x86.R15, []byte{0x4C, 0x29, 0xF8}}, + {x86.R1, x86.R14, []byte{0x4C, 0x29, 0xF1}}, + {x86.R2, x86.R13, []byte{0x4C, 0x29, 0xEA}}, + {x86.R3, x86.R12, []byte{0x4C, 0x29, 0xE3}}, + {x86.SP, x86.R11, []byte{0x4C, 0x29, 0xDC}}, + {x86.R5, x86.R10, []byte{0x4C, 0x29, 0xD5}}, + {x86.R6, x86.R9, []byte{0x4C, 0x29, 0xCE}}, + {x86.R7, x86.R8, []byte{0x4C, 0x29, 0xC7}}, + {x86.R8, x86.R7, []byte{0x49, 0x29, 0xF8}}, + {x86.R9, x86.R6, []byte{0x49, 0x29, 0xF1}}, + {x86.R10, x86.R5, []byte{0x49, 0x29, 0xEA}}, + {x86.R11, x86.SP, []byte{0x49, 0x29, 0xE3}}, + {x86.R12, x86.R3, []byte{0x49, 0x29, 0xDC}}, + {x86.R13, x86.R2, []byte{0x49, 0x29, 0xD5}}, + {x86.R14, x86.R1, []byte{0x49, 0x29, 0xCE}}, + {x86.R15, x86.R0, []byte{0x49, 0x29, 0xC7}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Xor_test.go b/src/x86/Xor_test.go index 6456cb4..770b688 100644 --- a/src/x86/Xor_test.go +++ b/src/x86/Xor_test.go @@ -14,14 +14,14 @@ func TestXorRegisterNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0x83, 0xF0, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0x83, 0xF1, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0x83, 0xF2, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0x83, 0xF3, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0x83, 0xF4, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0x83, 0xF5, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0x83, 0xF6, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0x83, 0xF7, 0x01}}, + {x86.R0, 1, []byte{0x48, 0x83, 0xF0, 0x01}}, + {x86.R1, 1, []byte{0x48, 0x83, 0xF1, 0x01}}, + {x86.R2, 1, []byte{0x48, 0x83, 0xF2, 0x01}}, + {x86.R3, 1, []byte{0x48, 0x83, 0xF3, 0x01}}, + {x86.SP, 1, []byte{0x48, 0x83, 0xF4, 0x01}}, + {x86.R5, 1, []byte{0x48, 0x83, 0xF5, 0x01}}, + {x86.R6, 1, []byte{0x48, 0x83, 0xF6, 0x01}}, + {x86.R7, 1, []byte{0x48, 0x83, 0xF7, 0x01}}, {x86.R8, 1, []byte{0x49, 0x83, 0xF0, 0x01}}, {x86.R9, 1, []byte{0x49, 0x83, 0xF1, 0x01}}, {x86.R10, 1, []byte{0x49, 0x83, 0xF2, 0x01}}, @@ -31,14 +31,14 @@ func TestXorRegisterNumber(t *testing.T) { {x86.R14, 1, []byte{0x49, 0x83, 0xF6, 0x01}}, {x86.R15, 1, []byte{0x49, 0x83, 0xF7, 0x01}}, - {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF7, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -62,22 +62,22 @@ func TestXorRegisterRegister(t *testing.T) { Right cpu.Register Code []byte }{ - {x86.RAX, x86.R15, []byte{0x4C, 0x31, 0xF8}}, - {x86.RCX, x86.R14, []byte{0x4C, 0x31, 0xF1}}, - {x86.RDX, x86.R13, []byte{0x4C, 0x31, 0xEA}}, - {x86.RBX, x86.R12, []byte{0x4C, 0x31, 0xE3}}, - {x86.RSP, x86.R11, []byte{0x4C, 0x31, 0xDC}}, - {x86.RBP, x86.R10, []byte{0x4C, 0x31, 0xD5}}, - {x86.RSI, x86.R9, []byte{0x4C, 0x31, 0xCE}}, - {x86.RDI, x86.R8, []byte{0x4C, 0x31, 0xC7}}, - {x86.R8, x86.RDI, []byte{0x49, 0x31, 0xF8}}, - {x86.R9, x86.RSI, []byte{0x49, 0x31, 0xF1}}, - {x86.R10, x86.RBP, []byte{0x49, 0x31, 0xEA}}, - {x86.R11, x86.RSP, []byte{0x49, 0x31, 0xE3}}, - {x86.R12, x86.RBX, []byte{0x49, 0x31, 0xDC}}, - {x86.R13, x86.RDX, []byte{0x49, 0x31, 0xD5}}, - {x86.R14, x86.RCX, []byte{0x49, 0x31, 0xCE}}, - {x86.R15, x86.RAX, []byte{0x49, 0x31, 0xC7}}, + {x86.R0, x86.R15, []byte{0x4C, 0x31, 0xF8}}, + {x86.R1, x86.R14, []byte{0x4C, 0x31, 0xF1}}, + {x86.R2, x86.R13, []byte{0x4C, 0x31, 0xEA}}, + {x86.R3, x86.R12, []byte{0x4C, 0x31, 0xE3}}, + {x86.SP, x86.R11, []byte{0x4C, 0x31, 0xDC}}, + {x86.R5, x86.R10, []byte{0x4C, 0x31, 0xD5}}, + {x86.R6, x86.R9, []byte{0x4C, 0x31, 0xCE}}, + {x86.R7, x86.R8, []byte{0x4C, 0x31, 0xC7}}, + {x86.R8, x86.R7, []byte{0x49, 0x31, 0xF8}}, + {x86.R9, x86.R6, []byte{0x49, 0x31, 0xF1}}, + {x86.R10, x86.R5, []byte{0x49, 0x31, 0xEA}}, + {x86.R11, x86.SP, []byte{0x49, 0x31, 0xE3}}, + {x86.R12, x86.R3, []byte{0x49, 0x31, 0xDC}}, + {x86.R13, x86.R2, []byte{0x49, 0x31, 0xD5}}, + {x86.R14, x86.R1, []byte{0x49, 0x31, 0xCE}}, + {x86.R15, x86.R0, []byte{0x49, 0x31, 0xC7}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/encode.go b/src/x86/encode.go index d09fd44..732a156 100644 --- a/src/x86/encode.go +++ b/src/x86/encode.go @@ -23,7 +23,7 @@ func encode(code []byte, mod AddressMode, reg cpu.Register, rm cpu.Register, num rm &= 0b111 } - if w != 0 || r != 0 || x != 0 || b != 0 || (numBytes == 1 && (reg == RSP || reg == RBP || reg == RSI || reg == RDI)) { + if w != 0 || r != 0 || x != 0 || b != 0 || (numBytes == 1 && (reg == SP || reg == R5 || reg == R6 || reg == R7)) { code = append(code, REX(w, r, x, b)) } diff --git a/src/x86/memoryAccess.go b/src/x86/memoryAccess.go index 42e6a37..c7559b8 100644 --- a/src/x86/memoryAccess.go +++ b/src/x86/memoryAccess.go @@ -12,7 +12,7 @@ func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Registe mod := AddressMemory - if offset != 0 || base == RBP || base == R13 { + if offset != 0 || base == R5 || base == R13 { mod = AddressMemoryOffset8 } @@ -22,7 +22,7 @@ func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Registe code = encode(code, mod, register, base, length, opCode) - if base == RSP || base == R12 { + if base == SP || base == R12 { code = append(code, SIB(Scale1, 0b100, 0b100)) } diff --git a/src/x86/memoryAccessDynamic.go b/src/x86/memoryAccessDynamic.go index 092fa64..f4eb76e 100644 --- a/src/x86/memoryAccessDynamic.go +++ b/src/x86/memoryAccessDynamic.go @@ -17,7 +17,7 @@ func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, register cpu. opCode = opCode8 } - if offset == RSP { + if offset == SP { offset, base = base, offset } @@ -40,7 +40,7 @@ func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, register cpu. base &= 0b111 } - if base == RBP || base == R13 { + if base == R5 || base == R13 { mod = AddressMemoryOffset8 } diff --git a/src/x86/x86_test.go b/src/x86/x86_test.go index 9d6b3bc..ece5699 100644 --- a/src/x86/x86_test.go +++ b/src/x86/x86_test.go @@ -10,7 +10,7 @@ import ( func TestX86(t *testing.T) { assert.DeepEqual(t, x86.Call(nil, 1), []byte{0xE8, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x86.CallAt(nil, 1), []byte{0xFF, 0x15, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x86.ExtendRAXToRDX(nil), []byte{0x48, 0x99}) + assert.DeepEqual(t, x86.ExtendR0ToR2(nil), []byte{0x48, 0x99}) assert.DeepEqual(t, x86.MoveRegisterNumber(nil, 0, 1), []byte{0xB8, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x86.MoveRegisterNumber(nil, 1, 1), []byte{0xB9, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x86.Return(nil), []byte{0xC3}) From 487c7fb3a6bbd172f6f31854ceea805af7296cbe Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 17 Apr 2025 14:16:00 +0200 Subject: [PATCH 1004/1012] Simplified register names --- src/arm/Registers.go | 12 +- src/arm/Registers_test.go | 2 +- src/asmc/x86Compiler.go | 40 +-- src/core/CallExtern.go | 12 +- src/core/CompileAssignDivision.go | 10 +- src/core/ExecuteRegisterNumber.go | 4 +- src/core/ExecuteRegisterRegister.go | 4 +- src/x86/Add_test.go | 64 ++--- src/x86/And_test.go | 64 ++--- src/x86/Call_test.go | 48 ++-- src/x86/Compare_test.go | 64 ++--- src/x86/Div_test.go | 16 +- src/x86/ExtendRAXToRDX.go | 4 +- src/x86/LoadAddress_test.go | 16 +- src/x86/LoadDynamic_test.go | 128 +++++----- src/x86/Load_test.go | 256 +++++++++---------- src/x86/Move_test.go | 80 +++--- src/x86/Mul_test.go | 64 ++--- src/x86/Negate_test.go | 16 +- src/x86/Or_test.go | 64 ++--- src/x86/Pop_test.go | 16 +- src/x86/Push_test.go | 16 +- src/x86/Registers.go | 38 ++- src/x86/Registers_test.go | 2 +- src/x86/Shift_test.go | 32 +-- src/x86/StoreDynamic_test.go | 256 +++++++++---------- src/x86/Store_test.go | 384 ++++++++++++++-------------- src/x86/Sub_test.go | 64 ++--- src/x86/Xor_test.go | 64 ++--- src/x86/encode.go | 2 +- src/x86/memoryAccess.go | 4 +- src/x86/memoryAccessDynamic.go | 4 +- src/x86/x86_test.go | 2 +- 33 files changed, 922 insertions(+), 930 deletions(-) diff --git a/src/arm/Registers.go b/src/arm/Registers.go index 9e54156..884816a 100644 --- a/src/arm/Registers.go +++ b/src/arm/Registers.go @@ -44,20 +44,16 @@ const ( ) var ( - GeneralRegisters = []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X19, X20, X21, X22, X23, X24, X25, X26} InputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5} - OutputRegisters = InputRegisters - SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5} - SyscallOutputRegisters = []cpu.Register{X0, X1} WindowsInputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5, X6, X7} WindowsOutputRegisters = []cpu.Register{X0, X1} CPU = cpu.CPU{ - General: GeneralRegisters, + General: []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X19, X20, X21, X22, X23, X24, X25, X26}, Input: InputRegisters, - Output: OutputRegisters, - SyscallInput: SyscallInputRegisters, - SyscallOutput: SyscallOutputRegisters, + Output: InputRegisters, + SyscallInput: []cpu.Register{X8, X0, X1, X2, X3, X4, X5}, + SyscallOutput: []cpu.Register{X0, X1}, NumRegisters: 32, } ) diff --git a/src/arm/Registers_test.go b/src/arm/Registers_test.go index 50cdae9..d5c9755 100644 --- a/src/arm/Registers_test.go +++ b/src/arm/Registers_test.go @@ -8,5 +8,5 @@ import ( ) func TestRegisters(t *testing.T) { - assert.NotNil(t, arm.SyscallInputRegisters) + assert.NotContains(t, arm.CPU.General, arm.SP) } diff --git a/src/asmc/x86Compiler.go b/src/asmc/x86Compiler.go index b721039..b09e1d2 100644 --- a/src/asmc/x86Compiler.go +++ b/src/asmc/x86Compiler.go @@ -241,29 +241,29 @@ func (c *x86Compiler) handleDivInstruction(instruction asm.Instruction) { case asm.TypeRegisterNumber: operands := c.assembler.Param.RegisterNumber[instruction.Index] - if operands.Register != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Register) + if operands.Register != x86.R0 { + c.code = x86.MoveRegisterRegister(c.code, x86.R0, operands.Register) } c.code = x86.MoveRegisterNumber(c.code, x86.TMP, operands.Number) - c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.ExtendR0ToR2(c.code) c.code = x86.DivRegister(c.code, x86.TMP) - if operands.Register != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.RAX) + if operands.Register != x86.R0 { + c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.R0) } case asm.TypeRegisterRegister: operands := c.assembler.Param.RegisterRegister[instruction.Index] - if operands.Destination != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) + if operands.Destination != x86.R0 { + c.code = x86.MoveRegisterRegister(c.code, x86.R0, operands.Destination) } - c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.ExtendR0ToR2(c.code) c.code = x86.DivRegister(c.code, operands.Source) - if operands.Destination != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.RAX) + if operands.Destination != x86.R0 { + c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.R0) } } } @@ -273,29 +273,29 @@ func (c *x86Compiler) handleModuloInstruction(instruction asm.Instruction) { case asm.TypeRegisterNumber: operands := c.assembler.Param.RegisterNumber[instruction.Index] - if operands.Register != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Register) + if operands.Register != x86.R0 { + c.code = x86.MoveRegisterRegister(c.code, x86.R0, operands.Register) } c.code = x86.MoveRegisterNumber(c.code, x86.TMP, operands.Number) - c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.ExtendR0ToR2(c.code) c.code = x86.DivRegister(c.code, x86.TMP) - if operands.Register != x86.RDX { - c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.RDX) + if operands.Register != x86.R2 { + c.code = x86.MoveRegisterRegister(c.code, operands.Register, x86.R2) } case asm.TypeRegisterRegister: operands := c.assembler.Param.RegisterRegister[instruction.Index] - if operands.Destination != x86.RAX { - c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination) + if operands.Destination != x86.R0 { + c.code = x86.MoveRegisterRegister(c.code, x86.R0, operands.Destination) } - c.code = x86.ExtendRAXToRDX(c.code) + c.code = x86.ExtendR0ToR2(c.code) c.code = x86.DivRegister(c.code, operands.Source) - if operands.Destination != x86.RDX { - c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.RDX) + if operands.Destination != x86.R2 { + c.code = x86.MoveRegisterRegister(c.code, operands.Destination, x86.R2) } } } diff --git a/src/core/CallExtern.go b/src/core/CallExtern.go index 241c15a..2adc153 100644 --- a/src/core/CallExtern.go +++ b/src/core/CallExtern.go @@ -30,15 +30,15 @@ func (f *Function) CallExtern(fn *Function, parameters []*expression.Expression) return nil, err } - f.Register(asm.PUSH, x86.RBP) - f.RegisterRegister(asm.MOVE, x86.RBP, x86.RSP) - f.RegisterNumber(asm.AND, x86.RSP, -16) + f.Register(asm.PUSH, x86.R5) + f.RegisterRegister(asm.MOVE, x86.R5, x86.SP) + f.RegisterNumber(asm.AND, x86.SP, -16) f.Number(asm.PUSH, 0) f.Number(asm.PUSH, 0) - f.RegisterNumber(asm.SUB, x86.RSP, 32) + f.RegisterNumber(asm.SUB, x86.SP, 32) f.DLLCall(fn.UniqueName) - f.RegisterRegister(asm.MOVE, x86.RSP, x86.RBP) - f.Register(asm.POP, x86.RBP) + f.RegisterRegister(asm.MOVE, x86.SP, x86.R5) + f.Register(asm.POP, x86.R5) for _, register := range registers { f.FreeRegister(register) diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go index 3d4ab0e..59fa074 100644 --- a/src/core/CompileAssignDivision.go +++ b/src/core/CompileAssignDivision.go @@ -76,9 +76,9 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { switch dividend := dividend.(type) { case *eval.Number: - f.SaveRegister(x86.RAX) - f.RegisterNumber(asm.MOVE, x86.RAX, dividend.Number) - err = f.Execute(division.Token, x86.RAX, divisor) + f.SaveRegister(x86.R0) + f.RegisterNumber(asm.MOVE, x86.R0, dividend.Number) + err = f.Execute(division.Token, x86.R0, divisor) case *eval.Register: if dividend.Register != quotientVariable.Value.Register && dividend.IsAlive() { tmp := f.NewRegister() @@ -92,7 +92,7 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error { panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, dividend)) } - f.RegisterRegister(asm.MOVE, quotientVariable.Value.Register, x86.RAX) - f.RegisterRegister(asm.MOVE, remainderVariable.Value.Register, x86.RDX) + f.RegisterRegister(asm.MOVE, quotientVariable.Value.Register, x86.R0) + f.RegisterRegister(asm.MOVE, remainderVariable.Value.Register, x86.R2) return err } diff --git a/src/core/ExecuteRegisterNumber.go b/src/core/ExecuteRegisterNumber.go index bd79684..5294e82 100644 --- a/src/core/ExecuteRegisterNumber.go +++ b/src/core/ExecuteRegisterNumber.go @@ -27,14 +27,14 @@ func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Reg case token.Div, token.DivAssign: if config.TargetArch == config.X86 { - f.SaveRegister(x86.RDX) + f.SaveRegister(x86.R2) } f.RegisterNumber(asm.DIV, register, number) case token.Mod, token.ModAssign: if config.TargetArch == config.X86 { - f.SaveRegister(x86.RDX) + f.SaveRegister(x86.R2) } f.RegisterNumber(asm.MODULO, register, number) diff --git a/src/core/ExecuteRegisterRegister.go b/src/core/ExecuteRegisterRegister.go index bcb19de..166ec7f 100644 --- a/src/core/ExecuteRegisterRegister.go +++ b/src/core/ExecuteRegisterRegister.go @@ -27,14 +27,14 @@ func (f *Function) ExecuteRegisterRegister(operation token.Token, register cpu.R case token.Div, token.DivAssign: if config.TargetArch == config.X86 { - f.SaveRegister(x86.RDX) + f.SaveRegister(x86.R2) } f.RegisterRegister(asm.DIV, register, operand) case token.Mod, token.ModAssign: if config.TargetArch == config.X86 { - f.SaveRegister(x86.RDX) + f.SaveRegister(x86.R2) } f.RegisterRegister(asm.MODULO, register, operand) diff --git a/src/x86/Add_test.go b/src/x86/Add_test.go index 71097d4..2291761 100644 --- a/src/x86/Add_test.go +++ b/src/x86/Add_test.go @@ -14,14 +14,14 @@ func TestAddRegisterNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0x83, 0xC0, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0x83, 0xC1, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0x83, 0xC2, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0x83, 0xC3, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0x83, 0xC4, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0x83, 0xC5, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0x83, 0xC6, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0x83, 0xC7, 0x01}}, + {x86.R0, 1, []byte{0x48, 0x83, 0xC0, 0x01}}, + {x86.R1, 1, []byte{0x48, 0x83, 0xC1, 0x01}}, + {x86.R2, 1, []byte{0x48, 0x83, 0xC2, 0x01}}, + {x86.R3, 1, []byte{0x48, 0x83, 0xC3, 0x01}}, + {x86.SP, 1, []byte{0x48, 0x83, 0xC4, 0x01}}, + {x86.R5, 1, []byte{0x48, 0x83, 0xC5, 0x01}}, + {x86.R6, 1, []byte{0x48, 0x83, 0xC6, 0x01}}, + {x86.R7, 1, []byte{0x48, 0x83, 0xC7, 0x01}}, {x86.R8, 1, []byte{0x49, 0x83, 0xC0, 0x01}}, {x86.R9, 1, []byte{0x49, 0x83, 0xC1, 0x01}}, {x86.R10, 1, []byte{0x49, 0x83, 0xC2, 0x01}}, @@ -31,14 +31,14 @@ func TestAddRegisterNumber(t *testing.T) { {x86.R14, 1, []byte{0x49, 0x83, 0xC6, 0x01}}, {x86.R15, 1, []byte{0x49, 0x83, 0xC7, 0x01}}, - {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -62,22 +62,22 @@ func TestAddRegisterRegister(t *testing.T) { Right cpu.Register Code []byte }{ - {x86.RAX, x86.R15, []byte{0x4C, 0x01, 0xF8}}, - {x86.RCX, x86.R14, []byte{0x4C, 0x01, 0xF1}}, - {x86.RDX, x86.R13, []byte{0x4C, 0x01, 0xEA}}, - {x86.RBX, x86.R12, []byte{0x4C, 0x01, 0xE3}}, - {x86.RSP, x86.R11, []byte{0x4C, 0x01, 0xDC}}, - {x86.RBP, x86.R10, []byte{0x4C, 0x01, 0xD5}}, - {x86.RSI, x86.R9, []byte{0x4C, 0x01, 0xCE}}, - {x86.RDI, x86.R8, []byte{0x4C, 0x01, 0xC7}}, - {x86.R8, x86.RDI, []byte{0x49, 0x01, 0xF8}}, - {x86.R9, x86.RSI, []byte{0x49, 0x01, 0xF1}}, - {x86.R10, x86.RBP, []byte{0x49, 0x01, 0xEA}}, - {x86.R11, x86.RSP, []byte{0x49, 0x01, 0xE3}}, - {x86.R12, x86.RBX, []byte{0x49, 0x01, 0xDC}}, - {x86.R13, x86.RDX, []byte{0x49, 0x01, 0xD5}}, - {x86.R14, x86.RCX, []byte{0x49, 0x01, 0xCE}}, - {x86.R15, x86.RAX, []byte{0x49, 0x01, 0xC7}}, + {x86.R0, x86.R15, []byte{0x4C, 0x01, 0xF8}}, + {x86.R1, x86.R14, []byte{0x4C, 0x01, 0xF1}}, + {x86.R2, x86.R13, []byte{0x4C, 0x01, 0xEA}}, + {x86.R3, x86.R12, []byte{0x4C, 0x01, 0xE3}}, + {x86.SP, x86.R11, []byte{0x4C, 0x01, 0xDC}}, + {x86.R5, x86.R10, []byte{0x4C, 0x01, 0xD5}}, + {x86.R6, x86.R9, []byte{0x4C, 0x01, 0xCE}}, + {x86.R7, x86.R8, []byte{0x4C, 0x01, 0xC7}}, + {x86.R8, x86.R7, []byte{0x49, 0x01, 0xF8}}, + {x86.R9, x86.R6, []byte{0x49, 0x01, 0xF1}}, + {x86.R10, x86.R5, []byte{0x49, 0x01, 0xEA}}, + {x86.R11, x86.SP, []byte{0x49, 0x01, 0xE3}}, + {x86.R12, x86.R3, []byte{0x49, 0x01, 0xDC}}, + {x86.R13, x86.R2, []byte{0x49, 0x01, 0xD5}}, + {x86.R14, x86.R1, []byte{0x49, 0x01, 0xCE}}, + {x86.R15, x86.R0, []byte{0x49, 0x01, 0xC7}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/And_test.go b/src/x86/And_test.go index 7fb6fd4..9474456 100644 --- a/src/x86/And_test.go +++ b/src/x86/And_test.go @@ -14,14 +14,14 @@ func TestAndRegisterNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0x83, 0xE0, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0x83, 0xE1, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0x83, 0xE2, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0x83, 0xE3, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0x83, 0xE4, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0x83, 0xE5, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0x83, 0xE6, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0x83, 0xE7, 0x01}}, + {x86.R0, 1, []byte{0x48, 0x83, 0xE0, 0x01}}, + {x86.R1, 1, []byte{0x48, 0x83, 0xE1, 0x01}}, + {x86.R2, 1, []byte{0x48, 0x83, 0xE2, 0x01}}, + {x86.R3, 1, []byte{0x48, 0x83, 0xE3, 0x01}}, + {x86.SP, 1, []byte{0x48, 0x83, 0xE4, 0x01}}, + {x86.R5, 1, []byte{0x48, 0x83, 0xE5, 0x01}}, + {x86.R6, 1, []byte{0x48, 0x83, 0xE6, 0x01}}, + {x86.R7, 1, []byte{0x48, 0x83, 0xE7, 0x01}}, {x86.R8, 1, []byte{0x49, 0x83, 0xE0, 0x01}}, {x86.R9, 1, []byte{0x49, 0x83, 0xE1, 0x01}}, {x86.R10, 1, []byte{0x49, 0x83, 0xE2, 0x01}}, @@ -31,14 +31,14 @@ func TestAndRegisterNumber(t *testing.T) { {x86.R14, 1, []byte{0x49, 0x83, 0xE6, 0x01}}, {x86.R15, 1, []byte{0x49, 0x83, 0xE7, 0x01}}, - {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE7, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE0, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE2, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -62,22 +62,22 @@ func TestAndRegisterRegister(t *testing.T) { Right cpu.Register Code []byte }{ - {x86.RAX, x86.R15, []byte{0x4C, 0x21, 0xF8}}, - {x86.RCX, x86.R14, []byte{0x4C, 0x21, 0xF1}}, - {x86.RDX, x86.R13, []byte{0x4C, 0x21, 0xEA}}, - {x86.RBX, x86.R12, []byte{0x4C, 0x21, 0xE3}}, - {x86.RSP, x86.R11, []byte{0x4C, 0x21, 0xDC}}, - {x86.RBP, x86.R10, []byte{0x4C, 0x21, 0xD5}}, - {x86.RSI, x86.R9, []byte{0x4C, 0x21, 0xCE}}, - {x86.RDI, x86.R8, []byte{0x4C, 0x21, 0xC7}}, - {x86.R8, x86.RDI, []byte{0x49, 0x21, 0xF8}}, - {x86.R9, x86.RSI, []byte{0x49, 0x21, 0xF1}}, - {x86.R10, x86.RBP, []byte{0x49, 0x21, 0xEA}}, - {x86.R11, x86.RSP, []byte{0x49, 0x21, 0xE3}}, - {x86.R12, x86.RBX, []byte{0x49, 0x21, 0xDC}}, - {x86.R13, x86.RDX, []byte{0x49, 0x21, 0xD5}}, - {x86.R14, x86.RCX, []byte{0x49, 0x21, 0xCE}}, - {x86.R15, x86.RAX, []byte{0x49, 0x21, 0xC7}}, + {x86.R0, x86.R15, []byte{0x4C, 0x21, 0xF8}}, + {x86.R1, x86.R14, []byte{0x4C, 0x21, 0xF1}}, + {x86.R2, x86.R13, []byte{0x4C, 0x21, 0xEA}}, + {x86.R3, x86.R12, []byte{0x4C, 0x21, 0xE3}}, + {x86.SP, x86.R11, []byte{0x4C, 0x21, 0xDC}}, + {x86.R5, x86.R10, []byte{0x4C, 0x21, 0xD5}}, + {x86.R6, x86.R9, []byte{0x4C, 0x21, 0xCE}}, + {x86.R7, x86.R8, []byte{0x4C, 0x21, 0xC7}}, + {x86.R8, x86.R7, []byte{0x49, 0x21, 0xF8}}, + {x86.R9, x86.R6, []byte{0x49, 0x21, 0xF1}}, + {x86.R10, x86.R5, []byte{0x49, 0x21, 0xEA}}, + {x86.R11, x86.SP, []byte{0x49, 0x21, 0xE3}}, + {x86.R12, x86.R3, []byte{0x49, 0x21, 0xDC}}, + {x86.R13, x86.R2, []byte{0x49, 0x21, 0xD5}}, + {x86.R14, x86.R1, []byte{0x49, 0x21, 0xCE}}, + {x86.R15, x86.R0, []byte{0x49, 0x21, 0xC7}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Call_test.go b/src/x86/Call_test.go index 033ee64..ec8006e 100644 --- a/src/x86/Call_test.go +++ b/src/x86/Call_test.go @@ -13,14 +13,14 @@ func TestCallRegister(t *testing.T) { Register cpu.Register Code []byte }{ - {x86.RAX, []byte{0xFF, 0xD0}}, - {x86.RCX, []byte{0xFF, 0xD1}}, - {x86.RDX, []byte{0xFF, 0xD2}}, - {x86.RBX, []byte{0xFF, 0xD3}}, - {x86.RSP, []byte{0xFF, 0xD4}}, - {x86.RBP, []byte{0xFF, 0xD5}}, - {x86.RSI, []byte{0xFF, 0xD6}}, - {x86.RDI, []byte{0xFF, 0xD7}}, + {x86.R0, []byte{0xFF, 0xD0}}, + {x86.R1, []byte{0xFF, 0xD1}}, + {x86.R2, []byte{0xFF, 0xD2}}, + {x86.R3, []byte{0xFF, 0xD3}}, + {x86.SP, []byte{0xFF, 0xD4}}, + {x86.R5, []byte{0xFF, 0xD5}}, + {x86.R6, []byte{0xFF, 0xD6}}, + {x86.R7, []byte{0xFF, 0xD7}}, {x86.R8, []byte{0x41, 0xFF, 0xD0}}, {x86.R9, []byte{0x41, 0xFF, 0xD1}}, {x86.R10, []byte{0x41, 0xFF, 0xD2}}, @@ -44,14 +44,14 @@ func TestCallAtMemory(t *testing.T) { Offset int8 Code []byte }{ - {x86.RAX, 0, []byte{0xFF, 0x10}}, - {x86.RCX, 0, []byte{0xFF, 0x11}}, - {x86.RDX, 0, []byte{0xFF, 0x12}}, - {x86.RBX, 0, []byte{0xFF, 0x13}}, - {x86.RSP, 0, []byte{0xFF, 0x14, 0x24}}, - {x86.RBP, 0, []byte{0xFF, 0x55, 0x00}}, - {x86.RSI, 0, []byte{0xFF, 0x16}}, - {x86.RDI, 0, []byte{0xFF, 0x17}}, + {x86.R0, 0, []byte{0xFF, 0x10}}, + {x86.R1, 0, []byte{0xFF, 0x11}}, + {x86.R2, 0, []byte{0xFF, 0x12}}, + {x86.R3, 0, []byte{0xFF, 0x13}}, + {x86.SP, 0, []byte{0xFF, 0x14, 0x24}}, + {x86.R5, 0, []byte{0xFF, 0x55, 0x00}}, + {x86.R6, 0, []byte{0xFF, 0x16}}, + {x86.R7, 0, []byte{0xFF, 0x17}}, {x86.R8, 0, []byte{0x41, 0xFF, 0x10}}, {x86.R9, 0, []byte{0x41, 0xFF, 0x11}}, {x86.R10, 0, []byte{0x41, 0xFF, 0x12}}, @@ -61,14 +61,14 @@ func TestCallAtMemory(t *testing.T) { {x86.R14, 0, []byte{0x41, 0xFF, 0x16}}, {x86.R15, 0, []byte{0x41, 0xFF, 0x17}}, - {x86.RAX, 1, []byte{0xFF, 0x50, 0x01}}, - {x86.RCX, 1, []byte{0xFF, 0x51, 0x01}}, - {x86.RDX, 1, []byte{0xFF, 0x52, 0x01}}, - {x86.RBX, 1, []byte{0xFF, 0x53, 0x01}}, - {x86.RSP, 1, []byte{0xFF, 0x54, 0x24, 0x01}}, - {x86.RBP, 1, []byte{0xFF, 0x55, 0x01}}, - {x86.RSI, 1, []byte{0xFF, 0x56, 0x01}}, - {x86.RDI, 1, []byte{0xFF, 0x57, 0x01}}, + {x86.R0, 1, []byte{0xFF, 0x50, 0x01}}, + {x86.R1, 1, []byte{0xFF, 0x51, 0x01}}, + {x86.R2, 1, []byte{0xFF, 0x52, 0x01}}, + {x86.R3, 1, []byte{0xFF, 0x53, 0x01}}, + {x86.SP, 1, []byte{0xFF, 0x54, 0x24, 0x01}}, + {x86.R5, 1, []byte{0xFF, 0x55, 0x01}}, + {x86.R6, 1, []byte{0xFF, 0x56, 0x01}}, + {x86.R7, 1, []byte{0xFF, 0x57, 0x01}}, {x86.R8, 1, []byte{0x41, 0xFF, 0x50, 0x01}}, {x86.R9, 1, []byte{0x41, 0xFF, 0x51, 0x01}}, {x86.R10, 1, []byte{0x41, 0xFF, 0x52, 0x01}}, diff --git a/src/x86/Compare_test.go b/src/x86/Compare_test.go index 141c863..953e4a0 100644 --- a/src/x86/Compare_test.go +++ b/src/x86/Compare_test.go @@ -14,14 +14,14 @@ func TestCompareRegisterNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0x83, 0xF8, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0x83, 0xF9, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0x83, 0xFA, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0x83, 0xFB, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0x83, 0xFC, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0x83, 0xFD, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0x83, 0xFE, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0x83, 0xFF, 0x01}}, + {x86.R0, 1, []byte{0x48, 0x83, 0xF8, 0x01}}, + {x86.R1, 1, []byte{0x48, 0x83, 0xF9, 0x01}}, + {x86.R2, 1, []byte{0x48, 0x83, 0xFA, 0x01}}, + {x86.R3, 1, []byte{0x48, 0x83, 0xFB, 0x01}}, + {x86.SP, 1, []byte{0x48, 0x83, 0xFC, 0x01}}, + {x86.R5, 1, []byte{0x48, 0x83, 0xFD, 0x01}}, + {x86.R6, 1, []byte{0x48, 0x83, 0xFE, 0x01}}, + {x86.R7, 1, []byte{0x48, 0x83, 0xFF, 0x01}}, {x86.R8, 1, []byte{0x49, 0x83, 0xF8, 0x01}}, {x86.R9, 1, []byte{0x49, 0x83, 0xF9, 0x01}}, {x86.R10, 1, []byte{0x49, 0x83, 0xFA, 0x01}}, @@ -31,14 +31,14 @@ func TestCompareRegisterNumber(t *testing.T) { {x86.R14, 1, []byte{0x49, 0x83, 0xFE, 0x01}}, {x86.R15, 1, []byte{0x49, 0x83, 0xFF, 0x01}}, - {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -62,22 +62,22 @@ func TestCompareRegisterRegister(t *testing.T) { Right cpu.Register Code []byte }{ - {x86.RAX, x86.R15, []byte{0x4C, 0x39, 0xF8}}, - {x86.RCX, x86.R14, []byte{0x4C, 0x39, 0xF1}}, - {x86.RDX, x86.R13, []byte{0x4C, 0x39, 0xEA}}, - {x86.RBX, x86.R12, []byte{0x4C, 0x39, 0xE3}}, - {x86.RSP, x86.R11, []byte{0x4C, 0x39, 0xDC}}, - {x86.RBP, x86.R10, []byte{0x4C, 0x39, 0xD5}}, - {x86.RSI, x86.R9, []byte{0x4C, 0x39, 0xCE}}, - {x86.RDI, x86.R8, []byte{0x4C, 0x39, 0xC7}}, - {x86.R8, x86.RDI, []byte{0x49, 0x39, 0xF8}}, - {x86.R9, x86.RSI, []byte{0x49, 0x39, 0xF1}}, - {x86.R10, x86.RBP, []byte{0x49, 0x39, 0xEA}}, - {x86.R11, x86.RSP, []byte{0x49, 0x39, 0xE3}}, - {x86.R12, x86.RBX, []byte{0x49, 0x39, 0xDC}}, - {x86.R13, x86.RDX, []byte{0x49, 0x39, 0xD5}}, - {x86.R14, x86.RCX, []byte{0x49, 0x39, 0xCE}}, - {x86.R15, x86.RAX, []byte{0x49, 0x39, 0xC7}}, + {x86.R0, x86.R15, []byte{0x4C, 0x39, 0xF8}}, + {x86.R1, x86.R14, []byte{0x4C, 0x39, 0xF1}}, + {x86.R2, x86.R13, []byte{0x4C, 0x39, 0xEA}}, + {x86.R3, x86.R12, []byte{0x4C, 0x39, 0xE3}}, + {x86.SP, x86.R11, []byte{0x4C, 0x39, 0xDC}}, + {x86.R5, x86.R10, []byte{0x4C, 0x39, 0xD5}}, + {x86.R6, x86.R9, []byte{0x4C, 0x39, 0xCE}}, + {x86.R7, x86.R8, []byte{0x4C, 0x39, 0xC7}}, + {x86.R8, x86.R7, []byte{0x49, 0x39, 0xF8}}, + {x86.R9, x86.R6, []byte{0x49, 0x39, 0xF1}}, + {x86.R10, x86.R5, []byte{0x49, 0x39, 0xEA}}, + {x86.R11, x86.SP, []byte{0x49, 0x39, 0xE3}}, + {x86.R12, x86.R3, []byte{0x49, 0x39, 0xDC}}, + {x86.R13, x86.R2, []byte{0x49, 0x39, 0xD5}}, + {x86.R14, x86.R1, []byte{0x49, 0x39, 0xCE}}, + {x86.R15, x86.R0, []byte{0x49, 0x39, 0xC7}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Div_test.go b/src/x86/Div_test.go index 5069f76..aa8ca0d 100644 --- a/src/x86/Div_test.go +++ b/src/x86/Div_test.go @@ -13,14 +13,14 @@ func TestDivRegister(t *testing.T) { Register cpu.Register Code []byte }{ - {x86.RAX, []byte{0x48, 0xF7, 0xF8}}, - {x86.RCX, []byte{0x48, 0xF7, 0xF9}}, - {x86.RDX, []byte{0x48, 0xF7, 0xFA}}, - {x86.RBX, []byte{0x48, 0xF7, 0xFB}}, - {x86.RSP, []byte{0x48, 0xF7, 0xFC}}, - {x86.RBP, []byte{0x48, 0xF7, 0xFD}}, - {x86.RSI, []byte{0x48, 0xF7, 0xFE}}, - {x86.RDI, []byte{0x48, 0xF7, 0xFF}}, + {x86.R0, []byte{0x48, 0xF7, 0xF8}}, + {x86.R1, []byte{0x48, 0xF7, 0xF9}}, + {x86.R2, []byte{0x48, 0xF7, 0xFA}}, + {x86.R3, []byte{0x48, 0xF7, 0xFB}}, + {x86.SP, []byte{0x48, 0xF7, 0xFC}}, + {x86.R5, []byte{0x48, 0xF7, 0xFD}}, + {x86.R6, []byte{0x48, 0xF7, 0xFE}}, + {x86.R7, []byte{0x48, 0xF7, 0xFF}}, {x86.R8, []byte{0x49, 0xF7, 0xF8}}, {x86.R9, []byte{0x49, 0xF7, 0xF9}}, {x86.R10, []byte{0x49, 0xF7, 0xFA}}, diff --git a/src/x86/ExtendRAXToRDX.go b/src/x86/ExtendRAXToRDX.go index 7d97a86..825a084 100644 --- a/src/x86/ExtendRAXToRDX.go +++ b/src/x86/ExtendRAXToRDX.go @@ -1,7 +1,7 @@ package x86 -// ExtendRAXToRDX doubles the size of RAX by sign-extending it to RDX. +// ExtendR0ToR2 doubles the size of R0 (RAX) by sign-extending it to R2 (RDX). // This is also known as CQO. -func ExtendRAXToRDX(code []byte) []byte { +func ExtendR0ToR2(code []byte) []byte { return append(code, 0x48, 0x99) } diff --git a/src/x86/LoadAddress_test.go b/src/x86/LoadAddress_test.go index cedf23f..a0570e3 100644 --- a/src/x86/LoadAddress_test.go +++ b/src/x86/LoadAddress_test.go @@ -14,14 +14,14 @@ func TestLoadAddress(t *testing.T) { Offset int Code []byte }{ - {x86.RAX, 0, []byte{0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00}}, - {x86.RCX, 0, []byte{0x48, 0x8D, 0x0D, 0x00, 0x00, 0x00, 0x00}}, - {x86.RDX, 0, []byte{0x48, 0x8D, 0x15, 0x00, 0x00, 0x00, 0x00}}, - {x86.RBX, 0, []byte{0x48, 0x8D, 0x1D, 0x00, 0x00, 0x00, 0x00}}, - {x86.RSP, 0, []byte{0x48, 0x8D, 0x25, 0x00, 0x00, 0x00, 0x00}}, - {x86.RBP, 0, []byte{0x48, 0x8D, 0x2D, 0x00, 0x00, 0x00, 0x00}}, - {x86.RSI, 0, []byte{0x48, 0x8D, 0x35, 0x00, 0x00, 0x00, 0x00}}, - {x86.RDI, 0, []byte{0x48, 0x8D, 0x3D, 0x00, 0x00, 0x00, 0x00}}, + {x86.R0, 0, []byte{0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00}}, + {x86.R1, 0, []byte{0x48, 0x8D, 0x0D, 0x00, 0x00, 0x00, 0x00}}, + {x86.R2, 0, []byte{0x48, 0x8D, 0x15, 0x00, 0x00, 0x00, 0x00}}, + {x86.R3, 0, []byte{0x48, 0x8D, 0x1D, 0x00, 0x00, 0x00, 0x00}}, + {x86.SP, 0, []byte{0x48, 0x8D, 0x25, 0x00, 0x00, 0x00, 0x00}}, + {x86.R5, 0, []byte{0x48, 0x8D, 0x2D, 0x00, 0x00, 0x00, 0x00}}, + {x86.R6, 0, []byte{0x48, 0x8D, 0x35, 0x00, 0x00, 0x00, 0x00}}, + {x86.R7, 0, []byte{0x48, 0x8D, 0x3D, 0x00, 0x00, 0x00, 0x00}}, {x86.R8, 0, []byte{0x4C, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00}}, {x86.R9, 0, []byte{0x4C, 0x8D, 0x0D, 0x00, 0x00, 0x00, 0x00}}, {x86.R10, 0, []byte{0x4C, 0x8D, 0x15, 0x00, 0x00, 0x00, 0x00}}, diff --git a/src/x86/LoadDynamic_test.go b/src/x86/LoadDynamic_test.go index 4cc3935..86cbf36 100644 --- a/src/x86/LoadDynamic_test.go +++ b/src/x86/LoadDynamic_test.go @@ -16,70 +16,70 @@ func TestLoadDynamicRegister(t *testing.T) { OffsetRegister cpu.Register Code []byte }{ - {x86.R15, 8, x86.RAX, x86.R15, []byte{0x4E, 0x8B, 0x3C, 0x38}}, - {x86.R15, 4, x86.RAX, x86.R15, []byte{0x46, 0x8B, 0x3C, 0x38}}, - {x86.R15, 2, x86.RAX, x86.R15, []byte{0x66, 0x46, 0x8B, 0x3C, 0x38}}, - {x86.R15, 1, x86.RAX, x86.R15, []byte{0x46, 0x8A, 0x3C, 0x38}}, - {x86.R14, 8, x86.RCX, x86.R14, []byte{0x4E, 0x8B, 0x34, 0x31}}, - {x86.R14, 4, x86.RCX, x86.R14, []byte{0x46, 0x8B, 0x34, 0x31}}, - {x86.R14, 2, x86.RCX, x86.R14, []byte{0x66, 0x46, 0x8B, 0x34, 0x31}}, - {x86.R14, 1, x86.RCX, x86.R14, []byte{0x46, 0x8A, 0x34, 0x31}}, - {x86.R13, 8, x86.RDX, x86.R13, []byte{0x4E, 0x8B, 0x2C, 0x2A}}, - {x86.R13, 4, x86.RDX, x86.R13, []byte{0x46, 0x8B, 0x2C, 0x2A}}, - {x86.R13, 2, x86.RDX, x86.R13, []byte{0x66, 0x46, 0x8B, 0x2C, 0x2A}}, - {x86.R13, 1, x86.RDX, x86.R13, []byte{0x46, 0x8A, 0x2C, 0x2A}}, - {x86.R12, 8, x86.RBX, x86.R12, []byte{0x4E, 0x8B, 0x24, 0x23}}, - {x86.R12, 4, x86.RBX, x86.R12, []byte{0x46, 0x8B, 0x24, 0x23}}, - {x86.R12, 2, x86.RBX, x86.R12, []byte{0x66, 0x46, 0x8B, 0x24, 0x23}}, - {x86.R12, 1, x86.RBX, x86.R12, []byte{0x46, 0x8A, 0x24, 0x23}}, - {x86.R11, 8, x86.RSP, x86.R11, []byte{0x4E, 0x8B, 0x1C, 0x1C}}, - {x86.R11, 4, x86.RSP, x86.R11, []byte{0x46, 0x8B, 0x1C, 0x1C}}, - {x86.R11, 2, x86.RSP, x86.R11, []byte{0x66, 0x46, 0x8B, 0x1C, 0x1C}}, - {x86.R11, 1, x86.RSP, x86.R11, []byte{0x46, 0x8A, 0x1C, 0x1C}}, - {x86.R10, 8, x86.RBP, x86.R10, []byte{0x4E, 0x8B, 0x54, 0x15, 0x00}}, - {x86.R10, 4, x86.RBP, x86.R10, []byte{0x46, 0x8B, 0x54, 0x15, 0x00}}, - {x86.R10, 2, x86.RBP, x86.R10, []byte{0x66, 0x46, 0x8B, 0x54, 0x15, 0x00}}, - {x86.R10, 1, x86.RBP, x86.R10, []byte{0x46, 0x8A, 0x54, 0x15, 0x00}}, - {x86.R9, 8, x86.RSI, x86.R9, []byte{0x4E, 0x8B, 0x0C, 0x0E}}, - {x86.R9, 4, x86.RSI, x86.R9, []byte{0x46, 0x8B, 0x0C, 0x0E}}, - {x86.R9, 2, x86.RSI, x86.R9, []byte{0x66, 0x46, 0x8B, 0x0C, 0x0E}}, - {x86.R9, 1, x86.RSI, x86.R9, []byte{0x46, 0x8A, 0x0C, 0x0E}}, - {x86.R8, 8, x86.RDI, x86.R8, []byte{0x4E, 0x8B, 0x04, 0x07}}, - {x86.R8, 4, x86.RDI, x86.R8, []byte{0x46, 0x8B, 0x04, 0x07}}, - {x86.R8, 2, x86.RDI, x86.R8, []byte{0x66, 0x46, 0x8B, 0x04, 0x07}}, - {x86.R8, 1, x86.RDI, x86.R8, []byte{0x46, 0x8A, 0x04, 0x07}}, - {x86.RDI, 8, x86.R8, x86.RDI, []byte{0x49, 0x8B, 0x3C, 0x38}}, - {x86.RDI, 4, x86.R8, x86.RDI, []byte{0x41, 0x8B, 0x3C, 0x38}}, - {x86.RDI, 2, x86.R8, x86.RDI, []byte{0x66, 0x41, 0x8B, 0x3C, 0x38}}, - {x86.RDI, 1, x86.R8, x86.RDI, []byte{0x41, 0x8A, 0x3C, 0x38}}, - {x86.RSI, 8, x86.R9, x86.RSI, []byte{0x49, 0x8B, 0x34, 0x31}}, - {x86.RSI, 4, x86.R9, x86.RSI, []byte{0x41, 0x8B, 0x34, 0x31}}, - {x86.RSI, 2, x86.R9, x86.RSI, []byte{0x66, 0x41, 0x8B, 0x34, 0x31}}, - {x86.RSI, 1, x86.R9, x86.RSI, []byte{0x41, 0x8A, 0x34, 0x31}}, - {x86.RBP, 8, x86.R10, x86.RBP, []byte{0x49, 0x8B, 0x2C, 0x2A}}, - {x86.RBP, 4, x86.R10, x86.RBP, []byte{0x41, 0x8B, 0x2C, 0x2A}}, - {x86.RBP, 2, x86.R10, x86.RBP, []byte{0x66, 0x41, 0x8B, 0x2C, 0x2A}}, - {x86.RBP, 1, x86.R10, x86.RBP, []byte{0x41, 0x8A, 0x2C, 0x2A}}, - {x86.RSP, 8, x86.R11, x86.RSP, []byte{0x4A, 0x8B, 0x24, 0x1C}}, - {x86.RSP, 4, x86.R11, x86.RSP, []byte{0x42, 0x8B, 0x24, 0x1C}}, - {x86.RSP, 2, x86.R11, x86.RSP, []byte{0x66, 0x42, 0x8B, 0x24, 0x1C}}, - {x86.RSP, 1, x86.R11, x86.RSP, []byte{0x42, 0x8A, 0x24, 0x1C}}, - {x86.RBX, 8, x86.R12, x86.RBX, []byte{0x49, 0x8B, 0x1C, 0x1C}}, - {x86.RBX, 4, x86.R12, x86.RBX, []byte{0x41, 0x8B, 0x1C, 0x1C}}, - {x86.RBX, 2, x86.R12, x86.RBX, []byte{0x66, 0x41, 0x8B, 0x1C, 0x1C}}, - {x86.RBX, 1, x86.R12, x86.RBX, []byte{0x41, 0x8A, 0x1C, 0x1C}}, - {x86.RDX, 8, x86.R13, x86.RDX, []byte{0x49, 0x8B, 0x54, 0x15, 0x00}}, - {x86.RDX, 4, x86.R13, x86.RDX, []byte{0x41, 0x8B, 0x54, 0x15, 0x00}}, - {x86.RDX, 2, x86.R13, x86.RDX, []byte{0x66, 0x41, 0x8B, 0x54, 0x15, 0x00}}, - {x86.RDX, 1, x86.R13, x86.RDX, []byte{0x41, 0x8A, 0x54, 0x15, 0x00}}, - {x86.RCX, 8, x86.R14, x86.RCX, []byte{0x49, 0x8B, 0x0C, 0x0E}}, - {x86.RCX, 4, x86.R14, x86.RCX, []byte{0x41, 0x8B, 0x0C, 0x0E}}, - {x86.RCX, 2, x86.R14, x86.RCX, []byte{0x66, 0x41, 0x8B, 0x0C, 0x0E}}, - {x86.RCX, 1, x86.R14, x86.RCX, []byte{0x41, 0x8A, 0x0C, 0x0E}}, - {x86.RAX, 8, x86.R15, x86.RAX, []byte{0x49, 0x8B, 0x04, 0x07}}, - {x86.RAX, 4, x86.R15, x86.RAX, []byte{0x41, 0x8B, 0x04, 0x07}}, - {x86.RAX, 2, x86.R15, x86.RAX, []byte{0x66, 0x41, 0x8B, 0x04, 0x07}}, - {x86.RAX, 1, x86.R15, x86.RAX, []byte{0x41, 0x8A, 0x04, 0x07}}, + {x86.R15, 8, x86.R0, x86.R15, []byte{0x4E, 0x8B, 0x3C, 0x38}}, + {x86.R15, 4, x86.R0, x86.R15, []byte{0x46, 0x8B, 0x3C, 0x38}}, + {x86.R15, 2, x86.R0, x86.R15, []byte{0x66, 0x46, 0x8B, 0x3C, 0x38}}, + {x86.R15, 1, x86.R0, x86.R15, []byte{0x46, 0x8A, 0x3C, 0x38}}, + {x86.R14, 8, x86.R1, x86.R14, []byte{0x4E, 0x8B, 0x34, 0x31}}, + {x86.R14, 4, x86.R1, x86.R14, []byte{0x46, 0x8B, 0x34, 0x31}}, + {x86.R14, 2, x86.R1, x86.R14, []byte{0x66, 0x46, 0x8B, 0x34, 0x31}}, + {x86.R14, 1, x86.R1, x86.R14, []byte{0x46, 0x8A, 0x34, 0x31}}, + {x86.R13, 8, x86.R2, x86.R13, []byte{0x4E, 0x8B, 0x2C, 0x2A}}, + {x86.R13, 4, x86.R2, x86.R13, []byte{0x46, 0x8B, 0x2C, 0x2A}}, + {x86.R13, 2, x86.R2, x86.R13, []byte{0x66, 0x46, 0x8B, 0x2C, 0x2A}}, + {x86.R13, 1, x86.R2, x86.R13, []byte{0x46, 0x8A, 0x2C, 0x2A}}, + {x86.R12, 8, x86.R3, x86.R12, []byte{0x4E, 0x8B, 0x24, 0x23}}, + {x86.R12, 4, x86.R3, x86.R12, []byte{0x46, 0x8B, 0x24, 0x23}}, + {x86.R12, 2, x86.R3, x86.R12, []byte{0x66, 0x46, 0x8B, 0x24, 0x23}}, + {x86.R12, 1, x86.R3, x86.R12, []byte{0x46, 0x8A, 0x24, 0x23}}, + {x86.R11, 8, x86.SP, x86.R11, []byte{0x4E, 0x8B, 0x1C, 0x1C}}, + {x86.R11, 4, x86.SP, x86.R11, []byte{0x46, 0x8B, 0x1C, 0x1C}}, + {x86.R11, 2, x86.SP, x86.R11, []byte{0x66, 0x46, 0x8B, 0x1C, 0x1C}}, + {x86.R11, 1, x86.SP, x86.R11, []byte{0x46, 0x8A, 0x1C, 0x1C}}, + {x86.R10, 8, x86.R5, x86.R10, []byte{0x4E, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R10, 4, x86.R5, x86.R10, []byte{0x46, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R10, 2, x86.R5, x86.R10, []byte{0x66, 0x46, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R10, 1, x86.R5, x86.R10, []byte{0x46, 0x8A, 0x54, 0x15, 0x00}}, + {x86.R9, 8, x86.R6, x86.R9, []byte{0x4E, 0x8B, 0x0C, 0x0E}}, + {x86.R9, 4, x86.R6, x86.R9, []byte{0x46, 0x8B, 0x0C, 0x0E}}, + {x86.R9, 2, x86.R6, x86.R9, []byte{0x66, 0x46, 0x8B, 0x0C, 0x0E}}, + {x86.R9, 1, x86.R6, x86.R9, []byte{0x46, 0x8A, 0x0C, 0x0E}}, + {x86.R8, 8, x86.R7, x86.R8, []byte{0x4E, 0x8B, 0x04, 0x07}}, + {x86.R8, 4, x86.R7, x86.R8, []byte{0x46, 0x8B, 0x04, 0x07}}, + {x86.R8, 2, x86.R7, x86.R8, []byte{0x66, 0x46, 0x8B, 0x04, 0x07}}, + {x86.R8, 1, x86.R7, x86.R8, []byte{0x46, 0x8A, 0x04, 0x07}}, + {x86.R7, 8, x86.R8, x86.R7, []byte{0x49, 0x8B, 0x3C, 0x38}}, + {x86.R7, 4, x86.R8, x86.R7, []byte{0x41, 0x8B, 0x3C, 0x38}}, + {x86.R7, 2, x86.R8, x86.R7, []byte{0x66, 0x41, 0x8B, 0x3C, 0x38}}, + {x86.R7, 1, x86.R8, x86.R7, []byte{0x41, 0x8A, 0x3C, 0x38}}, + {x86.R6, 8, x86.R9, x86.R6, []byte{0x49, 0x8B, 0x34, 0x31}}, + {x86.R6, 4, x86.R9, x86.R6, []byte{0x41, 0x8B, 0x34, 0x31}}, + {x86.R6, 2, x86.R9, x86.R6, []byte{0x66, 0x41, 0x8B, 0x34, 0x31}}, + {x86.R6, 1, x86.R9, x86.R6, []byte{0x41, 0x8A, 0x34, 0x31}}, + {x86.R5, 8, x86.R10, x86.R5, []byte{0x49, 0x8B, 0x2C, 0x2A}}, + {x86.R5, 4, x86.R10, x86.R5, []byte{0x41, 0x8B, 0x2C, 0x2A}}, + {x86.R5, 2, x86.R10, x86.R5, []byte{0x66, 0x41, 0x8B, 0x2C, 0x2A}}, + {x86.R5, 1, x86.R10, x86.R5, []byte{0x41, 0x8A, 0x2C, 0x2A}}, + {x86.SP, 8, x86.R11, x86.SP, []byte{0x4A, 0x8B, 0x24, 0x1C}}, + {x86.SP, 4, x86.R11, x86.SP, []byte{0x42, 0x8B, 0x24, 0x1C}}, + {x86.SP, 2, x86.R11, x86.SP, []byte{0x66, 0x42, 0x8B, 0x24, 0x1C}}, + {x86.SP, 1, x86.R11, x86.SP, []byte{0x42, 0x8A, 0x24, 0x1C}}, + {x86.R3, 8, x86.R12, x86.R3, []byte{0x49, 0x8B, 0x1C, 0x1C}}, + {x86.R3, 4, x86.R12, x86.R3, []byte{0x41, 0x8B, 0x1C, 0x1C}}, + {x86.R3, 2, x86.R12, x86.R3, []byte{0x66, 0x41, 0x8B, 0x1C, 0x1C}}, + {x86.R3, 1, x86.R12, x86.R3, []byte{0x41, 0x8A, 0x1C, 0x1C}}, + {x86.R2, 8, x86.R13, x86.R2, []byte{0x49, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R2, 4, x86.R13, x86.R2, []byte{0x41, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R2, 2, x86.R13, x86.R2, []byte{0x66, 0x41, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R2, 1, x86.R13, x86.R2, []byte{0x41, 0x8A, 0x54, 0x15, 0x00}}, + {x86.R1, 8, x86.R14, x86.R1, []byte{0x49, 0x8B, 0x0C, 0x0E}}, + {x86.R1, 4, x86.R14, x86.R1, []byte{0x41, 0x8B, 0x0C, 0x0E}}, + {x86.R1, 2, x86.R14, x86.R1, []byte{0x66, 0x41, 0x8B, 0x0C, 0x0E}}, + {x86.R1, 1, x86.R14, x86.R1, []byte{0x41, 0x8A, 0x0C, 0x0E}}, + {x86.R0, 8, x86.R15, x86.R0, []byte{0x49, 0x8B, 0x04, 0x07}}, + {x86.R0, 4, x86.R15, x86.R0, []byte{0x41, 0x8B, 0x04, 0x07}}, + {x86.R0, 2, x86.R15, x86.R0, []byte{0x66, 0x41, 0x8B, 0x04, 0x07}}, + {x86.R0, 1, x86.R15, x86.R0, []byte{0x41, 0x8A, 0x04, 0x07}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Load_test.go b/src/x86/Load_test.go index 3d5240b..006abad 100644 --- a/src/x86/Load_test.go +++ b/src/x86/Load_test.go @@ -17,136 +17,136 @@ func TestLoadRegister(t *testing.T) { Code []byte }{ // No offset - {x86.RAX, x86.R15, 0, 8, []byte{0x49, 0x8B, 0x07}}, - {x86.RAX, x86.R15, 0, 4, []byte{0x41, 0x8B, 0x07}}, - {x86.RAX, x86.R15, 0, 2, []byte{0x66, 0x41, 0x8B, 0x07}}, - {x86.RAX, x86.R15, 0, 1, []byte{0x41, 0x8A, 0x07}}, - {x86.RCX, x86.R14, 0, 8, []byte{0x49, 0x8B, 0x0E}}, - {x86.RCX, x86.R14, 0, 4, []byte{0x41, 0x8B, 0x0E}}, - {x86.RCX, x86.R14, 0, 2, []byte{0x66, 0x41, 0x8B, 0x0E}}, - {x86.RCX, x86.R14, 0, 1, []byte{0x41, 0x8A, 0x0E}}, - {x86.RDX, x86.R13, 0, 8, []byte{0x49, 0x8B, 0x55, 0x00}}, - {x86.RDX, x86.R13, 0, 4, []byte{0x41, 0x8B, 0x55, 0x00}}, - {x86.RDX, x86.R13, 0, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x00}}, - {x86.RDX, x86.R13, 0, 1, []byte{0x41, 0x8A, 0x55, 0x00}}, - {x86.RBX, x86.R12, 0, 8, []byte{0x49, 0x8B, 0x1C, 0x24}}, - {x86.RBX, x86.R12, 0, 4, []byte{0x41, 0x8B, 0x1C, 0x24}}, - {x86.RBX, x86.R12, 0, 2, []byte{0x66, 0x41, 0x8B, 0x1C, 0x24}}, - {x86.RBX, x86.R12, 0, 1, []byte{0x41, 0x8A, 0x1C, 0x24}}, - {x86.RSP, x86.R11, 0, 8, []byte{0x49, 0x8B, 0x23}}, - {x86.RSP, x86.R11, 0, 4, []byte{0x41, 0x8B, 0x23}}, - {x86.RSP, x86.R11, 0, 2, []byte{0x66, 0x41, 0x8B, 0x23}}, - {x86.RSP, x86.R11, 0, 1, []byte{0x41, 0x8A, 0x23}}, - {x86.RBP, x86.R10, 0, 8, []byte{0x49, 0x8B, 0x2A}}, - {x86.RBP, x86.R10, 0, 4, []byte{0x41, 0x8B, 0x2A}}, - {x86.RBP, x86.R10, 0, 2, []byte{0x66, 0x41, 0x8B, 0x2A}}, - {x86.RBP, x86.R10, 0, 1, []byte{0x41, 0x8A, 0x2A}}, - {x86.RSI, x86.R9, 0, 8, []byte{0x49, 0x8B, 0x31}}, - {x86.RSI, x86.R9, 0, 4, []byte{0x41, 0x8B, 0x31}}, - {x86.RSI, x86.R9, 0, 2, []byte{0x66, 0x41, 0x8B, 0x31}}, - {x86.RSI, x86.R9, 0, 1, []byte{0x41, 0x8A, 0x31}}, - {x86.RDI, x86.R8, 0, 8, []byte{0x49, 0x8B, 0x38}}, - {x86.RDI, x86.R8, 0, 4, []byte{0x41, 0x8B, 0x38}}, - {x86.RDI, x86.R8, 0, 2, []byte{0x66, 0x41, 0x8B, 0x38}}, - {x86.RDI, x86.R8, 0, 1, []byte{0x41, 0x8A, 0x38}}, - {x86.R8, x86.RDI, 0, 8, []byte{0x4C, 0x8B, 0x07}}, - {x86.R8, x86.RDI, 0, 4, []byte{0x44, 0x8B, 0x07}}, - {x86.R8, x86.RDI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x07}}, - {x86.R8, x86.RDI, 0, 1, []byte{0x44, 0x8A, 0x07}}, - {x86.R9, x86.RSI, 0, 8, []byte{0x4C, 0x8B, 0x0E}}, - {x86.R9, x86.RSI, 0, 4, []byte{0x44, 0x8B, 0x0E}}, - {x86.R9, x86.RSI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x0E}}, - {x86.R9, x86.RSI, 0, 1, []byte{0x44, 0x8A, 0x0E}}, - {x86.R10, x86.RBP, 0, 8, []byte{0x4C, 0x8B, 0x55, 0x00}}, - {x86.R10, x86.RBP, 0, 4, []byte{0x44, 0x8B, 0x55, 0x00}}, - {x86.R10, x86.RBP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x00}}, - {x86.R10, x86.RBP, 0, 1, []byte{0x44, 0x8A, 0x55, 0x00}}, - {x86.R11, x86.RSP, 0, 8, []byte{0x4C, 0x8B, 0x1C, 0x24}}, - {x86.R11, x86.RSP, 0, 4, []byte{0x44, 0x8B, 0x1C, 0x24}}, - {x86.R11, x86.RSP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x1C, 0x24}}, - {x86.R11, x86.RSP, 0, 1, []byte{0x44, 0x8A, 0x1C, 0x24}}, - {x86.R12, x86.RBX, 0, 8, []byte{0x4C, 0x8B, 0x23}}, - {x86.R12, x86.RBX, 0, 4, []byte{0x44, 0x8B, 0x23}}, - {x86.R12, x86.RBX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x23}}, - {x86.R12, x86.RBX, 0, 1, []byte{0x44, 0x8A, 0x23}}, - {x86.R13, x86.RDX, 0, 8, []byte{0x4C, 0x8B, 0x2A}}, - {x86.R13, x86.RDX, 0, 4, []byte{0x44, 0x8B, 0x2A}}, - {x86.R13, x86.RDX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x2A}}, - {x86.R13, x86.RDX, 0, 1, []byte{0x44, 0x8A, 0x2A}}, - {x86.R14, x86.RCX, 0, 8, []byte{0x4C, 0x8B, 0x31}}, - {x86.R14, x86.RCX, 0, 4, []byte{0x44, 0x8B, 0x31}}, - {x86.R14, x86.RCX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x31}}, - {x86.R14, x86.RCX, 0, 1, []byte{0x44, 0x8A, 0x31}}, - {x86.R15, x86.RAX, 0, 8, []byte{0x4C, 0x8B, 0x38}}, - {x86.R15, x86.RAX, 0, 4, []byte{0x44, 0x8B, 0x38}}, - {x86.R15, x86.RAX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x38}}, - {x86.R15, x86.RAX, 0, 1, []byte{0x44, 0x8A, 0x38}}, + {x86.R0, x86.R15, 0, 8, []byte{0x49, 0x8B, 0x07}}, + {x86.R0, x86.R15, 0, 4, []byte{0x41, 0x8B, 0x07}}, + {x86.R0, x86.R15, 0, 2, []byte{0x66, 0x41, 0x8B, 0x07}}, + {x86.R0, x86.R15, 0, 1, []byte{0x41, 0x8A, 0x07}}, + {x86.R1, x86.R14, 0, 8, []byte{0x49, 0x8B, 0x0E}}, + {x86.R1, x86.R14, 0, 4, []byte{0x41, 0x8B, 0x0E}}, + {x86.R1, x86.R14, 0, 2, []byte{0x66, 0x41, 0x8B, 0x0E}}, + {x86.R1, x86.R14, 0, 1, []byte{0x41, 0x8A, 0x0E}}, + {x86.R2, x86.R13, 0, 8, []byte{0x49, 0x8B, 0x55, 0x00}}, + {x86.R2, x86.R13, 0, 4, []byte{0x41, 0x8B, 0x55, 0x00}}, + {x86.R2, x86.R13, 0, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x00}}, + {x86.R2, x86.R13, 0, 1, []byte{0x41, 0x8A, 0x55, 0x00}}, + {x86.R3, x86.R12, 0, 8, []byte{0x49, 0x8B, 0x1C, 0x24}}, + {x86.R3, x86.R12, 0, 4, []byte{0x41, 0x8B, 0x1C, 0x24}}, + {x86.R3, x86.R12, 0, 2, []byte{0x66, 0x41, 0x8B, 0x1C, 0x24}}, + {x86.R3, x86.R12, 0, 1, []byte{0x41, 0x8A, 0x1C, 0x24}}, + {x86.SP, x86.R11, 0, 8, []byte{0x49, 0x8B, 0x23}}, + {x86.SP, x86.R11, 0, 4, []byte{0x41, 0x8B, 0x23}}, + {x86.SP, x86.R11, 0, 2, []byte{0x66, 0x41, 0x8B, 0x23}}, + {x86.SP, x86.R11, 0, 1, []byte{0x41, 0x8A, 0x23}}, + {x86.R5, x86.R10, 0, 8, []byte{0x49, 0x8B, 0x2A}}, + {x86.R5, x86.R10, 0, 4, []byte{0x41, 0x8B, 0x2A}}, + {x86.R5, x86.R10, 0, 2, []byte{0x66, 0x41, 0x8B, 0x2A}}, + {x86.R5, x86.R10, 0, 1, []byte{0x41, 0x8A, 0x2A}}, + {x86.R6, x86.R9, 0, 8, []byte{0x49, 0x8B, 0x31}}, + {x86.R6, x86.R9, 0, 4, []byte{0x41, 0x8B, 0x31}}, + {x86.R6, x86.R9, 0, 2, []byte{0x66, 0x41, 0x8B, 0x31}}, + {x86.R6, x86.R9, 0, 1, []byte{0x41, 0x8A, 0x31}}, + {x86.R7, x86.R8, 0, 8, []byte{0x49, 0x8B, 0x38}}, + {x86.R7, x86.R8, 0, 4, []byte{0x41, 0x8B, 0x38}}, + {x86.R7, x86.R8, 0, 2, []byte{0x66, 0x41, 0x8B, 0x38}}, + {x86.R7, x86.R8, 0, 1, []byte{0x41, 0x8A, 0x38}}, + {x86.R8, x86.R7, 0, 8, []byte{0x4C, 0x8B, 0x07}}, + {x86.R8, x86.R7, 0, 4, []byte{0x44, 0x8B, 0x07}}, + {x86.R8, x86.R7, 0, 2, []byte{0x66, 0x44, 0x8B, 0x07}}, + {x86.R8, x86.R7, 0, 1, []byte{0x44, 0x8A, 0x07}}, + {x86.R9, x86.R6, 0, 8, []byte{0x4C, 0x8B, 0x0E}}, + {x86.R9, x86.R6, 0, 4, []byte{0x44, 0x8B, 0x0E}}, + {x86.R9, x86.R6, 0, 2, []byte{0x66, 0x44, 0x8B, 0x0E}}, + {x86.R9, x86.R6, 0, 1, []byte{0x44, 0x8A, 0x0E}}, + {x86.R10, x86.R5, 0, 8, []byte{0x4C, 0x8B, 0x55, 0x00}}, + {x86.R10, x86.R5, 0, 4, []byte{0x44, 0x8B, 0x55, 0x00}}, + {x86.R10, x86.R5, 0, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x00}}, + {x86.R10, x86.R5, 0, 1, []byte{0x44, 0x8A, 0x55, 0x00}}, + {x86.R11, x86.SP, 0, 8, []byte{0x4C, 0x8B, 0x1C, 0x24}}, + {x86.R11, x86.SP, 0, 4, []byte{0x44, 0x8B, 0x1C, 0x24}}, + {x86.R11, x86.SP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x1C, 0x24}}, + {x86.R11, x86.SP, 0, 1, []byte{0x44, 0x8A, 0x1C, 0x24}}, + {x86.R12, x86.R3, 0, 8, []byte{0x4C, 0x8B, 0x23}}, + {x86.R12, x86.R3, 0, 4, []byte{0x44, 0x8B, 0x23}}, + {x86.R12, x86.R3, 0, 2, []byte{0x66, 0x44, 0x8B, 0x23}}, + {x86.R12, x86.R3, 0, 1, []byte{0x44, 0x8A, 0x23}}, + {x86.R13, x86.R2, 0, 8, []byte{0x4C, 0x8B, 0x2A}}, + {x86.R13, x86.R2, 0, 4, []byte{0x44, 0x8B, 0x2A}}, + {x86.R13, x86.R2, 0, 2, []byte{0x66, 0x44, 0x8B, 0x2A}}, + {x86.R13, x86.R2, 0, 1, []byte{0x44, 0x8A, 0x2A}}, + {x86.R14, x86.R1, 0, 8, []byte{0x4C, 0x8B, 0x31}}, + {x86.R14, x86.R1, 0, 4, []byte{0x44, 0x8B, 0x31}}, + {x86.R14, x86.R1, 0, 2, []byte{0x66, 0x44, 0x8B, 0x31}}, + {x86.R14, x86.R1, 0, 1, []byte{0x44, 0x8A, 0x31}}, + {x86.R15, x86.R0, 0, 8, []byte{0x4C, 0x8B, 0x38}}, + {x86.R15, x86.R0, 0, 4, []byte{0x44, 0x8B, 0x38}}, + {x86.R15, x86.R0, 0, 2, []byte{0x66, 0x44, 0x8B, 0x38}}, + {x86.R15, x86.R0, 0, 1, []byte{0x44, 0x8A, 0x38}}, // Offset of 1 - {x86.RAX, x86.R15, 1, 8, []byte{0x49, 0x8B, 0x47, 0x01}}, - {x86.RAX, x86.R15, 1, 4, []byte{0x41, 0x8B, 0x47, 0x01}}, - {x86.RAX, x86.R15, 1, 2, []byte{0x66, 0x41, 0x8B, 0x47, 0x01}}, - {x86.RAX, x86.R15, 1, 1, []byte{0x41, 0x8A, 0x47, 0x01}}, - {x86.RCX, x86.R14, 1, 8, []byte{0x49, 0x8B, 0x4E, 0x01}}, - {x86.RCX, x86.R14, 1, 4, []byte{0x41, 0x8B, 0x4E, 0x01}}, - {x86.RCX, x86.R14, 1, 2, []byte{0x66, 0x41, 0x8B, 0x4E, 0x01}}, - {x86.RCX, x86.R14, 1, 1, []byte{0x41, 0x8A, 0x4E, 0x01}}, - {x86.RDX, x86.R13, 1, 8, []byte{0x49, 0x8B, 0x55, 0x01}}, - {x86.RDX, x86.R13, 1, 4, []byte{0x41, 0x8B, 0x55, 0x01}}, - {x86.RDX, x86.R13, 1, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x01}}, - {x86.RDX, x86.R13, 1, 1, []byte{0x41, 0x8A, 0x55, 0x01}}, - {x86.RBX, x86.R12, 1, 8, []byte{0x49, 0x8B, 0x5C, 0x24, 0x01}}, - {x86.RBX, x86.R12, 1, 4, []byte{0x41, 0x8B, 0x5C, 0x24, 0x01}}, - {x86.RBX, x86.R12, 1, 2, []byte{0x66, 0x41, 0x8B, 0x5C, 0x24, 0x01}}, - {x86.RBX, x86.R12, 1, 1, []byte{0x41, 0x8A, 0x5C, 0x24, 0x01}}, - {x86.RSP, x86.R11, 1, 8, []byte{0x49, 0x8B, 0x63, 0x01}}, - {x86.RSP, x86.R11, 1, 4, []byte{0x41, 0x8B, 0x63, 0x01}}, - {x86.RSP, x86.R11, 1, 2, []byte{0x66, 0x41, 0x8B, 0x63, 0x01}}, - {x86.RSP, x86.R11, 1, 1, []byte{0x41, 0x8A, 0x63, 0x01}}, - {x86.RBP, x86.R10, 1, 8, []byte{0x49, 0x8B, 0x6A, 0x01}}, - {x86.RBP, x86.R10, 1, 4, []byte{0x41, 0x8B, 0x6A, 0x01}}, - {x86.RBP, x86.R10, 1, 2, []byte{0x66, 0x41, 0x8B, 0x6A, 0x01}}, - {x86.RBP, x86.R10, 1, 1, []byte{0x41, 0x8A, 0x6A, 0x01}}, - {x86.RSI, x86.R9, 1, 8, []byte{0x49, 0x8B, 0x71, 0x01}}, - {x86.RSI, x86.R9, 1, 4, []byte{0x41, 0x8B, 0x71, 0x01}}, - {x86.RSI, x86.R9, 1, 2, []byte{0x66, 0x41, 0x8B, 0x71, 0x01}}, - {x86.RSI, x86.R9, 1, 1, []byte{0x41, 0x8A, 0x71, 0x01}}, - {x86.RDI, x86.R8, 1, 8, []byte{0x49, 0x8B, 0x78, 0x01}}, - {x86.RDI, x86.R8, 1, 4, []byte{0x41, 0x8B, 0x78, 0x01}}, - {x86.RDI, x86.R8, 1, 2, []byte{0x66, 0x41, 0x8B, 0x78, 0x01}}, - {x86.RDI, x86.R8, 1, 1, []byte{0x41, 0x8A, 0x78, 0x01}}, - {x86.R8, x86.RDI, 1, 8, []byte{0x4C, 0x8B, 0x47, 0x01}}, - {x86.R8, x86.RDI, 1, 4, []byte{0x44, 0x8B, 0x47, 0x01}}, - {x86.R8, x86.RDI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x47, 0x01}}, - {x86.R8, x86.RDI, 1, 1, []byte{0x44, 0x8A, 0x47, 0x01}}, - {x86.R9, x86.RSI, 1, 8, []byte{0x4C, 0x8B, 0x4E, 0x01}}, - {x86.R9, x86.RSI, 1, 4, []byte{0x44, 0x8B, 0x4E, 0x01}}, - {x86.R9, x86.RSI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x4E, 0x01}}, - {x86.R9, x86.RSI, 1, 1, []byte{0x44, 0x8A, 0x4E, 0x01}}, - {x86.R10, x86.RBP, 1, 8, []byte{0x4C, 0x8B, 0x55, 0x01}}, - {x86.R10, x86.RBP, 1, 4, []byte{0x44, 0x8B, 0x55, 0x01}}, - {x86.R10, x86.RBP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x01}}, - {x86.R10, x86.RBP, 1, 1, []byte{0x44, 0x8A, 0x55, 0x01}}, - {x86.R11, x86.RSP, 1, 8, []byte{0x4C, 0x8B, 0x5C, 0x24, 0x01}}, - {x86.R11, x86.RSP, 1, 4, []byte{0x44, 0x8B, 0x5C, 0x24, 0x01}}, - {x86.R11, x86.RSP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x5C, 0x24, 0x01}}, - {x86.R11, x86.RSP, 1, 1, []byte{0x44, 0x8A, 0x5C, 0x24, 0x01}}, - {x86.R12, x86.RBX, 1, 8, []byte{0x4C, 0x8B, 0x63, 0x01}}, - {x86.R12, x86.RBX, 1, 4, []byte{0x44, 0x8B, 0x63, 0x01}}, - {x86.R12, x86.RBX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x63, 0x01}}, - {x86.R12, x86.RBX, 1, 1, []byte{0x44, 0x8A, 0x63, 0x01}}, - {x86.R13, x86.RDX, 1, 8, []byte{0x4C, 0x8B, 0x6A, 0x01}}, - {x86.R13, x86.RDX, 1, 4, []byte{0x44, 0x8B, 0x6A, 0x01}}, - {x86.R13, x86.RDX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x6A, 0x01}}, - {x86.R13, x86.RDX, 1, 1, []byte{0x44, 0x8A, 0x6A, 0x01}}, - {x86.R14, x86.RCX, 1, 8, []byte{0x4C, 0x8B, 0x71, 0x01}}, - {x86.R14, x86.RCX, 1, 4, []byte{0x44, 0x8B, 0x71, 0x01}}, - {x86.R14, x86.RCX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x71, 0x01}}, - {x86.R14, x86.RCX, 1, 1, []byte{0x44, 0x8A, 0x71, 0x01}}, - {x86.R15, x86.RAX, 1, 8, []byte{0x4C, 0x8B, 0x78, 0x01}}, - {x86.R15, x86.RAX, 1, 4, []byte{0x44, 0x8B, 0x78, 0x01}}, - {x86.R15, x86.RAX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x78, 0x01}}, - {x86.R15, x86.RAX, 1, 1, []byte{0x44, 0x8A, 0x78, 0x01}}, + {x86.R0, x86.R15, 1, 8, []byte{0x49, 0x8B, 0x47, 0x01}}, + {x86.R0, x86.R15, 1, 4, []byte{0x41, 0x8B, 0x47, 0x01}}, + {x86.R0, x86.R15, 1, 2, []byte{0x66, 0x41, 0x8B, 0x47, 0x01}}, + {x86.R0, x86.R15, 1, 1, []byte{0x41, 0x8A, 0x47, 0x01}}, + {x86.R1, x86.R14, 1, 8, []byte{0x49, 0x8B, 0x4E, 0x01}}, + {x86.R1, x86.R14, 1, 4, []byte{0x41, 0x8B, 0x4E, 0x01}}, + {x86.R1, x86.R14, 1, 2, []byte{0x66, 0x41, 0x8B, 0x4E, 0x01}}, + {x86.R1, x86.R14, 1, 1, []byte{0x41, 0x8A, 0x4E, 0x01}}, + {x86.R2, x86.R13, 1, 8, []byte{0x49, 0x8B, 0x55, 0x01}}, + {x86.R2, x86.R13, 1, 4, []byte{0x41, 0x8B, 0x55, 0x01}}, + {x86.R2, x86.R13, 1, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x01}}, + {x86.R2, x86.R13, 1, 1, []byte{0x41, 0x8A, 0x55, 0x01}}, + {x86.R3, x86.R12, 1, 8, []byte{0x49, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R3, x86.R12, 1, 4, []byte{0x41, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R3, x86.R12, 1, 2, []byte{0x66, 0x41, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R3, x86.R12, 1, 1, []byte{0x41, 0x8A, 0x5C, 0x24, 0x01}}, + {x86.SP, x86.R11, 1, 8, []byte{0x49, 0x8B, 0x63, 0x01}}, + {x86.SP, x86.R11, 1, 4, []byte{0x41, 0x8B, 0x63, 0x01}}, + {x86.SP, x86.R11, 1, 2, []byte{0x66, 0x41, 0x8B, 0x63, 0x01}}, + {x86.SP, x86.R11, 1, 1, []byte{0x41, 0x8A, 0x63, 0x01}}, + {x86.R5, x86.R10, 1, 8, []byte{0x49, 0x8B, 0x6A, 0x01}}, + {x86.R5, x86.R10, 1, 4, []byte{0x41, 0x8B, 0x6A, 0x01}}, + {x86.R5, x86.R10, 1, 2, []byte{0x66, 0x41, 0x8B, 0x6A, 0x01}}, + {x86.R5, x86.R10, 1, 1, []byte{0x41, 0x8A, 0x6A, 0x01}}, + {x86.R6, x86.R9, 1, 8, []byte{0x49, 0x8B, 0x71, 0x01}}, + {x86.R6, x86.R9, 1, 4, []byte{0x41, 0x8B, 0x71, 0x01}}, + {x86.R6, x86.R9, 1, 2, []byte{0x66, 0x41, 0x8B, 0x71, 0x01}}, + {x86.R6, x86.R9, 1, 1, []byte{0x41, 0x8A, 0x71, 0x01}}, + {x86.R7, x86.R8, 1, 8, []byte{0x49, 0x8B, 0x78, 0x01}}, + {x86.R7, x86.R8, 1, 4, []byte{0x41, 0x8B, 0x78, 0x01}}, + {x86.R7, x86.R8, 1, 2, []byte{0x66, 0x41, 0x8B, 0x78, 0x01}}, + {x86.R7, x86.R8, 1, 1, []byte{0x41, 0x8A, 0x78, 0x01}}, + {x86.R8, x86.R7, 1, 8, []byte{0x4C, 0x8B, 0x47, 0x01}}, + {x86.R8, x86.R7, 1, 4, []byte{0x44, 0x8B, 0x47, 0x01}}, + {x86.R8, x86.R7, 1, 2, []byte{0x66, 0x44, 0x8B, 0x47, 0x01}}, + {x86.R8, x86.R7, 1, 1, []byte{0x44, 0x8A, 0x47, 0x01}}, + {x86.R9, x86.R6, 1, 8, []byte{0x4C, 0x8B, 0x4E, 0x01}}, + {x86.R9, x86.R6, 1, 4, []byte{0x44, 0x8B, 0x4E, 0x01}}, + {x86.R9, x86.R6, 1, 2, []byte{0x66, 0x44, 0x8B, 0x4E, 0x01}}, + {x86.R9, x86.R6, 1, 1, []byte{0x44, 0x8A, 0x4E, 0x01}}, + {x86.R10, x86.R5, 1, 8, []byte{0x4C, 0x8B, 0x55, 0x01}}, + {x86.R10, x86.R5, 1, 4, []byte{0x44, 0x8B, 0x55, 0x01}}, + {x86.R10, x86.R5, 1, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x01}}, + {x86.R10, x86.R5, 1, 1, []byte{0x44, 0x8A, 0x55, 0x01}}, + {x86.R11, x86.SP, 1, 8, []byte{0x4C, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R11, x86.SP, 1, 4, []byte{0x44, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R11, x86.SP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x5C, 0x24, 0x01}}, + {x86.R11, x86.SP, 1, 1, []byte{0x44, 0x8A, 0x5C, 0x24, 0x01}}, + {x86.R12, x86.R3, 1, 8, []byte{0x4C, 0x8B, 0x63, 0x01}}, + {x86.R12, x86.R3, 1, 4, []byte{0x44, 0x8B, 0x63, 0x01}}, + {x86.R12, x86.R3, 1, 2, []byte{0x66, 0x44, 0x8B, 0x63, 0x01}}, + {x86.R12, x86.R3, 1, 1, []byte{0x44, 0x8A, 0x63, 0x01}}, + {x86.R13, x86.R2, 1, 8, []byte{0x4C, 0x8B, 0x6A, 0x01}}, + {x86.R13, x86.R2, 1, 4, []byte{0x44, 0x8B, 0x6A, 0x01}}, + {x86.R13, x86.R2, 1, 2, []byte{0x66, 0x44, 0x8B, 0x6A, 0x01}}, + {x86.R13, x86.R2, 1, 1, []byte{0x44, 0x8A, 0x6A, 0x01}}, + {x86.R14, x86.R1, 1, 8, []byte{0x4C, 0x8B, 0x71, 0x01}}, + {x86.R14, x86.R1, 1, 4, []byte{0x44, 0x8B, 0x71, 0x01}}, + {x86.R14, x86.R1, 1, 2, []byte{0x66, 0x44, 0x8B, 0x71, 0x01}}, + {x86.R14, x86.R1, 1, 1, []byte{0x44, 0x8A, 0x71, 0x01}}, + {x86.R15, x86.R0, 1, 8, []byte{0x4C, 0x8B, 0x78, 0x01}}, + {x86.R15, x86.R0, 1, 4, []byte{0x44, 0x8B, 0x78, 0x01}}, + {x86.R15, x86.R0, 1, 2, []byte{0x66, 0x44, 0x8B, 0x78, 0x01}}, + {x86.R15, x86.R0, 1, 1, []byte{0x44, 0x8A, 0x78, 0x01}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Move_test.go b/src/x86/Move_test.go index 9ac8318..803a418 100644 --- a/src/x86/Move_test.go +++ b/src/x86/Move_test.go @@ -15,14 +15,14 @@ func TestMoveRegisterNumber(t *testing.T) { Code []byte }{ // 32 bits - {x86.RAX, 0x7FFFFFFF, []byte{0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFF, []byte{0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFF, []byte{0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFF, []byte{0xBB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFF, []byte{0xBC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFF, []byte{0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFF, []byte{0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFF, []byte{0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFF, []byte{0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFF, []byte{0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFF, []byte{0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFF, []byte{0xBB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFF, []byte{0xBC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFF, []byte{0xBD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFF, []byte{0xBE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFF, []byte{0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFF, []byte{0x41, 0xB8, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFF, []byte{0x41, 0xB9, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFF, []byte{0x41, 0xBA, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -33,14 +33,14 @@ func TestMoveRegisterNumber(t *testing.T) { {x86.R15, 0x7FFFFFFF, []byte{0x41, 0xBF, 0xFF, 0xFF, 0xFF, 0x7F}}, // 64 bits - {x86.RAX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -51,14 +51,14 @@ func TestMoveRegisterNumber(t *testing.T) { {x86.R15, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, // Negative numbers - {x86.RAX, -1, []byte{0x48, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x86.RCX, -1, []byte{0x48, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x86.RDX, -1, []byte{0x48, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x86.RBX, -1, []byte{0x48, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x86.RSP, -1, []byte{0x48, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x86.RBP, -1, []byte{0x48, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x86.RSI, -1, []byte{0x48, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}}, - {x86.RDI, -1, []byte{0x48, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R0, -1, []byte{0x48, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R1, -1, []byte{0x48, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R2, -1, []byte{0x48, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R3, -1, []byte{0x48, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.SP, -1, []byte{0x48, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R5, -1, []byte{0x48, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R6, -1, []byte{0x48, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}}, + {x86.R7, -1, []byte{0x48, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}}, {x86.R8, -1, []byte{0x49, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}}, {x86.R9, -1, []byte{0x49, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}}, {x86.R10, -1, []byte{0x49, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}}, @@ -82,22 +82,22 @@ func TestMoveRegisterRegister(t *testing.T) { Right cpu.Register Code []byte }{ - {x86.RAX, x86.R15, []byte{0x4C, 0x89, 0xF8}}, - {x86.RCX, x86.R14, []byte{0x4C, 0x89, 0xF1}}, - {x86.RDX, x86.R13, []byte{0x4C, 0x89, 0xEA}}, - {x86.RBX, x86.R12, []byte{0x4C, 0x89, 0xE3}}, - {x86.RSP, x86.R11, []byte{0x4C, 0x89, 0xDC}}, - {x86.RBP, x86.R10, []byte{0x4C, 0x89, 0xD5}}, - {x86.RSI, x86.R9, []byte{0x4C, 0x89, 0xCE}}, - {x86.RDI, x86.R8, []byte{0x4C, 0x89, 0xC7}}, - {x86.R8, x86.RDI, []byte{0x49, 0x89, 0xF8}}, - {x86.R9, x86.RSI, []byte{0x49, 0x89, 0xF1}}, - {x86.R10, x86.RBP, []byte{0x49, 0x89, 0xEA}}, - {x86.R11, x86.RSP, []byte{0x49, 0x89, 0xE3}}, - {x86.R12, x86.RBX, []byte{0x49, 0x89, 0xDC}}, - {x86.R13, x86.RDX, []byte{0x49, 0x89, 0xD5}}, - {x86.R14, x86.RCX, []byte{0x49, 0x89, 0xCE}}, - {x86.R15, x86.RAX, []byte{0x49, 0x89, 0xC7}}, + {x86.R0, x86.R15, []byte{0x4C, 0x89, 0xF8}}, + {x86.R1, x86.R14, []byte{0x4C, 0x89, 0xF1}}, + {x86.R2, x86.R13, []byte{0x4C, 0x89, 0xEA}}, + {x86.R3, x86.R12, []byte{0x4C, 0x89, 0xE3}}, + {x86.SP, x86.R11, []byte{0x4C, 0x89, 0xDC}}, + {x86.R5, x86.R10, []byte{0x4C, 0x89, 0xD5}}, + {x86.R6, x86.R9, []byte{0x4C, 0x89, 0xCE}}, + {x86.R7, x86.R8, []byte{0x4C, 0x89, 0xC7}}, + {x86.R8, x86.R7, []byte{0x49, 0x89, 0xF8}}, + {x86.R9, x86.R6, []byte{0x49, 0x89, 0xF1}}, + {x86.R10, x86.R5, []byte{0x49, 0x89, 0xEA}}, + {x86.R11, x86.SP, []byte{0x49, 0x89, 0xE3}}, + {x86.R12, x86.R3, []byte{0x49, 0x89, 0xDC}}, + {x86.R13, x86.R2, []byte{0x49, 0x89, 0xD5}}, + {x86.R14, x86.R1, []byte{0x49, 0x89, 0xCE}}, + {x86.R15, x86.R0, []byte{0x49, 0x89, 0xC7}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Mul_test.go b/src/x86/Mul_test.go index cfde232..5584ede 100644 --- a/src/x86/Mul_test.go +++ b/src/x86/Mul_test.go @@ -14,14 +14,14 @@ func TestMulRegisterNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0x6B, 0xC0, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0x6B, 0xC9, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0x6B, 0xD2, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0x6B, 0xDB, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0x6B, 0xE4, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0x6B, 0xED, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0x6B, 0xF6, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0x6B, 0xFF, 0x01}}, + {x86.R0, 1, []byte{0x48, 0x6B, 0xC0, 0x01}}, + {x86.R1, 1, []byte{0x48, 0x6B, 0xC9, 0x01}}, + {x86.R2, 1, []byte{0x48, 0x6B, 0xD2, 0x01}}, + {x86.R3, 1, []byte{0x48, 0x6B, 0xDB, 0x01}}, + {x86.SP, 1, []byte{0x48, 0x6B, 0xE4, 0x01}}, + {x86.R5, 1, []byte{0x48, 0x6B, 0xED, 0x01}}, + {x86.R6, 1, []byte{0x48, 0x6B, 0xF6, 0x01}}, + {x86.R7, 1, []byte{0x48, 0x6B, 0xFF, 0x01}}, {x86.R8, 1, []byte{0x4D, 0x6B, 0xC0, 0x01}}, {x86.R9, 1, []byte{0x4D, 0x6B, 0xC9, 0x01}}, {x86.R10, 1, []byte{0x4D, 0x6B, 0xD2, 0x01}}, @@ -31,14 +31,14 @@ func TestMulRegisterNumber(t *testing.T) { {x86.R14, 1, []byte{0x4D, 0x6B, 0xF6, 0x01}}, {x86.R15, 1, []byte{0x4D, 0x6B, 0xFF, 0x01}}, - {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFF, []byte{0x48, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFF, []byte{0x48, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFF, []byte{0x48, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFF, []byte{0x48, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFF, []byte{0x48, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -62,22 +62,22 @@ func TestMulRegisterRegister(t *testing.T) { Right cpu.Register Code []byte }{ - {x86.RAX, x86.R15, []byte{0x49, 0x0F, 0xAF, 0xC7}}, - {x86.RCX, x86.R14, []byte{0x49, 0x0F, 0xAF, 0xCE}}, - {x86.RDX, x86.R13, []byte{0x49, 0x0F, 0xAF, 0xD5}}, - {x86.RBX, x86.R12, []byte{0x49, 0x0F, 0xAF, 0xDC}}, - {x86.RSP, x86.R11, []byte{0x49, 0x0F, 0xAF, 0xE3}}, - {x86.RBP, x86.R10, []byte{0x49, 0x0F, 0xAF, 0xEA}}, - {x86.RSI, x86.R9, []byte{0x49, 0x0F, 0xAF, 0xF1}}, - {x86.RDI, x86.R8, []byte{0x49, 0x0F, 0xAF, 0xF8}}, - {x86.R8, x86.RDI, []byte{0x4C, 0x0F, 0xAF, 0xC7}}, - {x86.R9, x86.RSI, []byte{0x4C, 0x0F, 0xAF, 0xCE}}, - {x86.R10, x86.RBP, []byte{0x4C, 0x0F, 0xAF, 0xD5}}, - {x86.R11, x86.RSP, []byte{0x4C, 0x0F, 0xAF, 0xDC}}, - {x86.R12, x86.RBX, []byte{0x4C, 0x0F, 0xAF, 0xE3}}, - {x86.R13, x86.RDX, []byte{0x4C, 0x0F, 0xAF, 0xEA}}, - {x86.R14, x86.RCX, []byte{0x4C, 0x0F, 0xAF, 0xF1}}, - {x86.R15, x86.RAX, []byte{0x4C, 0x0F, 0xAF, 0xF8}}, + {x86.R0, x86.R15, []byte{0x49, 0x0F, 0xAF, 0xC7}}, + {x86.R1, x86.R14, []byte{0x49, 0x0F, 0xAF, 0xCE}}, + {x86.R2, x86.R13, []byte{0x49, 0x0F, 0xAF, 0xD5}}, + {x86.R3, x86.R12, []byte{0x49, 0x0F, 0xAF, 0xDC}}, + {x86.SP, x86.R11, []byte{0x49, 0x0F, 0xAF, 0xE3}}, + {x86.R5, x86.R10, []byte{0x49, 0x0F, 0xAF, 0xEA}}, + {x86.R6, x86.R9, []byte{0x49, 0x0F, 0xAF, 0xF1}}, + {x86.R7, x86.R8, []byte{0x49, 0x0F, 0xAF, 0xF8}}, + {x86.R8, x86.R7, []byte{0x4C, 0x0F, 0xAF, 0xC7}}, + {x86.R9, x86.R6, []byte{0x4C, 0x0F, 0xAF, 0xCE}}, + {x86.R10, x86.R5, []byte{0x4C, 0x0F, 0xAF, 0xD5}}, + {x86.R11, x86.SP, []byte{0x4C, 0x0F, 0xAF, 0xDC}}, + {x86.R12, x86.R3, []byte{0x4C, 0x0F, 0xAF, 0xE3}}, + {x86.R13, x86.R2, []byte{0x4C, 0x0F, 0xAF, 0xEA}}, + {x86.R14, x86.R1, []byte{0x4C, 0x0F, 0xAF, 0xF1}}, + {x86.R15, x86.R0, []byte{0x4C, 0x0F, 0xAF, 0xF8}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Negate_test.go b/src/x86/Negate_test.go index df02835..b982850 100644 --- a/src/x86/Negate_test.go +++ b/src/x86/Negate_test.go @@ -13,14 +13,14 @@ func TestNegateRegister(t *testing.T) { Register cpu.Register Code []byte }{ - {x86.RAX, []byte{0x48, 0xF7, 0xD8}}, - {x86.RCX, []byte{0x48, 0xF7, 0xD9}}, - {x86.RDX, []byte{0x48, 0xF7, 0xDA}}, - {x86.RBX, []byte{0x48, 0xF7, 0xDB}}, - {x86.RSP, []byte{0x48, 0xF7, 0xDC}}, - {x86.RBP, []byte{0x48, 0xF7, 0xDD}}, - {x86.RSI, []byte{0x48, 0xF7, 0xDE}}, - {x86.RDI, []byte{0x48, 0xF7, 0xDF}}, + {x86.R0, []byte{0x48, 0xF7, 0xD8}}, + {x86.R1, []byte{0x48, 0xF7, 0xD9}}, + {x86.R2, []byte{0x48, 0xF7, 0xDA}}, + {x86.R3, []byte{0x48, 0xF7, 0xDB}}, + {x86.SP, []byte{0x48, 0xF7, 0xDC}}, + {x86.R5, []byte{0x48, 0xF7, 0xDD}}, + {x86.R6, []byte{0x48, 0xF7, 0xDE}}, + {x86.R7, []byte{0x48, 0xF7, 0xDF}}, {x86.R8, []byte{0x49, 0xF7, 0xD8}}, {x86.R9, []byte{0x49, 0xF7, 0xD9}}, {x86.R10, []byte{0x49, 0xF7, 0xDA}}, diff --git a/src/x86/Or_test.go b/src/x86/Or_test.go index bac71e4..0c4431e 100644 --- a/src/x86/Or_test.go +++ b/src/x86/Or_test.go @@ -14,14 +14,14 @@ func TestOrRegisterNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0x83, 0xC8, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0x83, 0xC9, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0x83, 0xCA, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0x83, 0xCB, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0x83, 0xCC, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0x83, 0xCD, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0x83, 0xCE, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0x83, 0xCF, 0x01}}, + {x86.R0, 1, []byte{0x48, 0x83, 0xC8, 0x01}}, + {x86.R1, 1, []byte{0x48, 0x83, 0xC9, 0x01}}, + {x86.R2, 1, []byte{0x48, 0x83, 0xCA, 0x01}}, + {x86.R3, 1, []byte{0x48, 0x83, 0xCB, 0x01}}, + {x86.SP, 1, []byte{0x48, 0x83, 0xCC, 0x01}}, + {x86.R5, 1, []byte{0x48, 0x83, 0xCD, 0x01}}, + {x86.R6, 1, []byte{0x48, 0x83, 0xCE, 0x01}}, + {x86.R7, 1, []byte{0x48, 0x83, 0xCF, 0x01}}, {x86.R8, 1, []byte{0x49, 0x83, 0xC8, 0x01}}, {x86.R9, 1, []byte{0x49, 0x83, 0xC9, 0x01}}, {x86.R10, 1, []byte{0x49, 0x83, 0xCA, 0x01}}, @@ -31,14 +31,14 @@ func TestOrRegisterNumber(t *testing.T) { {x86.R14, 1, []byte{0x49, 0x83, 0xCE, 0x01}}, {x86.R15, 1, []byte{0x49, 0x83, 0xCF, 0x01}}, - {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCD, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFF, []byte{0x48, 0x81, 0xCF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC8, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xCA, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -62,22 +62,22 @@ func TestOrRegisterRegister(t *testing.T) { Right cpu.Register Code []byte }{ - {x86.RAX, x86.R15, []byte{0x4C, 0x09, 0xF8}}, - {x86.RCX, x86.R14, []byte{0x4C, 0x09, 0xF1}}, - {x86.RDX, x86.R13, []byte{0x4C, 0x09, 0xEA}}, - {x86.RBX, x86.R12, []byte{0x4C, 0x09, 0xE3}}, - {x86.RSP, x86.R11, []byte{0x4C, 0x09, 0xDC}}, - {x86.RBP, x86.R10, []byte{0x4C, 0x09, 0xD5}}, - {x86.RSI, x86.R9, []byte{0x4C, 0x09, 0xCE}}, - {x86.RDI, x86.R8, []byte{0x4C, 0x09, 0xC7}}, - {x86.R8, x86.RDI, []byte{0x49, 0x09, 0xF8}}, - {x86.R9, x86.RSI, []byte{0x49, 0x09, 0xF1}}, - {x86.R10, x86.RBP, []byte{0x49, 0x09, 0xEA}}, - {x86.R11, x86.RSP, []byte{0x49, 0x09, 0xE3}}, - {x86.R12, x86.RBX, []byte{0x49, 0x09, 0xDC}}, - {x86.R13, x86.RDX, []byte{0x49, 0x09, 0xD5}}, - {x86.R14, x86.RCX, []byte{0x49, 0x09, 0xCE}}, - {x86.R15, x86.RAX, []byte{0x49, 0x09, 0xC7}}, + {x86.R0, x86.R15, []byte{0x4C, 0x09, 0xF8}}, + {x86.R1, x86.R14, []byte{0x4C, 0x09, 0xF1}}, + {x86.R2, x86.R13, []byte{0x4C, 0x09, 0xEA}}, + {x86.R3, x86.R12, []byte{0x4C, 0x09, 0xE3}}, + {x86.SP, x86.R11, []byte{0x4C, 0x09, 0xDC}}, + {x86.R5, x86.R10, []byte{0x4C, 0x09, 0xD5}}, + {x86.R6, x86.R9, []byte{0x4C, 0x09, 0xCE}}, + {x86.R7, x86.R8, []byte{0x4C, 0x09, 0xC7}}, + {x86.R8, x86.R7, []byte{0x49, 0x09, 0xF8}}, + {x86.R9, x86.R6, []byte{0x49, 0x09, 0xF1}}, + {x86.R10, x86.R5, []byte{0x49, 0x09, 0xEA}}, + {x86.R11, x86.SP, []byte{0x49, 0x09, 0xE3}}, + {x86.R12, x86.R3, []byte{0x49, 0x09, 0xDC}}, + {x86.R13, x86.R2, []byte{0x49, 0x09, 0xD5}}, + {x86.R14, x86.R1, []byte{0x49, 0x09, 0xCE}}, + {x86.R15, x86.R0, []byte{0x49, 0x09, 0xC7}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Pop_test.go b/src/x86/Pop_test.go index 65cb1aa..c01a1ee 100644 --- a/src/x86/Pop_test.go +++ b/src/x86/Pop_test.go @@ -13,14 +13,14 @@ func TestPopRegister(t *testing.T) { Register cpu.Register Code []byte }{ - {x86.RAX, []byte{0x58}}, - {x86.RCX, []byte{0x59}}, - {x86.RDX, []byte{0x5A}}, - {x86.RBX, []byte{0x5B}}, - {x86.RSP, []byte{0x5C}}, - {x86.RBP, []byte{0x5D}}, - {x86.RSI, []byte{0x5E}}, - {x86.RDI, []byte{0x5F}}, + {x86.R0, []byte{0x58}}, + {x86.R1, []byte{0x59}}, + {x86.R2, []byte{0x5A}}, + {x86.R3, []byte{0x5B}}, + {x86.SP, []byte{0x5C}}, + {x86.R5, []byte{0x5D}}, + {x86.R6, []byte{0x5E}}, + {x86.R7, []byte{0x5F}}, {x86.R8, []byte{0x41, 0x58}}, {x86.R9, []byte{0x41, 0x59}}, {x86.R10, []byte{0x41, 0x5A}}, diff --git a/src/x86/Push_test.go b/src/x86/Push_test.go index 138478d..c7f79dc 100644 --- a/src/x86/Push_test.go +++ b/src/x86/Push_test.go @@ -35,14 +35,14 @@ func TestPushRegister(t *testing.T) { Register cpu.Register Code []byte }{ - {x86.RAX, []byte{0x50}}, - {x86.RCX, []byte{0x51}}, - {x86.RDX, []byte{0x52}}, - {x86.RBX, []byte{0x53}}, - {x86.RSP, []byte{0x54}}, - {x86.RBP, []byte{0x55}}, - {x86.RSI, []byte{0x56}}, - {x86.RDI, []byte{0x57}}, + {x86.R0, []byte{0x50}}, + {x86.R1, []byte{0x51}}, + {x86.R2, []byte{0x52}}, + {x86.R3, []byte{0x53}}, + {x86.SP, []byte{0x54}}, + {x86.R5, []byte{0x55}}, + {x86.R6, []byte{0x56}}, + {x86.R7, []byte{0x57}}, {x86.R8, []byte{0x41, 0x50}}, {x86.R9, []byte{0x41, 0x51}}, {x86.R10, []byte{0x41, 0x52}}, diff --git a/src/x86/Registers.go b/src/x86/Registers.go index ab31583..3e2958d 100644 --- a/src/x86/Registers.go +++ b/src/x86/Registers.go @@ -3,14 +3,14 @@ package x86 import "git.urbach.dev/cli/q/src/cpu" const ( - RAX cpu.Register = iota - RCX - RDX - RBX - RSP - RBP - RSI - RDI + R0 cpu.Register = iota // RAX + R1 // RCX + R2 // RDX + R3 // RBX + SP // Stack pointer + R5 // RBP + R6 // RSI + R7 // RDI R8 R9 R10 @@ -19,25 +19,21 @@ const ( R13 R14 R15 - TMP = RCX + TMP = R1 ) var ( - SyscallInputRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} - SyscallOutputRegisters = []cpu.Register{RAX, RCX, R11} - GeneralRegisters = []cpu.Register{RBX, R12, R13, R14, R15, R11} - InputRegisters = SyscallInputRegisters - OutputRegisters = SyscallInputRegisters - WindowsInputRegisters = []cpu.Register{RCX, RDX, R8, R9} - WindowsOutputRegisters = []cpu.Register{RAX} - WindowsVolatileRegisters = []cpu.Register{RCX, RDX, R8, R9, R10, R11} + SyscallInputRegisters = []cpu.Register{R0, R7, R6, R2, R10, R8, R9} + WindowsInputRegisters = []cpu.Register{R1, R2, R8, R9} + WindowsOutputRegisters = []cpu.Register{R0} + WindowsVolatileRegisters = []cpu.Register{R1, R2, R8, R9, R10, R11} CPU = cpu.CPU{ - General: GeneralRegisters, - Input: InputRegisters, - Output: OutputRegisters, + General: []cpu.Register{R3, R12, R13, R14, R15, R11}, + Input: SyscallInputRegisters, + Output: SyscallInputRegisters, SyscallInput: SyscallInputRegisters, - SyscallOutput: SyscallOutputRegisters, + SyscallOutput: []cpu.Register{R0, R1, R11}, NumRegisters: 16, } ) diff --git a/src/x86/Registers_test.go b/src/x86/Registers_test.go index d1aadad..91eb96d 100644 --- a/src/x86/Registers_test.go +++ b/src/x86/Registers_test.go @@ -8,5 +8,5 @@ import ( ) func TestRegisters(t *testing.T) { - assert.NotContains(t, x86.GeneralRegisters, x86.RSP) + assert.NotContains(t, x86.CPU.General, x86.SP) } diff --git a/src/x86/Shift_test.go b/src/x86/Shift_test.go index 19fa94a..a65fb7b 100644 --- a/src/x86/Shift_test.go +++ b/src/x86/Shift_test.go @@ -14,14 +14,14 @@ func TestShiftLeftNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0xC1, 0xE0, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0xC1, 0xE1, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0xC1, 0xE2, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0xC1, 0xE3, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0xC1, 0xE4, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0xC1, 0xE5, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0xC1, 0xE6, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0xC1, 0xE7, 0x01}}, + {x86.R0, 1, []byte{0x48, 0xC1, 0xE0, 0x01}}, + {x86.R1, 1, []byte{0x48, 0xC1, 0xE1, 0x01}}, + {x86.R2, 1, []byte{0x48, 0xC1, 0xE2, 0x01}}, + {x86.R3, 1, []byte{0x48, 0xC1, 0xE3, 0x01}}, + {x86.SP, 1, []byte{0x48, 0xC1, 0xE4, 0x01}}, + {x86.R5, 1, []byte{0x48, 0xC1, 0xE5, 0x01}}, + {x86.R6, 1, []byte{0x48, 0xC1, 0xE6, 0x01}}, + {x86.R7, 1, []byte{0x48, 0xC1, 0xE7, 0x01}}, {x86.R8, 1, []byte{0x49, 0xC1, 0xE0, 0x01}}, {x86.R9, 1, []byte{0x49, 0xC1, 0xE1, 0x01}}, {x86.R10, 1, []byte{0x49, 0xC1, 0xE2, 0x01}}, @@ -45,14 +45,14 @@ func TestShiftRightSignedNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0xC1, 0xF8, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0xC1, 0xF9, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0xC1, 0xFA, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0xC1, 0xFB, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0xC1, 0xFC, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0xC1, 0xFD, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0xC1, 0xFE, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0xC1, 0xFF, 0x01}}, + {x86.R0, 1, []byte{0x48, 0xC1, 0xF8, 0x01}}, + {x86.R1, 1, []byte{0x48, 0xC1, 0xF9, 0x01}}, + {x86.R2, 1, []byte{0x48, 0xC1, 0xFA, 0x01}}, + {x86.R3, 1, []byte{0x48, 0xC1, 0xFB, 0x01}}, + {x86.SP, 1, []byte{0x48, 0xC1, 0xFC, 0x01}}, + {x86.R5, 1, []byte{0x48, 0xC1, 0xFD, 0x01}}, + {x86.R6, 1, []byte{0x48, 0xC1, 0xFE, 0x01}}, + {x86.R7, 1, []byte{0x48, 0xC1, 0xFF, 0x01}}, {x86.R8, 1, []byte{0x49, 0xC1, 0xF8, 0x01}}, {x86.R9, 1, []byte{0x49, 0xC1, 0xF9, 0x01}}, {x86.R10, 1, []byte{0x49, 0xC1, 0xFA, 0x01}}, diff --git a/src/x86/StoreDynamic_test.go b/src/x86/StoreDynamic_test.go index 2e55105..cc0756e 100644 --- a/src/x86/StoreDynamic_test.go +++ b/src/x86/StoreDynamic_test.go @@ -16,70 +16,70 @@ func TestStoreDynamicNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, x86.R15, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RAX, x86.R15, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RAX, x86.R15, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, - {x86.RAX, x86.R15, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x38, 0x7F}}, - {x86.RCX, x86.R14, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RCX, x86.R14, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RCX, x86.R14, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, - {x86.RCX, x86.R14, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x31, 0x7F}}, - {x86.RDX, x86.R13, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDX, x86.R13, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDX, x86.R13, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, - {x86.RDX, x86.R13, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x2A, 0x7F}}, - {x86.RBX, x86.R12, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBX, x86.R12, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBX, x86.R12, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00}}, - {x86.RBX, x86.R12, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x23, 0x7F}}, - {x86.RSP, x86.R11, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSP, x86.R11, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSP, x86.R11, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, - {x86.RSP, x86.R11, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, - {x86.RBP, x86.R10, 8, 0x7F, []byte{0x4A, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBP, x86.R10, 4, 0x7F, []byte{0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBP, x86.R10, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, - {x86.RBP, x86.R10, 1, 0x7F, []byte{0x42, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, - {x86.RSI, x86.R9, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSI, x86.R9, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSI, x86.R9, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, - {x86.RSI, x86.R9, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x0E, 0x7F}}, - {x86.RDI, x86.R8, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDI, x86.R8, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDI, x86.R8, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, - {x86.RDI, x86.R8, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x07, 0x7F}}, - {x86.R8, x86.RDI, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R8, x86.RDI, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R8, x86.RDI, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, - {x86.R8, x86.RDI, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x38, 0x7F}}, - {x86.R9, x86.RSI, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R9, x86.RSI, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R9, x86.RSI, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, - {x86.R9, x86.RSI, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x31, 0x7F}}, - {x86.R10, x86.RBP, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R10, x86.RBP, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R10, x86.RBP, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, - {x86.R10, x86.RBP, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x2A, 0x7F}}, - {x86.R11, x86.RSP, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R11, x86.RSP, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R11, x86.RSP, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, - {x86.R11, x86.RSP, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, - {x86.R12, x86.RBX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R12, x86.RBX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R12, x86.RBX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, - {x86.R12, x86.RBX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x1C, 0x7F}}, - {x86.R13, x86.RDX, 8, 0x7F, []byte{0x49, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R13, x86.RDX, 4, 0x7F, []byte{0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R13, x86.RDX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, - {x86.R13, x86.RDX, 1, 0x7F, []byte{0x41, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, - {x86.R14, x86.RCX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R14, x86.RCX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R14, x86.RCX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, - {x86.R14, x86.RCX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x0E, 0x7F}}, - {x86.R15, x86.RAX, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R15, x86.RAX, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x86.R15, x86.RAX, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, - {x86.R15, x86.RAX, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x07, 0x7F}}, + {x86.R0, x86.R15, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R0, x86.R15, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R0, x86.R15, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, + {x86.R0, x86.R15, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x38, 0x7F}}, + {x86.R1, x86.R14, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R1, x86.R14, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R1, x86.R14, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, + {x86.R1, x86.R14, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x31, 0x7F}}, + {x86.R2, x86.R13, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R2, x86.R13, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R2, x86.R13, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, + {x86.R2, x86.R13, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x2A, 0x7F}}, + {x86.R3, x86.R12, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R3, x86.R12, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R3, x86.R12, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x23, 0x7F, 0x00}}, + {x86.R3, x86.R12, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x23, 0x7F}}, + {x86.SP, x86.R11, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.SP, x86.R11, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.SP, x86.R11, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x86.SP, x86.R11, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, + {x86.R5, x86.R10, 8, 0x7F, []byte{0x4A, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R5, x86.R10, 4, 0x7F, []byte{0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R5, x86.R10, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, + {x86.R5, x86.R10, 1, 0x7F, []byte{0x42, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, + {x86.R6, x86.R9, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R6, x86.R9, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R6, x86.R9, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, + {x86.R6, x86.R9, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x0E, 0x7F}}, + {x86.R7, x86.R8, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R7, x86.R8, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R7, x86.R8, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, + {x86.R7, x86.R8, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x07, 0x7F}}, + {x86.R8, x86.R7, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R8, x86.R7, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R8, x86.R7, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x38, 0x7F, 0x00}}, + {x86.R8, x86.R7, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x38, 0x7F}}, + {x86.R9, x86.R6, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R9, x86.R6, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R9, x86.R6, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x31, 0x7F, 0x00}}, + {x86.R9, x86.R6, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x31, 0x7F}}, + {x86.R10, x86.R5, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R10, x86.R5, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R10, x86.R5, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x2A, 0x7F, 0x00}}, + {x86.R10, x86.R5, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x2A, 0x7F}}, + {x86.R11, x86.SP, 8, 0x7F, []byte{0x4A, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R11, x86.SP, 4, 0x7F, []byte{0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R11, x86.SP, 2, 0x7F, []byte{0x66, 0x42, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x86.R11, x86.SP, 1, 0x7F, []byte{0x42, 0xC6, 0x04, 0x1C, 0x7F}}, + {x86.R12, x86.R3, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R12, x86.R3, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R12, x86.R3, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x1C, 0x7F, 0x00}}, + {x86.R12, x86.R3, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x1C, 0x7F}}, + {x86.R13, x86.R2, 8, 0x7F, []byte{0x49, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R13, x86.R2, 4, 0x7F, []byte{0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R13, x86.R2, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x44, 0x15, 0x00, 0x7F, 0x00}}, + {x86.R13, x86.R2, 1, 0x7F, []byte{0x41, 0xC6, 0x44, 0x15, 0x00, 0x7F}}, + {x86.R14, x86.R1, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R14, x86.R1, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R14, x86.R1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x0E, 0x7F, 0x00}}, + {x86.R14, x86.R1, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x0E, 0x7F}}, + {x86.R15, x86.R0, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R15, x86.R0, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R15, x86.R0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x07, 0x7F, 0x00}}, + {x86.R15, x86.R0, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x07, 0x7F}}, } for _, pattern := range usagePatterns { @@ -97,70 +97,70 @@ func TestStoreDynamicRegister(t *testing.T) { RegisterFrom cpu.Register Code []byte }{ - {x86.RAX, x86.R15, 8, x86.R15, []byte{0x4E, 0x89, 0x3C, 0x38}}, - {x86.RAX, x86.R15, 4, x86.R15, []byte{0x46, 0x89, 0x3C, 0x38}}, - {x86.RAX, x86.R15, 2, x86.R15, []byte{0x66, 0x46, 0x89, 0x3C, 0x38}}, - {x86.RAX, x86.R15, 1, x86.R15, []byte{0x46, 0x88, 0x3C, 0x38}}, - {x86.RCX, x86.R14, 8, x86.R14, []byte{0x4E, 0x89, 0x34, 0x31}}, - {x86.RCX, x86.R14, 4, x86.R14, []byte{0x46, 0x89, 0x34, 0x31}}, - {x86.RCX, x86.R14, 2, x86.R14, []byte{0x66, 0x46, 0x89, 0x34, 0x31}}, - {x86.RCX, x86.R14, 1, x86.R14, []byte{0x46, 0x88, 0x34, 0x31}}, - {x86.RDX, x86.R13, 8, x86.R13, []byte{0x4E, 0x89, 0x2C, 0x2A}}, - {x86.RDX, x86.R13, 4, x86.R13, []byte{0x46, 0x89, 0x2C, 0x2A}}, - {x86.RDX, x86.R13, 2, x86.R13, []byte{0x66, 0x46, 0x89, 0x2C, 0x2A}}, - {x86.RDX, x86.R13, 1, x86.R13, []byte{0x46, 0x88, 0x2C, 0x2A}}, - {x86.RBX, x86.R12, 8, x86.R12, []byte{0x4E, 0x89, 0x24, 0x23}}, - {x86.RBX, x86.R12, 4, x86.R12, []byte{0x46, 0x89, 0x24, 0x23}}, - {x86.RBX, x86.R12, 2, x86.R12, []byte{0x66, 0x46, 0x89, 0x24, 0x23}}, - {x86.RBX, x86.R12, 1, x86.R12, []byte{0x46, 0x88, 0x24, 0x23}}, - {x86.RSP, x86.R11, 8, x86.R11, []byte{0x4E, 0x89, 0x1C, 0x1C}}, - {x86.RSP, x86.R11, 4, x86.R11, []byte{0x46, 0x89, 0x1C, 0x1C}}, - {x86.RSP, x86.R11, 2, x86.R11, []byte{0x66, 0x46, 0x89, 0x1C, 0x1C}}, - {x86.RSP, x86.R11, 1, x86.R11, []byte{0x46, 0x88, 0x1C, 0x1C}}, - {x86.RBP, x86.R10, 8, x86.R10, []byte{0x4E, 0x89, 0x54, 0x15, 0x00}}, - {x86.RBP, x86.R10, 4, x86.R10, []byte{0x46, 0x89, 0x54, 0x15, 0x00}}, - {x86.RBP, x86.R10, 2, x86.R10, []byte{0x66, 0x46, 0x89, 0x54, 0x15, 0x00}}, - {x86.RBP, x86.R10, 1, x86.R10, []byte{0x46, 0x88, 0x54, 0x15, 0x00}}, - {x86.RSI, x86.R9, 8, x86.R9, []byte{0x4E, 0x89, 0x0C, 0x0E}}, - {x86.RSI, x86.R9, 4, x86.R9, []byte{0x46, 0x89, 0x0C, 0x0E}}, - {x86.RSI, x86.R9, 2, x86.R9, []byte{0x66, 0x46, 0x89, 0x0C, 0x0E}}, - {x86.RSI, x86.R9, 1, x86.R9, []byte{0x46, 0x88, 0x0C, 0x0E}}, - {x86.RDI, x86.R8, 8, x86.R8, []byte{0x4E, 0x89, 0x04, 0x07}}, - {x86.RDI, x86.R8, 4, x86.R8, []byte{0x46, 0x89, 0x04, 0x07}}, - {x86.RDI, x86.R8, 2, x86.R8, []byte{0x66, 0x46, 0x89, 0x04, 0x07}}, - {x86.RDI, x86.R8, 1, x86.R8, []byte{0x46, 0x88, 0x04, 0x07}}, - {x86.R8, x86.RDI, 8, x86.RDI, []byte{0x49, 0x89, 0x3C, 0x38}}, - {x86.R8, x86.RDI, 4, x86.RDI, []byte{0x41, 0x89, 0x3C, 0x38}}, - {x86.R8, x86.RDI, 2, x86.RDI, []byte{0x66, 0x41, 0x89, 0x3C, 0x38}}, - {x86.R8, x86.RDI, 1, x86.RDI, []byte{0x41, 0x88, 0x3C, 0x38}}, - {x86.R9, x86.RSI, 8, x86.RSI, []byte{0x49, 0x89, 0x34, 0x31}}, - {x86.R9, x86.RSI, 4, x86.RSI, []byte{0x41, 0x89, 0x34, 0x31}}, - {x86.R9, x86.RSI, 2, x86.RSI, []byte{0x66, 0x41, 0x89, 0x34, 0x31}}, - {x86.R9, x86.RSI, 1, x86.RSI, []byte{0x41, 0x88, 0x34, 0x31}}, - {x86.R10, x86.RBP, 8, x86.RBP, []byte{0x49, 0x89, 0x2C, 0x2A}}, - {x86.R10, x86.RBP, 4, x86.RBP, []byte{0x41, 0x89, 0x2C, 0x2A}}, - {x86.R10, x86.RBP, 2, x86.RBP, []byte{0x66, 0x41, 0x89, 0x2C, 0x2A}}, - {x86.R10, x86.RBP, 1, x86.RBP, []byte{0x41, 0x88, 0x2C, 0x2A}}, - {x86.R11, x86.RSP, 8, x86.RSP, []byte{0x4A, 0x89, 0x24, 0x1C}}, - {x86.R11, x86.RSP, 4, x86.RSP, []byte{0x42, 0x89, 0x24, 0x1C}}, - {x86.R11, x86.RSP, 2, x86.RSP, []byte{0x66, 0x42, 0x89, 0x24, 0x1C}}, - {x86.R11, x86.RSP, 1, x86.RSP, []byte{0x42, 0x88, 0x24, 0x1C}}, - {x86.R12, x86.RBX, 8, x86.RBX, []byte{0x49, 0x89, 0x1C, 0x1C}}, - {x86.R12, x86.RBX, 4, x86.RBX, []byte{0x41, 0x89, 0x1C, 0x1C}}, - {x86.R12, x86.RBX, 2, x86.RBX, []byte{0x66, 0x41, 0x89, 0x1C, 0x1C}}, - {x86.R12, x86.RBX, 1, x86.RBX, []byte{0x41, 0x88, 0x1C, 0x1C}}, - {x86.R13, x86.RDX, 8, x86.RDX, []byte{0x49, 0x89, 0x54, 0x15, 0x00}}, - {x86.R13, x86.RDX, 4, x86.RDX, []byte{0x41, 0x89, 0x54, 0x15, 0x00}}, - {x86.R13, x86.RDX, 2, x86.RDX, []byte{0x66, 0x41, 0x89, 0x54, 0x15, 0x00}}, - {x86.R13, x86.RDX, 1, x86.RDX, []byte{0x41, 0x88, 0x54, 0x15, 0x00}}, - {x86.R14, x86.RCX, 8, x86.RCX, []byte{0x49, 0x89, 0x0C, 0x0E}}, - {x86.R14, x86.RCX, 4, x86.RCX, []byte{0x41, 0x89, 0x0C, 0x0E}}, - {x86.R14, x86.RCX, 2, x86.RCX, []byte{0x66, 0x41, 0x89, 0x0C, 0x0E}}, - {x86.R14, x86.RCX, 1, x86.RCX, []byte{0x41, 0x88, 0x0C, 0x0E}}, - {x86.R15, x86.RAX, 8, x86.RAX, []byte{0x49, 0x89, 0x04, 0x07}}, - {x86.R15, x86.RAX, 4, x86.RAX, []byte{0x41, 0x89, 0x04, 0x07}}, - {x86.R15, x86.RAX, 2, x86.RAX, []byte{0x66, 0x41, 0x89, 0x04, 0x07}}, - {x86.R15, x86.RAX, 1, x86.RAX, []byte{0x41, 0x88, 0x04, 0x07}}, + {x86.R0, x86.R15, 8, x86.R15, []byte{0x4E, 0x89, 0x3C, 0x38}}, + {x86.R0, x86.R15, 4, x86.R15, []byte{0x46, 0x89, 0x3C, 0x38}}, + {x86.R0, x86.R15, 2, x86.R15, []byte{0x66, 0x46, 0x89, 0x3C, 0x38}}, + {x86.R0, x86.R15, 1, x86.R15, []byte{0x46, 0x88, 0x3C, 0x38}}, + {x86.R1, x86.R14, 8, x86.R14, []byte{0x4E, 0x89, 0x34, 0x31}}, + {x86.R1, x86.R14, 4, x86.R14, []byte{0x46, 0x89, 0x34, 0x31}}, + {x86.R1, x86.R14, 2, x86.R14, []byte{0x66, 0x46, 0x89, 0x34, 0x31}}, + {x86.R1, x86.R14, 1, x86.R14, []byte{0x46, 0x88, 0x34, 0x31}}, + {x86.R2, x86.R13, 8, x86.R13, []byte{0x4E, 0x89, 0x2C, 0x2A}}, + {x86.R2, x86.R13, 4, x86.R13, []byte{0x46, 0x89, 0x2C, 0x2A}}, + {x86.R2, x86.R13, 2, x86.R13, []byte{0x66, 0x46, 0x89, 0x2C, 0x2A}}, + {x86.R2, x86.R13, 1, x86.R13, []byte{0x46, 0x88, 0x2C, 0x2A}}, + {x86.R3, x86.R12, 8, x86.R12, []byte{0x4E, 0x89, 0x24, 0x23}}, + {x86.R3, x86.R12, 4, x86.R12, []byte{0x46, 0x89, 0x24, 0x23}}, + {x86.R3, x86.R12, 2, x86.R12, []byte{0x66, 0x46, 0x89, 0x24, 0x23}}, + {x86.R3, x86.R12, 1, x86.R12, []byte{0x46, 0x88, 0x24, 0x23}}, + {x86.SP, x86.R11, 8, x86.R11, []byte{0x4E, 0x89, 0x1C, 0x1C}}, + {x86.SP, x86.R11, 4, x86.R11, []byte{0x46, 0x89, 0x1C, 0x1C}}, + {x86.SP, x86.R11, 2, x86.R11, []byte{0x66, 0x46, 0x89, 0x1C, 0x1C}}, + {x86.SP, x86.R11, 1, x86.R11, []byte{0x46, 0x88, 0x1C, 0x1C}}, + {x86.R5, x86.R10, 8, x86.R10, []byte{0x4E, 0x89, 0x54, 0x15, 0x00}}, + {x86.R5, x86.R10, 4, x86.R10, []byte{0x46, 0x89, 0x54, 0x15, 0x00}}, + {x86.R5, x86.R10, 2, x86.R10, []byte{0x66, 0x46, 0x89, 0x54, 0x15, 0x00}}, + {x86.R5, x86.R10, 1, x86.R10, []byte{0x46, 0x88, 0x54, 0x15, 0x00}}, + {x86.R6, x86.R9, 8, x86.R9, []byte{0x4E, 0x89, 0x0C, 0x0E}}, + {x86.R6, x86.R9, 4, x86.R9, []byte{0x46, 0x89, 0x0C, 0x0E}}, + {x86.R6, x86.R9, 2, x86.R9, []byte{0x66, 0x46, 0x89, 0x0C, 0x0E}}, + {x86.R6, x86.R9, 1, x86.R9, []byte{0x46, 0x88, 0x0C, 0x0E}}, + {x86.R7, x86.R8, 8, x86.R8, []byte{0x4E, 0x89, 0x04, 0x07}}, + {x86.R7, x86.R8, 4, x86.R8, []byte{0x46, 0x89, 0x04, 0x07}}, + {x86.R7, x86.R8, 2, x86.R8, []byte{0x66, 0x46, 0x89, 0x04, 0x07}}, + {x86.R7, x86.R8, 1, x86.R8, []byte{0x46, 0x88, 0x04, 0x07}}, + {x86.R8, x86.R7, 8, x86.R7, []byte{0x49, 0x89, 0x3C, 0x38}}, + {x86.R8, x86.R7, 4, x86.R7, []byte{0x41, 0x89, 0x3C, 0x38}}, + {x86.R8, x86.R7, 2, x86.R7, []byte{0x66, 0x41, 0x89, 0x3C, 0x38}}, + {x86.R8, x86.R7, 1, x86.R7, []byte{0x41, 0x88, 0x3C, 0x38}}, + {x86.R9, x86.R6, 8, x86.R6, []byte{0x49, 0x89, 0x34, 0x31}}, + {x86.R9, x86.R6, 4, x86.R6, []byte{0x41, 0x89, 0x34, 0x31}}, + {x86.R9, x86.R6, 2, x86.R6, []byte{0x66, 0x41, 0x89, 0x34, 0x31}}, + {x86.R9, x86.R6, 1, x86.R6, []byte{0x41, 0x88, 0x34, 0x31}}, + {x86.R10, x86.R5, 8, x86.R5, []byte{0x49, 0x89, 0x2C, 0x2A}}, + {x86.R10, x86.R5, 4, x86.R5, []byte{0x41, 0x89, 0x2C, 0x2A}}, + {x86.R10, x86.R5, 2, x86.R5, []byte{0x66, 0x41, 0x89, 0x2C, 0x2A}}, + {x86.R10, x86.R5, 1, x86.R5, []byte{0x41, 0x88, 0x2C, 0x2A}}, + {x86.R11, x86.SP, 8, x86.SP, []byte{0x4A, 0x89, 0x24, 0x1C}}, + {x86.R11, x86.SP, 4, x86.SP, []byte{0x42, 0x89, 0x24, 0x1C}}, + {x86.R11, x86.SP, 2, x86.SP, []byte{0x66, 0x42, 0x89, 0x24, 0x1C}}, + {x86.R11, x86.SP, 1, x86.SP, []byte{0x42, 0x88, 0x24, 0x1C}}, + {x86.R12, x86.R3, 8, x86.R3, []byte{0x49, 0x89, 0x1C, 0x1C}}, + {x86.R12, x86.R3, 4, x86.R3, []byte{0x41, 0x89, 0x1C, 0x1C}}, + {x86.R12, x86.R3, 2, x86.R3, []byte{0x66, 0x41, 0x89, 0x1C, 0x1C}}, + {x86.R12, x86.R3, 1, x86.R3, []byte{0x41, 0x88, 0x1C, 0x1C}}, + {x86.R13, x86.R2, 8, x86.R2, []byte{0x49, 0x89, 0x54, 0x15, 0x00}}, + {x86.R13, x86.R2, 4, x86.R2, []byte{0x41, 0x89, 0x54, 0x15, 0x00}}, + {x86.R13, x86.R2, 2, x86.R2, []byte{0x66, 0x41, 0x89, 0x54, 0x15, 0x00}}, + {x86.R13, x86.R2, 1, x86.R2, []byte{0x41, 0x88, 0x54, 0x15, 0x00}}, + {x86.R14, x86.R1, 8, x86.R1, []byte{0x49, 0x89, 0x0C, 0x0E}}, + {x86.R14, x86.R1, 4, x86.R1, []byte{0x41, 0x89, 0x0C, 0x0E}}, + {x86.R14, x86.R1, 2, x86.R1, []byte{0x66, 0x41, 0x89, 0x0C, 0x0E}}, + {x86.R14, x86.R1, 1, x86.R1, []byte{0x41, 0x88, 0x0C, 0x0E}}, + {x86.R15, x86.R0, 8, x86.R0, []byte{0x49, 0x89, 0x04, 0x07}}, + {x86.R15, x86.R0, 4, x86.R0, []byte{0x41, 0x89, 0x04, 0x07}}, + {x86.R15, x86.R0, 2, x86.R0, []byte{0x66, 0x41, 0x89, 0x04, 0x07}}, + {x86.R15, x86.R0, 1, x86.R0, []byte{0x41, 0x88, 0x04, 0x07}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Store_test.go b/src/x86/Store_test.go index 1e912c5..457209a 100644 --- a/src/x86/Store_test.go +++ b/src/x86/Store_test.go @@ -17,38 +17,38 @@ func TestStoreNumber(t *testing.T) { Code []byte }{ // No offset - {x86.RAX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RAX, 0, 4, 0x7F, []byte{0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RAX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x00, 0x7F, 0x00}}, - {x86.RAX, 0, 1, 0x7F, []byte{0xC6, 0x00, 0x7F}}, - {x86.RCX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RCX, 0, 4, 0x7F, []byte{0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RCX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x01, 0x7F, 0x00}}, - {x86.RCX, 0, 1, 0x7F, []byte{0xC6, 0x01, 0x7F}}, - {x86.RDX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDX, 0, 4, 0x7F, []byte{0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x02, 0x7F, 0x00}}, - {x86.RDX, 0, 1, 0x7F, []byte{0xC6, 0x02, 0x7F}}, - {x86.RBX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBX, 0, 4, 0x7F, []byte{0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x03, 0x7F, 0x00}}, - {x86.RBX, 0, 1, 0x7F, []byte{0xC6, 0x03, 0x7F}}, - {x86.RSP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSP, 0, 4, 0x7F, []byte{0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x04, 0x24, 0x7F, 0x00}}, - {x86.RSP, 0, 1, 0x7F, []byte{0xC6, 0x04, 0x24, 0x7F}}, - {x86.RBP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBP, 0, 4, 0x7F, []byte{0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x00, 0x7F, 0x00}}, - {x86.RBP, 0, 1, 0x7F, []byte{0xC6, 0x45, 0x00, 0x7F}}, - {x86.RSI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSI, 0, 4, 0x7F, []byte{0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x06, 0x7F, 0x00}}, - {x86.RSI, 0, 1, 0x7F, []byte{0xC6, 0x06, 0x7F}}, - {x86.RDI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDI, 0, 4, 0x7F, []byte{0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x07, 0x7F, 0x00}}, - {x86.RDI, 0, 1, 0x7F, []byte{0xC6, 0x07, 0x7F}}, + {x86.R0, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R0, 0, 4, 0x7F, []byte{0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R0, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x00, 0x7F, 0x00}}, + {x86.R0, 0, 1, 0x7F, []byte{0xC6, 0x00, 0x7F}}, + {x86.R1, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R1, 0, 4, 0x7F, []byte{0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R1, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x01, 0x7F, 0x00}}, + {x86.R1, 0, 1, 0x7F, []byte{0xC6, 0x01, 0x7F}}, + {x86.R2, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R2, 0, 4, 0x7F, []byte{0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R2, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x02, 0x7F, 0x00}}, + {x86.R2, 0, 1, 0x7F, []byte{0xC6, 0x02, 0x7F}}, + {x86.R3, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R3, 0, 4, 0x7F, []byte{0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R3, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x03, 0x7F, 0x00}}, + {x86.R3, 0, 1, 0x7F, []byte{0xC6, 0x03, 0x7F}}, + {x86.SP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x86.SP, 0, 4, 0x7F, []byte{0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}}, + {x86.SP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x04, 0x24, 0x7F, 0x00}}, + {x86.SP, 0, 1, 0x7F, []byte{0xC6, 0x04, 0x24, 0x7F}}, + {x86.R5, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R5, 0, 4, 0x7F, []byte{0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R5, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x00, 0x7F, 0x00}}, + {x86.R5, 0, 1, 0x7F, []byte{0xC6, 0x45, 0x00, 0x7F}}, + {x86.R6, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R6, 0, 4, 0x7F, []byte{0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R6, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x06, 0x7F, 0x00}}, + {x86.R6, 0, 1, 0x7F, []byte{0xC6, 0x06, 0x7F}}, + {x86.R7, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R7, 0, 4, 0x7F, []byte{0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R7, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x07, 0x7F, 0x00}}, + {x86.R7, 0, 1, 0x7F, []byte{0xC6, 0x07, 0x7F}}, {x86.R8, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, {x86.R8, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}}, {x86.R8, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x00, 0x7F, 0x00}}, @@ -83,38 +83,38 @@ func TestStoreNumber(t *testing.T) { {x86.R15, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x07, 0x7F}}, // Offset of 1 - {x86.RAX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RAX, 1, 4, 0x7F, []byte{0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RAX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, - {x86.RAX, 1, 1, 0x7F, []byte{0xC6, 0x40, 0x01, 0x7F}}, - {x86.RCX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RCX, 1, 4, 0x7F, []byte{0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RCX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x41, 0x01, 0x7F, 0x00}}, - {x86.RCX, 1, 1, 0x7F, []byte{0xC6, 0x41, 0x01, 0x7F}}, - {x86.RDX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDX, 1, 4, 0x7F, []byte{0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x42, 0x01, 0x7F, 0x00}}, - {x86.RDX, 1, 1, 0x7F, []byte{0xC6, 0x42, 0x01, 0x7F}}, - {x86.RBX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBX, 1, 4, 0x7F, []byte{0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, - {x86.RBX, 1, 1, 0x7F, []byte{0xC6, 0x43, 0x01, 0x7F}}, - {x86.RSP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSP, 1, 4, 0x7F, []byte{0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, - {x86.RSP, 1, 1, 0x7F, []byte{0xC6, 0x44, 0x24, 0x01, 0x7F}}, - {x86.RBP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBP, 1, 4, 0x7F, []byte{0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RBP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, - {x86.RBP, 1, 1, 0x7F, []byte{0xC6, 0x45, 0x01, 0x7F}}, - {x86.RSI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSI, 1, 4, 0x7F, []byte{0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RSI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, - {x86.RSI, 1, 1, 0x7F, []byte{0xC6, 0x46, 0x01, 0x7F}}, - {x86.RDI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDI, 1, 4, 0x7F, []byte{0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, - {x86.RDI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, - {x86.RDI, 1, 1, 0x7F, []byte{0xC6, 0x47, 0x01, 0x7F}}, + {x86.R0, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R0, 1, 4, 0x7F, []byte{0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R0, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, + {x86.R0, 1, 1, 0x7F, []byte{0xC6, 0x40, 0x01, 0x7F}}, + {x86.R1, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R1, 1, 4, 0x7F, []byte{0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R1, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x41, 0x01, 0x7F, 0x00}}, + {x86.R1, 1, 1, 0x7F, []byte{0xC6, 0x41, 0x01, 0x7F}}, + {x86.R2, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R2, 1, 4, 0x7F, []byte{0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R2, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x42, 0x01, 0x7F, 0x00}}, + {x86.R2, 1, 1, 0x7F, []byte{0xC6, 0x42, 0x01, 0x7F}}, + {x86.R3, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R3, 1, 4, 0x7F, []byte{0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R3, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x43, 0x01, 0x7F, 0x00}}, + {x86.R3, 1, 1, 0x7F, []byte{0xC6, 0x43, 0x01, 0x7F}}, + {x86.SP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.SP, 1, 4, 0x7F, []byte{0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.SP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}}, + {x86.SP, 1, 1, 0x7F, []byte{0xC6, 0x44, 0x24, 0x01, 0x7F}}, + {x86.R5, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R5, 1, 4, 0x7F, []byte{0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R5, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x01, 0x7F, 0x00}}, + {x86.R5, 1, 1, 0x7F, []byte{0xC6, 0x45, 0x01, 0x7F}}, + {x86.R6, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R6, 1, 4, 0x7F, []byte{0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R6, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x46, 0x01, 0x7F, 0x00}}, + {x86.R6, 1, 1, 0x7F, []byte{0xC6, 0x46, 0x01, 0x7F}}, + {x86.R7, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R7, 1, 4, 0x7F, []byte{0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}}, + {x86.R7, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x47, 0x01, 0x7F, 0x00}}, + {x86.R7, 1, 1, 0x7F, []byte{0xC6, 0x47, 0x01, 0x7F}}, {x86.R8, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, {x86.R8, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}}, {x86.R8, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00}}, @@ -165,136 +165,136 @@ func TestStoreRegister(t *testing.T) { Code []byte }{ // No offset - {x86.RAX, 0, 8, x86.R15, []byte{0x4C, 0x89, 0x38}}, - {x86.RAX, 0, 4, x86.R15, []byte{0x44, 0x89, 0x38}}, - {x86.RAX, 0, 2, x86.R15, []byte{0x66, 0x44, 0x89, 0x38}}, - {x86.RAX, 0, 1, x86.R15, []byte{0x44, 0x88, 0x38}}, - {x86.RCX, 0, 8, x86.R14, []byte{0x4C, 0x89, 0x31}}, - {x86.RCX, 0, 4, x86.R14, []byte{0x44, 0x89, 0x31}}, - {x86.RCX, 0, 2, x86.R14, []byte{0x66, 0x44, 0x89, 0x31}}, - {x86.RCX, 0, 1, x86.R14, []byte{0x44, 0x88, 0x31}}, - {x86.RDX, 0, 8, x86.R13, []byte{0x4C, 0x89, 0x2A}}, - {x86.RDX, 0, 4, x86.R13, []byte{0x44, 0x89, 0x2A}}, - {x86.RDX, 0, 2, x86.R13, []byte{0x66, 0x44, 0x89, 0x2A}}, - {x86.RDX, 0, 1, x86.R13, []byte{0x44, 0x88, 0x2A}}, - {x86.RBX, 0, 8, x86.R12, []byte{0x4C, 0x89, 0x23}}, - {x86.RBX, 0, 4, x86.R12, []byte{0x44, 0x89, 0x23}}, - {x86.RBX, 0, 2, x86.R12, []byte{0x66, 0x44, 0x89, 0x23}}, - {x86.RBX, 0, 1, x86.R12, []byte{0x44, 0x88, 0x23}}, - {x86.RSP, 0, 8, x86.R11, []byte{0x4C, 0x89, 0x1C, 0x24}}, - {x86.RSP, 0, 4, x86.R11, []byte{0x44, 0x89, 0x1C, 0x24}}, - {x86.RSP, 0, 2, x86.R11, []byte{0x66, 0x44, 0x89, 0x1C, 0x24}}, - {x86.RSP, 0, 1, x86.R11, []byte{0x44, 0x88, 0x1C, 0x24}}, - {x86.RBP, 0, 8, x86.R10, []byte{0x4C, 0x89, 0x55, 0x00}}, - {x86.RBP, 0, 4, x86.R10, []byte{0x44, 0x89, 0x55, 0x00}}, - {x86.RBP, 0, 2, x86.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x00}}, - {x86.RBP, 0, 1, x86.R10, []byte{0x44, 0x88, 0x55, 0x00}}, - {x86.RSI, 0, 8, x86.R9, []byte{0x4C, 0x89, 0x0E}}, - {x86.RSI, 0, 4, x86.R9, []byte{0x44, 0x89, 0x0E}}, - {x86.RSI, 0, 2, x86.R9, []byte{0x66, 0x44, 0x89, 0x0E}}, - {x86.RSI, 0, 1, x86.R9, []byte{0x44, 0x88, 0x0E}}, - {x86.RDI, 0, 8, x86.R8, []byte{0x4C, 0x89, 0x07}}, - {x86.RDI, 0, 4, x86.R8, []byte{0x44, 0x89, 0x07}}, - {x86.RDI, 0, 2, x86.R8, []byte{0x66, 0x44, 0x89, 0x07}}, - {x86.RDI, 0, 1, x86.R8, []byte{0x44, 0x88, 0x07}}, - {x86.R8, 0, 8, x86.RDI, []byte{0x49, 0x89, 0x38}}, - {x86.R8, 0, 4, x86.RDI, []byte{0x41, 0x89, 0x38}}, - {x86.R8, 0, 2, x86.RDI, []byte{0x66, 0x41, 0x89, 0x38}}, - {x86.R8, 0, 1, x86.RDI, []byte{0x41, 0x88, 0x38}}, - {x86.R9, 0, 8, x86.RSI, []byte{0x49, 0x89, 0x31}}, - {x86.R9, 0, 4, x86.RSI, []byte{0x41, 0x89, 0x31}}, - {x86.R9, 0, 2, x86.RSI, []byte{0x66, 0x41, 0x89, 0x31}}, - {x86.R9, 0, 1, x86.RSI, []byte{0x41, 0x88, 0x31}}, - {x86.R10, 0, 8, x86.RBP, []byte{0x49, 0x89, 0x2A}}, - {x86.R10, 0, 4, x86.RBP, []byte{0x41, 0x89, 0x2A}}, - {x86.R10, 0, 2, x86.RBP, []byte{0x66, 0x41, 0x89, 0x2A}}, - {x86.R10, 0, 1, x86.RBP, []byte{0x41, 0x88, 0x2A}}, - {x86.R11, 0, 8, x86.RSP, []byte{0x49, 0x89, 0x23}}, - {x86.R11, 0, 4, x86.RSP, []byte{0x41, 0x89, 0x23}}, - {x86.R11, 0, 2, x86.RSP, []byte{0x66, 0x41, 0x89, 0x23}}, - {x86.R11, 0, 1, x86.RSP, []byte{0x41, 0x88, 0x23}}, - {x86.R12, 0, 8, x86.RBX, []byte{0x49, 0x89, 0x1C, 0x24}}, - {x86.R12, 0, 4, x86.RBX, []byte{0x41, 0x89, 0x1C, 0x24}}, - {x86.R12, 0, 2, x86.RBX, []byte{0x66, 0x41, 0x89, 0x1C, 0x24}}, - {x86.R12, 0, 1, x86.RBX, []byte{0x41, 0x88, 0x1C, 0x24}}, - {x86.R13, 0, 8, x86.RDX, []byte{0x49, 0x89, 0x55, 0x00}}, - {x86.R13, 0, 4, x86.RDX, []byte{0x41, 0x89, 0x55, 0x00}}, - {x86.R13, 0, 2, x86.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x00}}, - {x86.R13, 0, 1, x86.RDX, []byte{0x41, 0x88, 0x55, 0x00}}, - {x86.R14, 0, 8, x86.RCX, []byte{0x49, 0x89, 0x0E}}, - {x86.R14, 0, 4, x86.RCX, []byte{0x41, 0x89, 0x0E}}, - {x86.R14, 0, 2, x86.RCX, []byte{0x66, 0x41, 0x89, 0x0E}}, - {x86.R14, 0, 1, x86.RCX, []byte{0x41, 0x88, 0x0E}}, - {x86.R15, 0, 8, x86.RAX, []byte{0x49, 0x89, 0x07}}, - {x86.R15, 0, 4, x86.RAX, []byte{0x41, 0x89, 0x07}}, - {x86.R15, 0, 2, x86.RAX, []byte{0x66, 0x41, 0x89, 0x07}}, - {x86.R15, 0, 1, x86.RAX, []byte{0x41, 0x88, 0x07}}, + {x86.R0, 0, 8, x86.R15, []byte{0x4C, 0x89, 0x38}}, + {x86.R0, 0, 4, x86.R15, []byte{0x44, 0x89, 0x38}}, + {x86.R0, 0, 2, x86.R15, []byte{0x66, 0x44, 0x89, 0x38}}, + {x86.R0, 0, 1, x86.R15, []byte{0x44, 0x88, 0x38}}, + {x86.R1, 0, 8, x86.R14, []byte{0x4C, 0x89, 0x31}}, + {x86.R1, 0, 4, x86.R14, []byte{0x44, 0x89, 0x31}}, + {x86.R1, 0, 2, x86.R14, []byte{0x66, 0x44, 0x89, 0x31}}, + {x86.R1, 0, 1, x86.R14, []byte{0x44, 0x88, 0x31}}, + {x86.R2, 0, 8, x86.R13, []byte{0x4C, 0x89, 0x2A}}, + {x86.R2, 0, 4, x86.R13, []byte{0x44, 0x89, 0x2A}}, + {x86.R2, 0, 2, x86.R13, []byte{0x66, 0x44, 0x89, 0x2A}}, + {x86.R2, 0, 1, x86.R13, []byte{0x44, 0x88, 0x2A}}, + {x86.R3, 0, 8, x86.R12, []byte{0x4C, 0x89, 0x23}}, + {x86.R3, 0, 4, x86.R12, []byte{0x44, 0x89, 0x23}}, + {x86.R3, 0, 2, x86.R12, []byte{0x66, 0x44, 0x89, 0x23}}, + {x86.R3, 0, 1, x86.R12, []byte{0x44, 0x88, 0x23}}, + {x86.SP, 0, 8, x86.R11, []byte{0x4C, 0x89, 0x1C, 0x24}}, + {x86.SP, 0, 4, x86.R11, []byte{0x44, 0x89, 0x1C, 0x24}}, + {x86.SP, 0, 2, x86.R11, []byte{0x66, 0x44, 0x89, 0x1C, 0x24}}, + {x86.SP, 0, 1, x86.R11, []byte{0x44, 0x88, 0x1C, 0x24}}, + {x86.R5, 0, 8, x86.R10, []byte{0x4C, 0x89, 0x55, 0x00}}, + {x86.R5, 0, 4, x86.R10, []byte{0x44, 0x89, 0x55, 0x00}}, + {x86.R5, 0, 2, x86.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x00}}, + {x86.R5, 0, 1, x86.R10, []byte{0x44, 0x88, 0x55, 0x00}}, + {x86.R6, 0, 8, x86.R9, []byte{0x4C, 0x89, 0x0E}}, + {x86.R6, 0, 4, x86.R9, []byte{0x44, 0x89, 0x0E}}, + {x86.R6, 0, 2, x86.R9, []byte{0x66, 0x44, 0x89, 0x0E}}, + {x86.R6, 0, 1, x86.R9, []byte{0x44, 0x88, 0x0E}}, + {x86.R7, 0, 8, x86.R8, []byte{0x4C, 0x89, 0x07}}, + {x86.R7, 0, 4, x86.R8, []byte{0x44, 0x89, 0x07}}, + {x86.R7, 0, 2, x86.R8, []byte{0x66, 0x44, 0x89, 0x07}}, + {x86.R7, 0, 1, x86.R8, []byte{0x44, 0x88, 0x07}}, + {x86.R8, 0, 8, x86.R7, []byte{0x49, 0x89, 0x38}}, + {x86.R8, 0, 4, x86.R7, []byte{0x41, 0x89, 0x38}}, + {x86.R8, 0, 2, x86.R7, []byte{0x66, 0x41, 0x89, 0x38}}, + {x86.R8, 0, 1, x86.R7, []byte{0x41, 0x88, 0x38}}, + {x86.R9, 0, 8, x86.R6, []byte{0x49, 0x89, 0x31}}, + {x86.R9, 0, 4, x86.R6, []byte{0x41, 0x89, 0x31}}, + {x86.R9, 0, 2, x86.R6, []byte{0x66, 0x41, 0x89, 0x31}}, + {x86.R9, 0, 1, x86.R6, []byte{0x41, 0x88, 0x31}}, + {x86.R10, 0, 8, x86.R5, []byte{0x49, 0x89, 0x2A}}, + {x86.R10, 0, 4, x86.R5, []byte{0x41, 0x89, 0x2A}}, + {x86.R10, 0, 2, x86.R5, []byte{0x66, 0x41, 0x89, 0x2A}}, + {x86.R10, 0, 1, x86.R5, []byte{0x41, 0x88, 0x2A}}, + {x86.R11, 0, 8, x86.SP, []byte{0x49, 0x89, 0x23}}, + {x86.R11, 0, 4, x86.SP, []byte{0x41, 0x89, 0x23}}, + {x86.R11, 0, 2, x86.SP, []byte{0x66, 0x41, 0x89, 0x23}}, + {x86.R11, 0, 1, x86.SP, []byte{0x41, 0x88, 0x23}}, + {x86.R12, 0, 8, x86.R3, []byte{0x49, 0x89, 0x1C, 0x24}}, + {x86.R12, 0, 4, x86.R3, []byte{0x41, 0x89, 0x1C, 0x24}}, + {x86.R12, 0, 2, x86.R3, []byte{0x66, 0x41, 0x89, 0x1C, 0x24}}, + {x86.R12, 0, 1, x86.R3, []byte{0x41, 0x88, 0x1C, 0x24}}, + {x86.R13, 0, 8, x86.R2, []byte{0x49, 0x89, 0x55, 0x00}}, + {x86.R13, 0, 4, x86.R2, []byte{0x41, 0x89, 0x55, 0x00}}, + {x86.R13, 0, 2, x86.R2, []byte{0x66, 0x41, 0x89, 0x55, 0x00}}, + {x86.R13, 0, 1, x86.R2, []byte{0x41, 0x88, 0x55, 0x00}}, + {x86.R14, 0, 8, x86.R1, []byte{0x49, 0x89, 0x0E}}, + {x86.R14, 0, 4, x86.R1, []byte{0x41, 0x89, 0x0E}}, + {x86.R14, 0, 2, x86.R1, []byte{0x66, 0x41, 0x89, 0x0E}}, + {x86.R14, 0, 1, x86.R1, []byte{0x41, 0x88, 0x0E}}, + {x86.R15, 0, 8, x86.R0, []byte{0x49, 0x89, 0x07}}, + {x86.R15, 0, 4, x86.R0, []byte{0x41, 0x89, 0x07}}, + {x86.R15, 0, 2, x86.R0, []byte{0x66, 0x41, 0x89, 0x07}}, + {x86.R15, 0, 1, x86.R0, []byte{0x41, 0x88, 0x07}}, // Offset of 1 - {x86.RAX, 1, 8, x86.R15, []byte{0x4C, 0x89, 0x78, 0x01}}, - {x86.RAX, 1, 4, x86.R15, []byte{0x44, 0x89, 0x78, 0x01}}, - {x86.RAX, 1, 2, x86.R15, []byte{0x66, 0x44, 0x89, 0x78, 0x01}}, - {x86.RAX, 1, 1, x86.R15, []byte{0x44, 0x88, 0x78, 0x01}}, - {x86.RCX, 1, 8, x86.R14, []byte{0x4C, 0x89, 0x71, 0x01}}, - {x86.RCX, 1, 4, x86.R14, []byte{0x44, 0x89, 0x71, 0x01}}, - {x86.RCX, 1, 2, x86.R14, []byte{0x66, 0x44, 0x89, 0x71, 0x01}}, - {x86.RCX, 1, 1, x86.R14, []byte{0x44, 0x88, 0x71, 0x01}}, - {x86.RDX, 1, 8, x86.R13, []byte{0x4C, 0x89, 0x6A, 0x01}}, - {x86.RDX, 1, 4, x86.R13, []byte{0x44, 0x89, 0x6A, 0x01}}, - {x86.RDX, 1, 2, x86.R13, []byte{0x66, 0x44, 0x89, 0x6A, 0x01}}, - {x86.RDX, 1, 1, x86.R13, []byte{0x44, 0x88, 0x6A, 0x01}}, - {x86.RBX, 1, 8, x86.R12, []byte{0x4C, 0x89, 0x63, 0x01}}, - {x86.RBX, 1, 4, x86.R12, []byte{0x44, 0x89, 0x63, 0x01}}, - {x86.RBX, 1, 2, x86.R12, []byte{0x66, 0x44, 0x89, 0x63, 0x01}}, - {x86.RBX, 1, 1, x86.R12, []byte{0x44, 0x88, 0x63, 0x01}}, - {x86.RSP, 1, 8, x86.R11, []byte{0x4C, 0x89, 0x5C, 0x24, 0x01}}, - {x86.RSP, 1, 4, x86.R11, []byte{0x44, 0x89, 0x5C, 0x24, 0x01}}, - {x86.RSP, 1, 2, x86.R11, []byte{0x66, 0x44, 0x89, 0x5C, 0x24, 0x01}}, - {x86.RSP, 1, 1, x86.R11, []byte{0x44, 0x88, 0x5C, 0x24, 0x01}}, - {x86.RBP, 1, 8, x86.R10, []byte{0x4C, 0x89, 0x55, 0x01}}, - {x86.RBP, 1, 4, x86.R10, []byte{0x44, 0x89, 0x55, 0x01}}, - {x86.RBP, 1, 2, x86.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x01}}, - {x86.RBP, 1, 1, x86.R10, []byte{0x44, 0x88, 0x55, 0x01}}, - {x86.RSI, 1, 8, x86.R9, []byte{0x4C, 0x89, 0x4E, 0x01}}, - {x86.RSI, 1, 4, x86.R9, []byte{0x44, 0x89, 0x4E, 0x01}}, - {x86.RSI, 1, 2, x86.R9, []byte{0x66, 0x44, 0x89, 0x4E, 0x01}}, - {x86.RSI, 1, 1, x86.R9, []byte{0x44, 0x88, 0x4E, 0x01}}, - {x86.RDI, 1, 8, x86.R8, []byte{0x4C, 0x89, 0x47, 0x01}}, - {x86.RDI, 1, 4, x86.R8, []byte{0x44, 0x89, 0x47, 0x01}}, - {x86.RDI, 1, 2, x86.R8, []byte{0x66, 0x44, 0x89, 0x47, 0x01}}, - {x86.RDI, 1, 1, x86.R8, []byte{0x44, 0x88, 0x47, 0x01}}, - {x86.R8, 1, 8, x86.RDI, []byte{0x49, 0x89, 0x78, 0x01}}, - {x86.R8, 1, 4, x86.RDI, []byte{0x41, 0x89, 0x78, 0x01}}, - {x86.R8, 1, 2, x86.RDI, []byte{0x66, 0x41, 0x89, 0x78, 0x01}}, - {x86.R8, 1, 1, x86.RDI, []byte{0x41, 0x88, 0x78, 0x01}}, - {x86.R9, 1, 8, x86.RSI, []byte{0x49, 0x89, 0x71, 0x01}}, - {x86.R9, 1, 4, x86.RSI, []byte{0x41, 0x89, 0x71, 0x01}}, - {x86.R9, 1, 2, x86.RSI, []byte{0x66, 0x41, 0x89, 0x71, 0x01}}, - {x86.R9, 1, 1, x86.RSI, []byte{0x41, 0x88, 0x71, 0x01}}, - {x86.R10, 1, 8, x86.RBP, []byte{0x49, 0x89, 0x6A, 0x01}}, - {x86.R10, 1, 4, x86.RBP, []byte{0x41, 0x89, 0x6A, 0x01}}, - {x86.R10, 1, 2, x86.RBP, []byte{0x66, 0x41, 0x89, 0x6A, 0x01}}, - {x86.R10, 1, 1, x86.RBP, []byte{0x41, 0x88, 0x6A, 0x01}}, - {x86.R11, 1, 8, x86.RSP, []byte{0x49, 0x89, 0x63, 0x01}}, - {x86.R11, 1, 4, x86.RSP, []byte{0x41, 0x89, 0x63, 0x01}}, - {x86.R11, 1, 2, x86.RSP, []byte{0x66, 0x41, 0x89, 0x63, 0x01}}, - {x86.R11, 1, 1, x86.RSP, []byte{0x41, 0x88, 0x63, 0x01}}, - {x86.R12, 1, 8, x86.RBX, []byte{0x49, 0x89, 0x5C, 0x24, 0x01}}, - {x86.R12, 1, 4, x86.RBX, []byte{0x41, 0x89, 0x5C, 0x24, 0x01}}, - {x86.R12, 1, 2, x86.RBX, []byte{0x66, 0x41, 0x89, 0x5C, 0x24, 0x01}}, - {x86.R12, 1, 1, x86.RBX, []byte{0x41, 0x88, 0x5C, 0x24, 01}}, - {x86.R13, 1, 8, x86.RDX, []byte{0x49, 0x89, 0x55, 0x01}}, - {x86.R13, 1, 4, x86.RDX, []byte{0x41, 0x89, 0x55, 0x01}}, - {x86.R13, 1, 2, x86.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x01}}, - {x86.R13, 1, 1, x86.RDX, []byte{0x41, 0x88, 0x55, 0x01}}, - {x86.R14, 1, 8, x86.RCX, []byte{0x49, 0x89, 0x4E, 0x01}}, - {x86.R14, 1, 4, x86.RCX, []byte{0x41, 0x89, 0x4E, 0x01}}, - {x86.R14, 1, 2, x86.RCX, []byte{0x66, 0x41, 0x89, 0x4E, 0x01}}, - {x86.R14, 1, 1, x86.RCX, []byte{0x41, 0x88, 0x4E, 0x01}}, - {x86.R15, 1, 8, x86.RAX, []byte{0x49, 0x89, 0x47, 0x01}}, - {x86.R15, 1, 4, x86.RAX, []byte{0x41, 0x89, 0x47, 0x01}}, - {x86.R15, 1, 2, x86.RAX, []byte{0x66, 0x41, 0x89, 0x47, 0x01}}, - {x86.R15, 1, 1, x86.RAX, []byte{0x41, 0x88, 0x47, 0x01}}, + {x86.R0, 1, 8, x86.R15, []byte{0x4C, 0x89, 0x78, 0x01}}, + {x86.R0, 1, 4, x86.R15, []byte{0x44, 0x89, 0x78, 0x01}}, + {x86.R0, 1, 2, x86.R15, []byte{0x66, 0x44, 0x89, 0x78, 0x01}}, + {x86.R0, 1, 1, x86.R15, []byte{0x44, 0x88, 0x78, 0x01}}, + {x86.R1, 1, 8, x86.R14, []byte{0x4C, 0x89, 0x71, 0x01}}, + {x86.R1, 1, 4, x86.R14, []byte{0x44, 0x89, 0x71, 0x01}}, + {x86.R1, 1, 2, x86.R14, []byte{0x66, 0x44, 0x89, 0x71, 0x01}}, + {x86.R1, 1, 1, x86.R14, []byte{0x44, 0x88, 0x71, 0x01}}, + {x86.R2, 1, 8, x86.R13, []byte{0x4C, 0x89, 0x6A, 0x01}}, + {x86.R2, 1, 4, x86.R13, []byte{0x44, 0x89, 0x6A, 0x01}}, + {x86.R2, 1, 2, x86.R13, []byte{0x66, 0x44, 0x89, 0x6A, 0x01}}, + {x86.R2, 1, 1, x86.R13, []byte{0x44, 0x88, 0x6A, 0x01}}, + {x86.R3, 1, 8, x86.R12, []byte{0x4C, 0x89, 0x63, 0x01}}, + {x86.R3, 1, 4, x86.R12, []byte{0x44, 0x89, 0x63, 0x01}}, + {x86.R3, 1, 2, x86.R12, []byte{0x66, 0x44, 0x89, 0x63, 0x01}}, + {x86.R3, 1, 1, x86.R12, []byte{0x44, 0x88, 0x63, 0x01}}, + {x86.SP, 1, 8, x86.R11, []byte{0x4C, 0x89, 0x5C, 0x24, 0x01}}, + {x86.SP, 1, 4, x86.R11, []byte{0x44, 0x89, 0x5C, 0x24, 0x01}}, + {x86.SP, 1, 2, x86.R11, []byte{0x66, 0x44, 0x89, 0x5C, 0x24, 0x01}}, + {x86.SP, 1, 1, x86.R11, []byte{0x44, 0x88, 0x5C, 0x24, 0x01}}, + {x86.R5, 1, 8, x86.R10, []byte{0x4C, 0x89, 0x55, 0x01}}, + {x86.R5, 1, 4, x86.R10, []byte{0x44, 0x89, 0x55, 0x01}}, + {x86.R5, 1, 2, x86.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x01}}, + {x86.R5, 1, 1, x86.R10, []byte{0x44, 0x88, 0x55, 0x01}}, + {x86.R6, 1, 8, x86.R9, []byte{0x4C, 0x89, 0x4E, 0x01}}, + {x86.R6, 1, 4, x86.R9, []byte{0x44, 0x89, 0x4E, 0x01}}, + {x86.R6, 1, 2, x86.R9, []byte{0x66, 0x44, 0x89, 0x4E, 0x01}}, + {x86.R6, 1, 1, x86.R9, []byte{0x44, 0x88, 0x4E, 0x01}}, + {x86.R7, 1, 8, x86.R8, []byte{0x4C, 0x89, 0x47, 0x01}}, + {x86.R7, 1, 4, x86.R8, []byte{0x44, 0x89, 0x47, 0x01}}, + {x86.R7, 1, 2, x86.R8, []byte{0x66, 0x44, 0x89, 0x47, 0x01}}, + {x86.R7, 1, 1, x86.R8, []byte{0x44, 0x88, 0x47, 0x01}}, + {x86.R8, 1, 8, x86.R7, []byte{0x49, 0x89, 0x78, 0x01}}, + {x86.R8, 1, 4, x86.R7, []byte{0x41, 0x89, 0x78, 0x01}}, + {x86.R8, 1, 2, x86.R7, []byte{0x66, 0x41, 0x89, 0x78, 0x01}}, + {x86.R8, 1, 1, x86.R7, []byte{0x41, 0x88, 0x78, 0x01}}, + {x86.R9, 1, 8, x86.R6, []byte{0x49, 0x89, 0x71, 0x01}}, + {x86.R9, 1, 4, x86.R6, []byte{0x41, 0x89, 0x71, 0x01}}, + {x86.R9, 1, 2, x86.R6, []byte{0x66, 0x41, 0x89, 0x71, 0x01}}, + {x86.R9, 1, 1, x86.R6, []byte{0x41, 0x88, 0x71, 0x01}}, + {x86.R10, 1, 8, x86.R5, []byte{0x49, 0x89, 0x6A, 0x01}}, + {x86.R10, 1, 4, x86.R5, []byte{0x41, 0x89, 0x6A, 0x01}}, + {x86.R10, 1, 2, x86.R5, []byte{0x66, 0x41, 0x89, 0x6A, 0x01}}, + {x86.R10, 1, 1, x86.R5, []byte{0x41, 0x88, 0x6A, 0x01}}, + {x86.R11, 1, 8, x86.SP, []byte{0x49, 0x89, 0x63, 0x01}}, + {x86.R11, 1, 4, x86.SP, []byte{0x41, 0x89, 0x63, 0x01}}, + {x86.R11, 1, 2, x86.SP, []byte{0x66, 0x41, 0x89, 0x63, 0x01}}, + {x86.R11, 1, 1, x86.SP, []byte{0x41, 0x88, 0x63, 0x01}}, + {x86.R12, 1, 8, x86.R3, []byte{0x49, 0x89, 0x5C, 0x24, 0x01}}, + {x86.R12, 1, 4, x86.R3, []byte{0x41, 0x89, 0x5C, 0x24, 0x01}}, + {x86.R12, 1, 2, x86.R3, []byte{0x66, 0x41, 0x89, 0x5C, 0x24, 0x01}}, + {x86.R12, 1, 1, x86.R3, []byte{0x41, 0x88, 0x5C, 0x24, 01}}, + {x86.R13, 1, 8, x86.R2, []byte{0x49, 0x89, 0x55, 0x01}}, + {x86.R13, 1, 4, x86.R2, []byte{0x41, 0x89, 0x55, 0x01}}, + {x86.R13, 1, 2, x86.R2, []byte{0x66, 0x41, 0x89, 0x55, 0x01}}, + {x86.R13, 1, 1, x86.R2, []byte{0x41, 0x88, 0x55, 0x01}}, + {x86.R14, 1, 8, x86.R1, []byte{0x49, 0x89, 0x4E, 0x01}}, + {x86.R14, 1, 4, x86.R1, []byte{0x41, 0x89, 0x4E, 0x01}}, + {x86.R14, 1, 2, x86.R1, []byte{0x66, 0x41, 0x89, 0x4E, 0x01}}, + {x86.R14, 1, 1, x86.R1, []byte{0x41, 0x88, 0x4E, 0x01}}, + {x86.R15, 1, 8, x86.R0, []byte{0x49, 0x89, 0x47, 0x01}}, + {x86.R15, 1, 4, x86.R0, []byte{0x41, 0x89, 0x47, 0x01}}, + {x86.R15, 1, 2, x86.R0, []byte{0x66, 0x41, 0x89, 0x47, 0x01}}, + {x86.R15, 1, 1, x86.R0, []byte{0x41, 0x88, 0x47, 0x01}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Sub_test.go b/src/x86/Sub_test.go index 5502edc..bd58796 100644 --- a/src/x86/Sub_test.go +++ b/src/x86/Sub_test.go @@ -14,14 +14,14 @@ func TestSubRegisterNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0x83, 0xE8, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0x83, 0xE9, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0x83, 0xEA, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0x83, 0xEB, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0x83, 0xEC, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0x83, 0xED, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0x83, 0xEE, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0x83, 0xEF, 0x01}}, + {x86.R0, 1, []byte{0x48, 0x83, 0xE8, 0x01}}, + {x86.R1, 1, []byte{0x48, 0x83, 0xE9, 0x01}}, + {x86.R2, 1, []byte{0x48, 0x83, 0xEA, 0x01}}, + {x86.R3, 1, []byte{0x48, 0x83, 0xEB, 0x01}}, + {x86.SP, 1, []byte{0x48, 0x83, 0xEC, 0x01}}, + {x86.R5, 1, []byte{0x48, 0x83, 0xED, 0x01}}, + {x86.R6, 1, []byte{0x48, 0x83, 0xEE, 0x01}}, + {x86.R7, 1, []byte{0x48, 0x83, 0xEF, 0x01}}, {x86.R8, 1, []byte{0x49, 0x83, 0xE8, 0x01}}, {x86.R9, 1, []byte{0x49, 0x83, 0xE9, 0x01}}, {x86.R10, 1, []byte{0x49, 0x83, 0xEA, 0x01}}, @@ -31,14 +31,14 @@ func TestSubRegisterNumber(t *testing.T) { {x86.R14, 1, []byte{0x49, 0x83, 0xEE, 0x01}}, {x86.R15, 1, []byte{0x49, 0x83, 0xEF, 0x01}}, - {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFF, []byte{0x48, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -62,22 +62,22 @@ func TestSubRegisterRegister(t *testing.T) { Right cpu.Register Code []byte }{ - {x86.RAX, x86.R15, []byte{0x4C, 0x29, 0xF8}}, - {x86.RCX, x86.R14, []byte{0x4C, 0x29, 0xF1}}, - {x86.RDX, x86.R13, []byte{0x4C, 0x29, 0xEA}}, - {x86.RBX, x86.R12, []byte{0x4C, 0x29, 0xE3}}, - {x86.RSP, x86.R11, []byte{0x4C, 0x29, 0xDC}}, - {x86.RBP, x86.R10, []byte{0x4C, 0x29, 0xD5}}, - {x86.RSI, x86.R9, []byte{0x4C, 0x29, 0xCE}}, - {x86.RDI, x86.R8, []byte{0x4C, 0x29, 0xC7}}, - {x86.R8, x86.RDI, []byte{0x49, 0x29, 0xF8}}, - {x86.R9, x86.RSI, []byte{0x49, 0x29, 0xF1}}, - {x86.R10, x86.RBP, []byte{0x49, 0x29, 0xEA}}, - {x86.R11, x86.RSP, []byte{0x49, 0x29, 0xE3}}, - {x86.R12, x86.RBX, []byte{0x49, 0x29, 0xDC}}, - {x86.R13, x86.RDX, []byte{0x49, 0x29, 0xD5}}, - {x86.R14, x86.RCX, []byte{0x49, 0x29, 0xCE}}, - {x86.R15, x86.RAX, []byte{0x49, 0x29, 0xC7}}, + {x86.R0, x86.R15, []byte{0x4C, 0x29, 0xF8}}, + {x86.R1, x86.R14, []byte{0x4C, 0x29, 0xF1}}, + {x86.R2, x86.R13, []byte{0x4C, 0x29, 0xEA}}, + {x86.R3, x86.R12, []byte{0x4C, 0x29, 0xE3}}, + {x86.SP, x86.R11, []byte{0x4C, 0x29, 0xDC}}, + {x86.R5, x86.R10, []byte{0x4C, 0x29, 0xD5}}, + {x86.R6, x86.R9, []byte{0x4C, 0x29, 0xCE}}, + {x86.R7, x86.R8, []byte{0x4C, 0x29, 0xC7}}, + {x86.R8, x86.R7, []byte{0x49, 0x29, 0xF8}}, + {x86.R9, x86.R6, []byte{0x49, 0x29, 0xF1}}, + {x86.R10, x86.R5, []byte{0x49, 0x29, 0xEA}}, + {x86.R11, x86.SP, []byte{0x49, 0x29, 0xE3}}, + {x86.R12, x86.R3, []byte{0x49, 0x29, 0xDC}}, + {x86.R13, x86.R2, []byte{0x49, 0x29, 0xD5}}, + {x86.R14, x86.R1, []byte{0x49, 0x29, 0xCE}}, + {x86.R15, x86.R0, []byte{0x49, 0x29, 0xC7}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/Xor_test.go b/src/x86/Xor_test.go index 6456cb4..770b688 100644 --- a/src/x86/Xor_test.go +++ b/src/x86/Xor_test.go @@ -14,14 +14,14 @@ func TestXorRegisterNumber(t *testing.T) { Number int Code []byte }{ - {x86.RAX, 1, []byte{0x48, 0x83, 0xF0, 0x01}}, - {x86.RCX, 1, []byte{0x48, 0x83, 0xF1, 0x01}}, - {x86.RDX, 1, []byte{0x48, 0x83, 0xF2, 0x01}}, - {x86.RBX, 1, []byte{0x48, 0x83, 0xF3, 0x01}}, - {x86.RSP, 1, []byte{0x48, 0x83, 0xF4, 0x01}}, - {x86.RBP, 1, []byte{0x48, 0x83, 0xF5, 0x01}}, - {x86.RSI, 1, []byte{0x48, 0x83, 0xF6, 0x01}}, - {x86.RDI, 1, []byte{0x48, 0x83, 0xF7, 0x01}}, + {x86.R0, 1, []byte{0x48, 0x83, 0xF0, 0x01}}, + {x86.R1, 1, []byte{0x48, 0x83, 0xF1, 0x01}}, + {x86.R2, 1, []byte{0x48, 0x83, 0xF2, 0x01}}, + {x86.R3, 1, []byte{0x48, 0x83, 0xF3, 0x01}}, + {x86.SP, 1, []byte{0x48, 0x83, 0xF4, 0x01}}, + {x86.R5, 1, []byte{0x48, 0x83, 0xF5, 0x01}}, + {x86.R6, 1, []byte{0x48, 0x83, 0xF6, 0x01}}, + {x86.R7, 1, []byte{0x48, 0x83, 0xF7, 0x01}}, {x86.R8, 1, []byte{0x49, 0x83, 0xF0, 0x01}}, {x86.R9, 1, []byte{0x49, 0x83, 0xF1, 0x01}}, {x86.R10, 1, []byte{0x49, 0x83, 0xF2, 0x01}}, @@ -31,14 +31,14 @@ func TestXorRegisterNumber(t *testing.T) { {x86.R14, 1, []byte{0x49, 0x83, 0xF6, 0x01}}, {x86.R15, 1, []byte{0x49, 0x83, 0xF7, 0x01}}, - {x86.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF3, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF4, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF5, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, - {x86.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF7, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R0, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R1, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R2, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R3, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF3, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.SP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF4, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R5, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF5, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R6, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x86.R7, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF7, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF0, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF1, 0xFF, 0xFF, 0xFF, 0x7F}}, {x86.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF2, 0xFF, 0xFF, 0xFF, 0x7F}}, @@ -62,22 +62,22 @@ func TestXorRegisterRegister(t *testing.T) { Right cpu.Register Code []byte }{ - {x86.RAX, x86.R15, []byte{0x4C, 0x31, 0xF8}}, - {x86.RCX, x86.R14, []byte{0x4C, 0x31, 0xF1}}, - {x86.RDX, x86.R13, []byte{0x4C, 0x31, 0xEA}}, - {x86.RBX, x86.R12, []byte{0x4C, 0x31, 0xE3}}, - {x86.RSP, x86.R11, []byte{0x4C, 0x31, 0xDC}}, - {x86.RBP, x86.R10, []byte{0x4C, 0x31, 0xD5}}, - {x86.RSI, x86.R9, []byte{0x4C, 0x31, 0xCE}}, - {x86.RDI, x86.R8, []byte{0x4C, 0x31, 0xC7}}, - {x86.R8, x86.RDI, []byte{0x49, 0x31, 0xF8}}, - {x86.R9, x86.RSI, []byte{0x49, 0x31, 0xF1}}, - {x86.R10, x86.RBP, []byte{0x49, 0x31, 0xEA}}, - {x86.R11, x86.RSP, []byte{0x49, 0x31, 0xE3}}, - {x86.R12, x86.RBX, []byte{0x49, 0x31, 0xDC}}, - {x86.R13, x86.RDX, []byte{0x49, 0x31, 0xD5}}, - {x86.R14, x86.RCX, []byte{0x49, 0x31, 0xCE}}, - {x86.R15, x86.RAX, []byte{0x49, 0x31, 0xC7}}, + {x86.R0, x86.R15, []byte{0x4C, 0x31, 0xF8}}, + {x86.R1, x86.R14, []byte{0x4C, 0x31, 0xF1}}, + {x86.R2, x86.R13, []byte{0x4C, 0x31, 0xEA}}, + {x86.R3, x86.R12, []byte{0x4C, 0x31, 0xE3}}, + {x86.SP, x86.R11, []byte{0x4C, 0x31, 0xDC}}, + {x86.R5, x86.R10, []byte{0x4C, 0x31, 0xD5}}, + {x86.R6, x86.R9, []byte{0x4C, 0x31, 0xCE}}, + {x86.R7, x86.R8, []byte{0x4C, 0x31, 0xC7}}, + {x86.R8, x86.R7, []byte{0x49, 0x31, 0xF8}}, + {x86.R9, x86.R6, []byte{0x49, 0x31, 0xF1}}, + {x86.R10, x86.R5, []byte{0x49, 0x31, 0xEA}}, + {x86.R11, x86.SP, []byte{0x49, 0x31, 0xE3}}, + {x86.R12, x86.R3, []byte{0x49, 0x31, 0xDC}}, + {x86.R13, x86.R2, []byte{0x49, 0x31, 0xD5}}, + {x86.R14, x86.R1, []byte{0x49, 0x31, 0xCE}}, + {x86.R15, x86.R0, []byte{0x49, 0x31, 0xC7}}, } for _, pattern := range usagePatterns { diff --git a/src/x86/encode.go b/src/x86/encode.go index d09fd44..732a156 100644 --- a/src/x86/encode.go +++ b/src/x86/encode.go @@ -23,7 +23,7 @@ func encode(code []byte, mod AddressMode, reg cpu.Register, rm cpu.Register, num rm &= 0b111 } - if w != 0 || r != 0 || x != 0 || b != 0 || (numBytes == 1 && (reg == RSP || reg == RBP || reg == RSI || reg == RDI)) { + if w != 0 || r != 0 || x != 0 || b != 0 || (numBytes == 1 && (reg == SP || reg == R5 || reg == R6 || reg == R7)) { code = append(code, REX(w, r, x, b)) } diff --git a/src/x86/memoryAccess.go b/src/x86/memoryAccess.go index 42e6a37..c7559b8 100644 --- a/src/x86/memoryAccess.go +++ b/src/x86/memoryAccess.go @@ -12,7 +12,7 @@ func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Registe mod := AddressMemory - if offset != 0 || base == RBP || base == R13 { + if offset != 0 || base == R5 || base == R13 { mod = AddressMemoryOffset8 } @@ -22,7 +22,7 @@ func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Registe code = encode(code, mod, register, base, length, opCode) - if base == RSP || base == R12 { + if base == SP || base == R12 { code = append(code, SIB(Scale1, 0b100, 0b100)) } diff --git a/src/x86/memoryAccessDynamic.go b/src/x86/memoryAccessDynamic.go index 092fa64..f4eb76e 100644 --- a/src/x86/memoryAccessDynamic.go +++ b/src/x86/memoryAccessDynamic.go @@ -17,7 +17,7 @@ func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, register cpu. opCode = opCode8 } - if offset == RSP { + if offset == SP { offset, base = base, offset } @@ -40,7 +40,7 @@ func memoryAccessDynamic(code []byte, opCode8 byte, opCode32 byte, register cpu. base &= 0b111 } - if base == RBP || base == R13 { + if base == R5 || base == R13 { mod = AddressMemoryOffset8 } diff --git a/src/x86/x86_test.go b/src/x86/x86_test.go index 9d6b3bc..ece5699 100644 --- a/src/x86/x86_test.go +++ b/src/x86/x86_test.go @@ -10,7 +10,7 @@ import ( func TestX86(t *testing.T) { assert.DeepEqual(t, x86.Call(nil, 1), []byte{0xE8, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x86.CallAt(nil, 1), []byte{0xFF, 0x15, 0x01, 0x00, 0x00, 0x00}) - assert.DeepEqual(t, x86.ExtendRAXToRDX(nil), []byte{0x48, 0x99}) + assert.DeepEqual(t, x86.ExtendR0ToR2(nil), []byte{0x48, 0x99}) assert.DeepEqual(t, x86.MoveRegisterNumber(nil, 0, 1), []byte{0xB8, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x86.MoveRegisterNumber(nil, 1, 1), []byte{0xB9, 0x01, 0x00, 0x00, 0x00}) assert.DeepEqual(t, x86.Return(nil), []byte{0xC3}) From 9107a06df55ce154be6f78738a11426c7db27fbc Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 18 Apr 2025 14:48:59 +0200 Subject: [PATCH 1005/1012] Improved Windows support in the tokenizer --- src/token/Tokenize.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/token/Tokenize.go b/src/token/Tokenize.go index 9178314..f7c540b 100644 --- a/src/token/Tokenize.go +++ b/src/token/Tokenize.go @@ -9,7 +9,7 @@ func Tokenize(buffer []byte) List { for i < Position(len(buffer)) { switch buffer[i] { - case ' ', '\t': + case ' ', '\t', '\r': case ',': tokens = append(tokens, Token{Kind: Separator, Position: i, Length: 1}) case '(': From 42bbe5d4ede21811e14391fb64d2aeaf6cc6f89a Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 18 Apr 2025 14:48:59 +0200 Subject: [PATCH 1006/1012] Improved Windows support in the tokenizer --- src/token/Tokenize.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/token/Tokenize.go b/src/token/Tokenize.go index 9178314..f7c540b 100644 --- a/src/token/Tokenize.go +++ b/src/token/Tokenize.go @@ -9,7 +9,7 @@ func Tokenize(buffer []byte) List { for i < Position(len(buffer)) { switch buffer[i] { - case ' ', '\t': + case ' ', '\t', '\r': case ',': tokens = append(tokens, Token{Kind: Separator, Position: i, Length: 1}) case '(': From aae3a552bbf294dd8e1d34b558c52985eba4734c Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 25 Apr 2025 12:12:05 +0200 Subject: [PATCH 1007/1012] Updated dependencies --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ebfb164..ad00fd8 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,4 @@ require ( git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12 ) -require golang.org/x/sys v0.31.0 // indirect +require golang.org/x/sys v0.32.0 // indirect diff --git a/go.sum b/go.sum index 452b14f..8885ade 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,5 @@ git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf h1:BQWa5GKNUsA5CSUa/ git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf/go.mod h1:y9jGII9JFiF1HNIju0u87OyPCt82xKCtqnAFyEreCDo= git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12 h1:DML0/oUE0lzX8wq/b2+JizZs/iYxeqxPCwT97LFhW+A= git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12/go.mod h1:s6wrC+nGE0YMz9K3BBjHoGCKkDZTUKnGbL0BqgzZkC4= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= From 58944289a9fafa4b636153820456c150084b4fa9 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 25 Apr 2025 12:12:05 +0200 Subject: [PATCH 1008/1012] Updated dependencies --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ebfb164..ad00fd8 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,4 @@ require ( git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12 ) -require golang.org/x/sys v0.31.0 // indirect +require golang.org/x/sys v0.32.0 // indirect diff --git a/go.sum b/go.sum index 452b14f..8885ade 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,5 @@ git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf h1:BQWa5GKNUsA5CSUa/ git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf/go.mod h1:y9jGII9JFiF1HNIju0u87OyPCt82xKCtqnAFyEreCDo= git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12 h1:DML0/oUE0lzX8wq/b2+JizZs/iYxeqxPCwT97LFhW+A= git.urbach.dev/go/color v0.0.0-20250311132441-f75ff8da5a12/go.mod h1:s6wrC+nGE0YMz9K3BBjHoGCKkDZTUKnGbL0BqgzZkC4= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= From 9d6356e8a9db4d1536ddc8a1e832e1672aff4a04 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 29 Apr 2025 18:12:51 +0200 Subject: [PATCH 1009/1012] Implemented code pointers on arm64 --- src/asmc/armCompiler.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/asmc/armCompiler.go b/src/asmc/armCompiler.go index 0ce0434..e8529a0 100644 --- a/src/asmc/armCompiler.go +++ b/src/asmc/armCompiler.go @@ -80,7 +80,7 @@ func (c *armCompiler) handleMoveInstruction(instruction asm.Instruction) { if operands.Label.Type == asm.DataLabel { c.dataPointers = append(c.dataPointers, c.createDataPointer(operands, position)) } else { - panic("not implemented") + c.codePointers = append(c.codePointers, c.createCodePointer(operands, position)) } } } @@ -398,6 +398,24 @@ func (c *armCompiler) handleNegateInstruction(instruction asm.Instruction) { } } +func (c *armCompiler) createCodePointer(operands asm.RegisterLabel, position Address) *pointer { + return &pointer{ + Position: position, + OpSize: 0, + Size: 4, + Resolve: func() Address { + destination, exists := c.codeLabels[operands.Label.Name] + + if !exists { + panic("unknown label") + } + + distance := destination - position + 8 + return arm.LoadAddress(operands.Register, int(distance)) + }, + } +} + func (c *armCompiler) createDataPointer(operands asm.RegisterLabel, position Address) *pointer { return &pointer{ Position: position, From 2e00b28016872cc2b75a44d380c7deb73e2463cf Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 29 Apr 2025 18:12:51 +0200 Subject: [PATCH 1010/1012] Implemented code pointers on arm64 --- src/asmc/armCompiler.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/asmc/armCompiler.go b/src/asmc/armCompiler.go index 0ce0434..e8529a0 100644 --- a/src/asmc/armCompiler.go +++ b/src/asmc/armCompiler.go @@ -80,7 +80,7 @@ func (c *armCompiler) handleMoveInstruction(instruction asm.Instruction) { if operands.Label.Type == asm.DataLabel { c.dataPointers = append(c.dataPointers, c.createDataPointer(operands, position)) } else { - panic("not implemented") + c.codePointers = append(c.codePointers, c.createCodePointer(operands, position)) } } } @@ -398,6 +398,24 @@ func (c *armCompiler) handleNegateInstruction(instruction asm.Instruction) { } } +func (c *armCompiler) createCodePointer(operands asm.RegisterLabel, position Address) *pointer { + return &pointer{ + Position: position, + OpSize: 0, + Size: 4, + Resolve: func() Address { + destination, exists := c.codeLabels[operands.Label.Name] + + if !exists { + panic("unknown label") + } + + distance := destination - position + 8 + return arm.LoadAddress(operands.Register, int(distance)) + }, + } +} + func (c *armCompiler) createDataPointer(operands asm.RegisterLabel, position Address) *pointer { return &pointer{ Position: position, From 60691d09ed8aa9d89b080a03b3be14590067f074 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 23 May 2025 19:35:52 +0200 Subject: [PATCH 1011/1012] Added sparse file support --- src/compiler/Write.go | 19 +++++++------------ src/compiler/WriteFile.go | 8 +------- src/elf/ELF.go | 7 +++---- src/elf/ELF_test.go | 4 ++-- src/macho/MachO.go | 7 +++---- src/macho/MachO_test.go | 4 ++-- src/pe/EXE.go | 8 ++++---- src/pe/EXE_test.go | 4 ++-- src/readme.md | 1 + src/test/Discard.go | 7 +++++++ 10 files changed, 32 insertions(+), 37 deletions(-) create mode 100644 src/test/Discard.go diff --git a/src/compiler/Write.go b/src/compiler/Write.go index cc1981f..f904b6b 100644 --- a/src/compiler/Write.go +++ b/src/compiler/Write.go @@ -1,7 +1,6 @@ package compiler import ( - "bufio" "io" "git.urbach.dev/cli/q/src/config" @@ -13,24 +12,20 @@ import ( ) // Write writes the executable to the given writer. -func (r *Result) Write(writer io.Writer) error { - return write(writer, r.Code, r.Data, r.DLLs) +func (r *Result) Write(writer io.WriteSeeker) { + write(writer, r.Code, r.Data, r.DLLs) } // write writes an executable file to the given writer. -func write(writer io.Writer, code []byte, data []byte, dlls dll.List) error { - buffer := bufio.NewWriter(writer) - +func write(writer io.WriteSeeker, code []byte, data []byte, dlls dll.List) { switch config.TargetOS { case config.Linux: - elf.Write(buffer, code, data) + elf.Write(writer, code, data) case config.Mac: - macho.Write(buffer, code, data) + macho.Write(writer, code, data) case config.Web: - wasm.Write(buffer, code, data) + wasm.Write(writer, code, data) case config.Windows: - pe.Write(buffer, code, data, dlls) + pe.Write(writer, code, data, dlls) } - - return buffer.Flush() } diff --git a/src/compiler/WriteFile.go b/src/compiler/WriteFile.go index 009562e..067ebfd 100644 --- a/src/compiler/WriteFile.go +++ b/src/compiler/WriteFile.go @@ -10,13 +10,7 @@ func (r *Result) WriteFile(path string) error { return err } - err = r.Write(file) - - if err != nil { - file.Close() - return err - } - + r.Write(file) err = file.Close() if err != nil { diff --git a/src/elf/ELF.go b/src/elf/ELF.go index 29c828a..0a03120 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -1,7 +1,6 @@ package elf import ( - "bytes" "encoding/binary" "io" @@ -21,7 +20,7 @@ type ELF struct { } // Write writes the ELF64 format to the given writer. -func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { +func Write(writer io.WriteSeeker, codeBytes []byte, dataBytes []byte) { sections := exe.MakeSections(HeaderEnd, codeBytes, dataBytes) code := sections[0] data := sections[1] @@ -76,9 +75,9 @@ func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.CodeHeader) binary.Write(writer, binary.LittleEndian, &elf.DataHeader) - writer.Write(bytes.Repeat([]byte{0x00}, code.Padding)) + writer.Seek(int64(code.Padding), io.SeekCurrent) writer.Write(code.Bytes) - writer.Write(bytes.Repeat([]byte{0x00}, data.Padding)) + writer.Seek(int64(data.Padding), io.SeekCurrent) writer.Write(data.Bytes) if config.Sections { diff --git a/src/elf/ELF_test.go b/src/elf/ELF_test.go index 74bd71c..528bd75 100644 --- a/src/elf/ELF_test.go +++ b/src/elf/ELF_test.go @@ -1,12 +1,12 @@ package elf_test import ( - "io" "testing" "git.urbach.dev/cli/q/src/elf" + "git.urbach.dev/cli/q/src/test" ) func TestWrite(t *testing.T) { - elf.Write(io.Discard, nil, nil) + elf.Write(&test.Discard{}, nil, nil) } diff --git a/src/macho/MachO.go b/src/macho/MachO.go index b305b6a..8588b3d 100644 --- a/src/macho/MachO.go +++ b/src/macho/MachO.go @@ -1,7 +1,6 @@ package macho import ( - "bytes" "encoding/binary" "io" @@ -24,7 +23,7 @@ type MachO struct { } // Write writes the Mach-O format to the given writer. -func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { +func Write(writer io.WriteSeeker, codeBytes []byte, dataBytes []byte) { sections := exe.MakeSections(HeaderEnd, codeBytes, dataBytes) code := sections[0] data := sections[1] @@ -116,8 +115,8 @@ func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { binary.Write(writer, binary.LittleEndian, &m.CodeHeader) binary.Write(writer, binary.LittleEndian, &m.DataHeader) binary.Write(writer, binary.LittleEndian, &m.UnixThread) - writer.Write(bytes.Repeat([]byte{0x00}, code.Padding)) + writer.Seek(int64(code.Padding), io.SeekCurrent) writer.Write(code.Bytes) - writer.Write(bytes.Repeat([]byte{0x00}, data.Padding)) + writer.Seek(int64(data.Padding), io.SeekCurrent) writer.Write(data.Bytes) } diff --git a/src/macho/MachO_test.go b/src/macho/MachO_test.go index df2d04c..f3cf5f9 100644 --- a/src/macho/MachO_test.go +++ b/src/macho/MachO_test.go @@ -1,12 +1,12 @@ package macho_test import ( - "io" "testing" "git.urbach.dev/cli/q/src/macho" + "git.urbach.dev/cli/q/src/test" ) func TestWrite(t *testing.T) { - macho.Write(io.Discard, nil, nil) + macho.Write(&test.Discard{}, nil, nil) } diff --git a/src/pe/EXE.go b/src/pe/EXE.go index 740d0a5..210208b 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -24,7 +24,7 @@ type EXE struct { } // Write writes the EXE file to the given writer. -func Write(writer io.Writer, codeBytes []byte, dataBytes []byte, dlls dll.List) { +func Write(writer io.WriteSeeker, codeBytes []byte, dataBytes []byte, dlls dll.List) { var ( sections = exe.MakeSections(HeaderEnd, codeBytes, dataBytes, nil) code = sections[0] @@ -144,10 +144,10 @@ func Write(writer io.Writer, codeBytes []byte, dataBytes []byte, dlls dll.List) binary.Write(writer, binary.LittleEndian, &pe.NTHeader) binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) binary.Write(writer, binary.LittleEndian, &pe.Sections) - writer.Write(bytes.Repeat([]byte{0x00}, code.Padding)) + writer.Seek(int64(code.Padding), io.SeekCurrent) writer.Write(code.Bytes) - writer.Write(bytes.Repeat([]byte{0x00}, data.Padding)) + writer.Seek(int64(data.Padding), io.SeekCurrent) writer.Write(data.Bytes) - writer.Write(bytes.Repeat([]byte{0x00}, imports.Padding)) + writer.Seek(int64(imports.Padding), io.SeekCurrent) writer.Write(imports.Bytes) } diff --git a/src/pe/EXE_test.go b/src/pe/EXE_test.go index d69d46a..4059778 100644 --- a/src/pe/EXE_test.go +++ b/src/pe/EXE_test.go @@ -1,12 +1,12 @@ package pe_test import ( - "io" "testing" "git.urbach.dev/cli/q/src/pe" + "git.urbach.dev/cli/q/src/test" ) func TestWrite(t *testing.T) { - pe.Write(io.Discard, nil, nil, nil) + pe.Write(&test.Discard{}, nil, nil, nil) } diff --git a/src/readme.md b/src/readme.md index 83641ce..417d18c 100644 --- a/src/readme.md +++ b/src/readme.md @@ -26,6 +26,7 @@ - [scope](scope) - Defines a `Scope` used for code blocks - [set](set) - Generic set implementation - [sizeof](sizeof) - Calculates the byte size of numbers +- [test](test) - Testing utilities - [token](token) - Converts a file to tokens with the `Tokenize` function - [types](types) - Type system - [x86](x86) - x86-64 implementation diff --git a/src/test/Discard.go b/src/test/Discard.go new file mode 100644 index 0000000..62cf0c0 --- /dev/null +++ b/src/test/Discard.go @@ -0,0 +1,7 @@ +package test + +// Discard implements a no-op WriteSeeker. +type Discard struct{} + +func (w *Discard) Write(_ []byte) (int, error) { return 0, nil } +func (w *Discard) Seek(_ int64, _ int) (int64, error) { return 0, nil } From dc315b8076be6931edb056db175004adcdcc9fe2 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 23 May 2025 19:35:52 +0200 Subject: [PATCH 1012/1012] Added sparse file support --- src/compiler/Write.go | 19 +++++++------------ src/compiler/WriteFile.go | 8 +------- src/elf/ELF.go | 7 +++---- src/elf/ELF_test.go | 4 ++-- src/macho/MachO.go | 7 +++---- src/macho/MachO_test.go | 4 ++-- src/pe/EXE.go | 8 ++++---- src/pe/EXE_test.go | 4 ++-- src/readme.md | 1 + src/test/Discard.go | 7 +++++++ 10 files changed, 32 insertions(+), 37 deletions(-) create mode 100644 src/test/Discard.go diff --git a/src/compiler/Write.go b/src/compiler/Write.go index cc1981f..f904b6b 100644 --- a/src/compiler/Write.go +++ b/src/compiler/Write.go @@ -1,7 +1,6 @@ package compiler import ( - "bufio" "io" "git.urbach.dev/cli/q/src/config" @@ -13,24 +12,20 @@ import ( ) // Write writes the executable to the given writer. -func (r *Result) Write(writer io.Writer) error { - return write(writer, r.Code, r.Data, r.DLLs) +func (r *Result) Write(writer io.WriteSeeker) { + write(writer, r.Code, r.Data, r.DLLs) } // write writes an executable file to the given writer. -func write(writer io.Writer, code []byte, data []byte, dlls dll.List) error { - buffer := bufio.NewWriter(writer) - +func write(writer io.WriteSeeker, code []byte, data []byte, dlls dll.List) { switch config.TargetOS { case config.Linux: - elf.Write(buffer, code, data) + elf.Write(writer, code, data) case config.Mac: - macho.Write(buffer, code, data) + macho.Write(writer, code, data) case config.Web: - wasm.Write(buffer, code, data) + wasm.Write(writer, code, data) case config.Windows: - pe.Write(buffer, code, data, dlls) + pe.Write(writer, code, data, dlls) } - - return buffer.Flush() } diff --git a/src/compiler/WriteFile.go b/src/compiler/WriteFile.go index 009562e..067ebfd 100644 --- a/src/compiler/WriteFile.go +++ b/src/compiler/WriteFile.go @@ -10,13 +10,7 @@ func (r *Result) WriteFile(path string) error { return err } - err = r.Write(file) - - if err != nil { - file.Close() - return err - } - + r.Write(file) err = file.Close() if err != nil { diff --git a/src/elf/ELF.go b/src/elf/ELF.go index 29c828a..0a03120 100644 --- a/src/elf/ELF.go +++ b/src/elf/ELF.go @@ -1,7 +1,6 @@ package elf import ( - "bytes" "encoding/binary" "io" @@ -21,7 +20,7 @@ type ELF struct { } // Write writes the ELF64 format to the given writer. -func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { +func Write(writer io.WriteSeeker, codeBytes []byte, dataBytes []byte) { sections := exe.MakeSections(HeaderEnd, codeBytes, dataBytes) code := sections[0] data := sections[1] @@ -76,9 +75,9 @@ func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { binary.Write(writer, binary.LittleEndian, &elf.Header) binary.Write(writer, binary.LittleEndian, &elf.CodeHeader) binary.Write(writer, binary.LittleEndian, &elf.DataHeader) - writer.Write(bytes.Repeat([]byte{0x00}, code.Padding)) + writer.Seek(int64(code.Padding), io.SeekCurrent) writer.Write(code.Bytes) - writer.Write(bytes.Repeat([]byte{0x00}, data.Padding)) + writer.Seek(int64(data.Padding), io.SeekCurrent) writer.Write(data.Bytes) if config.Sections { diff --git a/src/elf/ELF_test.go b/src/elf/ELF_test.go index 74bd71c..528bd75 100644 --- a/src/elf/ELF_test.go +++ b/src/elf/ELF_test.go @@ -1,12 +1,12 @@ package elf_test import ( - "io" "testing" "git.urbach.dev/cli/q/src/elf" + "git.urbach.dev/cli/q/src/test" ) func TestWrite(t *testing.T) { - elf.Write(io.Discard, nil, nil) + elf.Write(&test.Discard{}, nil, nil) } diff --git a/src/macho/MachO.go b/src/macho/MachO.go index b305b6a..8588b3d 100644 --- a/src/macho/MachO.go +++ b/src/macho/MachO.go @@ -1,7 +1,6 @@ package macho import ( - "bytes" "encoding/binary" "io" @@ -24,7 +23,7 @@ type MachO struct { } // Write writes the Mach-O format to the given writer. -func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { +func Write(writer io.WriteSeeker, codeBytes []byte, dataBytes []byte) { sections := exe.MakeSections(HeaderEnd, codeBytes, dataBytes) code := sections[0] data := sections[1] @@ -116,8 +115,8 @@ func Write(writer io.Writer, codeBytes []byte, dataBytes []byte) { binary.Write(writer, binary.LittleEndian, &m.CodeHeader) binary.Write(writer, binary.LittleEndian, &m.DataHeader) binary.Write(writer, binary.LittleEndian, &m.UnixThread) - writer.Write(bytes.Repeat([]byte{0x00}, code.Padding)) + writer.Seek(int64(code.Padding), io.SeekCurrent) writer.Write(code.Bytes) - writer.Write(bytes.Repeat([]byte{0x00}, data.Padding)) + writer.Seek(int64(data.Padding), io.SeekCurrent) writer.Write(data.Bytes) } diff --git a/src/macho/MachO_test.go b/src/macho/MachO_test.go index df2d04c..f3cf5f9 100644 --- a/src/macho/MachO_test.go +++ b/src/macho/MachO_test.go @@ -1,12 +1,12 @@ package macho_test import ( - "io" "testing" "git.urbach.dev/cli/q/src/macho" + "git.urbach.dev/cli/q/src/test" ) func TestWrite(t *testing.T) { - macho.Write(io.Discard, nil, nil) + macho.Write(&test.Discard{}, nil, nil) } diff --git a/src/pe/EXE.go b/src/pe/EXE.go index 740d0a5..210208b 100644 --- a/src/pe/EXE.go +++ b/src/pe/EXE.go @@ -24,7 +24,7 @@ type EXE struct { } // Write writes the EXE file to the given writer. -func Write(writer io.Writer, codeBytes []byte, dataBytes []byte, dlls dll.List) { +func Write(writer io.WriteSeeker, codeBytes []byte, dataBytes []byte, dlls dll.List) { var ( sections = exe.MakeSections(HeaderEnd, codeBytes, dataBytes, nil) code = sections[0] @@ -144,10 +144,10 @@ func Write(writer io.Writer, codeBytes []byte, dataBytes []byte, dlls dll.List) binary.Write(writer, binary.LittleEndian, &pe.NTHeader) binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64) binary.Write(writer, binary.LittleEndian, &pe.Sections) - writer.Write(bytes.Repeat([]byte{0x00}, code.Padding)) + writer.Seek(int64(code.Padding), io.SeekCurrent) writer.Write(code.Bytes) - writer.Write(bytes.Repeat([]byte{0x00}, data.Padding)) + writer.Seek(int64(data.Padding), io.SeekCurrent) writer.Write(data.Bytes) - writer.Write(bytes.Repeat([]byte{0x00}, imports.Padding)) + writer.Seek(int64(imports.Padding), io.SeekCurrent) writer.Write(imports.Bytes) } diff --git a/src/pe/EXE_test.go b/src/pe/EXE_test.go index d69d46a..4059778 100644 --- a/src/pe/EXE_test.go +++ b/src/pe/EXE_test.go @@ -1,12 +1,12 @@ package pe_test import ( - "io" "testing" "git.urbach.dev/cli/q/src/pe" + "git.urbach.dev/cli/q/src/test" ) func TestWrite(t *testing.T) { - pe.Write(io.Discard, nil, nil, nil) + pe.Write(&test.Discard{}, nil, nil, nil) } diff --git a/src/readme.md b/src/readme.md index 83641ce..417d18c 100644 --- a/src/readme.md +++ b/src/readme.md @@ -26,6 +26,7 @@ - [scope](scope) - Defines a `Scope` used for code blocks - [set](set) - Generic set implementation - [sizeof](sizeof) - Calculates the byte size of numbers +- [test](test) - Testing utilities - [token](token) - Converts a file to tokens with the `Tokenize` function - [types](types) - Type system - [x86](x86) - x86-64 implementation diff --git a/src/test/Discard.go b/src/test/Discard.go new file mode 100644 index 0000000..62cf0c0 --- /dev/null +++ b/src/test/Discard.go @@ -0,0 +1,7 @@ +package test + +// Discard implements a no-op WriteSeeker. +type Discard struct{} + +func (w *Discard) Write(_ []byte) (int, error) { return 0, nil } +func (w *Discard) Seek(_ int64, _ int) (int64, error) { return 0, nil }